@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.
Files changed (62) hide show
  1. package/package.json +2 -1
  2. package/src/answer/AnswerEngine.tsx +553 -0
  3. package/src/manifest-firefox.json +10 -2
  4. package/src/manifest.json +17 -2
  5. package/src/newtab.css +277 -0
  6. package/src/newtab.html +13 -0
  7. package/src/popup.html +28 -0
  8. package/dist/browser-extension/README.md +0 -43
  9. package/dist/browser-extension/background-firefox.js +0 -4556
  10. package/dist/browser-extension/background.js +0 -7245
  11. package/dist/browser-extension/browser-control.js +0 -1317
  12. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  13. package/dist/browser-extension/chrome/background.js +0 -7245
  14. package/dist/browser-extension/chrome/callback.html +0 -17
  15. package/dist/browser-extension/chrome/content-script.js +0 -2789
  16. package/dist/browser-extension/chrome/icon128.png +0 -0
  17. package/dist/browser-extension/chrome/icon16.png +0 -0
  18. package/dist/browser-extension/chrome/icon48.png +0 -0
  19. package/dist/browser-extension/chrome/manifest.json +0 -66
  20. package/dist/browser-extension/chrome/popup.css +0 -761
  21. package/dist/browser-extension/chrome/popup.html +0 -353
  22. package/dist/browser-extension/chrome/popup.js +0 -1650
  23. package/dist/browser-extension/chrome/sidebar.css +0 -1792
  24. package/dist/browser-extension/chrome/sidebar.html +0 -463
  25. package/dist/browser-extension/chrome/sidebar.js +0 -26541
  26. package/dist/browser-extension/cli.js +0 -233
  27. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  28. package/dist/browser-extension/firefox/background.js +0 -4556
  29. package/dist/browser-extension/firefox/callback.html +0 -17
  30. package/dist/browser-extension/firefox/content-script.js +0 -2789
  31. package/dist/browser-extension/firefox/icon128.png +0 -0
  32. package/dist/browser-extension/firefox/icon16.png +0 -0
  33. package/dist/browser-extension/firefox/icon48.png +0 -0
  34. package/dist/browser-extension/firefox/manifest.json +0 -77
  35. package/dist/browser-extension/firefox/popup.css +0 -761
  36. package/dist/browser-extension/firefox/popup.html +0 -353
  37. package/dist/browser-extension/firefox/popup.js +0 -1650
  38. package/dist/browser-extension/firefox/sidebar.css +0 -1792
  39. package/dist/browser-extension/firefox/sidebar.html +0 -463
  40. package/dist/browser-extension/firefox/sidebar.js +0 -26541
  41. package/dist/browser-extension/icon128.png +0 -0
  42. package/dist/browser-extension/icon16.png +0 -0
  43. package/dist/browser-extension/icon48.png +0 -0
  44. package/dist/browser-extension/manifest.json +0 -66
  45. package/dist/browser-extension/package.json +0 -41
  46. package/dist/browser-extension/popup.js +0 -1650
  47. package/dist/browser-extension/safari/Info.plist +0 -21
  48. package/dist/browser-extension/safari/ai-worker.js +0 -571
  49. package/dist/browser-extension/safari/background.js +0 -7245
  50. package/dist/browser-extension/safari/callback.html +0 -17
  51. package/dist/browser-extension/safari/content-script.js +0 -2789
  52. package/dist/browser-extension/safari/icon128.png +0 -0
  53. package/dist/browser-extension/safari/icon16.png +0 -0
  54. package/dist/browser-extension/safari/icon48.png +0 -0
  55. package/dist/browser-extension/safari/popup.css +0 -761
  56. package/dist/browser-extension/safari/popup.html +0 -353
  57. package/dist/browser-extension/safari/popup.js +0 -1650
  58. package/dist/browser-extension/safari/sidebar.css +0 -1792
  59. package/dist/browser-extension/safari/sidebar.html +0 -463
  60. package/dist/browser-extension/safari/sidebar.js +0 -26541
  61. package/dist/browser-extension/sidebar.js +0 -26541
  62. 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
- };