@hanzo/browser-extension 1.7.6 → 1.9.31

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 (135) hide show
  1. package/LICENSE +9 -18
  2. package/dist/browser-extension/README.md +1 -1
  3. package/dist/browser-extension/background-firefox.js +4180 -499
  4. package/dist/browser-extension/background.js +6932 -2419
  5. package/dist/browser-extension/browser-control.js +652 -18
  6. package/dist/browser-extension/chrome/background.js +6932 -2419
  7. package/dist/browser-extension/chrome/content-script.js +1642 -123
  8. package/dist/browser-extension/chrome/icon128.png +0 -0
  9. package/dist/browser-extension/chrome/icon16.png +0 -0
  10. package/dist/browser-extension/chrome/icon48.png +0 -0
  11. package/dist/browser-extension/chrome/manifest.json +20 -11
  12. package/dist/browser-extension/chrome/popup.css +352 -110
  13. package/dist/browser-extension/chrome/popup.html +159 -48
  14. package/dist/browser-extension/chrome/popup.js +1418 -46
  15. package/dist/browser-extension/chrome/sidebar.css +772 -156
  16. package/dist/browser-extension/chrome/sidebar.html +268 -85
  17. package/dist/browser-extension/chrome/sidebar.js +25846 -84
  18. package/dist/browser-extension/cli.js +2 -2
  19. package/dist/browser-extension/content-script.js +1642 -123
  20. package/dist/browser-extension/firefox/background.js +4180 -499
  21. package/dist/browser-extension/firefox/content-script.js +1642 -123
  22. package/dist/browser-extension/firefox/icon128.png +0 -0
  23. package/dist/browser-extension/firefox/icon16.png +0 -0
  24. package/dist/browser-extension/firefox/icon48.png +0 -0
  25. package/dist/browser-extension/firefox/manifest.json +24 -13
  26. package/dist/browser-extension/firefox/popup.css +352 -110
  27. package/dist/browser-extension/firefox/popup.html +159 -48
  28. package/dist/browser-extension/firefox/popup.js +1418 -46
  29. package/dist/browser-extension/firefox/sidebar.css +772 -156
  30. package/dist/browser-extension/firefox/sidebar.html +268 -85
  31. package/dist/browser-extension/firefox/sidebar.js +25846 -84
  32. package/dist/browser-extension/icon128.png +0 -0
  33. package/dist/browser-extension/icon16.png +0 -0
  34. package/dist/browser-extension/icon48.png +0 -0
  35. package/dist/browser-extension/manifest.json +20 -11
  36. package/dist/browser-extension/package.json +1 -1
  37. package/dist/browser-extension/popup.js +1418 -46
  38. package/dist/browser-extension/safari/Info.plist +3 -3
  39. package/dist/browser-extension/safari/background.js +6932 -2419
  40. package/dist/browser-extension/safari/content-script.js +1642 -123
  41. package/dist/browser-extension/safari/icon128.png +0 -0
  42. package/dist/browser-extension/safari/icon16.png +0 -0
  43. package/dist/browser-extension/safari/icon48.png +0 -0
  44. package/dist/browser-extension/safari/popup.css +352 -110
  45. package/dist/browser-extension/safari/popup.html +159 -48
  46. package/dist/browser-extension/safari/popup.js +1418 -46
  47. package/dist/browser-extension/safari/sidebar.css +772 -156
  48. package/dist/browser-extension/safari/sidebar.html +268 -85
  49. package/dist/browser-extension/safari/sidebar.js +25846 -84
  50. package/dist/browser-extension/sidebar.js +25846 -84
  51. package/dist/browser-extension/webgpu-ai.js +78 -5
  52. package/images/icon128.png +0 -0
  53. package/images/icon16.png +0 -0
  54. package/images/icon48.png +0 -0
  55. package/manifest.json +7 -2
  56. package/package.json +27 -10
  57. package/src/chat-widget.tsx +96 -0
  58. package/src/gui-primitives.tsx +116 -0
  59. package/src/icon128.png +0 -0
  60. package/src/icon16.png +0 -0
  61. package/src/icon32.png +0 -0
  62. package/src/icon48.png +0 -0
  63. package/src/manifest-firefox.json +23 -12
  64. package/src/manifest.json +19 -10
  65. package/src/package.json +1 -1
  66. package/src/popup.css +352 -110
  67. package/src/popup.html +159 -48
  68. package/src/popup.js +196 -0
  69. package/src/sidebar.css +772 -156
  70. package/src/sidebar.html +268 -85
  71. package/src/sidebar.js +885 -0
  72. package/.github/workflows/docs.yml +0 -44
  73. package/.github/workflows/release.yml +0 -55
  74. package/README.md +0 -126
  75. package/dist/browser-extension/ai-worker.js +0 -30
  76. package/dist/browser-extension/cdp-bridge-server.js +0 -444
  77. package/dist/browser-extension/chrome/icon32.png +0 -0
  78. package/dist/browser-extension/firefox/icon32.png +0 -0
  79. package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
  80. package/dist/browser-extension/icon32.png +0 -0
  81. package/dist/browser-extension/popup.css +0 -468
  82. package/dist/browser-extension/popup.html +0 -191
  83. package/dist/browser-extension/safari/icon32.png +0 -0
  84. package/dist/browser-extension/sidebar.css +0 -718
  85. package/dist/browser-extension/sidebar.html +0 -184
  86. package/dist/cdp-bridge-server.js +0 -411
  87. package/dist/hanzo-ai-chrome-1.7.2.zip +0 -0
  88. package/dist/hanzo-ai-chrome-1.7.3.zip +0 -0
  89. package/dist/hanzo-ai-firefox-1.7.2.zip +0 -0
  90. package/dist/hanzo-ai-firefox-1.7.3.zip +0 -0
  91. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +0 -1047
  92. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  93. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
  94. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
  95. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
  96. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
  97. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
  98. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
  99. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
  100. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
  101. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
  102. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
  103. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
  104. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
  105. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
  106. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +0 -6
  107. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +0 -21
  108. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
  109. package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +0 -20
  110. package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
  111. package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +0 -24
  112. package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +0 -61
  113. package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +0 -81
  114. package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +0 -42
  115. package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +0 -24
  116. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +0 -36
  117. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +0 -38
  118. package/dist/safari/Hanzo AI/iOS (App)/Info.plist +0 -27
  119. package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +0 -18
  120. package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +0 -13
  121. package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +0 -21
  122. package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +0 -124
  123. package/dist/safari/Hanzo AI/macOS (App)/Info.plist +0 -8
  124. package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +0 -13
  125. package/docs/_config.yml +0 -13
  126. package/docs/_layouts/default.html +0 -435
  127. package/docs/index.md +0 -54
  128. package/hanzo-ai-chrome-v1.7.6.zip +0 -0
  129. package/hanzo-ai-firefox-v1.7.6.zip +0 -0
  130. package/install-extension.sh +0 -75
  131. package/test/claude-integration.test.ts +0 -251
  132. package/test/server-frameworks-edge-cases.test.ts +0 -145
  133. package/test/server-frameworks.test.ts +0 -395
  134. package/test-cdp-connection.js +0 -140
  135. package/tsconfig.json +0 -19
@@ -365,6 +365,405 @@ var BrowserControl = class {
365
365
  `);
366
366
  }
367
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
+ // ===========================================================================
368
767
  // Navigation
369
768
  // ===========================================================================
370
769
  async navigateTo(tabId, url) {
@@ -386,6 +785,74 @@ var BrowserControl = class {
386
785
  });
387
786
  });
388
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
+ }
389
856
  async closeTab(tabId) {
390
857
  chrome.tabs.remove(tabId);
391
858
  }
@@ -597,9 +1064,33 @@ var BrowserControl = class {
597
1064
  case "browser.scroll":
598
1065
  sendResponse({ success: await this.scroll(tabId, request.x, request.y, request.selector) });
599
1066
  break;
1067
+ case "browser.dblclick":
1068
+ sendResponse({ success: await this.doubleClick(tabId, request.selector) });
1069
+ break;
600
1070
  case "browser.hover":
601
1071
  sendResponse({ success: await this.hover(tabId, request.selector) });
602
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;
603
1094
  case "browser.pressKey":
604
1095
  sendResponse({ success: await this.pressKey(tabId, request.key, request.modifiers) });
605
1096
  break;
@@ -613,6 +1104,43 @@ var BrowserControl = class {
613
1104
  await this.navigateTo(tabId, request.url);
614
1105
  sendResponse({ success: true });
615
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;
616
1144
  case "browser.waitForSelector":
617
1145
  sendResponse({ success: await this.waitForSelector(tabId, request.selector, request.timeout) });
618
1146
  break;
@@ -625,6 +1153,66 @@ var BrowserControl = class {
625
1153
  case "browser.querySelectorAll":
626
1154
  sendResponse({ success: true, elements: await this.querySelectorAll(tabId, request.selector) });
627
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;
628
1216
  default:
629
1217
  sendResponse({ success: false, error: `Unknown action: ${request.action}` });
630
1218
  }
@@ -653,29 +1241,75 @@ var BrowserControl = class {
653
1241
  // ===========================================================================
654
1242
  // Script Execution Helper
655
1243
  // ===========================================================================
656
- executeScript(tabId, code) {
657
- return new Promise((resolve, reject) => {
658
- if (chrome.scripting) {
659
- chrome.scripting.executeScript({
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({
660
1269
  target: { tabId },
661
- func: new Function(`return (${code})`)
662
- }, (results) => {
663
- if (chrome.runtime.lastError) {
664
- reject(new Error(chrome.runtime.lastError.message));
665
- } else {
666
- resolve(results?.[0]?.result);
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
+ }
667
1278
  }
668
1279
  });
669
- } else {
670
- chrome.tabs.executeScript(tabId, { code }, (results) => {
671
- if (chrome.runtime.lastError) {
672
- reject(new Error(chrome.runtime.lastError.message));
673
- } else {
674
- resolve(results?.[0]);
675
- }
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
+ });
676
1292
  });
677
1293
  }
678
- });
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;
679
1313
  }
680
1314
  };
681
1315
  export {