@btraut/browser-bridge 0.13.1 → 0.14.0
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/CHANGELOG.md +31 -0
- package/README.md +28 -37
- package/dist/api.js +1013 -626
- package/dist/api.js.map +4 -4
- package/dist/index.js +323 -308
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +1016 -675
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +470 -76
- package/extension/dist/content.js.map +4 -4
- package/extension/dist/options-ui.js +2 -113
- package/extension/dist/options-ui.js.map +2 -2
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +9 -0
- package/skills/browser-bridge/skill.json +1 -1
|
@@ -102,7 +102,6 @@ var DEFAULT_PERMISSION_PROMPT_WAIT_MS = 3e4;
|
|
|
102
102
|
var SITE_PERMISSIONS_MODE_KEY = "sitePermissionsMode";
|
|
103
103
|
var DEBUGGER_CAPABILITY_ENABLED_KEY = "debuggerCapabilityEnabled";
|
|
104
104
|
var DEFAULT_SITE_PERMISSIONS_MODE = "granular";
|
|
105
|
-
var DEFAULT_DEBUGGER_CAPABILITY_ENABLED = false;
|
|
106
105
|
var siteKeyFromUrl = (rawUrl) => {
|
|
107
106
|
if (!rawUrl || typeof rawUrl !== "string") {
|
|
108
107
|
return null;
|
|
@@ -204,24 +203,12 @@ var readPermissionPromptWaitMs = async () => {
|
|
|
204
203
|
);
|
|
205
204
|
});
|
|
206
205
|
};
|
|
207
|
-
var
|
|
206
|
+
var writeDebuggerCapabilityEnabled = async (enabled) => {
|
|
207
|
+
void enabled;
|
|
208
208
|
return await new Promise((resolve) => {
|
|
209
|
-
chrome.storage.local.
|
|
210
|
-
[DEBUGGER_CAPABILITY_ENABLED_KEY],
|
|
211
|
-
(
|
|
212
|
-
const raw = result?.[DEBUGGER_CAPABILITY_ENABLED_KEY];
|
|
213
|
-
if (typeof raw === "boolean") {
|
|
214
|
-
resolve(raw);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
try {
|
|
218
|
-
chrome.storage.local.set({
|
|
219
|
-
[DEBUGGER_CAPABILITY_ENABLED_KEY]: DEFAULT_DEBUGGER_CAPABILITY_ENABLED
|
|
220
|
-
});
|
|
221
|
-
} catch {
|
|
222
|
-
}
|
|
223
|
-
resolve(DEFAULT_DEBUGGER_CAPABILITY_ENABLED);
|
|
224
|
-
}
|
|
209
|
+
chrome.storage.local.set(
|
|
210
|
+
{ [DEBUGGER_CAPABILITY_ENABLED_KEY]: true },
|
|
211
|
+
() => resolve()
|
|
225
212
|
);
|
|
226
213
|
});
|
|
227
214
|
};
|
|
@@ -436,6 +423,416 @@ var PermissionPromptController = class {
|
|
|
436
423
|
}
|
|
437
424
|
};
|
|
438
425
|
|
|
426
|
+
// packages/extension/src/restricted-url.ts
|
|
427
|
+
var RESTRICTED_URL_PREFIXES = [
|
|
428
|
+
"chrome://",
|
|
429
|
+
"chrome-extension://",
|
|
430
|
+
"chrome-devtools://",
|
|
431
|
+
"devtools://",
|
|
432
|
+
"edge://",
|
|
433
|
+
"brave://",
|
|
434
|
+
"view-source:"
|
|
435
|
+
];
|
|
436
|
+
var isRestrictedUrl = (url) => {
|
|
437
|
+
if (!url || typeof url !== "string") {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
const lowered = url.toLowerCase();
|
|
441
|
+
if (RESTRICTED_URL_PREFIXES.some((prefix) => lowered.startsWith(prefix))) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const parsed = new URL(url);
|
|
446
|
+
if (parsed.hostname === "chromewebstore.google.com") {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
if (parsed.hostname === "chrome.google.com") {
|
|
450
|
+
return parsed.pathname.startsWith("/webstore");
|
|
451
|
+
}
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.debug("Ignoring invalid URL in restriction check.", error);
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
};
|
|
457
|
+
var getRestrictedUrlKind = (url) => {
|
|
458
|
+
const lowered = url.toLowerCase();
|
|
459
|
+
if (lowered.startsWith("chrome-extension://")) {
|
|
460
|
+
return "extension_internal";
|
|
461
|
+
}
|
|
462
|
+
if (lowered.startsWith("chrome://") || lowered.startsWith("edge://") || lowered.startsWith("brave://")) {
|
|
463
|
+
return "browser_internal";
|
|
464
|
+
}
|
|
465
|
+
if (lowered.includes("chromewebstore.google.com") || lowered.includes("chrome.google.com/webstore")) {
|
|
466
|
+
return "webstore";
|
|
467
|
+
}
|
|
468
|
+
return "restricted_url";
|
|
469
|
+
};
|
|
470
|
+
var getAlternativeCommands = (url) => {
|
|
471
|
+
const lowered = url.toLowerCase();
|
|
472
|
+
if (lowered.startsWith("chrome-extension://") || lowered.startsWith("chrome://extensions")) {
|
|
473
|
+
return ["browser-bridge diagnostics doctor", "browser-bridge dev info"];
|
|
474
|
+
}
|
|
475
|
+
return ["browser-bridge dev info", "browser-bridge diagnostics doctor"];
|
|
476
|
+
};
|
|
477
|
+
var buildRestrictedUrlError = (options) => {
|
|
478
|
+
const alternatives = getAlternativeCommands(options.url);
|
|
479
|
+
const operationLabel = options.operation === "navigate" ? "Navigation" : options.operation === "screenshot" ? "Screenshots" : options.operation === "debugger" ? "Debugger attach" : "This action";
|
|
480
|
+
return {
|
|
481
|
+
code: "NOT_SUPPORTED",
|
|
482
|
+
message: `${operationLabel} is not supported for browser internal URLs.`,
|
|
483
|
+
retryable: false,
|
|
484
|
+
details: {
|
|
485
|
+
reason: "restricted_internal_url",
|
|
486
|
+
url: options.url,
|
|
487
|
+
url_kind: getRestrictedUrlKind(options.url),
|
|
488
|
+
rationale: "Chrome restricts extension automation on internal browser surfaces (for example chrome:// and chrome-extension://).",
|
|
489
|
+
action: options.action,
|
|
490
|
+
next_step: alternatives[0],
|
|
491
|
+
alternatives
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// packages/extension/src/tab-resolution.ts
|
|
497
|
+
var invalidTabIdError = (message) => ({
|
|
498
|
+
code: "INVALID_ARGUMENT",
|
|
499
|
+
message,
|
|
500
|
+
retryable: false
|
|
501
|
+
});
|
|
502
|
+
var readOptionalTabId = (params, message = "tab_id must be a number when provided.") => {
|
|
503
|
+
const tabId = params.tab_id;
|
|
504
|
+
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
error: invalidTabIdError(message)
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
ok: true,
|
|
512
|
+
tabId: typeof tabId === "number" ? tabId : void 0
|
|
513
|
+
};
|
|
514
|
+
};
|
|
515
|
+
var readRequiredTabId = (params, message = "tab_id must be a number.") => {
|
|
516
|
+
const tabId = params.tab_id;
|
|
517
|
+
if (typeof tabId !== "number") {
|
|
518
|
+
return {
|
|
519
|
+
ok: false,
|
|
520
|
+
error: invalidTabIdError(message)
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
ok: true,
|
|
525
|
+
tabId
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
var resolveOptionalTabId = async (params, deps, message = "tab_id must be a number when provided.") => {
|
|
529
|
+
const parsed = readOptionalTabId(params, message);
|
|
530
|
+
if (!parsed.ok) {
|
|
531
|
+
return parsed;
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
ok: true,
|
|
535
|
+
tabId: parsed.tabId ?? await deps.getDefaultTabId()
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
var requireTab = async (tabId, getTab2) => {
|
|
539
|
+
try {
|
|
540
|
+
return {
|
|
541
|
+
ok: true,
|
|
542
|
+
tab: await getTab2(tabId)
|
|
543
|
+
};
|
|
544
|
+
} catch {
|
|
545
|
+
return {
|
|
546
|
+
ok: false,
|
|
547
|
+
error: {
|
|
548
|
+
code: "TAB_NOT_FOUND",
|
|
549
|
+
message: `tab_id ${tabId} was not found.`,
|
|
550
|
+
retryable: false,
|
|
551
|
+
details: { tab_id: tabId }
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// packages/extension/src/action-permissions.ts
|
|
558
|
+
var GATED_ACTIONS = /* @__PURE__ */ new Set([
|
|
559
|
+
"drive.navigate",
|
|
560
|
+
"drive.go_back",
|
|
561
|
+
"drive.go_forward",
|
|
562
|
+
"drive.click",
|
|
563
|
+
"drive.hover",
|
|
564
|
+
"drive.select",
|
|
565
|
+
"drive.type",
|
|
566
|
+
"drive.fill_form",
|
|
567
|
+
"drive.drag",
|
|
568
|
+
"drive.handle_dialog",
|
|
569
|
+
"drive.key",
|
|
570
|
+
"drive.key_press",
|
|
571
|
+
"drive.scroll",
|
|
572
|
+
"drive.screenshot",
|
|
573
|
+
"drive.wait_for"
|
|
574
|
+
]);
|
|
575
|
+
var gateDriveAction = async (options) => {
|
|
576
|
+
const { action, params, getDefaultTabId: getDefaultTabId2, getTab: getTab2, permissionPrompts: permissionPrompts2 } = options;
|
|
577
|
+
if (!GATED_ACTIONS.has(action)) {
|
|
578
|
+
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
579
|
+
}
|
|
580
|
+
let siteKey = null;
|
|
581
|
+
if (action === "drive.navigate") {
|
|
582
|
+
const url = params.url;
|
|
583
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
584
|
+
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
585
|
+
}
|
|
586
|
+
if (isRestrictedUrl(url)) {
|
|
587
|
+
return {
|
|
588
|
+
ok: false,
|
|
589
|
+
error: buildRestrictedUrlError({
|
|
590
|
+
url,
|
|
591
|
+
operation: "navigate",
|
|
592
|
+
action
|
|
593
|
+
})
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
siteKey = siteKeyFromUrl(url);
|
|
597
|
+
if (!siteKey) {
|
|
598
|
+
return {
|
|
599
|
+
ok: false,
|
|
600
|
+
error: {
|
|
601
|
+
code: "INVALID_ARGUMENT",
|
|
602
|
+
message: "Unable to resolve site permission key for url.",
|
|
603
|
+
retryable: false,
|
|
604
|
+
details: { url }
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
const parsedTabId = readOptionalTabId(params);
|
|
610
|
+
if (!parsedTabId.ok) {
|
|
611
|
+
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
612
|
+
}
|
|
613
|
+
const resolvedTabId = parsedTabId.tabId ?? await getDefaultTabId2();
|
|
614
|
+
const tab = await getTab2(resolvedTabId);
|
|
615
|
+
const url = tab.url;
|
|
616
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
617
|
+
return {
|
|
618
|
+
ok: false,
|
|
619
|
+
error: {
|
|
620
|
+
code: "FAILED_PRECONDITION",
|
|
621
|
+
message: "Active tab URL is unavailable for permission gating.",
|
|
622
|
+
retryable: false,
|
|
623
|
+
details: { tab_id: resolvedTabId }
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
if (isRestrictedUrl(url)) {
|
|
628
|
+
return {
|
|
629
|
+
ok: false,
|
|
630
|
+
error: buildRestrictedUrlError({
|
|
631
|
+
url,
|
|
632
|
+
operation: action === "drive.screenshot" ? "screenshot" : "action",
|
|
633
|
+
action
|
|
634
|
+
})
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
siteKey = siteKeyFromUrl(url);
|
|
638
|
+
if (!siteKey) {
|
|
639
|
+
return {
|
|
640
|
+
ok: false,
|
|
641
|
+
error: {
|
|
642
|
+
code: "FAILED_PRECONDITION",
|
|
643
|
+
message: "Unable to resolve site permission key for active tab.",
|
|
644
|
+
retryable: false,
|
|
645
|
+
details: { url, tab_id: resolvedTabId }
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (await readSitePermissionsMode() === "bypass") {
|
|
651
|
+
return { ok: true, siteKey, touchOnSuccess: false };
|
|
652
|
+
}
|
|
653
|
+
if (await isSiteAllowed(siteKey)) {
|
|
654
|
+
return { ok: true, siteKey, touchOnSuccess: true };
|
|
655
|
+
}
|
|
656
|
+
const decision = await permissionPrompts2.requestPermission({
|
|
657
|
+
siteKey,
|
|
658
|
+
action
|
|
659
|
+
});
|
|
660
|
+
if (decision.kind === "timed_out") {
|
|
661
|
+
return {
|
|
662
|
+
ok: false,
|
|
663
|
+
error: {
|
|
664
|
+
code: "PERMISSION_PROMPT_TIMEOUT",
|
|
665
|
+
message: `Permission prompt timed out for ${siteKey}.`,
|
|
666
|
+
retryable: true,
|
|
667
|
+
details: {
|
|
668
|
+
reason: "prompt_timed_out",
|
|
669
|
+
site: siteKey,
|
|
670
|
+
action,
|
|
671
|
+
wait_ms: decision.waitMs
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
if (decision.kind === "deny") {
|
|
677
|
+
return {
|
|
678
|
+
ok: false,
|
|
679
|
+
error: {
|
|
680
|
+
code: "PERMISSION_DENIED",
|
|
681
|
+
message: `User denied Browser Bridge permission for ${siteKey}.`,
|
|
682
|
+
retryable: false,
|
|
683
|
+
details: {
|
|
684
|
+
reason: "user_denied",
|
|
685
|
+
site: siteKey,
|
|
686
|
+
action,
|
|
687
|
+
next_step: "Ask the user to approve the permission prompt (Allow/Always allow) or allow the site in the extension options page, then retry the command."
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
if (decision.kind === "allow_always") {
|
|
693
|
+
await allowSiteAlways(siteKey);
|
|
694
|
+
return { ok: true, siteKey, touchOnSuccess: true };
|
|
695
|
+
}
|
|
696
|
+
return { ok: true, siteKey, touchOnSuccess: false };
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// packages/extension/src/popup-trigger-state.ts
|
|
700
|
+
var popupTriggerStateChanged = (before, after) => {
|
|
701
|
+
if (!before || !after) {
|
|
702
|
+
return before !== after;
|
|
703
|
+
}
|
|
704
|
+
return before.ariaExpanded !== after.ariaExpanded || before.dataState !== after.dataState || before.open !== after.open;
|
|
705
|
+
};
|
|
706
|
+
var coercePopupTriggerState = (value) => {
|
|
707
|
+
if (!value || typeof value !== "object") {
|
|
708
|
+
return void 0;
|
|
709
|
+
}
|
|
710
|
+
const record = value;
|
|
711
|
+
if (record.kind !== "popup_trigger") {
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
const ariaHasPopup = typeof record.ariaHasPopup === "string" ? record.ariaHasPopup : void 0;
|
|
715
|
+
const ariaExpanded = typeof record.ariaExpanded === "string" ? record.ariaExpanded : void 0;
|
|
716
|
+
const dataState = typeof record.dataState === "string" ? record.dataState : void 0;
|
|
717
|
+
const open = typeof record.open === "boolean" ? record.open : void 0;
|
|
718
|
+
return {
|
|
719
|
+
kind: "popup_trigger",
|
|
720
|
+
...ariaHasPopup ? { ariaHasPopup } : {},
|
|
721
|
+
...ariaExpanded !== void 0 ? { ariaExpanded } : {},
|
|
722
|
+
...dataState !== void 0 ? { dataState } : {},
|
|
723
|
+
...open !== void 0 ? { open } : {}
|
|
724
|
+
};
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// packages/extension/src/popup-click-verification.ts
|
|
728
|
+
var POPUP_TRIGGER_CLICK_SETTLE_MS = 50;
|
|
729
|
+
var POPUP_TRIGGER_RECHECK_MS = 125;
|
|
730
|
+
var readPopupTriggerAfterClick = async (options) => {
|
|
731
|
+
const after = await options.resolveLocatorPoint(options.locator);
|
|
732
|
+
if (!after.ok) {
|
|
733
|
+
if (shouldTreatPostClickReadErrorAsSuccess(after.error)) {
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
error: {
|
|
737
|
+
code: "NOT_FOUND",
|
|
738
|
+
message: "Popup trigger disappeared after click.",
|
|
739
|
+
retryable: false,
|
|
740
|
+
details: { reason: "popup_trigger_disappeared" }
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return after;
|
|
745
|
+
}
|
|
746
|
+
return after;
|
|
747
|
+
};
|
|
748
|
+
var shouldTreatPostClickReadErrorAsSuccess = (error) => {
|
|
749
|
+
return error.code === "LOCATOR_NOT_FOUND" || error.code === "NOT_FOUND" || error.retryable === true && (error.code === "TIMEOUT" || error.details?.reason === "transient_tab_channel_error");
|
|
750
|
+
};
|
|
751
|
+
var verifyPopupTriggerClick = async (options) => {
|
|
752
|
+
if (options.prepareTarget) {
|
|
753
|
+
await options.prepareTarget();
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
await options.dispatchCdpClick(
|
|
757
|
+
options.point.x,
|
|
758
|
+
options.point.y,
|
|
759
|
+
options.clickCount
|
|
760
|
+
);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
return { ok: false, error: options.mapDispatchError(error) };
|
|
763
|
+
}
|
|
764
|
+
await options.delayMs(options.settleMs ?? POPUP_TRIGGER_CLICK_SETTLE_MS);
|
|
765
|
+
const after = await readPopupTriggerAfterClick(options);
|
|
766
|
+
if (!after.ok) {
|
|
767
|
+
if (after.error.details?.reason === "popup_trigger_disappeared") {
|
|
768
|
+
return { ok: true };
|
|
769
|
+
}
|
|
770
|
+
return after;
|
|
771
|
+
}
|
|
772
|
+
if (popupTriggerStateChanged(
|
|
773
|
+
options.point.targetState ?? null,
|
|
774
|
+
after.point.targetState ?? null
|
|
775
|
+
)) {
|
|
776
|
+
return { ok: true };
|
|
777
|
+
}
|
|
778
|
+
if (options.clickCount === 1) {
|
|
779
|
+
await options.delayMs(POPUP_TRIGGER_RECHECK_MS);
|
|
780
|
+
const lateAfter = await readPopupTriggerAfterClick(options);
|
|
781
|
+
if (!lateAfter.ok) {
|
|
782
|
+
if (lateAfter.error.details?.reason === "popup_trigger_disappeared") {
|
|
783
|
+
return { ok: true };
|
|
784
|
+
}
|
|
785
|
+
return lateAfter;
|
|
786
|
+
}
|
|
787
|
+
if (popupTriggerStateChanged(
|
|
788
|
+
options.point.targetState ?? null,
|
|
789
|
+
lateAfter.point.targetState ?? null
|
|
790
|
+
)) {
|
|
791
|
+
return { ok: true };
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
await options.dispatchCdpClick(
|
|
795
|
+
options.point.x,
|
|
796
|
+
options.point.y,
|
|
797
|
+
options.clickCount
|
|
798
|
+
);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
return { ok: false, error: options.mapDispatchError(error) };
|
|
801
|
+
}
|
|
802
|
+
await options.delayMs(options.settleMs ?? POPUP_TRIGGER_CLICK_SETTLE_MS);
|
|
803
|
+
const retryAfter = await readPopupTriggerAfterClick(options);
|
|
804
|
+
if (!retryAfter.ok) {
|
|
805
|
+
if (retryAfter.error.details?.reason === "popup_trigger_disappeared") {
|
|
806
|
+
return { ok: true };
|
|
807
|
+
}
|
|
808
|
+
return retryAfter;
|
|
809
|
+
}
|
|
810
|
+
if (popupTriggerStateChanged(
|
|
811
|
+
options.point.targetState ?? null,
|
|
812
|
+
retryAfter.point.targetState ?? null
|
|
813
|
+
)) {
|
|
814
|
+
return { ok: true };
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
ok: false,
|
|
819
|
+
error: {
|
|
820
|
+
code: "FAILED_PRECONDITION",
|
|
821
|
+
message: "Click focused the popup trigger but did not change its open state.",
|
|
822
|
+
retryable: false,
|
|
823
|
+
details: {
|
|
824
|
+
reason: "click_state_unchanged",
|
|
825
|
+
control: "popup_trigger",
|
|
826
|
+
aria_haspopup: options.point.targetState?.ariaHasPopup,
|
|
827
|
+
aria_expanded_before: options.point.targetState?.ariaExpanded,
|
|
828
|
+
aria_expanded_after: after.point.targetState?.ariaExpanded,
|
|
829
|
+
data_state_before: options.point.targetState?.dataState,
|
|
830
|
+
data_state_after: after.point.targetState?.dataState
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
|
|
439
836
|
// packages/extension/src/drive-reliability.ts
|
|
440
837
|
var TRANSIENT_TAB_CHANNEL_ERROR_PATTERNS = [
|
|
441
838
|
"receiving end does not exist",
|
|
@@ -462,6 +859,15 @@ var isTransientTabChannelError = (message) => {
|
|
|
462
859
|
(pattern) => normalized.includes(pattern)
|
|
463
860
|
);
|
|
464
861
|
};
|
|
862
|
+
var shouldRetryTabChannelFailure = (action, error) => {
|
|
863
|
+
if (!error || typeof error !== "object") {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
if (isTransientTabChannelError(error.message)) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
return action === "drive.wait_for" && error.code === "TIMEOUT" && error.retryable === true && typeof error.message === "string" && error.message.includes("Timed out waiting for content response");
|
|
870
|
+
};
|
|
465
871
|
var getTabChannelRetryDelayMs = (attempt) => {
|
|
466
872
|
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
467
873
|
return void 0;
|
|
@@ -558,9 +964,228 @@ var ConnectionStateTracker = class {
|
|
|
558
964
|
}
|
|
559
965
|
};
|
|
560
966
|
|
|
561
|
-
// packages/extension/src/
|
|
967
|
+
// packages/extension/src/debugger-dispatch.ts
|
|
968
|
+
var dispatchDebuggerRequest = async (message, deps, responders) => {
|
|
969
|
+
const { respondAck, respondError } = responders;
|
|
970
|
+
try {
|
|
971
|
+
switch (message.action) {
|
|
972
|
+
case "debugger.attach": {
|
|
973
|
+
const parsedTabId = readRequiredTabId(
|
|
974
|
+
message.params ?? {}
|
|
975
|
+
);
|
|
976
|
+
if (!parsedTabId.ok) {
|
|
977
|
+
respondError(parsedTabId.error);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const error = await deps.ensureDebuggerAttached(parsedTabId.tabId);
|
|
981
|
+
if (error) {
|
|
982
|
+
respondError(error);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
respondAck({ ok: true });
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
case "debugger.detach": {
|
|
989
|
+
const parsedTabId = readRequiredTabId(
|
|
990
|
+
message.params ?? {}
|
|
991
|
+
);
|
|
992
|
+
if (!parsedTabId.ok) {
|
|
993
|
+
respondError(parsedTabId.error);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const error = await deps.detachDebugger(parsedTabId.tabId);
|
|
997
|
+
if (error) {
|
|
998
|
+
respondError(error);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
respondAck({ ok: true });
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
case "debugger.command": {
|
|
1005
|
+
const params = message.params ?? {};
|
|
1006
|
+
const parsedTabId = readRequiredTabId(
|
|
1007
|
+
params
|
|
1008
|
+
);
|
|
1009
|
+
if (!parsedTabId.ok) {
|
|
1010
|
+
respondError(parsedTabId.error);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof params.method !== "string" || params.method.length === 0) {
|
|
1014
|
+
respondError({
|
|
1015
|
+
code: "INVALID_ARGUMENT",
|
|
1016
|
+
message: "method must be a non-empty string.",
|
|
1017
|
+
retryable: false
|
|
1018
|
+
});
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const session = deps.getSession(parsedTabId.tabId);
|
|
1022
|
+
if (session?.attachPromise) {
|
|
1023
|
+
try {
|
|
1024
|
+
await session.attachPromise;
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
const info = deps.mapDebuggerErrorMessage(
|
|
1027
|
+
error instanceof Error ? error.message : "Debugger attach failed."
|
|
1028
|
+
);
|
|
1029
|
+
deps.clearDebuggerSession(parsedTabId.tabId);
|
|
1030
|
+
respondError(info);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
const attachedSession = deps.getSession(parsedTabId.tabId);
|
|
1035
|
+
if (!attachedSession?.attached) {
|
|
1036
|
+
respondError({
|
|
1037
|
+
code: "FAILED_PRECONDITION",
|
|
1038
|
+
message: "Debugger is not attached to the requested tab.",
|
|
1039
|
+
retryable: false
|
|
1040
|
+
});
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
const result = await deps.sendDebuggerCommand(
|
|
1045
|
+
parsedTabId.tabId,
|
|
1046
|
+
params.method,
|
|
1047
|
+
params.params,
|
|
1048
|
+
deps.debuggerCommandTimeoutMs
|
|
1049
|
+
);
|
|
1050
|
+
deps.touchDebuggerSession(parsedTabId.tabId);
|
|
1051
|
+
respondAck(result);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
const info = deps.mapDebuggerErrorMessage(
|
|
1054
|
+
error instanceof Error ? error.message : "Debugger command failed."
|
|
1055
|
+
);
|
|
1056
|
+
respondError(info);
|
|
1057
|
+
}
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
default:
|
|
1061
|
+
respondError({
|
|
1062
|
+
code: "NOT_IMPLEMENTED",
|
|
1063
|
+
message: `${message.action} not implemented in extension yet.`,
|
|
1064
|
+
retryable: false
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
const messageText = error instanceof Error ? error.message : "Unexpected debugger error.";
|
|
1069
|
+
respondError({
|
|
1070
|
+
code: "INSPECT_UNAVAILABLE",
|
|
1071
|
+
message: messageText,
|
|
1072
|
+
retryable: false
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// packages/extension/src/screenshot-errors.ts
|
|
1078
|
+
var isCaptureVisibleTabRateLimitedMessage = (message) => {
|
|
1079
|
+
const normalized = message.toLowerCase();
|
|
1080
|
+
const hasCaptureSignal = normalized.includes("capturevisibletab");
|
|
1081
|
+
const hasRateSignal = normalized.includes("max_capture_visible_tab_calls_per_second") || normalized.includes("too often") || normalized.includes("rate limit") || normalized.includes("rate-limit");
|
|
1082
|
+
return hasCaptureSignal && hasRateSignal;
|
|
1083
|
+
};
|
|
1084
|
+
var isCaptureVisibleTabPermissionMessage = (message) => {
|
|
1085
|
+
const normalized = message.toLowerCase();
|
|
1086
|
+
const hasCaptureSignal = normalized.includes("capturevisibletab");
|
|
1087
|
+
const hasPermissionSignal = normalized.includes("permission is required") || normalized.includes("requires permission") || normalized.includes("requires either");
|
|
1088
|
+
const hasPermissionTarget = normalized.includes("all_urls") || normalized.includes("activetab");
|
|
1089
|
+
return hasCaptureSignal && hasPermissionSignal && hasPermissionTarget;
|
|
1090
|
+
};
|
|
1091
|
+
var isCaptureVisibleTabRateLimitedError = (error) => error instanceof Error && isCaptureVisibleTabRateLimitedMessage(error.message);
|
|
1092
|
+
var isCaptureVisibleTabPermissionError = (error) => error instanceof Error && isCaptureVisibleTabPermissionMessage(error.message);
|
|
1093
|
+
var mapScreenshotCaptureError = (error, fallbackMessage) => {
|
|
1094
|
+
const message = error instanceof Error && error.message ? error.message : fallbackMessage;
|
|
1095
|
+
if (isCaptureVisibleTabRateLimitedError(error)) {
|
|
1096
|
+
return {
|
|
1097
|
+
code: "RATE_LIMITED",
|
|
1098
|
+
message: "Screenshot capture hit Chrome capture rate limits. Please retry shortly.",
|
|
1099
|
+
retryable: true,
|
|
1100
|
+
details: {
|
|
1101
|
+
reason: "capture_visible_tab_rate_limited",
|
|
1102
|
+
original_message: message
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
if (isCaptureVisibleTabPermissionError(error)) {
|
|
1107
|
+
return {
|
|
1108
|
+
code: "PERMISSION_REQUIRED",
|
|
1109
|
+
message: "Screenshot capture requires captureVisibleTab permission (<all_urls> or activeTab).",
|
|
1110
|
+
retryable: false,
|
|
1111
|
+
details: {
|
|
1112
|
+
reason: "capture_visible_tab_permission_required",
|
|
1113
|
+
required_any_of: ["<all_urls>", "activeTab"],
|
|
1114
|
+
next_step: "Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture.",
|
|
1115
|
+
original_message: message
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
code: "ARTIFACT_IO_ERROR",
|
|
1121
|
+
message,
|
|
1122
|
+
retryable: false
|
|
1123
|
+
};
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
// packages/extension/src/core-endpoint-config.ts
|
|
1127
|
+
var DEFAULT_CORE_HOST = "127.0.0.1";
|
|
562
1128
|
var DEFAULT_CORE_PORT = 3210;
|
|
563
|
-
var
|
|
1129
|
+
var LEGACY_CORE_PORT_KEY = "corePort";
|
|
1130
|
+
var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
1131
|
+
var readCoreEndpointConfig = async () => ({
|
|
1132
|
+
host: DEFAULT_CORE_HOST,
|
|
1133
|
+
port: DEFAULT_CORE_PORT,
|
|
1134
|
+
portSource: "default"
|
|
1135
|
+
});
|
|
1136
|
+
var clearLegacyCorePort = async (storage) => {
|
|
1137
|
+
return await new Promise((resolve) => {
|
|
1138
|
+
storage.get([LEGACY_CORE_PORT_KEY], (items) => {
|
|
1139
|
+
if (!hasOwn(items, LEGACY_CORE_PORT_KEY)) {
|
|
1140
|
+
resolve(false);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
storage.remove([LEGACY_CORE_PORT_KEY], () => resolve(true));
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// packages/extension/src/tab-activation.ts
|
|
1149
|
+
var resolveTabActivationOutcome = ({
|
|
1150
|
+
tabId,
|
|
1151
|
+
windowId,
|
|
1152
|
+
activated,
|
|
1153
|
+
focusErrorMessage,
|
|
1154
|
+
windowFocused
|
|
1155
|
+
}) => {
|
|
1156
|
+
if (!activated) {
|
|
1157
|
+
return {
|
|
1158
|
+
ok: false,
|
|
1159
|
+
error: {
|
|
1160
|
+
code: "FAILED_PRECONDITION",
|
|
1161
|
+
message: `Failed to activate tab_id ${tabId}.`,
|
|
1162
|
+
retryable: true,
|
|
1163
|
+
details: { tab_id: tabId }
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
const warnings = [];
|
|
1168
|
+
if (typeof windowId === "number") {
|
|
1169
|
+
if (focusErrorMessage) {
|
|
1170
|
+
warnings.push(
|
|
1171
|
+
`Activated tab_id ${tabId}, but failed to focus window_id ${windowId}: ${focusErrorMessage}`
|
|
1172
|
+
);
|
|
1173
|
+
} else if (windowFocused === false) {
|
|
1174
|
+
warnings.push(
|
|
1175
|
+
`Activated tab_id ${tabId}, but window_id ${windowId} did not report focused state.`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
ok: true,
|
|
1181
|
+
result: {
|
|
1182
|
+
ok: true,
|
|
1183
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// packages/extension/src/background.ts
|
|
564
1189
|
var CORE_WS_PATH = "/drive";
|
|
565
1190
|
var CORE_HEALTH_PATH = "/health";
|
|
566
1191
|
var CORE_HEALTH_TIMEOUT_MS = 1200;
|
|
@@ -598,6 +1223,7 @@ var BASE_NEGOTIATED_CAPABILITIES = Object.freeze({
|
|
|
598
1223
|
"drive.tab_list": true,
|
|
599
1224
|
"drive.tab_activate": true,
|
|
600
1225
|
"drive.tab_close": true,
|
|
1226
|
+
"drive.set_debugger_capability": true,
|
|
601
1227
|
"drive.ping": true
|
|
602
1228
|
});
|
|
603
1229
|
var DEBUGGER_CAPABILITY_ACTIONS = [
|
|
@@ -605,26 +1231,15 @@ var DEBUGGER_CAPABILITY_ACTIONS = [
|
|
|
605
1231
|
"debugger.detach",
|
|
606
1232
|
"debugger.command"
|
|
607
1233
|
];
|
|
608
|
-
var buildNegotiatedCapabilities = (
|
|
1234
|
+
var buildNegotiatedCapabilities = () => {
|
|
609
1235
|
const capabilities = {
|
|
610
1236
|
...BASE_NEGOTIATED_CAPABILITIES
|
|
611
1237
|
};
|
|
612
1238
|
for (const action of DEBUGGER_CAPABILITY_ACTIONS) {
|
|
613
|
-
capabilities[action] =
|
|
1239
|
+
capabilities[action] = true;
|
|
614
1240
|
}
|
|
615
1241
|
return capabilities;
|
|
616
1242
|
};
|
|
617
|
-
var debuggerCapabilityDisabledError = () => {
|
|
618
|
-
return {
|
|
619
|
-
code: "ATTACH_DENIED",
|
|
620
|
-
message: "Debugger capability is disabled. Enable debugger-based inspect in extension options and retry.",
|
|
621
|
-
retryable: false,
|
|
622
|
-
details: {
|
|
623
|
-
reason: "debugger_capability_disabled",
|
|
624
|
-
next_step: "Open Browser Bridge extension options, enable debugger-based inspect, then retry."
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
};
|
|
628
1243
|
var getAgentTabBootstrapUrl = () => {
|
|
629
1244
|
return typeof chrome.runtime?.getURL === "function" ? chrome.runtime.getURL(AGENT_TAB_BOOTSTRAP_PATH) : AGENT_TAB_BOOTSTRAP_PATH;
|
|
630
1245
|
};
|
|
@@ -687,15 +1302,6 @@ var CAPTURE_VISIBLE_TAB_MAX_RETRIES = 3;
|
|
|
687
1302
|
var CAPTURE_VISIBLE_TAB_RETRY_BASE_DELAY_MS = 500;
|
|
688
1303
|
var captureVisibleTabQueue = Promise.resolve();
|
|
689
1304
|
var captureVisibleTabLastCallAt = 0;
|
|
690
|
-
var isCaptureVisibleTabRateLimitedMessage = (message) => {
|
|
691
|
-
const normalized = message.toLowerCase();
|
|
692
|
-
const hasCaptureSignal = normalized.includes("capturevisibletab") || normalized.includes("max_capture_visible_tab_calls_per_second");
|
|
693
|
-
const hasRateSignal = normalized.includes("too often") || normalized.includes("rate limit") || normalized.includes("rate-limit");
|
|
694
|
-
return hasCaptureSignal && hasRateSignal;
|
|
695
|
-
};
|
|
696
|
-
var isCaptureVisibleTabRateLimitedError = (error) => {
|
|
697
|
-
return error instanceof Error && isCaptureVisibleTabRateLimitedMessage(error.message);
|
|
698
|
-
};
|
|
699
1305
|
var randomJitterMs = (maxMs) => {
|
|
700
1306
|
return Math.floor(Math.random() * Math.max(1, maxMs));
|
|
701
1307
|
};
|
|
@@ -737,25 +1343,6 @@ var captureVisibleTabWithThrottle = async (windowId) => {
|
|
|
737
1343
|
throw new Error("captureVisibleTab failed unexpectedly.");
|
|
738
1344
|
});
|
|
739
1345
|
};
|
|
740
|
-
var mapScreenshotCaptureError = (error, fallbackMessage) => {
|
|
741
|
-
const message = error instanceof Error && error.message ? error.message : fallbackMessage;
|
|
742
|
-
if (isCaptureVisibleTabRateLimitedError(error)) {
|
|
743
|
-
return {
|
|
744
|
-
code: "RATE_LIMITED",
|
|
745
|
-
message: "Screenshot capture hit Chrome capture rate limits. Please retry shortly.",
|
|
746
|
-
retryable: true,
|
|
747
|
-
details: {
|
|
748
|
-
reason: "capture_visible_tab_rate_limited",
|
|
749
|
-
original_message: message
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
return {
|
|
754
|
-
code: "ARTIFACT_IO_ERROR",
|
|
755
|
-
message,
|
|
756
|
-
retryable: false
|
|
757
|
-
};
|
|
758
|
-
};
|
|
759
1346
|
var parseDataUrl = (dataUrl) => {
|
|
760
1347
|
const match = /^data:([^;]+);base64,(.*)$/s.exec(dataUrl);
|
|
761
1348
|
if (!match) {
|
|
@@ -801,40 +1388,6 @@ var renderDataUrlToFormat = async (dataUrl, format, quality) => {
|
|
|
801
1388
|
bitmap.close();
|
|
802
1389
|
}
|
|
803
1390
|
};
|
|
804
|
-
var readCoreEndpointConfig = async () => {
|
|
805
|
-
return await new Promise((resolve) => {
|
|
806
|
-
chrome.storage.local.get(
|
|
807
|
-
[CORE_PORT_KEY],
|
|
808
|
-
(result) => {
|
|
809
|
-
const raw = result?.[CORE_PORT_KEY];
|
|
810
|
-
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
811
|
-
resolve({
|
|
812
|
-
host: "127.0.0.1",
|
|
813
|
-
port: raw,
|
|
814
|
-
portSource: "storage"
|
|
815
|
-
});
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
if (typeof raw === "string") {
|
|
819
|
-
const parsed = Number(raw);
|
|
820
|
-
if (Number.isFinite(parsed)) {
|
|
821
|
-
resolve({
|
|
822
|
-
host: "127.0.0.1",
|
|
823
|
-
port: parsed,
|
|
824
|
-
portSource: "storage"
|
|
825
|
-
});
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
resolve({
|
|
830
|
-
host: "127.0.0.1",
|
|
831
|
-
port: DEFAULT_CORE_PORT,
|
|
832
|
-
portSource: "default"
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
);
|
|
836
|
-
});
|
|
837
|
-
};
|
|
838
1391
|
var readDebuggerIdleTimeoutMs = async () => {
|
|
839
1392
|
return await new Promise((resolve) => {
|
|
840
1393
|
chrome.storage.local.get(
|
|
@@ -857,36 +1410,6 @@ var readDebuggerIdleTimeoutMs = async () => {
|
|
|
857
1410
|
);
|
|
858
1411
|
});
|
|
859
1412
|
};
|
|
860
|
-
var RESTRICTED_URL_PREFIXES = [
|
|
861
|
-
"chrome://",
|
|
862
|
-
"chrome-extension://",
|
|
863
|
-
"chrome-devtools://",
|
|
864
|
-
"devtools://",
|
|
865
|
-
"edge://",
|
|
866
|
-
"brave://",
|
|
867
|
-
"view-source:"
|
|
868
|
-
];
|
|
869
|
-
var isRestrictedUrl = (url) => {
|
|
870
|
-
if (!url || typeof url !== "string") {
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
873
|
-
const lowered = url.toLowerCase();
|
|
874
|
-
if (RESTRICTED_URL_PREFIXES.some((prefix) => lowered.startsWith(prefix))) {
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
const parsed = new URL(url);
|
|
879
|
-
if (parsed.hostname === "chromewebstore.google.com") {
|
|
880
|
-
return true;
|
|
881
|
-
}
|
|
882
|
-
if (parsed.hostname === "chrome.google.com") {
|
|
883
|
-
return parsed.pathname.startsWith("/webstore");
|
|
884
|
-
}
|
|
885
|
-
} catch (error) {
|
|
886
|
-
console.debug("Ignoring invalid URL in restriction check.", error);
|
|
887
|
-
}
|
|
888
|
-
return false;
|
|
889
|
-
};
|
|
890
1413
|
var mapDebuggerErrorMessage = (message, fallbackCode = "INSPECT_UNAVAILABLE") => {
|
|
891
1414
|
const normalized = message.toLowerCase();
|
|
892
1415
|
if (normalized.includes("already attached") || normalized.includes("another debugger") || normalized.includes("attached to this target")) {
|
|
@@ -943,6 +1466,25 @@ var buildTabInfo = (tab) => {
|
|
|
943
1466
|
last_active_at: ensureLastActiveAt(tabId)
|
|
944
1467
|
};
|
|
945
1468
|
};
|
|
1469
|
+
var tabRecencyScore = (tab) => {
|
|
1470
|
+
const parsed = Date.parse(tab.last_active_at);
|
|
1471
|
+
return Number.isFinite(parsed) ? parsed : -Infinity;
|
|
1472
|
+
};
|
|
1473
|
+
var compareTabsForReport = (a, b) => {
|
|
1474
|
+
const aActive = a.active === true ? 1 : 0;
|
|
1475
|
+
const bActive = b.active === true ? 1 : 0;
|
|
1476
|
+
if (aActive !== bActive) {
|
|
1477
|
+
return bActive - aActive;
|
|
1478
|
+
}
|
|
1479
|
+
const recencyDelta = tabRecencyScore(b) - tabRecencyScore(a);
|
|
1480
|
+
if (recencyDelta !== 0) {
|
|
1481
|
+
return recencyDelta;
|
|
1482
|
+
}
|
|
1483
|
+
if (a.window_id !== b.window_id) {
|
|
1484
|
+
return a.window_id - b.window_id;
|
|
1485
|
+
}
|
|
1486
|
+
return a.tab_id - b.tab_id;
|
|
1487
|
+
};
|
|
946
1488
|
var queryTabs = async () => {
|
|
947
1489
|
const tabs = await wrapChromeCallback(
|
|
948
1490
|
(callback) => chrome.tabs.query({}, callback)
|
|
@@ -954,6 +1496,7 @@ var queryTabs = async () => {
|
|
|
954
1496
|
result.push(info);
|
|
955
1497
|
}
|
|
956
1498
|
}
|
|
1499
|
+
result.sort(compareTabsForReport);
|
|
957
1500
|
return result;
|
|
958
1501
|
};
|
|
959
1502
|
var getTab = async (tabId) => {
|
|
@@ -961,6 +1504,21 @@ var getTab = async (tabId) => {
|
|
|
961
1504
|
(callback) => chrome.tabs.get(tabId, callback)
|
|
962
1505
|
);
|
|
963
1506
|
};
|
|
1507
|
+
var getWindow = async (windowId) => {
|
|
1508
|
+
return await wrapChromeCallback(
|
|
1509
|
+
(callback) => chrome.windows.get(windowId, callback)
|
|
1510
|
+
);
|
|
1511
|
+
};
|
|
1512
|
+
var withResolvedTabTarget = async (tabId, result) => {
|
|
1513
|
+
const payload = result && typeof result === "object" && !Array.isArray(result) ? { ...result } : {};
|
|
1514
|
+
const tab = await getTab(tabId).catch(() => void 0);
|
|
1515
|
+
const windowId = tab && typeof tab.windowId === "number" ? tab.windowId : void 0;
|
|
1516
|
+
return {
|
|
1517
|
+
...payload,
|
|
1518
|
+
tab_id: tabId,
|
|
1519
|
+
...typeof windowId === "number" ? { window_id: windowId } : {}
|
|
1520
|
+
};
|
|
1521
|
+
};
|
|
964
1522
|
var getActiveTabId = async () => {
|
|
965
1523
|
const tabs = await wrapChromeCallback(
|
|
966
1524
|
(callback) => chrome.tabs.query({ active: true, lastFocusedWindow: true }, callback)
|
|
@@ -1199,7 +1757,7 @@ var sendToTab = async (tabId, action, params, options) => {
|
|
|
1199
1757
|
if (result.ok) {
|
|
1200
1758
|
return result;
|
|
1201
1759
|
}
|
|
1202
|
-
if (!
|
|
1760
|
+
if (!shouldRetryTabChannelFailure(action, result.error)) {
|
|
1203
1761
|
return result;
|
|
1204
1762
|
}
|
|
1205
1763
|
const retryDelayMs = getTabChannelRetryDelayMs(attempt);
|
|
@@ -1480,7 +2038,6 @@ var DriveSocket = class {
|
|
|
1480
2038
|
async sendHello() {
|
|
1481
2039
|
const manifest = chrome.runtime.getManifest();
|
|
1482
2040
|
const endpoint = await readCoreEndpointConfig();
|
|
1483
|
-
const debuggerCapabilityEnabled = await readDebuggerCapabilityEnabled();
|
|
1484
2041
|
let tabs = [];
|
|
1485
2042
|
try {
|
|
1486
2043
|
tabs = await queryTabs();
|
|
@@ -1489,9 +2046,10 @@ var DriveSocket = class {
|
|
|
1489
2046
|
tabs = [];
|
|
1490
2047
|
}
|
|
1491
2048
|
const params = {
|
|
2049
|
+
extension_id: chrome.runtime.id,
|
|
1492
2050
|
version: manifest.version,
|
|
1493
2051
|
protocol_version: DRIVE_WS_PROTOCOL_VERSION,
|
|
1494
|
-
capabilities: buildNegotiatedCapabilities(
|
|
2052
|
+
capabilities: buildNegotiatedCapabilities(),
|
|
1495
2053
|
core_host: endpoint.host,
|
|
1496
2054
|
core_port: endpoint.port,
|
|
1497
2055
|
core_port_source: endpoint.portSource,
|
|
@@ -1567,16 +2125,10 @@ var DriveSocket = class {
|
|
|
1567
2125
|
}
|
|
1568
2126
|
}
|
|
1569
2127
|
async refreshDebuggerCapabilityState() {
|
|
1570
|
-
const enabled = await readDebuggerCapabilityEnabled();
|
|
1571
|
-
if (!enabled) {
|
|
1572
|
-
await this.detachAllDebuggerSessions();
|
|
1573
|
-
}
|
|
1574
2128
|
this.refreshCapabilities();
|
|
1575
2129
|
}
|
|
1576
2130
|
async handleDebuggerCapabilityChange(enabled) {
|
|
1577
|
-
|
|
1578
|
-
await this.detachAllDebuggerSessions();
|
|
1579
|
-
}
|
|
2131
|
+
void enabled;
|
|
1580
2132
|
this.refreshCapabilities();
|
|
1581
2133
|
}
|
|
1582
2134
|
async handleRequest(message) {
|
|
@@ -1617,15 +2169,6 @@ var DriveSocket = class {
|
|
|
1617
2169
|
return;
|
|
1618
2170
|
}
|
|
1619
2171
|
if (message.action.startsWith("debugger.")) {
|
|
1620
|
-
if (!await readDebuggerCapabilityEnabled()) {
|
|
1621
|
-
this.sendMessage({
|
|
1622
|
-
id: message.id,
|
|
1623
|
-
action: message.action,
|
|
1624
|
-
status: "error",
|
|
1625
|
-
error: sanitizeDriveErrorInfo(debuggerCapabilityDisabledError())
|
|
1626
|
-
});
|
|
1627
|
-
return;
|
|
1628
|
-
}
|
|
1629
2172
|
await this.handleDebuggerRequest(message);
|
|
1630
2173
|
return;
|
|
1631
2174
|
}
|
|
@@ -1633,162 +2176,58 @@ var DriveSocket = class {
|
|
|
1633
2176
|
return;
|
|
1634
2177
|
}
|
|
1635
2178
|
driveMessage = message;
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
"drive.type",
|
|
1644
|
-
"drive.fill_form",
|
|
1645
|
-
"drive.drag",
|
|
1646
|
-
"drive.handle_dialog",
|
|
1647
|
-
"drive.key",
|
|
1648
|
-
"drive.key_press",
|
|
1649
|
-
"drive.scroll",
|
|
1650
|
-
"drive.screenshot",
|
|
1651
|
-
"drive.wait_for"
|
|
1652
|
-
]);
|
|
1653
|
-
const gateDriveAction = async () => {
|
|
1654
|
-
const action = message.action;
|
|
1655
|
-
if (!gatedActions.has(action)) {
|
|
1656
|
-
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
1657
|
-
}
|
|
1658
|
-
const params = message.params ?? {};
|
|
1659
|
-
let siteKey = null;
|
|
1660
|
-
if (action === "drive.navigate") {
|
|
1661
|
-
const url = params.url;
|
|
1662
|
-
if (typeof url !== "string" || url.length === 0) {
|
|
1663
|
-
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
1664
|
-
}
|
|
1665
|
-
if (isRestrictedUrl(url)) {
|
|
1666
|
-
return {
|
|
1667
|
-
ok: false,
|
|
1668
|
-
error: {
|
|
1669
|
-
code: "NOT_SUPPORTED",
|
|
1670
|
-
message: "Navigation is not supported for this URL.",
|
|
1671
|
-
retryable: false,
|
|
1672
|
-
details: { url }
|
|
1673
|
-
}
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
siteKey = siteKeyFromUrl(url);
|
|
1677
|
-
if (!siteKey) {
|
|
1678
|
-
return {
|
|
1679
|
-
ok: false,
|
|
1680
|
-
error: {
|
|
1681
|
-
code: "INVALID_ARGUMENT",
|
|
1682
|
-
message: "Unable to resolve site permission key for url.",
|
|
1683
|
-
retryable: false,
|
|
1684
|
-
details: { url }
|
|
1685
|
-
}
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
} else {
|
|
1689
|
-
const tabId = params.tab_id;
|
|
1690
|
-
if (tabId !== void 0 && typeof tabId !== "number") {
|
|
1691
|
-
return { ok: true, siteKey: null, touchOnSuccess: false };
|
|
1692
|
-
}
|
|
1693
|
-
const resolvedTabId = typeof tabId === "number" ? tabId : await getDefaultTabId();
|
|
1694
|
-
const tab = await getTab(resolvedTabId);
|
|
1695
|
-
const url = tab.url;
|
|
1696
|
-
if (typeof url !== "string" || url.length === 0) {
|
|
1697
|
-
return {
|
|
1698
|
-
ok: false,
|
|
1699
|
-
error: {
|
|
1700
|
-
code: "FAILED_PRECONDITION",
|
|
1701
|
-
message: "Active tab URL is unavailable for permission gating.",
|
|
1702
|
-
retryable: false,
|
|
1703
|
-
details: { tab_id: resolvedTabId }
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
|
-
if (isRestrictedUrl(url)) {
|
|
1708
|
-
const message2 = action === "drive.screenshot" ? "Screenshots are not supported for this URL." : "This action is not supported for this URL.";
|
|
1709
|
-
return {
|
|
1710
|
-
ok: false,
|
|
1711
|
-
error: {
|
|
1712
|
-
code: "NOT_SUPPORTED",
|
|
1713
|
-
message: message2,
|
|
1714
|
-
retryable: false,
|
|
1715
|
-
details: { url }
|
|
1716
|
-
}
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
siteKey = siteKeyFromUrl(url);
|
|
1720
|
-
if (!siteKey) {
|
|
1721
|
-
return {
|
|
1722
|
-
ok: false,
|
|
1723
|
-
error: {
|
|
1724
|
-
code: "FAILED_PRECONDITION",
|
|
1725
|
-
message: "Unable to resolve site permission key for active tab.",
|
|
1726
|
-
retryable: false,
|
|
1727
|
-
details: { url, tab_id: resolvedTabId }
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
if (await readSitePermissionsMode() === "bypass") {
|
|
1733
|
-
return { ok: true, siteKey, touchOnSuccess: false };
|
|
1734
|
-
}
|
|
1735
|
-
if (await isSiteAllowed(siteKey)) {
|
|
1736
|
-
return { ok: true, siteKey, touchOnSuccess: true };
|
|
1737
|
-
}
|
|
1738
|
-
const decision = await permissionPrompts.requestPermission({
|
|
1739
|
-
siteKey,
|
|
1740
|
-
action
|
|
1741
|
-
});
|
|
1742
|
-
if (decision.kind === "timed_out") {
|
|
1743
|
-
return {
|
|
1744
|
-
ok: false,
|
|
1745
|
-
error: {
|
|
1746
|
-
code: "PERMISSION_PROMPT_TIMEOUT",
|
|
1747
|
-
message: `Permission prompt timed out for ${siteKey}.`,
|
|
1748
|
-
retryable: true,
|
|
1749
|
-
details: {
|
|
1750
|
-
reason: "prompt_timed_out",
|
|
1751
|
-
site: siteKey,
|
|
1752
|
-
action,
|
|
1753
|
-
wait_ms: decision.waitMs
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
if (decision.kind === "deny") {
|
|
1759
|
-
return {
|
|
1760
|
-
ok: false,
|
|
1761
|
-
error: {
|
|
1762
|
-
code: "PERMISSION_DENIED",
|
|
1763
|
-
message: `User denied Browser Bridge permission for ${siteKey}.`,
|
|
1764
|
-
retryable: false,
|
|
1765
|
-
details: {
|
|
1766
|
-
reason: "user_denied",
|
|
1767
|
-
site: siteKey,
|
|
1768
|
-
action,
|
|
1769
|
-
next_step: "Ask the user to approve the permission prompt (Allow/Always allow) or allow the site in the extension options page, then retry the command."
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
if (decision.kind === "allow_always") {
|
|
1775
|
-
await allowSiteAlways(siteKey);
|
|
1776
|
-
return { ok: true, siteKey, touchOnSuccess: true };
|
|
1777
|
-
}
|
|
1778
|
-
return { ok: true, siteKey, touchOnSuccess: false };
|
|
1779
|
-
};
|
|
1780
|
-
const gated = await gateDriveAction();
|
|
2179
|
+
const gated = await gateDriveAction({
|
|
2180
|
+
action: message.action,
|
|
2181
|
+
params: message.params ?? {},
|
|
2182
|
+
getDefaultTabId,
|
|
2183
|
+
getTab,
|
|
2184
|
+
permissionPrompts
|
|
2185
|
+
});
|
|
1781
2186
|
if (!gated.ok) {
|
|
1782
2187
|
respondError(gated.error);
|
|
1783
2188
|
return;
|
|
1784
2189
|
}
|
|
1785
2190
|
gatedSiteKey = gated.siteKey;
|
|
1786
2191
|
touchGatedSiteOnSuccess = gated.touchOnSuccess;
|
|
2192
|
+
const resolveActionTabId = async (params) => await resolveOptionalTabId(params, { getDefaultTabId });
|
|
1787
2193
|
switch (message.action) {
|
|
1788
2194
|
case "drive.ping": {
|
|
1789
2195
|
respondOk({ ok: true });
|
|
1790
2196
|
return;
|
|
1791
2197
|
}
|
|
2198
|
+
case "drive.set_debugger_capability": {
|
|
2199
|
+
const params = message.params ?? {};
|
|
2200
|
+
const enabled = typeof params.enabled === "boolean" ? params.enabled : true;
|
|
2201
|
+
const expectedExtensionId = params.extension_id;
|
|
2202
|
+
if (expectedExtensionId !== void 0 && typeof expectedExtensionId !== "string") {
|
|
2203
|
+
respondError({
|
|
2204
|
+
code: "INVALID_ARGUMENT",
|
|
2205
|
+
message: "extension_id must be a string when provided.",
|
|
2206
|
+
retryable: false
|
|
2207
|
+
});
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
if (typeof expectedExtensionId === "string" && expectedExtensionId.length > 0 && expectedExtensionId !== chrome.runtime.id) {
|
|
2211
|
+
respondError({
|
|
2212
|
+
code: "FAILED_PRECONDITION",
|
|
2213
|
+
message: "Connected extension id does not match the requested extension.",
|
|
2214
|
+
retryable: false,
|
|
2215
|
+
details: {
|
|
2216
|
+
expected_extension_id: expectedExtensionId,
|
|
2217
|
+
connected_extension_id: chrome.runtime.id
|
|
2218
|
+
}
|
|
2219
|
+
});
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
await writeDebuggerCapabilityEnabled(enabled);
|
|
2223
|
+
await this.refreshDebuggerCapabilityState();
|
|
2224
|
+
respondOk({
|
|
2225
|
+
ok: true,
|
|
2226
|
+
enabled,
|
|
2227
|
+
extension_id: chrome.runtime.id
|
|
2228
|
+
});
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
1792
2231
|
case "drive.navigate": {
|
|
1793
2232
|
const params = message.params ?? {};
|
|
1794
2233
|
const url = params.url;
|
|
@@ -1800,21 +2239,38 @@ var DriveSocket = class {
|
|
|
1800
2239
|
});
|
|
1801
2240
|
return;
|
|
1802
2241
|
}
|
|
1803
|
-
|
|
1804
|
-
if (
|
|
2242
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2243
|
+
if (!tabTarget.ok) {
|
|
2244
|
+
respondError(tabTarget.error);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const tabId = tabTarget.tabId;
|
|
2248
|
+
const requestedWaitMode = params.wait;
|
|
2249
|
+
if (requestedWaitMode !== void 0 && requestedWaitMode !== "none" && requestedWaitMode !== "domcontentloaded" && requestedWaitMode !== "networkidle") {
|
|
1805
2250
|
respondError({
|
|
1806
2251
|
code: "INVALID_ARGUMENT",
|
|
1807
|
-
message:
|
|
1808
|
-
retryable: false
|
|
2252
|
+
message: `Unsupported wait mode: ${String(requestedWaitMode)}.`,
|
|
2253
|
+
retryable: false,
|
|
2254
|
+
details: {
|
|
2255
|
+
field: "wait",
|
|
2256
|
+
supported_wait_modes: [
|
|
2257
|
+
"none",
|
|
2258
|
+
"domcontentloaded",
|
|
2259
|
+
"networkidle"
|
|
2260
|
+
],
|
|
2261
|
+
mapped_wait_mode: "domcontentloaded"
|
|
2262
|
+
}
|
|
1809
2263
|
});
|
|
1810
2264
|
return;
|
|
1811
2265
|
}
|
|
1812
|
-
|
|
1813
|
-
tabId = await getDefaultTabId();
|
|
1814
|
-
}
|
|
1815
|
-
const waitMode = params.wait === "none" || params.wait === "domcontentloaded" ? params.wait : "domcontentloaded";
|
|
2266
|
+
const waitMode = requestedWaitMode === "none" ? "none" : "domcontentloaded";
|
|
1816
2267
|
const domContentLoadedSignal = waitMode === "domcontentloaded" ? waitForDomContentLoaded(tabId, 3e4) : null;
|
|
1817
2268
|
const warnings = [];
|
|
2269
|
+
if (requestedWaitMode === "networkidle") {
|
|
2270
|
+
warnings.push(
|
|
2271
|
+
"wait=networkidle is mapped to domcontentloaded in this runtime."
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
1818
2274
|
await wrapChromeVoid(
|
|
1819
2275
|
(callback) => chrome.tabs.update(tabId, { url }, () => callback())
|
|
1820
2276
|
);
|
|
@@ -1841,39 +2297,30 @@ var DriveSocket = class {
|
|
|
1841
2297
|
if (tabId === agentTabId) {
|
|
1842
2298
|
void refreshAgentTabBranding(tabId);
|
|
1843
2299
|
}
|
|
1844
|
-
respondOk(
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2300
|
+
respondOk(
|
|
2301
|
+
await withResolvedTabTarget(tabId, {
|
|
2302
|
+
ok: true,
|
|
2303
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2304
|
+
})
|
|
2305
|
+
);
|
|
1848
2306
|
return;
|
|
1849
2307
|
}
|
|
1850
2308
|
case "drive.go_back":
|
|
1851
2309
|
case "drive.go_forward": {
|
|
1852
2310
|
const params = message.params ?? {};
|
|
1853
|
-
|
|
1854
|
-
if (
|
|
1855
|
-
respondError(
|
|
1856
|
-
code: "INVALID_ARGUMENT",
|
|
1857
|
-
message: "tab_id must be a number when provided.",
|
|
1858
|
-
retryable: false
|
|
1859
|
-
});
|
|
2311
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2312
|
+
if (!tabTarget.ok) {
|
|
2313
|
+
respondError(tabTarget.error);
|
|
1860
2314
|
return;
|
|
1861
2315
|
}
|
|
1862
|
-
|
|
1863
|
-
tabId = await getDefaultTabId();
|
|
1864
|
-
}
|
|
2316
|
+
const tabId = tabTarget.tabId;
|
|
1865
2317
|
const navigationSignal = waitForHistoryNavigationSignal(
|
|
1866
2318
|
tabId,
|
|
1867
2319
|
HISTORY_NAVIGATION_SIGNAL_TIMEOUT_MS
|
|
1868
2320
|
);
|
|
1869
|
-
const result = await sendToTab(
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
void 0,
|
|
1873
|
-
{
|
|
1874
|
-
timeoutMs: HISTORY_DISPATCH_TIMEOUT_MS
|
|
1875
|
-
}
|
|
1876
|
-
);
|
|
2321
|
+
const result = await sendToTab(tabId, message.action, void 0, {
|
|
2322
|
+
timeoutMs: HISTORY_DISPATCH_TIMEOUT_MS
|
|
2323
|
+
});
|
|
1877
2324
|
if (!result.ok && result.error.code !== "TIMEOUT") {
|
|
1878
2325
|
respondError(result.error);
|
|
1879
2326
|
return;
|
|
@@ -1899,9 +2346,11 @@ var DriveSocket = class {
|
|
|
1899
2346
|
if (tabId === agentTabId) {
|
|
1900
2347
|
void refreshAgentTabBranding(tabId);
|
|
1901
2348
|
}
|
|
1902
|
-
respondOk(
|
|
1903
|
-
|
|
1904
|
-
|
|
2349
|
+
respondOk(
|
|
2350
|
+
await withResolvedTabTarget(tabId, {
|
|
2351
|
+
ok: true
|
|
2352
|
+
})
|
|
2353
|
+
);
|
|
1905
2354
|
return;
|
|
1906
2355
|
}
|
|
1907
2356
|
case "drive.tab_list": {
|
|
@@ -1911,44 +2360,66 @@ var DriveSocket = class {
|
|
|
1911
2360
|
return;
|
|
1912
2361
|
}
|
|
1913
2362
|
case "drive.tab_activate": {
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
retryable: false
|
|
1920
|
-
});
|
|
2363
|
+
const parsedTabId = readRequiredTabId(
|
|
2364
|
+
message.params ?? {}
|
|
2365
|
+
);
|
|
2366
|
+
if (!parsedTabId.ok) {
|
|
2367
|
+
respondError(parsedTabId.error);
|
|
1921
2368
|
return;
|
|
1922
2369
|
}
|
|
1923
|
-
const
|
|
2370
|
+
const tabId = parsedTabId.tabId;
|
|
2371
|
+
const tabLookup = await requireTab(tabId, getTab);
|
|
2372
|
+
if (!tabLookup.ok) {
|
|
2373
|
+
respondError(tabLookup.error);
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const tab = tabLookup.tab;
|
|
1924
2377
|
await wrapChromeVoid(
|
|
1925
2378
|
(callback) => chrome.tabs.update(tabId, { active: true }, () => callback())
|
|
1926
2379
|
);
|
|
1927
2380
|
const windowId = tab.windowId;
|
|
2381
|
+
let focusErrorMessage;
|
|
1928
2382
|
if (typeof windowId === "number") {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2383
|
+
try {
|
|
2384
|
+
await wrapChromeVoid(
|
|
2385
|
+
(callback) => chrome.windows.update(
|
|
2386
|
+
windowId,
|
|
2387
|
+
{ focused: true },
|
|
2388
|
+
() => callback()
|
|
2389
|
+
)
|
|
2390
|
+
);
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
focusErrorMessage = error instanceof Error ? error.message : "Unknown focus error.";
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
const activatedTab = await getTab(tabId).catch(() => void 0);
|
|
2396
|
+
const focusedWindow = typeof windowId === "number" ? await getWindow(windowId).catch(() => void 0) : void 0;
|
|
2397
|
+
const windowFocused = focusedWindow && typeof focusedWindow.focused === "boolean" ? focusedWindow.focused : void 0;
|
|
2398
|
+
const outcome = resolveTabActivationOutcome({
|
|
2399
|
+
tabId,
|
|
2400
|
+
windowId: typeof windowId === "number" ? windowId : void 0,
|
|
2401
|
+
activated: Boolean(activatedTab?.active === true),
|
|
2402
|
+
focusErrorMessage,
|
|
2403
|
+
windowFocused
|
|
2404
|
+
});
|
|
2405
|
+
if (!outcome.ok) {
|
|
2406
|
+
respondError(outcome.error);
|
|
2407
|
+
return;
|
|
1936
2408
|
}
|
|
1937
2409
|
markTabActive(tabId);
|
|
1938
|
-
respondOk(
|
|
2410
|
+
respondOk(outcome.result);
|
|
1939
2411
|
this.sendTabReport();
|
|
1940
2412
|
return;
|
|
1941
2413
|
}
|
|
1942
2414
|
case "drive.tab_close": {
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
retryable: false
|
|
1949
|
-
});
|
|
2415
|
+
const parsedTabId = readRequiredTabId(
|
|
2416
|
+
message.params ?? {}
|
|
2417
|
+
);
|
|
2418
|
+
if (!parsedTabId.ok) {
|
|
2419
|
+
respondError(parsedTabId.error);
|
|
1950
2420
|
return;
|
|
1951
2421
|
}
|
|
2422
|
+
const tabId = parsedTabId.tabId;
|
|
1952
2423
|
await wrapChromeVoid(
|
|
1953
2424
|
(callback) => chrome.tabs.remove(tabId, () => callback())
|
|
1954
2425
|
);
|
|
@@ -1980,18 +2451,12 @@ var DriveSocket = class {
|
|
|
1980
2451
|
});
|
|
1981
2452
|
return;
|
|
1982
2453
|
}
|
|
1983
|
-
|
|
1984
|
-
if (
|
|
1985
|
-
respondError(
|
|
1986
|
-
code: "INVALID_ARGUMENT",
|
|
1987
|
-
message: "tab_id must be a number when provided.",
|
|
1988
|
-
retryable: false
|
|
1989
|
-
});
|
|
2454
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2455
|
+
if (!tabTarget.ok) {
|
|
2456
|
+
respondError(tabTarget.error);
|
|
1990
2457
|
return;
|
|
1991
2458
|
}
|
|
1992
|
-
|
|
1993
|
-
tabId = await getDefaultTabId();
|
|
1994
|
-
}
|
|
2459
|
+
const tabId = tabTarget.tabId;
|
|
1995
2460
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
1996
2461
|
if (error) {
|
|
1997
2462
|
respondError(error);
|
|
@@ -2008,7 +2473,7 @@ var DriveSocket = class {
|
|
|
2008
2473
|
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
2009
2474
|
);
|
|
2010
2475
|
this.touchDebuggerSession(tabId);
|
|
2011
|
-
respondOk({ ok: true });
|
|
2476
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2012
2477
|
} catch (error2) {
|
|
2013
2478
|
const info = mapDebuggerErrorMessage(
|
|
2014
2479
|
error2 instanceof Error ? error2.message : "Dialog handling failed."
|
|
@@ -2019,18 +2484,12 @@ var DriveSocket = class {
|
|
|
2019
2484
|
}
|
|
2020
2485
|
case "drive.click": {
|
|
2021
2486
|
const params = message.params ?? {};
|
|
2022
|
-
|
|
2023
|
-
if (
|
|
2024
|
-
respondError(
|
|
2025
|
-
code: "INVALID_ARGUMENT",
|
|
2026
|
-
message: "tab_id must be a number when provided.",
|
|
2027
|
-
retryable: false
|
|
2028
|
-
});
|
|
2487
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2488
|
+
if (!tabTarget.ok) {
|
|
2489
|
+
respondError(tabTarget.error);
|
|
2029
2490
|
return;
|
|
2030
2491
|
}
|
|
2031
|
-
|
|
2032
|
-
tabId = await getDefaultTabId();
|
|
2033
|
-
}
|
|
2492
|
+
const tabId = tabTarget.tabId;
|
|
2034
2493
|
const clickCount = params.click_count;
|
|
2035
2494
|
const count = typeof clickCount === "number" && Number.isFinite(clickCount) ? Math.max(1, Math.floor(clickCount)) : 1;
|
|
2036
2495
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
@@ -2046,31 +2505,48 @@ var DriveSocket = class {
|
|
|
2046
2505
|
respondError(pointResult.error);
|
|
2047
2506
|
return;
|
|
2048
2507
|
}
|
|
2049
|
-
const { x, y } = pointResult.point;
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2508
|
+
const { x, y, targetState } = pointResult.point;
|
|
2509
|
+
if (targetState) {
|
|
2510
|
+
const verified = await verifyPopupTriggerClick({
|
|
2511
|
+
clickCount: count,
|
|
2512
|
+
locator: params.locator,
|
|
2513
|
+
point: pointResult.point,
|
|
2514
|
+
prepareTarget: async () => await this.focusLocator(tabId, params.locator),
|
|
2515
|
+
resolveLocatorPoint: async (locator) => await this.resolveLocatorPoint(tabId, locator),
|
|
2516
|
+
dispatchCdpClick: async (clickX, clickY, clickCount2) => await this.dispatchCdpClick(tabId, clickX, clickY, clickCount2),
|
|
2517
|
+
mapDispatchError: (error2) => mapDebuggerErrorMessage(
|
|
2518
|
+
error2 instanceof Error ? error2.message : "Click dispatch failed."
|
|
2519
|
+
),
|
|
2520
|
+
delayMs
|
|
2521
|
+
});
|
|
2522
|
+
if (!verified.ok) {
|
|
2523
|
+
respondError(verified.error);
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
try {
|
|
2530
|
+
await this.dispatchCdpClick(tabId, x, y, count);
|
|
2531
|
+
} catch (error2) {
|
|
2532
|
+
respondError(
|
|
2533
|
+
mapDebuggerErrorMessage(
|
|
2534
|
+
error2 instanceof Error ? error2.message : "Click dispatch failed."
|
|
2535
|
+
)
|
|
2055
2536
|
);
|
|
2056
|
-
|
|
2057
|
-
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2058
2540
|
return;
|
|
2059
2541
|
}
|
|
2060
2542
|
case "drive.hover": {
|
|
2061
2543
|
const params = message.params ?? {};
|
|
2062
|
-
|
|
2063
|
-
if (
|
|
2064
|
-
respondError(
|
|
2065
|
-
code: "INVALID_ARGUMENT",
|
|
2066
|
-
message: "tab_id must be a number when provided.",
|
|
2067
|
-
retryable: false
|
|
2068
|
-
});
|
|
2544
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2545
|
+
if (!tabTarget.ok) {
|
|
2546
|
+
respondError(tabTarget.error);
|
|
2069
2547
|
return;
|
|
2070
2548
|
}
|
|
2071
|
-
|
|
2072
|
-
tabId = await getDefaultTabId();
|
|
2073
|
-
}
|
|
2549
|
+
const tabId = tabTarget.tabId;
|
|
2074
2550
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2075
2551
|
if (error) {
|
|
2076
2552
|
respondError(error);
|
|
@@ -2091,15 +2567,17 @@ var DriveSocket = class {
|
|
|
2091
2567
|
if (waitMs > 0) {
|
|
2092
2568
|
await delayMs(waitMs);
|
|
2093
2569
|
}
|
|
2094
|
-
const snapshot = await sendToTab(
|
|
2095
|
-
tabId,
|
|
2096
|
-
"drive.snapshot_html"
|
|
2097
|
-
);
|
|
2570
|
+
const snapshot = await sendToTab(tabId, "drive.snapshot_html");
|
|
2098
2571
|
if (!snapshot.ok) {
|
|
2099
2572
|
respondError(snapshot.error);
|
|
2100
2573
|
return;
|
|
2101
2574
|
}
|
|
2102
|
-
respondOk(
|
|
2575
|
+
respondOk(
|
|
2576
|
+
await withResolvedTabTarget(
|
|
2577
|
+
tabId,
|
|
2578
|
+
snapshot.result ?? { format: "html", snapshot: "" }
|
|
2579
|
+
)
|
|
2580
|
+
);
|
|
2103
2581
|
} catch (error2) {
|
|
2104
2582
|
const info = mapDebuggerErrorMessage(
|
|
2105
2583
|
error2 instanceof Error ? error2.message : "Hover dispatch failed."
|
|
@@ -2110,35 +2588,23 @@ var DriveSocket = class {
|
|
|
2110
2588
|
}
|
|
2111
2589
|
case "drive.drag": {
|
|
2112
2590
|
const params = message.params ?? {};
|
|
2113
|
-
|
|
2114
|
-
if (
|
|
2115
|
-
respondError(
|
|
2116
|
-
code: "INVALID_ARGUMENT",
|
|
2117
|
-
message: "tab_id must be a number when provided.",
|
|
2118
|
-
retryable: false
|
|
2119
|
-
});
|
|
2591
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2592
|
+
if (!tabTarget.ok) {
|
|
2593
|
+
respondError(tabTarget.error);
|
|
2120
2594
|
return;
|
|
2121
2595
|
}
|
|
2122
|
-
|
|
2123
|
-
tabId = await getDefaultTabId();
|
|
2124
|
-
}
|
|
2596
|
+
const tabId = tabTarget.tabId;
|
|
2125
2597
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2126
2598
|
if (error) {
|
|
2127
2599
|
respondError(error);
|
|
2128
2600
|
return;
|
|
2129
2601
|
}
|
|
2130
|
-
const fromResult = await this.resolveLocatorPoint(
|
|
2131
|
-
tabId,
|
|
2132
|
-
params.from
|
|
2133
|
-
);
|
|
2602
|
+
const fromResult = await this.resolveLocatorPoint(tabId, params.from);
|
|
2134
2603
|
if (!fromResult.ok) {
|
|
2135
2604
|
respondError(fromResult.error);
|
|
2136
2605
|
return;
|
|
2137
2606
|
}
|
|
2138
|
-
const toResult = await this.resolveLocatorPoint(
|
|
2139
|
-
tabId,
|
|
2140
|
-
params.to
|
|
2141
|
-
);
|
|
2607
|
+
const toResult = await this.resolveLocatorPoint(tabId, params.to);
|
|
2142
2608
|
if (!toResult.ok) {
|
|
2143
2609
|
respondError(toResult.error);
|
|
2144
2610
|
return;
|
|
@@ -2151,7 +2617,7 @@ var DriveSocket = class {
|
|
|
2151
2617
|
toResult.point,
|
|
2152
2618
|
steps
|
|
2153
2619
|
);
|
|
2154
|
-
respondOk({ ok: true });
|
|
2620
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2155
2621
|
} catch (error2) {
|
|
2156
2622
|
const info = mapDebuggerErrorMessage(
|
|
2157
2623
|
error2 instanceof Error ? error2.message : "Drag dispatch failed."
|
|
@@ -2171,30 +2637,20 @@ var DriveSocket = class {
|
|
|
2171
2637
|
});
|
|
2172
2638
|
return;
|
|
2173
2639
|
}
|
|
2174
|
-
|
|
2175
|
-
if (
|
|
2176
|
-
respondError(
|
|
2177
|
-
code: "INVALID_ARGUMENT",
|
|
2178
|
-
message: "tab_id must be a number when provided.",
|
|
2179
|
-
retryable: false
|
|
2180
|
-
});
|
|
2640
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2641
|
+
if (!tabTarget.ok) {
|
|
2642
|
+
respondError(tabTarget.error);
|
|
2181
2643
|
return;
|
|
2182
2644
|
}
|
|
2183
|
-
|
|
2184
|
-
tabId = await getDefaultTabId();
|
|
2185
|
-
}
|
|
2645
|
+
const tabId = tabTarget.tabId;
|
|
2186
2646
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2187
2647
|
if (error) {
|
|
2188
2648
|
respondError(error);
|
|
2189
2649
|
return;
|
|
2190
2650
|
}
|
|
2191
2651
|
try {
|
|
2192
|
-
await this.dispatchCdpKeyPress(
|
|
2193
|
-
|
|
2194
|
-
key,
|
|
2195
|
-
params.modifiers
|
|
2196
|
-
);
|
|
2197
|
-
respondOk({ ok: true });
|
|
2652
|
+
await this.dispatchCdpKeyPress(tabId, key, params.modifiers);
|
|
2653
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2198
2654
|
} catch (error2) {
|
|
2199
2655
|
const info = mapDebuggerErrorMessage(
|
|
2200
2656
|
error2 instanceof Error ? error2.message : "Keyboard dispatch failed."
|
|
@@ -2214,18 +2670,12 @@ var DriveSocket = class {
|
|
|
2214
2670
|
});
|
|
2215
2671
|
return;
|
|
2216
2672
|
}
|
|
2217
|
-
|
|
2218
|
-
if (
|
|
2219
|
-
respondError(
|
|
2220
|
-
code: "INVALID_ARGUMENT",
|
|
2221
|
-
message: "tab_id must be a number when provided.",
|
|
2222
|
-
retryable: false
|
|
2223
|
-
});
|
|
2673
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2674
|
+
if (!tabTarget.ok) {
|
|
2675
|
+
respondError(tabTarget.error);
|
|
2224
2676
|
return;
|
|
2225
2677
|
}
|
|
2226
|
-
|
|
2227
|
-
tabId = await getDefaultTabId();
|
|
2228
|
-
}
|
|
2678
|
+
const tabId = tabTarget.tabId;
|
|
2229
2679
|
const count = typeof params.repeat === "number" && Number.isFinite(params.repeat) ? Math.max(1, Math.min(50, Math.floor(params.repeat))) : 1;
|
|
2230
2680
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2231
2681
|
if (error) {
|
|
@@ -2234,13 +2684,9 @@ var DriveSocket = class {
|
|
|
2234
2684
|
}
|
|
2235
2685
|
try {
|
|
2236
2686
|
for (let i = 0; i < count; i += 1) {
|
|
2237
|
-
await this.dispatchCdpKeyPress(
|
|
2238
|
-
tabId,
|
|
2239
|
-
key,
|
|
2240
|
-
params.modifiers
|
|
2241
|
-
);
|
|
2687
|
+
await this.dispatchCdpKeyPress(tabId, key, params.modifiers);
|
|
2242
2688
|
}
|
|
2243
|
-
respondOk({ ok: true });
|
|
2689
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2244
2690
|
} catch (error2) {
|
|
2245
2691
|
const info = mapDebuggerErrorMessage(
|
|
2246
2692
|
error2 instanceof Error ? error2.message : "Keyboard dispatch failed."
|
|
@@ -2260,18 +2706,12 @@ var DriveSocket = class {
|
|
|
2260
2706
|
});
|
|
2261
2707
|
return;
|
|
2262
2708
|
}
|
|
2263
|
-
|
|
2264
|
-
if (
|
|
2265
|
-
respondError(
|
|
2266
|
-
code: "INVALID_ARGUMENT",
|
|
2267
|
-
message: "tab_id must be a number when provided.",
|
|
2268
|
-
retryable: false
|
|
2269
|
-
});
|
|
2709
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2710
|
+
if (!tabTarget.ok) {
|
|
2711
|
+
respondError(tabTarget.error);
|
|
2270
2712
|
return;
|
|
2271
2713
|
}
|
|
2272
|
-
|
|
2273
|
-
tabId = await getDefaultTabId();
|
|
2274
|
-
}
|
|
2714
|
+
const tabId = tabTarget.tabId;
|
|
2275
2715
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2276
2716
|
if (error) {
|
|
2277
2717
|
respondError(error);
|
|
@@ -2287,23 +2727,17 @@ var DriveSocket = class {
|
|
|
2287
2727
|
respondError(result.error);
|
|
2288
2728
|
return;
|
|
2289
2729
|
}
|
|
2290
|
-
respondOk({ ok: true });
|
|
2730
|
+
respondOk(await withResolvedTabTarget(tabId, { ok: true }));
|
|
2291
2731
|
return;
|
|
2292
2732
|
}
|
|
2293
2733
|
case "drive.select": {
|
|
2294
2734
|
const params = message.params ?? {};
|
|
2295
|
-
|
|
2296
|
-
if (
|
|
2297
|
-
respondError(
|
|
2298
|
-
code: "INVALID_ARGUMENT",
|
|
2299
|
-
message: "tab_id must be a number when provided.",
|
|
2300
|
-
retryable: false
|
|
2301
|
-
});
|
|
2735
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2736
|
+
if (!tabTarget.ok) {
|
|
2737
|
+
respondError(tabTarget.error);
|
|
2302
2738
|
return;
|
|
2303
2739
|
}
|
|
2304
|
-
|
|
2305
|
-
tabId = await getDefaultTabId();
|
|
2306
|
-
}
|
|
2740
|
+
const tabId = tabTarget.tabId;
|
|
2307
2741
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2308
2742
|
if (error) {
|
|
2309
2743
|
respondError(error);
|
|
@@ -2331,16 +2765,17 @@ var DriveSocket = class {
|
|
|
2331
2765
|
respondError(info);
|
|
2332
2766
|
return;
|
|
2333
2767
|
}
|
|
2334
|
-
const selectResult = await sendToTab(
|
|
2335
|
-
tabId,
|
|
2336
|
-
"drive.select",
|
|
2337
|
-
params
|
|
2338
|
-
);
|
|
2768
|
+
const selectResult = await sendToTab(tabId, "drive.select", params);
|
|
2339
2769
|
if (!selectResult.ok) {
|
|
2340
2770
|
respondError(selectResult.error);
|
|
2341
2771
|
return;
|
|
2342
2772
|
}
|
|
2343
|
-
respondOk(
|
|
2773
|
+
respondOk(
|
|
2774
|
+
await withResolvedTabTarget(
|
|
2775
|
+
tabId,
|
|
2776
|
+
selectResult.result ?? { ok: true }
|
|
2777
|
+
)
|
|
2778
|
+
);
|
|
2344
2779
|
return;
|
|
2345
2780
|
}
|
|
2346
2781
|
case "drive.fill_form": {
|
|
@@ -2354,18 +2789,12 @@ var DriveSocket = class {
|
|
|
2354
2789
|
});
|
|
2355
2790
|
return;
|
|
2356
2791
|
}
|
|
2357
|
-
|
|
2358
|
-
if (
|
|
2359
|
-
respondError(
|
|
2360
|
-
code: "INVALID_ARGUMENT",
|
|
2361
|
-
message: "tab_id must be a number when provided.",
|
|
2362
|
-
retryable: false
|
|
2363
|
-
});
|
|
2792
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2793
|
+
if (!tabTarget.ok) {
|
|
2794
|
+
respondError(tabTarget.error);
|
|
2364
2795
|
return;
|
|
2365
2796
|
}
|
|
2366
|
-
|
|
2367
|
-
tabId = await getDefaultTabId();
|
|
2368
|
-
}
|
|
2797
|
+
const tabId = tabTarget.tabId;
|
|
2369
2798
|
const error = await this.ensureDebuggerAttached(tabId);
|
|
2370
2799
|
if (error) {
|
|
2371
2800
|
respondError(error);
|
|
@@ -2426,13 +2855,9 @@ var DriveSocket = class {
|
|
|
2426
2855
|
filled += 1;
|
|
2427
2856
|
continue;
|
|
2428
2857
|
}
|
|
2429
|
-
const fallback = await sendToTab(
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
{
|
|
2433
|
-
fields: [field]
|
|
2434
|
-
}
|
|
2435
|
-
);
|
|
2858
|
+
const fallback = await sendToTab(tabId, "drive.fill_form", {
|
|
2859
|
+
fields: [field]
|
|
2860
|
+
});
|
|
2436
2861
|
if (!fallback.ok) {
|
|
2437
2862
|
errors.push(
|
|
2438
2863
|
`Field ${index} could not be filled: ${fallback.error.message}`
|
|
@@ -2451,39 +2876,32 @@ var DriveSocket = class {
|
|
|
2451
2876
|
}
|
|
2452
2877
|
errors.push(`Field ${index} could not be filled.`);
|
|
2453
2878
|
}
|
|
2454
|
-
respondOk(
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2879
|
+
respondOk(
|
|
2880
|
+
await withResolvedTabTarget(tabId, {
|
|
2881
|
+
filled,
|
|
2882
|
+
attempted: fields.length,
|
|
2883
|
+
errors: errors.length > 0 ? errors : []
|
|
2884
|
+
})
|
|
2885
|
+
);
|
|
2459
2886
|
return;
|
|
2460
2887
|
}
|
|
2461
2888
|
case "drive.scroll":
|
|
2462
2889
|
case "drive.wait_for": {
|
|
2463
2890
|
const params = message.params ?? {};
|
|
2464
|
-
|
|
2465
|
-
if (
|
|
2466
|
-
respondError(
|
|
2467
|
-
code: "INVALID_ARGUMENT",
|
|
2468
|
-
message: "tab_id must be a number when provided.",
|
|
2469
|
-
retryable: false
|
|
2470
|
-
});
|
|
2891
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2892
|
+
if (!tabTarget.ok) {
|
|
2893
|
+
respondError(tabTarget.error);
|
|
2471
2894
|
return;
|
|
2472
2895
|
}
|
|
2473
|
-
|
|
2474
|
-
tabId = await getDefaultTabId();
|
|
2475
|
-
}
|
|
2896
|
+
const tabId = tabTarget.tabId;
|
|
2476
2897
|
const timeoutMs = message.action === "drive.wait_for" && typeof params.timeout_ms === "number" && Number.isFinite(params.timeout_ms) ? Math.max(1, Math.floor(params.timeout_ms) + 1e3) : void 0;
|
|
2477
|
-
const result = await sendToTab(
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
params,
|
|
2481
|
-
{
|
|
2482
|
-
timeoutMs
|
|
2483
|
-
}
|
|
2484
|
-
);
|
|
2898
|
+
const result = await sendToTab(tabId, message.action, params, {
|
|
2899
|
+
timeoutMs
|
|
2900
|
+
});
|
|
2485
2901
|
if (result.ok) {
|
|
2486
|
-
respondOk(
|
|
2902
|
+
respondOk(
|
|
2903
|
+
await withResolvedTabTarget(tabId, result.result ?? { ok: true })
|
|
2904
|
+
);
|
|
2487
2905
|
} else {
|
|
2488
2906
|
respondError(result.error);
|
|
2489
2907
|
}
|
|
@@ -2491,30 +2909,25 @@ var DriveSocket = class {
|
|
|
2491
2909
|
}
|
|
2492
2910
|
case "drive.screenshot": {
|
|
2493
2911
|
const params = message.params ?? {};
|
|
2494
|
-
|
|
2495
|
-
if (
|
|
2496
|
-
respondError(
|
|
2497
|
-
code: "INVALID_ARGUMENT",
|
|
2498
|
-
message: "tab_id must be a number when provided.",
|
|
2499
|
-
retryable: false
|
|
2500
|
-
});
|
|
2912
|
+
const tabTarget = await resolveActionTabId(params);
|
|
2913
|
+
if (!tabTarget.ok) {
|
|
2914
|
+
respondError(tabTarget.error);
|
|
2501
2915
|
return;
|
|
2502
2916
|
}
|
|
2503
|
-
|
|
2504
|
-
tabId = await getDefaultTabId();
|
|
2505
|
-
}
|
|
2917
|
+
const tabId = tabTarget.tabId;
|
|
2506
2918
|
const mode = params.mode === "full_page" || params.mode === "viewport" || params.mode === "element" ? params.mode : "viewport";
|
|
2507
2919
|
const format = params.format === "jpeg" || params.format === "webp" ? params.format : "png";
|
|
2508
2920
|
const quality = typeof params.quality === "number" && Number.isFinite(params.quality) ? Math.max(0, Math.min(100, Math.floor(params.quality))) : void 0;
|
|
2509
2921
|
const tab = await getTab(tabId);
|
|
2510
2922
|
const url = tab.url;
|
|
2511
2923
|
if (typeof url === "string" && isRestrictedUrl(url)) {
|
|
2512
|
-
respondError(
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2924
|
+
respondError(
|
|
2925
|
+
buildRestrictedUrlError({
|
|
2926
|
+
url,
|
|
2927
|
+
operation: "screenshot",
|
|
2928
|
+
action: "drive.screenshot"
|
|
2929
|
+
})
|
|
2930
|
+
);
|
|
2518
2931
|
return;
|
|
2519
2932
|
}
|
|
2520
2933
|
const windowId = tab.windowId;
|
|
@@ -2679,7 +3092,7 @@ var DriveSocket = class {
|
|
|
2679
3092
|
format,
|
|
2680
3093
|
quality
|
|
2681
3094
|
);
|
|
2682
|
-
respondOk(rendered);
|
|
3095
|
+
respondOk(await withResolvedTabTarget(tabId, rendered));
|
|
2683
3096
|
} catch (error) {
|
|
2684
3097
|
respondError(
|
|
2685
3098
|
mapScreenshotCaptureError(
|
|
@@ -2786,7 +3199,12 @@ var DriveSocket = class {
|
|
|
2786
3199
|
srcW2,
|
|
2787
3200
|
srcH2
|
|
2788
3201
|
);
|
|
2789
|
-
respondOk(
|
|
3202
|
+
respondOk(
|
|
3203
|
+
await withResolvedTabTarget(
|
|
3204
|
+
tabId,
|
|
3205
|
+
await canvasToResult(cropCanvas2)
|
|
3206
|
+
)
|
|
3207
|
+
);
|
|
2790
3208
|
} finally {
|
|
2791
3209
|
bitmap.close();
|
|
2792
3210
|
}
|
|
@@ -2816,7 +3234,12 @@ var DriveSocket = class {
|
|
|
2816
3234
|
srcW,
|
|
2817
3235
|
srcH
|
|
2818
3236
|
);
|
|
2819
|
-
respondOk(
|
|
3237
|
+
respondOk(
|
|
3238
|
+
await withResolvedTabTarget(
|
|
3239
|
+
tabId,
|
|
3240
|
+
await canvasToResult(cropCanvas)
|
|
3241
|
+
)
|
|
3242
|
+
);
|
|
2820
3243
|
} catch (error) {
|
|
2821
3244
|
respondError(
|
|
2822
3245
|
mapScreenshotCaptureError(
|
|
@@ -2835,7 +3258,12 @@ var DriveSocket = class {
|
|
|
2835
3258
|
try {
|
|
2836
3259
|
const metaInfo = await getMetaInfo();
|
|
2837
3260
|
const canvas = await captureFullPageCanvas(metaInfo);
|
|
2838
|
-
respondOk(
|
|
3261
|
+
respondOk(
|
|
3262
|
+
await withResolvedTabTarget(
|
|
3263
|
+
tabId,
|
|
3264
|
+
await canvasToResult(canvas)
|
|
3265
|
+
)
|
|
3266
|
+
);
|
|
2839
3267
|
} catch (error) {
|
|
2840
3268
|
respondError(
|
|
2841
3269
|
mapScreenshotCaptureError(
|
|
@@ -2973,7 +3401,23 @@ var DriveSocket = class {
|
|
|
2973
3401
|
}
|
|
2974
3402
|
};
|
|
2975
3403
|
}
|
|
2976
|
-
|
|
3404
|
+
const targetState = coercePopupTriggerState(record.target_state);
|
|
3405
|
+
return {
|
|
3406
|
+
ok: true,
|
|
3407
|
+
point: {
|
|
3408
|
+
x,
|
|
3409
|
+
y,
|
|
3410
|
+
...targetState ? { targetState } : {}
|
|
3411
|
+
}
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
async focusLocator(tabId, locator) {
|
|
3415
|
+
const focused = await sendToTab(tabId, "drive.focus_locator", {
|
|
3416
|
+
locator
|
|
3417
|
+
});
|
|
3418
|
+
if (!focused.ok) {
|
|
3419
|
+
throw new Error(focused.error.message);
|
|
3420
|
+
}
|
|
2977
3421
|
}
|
|
2978
3422
|
async performCdpType(tabId, options) {
|
|
2979
3423
|
const targetPoint = await sendToTab(tabId, "drive.type_target_point", {
|
|
@@ -3163,132 +3607,25 @@ var DriveSocket = class {
|
|
|
3163
3607
|
error: sanitizeDriveErrorInfo(error)
|
|
3164
3608
|
});
|
|
3165
3609
|
};
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
respondError(error);
|
|
3182
|
-
return;
|
|
3183
|
-
}
|
|
3184
|
-
respondAck({ ok: true });
|
|
3185
|
-
return;
|
|
3186
|
-
}
|
|
3187
|
-
case "debugger.detach": {
|
|
3188
|
-
const params = message.params ?? {};
|
|
3189
|
-
const tabId = params.tab_id;
|
|
3190
|
-
if (typeof tabId !== "number") {
|
|
3191
|
-
respondError({
|
|
3192
|
-
code: "INVALID_ARGUMENT",
|
|
3193
|
-
message: "tab_id must be a number.",
|
|
3194
|
-
retryable: false
|
|
3195
|
-
});
|
|
3196
|
-
return;
|
|
3197
|
-
}
|
|
3198
|
-
const error = await this.detachDebugger(tabId);
|
|
3199
|
-
if (error) {
|
|
3200
|
-
respondError(error);
|
|
3201
|
-
return;
|
|
3202
|
-
}
|
|
3203
|
-
respondAck({ ok: true });
|
|
3204
|
-
return;
|
|
3205
|
-
}
|
|
3206
|
-
case "debugger.command": {
|
|
3207
|
-
const params = message.params ?? {};
|
|
3208
|
-
const tabId = params.tab_id;
|
|
3209
|
-
if (typeof tabId !== "number") {
|
|
3210
|
-
respondError({
|
|
3211
|
-
code: "INVALID_ARGUMENT",
|
|
3212
|
-
message: "tab_id must be a number.",
|
|
3213
|
-
retryable: false
|
|
3214
|
-
});
|
|
3215
|
-
return;
|
|
3216
|
-
}
|
|
3217
|
-
if (typeof params.method !== "string" || params.method.length === 0) {
|
|
3218
|
-
respondError({
|
|
3219
|
-
code: "INVALID_ARGUMENT",
|
|
3220
|
-
message: "method must be a non-empty string.",
|
|
3221
|
-
retryable: false
|
|
3222
|
-
});
|
|
3223
|
-
return;
|
|
3224
|
-
}
|
|
3225
|
-
const session = this.debuggerSessions.get(tabId);
|
|
3226
|
-
if (session?.attachPromise) {
|
|
3227
|
-
try {
|
|
3228
|
-
await session.attachPromise;
|
|
3229
|
-
} catch (error) {
|
|
3230
|
-
const info = mapDebuggerErrorMessage(
|
|
3231
|
-
error instanceof Error ? error.message : "Debugger attach failed."
|
|
3232
|
-
);
|
|
3233
|
-
this.clearDebuggerSession(tabId);
|
|
3234
|
-
respondError(info);
|
|
3235
|
-
return;
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
const attachedSession = this.debuggerSessions.get(tabId);
|
|
3239
|
-
if (!attachedSession?.attached) {
|
|
3240
|
-
respondError({
|
|
3241
|
-
code: "FAILED_PRECONDITION",
|
|
3242
|
-
message: "Debugger is not attached to the requested tab.",
|
|
3243
|
-
retryable: false
|
|
3244
|
-
});
|
|
3245
|
-
return;
|
|
3246
|
-
}
|
|
3247
|
-
try {
|
|
3248
|
-
const result = await this.sendDebuggerCommand(
|
|
3249
|
-
tabId,
|
|
3250
|
-
params.method,
|
|
3251
|
-
params.params,
|
|
3252
|
-
DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
3253
|
-
);
|
|
3254
|
-
this.touchDebuggerSession(tabId);
|
|
3255
|
-
respondAck(result);
|
|
3256
|
-
} catch (error) {
|
|
3257
|
-
if (error instanceof DebuggerTimeoutError) {
|
|
3258
|
-
respondError({
|
|
3259
|
-
code: "TIMEOUT",
|
|
3260
|
-
message: error.message,
|
|
3261
|
-
retryable: true
|
|
3262
|
-
});
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
const info = mapDebuggerErrorMessage(
|
|
3266
|
-
error instanceof Error ? error.message : "Debugger command failed."
|
|
3267
|
-
);
|
|
3268
|
-
respondError(info);
|
|
3269
|
-
}
|
|
3270
|
-
return;
|
|
3271
|
-
}
|
|
3272
|
-
default:
|
|
3273
|
-
respondError({
|
|
3274
|
-
code: "NOT_IMPLEMENTED",
|
|
3275
|
-
message: `${message.action} not implemented in extension yet.`,
|
|
3276
|
-
retryable: false
|
|
3277
|
-
});
|
|
3610
|
+
await dispatchDebuggerRequest(
|
|
3611
|
+
message,
|
|
3612
|
+
{
|
|
3613
|
+
getSession: (tabId) => this.debuggerSessions.get(tabId),
|
|
3614
|
+
ensureDebuggerAttached: async (tabId) => await this.ensureDebuggerAttached(tabId),
|
|
3615
|
+
detachDebugger: async (tabId) => await this.detachDebugger(tabId),
|
|
3616
|
+
sendDebuggerCommand: async (tabId, method, params, timeoutMs) => await this.sendDebuggerCommand(tabId, method, params, timeoutMs),
|
|
3617
|
+
touchDebuggerSession: (tabId) => this.touchDebuggerSession(tabId),
|
|
3618
|
+
clearDebuggerSession: (tabId) => this.clearDebuggerSession(tabId),
|
|
3619
|
+
mapDebuggerErrorMessage,
|
|
3620
|
+
debuggerCommandTimeoutMs: DEFAULT_DEBUGGER_COMMAND_TIMEOUT_MS
|
|
3621
|
+
},
|
|
3622
|
+
{
|
|
3623
|
+
respondAck,
|
|
3624
|
+
respondError
|
|
3278
3625
|
}
|
|
3279
|
-
|
|
3280
|
-
const messageText = error instanceof Error ? error.message : "Unexpected debugger error.";
|
|
3281
|
-
respondError({
|
|
3282
|
-
code: "INSPECT_UNAVAILABLE",
|
|
3283
|
-
message: messageText,
|
|
3284
|
-
retryable: false
|
|
3285
|
-
});
|
|
3286
|
-
}
|
|
3626
|
+
);
|
|
3287
3627
|
}
|
|
3288
3628
|
async handleDebuggerEvent(source, method, params) {
|
|
3289
|
-
if (!await readDebuggerCapabilityEnabled()) {
|
|
3290
|
-
return;
|
|
3291
|
-
}
|
|
3292
3629
|
const tabId = source.tabId;
|
|
3293
3630
|
if (typeof tabId !== "number") {
|
|
3294
3631
|
return;
|
|
@@ -3307,9 +3644,6 @@ var DriveSocket = class {
|
|
|
3307
3644
|
return;
|
|
3308
3645
|
}
|
|
3309
3646
|
this.clearDebuggerSession(tabId);
|
|
3310
|
-
if (!await readDebuggerCapabilityEnabled()) {
|
|
3311
|
-
return;
|
|
3312
|
-
}
|
|
3313
3647
|
this.sendDebuggerEvent({
|
|
3314
3648
|
tab_id: tabId,
|
|
3315
3649
|
method: "Debugger.detached",
|
|
@@ -3402,12 +3736,11 @@ var DriveSocket = class {
|
|
|
3402
3736
|
const tab = await getTab(tabId);
|
|
3403
3737
|
const url = typeof tab.url === "string" ? tab.url : void 0;
|
|
3404
3738
|
if (isRestrictedUrl(url)) {
|
|
3405
|
-
return {
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
};
|
|
3739
|
+
return buildRestrictedUrlError({
|
|
3740
|
+
url: url ?? "about:blank",
|
|
3741
|
+
operation: "debugger",
|
|
3742
|
+
action: "debugger.attach"
|
|
3743
|
+
});
|
|
3411
3744
|
}
|
|
3412
3745
|
} catch (error) {
|
|
3413
3746
|
return mapDebuggerErrorMessage(
|
|
@@ -3542,6 +3875,17 @@ chrome.runtime.onConnect.addListener((port) => {
|
|
|
3542
3875
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
3543
3876
|
permissionPrompts.handleWindowRemoved(windowId);
|
|
3544
3877
|
});
|
|
3878
|
+
chrome.windows.onFocusChanged.addListener((windowId) => {
|
|
3879
|
+
if (windowId === chrome.windows.WINDOW_ID_NONE) {
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
void queryActiveTabIdInWindow(windowId).then((tabId) => {
|
|
3883
|
+
markTabActive(tabId);
|
|
3884
|
+
socket.sendTabReport();
|
|
3885
|
+
}).catch(() => {
|
|
3886
|
+
socket.sendTabReport();
|
|
3887
|
+
});
|
|
3888
|
+
});
|
|
3545
3889
|
chrome.tabs.onActivated.addListener((activeInfo) => {
|
|
3546
3890
|
markTabActive(activeInfo.tabId);
|
|
3547
3891
|
socket.sendTabReport();
|
|
@@ -3615,26 +3959,23 @@ chrome.storage.onChanged.addListener(
|
|
|
3615
3959
|
if (areaName !== "local") {
|
|
3616
3960
|
return;
|
|
3617
3961
|
}
|
|
3618
|
-
const
|
|
3619
|
-
if (!
|
|
3962
|
+
const corePortChange = changes[LEGACY_CORE_PORT_KEY];
|
|
3963
|
+
if (!corePortChange) {
|
|
3620
3964
|
return;
|
|
3621
3965
|
}
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
error
|
|
3627
|
-
);
|
|
3966
|
+
let work = Promise.resolve();
|
|
3967
|
+
if (corePortChange) {
|
|
3968
|
+
work = work.then(async () => {
|
|
3969
|
+
await clearLegacyCorePort(chrome.storage.local);
|
|
3628
3970
|
});
|
|
3629
|
-
return;
|
|
3630
3971
|
}
|
|
3631
|
-
void
|
|
3632
|
-
console.error(
|
|
3633
|
-
"DriveSocket refreshDebuggerCapabilityState failed:",
|
|
3634
|
-
error
|
|
3635
|
-
);
|
|
3972
|
+
void work.catch((error) => {
|
|
3973
|
+
console.error("DriveSocket storage change handling failed:", error);
|
|
3636
3974
|
});
|
|
3637
3975
|
}
|
|
3638
3976
|
);
|
|
3977
|
+
void clearLegacyCorePort(chrome.storage.local).catch((error) => {
|
|
3978
|
+
console.warn("Failed to clear legacy corePort storage.", error);
|
|
3979
|
+
});
|
|
3639
3980
|
socket.start();
|
|
3640
3981
|
//# sourceMappingURL=background.js.map
|