@hanzo/browser-extension 1.9.31 → 1.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/answer/AnswerEngine.tsx +553 -0
- package/src/manifest-firefox.json +10 -2
- package/src/manifest.json +17 -2
- package/src/newtab.css +277 -0
- package/src/newtab.html +13 -0
- package/src/popup.html +28 -0
- package/dist/browser-extension/README.md +0 -43
- package/dist/browser-extension/background-firefox.js +0 -4556
- package/dist/browser-extension/background.js +0 -7245
- package/dist/browser-extension/browser-control.js +0 -1317
- package/dist/browser-extension/chrome/ai-worker.js +0 -571
- package/dist/browser-extension/chrome/background.js +0 -7245
- package/dist/browser-extension/chrome/callback.html +0 -17
- package/dist/browser-extension/chrome/content-script.js +0 -2789
- package/dist/browser-extension/chrome/icon128.png +0 -0
- package/dist/browser-extension/chrome/icon16.png +0 -0
- package/dist/browser-extension/chrome/icon48.png +0 -0
- package/dist/browser-extension/chrome/manifest.json +0 -66
- package/dist/browser-extension/chrome/popup.css +0 -761
- package/dist/browser-extension/chrome/popup.html +0 -353
- package/dist/browser-extension/chrome/popup.js +0 -1650
- package/dist/browser-extension/chrome/sidebar.css +0 -1792
- package/dist/browser-extension/chrome/sidebar.html +0 -463
- package/dist/browser-extension/chrome/sidebar.js +0 -26541
- package/dist/browser-extension/cli.js +0 -233
- package/dist/browser-extension/firefox/ai-worker.js +0 -571
- package/dist/browser-extension/firefox/background.js +0 -4556
- package/dist/browser-extension/firefox/callback.html +0 -17
- package/dist/browser-extension/firefox/content-script.js +0 -2789
- package/dist/browser-extension/firefox/icon128.png +0 -0
- package/dist/browser-extension/firefox/icon16.png +0 -0
- package/dist/browser-extension/firefox/icon48.png +0 -0
- package/dist/browser-extension/firefox/manifest.json +0 -77
- package/dist/browser-extension/firefox/popup.css +0 -761
- package/dist/browser-extension/firefox/popup.html +0 -353
- package/dist/browser-extension/firefox/popup.js +0 -1650
- package/dist/browser-extension/firefox/sidebar.css +0 -1792
- package/dist/browser-extension/firefox/sidebar.html +0 -463
- package/dist/browser-extension/firefox/sidebar.js +0 -26541
- package/dist/browser-extension/icon128.png +0 -0
- package/dist/browser-extension/icon16.png +0 -0
- package/dist/browser-extension/icon48.png +0 -0
- package/dist/browser-extension/manifest.json +0 -66
- package/dist/browser-extension/package.json +0 -41
- package/dist/browser-extension/popup.js +0 -1650
- package/dist/browser-extension/safari/Info.plist +0 -21
- package/dist/browser-extension/safari/ai-worker.js +0 -571
- package/dist/browser-extension/safari/background.js +0 -7245
- package/dist/browser-extension/safari/callback.html +0 -17
- package/dist/browser-extension/safari/content-script.js +0 -2789
- package/dist/browser-extension/safari/icon128.png +0 -0
- package/dist/browser-extension/safari/icon16.png +0 -0
- package/dist/browser-extension/safari/icon48.png +0 -0
- package/dist/browser-extension/safari/popup.css +0 -761
- package/dist/browser-extension/safari/popup.html +0 -353
- package/dist/browser-extension/safari/popup.js +0 -1650
- package/dist/browser-extension/safari/sidebar.css +0 -1792
- package/dist/browser-extension/safari/sidebar.html +0 -463
- package/dist/browser-extension/safari/sidebar.js +0 -26541
- package/dist/browser-extension/sidebar.js +0 -26541
- package/dist/browser-extension/webgpu-ai.js +0 -568
|
@@ -1,1317 +0,0 @@
|
|
|
1
|
-
// src/browser-control.ts
|
|
2
|
-
var BrowserControl = class {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.tabFS = /* @__PURE__ */ new Map();
|
|
5
|
-
this.aiWorkers = /* @__PURE__ */ new Map();
|
|
6
|
-
this.agents = /* @__PURE__ */ new Map();
|
|
7
|
-
this.initializeTabFileSystem();
|
|
8
|
-
this.setupMessageHandlers();
|
|
9
|
-
}
|
|
10
|
-
initializeTabFileSystem() {
|
|
11
|
-
if (typeof chrome === "undefined" || !chrome.tabs) return;
|
|
12
|
-
chrome.tabs.query({}, (tabs) => {
|
|
13
|
-
tabs.forEach((tab, index) => {
|
|
14
|
-
if (tab.id && tab.url) {
|
|
15
|
-
const path = `/tabs/${index}/${this.sanitizePath(tab.title || "untitled")}`;
|
|
16
|
-
this.tabFS.set(path, {
|
|
17
|
-
path,
|
|
18
|
-
tabId: tab.id,
|
|
19
|
-
url: tab.url,
|
|
20
|
-
title: tab.title || "Untitled"
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
26
|
-
if (changeInfo.status === "complete") {
|
|
27
|
-
this.updateTabFS(tab);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
31
|
-
for (const [path, entry] of this.tabFS) {
|
|
32
|
-
if (entry.tabId === tabId) {
|
|
33
|
-
this.tabFS.delete(path);
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
sanitizePath(title) {
|
|
40
|
-
return title.replace(/[^a-zA-Z0-9-_]/g, "_").toLowerCase().substring(0, 60);
|
|
41
|
-
}
|
|
42
|
-
updateTabFS(tab) {
|
|
43
|
-
if (!tab.id || !tab.url) return;
|
|
44
|
-
const existingEntry = Array.from(this.tabFS.values()).find(
|
|
45
|
-
(entry) => entry.tabId === tab.id
|
|
46
|
-
);
|
|
47
|
-
const path = existingEntry?.path || `/tabs/${this.tabFS.size}/${this.sanitizePath(tab.title || "untitled")}`;
|
|
48
|
-
this.tabFS.set(path, {
|
|
49
|
-
path,
|
|
50
|
-
tabId: tab.id,
|
|
51
|
-
url: tab.url,
|
|
52
|
-
title: tab.title || "Untitled"
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
// ===========================================================================
|
|
56
|
-
// FUSE-like Tab Filesystem
|
|
57
|
-
// ===========================================================================
|
|
58
|
-
async readTab(path) {
|
|
59
|
-
const entry = this.tabFS.get(path);
|
|
60
|
-
if (!entry) throw new Error(`Tab not found: ${path}`);
|
|
61
|
-
return new Promise((resolve, reject) => {
|
|
62
|
-
chrome.tabs.sendMessage(entry.tabId, { action: "getContent" }, (response) => {
|
|
63
|
-
if (chrome.runtime.lastError) {
|
|
64
|
-
reject(new Error(chrome.runtime.lastError.message));
|
|
65
|
-
} else {
|
|
66
|
-
resolve(response?.content || "");
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async writeTab(path, content) {
|
|
72
|
-
const entry = this.tabFS.get(path);
|
|
73
|
-
if (!entry) throw new Error(`Tab not found: ${path}`);
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
chrome.tabs.sendMessage(entry.tabId, { action: "setContent", content }, (response) => {
|
|
76
|
-
if (chrome.runtime.lastError) {
|
|
77
|
-
reject(new Error(chrome.runtime.lastError.message));
|
|
78
|
-
} else {
|
|
79
|
-
resolve();
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
async listTabs() {
|
|
85
|
-
return Array.from(this.tabFS.keys());
|
|
86
|
-
}
|
|
87
|
-
// ===========================================================================
|
|
88
|
-
// Native Browser Actions
|
|
89
|
-
// ===========================================================================
|
|
90
|
-
async click(tabId, selector) {
|
|
91
|
-
return this.executeScript(tabId, `
|
|
92
|
-
(() => {
|
|
93
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
94
|
-
if (!el) return false;
|
|
95
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
96
|
-
el.click();
|
|
97
|
-
return true;
|
|
98
|
-
})()
|
|
99
|
-
`);
|
|
100
|
-
}
|
|
101
|
-
async clickAtPoint(tabId, x, y) {
|
|
102
|
-
return this.executeScript(tabId, `
|
|
103
|
-
(() => {
|
|
104
|
-
const el = document.elementFromPoint(${x}, ${y});
|
|
105
|
-
if (!el) return false;
|
|
106
|
-
el.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: ${x}, clientY: ${y} }));
|
|
107
|
-
return true;
|
|
108
|
-
})()
|
|
109
|
-
`);
|
|
110
|
-
}
|
|
111
|
-
async doubleClick(tabId, selector) {
|
|
112
|
-
return this.executeScript(tabId, `
|
|
113
|
-
(() => {
|
|
114
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
115
|
-
if (!el) return false;
|
|
116
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
117
|
-
el.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }));
|
|
118
|
-
return true;
|
|
119
|
-
})()
|
|
120
|
-
`);
|
|
121
|
-
}
|
|
122
|
-
async type(tabId, selector, text) {
|
|
123
|
-
return this.executeScript(tabId, `
|
|
124
|
-
(() => {
|
|
125
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
126
|
-
if (!el) return false;
|
|
127
|
-
el.focus();
|
|
128
|
-
for (const char of ${JSON.stringify(text)}) {
|
|
129
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true }));
|
|
130
|
-
el.dispatchEvent(new KeyboardEvent('keypress', { key: char, bubbles: true }));
|
|
131
|
-
if ('value' in el) el.value += char;
|
|
132
|
-
el.dispatchEvent(new InputEvent('input', { data: char, inputType: 'insertText', bubbles: true }));
|
|
133
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: char, bubbles: true }));
|
|
134
|
-
}
|
|
135
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
136
|
-
return true;
|
|
137
|
-
})()
|
|
138
|
-
`);
|
|
139
|
-
}
|
|
140
|
-
async fill(tabId, selector, value) {
|
|
141
|
-
return this.executeScript(tabId, `
|
|
142
|
-
(() => {
|
|
143
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
144
|
-
if (!el) return false;
|
|
145
|
-
el.focus();
|
|
146
|
-
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
147
|
-
Object.getPrototypeOf(el), 'value'
|
|
148
|
-
)?.set || Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
|
149
|
-
if (nativeSetter) {
|
|
150
|
-
nativeSetter.call(el, ${JSON.stringify(value)});
|
|
151
|
-
} else {
|
|
152
|
-
el.value = ${JSON.stringify(value)};
|
|
153
|
-
}
|
|
154
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
155
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
156
|
-
return true;
|
|
157
|
-
})()
|
|
158
|
-
`);
|
|
159
|
-
}
|
|
160
|
-
async select(tabId, selector, value) {
|
|
161
|
-
return this.executeScript(tabId, `
|
|
162
|
-
(() => {
|
|
163
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
164
|
-
if (!el || el.tagName !== 'SELECT') return false;
|
|
165
|
-
el.value = ${JSON.stringify(value)};
|
|
166
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
167
|
-
return true;
|
|
168
|
-
})()
|
|
169
|
-
`);
|
|
170
|
-
}
|
|
171
|
-
async check(tabId, selector, checked) {
|
|
172
|
-
return this.executeScript(tabId, `
|
|
173
|
-
(() => {
|
|
174
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
175
|
-
if (!el) return false;
|
|
176
|
-
if (el.checked !== ${checked}) el.click();
|
|
177
|
-
return true;
|
|
178
|
-
})()
|
|
179
|
-
`);
|
|
180
|
-
}
|
|
181
|
-
async hover(tabId, selector) {
|
|
182
|
-
return this.executeScript(tabId, `
|
|
183
|
-
(() => {
|
|
184
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
185
|
-
if (!el) return false;
|
|
186
|
-
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
187
|
-
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
|
|
188
|
-
return true;
|
|
189
|
-
})()
|
|
190
|
-
`);
|
|
191
|
-
}
|
|
192
|
-
async focus(tabId, selector) {
|
|
193
|
-
return this.executeScript(tabId, `
|
|
194
|
-
(() => {
|
|
195
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
196
|
-
if (!el) return false;
|
|
197
|
-
el.focus();
|
|
198
|
-
return true;
|
|
199
|
-
})()
|
|
200
|
-
`);
|
|
201
|
-
}
|
|
202
|
-
async blur(tabId, selector) {
|
|
203
|
-
return this.executeScript(tabId, `
|
|
204
|
-
(() => {
|
|
205
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
206
|
-
if (!el) return false;
|
|
207
|
-
el.blur();
|
|
208
|
-
return true;
|
|
209
|
-
})()
|
|
210
|
-
`);
|
|
211
|
-
}
|
|
212
|
-
async scroll(tabId, x, y, selector) {
|
|
213
|
-
if (selector) {
|
|
214
|
-
return this.executeScript(tabId, `
|
|
215
|
-
(() => {
|
|
216
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
217
|
-
if (!el) return false;
|
|
218
|
-
el.scrollBy({ left: ${x}, top: ${y}, behavior: 'smooth' });
|
|
219
|
-
return true;
|
|
220
|
-
})()
|
|
221
|
-
`);
|
|
222
|
-
}
|
|
223
|
-
return this.executeScript(tabId, `
|
|
224
|
-
(() => { window.scrollBy({ left: ${x}, top: ${y}, behavior: 'smooth' }); return true; })()
|
|
225
|
-
`);
|
|
226
|
-
}
|
|
227
|
-
async scrollIntoView(tabId, selector) {
|
|
228
|
-
return this.executeScript(tabId, `
|
|
229
|
-
(() => {
|
|
230
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
231
|
-
if (!el) return false;
|
|
232
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
233
|
-
return true;
|
|
234
|
-
})()
|
|
235
|
-
`);
|
|
236
|
-
}
|
|
237
|
-
async pressKey(tabId, key, modifiers = {}) {
|
|
238
|
-
return this.executeScript(tabId, `
|
|
239
|
-
(() => {
|
|
240
|
-
const opts = {
|
|
241
|
-
key: ${JSON.stringify(key)},
|
|
242
|
-
code: ${JSON.stringify(key)},
|
|
243
|
-
bubbles: true,
|
|
244
|
-
ctrlKey: ${!!modifiers.ctrl},
|
|
245
|
-
shiftKey: ${!!modifiers.shift},
|
|
246
|
-
altKey: ${!!modifiers.alt},
|
|
247
|
-
metaKey: ${!!modifiers.meta},
|
|
248
|
-
};
|
|
249
|
-
document.activeElement.dispatchEvent(new KeyboardEvent('keydown', opts));
|
|
250
|
-
document.activeElement.dispatchEvent(new KeyboardEvent('keypress', opts));
|
|
251
|
-
document.activeElement.dispatchEvent(new KeyboardEvent('keyup', opts));
|
|
252
|
-
return true;
|
|
253
|
-
})()
|
|
254
|
-
`);
|
|
255
|
-
}
|
|
256
|
-
async drag(tabId, fromSelector, toSelector) {
|
|
257
|
-
return this.executeScript(tabId, `
|
|
258
|
-
(() => {
|
|
259
|
-
const from = document.querySelector(${JSON.stringify(fromSelector)});
|
|
260
|
-
const to = document.querySelector(${JSON.stringify(toSelector)});
|
|
261
|
-
if (!from || !to) return false;
|
|
262
|
-
const fromRect = from.getBoundingClientRect();
|
|
263
|
-
const toRect = to.getBoundingClientRect();
|
|
264
|
-
const fromX = fromRect.left + fromRect.width / 2;
|
|
265
|
-
const fromY = fromRect.top + fromRect.height / 2;
|
|
266
|
-
const toX = toRect.left + toRect.width / 2;
|
|
267
|
-
const toY = toRect.top + toRect.height / 2;
|
|
268
|
-
from.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: fromX, clientY: fromY }));
|
|
269
|
-
from.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: toX, clientY: toY }));
|
|
270
|
-
to.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: toX, clientY: toY }));
|
|
271
|
-
to.dispatchEvent(new DragEvent('drop', { bubbles: true }));
|
|
272
|
-
return true;
|
|
273
|
-
})()
|
|
274
|
-
`);
|
|
275
|
-
}
|
|
276
|
-
async waitForSelector(tabId, selector, timeoutMs = 1e4) {
|
|
277
|
-
return this.executeScript(tabId, `
|
|
278
|
-
new Promise((resolve) => {
|
|
279
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
280
|
-
if (el) { resolve(true); return; }
|
|
281
|
-
const observer = new MutationObserver(() => {
|
|
282
|
-
if (document.querySelector(${JSON.stringify(selector)})) {
|
|
283
|
-
observer.disconnect();
|
|
284
|
-
resolve(true);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
288
|
-
setTimeout(() => { observer.disconnect(); resolve(false); }, ${timeoutMs});
|
|
289
|
-
})
|
|
290
|
-
`);
|
|
291
|
-
}
|
|
292
|
-
async waitForNavigation(tabId, timeoutMs = 3e4) {
|
|
293
|
-
return new Promise((resolve) => {
|
|
294
|
-
const timer = setTimeout(() => {
|
|
295
|
-
chrome.tabs.onUpdated.removeListener(listener);
|
|
296
|
-
resolve(false);
|
|
297
|
-
}, timeoutMs);
|
|
298
|
-
const listener = (updatedTabId, changeInfo) => {
|
|
299
|
-
if (updatedTabId === tabId && changeInfo.status === "complete") {
|
|
300
|
-
clearTimeout(timer);
|
|
301
|
-
chrome.tabs.onUpdated.removeListener(listener);
|
|
302
|
-
resolve(true);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
chrome.tabs.onUpdated.addListener(listener);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async evaluate(tabId, expression) {
|
|
309
|
-
return this.executeScript(tabId, expression);
|
|
310
|
-
}
|
|
311
|
-
async getElementInfo(tabId, selector) {
|
|
312
|
-
return this.executeScript(tabId, `
|
|
313
|
-
(() => {
|
|
314
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
315
|
-
if (!el) return null;
|
|
316
|
-
const rect = el.getBoundingClientRect();
|
|
317
|
-
const styles = window.getComputedStyle(el);
|
|
318
|
-
return {
|
|
319
|
-
tagName: el.tagName.toLowerCase(),
|
|
320
|
-
id: el.id,
|
|
321
|
-
className: el.className,
|
|
322
|
-
textContent: el.textContent?.substring(0, 500),
|
|
323
|
-
innerText: el.innerText?.substring(0, 500),
|
|
324
|
-
value: el.value,
|
|
325
|
-
href: el.href,
|
|
326
|
-
src: el.src,
|
|
327
|
-
type: el.type,
|
|
328
|
-
checked: el.checked,
|
|
329
|
-
disabled: el.disabled,
|
|
330
|
-
visible: styles.display !== 'none' && styles.visibility !== 'hidden' && rect.width > 0 && rect.height > 0,
|
|
331
|
-
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
332
|
-
attributes: Object.fromEntries(Array.from(el.attributes).map(a => [a.name, a.value])),
|
|
333
|
-
};
|
|
334
|
-
})()
|
|
335
|
-
`);
|
|
336
|
-
}
|
|
337
|
-
async getPageInfo(tabId) {
|
|
338
|
-
return this.executeScript(tabId, `
|
|
339
|
-
(() => ({
|
|
340
|
-
url: location.href,
|
|
341
|
-
title: document.title,
|
|
342
|
-
readyState: document.readyState,
|
|
343
|
-
scrollX: window.scrollX,
|
|
344
|
-
scrollY: window.scrollY,
|
|
345
|
-
innerWidth: window.innerWidth,
|
|
346
|
-
innerHeight: window.innerHeight,
|
|
347
|
-
documentWidth: document.documentElement.scrollWidth,
|
|
348
|
-
documentHeight: document.documentElement.scrollHeight,
|
|
349
|
-
forms: document.forms.length,
|
|
350
|
-
links: document.links.length,
|
|
351
|
-
images: document.images.length,
|
|
352
|
-
}))()
|
|
353
|
-
`);
|
|
354
|
-
}
|
|
355
|
-
async querySelectorAll(tabId, selector) {
|
|
356
|
-
return this.executeScript(tabId, `
|
|
357
|
-
(() => Array.from(document.querySelectorAll(${JSON.stringify(selector)})).slice(0, 100).map((el, i) => ({
|
|
358
|
-
index: i,
|
|
359
|
-
tagName: el.tagName.toLowerCase(),
|
|
360
|
-
id: el.id,
|
|
361
|
-
className: el.className,
|
|
362
|
-
textContent: el.textContent?.substring(0, 200),
|
|
363
|
-
visible: el.offsetWidth > 0 && el.offsetHeight > 0,
|
|
364
|
-
})))()
|
|
365
|
-
`);
|
|
366
|
-
}
|
|
367
|
-
// ===========================================================================
|
|
368
|
-
// DOM Read/Write/Observe
|
|
369
|
-
// ===========================================================================
|
|
370
|
-
async getHTML(tabId, selector, outer = true) {
|
|
371
|
-
return this.executeScript(tabId, `
|
|
372
|
-
(() => {
|
|
373
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
374
|
-
return el ? (${outer} ? el.outerHTML : el.innerHTML) : null;
|
|
375
|
-
})()
|
|
376
|
-
`);
|
|
377
|
-
}
|
|
378
|
-
async setHTML(tabId, selector, html, outer = false) {
|
|
379
|
-
return this.executeScript(tabId, `
|
|
380
|
-
(() => {
|
|
381
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
382
|
-
if (!el) return false;
|
|
383
|
-
${outer ? `el.outerHTML = ${JSON.stringify(html)};` : `el.innerHTML = ${JSON.stringify(html)};`}
|
|
384
|
-
return true;
|
|
385
|
-
})()
|
|
386
|
-
`);
|
|
387
|
-
}
|
|
388
|
-
async getText(tabId, selector) {
|
|
389
|
-
return this.executeScript(tabId, `
|
|
390
|
-
(() => {
|
|
391
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
392
|
-
return el ? el.textContent : null;
|
|
393
|
-
})()
|
|
394
|
-
`);
|
|
395
|
-
}
|
|
396
|
-
async setText(tabId, selector, text) {
|
|
397
|
-
return this.executeScript(tabId, `
|
|
398
|
-
(() => {
|
|
399
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
400
|
-
if (!el) return false;
|
|
401
|
-
el.textContent = ${JSON.stringify(text)};
|
|
402
|
-
return true;
|
|
403
|
-
})()
|
|
404
|
-
`);
|
|
405
|
-
}
|
|
406
|
-
async getAttribute(tabId, selector, attr) {
|
|
407
|
-
return this.executeScript(tabId, `
|
|
408
|
-
document.querySelector(${JSON.stringify(selector)})?.getAttribute(${JSON.stringify(attr)}) ?? null
|
|
409
|
-
`);
|
|
410
|
-
}
|
|
411
|
-
async setAttribute(tabId, selector, attr, value) {
|
|
412
|
-
return this.executeScript(tabId, `
|
|
413
|
-
(() => {
|
|
414
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
415
|
-
if (!el) return false;
|
|
416
|
-
el.setAttribute(${JSON.stringify(attr)}, ${JSON.stringify(value)});
|
|
417
|
-
return true;
|
|
418
|
-
})()
|
|
419
|
-
`);
|
|
420
|
-
}
|
|
421
|
-
async removeAttribute(tabId, selector, attr) {
|
|
422
|
-
return this.executeScript(tabId, `
|
|
423
|
-
(() => {
|
|
424
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
425
|
-
if (!el) return false;
|
|
426
|
-
el.removeAttribute(${JSON.stringify(attr)});
|
|
427
|
-
return true;
|
|
428
|
-
})()
|
|
429
|
-
`);
|
|
430
|
-
}
|
|
431
|
-
async setStyle(tabId, selector, styles) {
|
|
432
|
-
return this.executeScript(tabId, `
|
|
433
|
-
(() => {
|
|
434
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
435
|
-
if (!el) return false;
|
|
436
|
-
Object.assign(el.style, ${JSON.stringify(styles)});
|
|
437
|
-
return true;
|
|
438
|
-
})()
|
|
439
|
-
`);
|
|
440
|
-
}
|
|
441
|
-
async addClass(tabId, selector, ...classNames) {
|
|
442
|
-
return this.executeScript(tabId, `
|
|
443
|
-
(() => {
|
|
444
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
445
|
-
if (!el) return false;
|
|
446
|
-
el.classList.add(${classNames.map((c) => JSON.stringify(c)).join(",")});
|
|
447
|
-
return true;
|
|
448
|
-
})()
|
|
449
|
-
`);
|
|
450
|
-
}
|
|
451
|
-
async removeClass(tabId, selector, ...classNames) {
|
|
452
|
-
return this.executeScript(tabId, `
|
|
453
|
-
(() => {
|
|
454
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
455
|
-
if (!el) return false;
|
|
456
|
-
el.classList.remove(${classNames.map((c) => JSON.stringify(c)).join(",")});
|
|
457
|
-
return true;
|
|
458
|
-
})()
|
|
459
|
-
`);
|
|
460
|
-
}
|
|
461
|
-
async insertElement(tabId, parentSelector, html, position = "beforeend") {
|
|
462
|
-
return this.executeScript(tabId, `
|
|
463
|
-
(() => {
|
|
464
|
-
const parent = document.querySelector(${JSON.stringify(parentSelector)});
|
|
465
|
-
if (!parent) return false;
|
|
466
|
-
parent.insertAdjacentHTML(${JSON.stringify(position)}, ${JSON.stringify(html)});
|
|
467
|
-
return true;
|
|
468
|
-
})()
|
|
469
|
-
`);
|
|
470
|
-
}
|
|
471
|
-
async removeElement(tabId, selector) {
|
|
472
|
-
return this.executeScript(tabId, `
|
|
473
|
-
(() => {
|
|
474
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
475
|
-
if (!el) return false;
|
|
476
|
-
el.remove();
|
|
477
|
-
return true;
|
|
478
|
-
})()
|
|
479
|
-
`);
|
|
480
|
-
}
|
|
481
|
-
async observeMutations(tabId, selector, options = {}, durationMs = 5e3) {
|
|
482
|
-
const opts = { childList: true, subtree: true, attributes: true, ...options };
|
|
483
|
-
return this.executeScript(tabId, `
|
|
484
|
-
new Promise((resolve) => {
|
|
485
|
-
const target = ${selector === "document" ? "document.documentElement" : `document.querySelector(${JSON.stringify(selector)})`};
|
|
486
|
-
if (!target) { resolve([]); return; }
|
|
487
|
-
const mutations = [];
|
|
488
|
-
const observer = new MutationObserver((records) => {
|
|
489
|
-
for (const r of records) {
|
|
490
|
-
mutations.push({
|
|
491
|
-
type: r.type,
|
|
492
|
-
target: r.target.nodeName + (r.target.id ? '#' + r.target.id : ''),
|
|
493
|
-
added: r.addedNodes.length,
|
|
494
|
-
removed: r.removedNodes.length,
|
|
495
|
-
attribute: r.attributeName || null,
|
|
496
|
-
oldValue: r.oldValue?.substring(0, 200) || null,
|
|
497
|
-
});
|
|
498
|
-
if (mutations.length >= 200) { observer.disconnect(); resolve(mutations); }
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
observer.observe(target, ${JSON.stringify(opts)});
|
|
502
|
-
setTimeout(() => { observer.disconnect(); resolve(mutations); }, ${durationMs});
|
|
503
|
-
})
|
|
504
|
-
`);
|
|
505
|
-
}
|
|
506
|
-
async getComputedStyles(tabId, selector, properties) {
|
|
507
|
-
return this.executeScript(tabId, `
|
|
508
|
-
(() => {
|
|
509
|
-
const el = document.querySelector(${JSON.stringify(selector)});
|
|
510
|
-
if (!el) return null;
|
|
511
|
-
const cs = window.getComputedStyle(el);
|
|
512
|
-
${properties ? `return Object.fromEntries(${JSON.stringify(properties)}.map(p => [p, cs.getPropertyValue(p)]));` : `
|
|
513
|
-
const props = ['display','visibility','opacity','position','width','height','margin','padding',
|
|
514
|
-
'color','background','font-size','font-weight','border','overflow','z-index','transform'];
|
|
515
|
-
return Object.fromEntries(props.map(p => [p, cs.getPropertyValue(p)]));`}
|
|
516
|
-
})()
|
|
517
|
-
`);
|
|
518
|
-
}
|
|
519
|
-
async getBoundingRects(tabId, selector) {
|
|
520
|
-
return this.executeScript(tabId, `
|
|
521
|
-
Array.from(document.querySelectorAll(${JSON.stringify(selector)})).slice(0, 50).map(el => {
|
|
522
|
-
const r = el.getBoundingClientRect();
|
|
523
|
-
return { tag: el.tagName.toLowerCase(), id: el.id, x: r.x, y: r.y, w: r.width, h: r.height };
|
|
524
|
-
})
|
|
525
|
-
`);
|
|
526
|
-
}
|
|
527
|
-
async injectScript(tabId, url) {
|
|
528
|
-
return this.executeScript(tabId, `
|
|
529
|
-
new Promise((resolve) => {
|
|
530
|
-
const s = document.createElement('script');
|
|
531
|
-
s.src = ${JSON.stringify(url)};
|
|
532
|
-
s.onload = () => resolve(true);
|
|
533
|
-
s.onerror = () => resolve(false);
|
|
534
|
-
document.head.appendChild(s);
|
|
535
|
-
})
|
|
536
|
-
`);
|
|
537
|
-
}
|
|
538
|
-
async injectCSS(tabId, css) {
|
|
539
|
-
return this.executeScript(tabId, `
|
|
540
|
-
(() => {
|
|
541
|
-
const style = document.createElement('style');
|
|
542
|
-
style.textContent = ${JSON.stringify(css)};
|
|
543
|
-
document.head.appendChild(style);
|
|
544
|
-
return true;
|
|
545
|
-
})()
|
|
546
|
-
`);
|
|
547
|
-
}
|
|
548
|
-
async getLocalStorage(tabId, key) {
|
|
549
|
-
if (key) {
|
|
550
|
-
return this.executeScript(tabId, `localStorage.getItem(${JSON.stringify(key)})`);
|
|
551
|
-
}
|
|
552
|
-
return this.executeScript(tabId, `JSON.parse(JSON.stringify(localStorage))`);
|
|
553
|
-
}
|
|
554
|
-
async setLocalStorage(tabId, key, value) {
|
|
555
|
-
return this.executeScript(tabId, `
|
|
556
|
-
(() => { localStorage.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)}); return true; })()
|
|
557
|
-
`);
|
|
558
|
-
}
|
|
559
|
-
async getCookies(tabId) {
|
|
560
|
-
return new Promise((resolve) => {
|
|
561
|
-
chrome.tabs.get(tabId, (tab) => {
|
|
562
|
-
if (!tab?.url) {
|
|
563
|
-
resolve([]);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
chrome.cookies.getAll({ url: tab.url }, (cookies) => {
|
|
567
|
-
resolve(cookies || []);
|
|
568
|
-
});
|
|
569
|
-
});
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
async setCookie(tabId, cookie) {
|
|
573
|
-
return new Promise((resolve) => {
|
|
574
|
-
chrome.tabs.get(tabId, (tab) => {
|
|
575
|
-
if (!tab?.url) {
|
|
576
|
-
resolve(false);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
chrome.cookies.set({ url: tab.url, ...cookie }, (c) => resolve(!!c));
|
|
580
|
-
});
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
async deleteCookie(tabId, name) {
|
|
584
|
-
return new Promise((resolve) => {
|
|
585
|
-
chrome.tabs.get(tabId, (tab) => {
|
|
586
|
-
if (!tab?.url) {
|
|
587
|
-
resolve(false);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
chrome.cookies.remove({ url: tab.url, name }, () => resolve(true));
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
async getSessionStorage(tabId, key) {
|
|
595
|
-
if (key) {
|
|
596
|
-
return this.executeScript(tabId, `sessionStorage.getItem(${JSON.stringify(key)})`);
|
|
597
|
-
}
|
|
598
|
-
return this.executeScript(tabId, `JSON.parse(JSON.stringify(sessionStorage))`);
|
|
599
|
-
}
|
|
600
|
-
async setSessionStorage(tabId, key, value) {
|
|
601
|
-
return this.executeScript(tabId, `
|
|
602
|
-
(() => { sessionStorage.setItem(${JSON.stringify(key)}, ${JSON.stringify(value)}); return true; })()
|
|
603
|
-
`);
|
|
604
|
-
}
|
|
605
|
-
async getIndexedDBDatabases(tabId) {
|
|
606
|
-
return this.executeScript(tabId, `
|
|
607
|
-
(async () => {
|
|
608
|
-
const dbs = await indexedDB.databases();
|
|
609
|
-
return dbs.map(db => ({ name: db.name, version: db.version }));
|
|
610
|
-
})()
|
|
611
|
-
`);
|
|
612
|
-
}
|
|
613
|
-
async queryIndexedDB(tabId, dbName, storeName, query) {
|
|
614
|
-
const count = query?.count || 100;
|
|
615
|
-
return this.executeScript(tabId, `
|
|
616
|
-
new Promise((resolve, reject) => {
|
|
617
|
-
const req = indexedDB.open(${JSON.stringify(dbName)});
|
|
618
|
-
req.onerror = () => reject(new Error(req.error?.message || 'Failed to open DB'));
|
|
619
|
-
req.onsuccess = () => {
|
|
620
|
-
const db = req.result;
|
|
621
|
-
try {
|
|
622
|
-
const tx = db.transaction(${JSON.stringify(storeName)}, 'readonly');
|
|
623
|
-
const store = tx.objectStore(${JSON.stringify(storeName)});
|
|
624
|
-
const src = ${query?.index ? `store.index(${JSON.stringify(query.index)})` : "store"};
|
|
625
|
-
const getReq = ${query?.key ? `src.get(${JSON.stringify(query.key)})` : `src.getAll(null, ${count})`};
|
|
626
|
-
getReq.onsuccess = () => resolve(${query?.key ? "[getReq.result]" : "getReq.result"});
|
|
627
|
-
getReq.onerror = () => reject(new Error(getReq.error?.message || 'Query failed'));
|
|
628
|
-
} catch(e) { reject(e); }
|
|
629
|
-
};
|
|
630
|
-
})
|
|
631
|
-
`);
|
|
632
|
-
}
|
|
633
|
-
async getIndexedDBStores(tabId, dbName) {
|
|
634
|
-
return this.executeScript(tabId, `
|
|
635
|
-
new Promise((resolve, reject) => {
|
|
636
|
-
const req = indexedDB.open(${JSON.stringify(dbName)});
|
|
637
|
-
req.onerror = () => reject(new Error(req.error?.message || 'Failed to open DB'));
|
|
638
|
-
req.onsuccess = () => {
|
|
639
|
-
resolve(Array.from(req.result.objectStoreNames));
|
|
640
|
-
};
|
|
641
|
-
})
|
|
642
|
-
`);
|
|
643
|
-
}
|
|
644
|
-
async clearSiteData(tabId, what = ["all"]) {
|
|
645
|
-
const doAll = what.includes("all");
|
|
646
|
-
const results = {};
|
|
647
|
-
if (doAll || what.includes("cookies")) {
|
|
648
|
-
await new Promise((resolve) => {
|
|
649
|
-
chrome.tabs.get(tabId, (tab) => {
|
|
650
|
-
if (!tab?.url) {
|
|
651
|
-
results.cookies = false;
|
|
652
|
-
resolve();
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
chrome.cookies.getAll({ url: tab.url }, (cookies) => {
|
|
656
|
-
Promise.all((cookies || []).map(
|
|
657
|
-
(c) => new Promise((r) => chrome.cookies.remove({ url: tab.url, name: c.name }, () => r()))
|
|
658
|
-
)).then(() => {
|
|
659
|
-
results.cookies = true;
|
|
660
|
-
resolve();
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
if (doAll || what.includes("localStorage")) {
|
|
667
|
-
results.localStorage = await this.executeScript(tabId, `(() => { localStorage.clear(); return true; })()`);
|
|
668
|
-
}
|
|
669
|
-
if (doAll || what.includes("sessionStorage")) {
|
|
670
|
-
results.sessionStorage = await this.executeScript(tabId, `(() => { sessionStorage.clear(); return true; })()`);
|
|
671
|
-
}
|
|
672
|
-
if (doAll || what.includes("indexedDB")) {
|
|
673
|
-
results.indexedDB = await this.executeScript(tabId, `
|
|
674
|
-
(async () => {
|
|
675
|
-
const dbs = await indexedDB.databases();
|
|
676
|
-
await Promise.all(dbs.map(db => new Promise((r, j) => {
|
|
677
|
-
const req = indexedDB.deleteDatabase(db.name);
|
|
678
|
-
req.onsuccess = () => r(true);
|
|
679
|
-
req.onerror = () => r(false);
|
|
680
|
-
})));
|
|
681
|
-
return true;
|
|
682
|
-
})()
|
|
683
|
-
`);
|
|
684
|
-
}
|
|
685
|
-
if (doAll || what.includes("cache")) {
|
|
686
|
-
results.cache = await this.executeScript(tabId, `
|
|
687
|
-
(async () => {
|
|
688
|
-
const keys = await caches.keys();
|
|
689
|
-
await Promise.all(keys.map(k => caches.delete(k)));
|
|
690
|
-
return true;
|
|
691
|
-
})()
|
|
692
|
-
`);
|
|
693
|
-
}
|
|
694
|
-
return results;
|
|
695
|
-
}
|
|
696
|
-
async getCacheStorageKeys(tabId) {
|
|
697
|
-
return this.executeScript(tabId, `caches.keys()`);
|
|
698
|
-
}
|
|
699
|
-
async checkWebGPU(tabId) {
|
|
700
|
-
return this.executeScript(tabId, `
|
|
701
|
-
(async () => {
|
|
702
|
-
if (!navigator.gpu) return { available: false, reason: 'WebGPU not supported' };
|
|
703
|
-
try {
|
|
704
|
-
const adapter = await navigator.gpu.requestAdapter();
|
|
705
|
-
if (!adapter) return { available: false, reason: 'No GPU adapter' };
|
|
706
|
-
const info = await adapter.requestAdapterInfo();
|
|
707
|
-
return {
|
|
708
|
-
available: true,
|
|
709
|
-
adapter: info.description || info.device || info.vendor || 'Unknown',
|
|
710
|
-
vendor: info.vendor,
|
|
711
|
-
architecture: info.architecture,
|
|
712
|
-
features: [...adapter.features].sort(),
|
|
713
|
-
limits: {
|
|
714
|
-
maxBufferSize: adapter.limits.maxBufferSize,
|
|
715
|
-
maxTextureDimension2D: adapter.limits.maxTextureDimension2D,
|
|
716
|
-
maxComputeWorkgroupSizeX: adapter.limits.maxComputeWorkgroupSizeX,
|
|
717
|
-
maxBindGroups: adapter.limits.maxBindGroups,
|
|
718
|
-
maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,
|
|
719
|
-
},
|
|
720
|
-
};
|
|
721
|
-
} catch(e) { return { available: false, reason: e.message }; }
|
|
722
|
-
})()
|
|
723
|
-
`);
|
|
724
|
-
}
|
|
725
|
-
async hardReload(tabId) {
|
|
726
|
-
chrome.tabs.reload(tabId, { bypassCache: true });
|
|
727
|
-
}
|
|
728
|
-
async getServiceWorkers(tabId) {
|
|
729
|
-
return this.executeScript(tabId, `
|
|
730
|
-
(async () => {
|
|
731
|
-
const regs = await navigator.serviceWorker?.getRegistrations() || [];
|
|
732
|
-
return regs.map(r => ({
|
|
733
|
-
scope: r.scope,
|
|
734
|
-
active: r.active ? { state: r.active.state, scriptURL: r.active.scriptURL } : null,
|
|
735
|
-
waiting: r.waiting ? { state: r.waiting.state } : null,
|
|
736
|
-
installing: r.installing ? { state: r.installing.state } : null,
|
|
737
|
-
}));
|
|
738
|
-
})()
|
|
739
|
-
`);
|
|
740
|
-
}
|
|
741
|
-
async getPerformanceMetrics(tabId) {
|
|
742
|
-
return this.executeScript(tabId, `
|
|
743
|
-
(() => {
|
|
744
|
-
const nav = performance.getEntriesByType('navigation')[0] || {};
|
|
745
|
-
const paint = performance.getEntriesByType('paint');
|
|
746
|
-
const resources = performance.getEntriesByType('resource');
|
|
747
|
-
return {
|
|
748
|
-
timing: {
|
|
749
|
-
domContentLoaded: Math.round(nav.domContentLoadedEventEnd - nav.startTime),
|
|
750
|
-
load: Math.round(nav.loadEventEnd - nav.startTime),
|
|
751
|
-
ttfb: Math.round(nav.responseStart - nav.startTime),
|
|
752
|
-
domInteractive: Math.round(nav.domInteractive - nav.startTime),
|
|
753
|
-
},
|
|
754
|
-
paint: Object.fromEntries(paint.map(p => [p.name, Math.round(p.startTime)])),
|
|
755
|
-
resourceCount: resources.length,
|
|
756
|
-
transferSize: resources.reduce((s, r) => s + (r.transferSize || 0), 0),
|
|
757
|
-
memory: performance.memory ? {
|
|
758
|
-
usedJSHeapSize: performance.memory.usedJSHeapSize,
|
|
759
|
-
totalJSHeapSize: performance.memory.totalJSHeapSize,
|
|
760
|
-
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
|
|
761
|
-
} : null,
|
|
762
|
-
};
|
|
763
|
-
})()
|
|
764
|
-
`);
|
|
765
|
-
}
|
|
766
|
-
// ===========================================================================
|
|
767
|
-
// Navigation
|
|
768
|
-
// ===========================================================================
|
|
769
|
-
async navigateTo(tabId, url) {
|
|
770
|
-
chrome.tabs.update(tabId, { url });
|
|
771
|
-
}
|
|
772
|
-
async reload(tabId) {
|
|
773
|
-
chrome.tabs.reload(tabId);
|
|
774
|
-
}
|
|
775
|
-
async goBack(tabId) {
|
|
776
|
-
chrome.tabs.goBack(tabId);
|
|
777
|
-
}
|
|
778
|
-
async goForward(tabId) {
|
|
779
|
-
chrome.tabs.goForward(tabId);
|
|
780
|
-
}
|
|
781
|
-
async createTab(url, active = true) {
|
|
782
|
-
return new Promise((resolve) => {
|
|
783
|
-
chrome.tabs.create({ url, active }, (tab) => {
|
|
784
|
-
resolve(tab.id);
|
|
785
|
-
});
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
async getURL(tabId) {
|
|
789
|
-
return new Promise((resolve) => {
|
|
790
|
-
chrome.tabs.get(tabId, (tab) => resolve(tab?.url || ""));
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
async getTitle(tabId) {
|
|
794
|
-
return new Promise((resolve) => {
|
|
795
|
-
chrome.tabs.get(tabId, (tab) => resolve(tab?.title || ""));
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
async getTabInfo(tabId) {
|
|
799
|
-
return new Promise((resolve) => {
|
|
800
|
-
chrome.tabs.get(tabId, (tab) => resolve({
|
|
801
|
-
id: tab?.id,
|
|
802
|
-
url: tab?.url,
|
|
803
|
-
title: tab?.title,
|
|
804
|
-
status: tab?.status,
|
|
805
|
-
active: tab?.active,
|
|
806
|
-
index: tab?.index,
|
|
807
|
-
pinned: tab?.pinned,
|
|
808
|
-
incognito: tab?.incognito
|
|
809
|
-
}));
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
async getHistory(tabId, maxResults = 50) {
|
|
813
|
-
return this.executeScript(tabId, `
|
|
814
|
-
(() => {
|
|
815
|
-
const entries = performance.getEntriesByType('navigation');
|
|
816
|
-
return {
|
|
817
|
-
length: history.length,
|
|
818
|
-
scrollRestoration: history.scrollRestoration,
|
|
819
|
-
current: location.href,
|
|
820
|
-
navigation: entries.map(e => ({
|
|
821
|
-
type: e.type,
|
|
822
|
-
redirectCount: e.redirectCount,
|
|
823
|
-
duration: Math.round(e.duration),
|
|
824
|
-
})),
|
|
825
|
-
};
|
|
826
|
-
})()
|
|
827
|
-
`);
|
|
828
|
-
}
|
|
829
|
-
async browserFetch(tabId, url, options) {
|
|
830
|
-
const opts = {
|
|
831
|
-
method: options?.method || "GET",
|
|
832
|
-
headers: options?.headers || {},
|
|
833
|
-
body: options?.body,
|
|
834
|
-
mode: options?.mode || "cors",
|
|
835
|
-
credentials: options?.credentials || "include"
|
|
836
|
-
};
|
|
837
|
-
return this.executeScript(tabId, `
|
|
838
|
-
(async () => {
|
|
839
|
-
const opts = ${JSON.stringify(opts)};
|
|
840
|
-
if (!opts.body) delete opts.body;
|
|
841
|
-
const res = await fetch(${JSON.stringify(url)}, opts);
|
|
842
|
-
const headers = {};
|
|
843
|
-
res.headers.forEach((v, k) => headers[k] = v);
|
|
844
|
-
const ct = res.headers.get('content-type') || '';
|
|
845
|
-
let body;
|
|
846
|
-
if (ct.includes('json')) {
|
|
847
|
-
body = JSON.stringify(await res.json());
|
|
848
|
-
} else {
|
|
849
|
-
body = await res.text();
|
|
850
|
-
}
|
|
851
|
-
if (body.length > 500000) body = body.substring(0, 500000) + '...(truncated)';
|
|
852
|
-
return { status: res.status, statusText: res.statusText, headers, body, url: res.url };
|
|
853
|
-
})()
|
|
854
|
-
`);
|
|
855
|
-
}
|
|
856
|
-
async closeTab(tabId) {
|
|
857
|
-
chrome.tabs.remove(tabId);
|
|
858
|
-
}
|
|
859
|
-
// ===========================================================================
|
|
860
|
-
// Screenshots
|
|
861
|
-
// ===========================================================================
|
|
862
|
-
async captureScreenshot(tabId, options) {
|
|
863
|
-
return new Promise((resolve, reject) => {
|
|
864
|
-
const format = options?.format || "jpeg";
|
|
865
|
-
const quality = options?.quality || (format === "jpeg" ? 80 : void 0);
|
|
866
|
-
const captureOpts = { format };
|
|
867
|
-
if (quality !== void 0) captureOpts.quality = quality;
|
|
868
|
-
chrome.tabs.captureVisibleTab(captureOpts, (dataUrl) => {
|
|
869
|
-
if (chrome.runtime.lastError) {
|
|
870
|
-
reject(new Error(chrome.runtime.lastError.message));
|
|
871
|
-
} else {
|
|
872
|
-
resolve(dataUrl);
|
|
873
|
-
}
|
|
874
|
-
});
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
async captureFullPage(tabId) {
|
|
878
|
-
const pageInfo = await this.getPageInfo(tabId);
|
|
879
|
-
return this.captureScreenshot(tabId, { format: "png" });
|
|
880
|
-
}
|
|
881
|
-
// ===========================================================================
|
|
882
|
-
// AI Worker Management
|
|
883
|
-
// ===========================================================================
|
|
884
|
-
async launchAIWorker(tabId, modelName) {
|
|
885
|
-
const worker = new Worker(new URL("./ai-worker.js", import.meta.url), {
|
|
886
|
-
type: "module"
|
|
887
|
-
});
|
|
888
|
-
const agentId = `agent-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`;
|
|
889
|
-
worker.postMessage({ type: "init", payload: { tabId, modelName } });
|
|
890
|
-
worker.onmessage = (event) => {
|
|
891
|
-
this.handleAIWorkerMessage(tabId, agentId, event.data);
|
|
892
|
-
};
|
|
893
|
-
worker.onerror = (event) => {
|
|
894
|
-
console.error(`[Hanzo] AI worker error for tab ${tabId}:`, event.message);
|
|
895
|
-
const agent = this.agents.get(agentId);
|
|
896
|
-
if (agent) agent.status = "error";
|
|
897
|
-
};
|
|
898
|
-
this.aiWorkers.set(tabId, worker);
|
|
899
|
-
this.agents.set(agentId, {
|
|
900
|
-
id: agentId,
|
|
901
|
-
name: modelName,
|
|
902
|
-
tabId,
|
|
903
|
-
status: "running",
|
|
904
|
-
worker
|
|
905
|
-
});
|
|
906
|
-
return agentId;
|
|
907
|
-
}
|
|
908
|
-
stopAgent(agentId) {
|
|
909
|
-
const agent = this.agents.get(agentId);
|
|
910
|
-
if (!agent) return false;
|
|
911
|
-
agent.worker.terminate();
|
|
912
|
-
agent.status = "stopped";
|
|
913
|
-
this.aiWorkers.delete(agent.tabId);
|
|
914
|
-
this.agents.delete(agentId);
|
|
915
|
-
return true;
|
|
916
|
-
}
|
|
917
|
-
getAgents() {
|
|
918
|
-
return Array.from(this.agents.values()).map((a) => ({
|
|
919
|
-
id: a.id,
|
|
920
|
-
name: a.name,
|
|
921
|
-
tabId: a.tabId,
|
|
922
|
-
status: a.status
|
|
923
|
-
}));
|
|
924
|
-
}
|
|
925
|
-
handleAIWorkerMessage(tabId, agentId, data) {
|
|
926
|
-
switch (data.type) {
|
|
927
|
-
case "click":
|
|
928
|
-
this.click(tabId, data.selector);
|
|
929
|
-
break;
|
|
930
|
-
case "type":
|
|
931
|
-
this.type(tabId, data.selector, data.text);
|
|
932
|
-
break;
|
|
933
|
-
case "fill":
|
|
934
|
-
this.fill(tabId, data.selector, data.value);
|
|
935
|
-
break;
|
|
936
|
-
case "navigate":
|
|
937
|
-
this.navigateTo(tabId, data.url);
|
|
938
|
-
break;
|
|
939
|
-
case "scroll":
|
|
940
|
-
this.scroll(tabId, data.x || 0, data.y || 0, data.selector);
|
|
941
|
-
break;
|
|
942
|
-
case "hover":
|
|
943
|
-
this.hover(tabId, data.selector);
|
|
944
|
-
break;
|
|
945
|
-
case "select":
|
|
946
|
-
this.select(tabId, data.selector, data.value);
|
|
947
|
-
break;
|
|
948
|
-
case "pressKey":
|
|
949
|
-
this.pressKey(tabId, data.key, data.modifiers);
|
|
950
|
-
break;
|
|
951
|
-
case "waitForSelector":
|
|
952
|
-
this.waitForSelector(tabId, data.selector, data.timeout).then((found) => {
|
|
953
|
-
const worker = this.aiWorkers.get(tabId);
|
|
954
|
-
worker?.postMessage({ type: "waitResult", found });
|
|
955
|
-
});
|
|
956
|
-
break;
|
|
957
|
-
case "evaluate":
|
|
958
|
-
this.evaluate(tabId, data.expression).then((result) => {
|
|
959
|
-
const worker = this.aiWorkers.get(tabId);
|
|
960
|
-
worker?.postMessage({ type: "evalResult", result });
|
|
961
|
-
});
|
|
962
|
-
break;
|
|
963
|
-
case "screenshot":
|
|
964
|
-
this.captureScreenshot(tabId).then((screenshot) => {
|
|
965
|
-
const worker = this.aiWorkers.get(tabId);
|
|
966
|
-
worker?.postMessage({ type: "screenshot", data: screenshot });
|
|
967
|
-
});
|
|
968
|
-
break;
|
|
969
|
-
case "getElementInfo":
|
|
970
|
-
this.getElementInfo(tabId, data.selector).then((info) => {
|
|
971
|
-
const worker = this.aiWorkers.get(tabId);
|
|
972
|
-
worker?.postMessage({ type: "elementInfo", info });
|
|
973
|
-
});
|
|
974
|
-
break;
|
|
975
|
-
case "getPageInfo":
|
|
976
|
-
this.getPageInfo(tabId).then((info) => {
|
|
977
|
-
const worker = this.aiWorkers.get(tabId);
|
|
978
|
-
worker?.postMessage({ type: "pageInfo", info });
|
|
979
|
-
});
|
|
980
|
-
break;
|
|
981
|
-
case "querySelectorAll":
|
|
982
|
-
this.querySelectorAll(tabId, data.selector).then((elements) => {
|
|
983
|
-
const worker = this.aiWorkers.get(tabId);
|
|
984
|
-
worker?.postMessage({ type: "elements", elements });
|
|
985
|
-
});
|
|
986
|
-
break;
|
|
987
|
-
case "done":
|
|
988
|
-
case "complete": {
|
|
989
|
-
const agent = this.agents.get(agentId);
|
|
990
|
-
if (agent) {
|
|
991
|
-
agent.status = "stopped";
|
|
992
|
-
agent.worker.terminate();
|
|
993
|
-
this.aiWorkers.delete(tabId);
|
|
994
|
-
this.agents.delete(agentId);
|
|
995
|
-
}
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
// ===========================================================================
|
|
1001
|
-
// Cross-tab Communication
|
|
1002
|
-
// ===========================================================================
|
|
1003
|
-
async broadcastToAI(message) {
|
|
1004
|
-
this.aiWorkers.forEach((worker) => {
|
|
1005
|
-
worker.postMessage({ type: "broadcast", message });
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
// ===========================================================================
|
|
1009
|
-
// Content Script Message Handler
|
|
1010
|
-
// ===========================================================================
|
|
1011
|
-
setupMessageHandlers() {
|
|
1012
|
-
if (typeof chrome === "undefined" || !chrome.runtime) return;
|
|
1013
|
-
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
1014
|
-
if (request.from === "content" && sender.tab?.id) {
|
|
1015
|
-
this.handleContentMessage(sender.tab.id, request, sendResponse);
|
|
1016
|
-
return true;
|
|
1017
|
-
}
|
|
1018
|
-
if (request.action?.startsWith("browser.")) {
|
|
1019
|
-
this.handleBrowserAction(request, sendResponse);
|
|
1020
|
-
return true;
|
|
1021
|
-
}
|
|
1022
|
-
if (request.action === "listAgents") {
|
|
1023
|
-
sendResponse({ success: true, agents: this.getAgents() });
|
|
1024
|
-
return true;
|
|
1025
|
-
}
|
|
1026
|
-
if (request.action === "stopAgent") {
|
|
1027
|
-
const stopped = this.stopAgent(request.agentId);
|
|
1028
|
-
sendResponse({ success: stopped });
|
|
1029
|
-
return true;
|
|
1030
|
-
}
|
|
1031
|
-
if (request.action === "checkWebGPU") {
|
|
1032
|
-
if (navigator.gpu) {
|
|
1033
|
-
navigator.gpu.requestAdapter().then((adapter) => {
|
|
1034
|
-
sendResponse({
|
|
1035
|
-
available: !!adapter,
|
|
1036
|
-
adapter: adapter?.name || "Unknown GPU"
|
|
1037
|
-
});
|
|
1038
|
-
}).catch(() => {
|
|
1039
|
-
sendResponse({ available: false });
|
|
1040
|
-
});
|
|
1041
|
-
} else {
|
|
1042
|
-
sendResponse({ available: false });
|
|
1043
|
-
}
|
|
1044
|
-
return true;
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
async handleBrowserAction(request, sendResponse) {
|
|
1049
|
-
const tabId = request.tabId;
|
|
1050
|
-
try {
|
|
1051
|
-
switch (request.action) {
|
|
1052
|
-
case "browser.click":
|
|
1053
|
-
sendResponse({ success: await this.click(tabId, request.selector) });
|
|
1054
|
-
break;
|
|
1055
|
-
case "browser.type":
|
|
1056
|
-
sendResponse({ success: await this.type(tabId, request.selector, request.text) });
|
|
1057
|
-
break;
|
|
1058
|
-
case "browser.fill":
|
|
1059
|
-
sendResponse({ success: await this.fill(tabId, request.selector, request.value) });
|
|
1060
|
-
break;
|
|
1061
|
-
case "browser.select":
|
|
1062
|
-
sendResponse({ success: await this.select(tabId, request.selector, request.value) });
|
|
1063
|
-
break;
|
|
1064
|
-
case "browser.scroll":
|
|
1065
|
-
sendResponse({ success: await this.scroll(tabId, request.x, request.y, request.selector) });
|
|
1066
|
-
break;
|
|
1067
|
-
case "browser.dblclick":
|
|
1068
|
-
sendResponse({ success: await this.doubleClick(tabId, request.selector) });
|
|
1069
|
-
break;
|
|
1070
|
-
case "browser.hover":
|
|
1071
|
-
sendResponse({ success: await this.hover(tabId, request.selector) });
|
|
1072
|
-
break;
|
|
1073
|
-
case "browser.clear":
|
|
1074
|
-
sendResponse({ success: await this.fill(tabId, request.selector, "") });
|
|
1075
|
-
break;
|
|
1076
|
-
case "browser.check":
|
|
1077
|
-
sendResponse({ success: await this.check(tabId, request.selector, true) });
|
|
1078
|
-
break;
|
|
1079
|
-
case "browser.uncheck":
|
|
1080
|
-
sendResponse({ success: await this.check(tabId, request.selector, false) });
|
|
1081
|
-
break;
|
|
1082
|
-
case "browser.focus":
|
|
1083
|
-
sendResponse({ success: await this.focus(tabId, request.selector) });
|
|
1084
|
-
break;
|
|
1085
|
-
case "browser.blur":
|
|
1086
|
-
sendResponse({ success: await this.blur(tabId, request.selector) });
|
|
1087
|
-
break;
|
|
1088
|
-
case "browser.drag":
|
|
1089
|
-
sendResponse({ success: await this.drag(tabId, request.from || request.selector, request.to) });
|
|
1090
|
-
break;
|
|
1091
|
-
case "browser.scrollIntoView":
|
|
1092
|
-
sendResponse({ success: await this.scrollIntoView(tabId, request.selector) });
|
|
1093
|
-
break;
|
|
1094
|
-
case "browser.pressKey":
|
|
1095
|
-
sendResponse({ success: await this.pressKey(tabId, request.key, request.modifiers) });
|
|
1096
|
-
break;
|
|
1097
|
-
case "browser.evaluate":
|
|
1098
|
-
sendResponse({ success: true, result: await this.evaluate(tabId, request.expression) });
|
|
1099
|
-
break;
|
|
1100
|
-
case "browser.screenshot":
|
|
1101
|
-
sendResponse({ success: true, data: await this.captureScreenshot(tabId, request.options) });
|
|
1102
|
-
break;
|
|
1103
|
-
case "browser.navigate":
|
|
1104
|
-
await this.navigateTo(tabId, request.url);
|
|
1105
|
-
sendResponse({ success: true });
|
|
1106
|
-
break;
|
|
1107
|
-
case "browser.goBack":
|
|
1108
|
-
await this.goBack(tabId);
|
|
1109
|
-
sendResponse({ success: true });
|
|
1110
|
-
break;
|
|
1111
|
-
case "browser.goForward":
|
|
1112
|
-
await this.goForward(tabId);
|
|
1113
|
-
sendResponse({ success: true });
|
|
1114
|
-
break;
|
|
1115
|
-
case "browser.reload":
|
|
1116
|
-
await this.reload(tabId);
|
|
1117
|
-
sendResponse({ success: true });
|
|
1118
|
-
break;
|
|
1119
|
-
case "browser.getURL":
|
|
1120
|
-
sendResponse({ success: true, url: await this.getURL(tabId) });
|
|
1121
|
-
break;
|
|
1122
|
-
case "browser.getTitle":
|
|
1123
|
-
sendResponse({ success: true, title: await this.getTitle(tabId) });
|
|
1124
|
-
break;
|
|
1125
|
-
case "browser.getTabInfo":
|
|
1126
|
-
sendResponse({ success: true, info: await this.getTabInfo(tabId) });
|
|
1127
|
-
break;
|
|
1128
|
-
case "browser.waitForNavigation":
|
|
1129
|
-
sendResponse({ success: await this.waitForNavigation(tabId, request.timeout) });
|
|
1130
|
-
break;
|
|
1131
|
-
case "browser.getHistory":
|
|
1132
|
-
sendResponse({ success: true, history: await this.getHistory(tabId, request.maxResults) });
|
|
1133
|
-
break;
|
|
1134
|
-
case "browser.fetch":
|
|
1135
|
-
sendResponse({ success: true, response: await this.browserFetch(tabId, request.url, request.options) });
|
|
1136
|
-
break;
|
|
1137
|
-
case "browser.createTab":
|
|
1138
|
-
sendResponse({ success: true, tabId: await this.createTab(request.url || "about:blank", request.active !== false) });
|
|
1139
|
-
break;
|
|
1140
|
-
case "browser.closeTab":
|
|
1141
|
-
await this.closeTab(tabId);
|
|
1142
|
-
sendResponse({ success: true });
|
|
1143
|
-
break;
|
|
1144
|
-
case "browser.waitForSelector":
|
|
1145
|
-
sendResponse({ success: await this.waitForSelector(tabId, request.selector, request.timeout) });
|
|
1146
|
-
break;
|
|
1147
|
-
case "browser.getElementInfo":
|
|
1148
|
-
sendResponse({ success: true, info: await this.getElementInfo(tabId, request.selector) });
|
|
1149
|
-
break;
|
|
1150
|
-
case "browser.getPageInfo":
|
|
1151
|
-
sendResponse({ success: true, info: await this.getPageInfo(tabId) });
|
|
1152
|
-
break;
|
|
1153
|
-
case "browser.querySelectorAll":
|
|
1154
|
-
sendResponse({ success: true, elements: await this.querySelectorAll(tabId, request.selector) });
|
|
1155
|
-
break;
|
|
1156
|
-
case "browser.getHTML":
|
|
1157
|
-
sendResponse({ success: true, html: await this.getHTML(tabId, request.selector, request.outer !== false) });
|
|
1158
|
-
break;
|
|
1159
|
-
case "browser.setHTML":
|
|
1160
|
-
sendResponse({ success: await this.setHTML(tabId, request.selector, request.html, request.outer) });
|
|
1161
|
-
break;
|
|
1162
|
-
case "browser.getText":
|
|
1163
|
-
sendResponse({ success: true, text: await this.getText(tabId, request.selector) });
|
|
1164
|
-
break;
|
|
1165
|
-
case "browser.setText":
|
|
1166
|
-
sendResponse({ success: await this.setText(tabId, request.selector, request.text) });
|
|
1167
|
-
break;
|
|
1168
|
-
case "browser.getAttribute":
|
|
1169
|
-
sendResponse({ success: true, value: await this.getAttribute(tabId, request.selector, request.attr) });
|
|
1170
|
-
break;
|
|
1171
|
-
case "browser.setAttribute":
|
|
1172
|
-
sendResponse({ success: await this.setAttribute(tabId, request.selector, request.attr, request.value) });
|
|
1173
|
-
break;
|
|
1174
|
-
case "browser.removeAttribute":
|
|
1175
|
-
sendResponse({ success: await this.removeAttribute(tabId, request.selector, request.attr) });
|
|
1176
|
-
break;
|
|
1177
|
-
case "browser.setStyle":
|
|
1178
|
-
sendResponse({ success: await this.setStyle(tabId, request.selector, request.styles) });
|
|
1179
|
-
break;
|
|
1180
|
-
case "browser.addClass":
|
|
1181
|
-
sendResponse({ success: await this.addClass(tabId, request.selector, ...request.classNames || []) });
|
|
1182
|
-
break;
|
|
1183
|
-
case "browser.removeClass":
|
|
1184
|
-
sendResponse({ success: await this.removeClass(tabId, request.selector, ...request.classNames || []) });
|
|
1185
|
-
break;
|
|
1186
|
-
case "browser.insertElement":
|
|
1187
|
-
sendResponse({ success: await this.insertElement(tabId, request.selector, request.html, request.position) });
|
|
1188
|
-
break;
|
|
1189
|
-
case "browser.removeElement":
|
|
1190
|
-
sendResponse({ success: await this.removeElement(tabId, request.selector) });
|
|
1191
|
-
break;
|
|
1192
|
-
case "browser.observeMutations":
|
|
1193
|
-
sendResponse({ success: true, mutations: await this.observeMutations(tabId, request.selector || "document", request.options, request.duration) });
|
|
1194
|
-
break;
|
|
1195
|
-
case "browser.getComputedStyles":
|
|
1196
|
-
sendResponse({ success: true, styles: await this.getComputedStyles(tabId, request.selector, request.properties) });
|
|
1197
|
-
break;
|
|
1198
|
-
case "browser.getBoundingRects":
|
|
1199
|
-
sendResponse({ success: true, rects: await this.getBoundingRects(tabId, request.selector) });
|
|
1200
|
-
break;
|
|
1201
|
-
case "browser.injectScript":
|
|
1202
|
-
sendResponse({ success: await this.injectScript(tabId, request.url) });
|
|
1203
|
-
break;
|
|
1204
|
-
case "browser.injectCSS":
|
|
1205
|
-
sendResponse({ success: await this.injectCSS(tabId, request.css) });
|
|
1206
|
-
break;
|
|
1207
|
-
case "browser.getLocalStorage":
|
|
1208
|
-
sendResponse({ success: true, data: await this.getLocalStorage(tabId, request.key) });
|
|
1209
|
-
break;
|
|
1210
|
-
case "browser.setLocalStorage":
|
|
1211
|
-
sendResponse({ success: await this.setLocalStorage(tabId, request.key, request.value) });
|
|
1212
|
-
break;
|
|
1213
|
-
case "browser.getCookies":
|
|
1214
|
-
sendResponse({ success: true, cookies: await this.getCookies(tabId) });
|
|
1215
|
-
break;
|
|
1216
|
-
default:
|
|
1217
|
-
sendResponse({ success: false, error: `Unknown action: ${request.action}` });
|
|
1218
|
-
}
|
|
1219
|
-
} catch (e) {
|
|
1220
|
-
sendResponse({ success: false, error: e.message });
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
handleContentMessage(tabId, request, sendResponse) {
|
|
1224
|
-
const worker = this.aiWorkers.get(tabId);
|
|
1225
|
-
if (worker) {
|
|
1226
|
-
worker.postMessage({ type: "contentMessage", data: request });
|
|
1227
|
-
const responseHandler = (event) => {
|
|
1228
|
-
if (event.data.type === "contentResponse") {
|
|
1229
|
-
sendResponse(event.data.response);
|
|
1230
|
-
worker.removeEventListener("message", responseHandler);
|
|
1231
|
-
}
|
|
1232
|
-
};
|
|
1233
|
-
worker.addEventListener("message", responseHandler);
|
|
1234
|
-
setTimeout(() => {
|
|
1235
|
-
worker.removeEventListener("message", responseHandler);
|
|
1236
|
-
}, 1e4);
|
|
1237
|
-
} else {
|
|
1238
|
-
sendResponse({ error: "No AI worker for this tab" });
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
// ===========================================================================
|
|
1242
|
-
// Script Execution Helper
|
|
1243
|
-
// ===========================================================================
|
|
1244
|
-
/**
|
|
1245
|
-
* Run JS in a tab and return its result.
|
|
1246
|
-
*
|
|
1247
|
-
* Why MV3 + MAIN world + the Function trampoline?
|
|
1248
|
-
*
|
|
1249
|
-
* - `chrome.scripting.executeScript` only accepts a function (or files);
|
|
1250
|
-
* it does NOT take a code string the way MV2's `tabs.executeScript`
|
|
1251
|
-
* did. To support our existing call sites that pass dynamic code as
|
|
1252
|
-
* a string, we serialise the user's code through `args` and run it
|
|
1253
|
-
* via `Function(codeStr)()` inside the page.
|
|
1254
|
-
* - MAIN world is required so the page's own globals (window.fetch,
|
|
1255
|
-
* CustomElements, framework instances) are visible. ISOLATED world
|
|
1256
|
-
* can't see them.
|
|
1257
|
-
* - Async unwrap: if the caller's code yields a Promise, executeScript
|
|
1258
|
-
* serialises it as `{}` (the value-loss bug). We detect that shape
|
|
1259
|
-
* and re-run wrapping in `Promise.resolve(...)` so the resolved
|
|
1260
|
-
* value comes back instead of the placeholder.
|
|
1261
|
-
*
|
|
1262
|
-
* Falls back to the MV2 string API on browsers / runtimes that still
|
|
1263
|
-
* have it (older Firefox; no Chrome MV2 ships now).
|
|
1264
|
-
*/
|
|
1265
|
-
async executeScript(tabId, code) {
|
|
1266
|
-
const exec = async (wrapped) => {
|
|
1267
|
-
if (chrome.scripting?.executeScript) {
|
|
1268
|
-
const results = await chrome.scripting.executeScript({
|
|
1269
|
-
target: { tabId },
|
|
1270
|
-
world: "MAIN",
|
|
1271
|
-
args: [wrapped],
|
|
1272
|
-
func: (codeStr) => {
|
|
1273
|
-
try {
|
|
1274
|
-
return Function(codeStr)();
|
|
1275
|
-
} catch (e) {
|
|
1276
|
-
return { __hanzo_error: e?.message || String(e) };
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
return results?.[0]?.result;
|
|
1281
|
-
}
|
|
1282
|
-
const legacy = chrome.tabs.executeScript;
|
|
1283
|
-
if (legacy) {
|
|
1284
|
-
return new Promise((resolve, reject) => {
|
|
1285
|
-
legacy(tabId, { code: wrapped }, (results) => {
|
|
1286
|
-
if (chrome.runtime.lastError) {
|
|
1287
|
-
reject(new Error(chrome.runtime.lastError.message));
|
|
1288
|
-
} else {
|
|
1289
|
-
resolve(results?.[0]);
|
|
1290
|
-
}
|
|
1291
|
-
});
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
throw new Error("No executeScript API available (need MV3 scripting permission)");
|
|
1295
|
-
};
|
|
1296
|
-
const trimmed = code.trim();
|
|
1297
|
-
const wrappedCode = /^\s*return\b/.test(trimmed) ? trimmed : `try { return (${trimmed}); } catch(__e) { return { __hanzo_error: __e.message || String(__e) }; }`;
|
|
1298
|
-
let result = await exec(wrappedCode);
|
|
1299
|
-
if (result && typeof result === "object" && "__hanzo_error" in result) {
|
|
1300
|
-
throw new Error(result.__hanzo_error);
|
|
1301
|
-
}
|
|
1302
|
-
const looksEmpty = result !== null && typeof result === "object" && Object.keys(result).length === 0 && !Array.isArray(result);
|
|
1303
|
-
if (looksEmpty) {
|
|
1304
|
-
const promiseWrapped = `return Promise.resolve((function(){ ${wrappedCode} })()).then(function(__v){
|
|
1305
|
-
try { return JSON.parse(JSON.stringify(__v === undefined ? null : __v)); } catch(e) { return String(__v); }
|
|
1306
|
-
});`;
|
|
1307
|
-
result = await exec(promiseWrapped);
|
|
1308
|
-
if (result && typeof result === "object" && "__hanzo_error" in result) {
|
|
1309
|
-
throw new Error(result.__hanzo_error);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
return result;
|
|
1313
|
-
}
|
|
1314
|
-
};
|
|
1315
|
-
export {
|
|
1316
|
-
BrowserControl
|
|
1317
|
-
};
|