opendevbrowser 0.0.16 → 0.0.18
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/README.md +201 -79
- package/dist/annotate/agent-inbox-store.d.ts +58 -0
- package/dist/annotate/agent-inbox-store.d.ts.map +1 -0
- package/dist/annotate/agent-inbox.d.ts +25 -0
- package/dist/annotate/agent-inbox.d.ts.map +1 -0
- package/dist/annotate/direct-annotator.d.ts.map +1 -1
- package/dist/annotate/timeout-messages.d.ts +4 -0
- package/dist/annotate/timeout-messages.d.ts.map +1 -0
- package/dist/automation/coordinator.d.ts +55 -0
- package/dist/automation/coordinator.d.ts.map +1 -0
- package/dist/browser/annotation-manager.d.ts +7 -1
- package/dist/browser/annotation-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.d.ts +153 -48
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/canvas-client.d.ts +54 -0
- package/dist/browser/canvas-client.d.ts.map +1 -0
- package/dist/browser/canvas-code-sync-manager.d.ts +87 -0
- package/dist/browser/canvas-code-sync-manager.d.ts.map +1 -0
- package/dist/browser/canvas-manager.d.ts +122 -0
- package/dist/browser/canvas-manager.d.ts.map +1 -0
- package/dist/browser/canvas-runtime-preview-bridge.d.ts +20 -0
- package/dist/browser/canvas-runtime-preview-bridge.d.ts.map +1 -0
- package/dist/browser/canvas-session-sync-manager.d.ts +21 -0
- package/dist/browser/canvas-session-sync-manager.d.ts.map +1 -0
- package/dist/browser/global-challenge-coordinator.d.ts +27 -0
- package/dist/browser/global-challenge-coordinator.d.ts.map +1 -0
- package/dist/browser/manager-types.d.ts +179 -1
- package/dist/browser/manager-types.d.ts.map +1 -1
- package/dist/browser/ops-browser-manager.d.ts +114 -4
- package/dist/browser/ops-browser-manager.d.ts.map +1 -1
- package/dist/browser/ops-client.d.ts +17 -1
- package/dist/browser/ops-client.d.ts.map +1 -1
- package/dist/browser/playwright-runtime.d.ts +4 -0
- package/dist/browser/playwright-runtime.d.ts.map +1 -0
- package/dist/browser/review-surface.d.ts +9 -0
- package/dist/browser/review-surface.d.ts.map +1 -0
- package/dist/browser/screencast-recorder.d.ts +57 -0
- package/dist/browser/screencast-recorder.d.ts.map +1 -0
- package/dist/browser/session-inspector.d.ts +71 -0
- package/dist/browser/session-inspector.d.ts.map +1 -0
- package/dist/browser/session-store.d.ts +5 -1
- package/dist/browser/session-store.d.ts.map +1 -1
- package/dist/browser/system-chrome-cookies.d.ts +46 -0
- package/dist/browser/system-chrome-cookies.d.ts.map +1 -0
- package/dist/browser/target-manager.d.ts +1 -0
- package/dist/browser/target-manager.d.ts.map +1 -1
- package/dist/cache/chrome-locator.d.ts.map +1 -1
- package/dist/cache/chrome-user-data.d.ts +17 -0
- package/dist/cache/chrome-user-data.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/loader.d.ts +13 -0
- package/dist/canvas/adapter-plugins/loader.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/manifest.d.ts +146 -0
- package/dist/canvas/adapter-plugins/manifest.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/types.d.ts +83 -0
- package/dist/canvas/adapter-plugins/types.d.ts.map +1 -0
- package/dist/canvas/adapter-plugins/validator.d.ts +10 -0
- package/dist/canvas/adapter-plugins/validator.d.ts.map +1 -0
- package/dist/canvas/code-sync/apply-tsx.d.ts +25 -0
- package/dist/canvas/code-sync/apply-tsx.d.ts.map +1 -0
- package/dist/canvas/code-sync/graph.d.ts +5 -0
- package/dist/canvas/code-sync/graph.d.ts.map +1 -0
- package/dist/canvas/code-sync/hash.d.ts +3 -0
- package/dist/canvas/code-sync/hash.d.ts.map +1 -0
- package/dist/canvas/code-sync/import.d.ts +19 -0
- package/dist/canvas/code-sync/import.d.ts.map +1 -0
- package/dist/canvas/code-sync/manifest.d.ts +6 -0
- package/dist/canvas/code-sync/manifest.d.ts.map +1 -0
- package/dist/canvas/code-sync/tsx-adapter.d.ts +8 -0
- package/dist/canvas/code-sync/tsx-adapter.d.ts.map +1 -0
- package/dist/canvas/code-sync/types.d.ts +244 -0
- package/dist/canvas/code-sync/types.d.ts.map +1 -0
- package/dist/canvas/code-sync/write.d.ts +9 -0
- package/dist/canvas/code-sync/write.d.ts.map +1 -0
- package/dist/canvas/document-store.d.ts +91 -0
- package/dist/canvas/document-store.d.ts.map +1 -0
- package/dist/canvas/export.d.ts +12 -0
- package/dist/canvas/export.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/custom-elements-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/custom-elements-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/html-static-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/html-static-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/markup.d.ts +9 -0
- package/dist/canvas/framework-adapters/markup.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/react-tsx-v2.d.ts +3 -0
- package/dist/canvas/framework-adapters/react-tsx-v2.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/registry.d.ts +12 -0
- package/dist/canvas/framework-adapters/registry.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/svelte-sfc-v1.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/types.d.ts +57 -0
- package/dist/canvas/framework-adapters/types.d.ts.map +1 -0
- package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts +3 -0
- package/dist/canvas/framework-adapters/vue-sfc-v1.d.ts.map +1 -0
- package/dist/canvas/kits/catalog.d.ts +5 -0
- package/dist/canvas/kits/catalog.d.ts.map +1 -0
- package/dist/canvas/library-adapters/react/index.d.ts +3 -0
- package/dist/canvas/library-adapters/react/index.d.ts.map +1 -0
- package/dist/canvas/library-adapters/registry.d.ts +11 -0
- package/dist/canvas/library-adapters/registry.d.ts.map +1 -0
- package/dist/canvas/library-adapters/types.d.ts +43 -0
- package/dist/canvas/library-adapters/types.d.ts.map +1 -0
- package/dist/canvas/repo-store.d.ts +12 -0
- package/dist/canvas/repo-store.d.ts.map +1 -0
- package/dist/canvas/starters/catalog.d.ts +34 -0
- package/dist/canvas/starters/catalog.d.ts.map +1 -0
- package/dist/canvas/surface-palette.d.ts +15 -0
- package/dist/canvas/surface-palette.d.ts.map +1 -0
- package/dist/canvas/token-references.d.ts +22 -0
- package/dist/canvas/token-references.d.ts.map +1 -0
- package/dist/canvas/types.d.ts +594 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/canvas-runtime-preview-bridge-HBEHXM4T.js +7 -0
- package/dist/canvas-runtime-preview-bridge-HBEHXM4T.js.map +1 -0
- package/dist/challenges/action-loop.d.ts +13 -0
- package/dist/challenges/action-loop.d.ts.map +1 -0
- package/dist/challenges/capability-matrix.d.ts +3 -0
- package/dist/challenges/capability-matrix.d.ts.map +1 -0
- package/dist/challenges/evidence-bundle.d.ts +48 -0
- package/dist/challenges/evidence-bundle.d.ts.map +1 -0
- package/dist/challenges/governed-adapter-gateway.d.ts +4 -0
- package/dist/challenges/governed-adapter-gateway.d.ts.map +1 -0
- package/dist/challenges/human-yield-gate.d.ts +20 -0
- package/dist/challenges/human-yield-gate.d.ts.map +1 -0
- package/dist/challenges/index.d.ts +15 -0
- package/dist/challenges/index.d.ts.map +1 -0
- package/dist/challenges/interpreter.d.ts +3 -0
- package/dist/challenges/interpreter.d.ts.map +1 -0
- package/dist/challenges/optional-computer-use-bridge.d.ts +9 -0
- package/dist/challenges/optional-computer-use-bridge.d.ts.map +1 -0
- package/dist/challenges/orchestrator.d.ts +32 -0
- package/dist/challenges/orchestrator.d.ts.map +1 -0
- package/dist/challenges/outcome-recorder.d.ts +8 -0
- package/dist/challenges/outcome-recorder.d.ts.map +1 -0
- package/dist/challenges/owned-environment-lane.d.ts +3 -0
- package/dist/challenges/owned-environment-lane.d.ts.map +1 -0
- package/dist/challenges/policy-gate.d.ts +9 -0
- package/dist/challenges/policy-gate.d.ts.map +1 -0
- package/dist/challenges/sanctioned-identity-lane.d.ts +3 -0
- package/dist/challenges/sanctioned-identity-lane.d.ts.map +1 -0
- package/dist/challenges/service-adapter-lane.d.ts +3 -0
- package/dist/challenges/service-adapter-lane.d.ts.map +1 -0
- package/dist/challenges/strategy-selector.d.ts +10 -0
- package/dist/challenges/strategy-selector.d.ts.map +1 -0
- package/dist/challenges/types.d.ts +277 -0
- package/dist/challenges/types.d.ts.map +1 -0
- package/dist/challenges/verification-gate.d.ts +15 -0
- package/dist/challenges/verification-gate.d.ts.map +1 -0
- package/dist/chunk-5FZQJRBQ.js +15256 -0
- package/dist/chunk-5FZQJRBQ.js.map +1 -0
- package/dist/{chunk-7W3SPXIB.js → chunk-FUSXMW3G.js} +4 -1
- package/dist/chunk-L57D35TB.js +33513 -0
- package/dist/chunk-L57D35TB.js.map +1 -0
- package/dist/chunk-TBUCZX4A.js +34 -0
- package/dist/chunk-TBUCZX4A.js.map +1 -0
- package/dist/chunk-Y2KL55OG.js +59 -0
- package/dist/chunk-Y2KL55OG.js.map +1 -0
- package/dist/chunk-YBQECXZX.js +409 -0
- package/dist/chunk-YBQECXZX.js.map +1 -0
- package/dist/cli/args.d.ts +4 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/commands/annotate.d.ts +11 -0
- package/dist/cli/commands/annotate.d.ts.map +1 -1
- package/dist/cli/commands/artifacts.d.ts.map +1 -1
- package/dist/cli/commands/canvas.d.ts +45 -0
- package/dist/cli/commands/canvas.d.ts.map +1 -0
- package/dist/cli/commands/daemon.d.ts +7 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/desktop/accessibility-snapshot.d.ts +3 -0
- package/dist/cli/commands/desktop/accessibility-snapshot.d.ts.map +1 -0
- package/dist/cli/commands/desktop/active-window.d.ts +3 -0
- package/dist/cli/commands/desktop/active-window.d.ts.map +1 -0
- package/dist/cli/commands/desktop/capture-desktop.d.ts +3 -0
- package/dist/cli/commands/desktop/capture-desktop.d.ts.map +1 -0
- package/dist/cli/commands/desktop/capture-window.d.ts +3 -0
- package/dist/cli/commands/desktop/capture-window.d.ts.map +1 -0
- package/dist/cli/commands/desktop/shared.d.ts +19 -0
- package/dist/cli/commands/desktop/shared.d.ts.map +1 -0
- package/dist/cli/commands/desktop/status.d.ts +3 -0
- package/dist/cli/commands/desktop/status.d.ts.map +1 -0
- package/dist/cli/commands/desktop/windows.d.ts +3 -0
- package/dist/cli/commands/desktop/windows.d.ts.map +1 -0
- package/dist/cli/commands/devtools/dialog.d.ts +19 -0
- package/dist/cli/commands/devtools/dialog.d.ts.map +1 -0
- package/dist/cli/commands/devtools/perf.d.ts.map +1 -1
- package/dist/cli/commands/devtools/screencast-start.d.ts +20 -0
- package/dist/cli/commands/devtools/screencast-start.d.ts.map +1 -0
- package/dist/cli/commands/devtools/screencast-stop.d.ts +17 -0
- package/dist/cli/commands/devtools/screencast-stop.d.ts.map +1 -0
- package/dist/cli/commands/devtools/screenshot.d.ts +3 -0
- package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -1
- package/dist/cli/commands/dom/attr.d.ts.map +1 -1
- package/dist/cli/commands/dom/checked.d.ts.map +1 -1
- package/dist/cli/commands/dom/enabled.d.ts.map +1 -1
- package/dist/cli/commands/dom/html.d.ts.map +1 -1
- package/dist/cli/commands/dom/text.d.ts.map +1 -1
- package/dist/cli/commands/dom/value.d.ts.map +1 -1
- package/dist/cli/commands/dom/visible.d.ts.map +1 -1
- package/dist/cli/commands/export/clone-component.d.ts +9 -0
- package/dist/cli/commands/export/clone-component.d.ts.map +1 -1
- package/dist/cli/commands/export/clone-page.d.ts +8 -0
- package/dist/cli/commands/export/clone-page.d.ts.map +1 -1
- package/dist/cli/commands/interact/check.d.ts.map +1 -1
- package/dist/cli/commands/interact/click.d.ts.map +1 -1
- package/dist/cli/commands/interact/hover.d.ts.map +1 -1
- package/dist/cli/commands/interact/pointer-down.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-down.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-drag.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-drag.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-move.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-move.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-shared.d.ts +6 -0
- package/dist/cli/commands/interact/pointer-shared.d.ts.map +1 -0
- package/dist/cli/commands/interact/pointer-up.d.ts +7 -0
- package/dist/cli/commands/interact/pointer-up.d.ts.map +1 -0
- package/dist/cli/commands/interact/press.d.ts.map +1 -1
- package/dist/cli/commands/interact/scroll-into-view.d.ts.map +1 -1
- package/dist/cli/commands/interact/scroll.d.ts.map +1 -1
- package/dist/cli/commands/interact/select.d.ts.map +1 -1
- package/dist/cli/commands/interact/type.d.ts.map +1 -1
- package/dist/cli/commands/interact/uncheck.d.ts.map +1 -1
- package/dist/cli/commands/interact/upload.d.ts +18 -0
- package/dist/cli/commands/interact/upload.d.ts.map +1 -0
- package/dist/cli/commands/macro-resolve.d.ts +2 -0
- package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
- package/dist/cli/commands/native.d.ts +22 -8
- package/dist/cli/commands/native.d.ts.map +1 -1
- package/dist/cli/commands/nav/goto.d.ts.map +1 -1
- package/dist/cli/commands/nav/review.d.ts +7 -0
- package/dist/cli/commands/nav/review.d.ts.map +1 -0
- package/dist/cli/commands/nav/snapshot.d.ts.map +1 -1
- package/dist/cli/commands/nav/wait.d.ts.map +1 -1
- package/dist/cli/commands/pages/open.d.ts.map +1 -1
- package/dist/cli/commands/product-video.d.ts +2 -0
- package/dist/cli/commands/product-video.d.ts.map +1 -1
- package/dist/cli/commands/research.d.ts +3 -0
- package/dist/cli/commands/research.d.ts.map +1 -1
- package/dist/cli/commands/run.d.ts +14 -0
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/serve.d.ts +1 -21
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/session/connect.d.ts.map +1 -1
- package/dist/cli/commands/session/disconnect.d.ts.map +1 -1
- package/dist/cli/commands/session/inspector.d.ts +21 -0
- package/dist/cli/commands/session/inspector.d.ts.map +1 -0
- package/dist/cli/commands/session/launch.d.ts.map +1 -1
- package/dist/cli/commands/shopping.d.ts +5 -0
- package/dist/cli/commands/shopping.d.ts.map +1 -1
- package/dist/cli/commands/status.d.ts +2 -9
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/targets/new.d.ts.map +1 -1
- package/dist/cli/daemon-autostart.d.ts +11 -0
- package/dist/cli/daemon-autostart.d.ts.map +1 -1
- package/dist/cli/daemon-client.d.ts +3 -0
- package/dist/cli/daemon-client.d.ts.map +1 -1
- package/dist/cli/daemon-commands.d.ts.map +1 -1
- package/dist/cli/daemon-state.d.ts +16 -0
- package/dist/cli/daemon-state.d.ts.map +1 -1
- package/dist/cli/daemon-status.d.ts +7 -2
- package/dist/cli/daemon-status.d.ts.map +1 -1
- package/dist/cli/daemon.d.ts +1 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/help.d.ts +19 -3
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/index.js +2927 -932
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install-autostart-output.d.ts +6 -0
- package/dist/cli/install-autostart-output.d.ts.map +1 -0
- package/dist/cli/install-autostart-reconciliation.d.ts +23 -0
- package/dist/cli/install-autostart-reconciliation.d.ts.map +1 -0
- package/dist/cli/installers/skills.d.ts +42 -6
- package/dist/cli/installers/skills.d.ts.map +1 -1
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/remote-canvas-manager.d.ts +8 -0
- package/dist/cli/remote-canvas-manager.d.ts.map +1 -0
- package/dist/cli/remote-desktop-runtime.d.ts +15 -0
- package/dist/cli/remote-desktop-runtime.d.ts.map +1 -0
- package/dist/cli/remote-manager.d.ts +27 -3
- package/dist/cli/remote-manager.d.ts.map +1 -1
- package/dist/cli/remote-relay.d.ts +2 -0
- package/dist/cli/remote-relay.d.ts.map +1 -1
- package/dist/cli/transport-timeouts.d.ts +8 -0
- package/dist/cli/transport-timeouts.d.ts.map +1 -0
- package/dist/cli/utils/http.d.ts +9 -0
- package/dist/cli/utils/http.d.ts.map +1 -1
- package/dist/cli/utils/parse.d.ts +3 -0
- package/dist/cli/utils/parse.d.ts.map +1 -1
- package/dist/cli/utils/skills.d.ts +1 -2
- package/dist/cli/utils/skills.d.ts.map +1 -1
- package/dist/cli/utils/workflow-message.d.ts +2 -0
- package/dist/cli/utils/workflow-message.d.ts.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/core/bootstrap.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/logging.d.ts +3 -1
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/runtime-assemblies.d.ts +22 -0
- package/dist/core/runtime-assemblies.d.ts.map +1 -0
- package/dist/core/types.d.ts +17 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/desktop/audit.d.ts +37 -0
- package/dist/desktop/audit.d.ts.map +1 -0
- package/dist/desktop/errors.d.ts +7 -0
- package/dist/desktop/errors.d.ts.map +1 -0
- package/dist/desktop/index.d.ts +6 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/runtime.d.ts +26 -0
- package/dist/desktop/runtime.d.ts.map +1 -0
- package/dist/desktop/types.d.ts +76 -0
- package/dist/desktop/types.d.ts.map +1 -0
- package/dist/extension-extractor.d.ts +6 -0
- package/dist/extension-extractor.d.ts.map +1 -1
- package/dist/fs-UMRKOBNN.js +7 -0
- package/dist/fs-UMRKOBNN.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1221 -460
- package/dist/index.js.map +1 -1
- package/dist/integrations/figma/assets.d.ts +13 -0
- package/dist/integrations/figma/assets.d.ts.map +1 -0
- package/dist/integrations/figma/auth.d.ts +3 -0
- package/dist/integrations/figma/auth.d.ts.map +1 -0
- package/dist/integrations/figma/client.d.ts +42 -0
- package/dist/integrations/figma/client.d.ts.map +1 -0
- package/dist/integrations/figma/mappers.d.ts +23 -0
- package/dist/integrations/figma/mappers.d.ts.map +1 -0
- package/dist/integrations/figma/normalize.d.ts +99 -0
- package/dist/integrations/figma/normalize.d.ts.map +1 -0
- package/dist/integrations/figma/url.d.ts +17 -0
- package/dist/integrations/figma/url.d.ts.map +1 -0
- package/dist/integrations/figma/variables.d.ts +21 -0
- package/dist/integrations/figma/variables.d.ts.map +1 -0
- package/dist/macros/execute-runtime.d.ts +19 -0
- package/dist/macros/execute-runtime.d.ts.map +1 -0
- package/dist/macros/execute.d.ts +3 -1
- package/dist/macros/execute.d.ts.map +1 -1
- package/dist/{macros-NUBRM44Y.js → macros-ND2M7LWU.js} +2 -2
- package/dist/opendevbrowser.d.ts.map +1 -1
- package/dist/opendevbrowser.js +1221 -460
- package/dist/opendevbrowser.js.map +1 -1
- package/dist/providers/blocker.d.ts.map +1 -1
- package/dist/providers/browser-fallback.d.ts +30 -0
- package/dist/providers/browser-fallback.d.ts.map +1 -0
- package/dist/providers/constraint.d.ts +45 -0
- package/dist/providers/constraint.d.ts.map +1 -0
- package/dist/providers/index.d.ts +11 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/policy.d.ts.map +1 -1
- package/dist/providers/product-video-compiler.d.ts +92 -0
- package/dist/providers/product-video-compiler.d.ts.map +1 -0
- package/dist/providers/registry.d.ts +37 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/renderer.d.ts.map +1 -1
- package/dist/providers/research-compiler.d.ts +64 -0
- package/dist/providers/research-compiler.d.ts.map +1 -0
- package/dist/providers/research-executor.d.ts +27 -0
- package/dist/providers/research-executor.d.ts.map +1 -0
- package/dist/providers/runtime-bundle.d.ts +26 -0
- package/dist/providers/runtime-bundle.d.ts.map +1 -0
- package/dist/providers/runtime-factory.d.ts +6 -1
- package/dist/providers/runtime-factory.d.ts.map +1 -1
- package/dist/providers/runtime-policy.d.ts +24 -0
- package/dist/providers/runtime-policy.d.ts.map +1 -0
- package/dist/providers/shared/anti-bot-policy.d.ts +3 -2
- package/dist/providers/shared/anti-bot-policy.d.ts.map +1 -1
- package/dist/providers/shopping/index.d.ts +11 -1
- package/dist/providers/shopping/index.d.ts.map +1 -1
- package/dist/providers/shopping-compiler.d.ts +51 -0
- package/dist/providers/shopping-compiler.d.ts.map +1 -0
- package/dist/providers/shopping-executor.d.ts +18 -0
- package/dist/providers/shopping-executor.d.ts.map +1 -0
- package/dist/providers/shopping-postprocess.d.ts +46 -0
- package/dist/providers/shopping-postprocess.d.ts.map +1 -0
- package/dist/providers/shopping-workflow.d.ts +33 -0
- package/dist/providers/shopping-workflow.d.ts.map +1 -0
- package/dist/providers/social/platform.d.ts +2 -1
- package/dist/providers/social/platform.d.ts.map +1 -1
- package/dist/providers/social/search-quality.d.ts +16 -0
- package/dist/providers/social/search-quality.d.ts.map +1 -0
- package/dist/providers/social/youtube-resolver.d.ts +2 -1
- package/dist/providers/social/youtube-resolver.d.ts.map +1 -1
- package/dist/providers/social/youtube.d.ts.map +1 -1
- package/dist/providers/types.d.ts +116 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/web/crawl-worker.d.ts.map +1 -1
- package/dist/providers/web/extract.d.ts +16 -0
- package/dist/providers/web/extract.d.ts.map +1 -1
- package/dist/providers/web/index.d.ts.map +1 -1
- package/dist/providers/workflow-contracts.d.ts +53 -0
- package/dist/providers/workflow-contracts.d.ts.map +1 -0
- package/dist/providers/workflows.d.ts +30 -6
- package/dist/providers/workflows.d.ts.map +1 -1
- package/dist/providers-G36AM3Z2.js +121 -0
- package/dist/providers-G36AM3Z2.js.map +1 -0
- package/dist/public-surface/generated-manifest.d.ts +1168 -0
- package/dist/public-surface/generated-manifest.d.ts.map +1 -0
- package/dist/public-surface/source.d.ts +437 -0
- package/dist/public-surface/source.d.ts.map +1 -0
- package/dist/relay/protocol.d.ts +108 -4
- package/dist/relay/protocol.d.ts.map +1 -1
- package/dist/relay/relay-endpoints.d.ts +21 -0
- package/dist/relay/relay-endpoints.d.ts.map +1 -1
- package/dist/relay/relay-server.d.ts +32 -1
- package/dist/relay/relay-server.d.ts.map +1 -1
- package/dist/relay/relay-types.d.ts +3 -0
- package/dist/relay/relay-types.d.ts.map +1 -1
- package/dist/skills/bundled-skill-directories.d.ts +8 -0
- package/dist/skills/bundled-skill-directories.d.ts.map +1 -0
- package/dist/skills/skill-loader.d.ts +9 -1
- package/dist/skills/skill-loader.d.ts.map +1 -1
- package/dist/skills/skill-loader.js +7 -0
- package/dist/skills/skill-loader.js.map +1 -0
- package/dist/skills/skill-nudge.d.ts.map +1 -1
- package/dist/skills/types.d.ts +31 -0
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/snapshot/ops-snapshot.d.ts +1 -1
- package/dist/snapshot/ops-snapshot.d.ts.map +1 -1
- package/dist/snapshot/refs.d.ts +6 -1
- package/dist/snapshot/refs.d.ts.map +1 -1
- package/dist/snapshot/snapshotter.d.ts.map +1 -1
- package/dist/tools/annotate.d.ts.map +1 -1
- package/dist/tools/canvas.d.ts +4 -0
- package/dist/tools/canvas.d.ts.map +1 -0
- package/dist/tools/check.d.ts.map +1 -1
- package/dist/tools/click.d.ts.map +1 -1
- package/dist/tools/clone_component.d.ts.map +1 -1
- package/dist/tools/clone_page.d.ts.map +1 -1
- package/dist/tools/connect.d.ts.map +1 -1
- package/dist/tools/deps.d.ts +6 -0
- package/dist/tools/deps.d.ts.map +1 -1
- package/dist/tools/desktop-shared.d.ts +6 -0
- package/dist/tools/desktop-shared.d.ts.map +1 -0
- package/dist/tools/desktop_accessibility_snapshot.d.ts +4 -0
- package/dist/tools/desktop_accessibility_snapshot.d.ts.map +1 -0
- package/dist/tools/desktop_active_window.d.ts +4 -0
- package/dist/tools/desktop_active_window.d.ts.map +1 -0
- package/dist/tools/desktop_capture_desktop.d.ts +4 -0
- package/dist/tools/desktop_capture_desktop.d.ts.map +1 -0
- package/dist/tools/desktop_capture_window.d.ts +4 -0
- package/dist/tools/desktop_capture_window.d.ts.map +1 -0
- package/dist/tools/desktop_status.d.ts +4 -0
- package/dist/tools/desktop_status.d.ts.map +1 -0
- package/dist/tools/desktop_windows.d.ts +4 -0
- package/dist/tools/desktop_windows.d.ts.map +1 -0
- package/dist/tools/dialog.d.ts +4 -0
- package/dist/tools/dialog.d.ts.map +1 -0
- package/dist/tools/dom_get_html.d.ts.map +1 -1
- package/dist/tools/dom_get_text.d.ts.map +1 -1
- package/dist/tools/get_attr.d.ts.map +1 -1
- package/dist/tools/get_value.d.ts.map +1 -1
- package/dist/tools/goto.d.ts.map +1 -1
- package/dist/tools/hover.d.ts.map +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/is_checked.d.ts.map +1 -1
- package/dist/tools/is_enabled.d.ts.map +1 -1
- package/dist/tools/is_visible.d.ts.map +1 -1
- package/dist/tools/launch.d.ts.map +1 -1
- package/dist/tools/macro_resolve.d.ts.map +1 -1
- package/dist/tools/perf.d.ts.map +1 -1
- package/dist/tools/pointer_down.d.ts +4 -0
- package/dist/tools/pointer_down.d.ts.map +1 -0
- package/dist/tools/pointer_drag.d.ts +4 -0
- package/dist/tools/pointer_drag.d.ts.map +1 -0
- package/dist/tools/pointer_move.d.ts +4 -0
- package/dist/tools/pointer_move.d.ts.map +1 -0
- package/dist/tools/pointer_up.d.ts +4 -0
- package/dist/tools/pointer_up.d.ts.map +1 -0
- package/dist/tools/press.d.ts.map +1 -1
- package/dist/tools/product_video_run.d.ts.map +1 -1
- package/dist/tools/prompting_guide.d.ts.map +1 -1
- package/dist/tools/research_run.d.ts.map +1 -1
- package/dist/tools/response.d.ts +4 -1
- package/dist/tools/response.d.ts.map +1 -1
- package/dist/tools/review.d.ts +4 -0
- package/dist/tools/review.d.ts.map +1 -0
- package/dist/tools/screencast_start.d.ts +4 -0
- package/dist/tools/screencast_start.d.ts.map +1 -0
- package/dist/tools/screencast_stop.d.ts +4 -0
- package/dist/tools/screencast_stop.d.ts.map +1 -0
- package/dist/tools/screenshot.d.ts.map +1 -1
- package/dist/tools/scroll.d.ts.map +1 -1
- package/dist/tools/scroll_into_view.d.ts.map +1 -1
- package/dist/tools/select.d.ts.map +1 -1
- package/dist/tools/session_inspector.d.ts +4 -0
- package/dist/tools/session_inspector.d.ts.map +1 -0
- package/dist/tools/shopping_run.d.ts.map +1 -1
- package/dist/tools/skill_list.d.ts.map +1 -1
- package/dist/tools/skill_load.d.ts.map +1 -1
- package/dist/tools/snapshot.d.ts.map +1 -1
- package/dist/tools/type.d.ts.map +1 -1
- package/dist/tools/uncheck.d.ts.map +1 -1
- package/dist/tools/upload.d.ts +4 -0
- package/dist/tools/upload.d.ts.map +1 -0
- package/dist/tools/wait.d.ts.map +1 -1
- package/dist/tools/workflow-runtime.d.ts +4 -2
- package/dist/tools/workflow-runtime.d.ts.map +1 -1
- package/dist/utils/package-assets.d.ts +4 -0
- package/dist/utils/package-assets.d.ts.map +1 -0
- package/extension/canvas.html +1006 -0
- package/extension/dist/annotate-content.css +15 -6
- package/extension/dist/annotate-content.js +175 -35
- package/extension/dist/annotation-payload.js +199 -0
- package/extension/dist/background.js +544 -69
- package/extension/dist/canvas/canvas-runtime.js +1490 -0
- package/extension/dist/canvas/model.js +341 -0
- package/extension/dist/canvas/viewport-fit.js +67 -0
- package/extension/dist/canvas-page.js +3609 -0
- package/extension/dist/ops/dom-bridge.js +255 -3
- package/extension/dist/ops/ops-runtime.js +3324 -301
- package/extension/dist/ops/ops-session-store.js +97 -112
- package/extension/dist/ops/snapshot-builder.js +2 -2
- package/extension/dist/ops/snapshot-shared.js +2 -2
- package/extension/dist/ops/target-session-coordinator.js +159 -0
- package/extension/dist/popup.js +201 -42
- package/extension/dist/services/CDPRouter.js +1567 -63
- package/extension/dist/services/ConnectionManager.js +453 -78
- package/extension/dist/services/RelayClient.js +79 -30
- package/extension/dist/services/TabManager.js +118 -22
- package/extension/dist/services/TargetSessionMap.js +127 -3
- package/extension/dist/services/attach-errors.js +20 -0
- package/extension/dist/services/cdp-router-commands.js +135 -8
- package/extension/dist/services/url-restrictions.js +9 -13
- package/extension/dist/types.js +2 -0
- package/extension/manifest.json +2 -2
- package/extension/popup.html +59 -6
- package/package.json +19 -9
- package/skills/AGENTS.md +8 -4
- package/skills/opendevbrowser-best-practices/SKILL.md +183 -6
- package/skills/opendevbrowser-best-practices/artifacts/browser-agent-known-issues-matrix.md +1 -0
- package/skills/opendevbrowser-best-practices/artifacts/canvas-governance-playbook.md +141 -0
- package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +129 -19
- package/skills/opendevbrowser-best-practices/artifacts/parity-gates.md +9 -2
- package/skills/opendevbrowser-best-practices/artifacts/provider-workflows.md +6 -0
- package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +58 -0
- package/skills/opendevbrowser-best-practices/assets/templates/canvas-blocker-checklist.json +70 -0
- package/skills/opendevbrowser-best-practices/assets/templates/canvas-feedback-eval.json +73 -0
- package/skills/opendevbrowser-best-practices/assets/templates/canvas-generation-plan.v1.json +67 -0
- package/skills/opendevbrowser-best-practices/assets/templates/canvas-handshake-example.json +126 -0
- package/skills/opendevbrowser-best-practices/assets/templates/robustness-checklist.json +57 -0
- package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +674 -0
- package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +12 -3
- package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +107 -12
- package/skills/opendevbrowser-best-practices/scripts/resolve-odb-cli.sh +100 -0
- package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +83 -1
- package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +365 -84
- package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +208 -0
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +14 -1
- package/skills/opendevbrowser-continuity-ledger/scripts/validate-skill-assets.sh +61 -0
- package/skills/opendevbrowser-data-extraction/SKILL.md +6 -0
- package/skills/opendevbrowser-data-extraction/scripts/validate-skill-assets.sh +112 -0
- package/skills/opendevbrowser-design-agent/SKILL.md +275 -0
- package/skills/opendevbrowser-design-agent/artifacts/app-shell-and-state-wiring.md +84 -0
- package/skills/opendevbrowser-design-agent/artifacts/async-search-state-ownership.md +58 -0
- package/skills/opendevbrowser-design-agent/artifacts/component-pattern-index.md +130 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-contract-playbook.md +157 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-release-gate.md +40 -0
- package/skills/opendevbrowser-design-agent/artifacts/design-workflows.md +153 -0
- package/skills/opendevbrowser-design-agent/artifacts/existing-surface-adaptation.md +56 -0
- package/skills/opendevbrowser-design-agent/artifacts/external-pattern-synthesis.md +103 -0
- package/skills/opendevbrowser-design-agent/artifacts/frontend-evaluation-rubric.md +61 -0
- package/skills/opendevbrowser-design-agent/artifacts/implementation-anti-patterns.md +163 -0
- package/skills/opendevbrowser-design-agent/artifacts/isolated-preview-validation.md +68 -0
- package/skills/opendevbrowser-design-agent/artifacts/loading-and-feedback-surfaces.md +56 -0
- package/skills/opendevbrowser-design-agent/artifacts/opendevbrowser-ui-example-map.md +44 -0
- package/skills/opendevbrowser-design-agent/artifacts/performance-audit-playbook.md +70 -0
- package/skills/opendevbrowser-design-agent/artifacts/research-harvest-workflow.md +81 -0
- package/skills/opendevbrowser-design-agent/artifacts/scroll-reveal-surface-planning.md +64 -0
- package/skills/opendevbrowser-design-agent/artifacts/state-ownership-matrix.md +36 -0
- package/skills/opendevbrowser-design-agent/artifacts/theming-and-token-ownership.md +43 -0
- package/skills/opendevbrowser-design-agent/assets/templates/canvas-generation-plan.design.v1.json +58 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-audit-report.v1.md +34 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-brief.v1.md +40 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-contract.v1.json +226 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-release-gate.v1.json +35 -0
- package/skills/opendevbrowser-design-agent/assets/templates/design-review-checklist.json +57 -0
- package/skills/opendevbrowser-design-agent/assets/templates/real-surface-design-matrix.json +32 -0
- package/skills/opendevbrowser-design-agent/assets/templates/reference-pattern-board.v1.json +31 -0
- package/skills/opendevbrowser-design-agent/scripts/design-workflow.sh +171 -0
- package/skills/opendevbrowser-design-agent/scripts/extract-canvas-plan.sh +56 -0
- package/skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh +223 -0
- package/skills/opendevbrowser-form-testing/SKILL.md +19 -3
- package/skills/opendevbrowser-form-testing/artifacts/form-workflows.md +5 -4
- package/skills/opendevbrowser-form-testing/assets/templates/challenge-decision-tree.json +2 -0
- package/skills/opendevbrowser-form-testing/scripts/validate-skill-assets.sh +109 -0
- package/skills/opendevbrowser-login-automation/SKILL.md +21 -3
- package/skills/opendevbrowser-login-automation/artifacts/login-workflows.md +5 -4
- package/skills/opendevbrowser-login-automation/assets/templates/auth-signals.json +5 -0
- package/skills/opendevbrowser-login-automation/assets/templates/login-scenario-matrix.json +3 -2
- package/skills/opendevbrowser-login-automation/scripts/run-login-workflow.sh +17 -1
- package/skills/opendevbrowser-login-automation/scripts/validate-skill-assets.sh +133 -0
- package/skills/opendevbrowser-product-presentation-asset/SKILL.md +23 -11
- package/skills/opendevbrowser-product-presentation-asset/artifacts/asset-pack-assembly.md +5 -3
- package/skills/opendevbrowser-product-presentation-asset/assets/templates/shot-list.md +2 -0
- package/skills/opendevbrowser-product-presentation-asset/assets/templates/video-assembly.md +3 -2
- package/skills/opendevbrowser-product-presentation-asset/scripts/capture-screenshots.sh +5 -1
- package/skills/opendevbrowser-product-presentation-asset/scripts/collect-product.sh +6 -2
- package/skills/opendevbrowser-product-presentation-asset/scripts/download-images.sh +5 -1
- package/skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh +20 -7
- package/skills/opendevbrowser-product-presentation-asset/scripts/validate-skill-assets.sh +39 -0
- package/skills/opendevbrowser-product-presentation-asset/scripts/write-manifest.sh +5 -1
- package/skills/opendevbrowser-research/SKILL.md +14 -6
- package/skills/opendevbrowser-research/scripts/render-output.sh +5 -1
- package/skills/opendevbrowser-research/scripts/run-research.sh +5 -1
- package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +45 -0
- package/skills/opendevbrowser-research/scripts/write-artifacts.sh +5 -1
- package/skills/opendevbrowser-shopping/SKILL.md +20 -1
- package/skills/opendevbrowser-shopping/scripts/normalize-offers.sh +6 -2
- package/skills/opendevbrowser-shopping/scripts/run-deal-hunt.sh +5 -1
- package/skills/opendevbrowser-shopping/scripts/run-shopping.sh +5 -1
- package/skills/opendevbrowser-shopping/scripts/validate-skill-assets.sh +54 -0
- package/dist/chunk-ST7CO5FA.js +0 -18668
- package/dist/chunk-ST7CO5FA.js.map +0 -1
- /package/dist/{chunk-7W3SPXIB.js.map → chunk-FUSXMW3G.js.map} +0 -0
- /package/dist/{macros-NUBRM44Y.js.map → macros-ND2M7LWU.js.map} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MAX_OPS_PAYLOAD_BYTES, MAX_SNAPSHOT_BYTES, OPS_PROTOCOL_VERSION } from "../types.js";
|
|
2
|
+
import { isAttachBlockedError } from "../services/attach-errors.js";
|
|
2
3
|
import { TabManager } from "../services/TabManager.js";
|
|
3
4
|
import { getRestrictionMessage, isRestrictedUrl } from "../services/url-restrictions.js";
|
|
4
5
|
import { logError } from "../logging.js";
|
|
@@ -12,12 +13,195 @@ const MAX_NETWORK_EVENTS = 300;
|
|
|
12
13
|
const SESSION_TTL_MS = 20_000;
|
|
13
14
|
const SCREENSHOT_TIMEOUT_MS = 8000;
|
|
14
15
|
const TAB_CLOSE_TIMEOUT_MS = 5000;
|
|
16
|
+
const POPUP_ATTACH_RETRY_DELAY_MS = 100;
|
|
17
|
+
const STALE_REF_ERROR_SUFFIX = "Take a new snapshot first.";
|
|
18
|
+
const DOM_OUTER_HTML_DECLARATION = `
|
|
19
|
+
function() {
|
|
20
|
+
if (!(this instanceof Element)) return "";
|
|
21
|
+
return this.outerHTML;
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
const DOM_INNER_TEXT_DECLARATION = `
|
|
25
|
+
function() {
|
|
26
|
+
if (!(this instanceof Element)) return "";
|
|
27
|
+
return this instanceof HTMLElement ? (this.innerText || this.textContent || "") : (this.textContent || "");
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
const DOM_GET_ATTR_DECLARATION = `
|
|
31
|
+
function(name) {
|
|
32
|
+
if (!(this instanceof Element)) return null;
|
|
33
|
+
const value = this.getAttribute(name);
|
|
34
|
+
return value === null ? null : String(value);
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
const DOM_GET_VALUE_DECLARATION = `
|
|
38
|
+
function() {
|
|
39
|
+
if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement || this instanceof HTMLSelectElement) {
|
|
40
|
+
return this.value;
|
|
41
|
+
}
|
|
42
|
+
if (!(this instanceof Element)) return null;
|
|
43
|
+
const value = this.getAttribute("value");
|
|
44
|
+
return value === null ? null : String(value);
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
const DOM_IS_VISIBLE_DECLARATION = `
|
|
48
|
+
function() {
|
|
49
|
+
if (!(this instanceof Element)) return false;
|
|
50
|
+
const style = window.getComputedStyle(this);
|
|
51
|
+
if (!style || style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const rect = this.getBoundingClientRect();
|
|
55
|
+
return rect.width > 0 && rect.height > 0;
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
const DOM_IS_ENABLED_DECLARATION = `
|
|
59
|
+
function() {
|
|
60
|
+
if (!(this instanceof Element)) return false;
|
|
61
|
+
return !this.hasAttribute("disabled") && this.getAttribute("aria-disabled") !== "true";
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
const DOM_IS_CHECKED_DECLARATION = `
|
|
65
|
+
function() {
|
|
66
|
+
if (this instanceof HTMLInputElement && (this.type === "checkbox" || this.type === "radio")) {
|
|
67
|
+
return this.checked;
|
|
68
|
+
}
|
|
69
|
+
if (!(this instanceof Element)) return false;
|
|
70
|
+
return this.getAttribute("aria-checked") === "true";
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
const DOM_SELECTOR_STATE_DECLARATION = `
|
|
74
|
+
function() {
|
|
75
|
+
if (!(this instanceof Element)) {
|
|
76
|
+
return { attached: false, visible: false };
|
|
77
|
+
}
|
|
78
|
+
const style = window.getComputedStyle(this);
|
|
79
|
+
const rect = this.getBoundingClientRect();
|
|
80
|
+
return {
|
|
81
|
+
attached: true,
|
|
82
|
+
visible: Boolean(style && style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0" && rect.width > 0 && rect.height > 0)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
const DOM_HOVER_DECLARATION = `
|
|
87
|
+
function() {
|
|
88
|
+
if (!(this instanceof Element)) return;
|
|
89
|
+
const init = { bubbles: true, cancelable: true, view: window };
|
|
90
|
+
this.dispatchEvent(new MouseEvent("mouseenter", init));
|
|
91
|
+
this.dispatchEvent(new MouseEvent("mouseover", init));
|
|
92
|
+
this.dispatchEvent(new MouseEvent("mousemove", init));
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
const DOM_FOCUS_DECLARATION = `
|
|
96
|
+
function() {
|
|
97
|
+
if (this instanceof HTMLElement) {
|
|
98
|
+
this.focus();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
const DOM_SET_CHECKED_DECLARATION = `
|
|
103
|
+
function(checked) {
|
|
104
|
+
if (this instanceof HTMLInputElement && (this.type === "checkbox" || this.type === "radio")) {
|
|
105
|
+
this.checked = Boolean(checked);
|
|
106
|
+
this.dispatchEvent(new Event("input", { bubbles: true }));
|
|
107
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (this instanceof Element) {
|
|
111
|
+
this.setAttribute("aria-checked", checked ? "true" : "false");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
const DOM_TYPE_DECLARATION = `
|
|
116
|
+
function(value, clear, submit) {
|
|
117
|
+
if (!(this instanceof Element)) return;
|
|
118
|
+
if (this instanceof HTMLElement) {
|
|
119
|
+
this.focus();
|
|
120
|
+
}
|
|
121
|
+
if (this instanceof HTMLInputElement || this instanceof HTMLTextAreaElement) {
|
|
122
|
+
this.value = clear ? "" : this.value;
|
|
123
|
+
this.value = String(value);
|
|
124
|
+
this.dispatchEvent(new Event("input", { bubbles: true }));
|
|
125
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
126
|
+
if (submit) {
|
|
127
|
+
this.form?.requestSubmit?.();
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (this instanceof HTMLSelectElement) {
|
|
132
|
+
this.value = String(value);
|
|
133
|
+
this.dispatchEvent(new Event("input", { bubbles: true }));
|
|
134
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
const DOM_SELECT_DECLARATION = `
|
|
139
|
+
function(values) {
|
|
140
|
+
if (!(this instanceof HTMLSelectElement)) return;
|
|
141
|
+
const nextValues = Array.isArray(values) ? values.map((value) => String(value)) : [];
|
|
142
|
+
for (const option of Array.from(this.options)) {
|
|
143
|
+
option.selected = nextValues.includes(option.value);
|
|
144
|
+
}
|
|
145
|
+
this.dispatchEvent(new Event("input", { bubbles: true }));
|
|
146
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
const DOM_SCROLL_BY_DECLARATION = `
|
|
150
|
+
function(dy) {
|
|
151
|
+
if (!(this instanceof HTMLElement)) return;
|
|
152
|
+
this.scrollBy(0, Number(dy) || 0);
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
const DOM_SCROLL_INTO_VIEW_DECLARATION = `
|
|
156
|
+
function() {
|
|
157
|
+
if (this instanceof Element) {
|
|
158
|
+
this.scrollIntoView({ block: "center", inline: "center", behavior: "auto" });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
const DOM_REF_POINT_DECLARATION = `
|
|
163
|
+
function() {
|
|
164
|
+
if (!(this instanceof Element)) return null;
|
|
165
|
+
const rect = this.getBoundingClientRect();
|
|
166
|
+
return {
|
|
167
|
+
x: rect.left + rect.width / 2,
|
|
168
|
+
y: rect.top + rect.height / 2
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
const DOM_SCREENSHOT_CLIP_DECLARATION = `
|
|
173
|
+
function() {
|
|
174
|
+
/* odb-dom-screenshot-clip */
|
|
175
|
+
if (!(this instanceof Element)) return null;
|
|
176
|
+
const rect = this.getBoundingClientRect();
|
|
177
|
+
if (!Number.isFinite(rect.width) || !Number.isFinite(rect.height) || rect.width <= 0 || rect.height <= 0) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
x: rect.left + window.scrollX,
|
|
182
|
+
y: rect.top + window.scrollY,
|
|
183
|
+
width: rect.width,
|
|
184
|
+
height: rect.height
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
const DOM_FILE_INPUT_INFO_DECLARATION = `
|
|
189
|
+
function() {
|
|
190
|
+
/* odb-dom-file-input-info */
|
|
191
|
+
const isFileInput = this instanceof HTMLInputElement && this.type === "file";
|
|
192
|
+
return {
|
|
193
|
+
isFileInput,
|
|
194
|
+
disabled: isFileInput ? this.disabled : false
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
15
198
|
const TARGET_SCOPED_COMMANDS = new Set([
|
|
16
199
|
"storage.setCookies",
|
|
17
200
|
"storage.getCookies",
|
|
18
201
|
"nav.goto",
|
|
19
202
|
"nav.wait",
|
|
20
203
|
"nav.snapshot",
|
|
204
|
+
"nav.review",
|
|
21
205
|
"interact.click",
|
|
22
206
|
"interact.hover",
|
|
23
207
|
"interact.press",
|
|
@@ -27,6 +211,11 @@ const TARGET_SCOPED_COMMANDS = new Set([
|
|
|
27
211
|
"interact.select",
|
|
28
212
|
"interact.scroll",
|
|
29
213
|
"interact.scrollIntoView",
|
|
214
|
+
"interact.upload",
|
|
215
|
+
"pointer.move",
|
|
216
|
+
"pointer.down",
|
|
217
|
+
"pointer.up",
|
|
218
|
+
"pointer.drag",
|
|
30
219
|
"dom.getHtml",
|
|
31
220
|
"dom.getText",
|
|
32
221
|
"dom.getAttr",
|
|
@@ -34,27 +223,63 @@ const TARGET_SCOPED_COMMANDS = new Set([
|
|
|
34
223
|
"dom.isVisible",
|
|
35
224
|
"dom.isEnabled",
|
|
36
225
|
"dom.isChecked",
|
|
226
|
+
"dom.refPoint",
|
|
227
|
+
"canvas.overlay.mount",
|
|
228
|
+
"canvas.overlay.unmount",
|
|
229
|
+
"canvas.overlay.select",
|
|
230
|
+
"canvas.overlay.sync",
|
|
231
|
+
"canvas.applyRuntimePreviewBridge",
|
|
37
232
|
"export.clonePage",
|
|
38
233
|
"export.cloneComponent",
|
|
39
234
|
"devtools.perf",
|
|
40
235
|
"page.screenshot"
|
|
41
236
|
]);
|
|
237
|
+
const DIALOG_SCOPED_COMMANDS = new Set([
|
|
238
|
+
"page.dialog"
|
|
239
|
+
]);
|
|
42
240
|
export class OpsRuntime {
|
|
43
241
|
sendEnvelope;
|
|
44
242
|
cdp;
|
|
243
|
+
getCanvasPageState;
|
|
244
|
+
performCanvasPageAction;
|
|
45
245
|
tabs = new TabManager();
|
|
46
246
|
dom = new DomBridge();
|
|
47
247
|
sessions = new OpsSessionStore();
|
|
48
248
|
encoder = new TextEncoder();
|
|
249
|
+
popupOpenerTabIds = new Map();
|
|
250
|
+
popupAttachDiagnostics = new Map();
|
|
251
|
+
commandCreatedTabs = new Map();
|
|
252
|
+
dialogQueues = new Map();
|
|
49
253
|
closingTimers = new Map();
|
|
50
254
|
parallelWaiters = new Map();
|
|
51
255
|
constructor(options) {
|
|
52
256
|
this.sendEnvelope = options.send;
|
|
53
257
|
this.cdp = options.cdp;
|
|
258
|
+
this.getCanvasPageState = options.getCanvasPageState;
|
|
259
|
+
this.performCanvasPageAction = options.performCanvasPageAction;
|
|
260
|
+
chrome.tabs.onCreated.addListener(this.handleTabCreated);
|
|
54
261
|
chrome.tabs.onRemoved.addListener(this.handleTabRemoved);
|
|
55
262
|
chrome.tabs.onUpdated.addListener(this.handleTabUpdated);
|
|
263
|
+
chrome.webNavigation?.onCreatedNavigationTarget?.addListener?.(this.handleCreatedNavigationTarget);
|
|
56
264
|
chrome.debugger.onEvent.addListener(this.handleDebuggerEvent);
|
|
57
265
|
chrome.debugger.onDetach.addListener(this.handleDebuggerDetach);
|
|
266
|
+
if (typeof this.cdp.addEventListener === "function") {
|
|
267
|
+
this.cdp.addEventListener(this.handleCdpRouterEvent);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async registerCanvasTargetForSession(opsSessionId, targetId) {
|
|
271
|
+
const session = this.sessions.get(opsSessionId);
|
|
272
|
+
if (!session) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return await this.registerCanvasTarget(session, targetId);
|
|
276
|
+
}
|
|
277
|
+
unregisterCanvasTargetForSession(opsSessionId, targetId) {
|
|
278
|
+
const session = this.sessions.get(opsSessionId);
|
|
279
|
+
if (!session || targetId === session.targetId) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return this.sessions.removeTarget(session.id, targetId) !== null;
|
|
58
283
|
}
|
|
59
284
|
handleMessage(message) {
|
|
60
285
|
if (message.type === "ops_hello") {
|
|
@@ -117,18 +342,125 @@ export class OpsRuntime {
|
|
|
117
342
|
const clientId = message.clientId;
|
|
118
343
|
if (!clientId)
|
|
119
344
|
return;
|
|
345
|
+
this.cdp.markClientClosed();
|
|
120
346
|
const sessions = this.sessions.listOwnedBy(clientId);
|
|
121
347
|
for (const session of sessions) {
|
|
122
|
-
this.markSessionClosing(session, "ops_session_expired")
|
|
348
|
+
if (this.markSessionClosing(session, "ops_session_expired")) {
|
|
349
|
+
this.emitSessionEvent(session, "ops_session_released");
|
|
350
|
+
}
|
|
123
351
|
}
|
|
124
352
|
}
|
|
125
353
|
handleTabRemoved = (tabId) => {
|
|
354
|
+
this.forgetCommandCreatedTab(tabId);
|
|
355
|
+
this.popupOpenerTabIds.delete(tabId);
|
|
126
356
|
this.handleClosedTarget(tabId, "ops_tab_closed");
|
|
127
357
|
};
|
|
358
|
+
handleCreatedNavigationTarget = (details) => {
|
|
359
|
+
const tabId = typeof details.tabId === "number" ? details.tabId : null;
|
|
360
|
+
const openerTabId = typeof details.sourceTabId === "number" ? details.sourceTabId : null;
|
|
361
|
+
if (tabId === null || openerTabId === null) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this.popupOpenerTabIds.set(tabId, openerTabId);
|
|
365
|
+
};
|
|
366
|
+
handleTabCreated = (tab) => {
|
|
367
|
+
const tabId = typeof tab.id === "number" ? tab.id : null;
|
|
368
|
+
if (tabId === null) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (this.isCommandCreatedTab(tabId)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const openerTabId = typeof tab.openerTabId === "number" ? tab.openerTabId : null;
|
|
375
|
+
if (openerTabId !== null) {
|
|
376
|
+
const session = this.sessions.getByTabId(openerTabId);
|
|
377
|
+
if (!session) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.finishCreatedTab(session, this.sessions.getTargetIdByTabId(session.id, openerTabId) ?? session.targetId, tab, tabId);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
void this.handleCreatedTab(tab, tabId);
|
|
384
|
+
};
|
|
385
|
+
async handleCreatedTab(tab, tabId) {
|
|
386
|
+
const opener = await this.resolvePopupOpenerContext(tabId, typeof tab.openerTabId === "number" ? tab.openerTabId : null);
|
|
387
|
+
if (!opener) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this.finishCreatedTab(opener.session, opener.openerTargetId, tab, tabId);
|
|
391
|
+
}
|
|
392
|
+
finishCreatedTab(session, openerTargetId, tab, tabId) {
|
|
393
|
+
this.popupOpenerTabIds.delete(tabId);
|
|
394
|
+
const existingTargetId = this.updateKnownTabTarget(session, tab);
|
|
395
|
+
if (existingTargetId) {
|
|
396
|
+
const existingTarget = session.targets.get(existingTargetId) ?? null;
|
|
397
|
+
if (existingTarget && !existingTarget.openerTargetId) {
|
|
398
|
+
existingTarget.openerTargetId = openerTargetId;
|
|
399
|
+
}
|
|
400
|
+
const resolvedTarget = this.resolveTargetContext(session, existingTargetId);
|
|
401
|
+
if (resolvedTarget
|
|
402
|
+
&& this.shouldPromotePopupTarget(session, openerTargetId, resolvedTarget)) {
|
|
403
|
+
session.activeTargetId = existingTargetId;
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const target = this.sessions.addTarget(session.id, tabId, {
|
|
408
|
+
url: tab.url ?? undefined,
|
|
409
|
+
title: tab.title ?? undefined,
|
|
410
|
+
openerTargetId: openerTargetId
|
|
411
|
+
});
|
|
412
|
+
void this.attachCreatedTab(session, target.targetId, tabId);
|
|
413
|
+
}
|
|
414
|
+
async resolvePopupOpenerContext(tabId, openerTabId) {
|
|
415
|
+
let resolvedOpenerTabId = openerTabId ?? this.popupOpenerTabIds.get(tabId) ?? null;
|
|
416
|
+
if (resolvedOpenerTabId === null && typeof this.cdp.resolveTabOpenerTargetId === "function") {
|
|
417
|
+
const openerTargetId = await this.cdp.resolveTabOpenerTargetId(tabId).catch(() => null);
|
|
418
|
+
resolvedOpenerTabId = parseTargetAliasTabId(openerTargetId ?? undefined);
|
|
419
|
+
}
|
|
420
|
+
if (resolvedOpenerTabId === null) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
const session = this.sessions.getByTabId(resolvedOpenerTabId);
|
|
424
|
+
if (!session) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
session,
|
|
429
|
+
openerTargetId: this.sessions.getTargetIdByTabId(session.id, resolvedOpenerTabId) ?? session.targetId
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
async hydratePopupOpenerTarget(session, targetId) {
|
|
433
|
+
const target = session.targets.get(targetId) ?? null;
|
|
434
|
+
if (!target || target.openerTargetId) {
|
|
435
|
+
return target;
|
|
436
|
+
}
|
|
437
|
+
const opener = await this.resolvePopupOpenerContext(target.tabId, null);
|
|
438
|
+
if (!opener || opener.session.id !== session.id) {
|
|
439
|
+
return target;
|
|
440
|
+
}
|
|
441
|
+
target.openerTargetId = opener.openerTargetId;
|
|
442
|
+
return target;
|
|
443
|
+
}
|
|
128
444
|
handleTabUpdated = (tabId, changeInfo, tab) => {
|
|
129
445
|
const session = this.sessions.getByTabId(tabId);
|
|
130
|
-
if (!session)
|
|
446
|
+
if (!session) {
|
|
447
|
+
if (this.isCommandCreatedTab(tabId)) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (changeInfo.status === "complete" || tab.status === "complete" || typeof tab.openerTabId === "number") {
|
|
451
|
+
void this.handleCreatedTab(tab, tabId);
|
|
452
|
+
}
|
|
131
453
|
return;
|
|
454
|
+
}
|
|
455
|
+
const targetId = this.updateKnownTabTarget(session, tab);
|
|
456
|
+
if (targetId
|
|
457
|
+
&& tab.active === true
|
|
458
|
+
&& (changeInfo.status === "complete" || tab.status === "complete")) {
|
|
459
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
460
|
+
if (target && (!target.openerTargetId || this.hasUsableDebuggee(target))) {
|
|
461
|
+
session.activeTargetId = targetId;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
132
464
|
if (changeInfo.discarded === true || tab.discarded === true) {
|
|
133
465
|
session.discardedSignals += 1;
|
|
134
466
|
}
|
|
@@ -143,10 +475,60 @@ export class OpsRuntime {
|
|
|
143
475
|
return;
|
|
144
476
|
void this.handleDebuggerDetachForTab(source.tabId);
|
|
145
477
|
};
|
|
478
|
+
updateKnownTabTarget(session, tab) {
|
|
479
|
+
const tabId = typeof tab.id === "number" ? tab.id : null;
|
|
480
|
+
if (tabId === null) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
const targetId = this.sessions.getTargetIdByTabId(session.id, tabId);
|
|
484
|
+
if (!targetId) {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
const target = session.targets.get(targetId);
|
|
488
|
+
if (!target) {
|
|
489
|
+
return targetId;
|
|
490
|
+
}
|
|
491
|
+
const nextUrl = getReportedTabUrl(tab);
|
|
492
|
+
const nextTitle = getReportedTabTitle(tab);
|
|
493
|
+
if (typeof nextUrl === "string" && nextUrl.length > 0) {
|
|
494
|
+
target.url = nextUrl;
|
|
495
|
+
}
|
|
496
|
+
if (typeof nextTitle === "string" && nextTitle.length > 0) {
|
|
497
|
+
target.title = nextTitle;
|
|
498
|
+
}
|
|
499
|
+
return targetId;
|
|
500
|
+
}
|
|
501
|
+
async attachCreatedTab(session, targetId, tabId) {
|
|
502
|
+
const target = await this.hydratePopupOpenerTarget(session, targetId);
|
|
503
|
+
if (target?.openerTargetId) {
|
|
504
|
+
// Keep the opener root stable and attach popup tabs only when the caller explicitly targets them.
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
await this.tabs.waitForTabComplete(tabId, 5000).catch(() => undefined);
|
|
508
|
+
try {
|
|
509
|
+
await this.attachTargetTab(tabId);
|
|
510
|
+
await this.enableTargetDomains(tabId, true);
|
|
511
|
+
this.promotePopupTarget(session, targetId);
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
if (target && isAttachBlockedError(error)) {
|
|
515
|
+
const bridged = await this.attachTargetViaOpenerSession(session, target).catch(() => false);
|
|
516
|
+
if (bridged) {
|
|
517
|
+
this.promotePopupTarget(session, targetId);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
logError("ops.popup_tab_attach", error, {
|
|
522
|
+
code: "popup_tab_attach_failed",
|
|
523
|
+
extra: { tabId }
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
146
527
|
handleDebuggerEvent = (source, method, params) => {
|
|
147
|
-
|
|
528
|
+
const eventTabId = this.cdp.resolveSourceTabId(source);
|
|
529
|
+
if (eventTabId === null)
|
|
148
530
|
return;
|
|
149
|
-
const session = this.sessions.getByTabId(
|
|
531
|
+
const session = this.sessions.getByTabId(eventTabId);
|
|
150
532
|
if (!session)
|
|
151
533
|
return;
|
|
152
534
|
if (method === "Runtime.consoleAPICalled") {
|
|
@@ -175,6 +557,29 @@ export class OpsRuntime {
|
|
|
175
557
|
}
|
|
176
558
|
return;
|
|
177
559
|
}
|
|
560
|
+
if (method === "Page.javascriptDialogOpening") {
|
|
561
|
+
const targetId = this.resolveDebuggerEventTargetId(session, source, eventTabId);
|
|
562
|
+
if (!targetId) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
this.applyDialogOpening(session, targetId, params);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (method === "Page.javascriptDialogClosed") {
|
|
569
|
+
const targetId = this.resolveDebuggerEventTargetId(session, source, eventTabId);
|
|
570
|
+
if (targetId) {
|
|
571
|
+
this.applyDialogClosed(session, targetId);
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (method === "Page.fileChooserOpened") {
|
|
576
|
+
const targetId = this.resolveDebuggerEventTargetId(session, source, eventTabId);
|
|
577
|
+
if (!targetId) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
this.applyFileChooserOpened(session, targetId, params);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
178
583
|
if (method === "Network.requestWillBeSent") {
|
|
179
584
|
const payload = params;
|
|
180
585
|
const requestId = payload.requestId;
|
|
@@ -223,6 +628,130 @@ export class OpsRuntime {
|
|
|
223
628
|
}
|
|
224
629
|
}
|
|
225
630
|
};
|
|
631
|
+
handleCdpRouterEvent = (event) => {
|
|
632
|
+
const session = this.sessions.getByTabId(event.tabId);
|
|
633
|
+
if (!session) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
switch (event.method) {
|
|
637
|
+
case "Page.javascriptDialogOpening": {
|
|
638
|
+
const targetId = this.resolveRouterEventTargetId(session, event);
|
|
639
|
+
if (!targetId) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
this.applyDialogOpening(session, targetId, event.params);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
case "Page.javascriptDialogClosed": {
|
|
646
|
+
const targetId = this.resolveRouterEventTargetId(session, event);
|
|
647
|
+
if (!targetId) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.applyDialogClosed(session, targetId);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
case "Page.fileChooserOpened": {
|
|
654
|
+
const targetId = this.resolveRouterEventTargetId(session, event);
|
|
655
|
+
if (!targetId) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.applyFileChooserOpened(session, targetId, event.params);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
case "Target.targetCreated":
|
|
662
|
+
this.handleSyntheticTargetCreated(session, event);
|
|
663
|
+
return;
|
|
664
|
+
case "Target.attachedToTarget":
|
|
665
|
+
this.handleSyntheticTargetAttached(session, event);
|
|
666
|
+
return;
|
|
667
|
+
case "Target.targetDestroyed":
|
|
668
|
+
this.handleSyntheticTargetDestroyed(session, event);
|
|
669
|
+
return;
|
|
670
|
+
case "Target.detachedFromTarget":
|
|
671
|
+
this.handleSyntheticTargetDetached(session, event);
|
|
672
|
+
return;
|
|
673
|
+
default:
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
handleSyntheticTargetCreated(session, event) {
|
|
678
|
+
const targetInfo = extractTargetInfo(event.params);
|
|
679
|
+
if (!targetInfo || !isSyntheticPageTarget(session, targetInfo.targetId, targetInfo.type)) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const resolvedTabId = parseTabTargetId(targetInfo.targetId) ?? event.tabId;
|
|
683
|
+
this.sessions.upsertSyntheticTarget(session.id, {
|
|
684
|
+
targetId: targetInfo.targetId,
|
|
685
|
+
tabId: resolvedTabId,
|
|
686
|
+
type: targetInfo.type,
|
|
687
|
+
...(typeof targetInfo.url === "string" ? { url: targetInfo.url } : {}),
|
|
688
|
+
...(typeof targetInfo.title === "string" ? { title: targetInfo.title } : {}),
|
|
689
|
+
...(typeof targetInfo.openerId === "string" ? { openerTargetId: targetInfo.openerId } : {}),
|
|
690
|
+
attachedAt: Date.now()
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
handleSyntheticTargetAttached(session, event) {
|
|
694
|
+
const payload = isRecord(event.params) ? event.params : null;
|
|
695
|
+
const targetInfo = extractTargetInfo(payload);
|
|
696
|
+
const childSessionId = payload && typeof payload.sessionId === "string" ? payload.sessionId : undefined;
|
|
697
|
+
if (!targetInfo || !isSyntheticPageTarget(session, targetInfo.targetId, targetInfo.type)) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const resolvedTabId = parseTabTargetId(targetInfo.targetId) ?? event.tabId;
|
|
701
|
+
const synthetic = this.sessions.upsertSyntheticTarget(session.id, {
|
|
702
|
+
targetId: targetInfo.targetId,
|
|
703
|
+
tabId: resolvedTabId,
|
|
704
|
+
type: targetInfo.type,
|
|
705
|
+
...(typeof targetInfo.url === "string" ? { url: targetInfo.url } : {}),
|
|
706
|
+
...(typeof targetInfo.title === "string" ? { title: targetInfo.title } : {}),
|
|
707
|
+
...(childSessionId ? { sessionId: childSessionId } : {}),
|
|
708
|
+
...(typeof targetInfo.openerId === "string" ? { openerTargetId: targetInfo.openerId } : {}),
|
|
709
|
+
attachedAt: Date.now()
|
|
710
|
+
});
|
|
711
|
+
if (!session.activeTargetId
|
|
712
|
+
|| session.activeTargetId === session.targetId
|
|
713
|
+
|| session.activeTargetId === synthetic.openerTargetId) {
|
|
714
|
+
session.activeTargetId = synthetic.targetId;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
handleSyntheticTargetDestroyed(session, event) {
|
|
718
|
+
const payload = isRecord(event.params) ? event.params : null;
|
|
719
|
+
const targetId = payload && typeof payload.targetId === "string" ? payload.targetId : null;
|
|
720
|
+
if (!targetId) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const removed = this.sessions.removeSyntheticTarget(session.id, targetId);
|
|
724
|
+
if (!removed) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
this.restoreSyntheticFallbackTarget(session, removed);
|
|
728
|
+
}
|
|
729
|
+
handleSyntheticTargetDetached(session, event) {
|
|
730
|
+
const payload = isRecord(event.params) ? event.params : null;
|
|
731
|
+
const targetId = payload && typeof payload.targetId === "string" ? payload.targetId : null;
|
|
732
|
+
const sessionId = payload && typeof payload.sessionId === "string" ? payload.sessionId : null;
|
|
733
|
+
const removed = targetId
|
|
734
|
+
? this.sessions.removeSyntheticTarget(session.id, targetId)
|
|
735
|
+
: (sessionId ? this.sessions.findSyntheticTargetBySessionId(session.id, sessionId) : null);
|
|
736
|
+
if (!removed) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!targetId && sessionId) {
|
|
740
|
+
this.sessions.removeSyntheticTarget(session.id, removed.targetId);
|
|
741
|
+
}
|
|
742
|
+
this.restoreSyntheticFallbackTarget(session, removed);
|
|
743
|
+
}
|
|
744
|
+
restoreSyntheticFallbackTarget(session, removed) {
|
|
745
|
+
if (session.activeTargetId !== removed.targetId) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (removed.openerTargetId && this.hasOpsTarget(session, removed.openerTargetId)) {
|
|
749
|
+
session.activeTargetId = removed.openerTargetId;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const firstSynthetic = this.sessions.listSyntheticTargets(session.id)[0];
|
|
753
|
+
session.activeTargetId = firstSynthetic?.targetId ?? session.targetId;
|
|
754
|
+
}
|
|
226
755
|
async handleRequest(message) {
|
|
227
756
|
const clientId = message.clientId;
|
|
228
757
|
if (!clientId) {
|
|
@@ -252,6 +781,9 @@ export class OpsRuntime {
|
|
|
252
781
|
case "targets.use":
|
|
253
782
|
await this.withSession(message, clientId, (session) => this.handleTargetsUse(message, session));
|
|
254
783
|
return;
|
|
784
|
+
case "targets.registerCanvas":
|
|
785
|
+
await this.withSession(message, clientId, (session) => this.handleTargetsRegisterCanvas(message, session));
|
|
786
|
+
return;
|
|
255
787
|
case "targets.new":
|
|
256
788
|
await this.withSession(message, clientId, (session) => this.handleTargetsNew(message, session));
|
|
257
789
|
return;
|
|
@@ -276,6 +808,9 @@ export class OpsRuntime {
|
|
|
276
808
|
case "nav.snapshot":
|
|
277
809
|
await this.withSession(message, clientId, (session) => this.handleSnapshot(message, session));
|
|
278
810
|
return;
|
|
811
|
+
case "nav.review":
|
|
812
|
+
await this.withSession(message, clientId, (session) => this.handleReview(message, session));
|
|
813
|
+
return;
|
|
279
814
|
case "interact.click":
|
|
280
815
|
await this.withSession(message, clientId, (session) => this.handleClick(message, session));
|
|
281
816
|
return;
|
|
@@ -303,6 +838,21 @@ export class OpsRuntime {
|
|
|
303
838
|
case "interact.scrollIntoView":
|
|
304
839
|
await this.withSession(message, clientId, (session) => this.handleScrollIntoView(message, session));
|
|
305
840
|
return;
|
|
841
|
+
case "interact.upload":
|
|
842
|
+
await this.withSession(message, clientId, (session) => this.handleUpload(message, session));
|
|
843
|
+
return;
|
|
844
|
+
case "pointer.move":
|
|
845
|
+
await this.withSession(message, clientId, (session) => this.handlePointerMove(message, session));
|
|
846
|
+
return;
|
|
847
|
+
case "pointer.down":
|
|
848
|
+
await this.withSession(message, clientId, (session) => this.handlePointerDown(message, session));
|
|
849
|
+
return;
|
|
850
|
+
case "pointer.up":
|
|
851
|
+
await this.withSession(message, clientId, (session) => this.handlePointerUp(message, session));
|
|
852
|
+
return;
|
|
853
|
+
case "pointer.drag":
|
|
854
|
+
await this.withSession(message, clientId, (session) => this.handlePointerDrag(message, session));
|
|
855
|
+
return;
|
|
306
856
|
case "dom.getHtml":
|
|
307
857
|
await this.withSession(message, clientId, (session) => this.handleDomGetHtml(message, session));
|
|
308
858
|
return;
|
|
@@ -324,6 +874,24 @@ export class OpsRuntime {
|
|
|
324
874
|
case "dom.isChecked":
|
|
325
875
|
await this.withSession(message, clientId, (session) => this.handleDomIsChecked(message, session));
|
|
326
876
|
return;
|
|
877
|
+
case "dom.refPoint":
|
|
878
|
+
await this.withSession(message, clientId, (session) => this.handleDomRefPoint(message, session));
|
|
879
|
+
return;
|
|
880
|
+
case "canvas.overlay.mount":
|
|
881
|
+
await this.withSession(message, clientId, (session) => this.handleCanvasOverlayMount(message, session));
|
|
882
|
+
return;
|
|
883
|
+
case "canvas.overlay.unmount":
|
|
884
|
+
await this.withSession(message, clientId, (session) => this.handleCanvasOverlayUnmount(message, session));
|
|
885
|
+
return;
|
|
886
|
+
case "canvas.overlay.select":
|
|
887
|
+
await this.withSession(message, clientId, (session) => this.handleCanvasOverlaySelect(message, session));
|
|
888
|
+
return;
|
|
889
|
+
case "canvas.overlay.sync":
|
|
890
|
+
await this.withSession(message, clientId, (session) => this.handleCanvasOverlaySync(message, session));
|
|
891
|
+
return;
|
|
892
|
+
case "canvas.applyRuntimePreviewBridge":
|
|
893
|
+
await this.withSession(message, clientId, (session) => this.handleCanvasRuntimePreviewBridge(message, session));
|
|
894
|
+
return;
|
|
327
895
|
case "export.clonePage":
|
|
328
896
|
await this.withSession(message, clientId, (session) => this.handleClonePage(message, session));
|
|
329
897
|
return;
|
|
@@ -336,6 +904,9 @@ export class OpsRuntime {
|
|
|
336
904
|
case "page.screenshot":
|
|
337
905
|
await this.withSession(message, clientId, (session) => this.handleScreenshot(message, session));
|
|
338
906
|
return;
|
|
907
|
+
case "page.dialog":
|
|
908
|
+
await this.withSession(message, clientId, (session) => this.handleDialog(message, session));
|
|
909
|
+
return;
|
|
339
910
|
case "devtools.consolePoll":
|
|
340
911
|
await this.withSession(message, clientId, (session) => this.handleConsolePoll(message, session));
|
|
341
912
|
return;
|
|
@@ -350,6 +921,13 @@ export class OpsRuntime {
|
|
|
350
921
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
351
922
|
const parallelismPolicy = parseParallelismPolicy(payload.parallelismPolicy);
|
|
352
923
|
const startUrl = typeof payload.startUrl === "string" ? payload.startUrl : undefined;
|
|
924
|
+
const isStartUrlConnect = message.command === "session.connect" && typeof startUrl === "string";
|
|
925
|
+
const requestedSessionId = typeof payload.sessionId === "string" && payload.sessionId.trim().length > 0
|
|
926
|
+
? payload.sessionId.trim()
|
|
927
|
+
: undefined;
|
|
928
|
+
const requestedTabId = typeof payload.tabId === "number" && Number.isInteger(payload.tabId)
|
|
929
|
+
? payload.tabId
|
|
930
|
+
: undefined;
|
|
353
931
|
if (startUrl) {
|
|
354
932
|
try {
|
|
355
933
|
const restriction = getRestrictionMessage(new URL(startUrl));
|
|
@@ -363,51 +941,81 @@ export class OpsRuntime {
|
|
|
363
941
|
return;
|
|
364
942
|
}
|
|
365
943
|
}
|
|
366
|
-
|
|
944
|
+
let activeTab = startUrl
|
|
367
945
|
? await this.tabs.createTab(startUrl, true)
|
|
368
|
-
:
|
|
946
|
+
: typeof requestedTabId === "number"
|
|
947
|
+
? await this.tabs.getTab(requestedTabId)
|
|
948
|
+
: await this.tabs.getActiveTab();
|
|
949
|
+
if (!startUrl && typeof requestedTabId !== "number") {
|
|
950
|
+
const currentRawUrl = activeTab?.url ?? activeTab?.pendingUrl ?? "";
|
|
951
|
+
const needsFallback = !activeTab
|
|
952
|
+
|| typeof activeTab.id !== "number"
|
|
953
|
+
|| currentRawUrl.length === 0
|
|
954
|
+
|| isRestrictedUrl(currentRawUrl).restricted;
|
|
955
|
+
if (needsFallback) {
|
|
956
|
+
activeTab = await this.tabs.getFirstAttachableTab(typeof activeTab?.id === "number" ? activeTab.id : undefined) ?? activeTab;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
369
959
|
if (!activeTab || typeof activeTab.id !== "number") {
|
|
960
|
+
if (typeof requestedTabId === "number") {
|
|
961
|
+
this.sendError(message, buildError("invalid_request", `Unknown tabId: ${requestedTabId}`, false));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
370
964
|
this.sendError(message, buildError("ops_unavailable", "No active tab to attach.", true));
|
|
371
965
|
return;
|
|
372
966
|
}
|
|
373
|
-
|
|
374
|
-
|
|
967
|
+
const activeTabId = activeTab.id;
|
|
968
|
+
let resolvedTab = startUrl
|
|
969
|
+
? await this.tabs.waitForTabComplete(activeTabId)
|
|
970
|
+
.catch(() => undefined)
|
|
971
|
+
.then(async () => await this.tabs.getTab(activeTabId) ?? activeTab)
|
|
972
|
+
: activeTab;
|
|
973
|
+
if (resolvedTab.url) {
|
|
974
|
+
const restriction = isRestrictedUrl(resolvedTab.url);
|
|
375
975
|
if (restriction.restricted) {
|
|
376
976
|
this.sendError(message, buildError("restricted_url", restriction.message ?? "Restricted tab.", false));
|
|
377
977
|
return;
|
|
378
978
|
}
|
|
379
979
|
}
|
|
380
980
|
try {
|
|
381
|
-
|
|
981
|
+
const refreshedTab = isStartUrlConnect
|
|
982
|
+
? await this.attachStartUrlConnectTab(activeTabId)
|
|
983
|
+
: await this.attachLaunchTargetTab(activeTabId, false);
|
|
984
|
+
if (refreshedTab) {
|
|
985
|
+
resolvedTab = refreshedTab;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
990
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (!startUrl) {
|
|
994
|
+
await this.tabs.waitForTabComplete(activeTab.id).catch(() => undefined);
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
await this.enableTargetDomains(activeTabId, true);
|
|
382
998
|
}
|
|
383
999
|
catch (error) {
|
|
384
1000
|
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
385
|
-
this.sendError(message, buildError("cdp_attach_failed", detail, false));
|
|
1001
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
386
1002
|
return;
|
|
387
1003
|
}
|
|
388
|
-
await this.tabs.waitForTabComplete(activeTab.id).catch(() => undefined);
|
|
389
1004
|
const leaseId = typeof message.leaseId === "string" && message.leaseId.trim().length > 0
|
|
390
1005
|
? message.leaseId.trim()
|
|
391
1006
|
: createId();
|
|
392
|
-
const session = this.sessions.createSession(clientId,
|
|
393
|
-
url:
|
|
394
|
-
title:
|
|
1007
|
+
const session = this.sessions.createSession(clientId, activeTabId, leaseId, {
|
|
1008
|
+
url: resolvedTab.url ?? undefined,
|
|
1009
|
+
title: resolvedTab.title ?? undefined
|
|
395
1010
|
}, {
|
|
396
1011
|
parallelismPolicy
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
this.sendEvent({
|
|
400
|
-
type: "ops_event",
|
|
401
|
-
clientId,
|
|
402
|
-
opsSessionId: session.id,
|
|
403
|
-
event: "ops_session_created",
|
|
404
|
-
payload: { tabId: session.tabId, targetId: session.targetId }
|
|
405
|
-
});
|
|
1012
|
+
}, requestedSessionId);
|
|
1013
|
+
this.emitSessionEvent(session, "ops_session_created");
|
|
406
1014
|
this.sendResponse(message, {
|
|
407
1015
|
opsSessionId: session.id,
|
|
408
1016
|
activeTargetId: session.activeTargetId,
|
|
409
|
-
url:
|
|
410
|
-
title:
|
|
1017
|
+
url: resolvedTab.url ?? undefined,
|
|
1018
|
+
title: resolvedTab.title ?? undefined,
|
|
411
1019
|
leaseId: session.leaseId
|
|
412
1020
|
});
|
|
413
1021
|
}
|
|
@@ -422,12 +1030,18 @@ export class OpsRuntime {
|
|
|
422
1030
|
const session = this.getSessionForMessage(message, clientId);
|
|
423
1031
|
if (!session)
|
|
424
1032
|
return;
|
|
425
|
-
const
|
|
1033
|
+
const activeTargetId = session.activeTargetId ?? session.targetId;
|
|
1034
|
+
const reportedTarget = activeTargetId
|
|
1035
|
+
? this.resolveRequestedTargetContext(session, activeTargetId, false)
|
|
1036
|
+
: null;
|
|
1037
|
+
const reportedTargetId = reportedTarget?.targetId ?? null;
|
|
1038
|
+
const tab = reportedTarget ? await this.tabs.getTab(reportedTarget.tabId) : null;
|
|
426
1039
|
this.sendResponse(message, {
|
|
427
1040
|
mode: "extension",
|
|
428
|
-
activeTargetId:
|
|
429
|
-
url: tab
|
|
430
|
-
title: tab
|
|
1041
|
+
activeTargetId: reportedTargetId,
|
|
1042
|
+
url: resolveReportedTargetUrl(reportedTarget, tab),
|
|
1043
|
+
title: resolveReportedTargetTitle(reportedTarget, tab),
|
|
1044
|
+
dialog: this.serializeDialogState(session, reportedTargetId),
|
|
431
1045
|
leaseId: session.leaseId,
|
|
432
1046
|
state: session.state
|
|
433
1047
|
});
|
|
@@ -435,13 +1049,26 @@ export class OpsRuntime {
|
|
|
435
1049
|
async handleTargetsList(message, session) {
|
|
436
1050
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
437
1051
|
const includeUrls = payload.includeUrls === true;
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
1052
|
+
const targetContexts = [
|
|
1053
|
+
...Array.from(session.targets.values()).map((target) => ({
|
|
1054
|
+
targetId: target.targetId,
|
|
1055
|
+
tabId: target.tabId
|
|
1056
|
+
})),
|
|
1057
|
+
...this.sessions.listSyntheticTargets(session.id)
|
|
1058
|
+
.filter((target) => !session.targets.has(target.targetId))
|
|
1059
|
+
.map((target) => ({
|
|
441
1060
|
targetId: target.targetId,
|
|
1061
|
+
tabId: target.tabId
|
|
1062
|
+
}))
|
|
1063
|
+
];
|
|
1064
|
+
const targets = await Promise.all(targetContexts.map(async ({ targetId, tabId }) => {
|
|
1065
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
1066
|
+
const tab = await this.tabs.getTab(tabId);
|
|
1067
|
+
return {
|
|
1068
|
+
targetId,
|
|
442
1069
|
type: "page",
|
|
443
|
-
title:
|
|
444
|
-
url: includeUrls ? tab
|
|
1070
|
+
title: resolveReportedTargetTitle(target, tab),
|
|
1071
|
+
url: includeUrls ? resolveReportedTargetUrl(target, tab) : undefined
|
|
445
1072
|
};
|
|
446
1073
|
}));
|
|
447
1074
|
this.sendResponse(message, { activeTargetId: session.activeTargetId || null, targets });
|
|
@@ -449,65 +1076,270 @@ export class OpsRuntime {
|
|
|
449
1076
|
async handleTargetsUse(message, session) {
|
|
450
1077
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
451
1078
|
const targetId = typeof payload.targetId === "string" ? payload.targetId : null;
|
|
452
|
-
if (!targetId || !
|
|
1079
|
+
if (!targetId || !this.hasOpsTarget(session, targetId)) {
|
|
453
1080
|
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
454
1081
|
return;
|
|
455
1082
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1083
|
+
let target = this.rehydrateSyntheticPopupBridge(session, targetId) ?? this.resolveTargetContext(session, targetId);
|
|
1084
|
+
if (target && !this.hasUsableDebuggee(target) && (target.synthetic || !!target.openerTargetId)) {
|
|
1085
|
+
try {
|
|
1086
|
+
target = await this.preparePopupTarget(session, targetId) ?? target;
|
|
1087
|
+
target = this.rehydrateSyntheticPopupBridge(session, targetId) ?? target;
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1091
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
460
1094
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1095
|
+
if (target?.synthetic && !target.sessionId) {
|
|
1096
|
+
const syntheticPopupTarget = target.openerTargetId
|
|
1097
|
+
? {
|
|
1098
|
+
targetId,
|
|
1099
|
+
tabId: target.tabId,
|
|
1100
|
+
...(typeof target.url === "string" ? { url: target.url } : {}),
|
|
1101
|
+
...(typeof target.title === "string" ? { title: target.title } : {}),
|
|
1102
|
+
openerTargetId: target.openerTargetId
|
|
1103
|
+
}
|
|
1104
|
+
: null;
|
|
1105
|
+
if (syntheticPopupTarget && await this.attachTargetViaOpenerSession(session, syntheticPopupTarget).catch(() => false)) {
|
|
1106
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1107
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
this.sendPopupAttachPendingError(message, session, targetId);
|
|
474
1111
|
return;
|
|
475
1112
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
1113
|
+
if (target && !target.synthetic) {
|
|
1114
|
+
const targetHasUsableDebuggee = this.hasUsableDebuggee(target);
|
|
1115
|
+
const hydratedPopupTarget = !targetHasUsableDebuggee
|
|
1116
|
+
? await this.hydratePopupOpenerTarget(session, targetId)
|
|
1117
|
+
: null;
|
|
1118
|
+
const popupTarget = hydratedPopupTarget?.openerTargetId
|
|
1119
|
+
? hydratedPopupTarget
|
|
1120
|
+
: target.openerTargetId
|
|
1121
|
+
? {
|
|
1122
|
+
targetId,
|
|
1123
|
+
tabId: target.tabId,
|
|
1124
|
+
...(typeof target.url === "string" ? { url: target.url } : {}),
|
|
1125
|
+
...(typeof target.title === "string" ? { title: target.title } : {}),
|
|
1126
|
+
openerTargetId: target.openerTargetId
|
|
1127
|
+
}
|
|
1128
|
+
: null;
|
|
1129
|
+
if (popupTarget?.openerTargetId) {
|
|
1130
|
+
const resolvedPopupTarget = this.resolveTargetContext(session, targetId);
|
|
1131
|
+
if (resolvedPopupTarget && this.hasUsableDebuggee(resolvedPopupTarget)) {
|
|
1132
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1133
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const deferPopupActivation = Boolean(popupTarget?.openerTargetId && !targetHasUsableDebuggee);
|
|
1138
|
+
if (!deferPopupActivation) {
|
|
1139
|
+
await this.tabs.activateTab(target.tabId).catch(() => undefined);
|
|
1140
|
+
}
|
|
1141
|
+
if (!targetHasUsableDebuggee) {
|
|
1142
|
+
if (popupTarget?.openerTargetId && await this.attachTargetViaOpenerSession(session, popupTarget).catch(() => false)) {
|
|
1143
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1144
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (popupTarget?.openerTargetId) {
|
|
1148
|
+
const resolvedPopupTarget = this.resolveTargetContext(session, targetId);
|
|
1149
|
+
if (resolvedPopupTarget && this.hasUsableDebuggee(resolvedPopupTarget)) {
|
|
1150
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1151
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (this.shouldPreferDirectPopupTabAttach(popupTarget)) {
|
|
1155
|
+
try {
|
|
1156
|
+
await this.attachTargetTab(target.tabId);
|
|
1157
|
+
await this.enableTargetDomains(target.tabId, true);
|
|
1158
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1159
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
if (!isAttachBlockedError(error)) {
|
|
1164
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1165
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
this.sendPopupAttachPendingError(message, session, targetId);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
await this.attachTargetTab(target.tabId);
|
|
1175
|
+
await this.enableTargetDomains(target.tabId, true);
|
|
1176
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1177
|
+
}
|
|
1178
|
+
catch (error) {
|
|
1179
|
+
if (isAttachBlockedError(error) && popupTarget && await this.attachTargetViaOpenerSession(session, popupTarget).catch(() => false)) {
|
|
1180
|
+
session.activeTargetId = targetId;
|
|
1181
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
1182
|
+
await this.tabs.activateTab(target.tabId).catch(() => undefined);
|
|
1183
|
+
const tab = await this.tabs.getTab(target.tabId);
|
|
1184
|
+
this.sendResponse(message, {
|
|
1185
|
+
activeTargetId: targetId,
|
|
1186
|
+
url: resolveReportedTargetUrl(this.resolveTargetContext(session, targetId), tab),
|
|
1187
|
+
title: resolveReportedTargetTitle(this.resolveTargetContext(session, targetId), tab)
|
|
1188
|
+
});
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1192
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
484
1196
|
}
|
|
485
|
-
|
|
486
|
-
session.activeTargetId = target.targetId;
|
|
487
|
-
this.sendResponse(message, { targetId: target.targetId });
|
|
1197
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
488
1198
|
}
|
|
489
|
-
async
|
|
1199
|
+
async handleTargetsRegisterCanvas(message, session) {
|
|
490
1200
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
491
|
-
const targetId = typeof payload.targetId === "string" ? payload.targetId :
|
|
1201
|
+
const targetId = typeof payload.targetId === "string" ? payload.targetId.trim() : "";
|
|
492
1202
|
if (!targetId) {
|
|
493
1203
|
this.sendError(message, buildError("invalid_request", "Missing targetId", false));
|
|
494
1204
|
return;
|
|
495
1205
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
499
|
-
return;
|
|
1206
|
+
try {
|
|
1207
|
+
this.sendResponse(message, await this.registerCanvasTarget(session, targetId));
|
|
500
1208
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
1209
|
+
catch (error) {
|
|
1210
|
+
const detail = error instanceof Error ? error.message : "Canvas target registration failed";
|
|
1211
|
+
if (detail === "Canvas targetId must be tab-<id>.") {
|
|
1212
|
+
this.sendError(message, buildError("invalid_request", detail, false));
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (detail === "Unknown targetId") {
|
|
1216
|
+
this.sendError(message, buildError("invalid_request", detail, false));
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
if (detail === "Only the extension canvas tab can be registered.") {
|
|
1220
|
+
this.sendError(message, buildError("restricted_url", detail, false));
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
logError("ops.register_canvas_target", error, {
|
|
1224
|
+
code: "canvas_target_attach_failed",
|
|
1225
|
+
extra: { targetId }
|
|
1226
|
+
});
|
|
1227
|
+
this.sendError(message, buildError("execution_failed", detail, false));
|
|
506
1228
|
return;
|
|
507
1229
|
}
|
|
508
|
-
this.sendResponse(message, { ok: true });
|
|
509
1230
|
}
|
|
510
|
-
async
|
|
1231
|
+
async registerCanvasTarget(session, targetId) {
|
|
1232
|
+
const tabId = parseTabTargetId(targetId);
|
|
1233
|
+
if (tabId === null) {
|
|
1234
|
+
throw new Error("Canvas targetId must be tab-<id>.");
|
|
1235
|
+
}
|
|
1236
|
+
let tab = await this.tabs.getTab(tabId);
|
|
1237
|
+
if (!tab) {
|
|
1238
|
+
throw new Error("Unknown targetId");
|
|
1239
|
+
}
|
|
1240
|
+
await this.tabs.waitForTabComplete(tabId, 5000).catch(() => undefined);
|
|
1241
|
+
tab = await this.tabs.getTab(tabId) ?? tab;
|
|
1242
|
+
if (!this.isAllowedCanvasTargetUrl(tab.url)) {
|
|
1243
|
+
throw new Error("Only the extension canvas tab can be registered.");
|
|
1244
|
+
}
|
|
1245
|
+
const existing = session.targets.get(targetId);
|
|
1246
|
+
if (existing) {
|
|
1247
|
+
existing.url = tab.url ?? existing.url;
|
|
1248
|
+
existing.title = tab.title ?? existing.title;
|
|
1249
|
+
session.activeTargetId = targetId;
|
|
1250
|
+
return {
|
|
1251
|
+
targetId,
|
|
1252
|
+
url: existing.url,
|
|
1253
|
+
title: existing.title,
|
|
1254
|
+
adopted: false
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
try {
|
|
1258
|
+
await this.attachTargetTab(tabId);
|
|
1259
|
+
await this.enableTargetDomains(tabId);
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
logError("ops.register_canvas_target", error, {
|
|
1263
|
+
code: "canvas_target_attach_failed",
|
|
1264
|
+
extra: { tabId, targetId }
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
const target = this.sessions.addTarget(session.id, tabId, { url: tab.url ?? undefined, title: tab.title ?? undefined });
|
|
1268
|
+
session.activeTargetId = target.targetId;
|
|
1269
|
+
return {
|
|
1270
|
+
targetId: target.targetId,
|
|
1271
|
+
url: target.url,
|
|
1272
|
+
title: target.title,
|
|
1273
|
+
adopted: true
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
async handleTargetsNew(message, session) {
|
|
1277
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1278
|
+
const url = typeof payload.url === "string" ? payload.url : undefined;
|
|
1279
|
+
const tab = await this.tabs.createTab(url, false);
|
|
1280
|
+
if (!tab?.id) {
|
|
1281
|
+
this.sendError(message, buildError("execution_failed", "Target creation failed", false));
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
this.rememberCommandCreatedTab(session.id, tab.id, "targets.new");
|
|
1285
|
+
try {
|
|
1286
|
+
const existingTarget = await this.claimCommandCreatedTab(session, tab);
|
|
1287
|
+
try {
|
|
1288
|
+
if (!this.hasAttachedTabDebuggee(tab.id)) {
|
|
1289
|
+
await this.attachCreatedTargetTab(tab.id);
|
|
1290
|
+
}
|
|
1291
|
+
await this.enableTargetDomains(tab.id, true);
|
|
1292
|
+
}
|
|
1293
|
+
catch (error) {
|
|
1294
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1295
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const target = existingTarget ?? this.sessions.addTarget(session.id, tab.id, await this.getCreatedTabSeed(tab));
|
|
1299
|
+
session.activeTargetId = target.targetId;
|
|
1300
|
+
await this.activateCreatedTab(tab.id);
|
|
1301
|
+
this.sendResponse(message, { targetId: target.targetId });
|
|
1302
|
+
}
|
|
1303
|
+
finally {
|
|
1304
|
+
this.forgetCommandCreatedTab(tab.id);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
async handleTargetsClose(message, session) {
|
|
1308
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1309
|
+
const targetId = typeof payload.targetId === "string" ? payload.targetId : null;
|
|
1310
|
+
if (!targetId) {
|
|
1311
|
+
this.sendError(message, buildError("invalid_request", "Missing targetId", false));
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const target = session.targets.get(targetId) ?? null;
|
|
1315
|
+
const synthetic = this.sessions.getSyntheticTarget(session.id, targetId);
|
|
1316
|
+
if (!target && !synthetic) {
|
|
1317
|
+
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (synthetic) {
|
|
1321
|
+
this.sessions.removeSyntheticTarget(session.id, targetId);
|
|
1322
|
+
await this.cdp.sendCommand(synthetic.sessionId ? { tabId: synthetic.tabId, sessionId: synthetic.sessionId } : { tabId: synthetic.tabId }, "Target.closeTarget", {
|
|
1323
|
+
targetId: synthetic.targetId
|
|
1324
|
+
}).catch(() => undefined);
|
|
1325
|
+
this.restoreSyntheticFallbackTarget(session, synthetic);
|
|
1326
|
+
this.sendResponse(message, { ok: true });
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (!target) {
|
|
1330
|
+
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
this.sessions.removeTarget(session.id, targetId);
|
|
1334
|
+
void this.closeTabBestEffort(target.tabId);
|
|
1335
|
+
if (target.targetId === session.targetId || session.targets.size === 0) {
|
|
1336
|
+
this.sendResponse(message, { ok: true });
|
|
1337
|
+
this.scheduleSessionCleanup(session.id, "ops_session_closed");
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
this.sendResponse(message, { ok: true });
|
|
1341
|
+
}
|
|
1342
|
+
async handlePageOpen(message, session) {
|
|
511
1343
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
512
1344
|
const name = typeof payload.name === "string" ? payload.name : null;
|
|
513
1345
|
if (!name) {
|
|
@@ -521,34 +1353,44 @@ export class OpsRuntime {
|
|
|
521
1353
|
return;
|
|
522
1354
|
}
|
|
523
1355
|
const url = typeof payload.url === "string" ? payload.url : undefined;
|
|
524
|
-
const tab = await this.tabs.createTab(url,
|
|
1356
|
+
const tab = await this.tabs.createTab(url, false);
|
|
525
1357
|
if (!tab?.id) {
|
|
526
1358
|
this.sendError(message, buildError("execution_failed", "Target creation failed", false));
|
|
527
1359
|
return;
|
|
528
1360
|
}
|
|
529
|
-
|
|
1361
|
+
this.rememberCommandCreatedTab(session.id, tab.id, "page.open");
|
|
530
1362
|
try {
|
|
531
|
-
await this.
|
|
1363
|
+
const existingTarget = await this.claimCommandCreatedTab(session, tab);
|
|
1364
|
+
try {
|
|
1365
|
+
if (!this.hasAttachedTabDebuggee(tab.id)) {
|
|
1366
|
+
await this.attachCreatedTargetTab(tab.id);
|
|
1367
|
+
}
|
|
1368
|
+
await this.enableTargetDomains(tab.id, true);
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1372
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const target = existingTarget ?? this.sessions.addTarget(session.id, tab.id, await this.getCreatedTabSeed(tab));
|
|
1376
|
+
this.sessions.setName(session.id, target.targetId, name);
|
|
1377
|
+
session.activeTargetId = target.targetId;
|
|
1378
|
+
await this.activateCreatedTab(tab.id);
|
|
1379
|
+
this.sendResponse(message, { targetId: target.targetId, created: true, url: target.url, title: target.title });
|
|
532
1380
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
this.sendError(message, buildError("cdp_attach_failed", detail, false));
|
|
536
|
-
return;
|
|
1381
|
+
finally {
|
|
1382
|
+
this.forgetCommandCreatedTab(tab.id);
|
|
537
1383
|
}
|
|
538
|
-
const target = this.sessions.addTarget(session.id, tab.id, { url: tab.url ?? undefined, title: tab.title ?? undefined });
|
|
539
|
-
this.sessions.setName(session.id, target.targetId, name);
|
|
540
|
-
session.activeTargetId = target.targetId;
|
|
541
|
-
this.sendResponse(message, { targetId: target.targetId, created: true, url: target.url, title: target.title });
|
|
542
1384
|
}
|
|
543
1385
|
async handlePageList(message, session) {
|
|
544
1386
|
const pages = await Promise.all(this.sessions.listNamedTargets(session.id).map(async ({ name, targetId }) => {
|
|
545
|
-
const target =
|
|
1387
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
546
1388
|
const tab = target ? await this.tabs.getTab(target.tabId) : null;
|
|
547
1389
|
return {
|
|
548
1390
|
name,
|
|
549
1391
|
targetId,
|
|
550
|
-
url:
|
|
551
|
-
title: tab
|
|
1392
|
+
url: resolveReportedTargetUrl(target, tab),
|
|
1393
|
+
title: resolveReportedTargetTitle(target, tab)
|
|
552
1394
|
};
|
|
553
1395
|
}));
|
|
554
1396
|
this.sendResponse(message, { pages });
|
|
@@ -568,7 +1410,7 @@ export class OpsRuntime {
|
|
|
568
1410
|
const target = session.targets.get(targetId);
|
|
569
1411
|
if (target) {
|
|
570
1412
|
this.sessions.removeTarget(session.id, targetId);
|
|
571
|
-
|
|
1413
|
+
void this.closeTabBestEffort(target.tabId);
|
|
572
1414
|
if (target.targetId === session.targetId || session.targets.size === 0) {
|
|
573
1415
|
this.sendResponse(message, { ok: true });
|
|
574
1416
|
this.scheduleSessionCleanup(session.id, "ops_session_closed");
|
|
@@ -584,11 +1426,14 @@ export class OpsRuntime {
|
|
|
584
1426
|
this.sendError(message, buildError("invalid_request", "Missing url", false));
|
|
585
1427
|
return;
|
|
586
1428
|
}
|
|
1429
|
+
const syntheticHtml = decodeHtmlDataUrl(url);
|
|
587
1430
|
try {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
1431
|
+
if (syntheticHtml === null) {
|
|
1432
|
+
const restriction = getRestrictionMessage(new URL(url));
|
|
1433
|
+
if (restriction) {
|
|
1434
|
+
this.sendError(message, buildError("restricted_url", restriction, false));
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
592
1437
|
}
|
|
593
1438
|
}
|
|
594
1439
|
catch {
|
|
@@ -601,6 +1446,27 @@ export class OpsRuntime {
|
|
|
601
1446
|
if (!target)
|
|
602
1447
|
return;
|
|
603
1448
|
await this.tabs.activateTab(target.tabId).catch(() => undefined);
|
|
1449
|
+
const targetRecord = session.targets.get(target.targetId);
|
|
1450
|
+
if (syntheticHtml !== null) {
|
|
1451
|
+
const result = await executeInTab(target.tabId, replaceDocumentWithHtmlScript, [{ html: syntheticHtml }]);
|
|
1452
|
+
session.refStore.clearTarget(target.targetId);
|
|
1453
|
+
this.sessions.upsertSyntheticTarget(session.id, {
|
|
1454
|
+
targetId: target.targetId,
|
|
1455
|
+
tabId: target.tabId,
|
|
1456
|
+
type: "page",
|
|
1457
|
+
url,
|
|
1458
|
+
title: typeof result?.title === "string" && result.title.trim().length > 0
|
|
1459
|
+
? result.title
|
|
1460
|
+
: targetRecord?.title,
|
|
1461
|
+
attachedAt: Date.now()
|
|
1462
|
+
});
|
|
1463
|
+
this.sendResponse(message, {
|
|
1464
|
+
finalUrl: url,
|
|
1465
|
+
status: undefined,
|
|
1466
|
+
timingMs: Date.now() - start
|
|
1467
|
+
});
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
604
1470
|
const updated = await new Promise((resolve) => {
|
|
605
1471
|
chrome.tabs.update(target.tabId, { url }, (tab) => {
|
|
606
1472
|
resolve(tab ?? null);
|
|
@@ -608,10 +1474,13 @@ export class OpsRuntime {
|
|
|
608
1474
|
});
|
|
609
1475
|
await this.tabs.waitForTabComplete(target.tabId, timeoutMs).catch(() => undefined);
|
|
610
1476
|
const refreshed = await this.tabs.getTab(target.tabId);
|
|
611
|
-
|
|
1477
|
+
this.sessions.removeSyntheticTarget(session.id, target.targetId);
|
|
612
1478
|
if (targetRecord) {
|
|
613
|
-
|
|
614
|
-
|
|
1479
|
+
session.targets.set(target.targetId, {
|
|
1480
|
+
...targetRecord,
|
|
1481
|
+
url: refreshed?.url ?? updated?.url ?? url,
|
|
1482
|
+
title: refreshed?.title ?? updated?.title ?? targetRecord.title
|
|
1483
|
+
});
|
|
615
1484
|
}
|
|
616
1485
|
this.sendResponse(message, {
|
|
617
1486
|
finalUrl: refreshed?.url ?? updated?.url ?? url,
|
|
@@ -628,11 +1497,16 @@ export class OpsRuntime {
|
|
|
628
1497
|
return;
|
|
629
1498
|
if (typeof payload.ref === "string") {
|
|
630
1499
|
const state = payload.state === "visible" || payload.state === "hidden" ? payload.state : "attached";
|
|
631
|
-
const
|
|
632
|
-
if (!
|
|
1500
|
+
const resolved = this.resolveRefFromPayload(session, payload.ref, message);
|
|
1501
|
+
if (!resolved)
|
|
633
1502
|
return;
|
|
634
1503
|
try {
|
|
635
|
-
|
|
1504
|
+
if (this.isAllowedCanvasTargetUrl(target.url)) {
|
|
1505
|
+
await this.waitForSelector(target, resolved.selector, state, timeoutMs);
|
|
1506
|
+
}
|
|
1507
|
+
else {
|
|
1508
|
+
await this.waitForRefState(resolved, state, timeoutMs);
|
|
1509
|
+
}
|
|
636
1510
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
637
1511
|
}
|
|
638
1512
|
catch (error) {
|
|
@@ -650,62 +1524,90 @@ export class OpsRuntime {
|
|
|
650
1524
|
}
|
|
651
1525
|
async handleSnapshot(message, session) {
|
|
652
1526
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1527
|
+
const snapshot = await this.captureSnapshotPayload(message, session, {
|
|
1528
|
+
mode: payload.mode === "actionables" ? "actionables" : "outline",
|
|
1529
|
+
maxChars: typeof payload.maxChars === "number" ? payload.maxChars : 16000,
|
|
1530
|
+
cursor: typeof payload.cursor === "string" ? payload.cursor : undefined,
|
|
1531
|
+
maxNodes: typeof payload.maxNodes === "number" ? payload.maxNodes : undefined
|
|
1532
|
+
});
|
|
1533
|
+
if (!snapshot)
|
|
659
1534
|
return;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1535
|
+
this.sendResponse(message, {
|
|
1536
|
+
snapshotId: snapshot.snapshotId,
|
|
1537
|
+
url: snapshot.url,
|
|
1538
|
+
title: snapshot.title,
|
|
1539
|
+
content: snapshot.content,
|
|
1540
|
+
truncated: snapshot.truncated,
|
|
1541
|
+
...(snapshot.nextCursor ? { nextCursor: snapshot.nextCursor } : {}),
|
|
1542
|
+
refCount: snapshot.refCount,
|
|
1543
|
+
timingMs: snapshot.timingMs,
|
|
1544
|
+
warnings: snapshot.warnings
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
async handleReview(message, session) {
|
|
1548
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1549
|
+
const snapshot = await this.captureSnapshotPayload(message, session, {
|
|
1550
|
+
mode: "actionables",
|
|
1551
|
+
maxChars: typeof payload.maxChars === "number" ? payload.maxChars : 16000,
|
|
1552
|
+
cursor: typeof payload.cursor === "string" ? payload.cursor : undefined,
|
|
1553
|
+
maxNodes: typeof payload.maxNodes === "number" ? payload.maxNodes : undefined
|
|
1554
|
+
});
|
|
1555
|
+
if (!snapshot)
|
|
671
1556
|
return;
|
|
672
|
-
}
|
|
673
|
-
const tab = await this.tabs.getTab(target.tabId);
|
|
674
1557
|
this.sendResponse(message, {
|
|
1558
|
+
sessionId: session.id,
|
|
1559
|
+
targetId: snapshot.target.targetId,
|
|
1560
|
+
mode: "extension",
|
|
675
1561
|
snapshotId: snapshot.snapshotId,
|
|
676
|
-
url:
|
|
677
|
-
title:
|
|
678
|
-
content,
|
|
679
|
-
truncated,
|
|
680
|
-
nextCursor,
|
|
681
|
-
refCount: snapshot.
|
|
682
|
-
timingMs:
|
|
683
|
-
|
|
1562
|
+
url: snapshot.url,
|
|
1563
|
+
title: snapshot.title,
|
|
1564
|
+
content: snapshot.content,
|
|
1565
|
+
truncated: snapshot.truncated,
|
|
1566
|
+
...(snapshot.nextCursor ? { nextCursor: snapshot.nextCursor } : {}),
|
|
1567
|
+
refCount: snapshot.refCount,
|
|
1568
|
+
timingMs: snapshot.timingMs,
|
|
1569
|
+
dialog: this.serializeDialogState(session, snapshot.target.targetId),
|
|
1570
|
+
...(snapshot.warnings.length > 0 ? { warnings: snapshot.warnings } : {})
|
|
684
1571
|
});
|
|
685
1572
|
}
|
|
686
1573
|
async handleClick(message, session) {
|
|
687
|
-
const
|
|
688
|
-
if (!
|
|
689
|
-
return;
|
|
690
|
-
const target = this.requireActiveTarget(session, message);
|
|
691
|
-
if (!target)
|
|
1574
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1575
|
+
if (!resolved)
|
|
692
1576
|
return;
|
|
693
1577
|
const start = Date.now();
|
|
694
|
-
const before = await this.tabs.getTab(target.tabId);
|
|
695
|
-
await this.
|
|
696
|
-
|
|
1578
|
+
const before = await this.tabs.getTab(resolved.target.tabId);
|
|
1579
|
+
await this.tabs.activateTab(resolved.target.tabId).catch(() => undefined);
|
|
1580
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1581
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "click" }, () => this.dom.click(resolved.target.tabId, resolved.selector));
|
|
1582
|
+
}
|
|
1583
|
+
else {
|
|
1584
|
+
await this.callFunctionOnRef(resolved, DOM_SCROLL_INTO_VIEW_DECLARATION);
|
|
1585
|
+
const point = await this.resolveRefPoint(resolved);
|
|
1586
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mouseMoved", point.x, point.y);
|
|
1587
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mousePressed", point.x, point.y, {
|
|
1588
|
+
button: "left",
|
|
1589
|
+
clickCount: 1
|
|
1590
|
+
});
|
|
1591
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mouseReleased", point.x, point.y, {
|
|
1592
|
+
button: "left",
|
|
1593
|
+
clickCount: 1
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
const after = await this.tabs.getTab(resolved.target.tabId);
|
|
697
1597
|
const navigated = Boolean(before?.url && after?.url && before.url !== after.url);
|
|
698
1598
|
this.sendResponse(message, { timingMs: Date.now() - start, navigated });
|
|
699
1599
|
}
|
|
700
1600
|
async handleHover(message, session) {
|
|
701
|
-
const
|
|
702
|
-
if (!
|
|
703
|
-
return;
|
|
704
|
-
const target = this.requireActiveTarget(session, message);
|
|
705
|
-
if (!target)
|
|
1601
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1602
|
+
if (!resolved)
|
|
706
1603
|
return;
|
|
707
1604
|
const start = Date.now();
|
|
708
|
-
|
|
1605
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1606
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "hover" }, () => this.dom.hover(resolved.target.tabId, resolved.selector));
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
await this.callFunctionOnRef(resolved, DOM_HOVER_DECLARATION);
|
|
1610
|
+
}
|
|
709
1611
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
710
1612
|
}
|
|
711
1613
|
async handlePress(message, session) {
|
|
@@ -718,22 +1620,33 @@ export class OpsRuntime {
|
|
|
718
1620
|
const target = this.requireActiveTarget(session, message);
|
|
719
1621
|
if (!target)
|
|
720
1622
|
return;
|
|
721
|
-
const
|
|
722
|
-
if (payload.ref && !
|
|
1623
|
+
const resolved = typeof payload.ref === "string" ? this.resolveRefFromPayload(session, payload.ref, message) : null;
|
|
1624
|
+
if (payload.ref && !resolved)
|
|
723
1625
|
return;
|
|
724
1626
|
const start = Date.now();
|
|
725
|
-
|
|
1627
|
+
if (resolved && this.isAllowedCanvasTargetUrl(target.url)) {
|
|
1628
|
+
await this.runCanvasPageAction(target, { type: "press", key }, resolved.selector, () => this.dom.press(target.tabId, resolved.selector, key));
|
|
1629
|
+
}
|
|
1630
|
+
else if (resolved) {
|
|
1631
|
+
await this.callFunctionOnRef(resolved, DOM_FOCUS_DECLARATION);
|
|
1632
|
+
await this.dispatchKeyPress(target.debuggee, key);
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
await this.dispatchKeyPress(target.debuggee, key);
|
|
1636
|
+
}
|
|
726
1637
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
727
1638
|
}
|
|
728
1639
|
async handleCheck(message, session, checked) {
|
|
729
|
-
const
|
|
730
|
-
if (!
|
|
731
|
-
return;
|
|
732
|
-
const target = this.requireActiveTarget(session, message);
|
|
733
|
-
if (!target)
|
|
1640
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1641
|
+
if (!resolved)
|
|
734
1642
|
return;
|
|
735
1643
|
const start = Date.now();
|
|
736
|
-
|
|
1644
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1645
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "setChecked", checked }, () => this.dom.setChecked(resolved.target.tabId, resolved.selector, checked));
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
await this.callFunctionOnRef(resolved, DOM_SET_CHECKED_DECLARATION, [checked]);
|
|
1649
|
+
}
|
|
737
1650
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
738
1651
|
}
|
|
739
1652
|
async handleType(message, session) {
|
|
@@ -744,14 +1657,16 @@ export class OpsRuntime {
|
|
|
744
1657
|
this.sendError(message, buildError("invalid_request", "Missing ref or text", false));
|
|
745
1658
|
return;
|
|
746
1659
|
}
|
|
747
|
-
const
|
|
748
|
-
if (!
|
|
749
|
-
return;
|
|
750
|
-
const target = this.requireActiveTarget(session, message);
|
|
751
|
-
if (!target)
|
|
1660
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1661
|
+
if (!resolved)
|
|
752
1662
|
return;
|
|
753
1663
|
const start = Date.now();
|
|
754
|
-
|
|
1664
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1665
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "type", value: text, clear: payload.clear === true, submit: payload.submit === true }, () => this.dom.type(resolved.target.tabId, resolved.selector, text, payload.clear === true, payload.submit === true));
|
|
1666
|
+
}
|
|
1667
|
+
else {
|
|
1668
|
+
await this.callFunctionOnRef(resolved, DOM_TYPE_DECLARATION, [text, payload.clear === true, payload.submit === true]);
|
|
1669
|
+
}
|
|
755
1670
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
756
1671
|
}
|
|
757
1672
|
async handleSelect(message, session) {
|
|
@@ -762,37 +1677,126 @@ export class OpsRuntime {
|
|
|
762
1677
|
this.sendError(message, buildError("invalid_request", "Missing ref or values", false));
|
|
763
1678
|
return;
|
|
764
1679
|
}
|
|
765
|
-
const
|
|
766
|
-
if (!
|
|
767
|
-
return;
|
|
768
|
-
const target = this.requireActiveTarget(session, message);
|
|
769
|
-
if (!target)
|
|
1680
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1681
|
+
if (!resolved)
|
|
770
1682
|
return;
|
|
771
|
-
|
|
1683
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1684
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "select", values: values }, () => this.dom.select(resolved.target.tabId, resolved.selector, values));
|
|
1685
|
+
}
|
|
1686
|
+
else {
|
|
1687
|
+
await this.callFunctionOnRef(resolved, DOM_SELECT_DECLARATION, [values]);
|
|
1688
|
+
}
|
|
772
1689
|
this.sendResponse(message, {});
|
|
773
1690
|
}
|
|
774
1691
|
async handleScroll(message, session) {
|
|
775
1692
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
776
1693
|
const dy = typeof payload.dy === "number" ? payload.dy : 0;
|
|
777
1694
|
const ref = typeof payload.ref === "string" ? payload.ref : undefined;
|
|
778
|
-
const selector = ref ? this.resolveSelector(session, ref, message) ?? undefined : undefined;
|
|
779
|
-
if (ref && !selector)
|
|
780
|
-
return;
|
|
781
1695
|
const target = this.requireActiveTarget(session, message);
|
|
782
1696
|
if (!target)
|
|
783
1697
|
return;
|
|
784
|
-
|
|
1698
|
+
const resolved = ref ? this.resolveRefFromPayload(session, ref, message) : null;
|
|
1699
|
+
if (ref && !resolved)
|
|
1700
|
+
return;
|
|
1701
|
+
if (resolved && !this.isAllowedCanvasTargetUrl(target.url)) {
|
|
1702
|
+
await this.callFunctionOnRef(resolved, DOM_SCROLL_BY_DECLARATION, [dy]);
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
const selector = resolved?.selector;
|
|
1706
|
+
await this.runCanvasPageAction(target, { type: "scroll", dy }, selector ?? null, () => this.dom.scroll(target.tabId, dy, selector));
|
|
1707
|
+
}
|
|
785
1708
|
this.sendResponse(message, {});
|
|
786
1709
|
}
|
|
787
1710
|
async handleScrollIntoView(message, session) {
|
|
788
|
-
const
|
|
789
|
-
if (!
|
|
1711
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1712
|
+
if (!resolved)
|
|
1713
|
+
return;
|
|
1714
|
+
const start = Date.now();
|
|
1715
|
+
if (this.isAllowedCanvasTargetUrl(resolved.target.url)) {
|
|
1716
|
+
await this.runElementAction(resolved.target, resolved.selector, { type: "scrollIntoView" }, () => this.dom.scrollIntoView(resolved.target.tabId, resolved.selector));
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
await this.callFunctionOnRef(resolved, DOM_SCROLL_INTO_VIEW_DECLARATION);
|
|
1720
|
+
}
|
|
1721
|
+
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
1722
|
+
}
|
|
1723
|
+
async handlePointerMove(message, session) {
|
|
1724
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1725
|
+
const coords = this.parsePointerCoords(payload);
|
|
1726
|
+
if (!coords) {
|
|
1727
|
+
this.sendError(message, buildError("invalid_request", "Pointer move requires numeric x and y.", false));
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const target = this.requireActiveTarget(session, message);
|
|
1731
|
+
if (!target)
|
|
1732
|
+
return;
|
|
1733
|
+
const start = Date.now();
|
|
1734
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseMoved", coords.x, coords.y, {
|
|
1735
|
+
steps: typeof payload.steps === "number" && Number.isFinite(payload.steps)
|
|
1736
|
+
? Math.max(1, Math.floor(payload.steps))
|
|
1737
|
+
: undefined
|
|
1738
|
+
});
|
|
1739
|
+
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
1740
|
+
}
|
|
1741
|
+
async handlePointerDown(message, session) {
|
|
1742
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1743
|
+
const coords = this.parsePointerCoords(payload);
|
|
1744
|
+
if (!coords) {
|
|
1745
|
+
this.sendError(message, buildError("invalid_request", "Pointer down requires numeric x and y.", false));
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
const target = this.requireActiveTarget(session, message);
|
|
1749
|
+
if (!target)
|
|
1750
|
+
return;
|
|
1751
|
+
const start = Date.now();
|
|
1752
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseMoved", coords.x, coords.y);
|
|
1753
|
+
await this.dispatchMouseEvent(target.debuggee, "mousePressed", coords.x, coords.y, {
|
|
1754
|
+
button: this.parsePointerButton(payload.button),
|
|
1755
|
+
clickCount: typeof payload.clickCount === "number" && Number.isFinite(payload.clickCount)
|
|
1756
|
+
? Math.max(1, Math.floor(payload.clickCount))
|
|
1757
|
+
: 1
|
|
1758
|
+
});
|
|
1759
|
+
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
1760
|
+
}
|
|
1761
|
+
async handlePointerUp(message, session) {
|
|
1762
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1763
|
+
const coords = this.parsePointerCoords(payload);
|
|
1764
|
+
if (!coords) {
|
|
1765
|
+
this.sendError(message, buildError("invalid_request", "Pointer up requires numeric x and y.", false));
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
const target = this.requireActiveTarget(session, message);
|
|
1769
|
+
if (!target)
|
|
1770
|
+
return;
|
|
1771
|
+
const start = Date.now();
|
|
1772
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseMoved", coords.x, coords.y);
|
|
1773
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseReleased", coords.x, coords.y, {
|
|
1774
|
+
button: this.parsePointerButton(payload.button),
|
|
1775
|
+
clickCount: typeof payload.clickCount === "number" && Number.isFinite(payload.clickCount)
|
|
1776
|
+
? Math.max(1, Math.floor(payload.clickCount))
|
|
1777
|
+
: 1
|
|
1778
|
+
});
|
|
1779
|
+
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
1780
|
+
}
|
|
1781
|
+
async handlePointerDrag(message, session) {
|
|
1782
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1783
|
+
const from = isRecord(payload.from) ? this.parsePointerCoords(payload.from) : null;
|
|
1784
|
+
const to = isRecord(payload.to) ? this.parsePointerCoords(payload.to) : null;
|
|
1785
|
+
if (!from || !to) {
|
|
1786
|
+
this.sendError(message, buildError("invalid_request", "Pointer drag requires numeric from/to coordinates.", false));
|
|
790
1787
|
return;
|
|
1788
|
+
}
|
|
791
1789
|
const target = this.requireActiveTarget(session, message);
|
|
792
1790
|
if (!target)
|
|
793
1791
|
return;
|
|
794
1792
|
const start = Date.now();
|
|
795
|
-
|
|
1793
|
+
const steps = typeof payload.steps === "number" && Number.isFinite(payload.steps)
|
|
1794
|
+
? Math.max(1, Math.floor(payload.steps))
|
|
1795
|
+
: 1;
|
|
1796
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseMoved", from.x, from.y);
|
|
1797
|
+
await this.dispatchMouseEvent(target.debuggee, "mousePressed", from.x, from.y);
|
|
1798
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseMoved", to.x, to.y, { steps });
|
|
1799
|
+
await this.dispatchMouseEvent(target.debuggee, "mouseReleased", to.x, to.y);
|
|
796
1800
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
797
1801
|
}
|
|
798
1802
|
async handleDomGetHtml(message, session) {
|
|
@@ -803,13 +1807,12 @@ export class OpsRuntime {
|
|
|
803
1807
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
804
1808
|
return;
|
|
805
1809
|
}
|
|
806
|
-
const
|
|
807
|
-
if (!
|
|
808
|
-
return;
|
|
809
|
-
const target = this.requireActiveTarget(session, message);
|
|
810
|
-
if (!target)
|
|
1810
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1811
|
+
if (!resolved)
|
|
811
1812
|
return;
|
|
812
|
-
const html =
|
|
1813
|
+
const html = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1814
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "outerHTML" }, () => this.dom.getOuterHtml(resolved.target.tabId, resolved.selector))
|
|
1815
|
+
: await this.callFunctionOnRef(resolved, DOM_OUTER_HTML_DECLARATION);
|
|
813
1816
|
const truncated = html.length > maxChars;
|
|
814
1817
|
const outerHTML = truncated ? html.slice(0, maxChars) : html;
|
|
815
1818
|
this.sendResponse(message, { outerHTML, truncated });
|
|
@@ -822,13 +1825,12 @@ export class OpsRuntime {
|
|
|
822
1825
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
823
1826
|
return;
|
|
824
1827
|
}
|
|
825
|
-
const
|
|
826
|
-
if (!
|
|
1828
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1829
|
+
if (!resolved)
|
|
827
1830
|
return;
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const text = await this.dom.getInnerText(target.tabId, selector);
|
|
1831
|
+
const text = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1832
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "innerText" }, () => this.dom.getInnerText(resolved.target.tabId, resolved.selector))
|
|
1833
|
+
: await this.callFunctionOnRef(resolved, DOM_INNER_TEXT_DECLARATION);
|
|
832
1834
|
const truncated = text.length > maxChars;
|
|
833
1835
|
this.sendResponse(message, { text: truncated ? text.slice(0, maxChars) : text, truncated });
|
|
834
1836
|
}
|
|
@@ -840,13 +1842,12 @@ export class OpsRuntime {
|
|
|
840
1842
|
this.sendError(message, buildError("invalid_request", "Missing ref or name", false));
|
|
841
1843
|
return;
|
|
842
1844
|
}
|
|
843
|
-
const
|
|
844
|
-
if (!
|
|
845
|
-
return;
|
|
846
|
-
const target = this.requireActiveTarget(session, message);
|
|
847
|
-
if (!target)
|
|
1845
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1846
|
+
if (!resolved)
|
|
848
1847
|
return;
|
|
849
|
-
const value =
|
|
1848
|
+
const value = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1849
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "getAttr", name }, () => this.dom.getAttr(resolved.target.tabId, resolved.selector, name))
|
|
1850
|
+
: await this.callFunctionOnRef(resolved, DOM_GET_ATTR_DECLARATION, [name]);
|
|
850
1851
|
this.sendResponse(message, { value });
|
|
851
1852
|
}
|
|
852
1853
|
async handleDomGetValue(message, session) {
|
|
@@ -856,94 +1857,271 @@ export class OpsRuntime {
|
|
|
856
1857
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
857
1858
|
return;
|
|
858
1859
|
}
|
|
859
|
-
const
|
|
860
|
-
if (!
|
|
861
|
-
return;
|
|
862
|
-
const target = this.requireActiveTarget(session, message);
|
|
863
|
-
if (!target)
|
|
1860
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1861
|
+
if (!resolved)
|
|
864
1862
|
return;
|
|
865
|
-
const value =
|
|
1863
|
+
const value = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1864
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "getValue" }, () => this.dom.getValue(resolved.target.tabId, resolved.selector))
|
|
1865
|
+
: await this.callFunctionOnRef(resolved, DOM_GET_VALUE_DECLARATION);
|
|
866
1866
|
this.sendResponse(message, { value });
|
|
867
1867
|
}
|
|
868
1868
|
async handleDomIsVisible(message, session) {
|
|
869
|
-
const
|
|
870
|
-
if (!
|
|
871
|
-
return;
|
|
872
|
-
const target = this.requireActiveTarget(session, message);
|
|
873
|
-
if (!target)
|
|
1869
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1870
|
+
if (!resolved)
|
|
874
1871
|
return;
|
|
875
|
-
const visible =
|
|
876
|
-
|
|
1872
|
+
const visible = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1873
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "getSelectorState" }, async () => await this.dom.getSelectorState(resolved.target.tabId, resolved.selector))
|
|
1874
|
+
: await this.callFunctionOnRef(resolved, DOM_IS_VISIBLE_DECLARATION);
|
|
1875
|
+
const isVisible = typeof visible === "object" && visible !== null && "visible" in visible
|
|
1876
|
+
? Boolean(visible.visible)
|
|
1877
|
+
: Boolean(visible);
|
|
1878
|
+
this.sendResponse(message, { value: isVisible });
|
|
877
1879
|
}
|
|
878
1880
|
async handleDomIsEnabled(message, session) {
|
|
879
|
-
const
|
|
880
|
-
if (!
|
|
881
|
-
return;
|
|
882
|
-
const target = this.requireActiveTarget(session, message);
|
|
883
|
-
if (!target)
|
|
1881
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1882
|
+
if (!resolved)
|
|
884
1883
|
return;
|
|
885
|
-
const enabled =
|
|
1884
|
+
const enabled = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1885
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "isEnabled" }, () => this.dom.isEnabled(resolved.target.tabId, resolved.selector))
|
|
1886
|
+
: await this.callFunctionOnRef(resolved, DOM_IS_ENABLED_DECLARATION);
|
|
886
1887
|
this.sendResponse(message, { value: enabled });
|
|
887
1888
|
}
|
|
888
1889
|
async handleDomIsChecked(message, session) {
|
|
889
|
-
const
|
|
890
|
-
if (!
|
|
891
|
-
return;
|
|
892
|
-
const target = this.requireActiveTarget(session, message);
|
|
893
|
-
if (!target)
|
|
1890
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1891
|
+
if (!resolved)
|
|
894
1892
|
return;
|
|
895
|
-
const checked =
|
|
1893
|
+
const checked = this.isAllowedCanvasTargetUrl(resolved.target.url)
|
|
1894
|
+
? await this.runElementAction(resolved.target, resolved.selector, { type: "isChecked" }, () => this.dom.isChecked(resolved.target.tabId, resolved.selector))
|
|
1895
|
+
: await this.callFunctionOnRef(resolved, DOM_IS_CHECKED_DECLARATION);
|
|
896
1896
|
this.sendResponse(message, { value: checked });
|
|
897
1897
|
}
|
|
898
|
-
async
|
|
1898
|
+
async handleDomRefPoint(message, session) {
|
|
1899
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1900
|
+
if (!resolved)
|
|
1901
|
+
return;
|
|
1902
|
+
const point = await this.resolveRefPoint(resolved);
|
|
1903
|
+
this.sendResponse(message, point);
|
|
1904
|
+
}
|
|
1905
|
+
async handleCanvasOverlayMount(message, session) {
|
|
899
1906
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1907
|
+
const mountId = typeof payload.mountId === "string" && payload.mountId.trim().length > 0
|
|
1908
|
+
? payload.mountId.trim()
|
|
1909
|
+
: `mount_${createId()}`;
|
|
1910
|
+
const title = typeof payload.title === "string" && payload.title.trim().length > 0
|
|
1911
|
+
? payload.title.trim()
|
|
1912
|
+
: "OpenDevBrowser Canvas";
|
|
1913
|
+
const prototypeId = typeof payload.prototypeId === "string" && payload.prototypeId.trim().length > 0
|
|
1914
|
+
? payload.prototypeId.trim()
|
|
1915
|
+
: "prototype";
|
|
900
1916
|
const target = this.requireActiveTarget(session, message);
|
|
901
1917
|
if (!target)
|
|
902
1918
|
return;
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1919
|
+
const selection = parseCanvasOverlaySelection(payload.selection, target.targetId);
|
|
1920
|
+
const result = await this.dom.mountCanvasOverlay(target.tabId, {
|
|
1921
|
+
mountId,
|
|
1922
|
+
title,
|
|
1923
|
+
prototypeId,
|
|
1924
|
+
selection
|
|
1925
|
+
});
|
|
1926
|
+
this.sendResponse(message, {
|
|
1927
|
+
mountId,
|
|
1928
|
+
targetId: target.targetId,
|
|
1929
|
+
previewState: "background",
|
|
1930
|
+
overlayState: result.overlayState ?? "mounted",
|
|
1931
|
+
capabilities: { selection: true, guides: true }
|
|
909
1932
|
});
|
|
910
|
-
this.sendResponse(message, { capture });
|
|
911
1933
|
}
|
|
912
|
-
async
|
|
1934
|
+
async handleCanvasOverlayUnmount(message, session) {
|
|
913
1935
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
914
|
-
const
|
|
915
|
-
if (!
|
|
916
|
-
this.sendError(message, buildError("invalid_request", "Missing
|
|
1936
|
+
const mountId = typeof payload.mountId === "string" ? payload.mountId.trim() : "";
|
|
1937
|
+
if (!mountId) {
|
|
1938
|
+
this.sendError(message, buildError("invalid_request", "Missing mountId", false));
|
|
917
1939
|
return;
|
|
918
1940
|
}
|
|
919
|
-
const selector = this.resolveSelector(session, ref, message);
|
|
920
|
-
if (!selector)
|
|
921
|
-
return;
|
|
922
1941
|
const target = this.requireActiveTarget(session, message);
|
|
923
1942
|
if (!target)
|
|
924
1943
|
return;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1944
|
+
await this.dom.unmountCanvasOverlay(target.tabId, mountId);
|
|
1945
|
+
this.sendResponse(message, {
|
|
1946
|
+
ok: true,
|
|
1947
|
+
mountId,
|
|
1948
|
+
targetId: target.targetId,
|
|
1949
|
+
overlayState: "idle"
|
|
931
1950
|
});
|
|
932
|
-
this.sendResponse(message, { capture });
|
|
933
1951
|
}
|
|
934
|
-
async
|
|
1952
|
+
async handleCanvasOverlaySelect(message, session) {
|
|
1953
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1954
|
+
const mountId = typeof payload.mountId === "string" ? payload.mountId.trim() : "";
|
|
1955
|
+
const nodeId = typeof payload.nodeId === "string" && payload.nodeId.trim().length > 0
|
|
1956
|
+
? payload.nodeId.trim()
|
|
1957
|
+
: null;
|
|
1958
|
+
const selectionHint = isRecord(payload.selectionHint) ? payload.selectionHint : {};
|
|
1959
|
+
if (!mountId || (!nodeId && Object.keys(selectionHint).length === 0)) {
|
|
1960
|
+
this.sendError(message, buildError("invalid_request", "Missing mountId or selection target", false));
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
935
1963
|
const target = this.requireActiveTarget(session, message);
|
|
936
1964
|
if (!target)
|
|
937
1965
|
return;
|
|
938
|
-
const
|
|
1966
|
+
const selection = await this.dom.selectCanvasOverlay(target.tabId, { nodeId, selectionHint });
|
|
1967
|
+
this.sendResponse(message, {
|
|
1968
|
+
mountId,
|
|
1969
|
+
targetId: target.targetId,
|
|
1970
|
+
selection
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
async handleCanvasOverlaySync(message, session) {
|
|
1974
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1975
|
+
const mountId = typeof payload.mountId === "string" ? payload.mountId.trim() : "";
|
|
1976
|
+
if (!mountId) {
|
|
1977
|
+
this.sendError(message, buildError("invalid_request", "Missing mountId", false));
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const title = typeof payload.title === "string" && payload.title.trim().length > 0
|
|
1981
|
+
? payload.title.trim()
|
|
1982
|
+
: "OpenDevBrowser Canvas";
|
|
1983
|
+
const target = this.requireActiveTarget(session, message);
|
|
1984
|
+
if (!target)
|
|
1985
|
+
return;
|
|
1986
|
+
const selection = parseCanvasOverlaySelection(payload.selection, target.targetId);
|
|
1987
|
+
const result = await this.dom.syncCanvasOverlay(target.tabId, {
|
|
1988
|
+
mountId,
|
|
1989
|
+
title,
|
|
1990
|
+
selection
|
|
1991
|
+
});
|
|
1992
|
+
this.sendResponse(message, {
|
|
1993
|
+
ok: true,
|
|
1994
|
+
mountId,
|
|
1995
|
+
targetId: target.targetId,
|
|
1996
|
+
overlayState: result.overlayState ?? "mounted"
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
async handleCanvasRuntimePreviewBridge(message, session) {
|
|
2000
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2001
|
+
const bindingId = typeof payload.bindingId === "string" ? payload.bindingId.trim() : "";
|
|
2002
|
+
const rootSelector = typeof payload.rootSelector === "string" ? payload.rootSelector.trim() : "";
|
|
2003
|
+
const html = typeof payload.html === "string" ? payload.html : "";
|
|
2004
|
+
if (!bindingId || !rootSelector) {
|
|
2005
|
+
this.sendError(message, buildError("invalid_request", "Missing bindingId or rootSelector", false));
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const target = this.requireActiveTarget(session, message);
|
|
2009
|
+
if (!target)
|
|
2010
|
+
return;
|
|
2011
|
+
const result = await this.dom.applyRuntimePreviewBridge(target.tabId, bindingId, rootSelector, html);
|
|
2012
|
+
this.sendResponse(message, result);
|
|
2013
|
+
}
|
|
2014
|
+
async handleClonePage(message, session) {
|
|
2015
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2016
|
+
const target = this.requireActiveTarget(session, message);
|
|
2017
|
+
if (!target)
|
|
2018
|
+
return;
|
|
2019
|
+
const canvasCapture = await this.captureCanvasPage(target.tabId, target.targetId);
|
|
2020
|
+
if (canvasCapture) {
|
|
2021
|
+
this.sendResponse(message, { capture: canvasCapture });
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const capture = await this.dom.captureDom(target.tabId, "body", {
|
|
2025
|
+
sanitize: payload.sanitize !== false,
|
|
2026
|
+
maxNodes: typeof payload.maxNodes === "number" ? payload.maxNodes : undefined,
|
|
2027
|
+
inlineStyles: payload.inlineStyles !== false,
|
|
2028
|
+
styleAllowlist: Array.isArray(payload.styleAllowlist) ? payload.styleAllowlist.filter((item) => typeof item === "string") : [],
|
|
2029
|
+
skipStyleValues: Array.isArray(payload.skipStyleValues) ? payload.skipStyleValues.filter((item) => typeof item === "string") : []
|
|
2030
|
+
});
|
|
2031
|
+
this.sendResponse(message, { capture });
|
|
2032
|
+
}
|
|
2033
|
+
async handleCloneComponent(message, session) {
|
|
2034
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2035
|
+
const ref = typeof payload.ref === "string" ? payload.ref : null;
|
|
2036
|
+
if (!ref) {
|
|
2037
|
+
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
const selector = this.resolveSelector(session, ref, message);
|
|
2041
|
+
if (!selector)
|
|
2042
|
+
return;
|
|
2043
|
+
const target = this.requireActiveTarget(session, message);
|
|
2044
|
+
if (!target)
|
|
2045
|
+
return;
|
|
2046
|
+
const capture = await this.dom.captureDom(target.tabId, selector, {
|
|
2047
|
+
sanitize: payload.sanitize !== false,
|
|
2048
|
+
maxNodes: typeof payload.maxNodes === "number" ? payload.maxNodes : undefined,
|
|
2049
|
+
inlineStyles: payload.inlineStyles !== false,
|
|
2050
|
+
styleAllowlist: Array.isArray(payload.styleAllowlist) ? payload.styleAllowlist.filter((item) => typeof item === "string") : [],
|
|
2051
|
+
skipStyleValues: Array.isArray(payload.skipStyleValues) ? payload.skipStyleValues.filter((item) => typeof item === "string") : []
|
|
2052
|
+
});
|
|
2053
|
+
this.sendResponse(message, { capture });
|
|
2054
|
+
}
|
|
2055
|
+
async handlePerf(message, session) {
|
|
2056
|
+
const target = this.requireActiveTarget(session, message);
|
|
2057
|
+
if (!target)
|
|
2058
|
+
return;
|
|
2059
|
+
const result = await this.cdp.sendCommand(target.debuggee, "Performance.getMetrics", {});
|
|
939
2060
|
this.sendResponse(message, { metrics: Array.isArray(result.metrics) ? result.metrics : [] });
|
|
940
2061
|
}
|
|
941
2062
|
async handleScreenshot(message, session) {
|
|
2063
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2064
|
+
if (payload.fullPage === true && typeof payload.ref === "string") {
|
|
2065
|
+
this.sendError(message, buildError("invalid_request", "ref and fullPage cannot be combined.", false));
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (typeof payload.ref === "string") {
|
|
2069
|
+
const resolved = this.resolveRefFromPayload(session, payload, message);
|
|
2070
|
+
if (!resolved)
|
|
2071
|
+
return;
|
|
2072
|
+
try {
|
|
2073
|
+
await this.callFunctionOnRef(resolved, DOM_SCROLL_INTO_VIEW_DECLARATION);
|
|
2074
|
+
const clip = await this.callFunctionOnRef(resolved, DOM_SCREENSHOT_CLIP_DECLARATION);
|
|
2075
|
+
const result = await withTimeout(this.cdp.sendCommand(resolved.target.debuggee, "Page.captureScreenshot", {
|
|
2076
|
+
format: "png",
|
|
2077
|
+
captureBeyondViewport: true,
|
|
2078
|
+
clip: this.normalizeScreenshotClip(clip, resolved.ref)
|
|
2079
|
+
}), SCREENSHOT_TIMEOUT_MS, "Ops screenshot timed out");
|
|
2080
|
+
if (result?.data) {
|
|
2081
|
+
this.sendResponse(message, { base64: result.data });
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
logError("ops.screenshot.ref", error, { code: "screenshot_failed" });
|
|
2087
|
+
}
|
|
2088
|
+
this.sendError(message, buildError("execution_failed", "Screenshot failed", false));
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
942
2091
|
const target = this.requireActiveTarget(session, message);
|
|
943
2092
|
if (!target)
|
|
944
2093
|
return;
|
|
2094
|
+
if (payload.fullPage === true) {
|
|
2095
|
+
try {
|
|
2096
|
+
const metrics = await this.cdp.sendCommand(target.debuggee, "Page.getLayoutMetrics", {});
|
|
2097
|
+
const contentSize = isRecord(metrics.cssContentSize) ? metrics.cssContentSize : metrics.contentSize;
|
|
2098
|
+
const width = typeof contentSize?.width === "number" && Number.isFinite(contentSize.width)
|
|
2099
|
+
? Math.max(1, Math.ceil(contentSize.width))
|
|
2100
|
+
: null;
|
|
2101
|
+
const height = typeof contentSize?.height === "number" && Number.isFinite(contentSize.height)
|
|
2102
|
+
? Math.max(1, Math.ceil(contentSize.height))
|
|
2103
|
+
: null;
|
|
2104
|
+
if (width === null || height === null) {
|
|
2105
|
+
throw new Error("Full-page screenshot metrics unavailable");
|
|
2106
|
+
}
|
|
2107
|
+
const result = await withTimeout(this.cdp.sendCommand(target.debuggee, "Page.captureScreenshot", {
|
|
2108
|
+
format: "png",
|
|
2109
|
+
captureBeyondViewport: true,
|
|
2110
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
2111
|
+
}), SCREENSHOT_TIMEOUT_MS, "Ops screenshot timed out");
|
|
2112
|
+
if (result?.data) {
|
|
2113
|
+
this.sendResponse(message, { base64: result.data });
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
catch (error) {
|
|
2118
|
+
logError("ops.screenshot.full_page", error, { code: "screenshot_failed" });
|
|
2119
|
+
}
|
|
2120
|
+
this.sendError(message, buildError("execution_failed", "Screenshot failed", false));
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
945
2123
|
try {
|
|
946
|
-
const result = await withTimeout(this.cdp.sendCommand(
|
|
2124
|
+
const result = await withTimeout(this.cdp.sendCommand(target.debuggee, "Page.captureScreenshot", { format: "png" }), SCREENSHOT_TIMEOUT_MS, "Ops screenshot timed out");
|
|
947
2125
|
if (result?.data) {
|
|
948
2126
|
this.sendResponse(message, { base64: result.data });
|
|
949
2127
|
return;
|
|
@@ -959,6 +2137,102 @@ export class OpsRuntime {
|
|
|
959
2137
|
}
|
|
960
2138
|
this.sendError(message, buildError("execution_failed", "Screenshot failed", false));
|
|
961
2139
|
}
|
|
2140
|
+
async handleUpload(message, session) {
|
|
2141
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2142
|
+
const files = Array.isArray(payload.files)
|
|
2143
|
+
? payload.files.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
2144
|
+
: [];
|
|
2145
|
+
if (files.length === 0) {
|
|
2146
|
+
this.sendError(message, buildError("invalid_request", "Missing files", false));
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
const resolved = this.resolveRefFromPayload(session, payload, message);
|
|
2150
|
+
if (!resolved)
|
|
2151
|
+
return;
|
|
2152
|
+
try {
|
|
2153
|
+
const info = await this.callFunctionOnRef(resolved, DOM_FILE_INPUT_INFO_DECLARATION);
|
|
2154
|
+
if (info?.disabled === true) {
|
|
2155
|
+
this.sendError(message, buildError("execution_failed", `Cannot upload files to disabled ref: ${resolved.ref}`, false));
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (info?.isFileInput === true) {
|
|
2159
|
+
await this.cdp.sendCommand(resolved.target.debuggee, "DOM.setFileInputFiles", {
|
|
2160
|
+
backendNodeId: resolved.backendNodeId,
|
|
2161
|
+
files
|
|
2162
|
+
});
|
|
2163
|
+
this.sendResponse(message, {
|
|
2164
|
+
targetId: resolved.target.targetId,
|
|
2165
|
+
fileCount: files.length,
|
|
2166
|
+
mode: "direct_input"
|
|
2167
|
+
});
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
this.sessions.clearFileChooser(session.id, resolved.target.targetId);
|
|
2171
|
+
await this.cdp.sendCommand(resolved.target.debuggee, "Page.setInterceptFileChooserDialog", { enabled: true });
|
|
2172
|
+
await this.callFunctionOnRef(resolved, DOM_SCROLL_INTO_VIEW_DECLARATION);
|
|
2173
|
+
const point = await this.resolveRefPoint(resolved);
|
|
2174
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mouseMoved", point.x, point.y);
|
|
2175
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mousePressed", point.x, point.y, {
|
|
2176
|
+
button: "left",
|
|
2177
|
+
clickCount: 1
|
|
2178
|
+
});
|
|
2179
|
+
await this.dispatchMouseEvent(resolved.target.debuggee, "mouseReleased", point.x, point.y, {
|
|
2180
|
+
button: "left",
|
|
2181
|
+
clickCount: 1
|
|
2182
|
+
});
|
|
2183
|
+
const chooser = await this.waitForFileChooser(session.id, resolved.target.targetId);
|
|
2184
|
+
if (typeof chooser.backendNodeId !== "number") {
|
|
2185
|
+
throw new Error("File chooser opened without backend node id");
|
|
2186
|
+
}
|
|
2187
|
+
await this.cdp.sendCommand(resolved.target.debuggee, "DOM.setFileInputFiles", {
|
|
2188
|
+
backendNodeId: chooser.backendNodeId,
|
|
2189
|
+
files
|
|
2190
|
+
});
|
|
2191
|
+
this.sessions.clearFileChooser(session.id, resolved.target.targetId);
|
|
2192
|
+
this.sendResponse(message, {
|
|
2193
|
+
targetId: resolved.target.targetId,
|
|
2194
|
+
fileCount: files.length,
|
|
2195
|
+
mode: "file_chooser"
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
catch (error) {
|
|
2199
|
+
this.sessions.clearFileChooser(session.id, resolved.target.targetId);
|
|
2200
|
+
logError("ops.upload", error, { code: "upload_failed" });
|
|
2201
|
+
this.sendError(message, buildError("execution_failed", error instanceof Error ? error.message : "Upload failed", false));
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
async handleDialog(message, session) {
|
|
2205
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2206
|
+
const action = payload.action === "accept" || payload.action === "dismiss" || payload.action === "status"
|
|
2207
|
+
? payload.action
|
|
2208
|
+
: "status";
|
|
2209
|
+
const target = this.requireActiveTarget(session, message);
|
|
2210
|
+
if (!target)
|
|
2211
|
+
return;
|
|
2212
|
+
const dialog = this.serializeDialogState(session, target.targetId);
|
|
2213
|
+
if (!dialog.open || action === "status") {
|
|
2214
|
+
this.sendResponse(message, {
|
|
2215
|
+
dialog,
|
|
2216
|
+
...(action === "status" ? {} : { handled: false })
|
|
2217
|
+
});
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
try {
|
|
2221
|
+
await this.cdp.sendCommand(target.debuggee, "Page.handleJavaScriptDialog", {
|
|
2222
|
+
accept: action === "accept",
|
|
2223
|
+
...(action === "accept" && typeof payload.promptText === "string" ? { promptText: payload.promptText } : {})
|
|
2224
|
+
});
|
|
2225
|
+
this.sessions.clearDialog(session.id, target.targetId);
|
|
2226
|
+
this.sendResponse(message, {
|
|
2227
|
+
dialog: { open: false, targetId: target.targetId },
|
|
2228
|
+
handled: true
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
catch (error) {
|
|
2232
|
+
logError("ops.dialog", error, { code: "dialog_failed" });
|
|
2233
|
+
this.sendError(message, buildError("execution_failed", error instanceof Error ? error.message : "Dialog handling failed", false));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
962
2236
|
async handleConsolePoll(message, session) {
|
|
963
2237
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
964
2238
|
const sinceSeq = typeof payload.sinceSeq === "number" ? payload.sinceSeq : 0;
|
|
@@ -1011,7 +2285,7 @@ export class OpsRuntime {
|
|
|
1011
2285
|
if (!target)
|
|
1012
2286
|
return;
|
|
1013
2287
|
try {
|
|
1014
|
-
await this.cdp.sendCommand(
|
|
2288
|
+
await this.cdp.sendCommand(target.debuggee, "Network.setCookies", { cookies: normalized });
|
|
1015
2289
|
}
|
|
1016
2290
|
catch (error) {
|
|
1017
2291
|
const detail = error instanceof Error ? error.message : "Cookie import failed";
|
|
@@ -1044,7 +2318,7 @@ export class OpsRuntime {
|
|
|
1044
2318
|
return;
|
|
1045
2319
|
let rawCookies = [];
|
|
1046
2320
|
try {
|
|
1047
|
-
const response = await this.cdp.sendCommand(
|
|
2321
|
+
const response = await this.cdp.sendCommand(target.debuggee, "Network.getCookies", urls ? { urls } : {});
|
|
1048
2322
|
rawCookies = Array.isArray(response.cookies) ? response.cookies : [];
|
|
1049
2323
|
}
|
|
1050
2324
|
catch (error) {
|
|
@@ -1061,20 +2335,323 @@ export class OpsRuntime {
|
|
|
1061
2335
|
count: cookies.length
|
|
1062
2336
|
});
|
|
1063
2337
|
}
|
|
1064
|
-
async
|
|
2338
|
+
async attachTargetTab(tabId) {
|
|
2339
|
+
try {
|
|
2340
|
+
await this.cdp.attach(tabId);
|
|
2341
|
+
}
|
|
2342
|
+
catch (error) {
|
|
2343
|
+
if (isAttachBlockedError(error)) {
|
|
2344
|
+
await delay(50);
|
|
2345
|
+
try {
|
|
2346
|
+
await this.cdp.attach(tabId);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
catch (retryError) {
|
|
2350
|
+
error = retryError;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
const diagnostic = this.cdp.getLastRootAttachDiagnostic?.(tabId) ?? null;
|
|
2354
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
2355
|
+
logError("ops.direct_attach_stage", error instanceof Error ? error : new Error(detail), {
|
|
2356
|
+
code: "direct_attach_stage",
|
|
2357
|
+
extra: {
|
|
2358
|
+
tabId,
|
|
2359
|
+
...(this.toDirectAttachDiagnosticDetails(diagnostic) ?? {}),
|
|
2360
|
+
...(!diagnostic ? { reason: detail } : {})
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
throw this.decorateDirectAttachError(error, diagnostic);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
async attachCreatedTargetTab(tabId) {
|
|
2367
|
+
try {
|
|
2368
|
+
await this.attachTargetTab(tabId);
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
catch (error) {
|
|
2372
|
+
if (!isAttachBlockedError(error)) {
|
|
2373
|
+
throw error;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
await this.tabs.waitForTabComplete(tabId).catch(() => undefined);
|
|
2377
|
+
if (this.hasAttachedTabDebuggee(tabId)) {
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
this.cdp.markClientClosed();
|
|
2381
|
+
await this.attachTargetTab(tabId);
|
|
2382
|
+
}
|
|
2383
|
+
async attachStartUrlConnectTab(tabId) {
|
|
2384
|
+
return this.attachLaunchTargetTab(tabId, true);
|
|
2385
|
+
}
|
|
2386
|
+
async attachLaunchTargetTab(tabId, waitForTabCompleteBeforeRetry) {
|
|
2387
|
+
try {
|
|
2388
|
+
await this.attachTargetTab(tabId);
|
|
2389
|
+
return null;
|
|
2390
|
+
}
|
|
2391
|
+
catch (error) {
|
|
2392
|
+
if (!isAttachBlockedError(error)) {
|
|
2393
|
+
throw error;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
this.cdp.markClientClosed();
|
|
2397
|
+
if (waitForTabCompleteBeforeRetry) {
|
|
2398
|
+
await this.tabs.waitForTabComplete(tabId).catch(() => undefined);
|
|
2399
|
+
}
|
|
2400
|
+
const refreshedTab = waitForTabCompleteBeforeRetry
|
|
2401
|
+
? await this.tabs.getTab(tabId)
|
|
2402
|
+
: null;
|
|
2403
|
+
try {
|
|
2404
|
+
await this.attachTargetTab(tabId);
|
|
2405
|
+
return refreshedTab ?? null;
|
|
2406
|
+
}
|
|
2407
|
+
catch (error) {
|
|
2408
|
+
if (!isAttachBlockedError(error) || typeof this.cdp.refreshTabAttachment !== "function") {
|
|
2409
|
+
throw error;
|
|
2410
|
+
}
|
|
2411
|
+
await this.cdp.refreshTabAttachment(tabId);
|
|
2412
|
+
await this.resolveReadyTabDebuggee(tabId, { strict: true, allowRefresh: false });
|
|
2413
|
+
return await this.tabs.getTab(tabId) ?? refreshedTab ?? null;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
async getCreatedTabSeed(tab) {
|
|
2417
|
+
const refreshedTab = typeof tab.id === "number"
|
|
2418
|
+
? await this.tabs.getTab(tab.id)
|
|
2419
|
+
: null;
|
|
2420
|
+
return getReportedTabSeed(refreshedTab ?? tab);
|
|
2421
|
+
}
|
|
2422
|
+
async claimCommandCreatedTab(session, tab) {
|
|
2423
|
+
const tabId = typeof tab.id === "number" ? tab.id : null;
|
|
2424
|
+
if (tabId === null) {
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
const currentOwner = this.sessions.getByTabId(tabId);
|
|
2428
|
+
if (currentOwner && currentOwner.id !== session.id) {
|
|
2429
|
+
this.sessions.removeTargetByTabId(currentOwner.id, tabId);
|
|
2430
|
+
}
|
|
2431
|
+
const existingTargetId = this.sessions.getTargetIdByTabId(session.id, tabId);
|
|
2432
|
+
if (!existingTargetId) {
|
|
2433
|
+
return null;
|
|
2434
|
+
}
|
|
2435
|
+
const target = session.targets.get(existingTargetId) ?? null;
|
|
2436
|
+
if (!target) {
|
|
2437
|
+
return null;
|
|
2438
|
+
}
|
|
2439
|
+
const seed = await this.getCreatedTabSeed(tab);
|
|
2440
|
+
if (typeof seed.url === "string" && seed.url.length > 0) {
|
|
2441
|
+
target.url = seed.url;
|
|
2442
|
+
}
|
|
2443
|
+
if (typeof seed.title === "string" && seed.title.length > 0) {
|
|
2444
|
+
target.title = seed.title;
|
|
2445
|
+
}
|
|
2446
|
+
target.openerTargetId = undefined;
|
|
2447
|
+
return target;
|
|
2448
|
+
}
|
|
2449
|
+
async activateCreatedTab(tabId) {
|
|
2450
|
+
await this.tabs.activateTab(tabId).catch(() => undefined);
|
|
2451
|
+
}
|
|
2452
|
+
rememberCommandCreatedTab(sessionId, tabId, kind) {
|
|
2453
|
+
this.commandCreatedTabs.set(tabId, { sessionId, kind });
|
|
2454
|
+
}
|
|
2455
|
+
isCommandCreatedTab(tabId, sessionId) {
|
|
2456
|
+
const entry = this.commandCreatedTabs.get(tabId);
|
|
2457
|
+
return sessionId ? entry?.sessionId === sessionId : Boolean(entry);
|
|
2458
|
+
}
|
|
2459
|
+
forgetCommandCreatedTab(tabId) {
|
|
2460
|
+
this.commandCreatedTabs.delete(tabId);
|
|
2461
|
+
}
|
|
2462
|
+
isConcreteDebuggee(debuggee) {
|
|
2463
|
+
return Boolean(debuggee
|
|
2464
|
+
&& ((typeof debuggee.sessionId === "string" && debuggee.sessionId.length > 0)
|
|
2465
|
+
|| (typeof debuggee.targetId === "string" && debuggee.targetId.length > 0)));
|
|
2466
|
+
}
|
|
2467
|
+
hasAttachedSessionDebuggee(debuggee) {
|
|
2468
|
+
return Boolean(debuggee && typeof debuggee.sessionId === "string" && debuggee.sessionId.length > 0);
|
|
2469
|
+
}
|
|
2470
|
+
hasAttachedTabDebuggee(tabId) {
|
|
2471
|
+
if (typeof this.cdp.isTabAttached === "function") {
|
|
2472
|
+
return this.cdp.isTabAttached(tabId);
|
|
2473
|
+
}
|
|
2474
|
+
if (typeof this.cdp.getAttachedTabIds === "function") {
|
|
2475
|
+
return this.cdp.getAttachedTabIds().includes(tabId);
|
|
2476
|
+
}
|
|
2477
|
+
return false;
|
|
2478
|
+
}
|
|
2479
|
+
async resolveReadyTabDebuggee(tabId, options) {
|
|
2480
|
+
const readDebuggee = () => this.cdp.getTabDebuggee?.(tabId);
|
|
2481
|
+
let pageDebuggee = readDebuggee();
|
|
2482
|
+
if (this.hasAttachedSessionDebuggee(pageDebuggee)) {
|
|
2483
|
+
return pageDebuggee;
|
|
2484
|
+
}
|
|
2485
|
+
if (!options.strict && this.isConcreteDebuggee(pageDebuggee)) {
|
|
2486
|
+
return pageDebuggee;
|
|
2487
|
+
}
|
|
2488
|
+
await this.cdp.primeAttachedRootSession?.(tabId);
|
|
2489
|
+
pageDebuggee = readDebuggee();
|
|
2490
|
+
if (this.hasAttachedSessionDebuggee(pageDebuggee)) {
|
|
2491
|
+
return pageDebuggee;
|
|
2492
|
+
}
|
|
2493
|
+
if (!options.strict && this.isConcreteDebuggee(pageDebuggee)) {
|
|
2494
|
+
return pageDebuggee;
|
|
2495
|
+
}
|
|
2496
|
+
if (options.allowRefresh && typeof this.cdp.refreshTabAttachment === "function") {
|
|
2497
|
+
await this.cdp.refreshTabAttachment(tabId);
|
|
2498
|
+
await this.cdp.primeAttachedRootSession?.(tabId);
|
|
2499
|
+
pageDebuggee = readDebuggee();
|
|
2500
|
+
if (this.hasAttachedSessionDebuggee(pageDebuggee)) {
|
|
2501
|
+
return pageDebuggee;
|
|
2502
|
+
}
|
|
2503
|
+
if (!options.strict && this.isConcreteDebuggee(pageDebuggee)) {
|
|
2504
|
+
return pageDebuggee;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
if (this.isConcreteDebuggee(pageDebuggee)) {
|
|
2508
|
+
return pageDebuggee;
|
|
2509
|
+
}
|
|
2510
|
+
if (options.strict) {
|
|
2511
|
+
throw new Error(`Concrete debugger session unavailable for tab ${tabId}.`);
|
|
2512
|
+
}
|
|
2513
|
+
return pageDebuggee ?? { tabId };
|
|
2514
|
+
}
|
|
2515
|
+
async enableTargetDomains(tabId, strict = false) {
|
|
2516
|
+
const buildEnablementFailureDetails = (allowRefresh, refreshedAfterBlock, enablementStage) => ({
|
|
2517
|
+
phase: "strict_enablement",
|
|
2518
|
+
enablementStage,
|
|
2519
|
+
tabId,
|
|
2520
|
+
strict,
|
|
2521
|
+
allowRefresh,
|
|
2522
|
+
refreshedAfterBlock
|
|
2523
|
+
});
|
|
2524
|
+
const enableOnce = async (allowRefresh, refreshedAfterBlock) => {
|
|
2525
|
+
let pageDebuggee;
|
|
2526
|
+
try {
|
|
2527
|
+
pageDebuggee = await this.resolveReadyTabDebuggee(tabId, {
|
|
2528
|
+
strict,
|
|
2529
|
+
allowRefresh
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
catch (error) {
|
|
2533
|
+
throw this.decorateCdpFailure(error, buildEnablementFailureDetails(allowRefresh, refreshedAfterBlock, "resolve_ready_debuggee"));
|
|
2534
|
+
}
|
|
2535
|
+
await this.enableRootTracking(buildEnablementFailureDetails(allowRefresh, refreshedAfterBlock, "configure_auto_attach"));
|
|
2536
|
+
await this.enableTargetDomainsOnDebuggee(pageDebuggee, buildEnablementFailureDetails(allowRefresh, refreshedAfterBlock, "page_enable"));
|
|
2537
|
+
await this.enableTargetDiscovery(buildEnablementFailureDetails(allowRefresh, refreshedAfterBlock, "set_discover_targets"));
|
|
2538
|
+
};
|
|
2539
|
+
try {
|
|
2540
|
+
try {
|
|
2541
|
+
await enableOnce(strict, false);
|
|
2542
|
+
}
|
|
2543
|
+
catch (error) {
|
|
2544
|
+
if (!strict || !isAttachBlockedError(error) || typeof this.cdp.refreshTabAttachment !== "function") {
|
|
2545
|
+
throw error;
|
|
2546
|
+
}
|
|
2547
|
+
this.cdp.markClientClosed?.();
|
|
2548
|
+
await this.cdp.refreshTabAttachment(tabId);
|
|
2549
|
+
await enableOnce(false, true);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
catch (error) {
|
|
2553
|
+
logError("ops.enable_domains", error, { code: "enable_domains_failed", extra: { tabId, strict } });
|
|
2554
|
+
if (strict) {
|
|
2555
|
+
throw error;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
async enableRootTracking(baseDetails) {
|
|
2560
|
+
try {
|
|
2561
|
+
await this.cdp.configureAutoAttach?.({
|
|
2562
|
+
autoAttach: true,
|
|
2563
|
+
waitForDebuggerOnStart: false,
|
|
2564
|
+
flatten: true
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
catch (error) {
|
|
2568
|
+
throw this.decorateCdpFailure(error, {
|
|
2569
|
+
...baseDetails,
|
|
2570
|
+
enablementStage: "configure_auto_attach"
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
async enableTargetDiscovery(baseDetails) {
|
|
1065
2575
|
try {
|
|
1066
|
-
await this.cdp.
|
|
1067
|
-
await this.cdp.sendCommand({ tabId: session.tabId }, "Network.enable", {});
|
|
1068
|
-
await this.cdp.sendCommand({ tabId: session.tabId }, "Performance.enable", {});
|
|
2576
|
+
await this.cdp.setDiscoverTargetsEnabled?.(true);
|
|
1069
2577
|
}
|
|
1070
2578
|
catch (error) {
|
|
1071
|
-
logError("ops.
|
|
2579
|
+
logError("ops.discover_targets", error, {
|
|
2580
|
+
code: "discover_targets_enable_failed",
|
|
2581
|
+
extra: baseDetails
|
|
2582
|
+
});
|
|
1072
2583
|
}
|
|
1073
2584
|
}
|
|
2585
|
+
async enableTargetDomainsOnDebuggee(debuggee, baseDetails) {
|
|
2586
|
+
const enableCommands = [
|
|
2587
|
+
{ method: "Page.enable", params: {}, stage: "page_enable" },
|
|
2588
|
+
{ method: "Page.setInterceptFileChooserDialog", params: { enabled: true }, stage: "page_file_chooser" },
|
|
2589
|
+
{ method: "Runtime.enable", params: {}, stage: "runtime_enable" },
|
|
2590
|
+
{ method: "Network.enable", params: {}, stage: "network_enable" },
|
|
2591
|
+
{ method: "Performance.enable", params: {}, stage: "performance_enable" }
|
|
2592
|
+
];
|
|
2593
|
+
for (const command of enableCommands) {
|
|
2594
|
+
try {
|
|
2595
|
+
await this.cdp.sendCommand(debuggee, command.method, command.params);
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
throw this.decorateCdpFailure(error, {
|
|
2599
|
+
...baseDetails,
|
|
2600
|
+
enablementStage: command.stage
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
parsePointerCoords(payload) {
|
|
2606
|
+
const x = typeof payload.x === "number" && Number.isFinite(payload.x) ? payload.x : null;
|
|
2607
|
+
const y = typeof payload.y === "number" && Number.isFinite(payload.y) ? payload.y : null;
|
|
2608
|
+
return x === null || y === null ? null : { x, y };
|
|
2609
|
+
}
|
|
2610
|
+
parsePointerButton(value) {
|
|
2611
|
+
return value === "middle" || value === "right" ? value : "left";
|
|
2612
|
+
}
|
|
2613
|
+
async dispatchMouseEvent(debuggee, type, x, y, options = {}) {
|
|
2614
|
+
if (type === "mouseMoved" && options.steps && options.steps > 1) {
|
|
2615
|
+
const stepCount = Math.max(1, options.steps);
|
|
2616
|
+
for (let index = 1; index <= stepCount; index += 1) {
|
|
2617
|
+
await this.cdp.sendCommand(debuggee, "Input.dispatchMouseEvent", {
|
|
2618
|
+
type,
|
|
2619
|
+
x,
|
|
2620
|
+
y,
|
|
2621
|
+
button: options.button ?? "none",
|
|
2622
|
+
clickCount: options.clickCount ?? 0
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
await this.cdp.sendCommand(debuggee, "Input.dispatchMouseEvent", {
|
|
2628
|
+
type,
|
|
2629
|
+
x,
|
|
2630
|
+
y,
|
|
2631
|
+
button: options.button ?? (type === "mouseMoved" ? "none" : "left"),
|
|
2632
|
+
clickCount: options.clickCount ?? (type === "mouseMoved" ? 0 : 1)
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
async dispatchKeyPress(debuggee, key) {
|
|
2636
|
+
const text = key.length === 1 ? key : undefined;
|
|
2637
|
+
await this.cdp.sendCommand(debuggee, "Input.dispatchKeyEvent", {
|
|
2638
|
+
type: "keyDown",
|
|
2639
|
+
key,
|
|
2640
|
+
...(text ? { text } : {})
|
|
2641
|
+
});
|
|
2642
|
+
await this.cdp.sendCommand(debuggee, "Input.dispatchKeyEvent", {
|
|
2643
|
+
type: "keyUp",
|
|
2644
|
+
key
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
1074
2647
|
async withSession(message, clientId, handler) {
|
|
1075
2648
|
const session = this.getSessionForMessage(message, clientId);
|
|
1076
2649
|
if (!session)
|
|
1077
2650
|
return;
|
|
2651
|
+
if (DIALOG_SCOPED_COMMANDS.has(message.command)) {
|
|
2652
|
+
await this.withDialogQueue(message, session, handler);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
1078
2655
|
if (!TARGET_SCOPED_COMMANDS.has(message.command)) {
|
|
1079
2656
|
session.queue = session.queue.then(() => handler(session), () => handler(session));
|
|
1080
2657
|
await session.queue;
|
|
@@ -1096,6 +2673,9 @@ export class OpsRuntime {
|
|
|
1096
2673
|
const requested = typeof payload.targetId === "string" ? payload.targetId.trim() : "";
|
|
1097
2674
|
return requested || session.activeTargetId || session.targetId;
|
|
1098
2675
|
}
|
|
2676
|
+
dialogQueueKey(sessionId, targetId) {
|
|
2677
|
+
return `${sessionId}:${targetId}`;
|
|
2678
|
+
}
|
|
1099
2679
|
sessionQueueAgeMs(session) {
|
|
1100
2680
|
let oldest = null;
|
|
1101
2681
|
for (const value of session.targetQueueOldestAt.values()) {
|
|
@@ -1245,66 +2825,950 @@ export class OpsRuntime {
|
|
|
1245
2825
|
}
|
|
1246
2826
|
}
|
|
1247
2827
|
}
|
|
1248
|
-
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
2828
|
+
async withDialogQueue(message, session, handler) {
|
|
2829
|
+
const targetId = this.resolveTargetIdForQueue(session, message);
|
|
2830
|
+
const queueKey = this.dialogQueueKey(session.id, targetId);
|
|
2831
|
+
const previous = this.dialogQueues.get(queueKey) ?? Promise.resolve();
|
|
2832
|
+
let releaseQueue = () => { };
|
|
2833
|
+
const gate = new Promise((resolve) => {
|
|
2834
|
+
releaseQueue = resolve;
|
|
2835
|
+
});
|
|
2836
|
+
const tail = previous.then(() => gate, () => gate);
|
|
2837
|
+
this.dialogQueues.set(queueKey, tail);
|
|
2838
|
+
await previous;
|
|
2839
|
+
try {
|
|
2840
|
+
await handler(session);
|
|
2841
|
+
}
|
|
2842
|
+
finally {
|
|
2843
|
+
releaseQueue();
|
|
2844
|
+
if (this.dialogQueues.get(queueKey) === tail) {
|
|
2845
|
+
this.dialogQueues.delete(queueKey);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
getSessionForMessage(message, clientId) {
|
|
2850
|
+
const opsSessionId = message.opsSessionId;
|
|
2851
|
+
if (!opsSessionId) {
|
|
2852
|
+
this.sendError(message, buildError("invalid_request", "Missing opsSessionId", false));
|
|
2853
|
+
return null;
|
|
2854
|
+
}
|
|
2855
|
+
const session = this.sessions.get(opsSessionId);
|
|
2856
|
+
if (!session) {
|
|
2857
|
+
this.sendError(message, buildError("invalid_session", "Unknown ops session", false));
|
|
2858
|
+
return null;
|
|
2859
|
+
}
|
|
2860
|
+
const leaseId = typeof message.leaseId === "string" ? message.leaseId : "";
|
|
2861
|
+
if (session.ownerClientId !== clientId) {
|
|
2862
|
+
if (leaseId && leaseId === session.leaseId) {
|
|
2863
|
+
this.reclaimSession(session, clientId);
|
|
2864
|
+
}
|
|
2865
|
+
else {
|
|
2866
|
+
this.sendError(message, buildError("not_owner", "Client does not own session", false));
|
|
2867
|
+
return null;
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
else if (session.state === "closing") {
|
|
2871
|
+
if (leaseId && leaseId === session.leaseId) {
|
|
2872
|
+
this.reclaimSession(session, clientId);
|
|
2873
|
+
}
|
|
2874
|
+
else {
|
|
2875
|
+
this.sendError(message, buildError("not_owner", "Client does not own session", false));
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
if (leaseId !== session.leaseId) {
|
|
2880
|
+
this.sendError(message, buildError("not_owner", "Lease does not match session owner", false));
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
session.lastUsedAt = Date.now();
|
|
2884
|
+
return session;
|
|
2885
|
+
}
|
|
2886
|
+
requestedTargetId(session, message) {
|
|
2887
|
+
return this.extractPayloadTargetId(message.payload) ?? session.activeTargetId ?? session.targetId;
|
|
2888
|
+
}
|
|
2889
|
+
hasOpsTarget(session, targetId) {
|
|
2890
|
+
return session.targets.has(targetId) || this.sessions.getSyntheticTarget(session.id, targetId) !== null;
|
|
2891
|
+
}
|
|
2892
|
+
resolveTargetContext(session, targetId) {
|
|
2893
|
+
const target = session.targets.get(targetId) ?? null;
|
|
2894
|
+
const explicitSynthetic = this.sessions.getSyntheticTarget(session.id, targetId);
|
|
2895
|
+
const bridgeSynthetic = explicitSynthetic ? null : this.findSyntheticSessionBridge(session, target);
|
|
2896
|
+
const synthetic = explicitSynthetic ?? bridgeSynthetic;
|
|
2897
|
+
if (!target && !synthetic) {
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
const targetTabId = target?.tabId ?? synthetic?.tabId ?? session.tabId;
|
|
2901
|
+
const baseType = synthetic?.type ?? "page";
|
|
2902
|
+
return {
|
|
2903
|
+
targetId,
|
|
2904
|
+
tabId: targetTabId,
|
|
2905
|
+
type: baseType,
|
|
2906
|
+
synthetic: explicitSynthetic !== null && !session.targets.has(targetId),
|
|
2907
|
+
...(explicitSynthetic?.url ? { url: explicitSynthetic.url } : target?.url ? { url: target.url } : bridgeSynthetic?.url ? { url: bridgeSynthetic.url } : {}),
|
|
2908
|
+
...(explicitSynthetic?.title ? { title: explicitSynthetic.title } : target?.title ? { title: target.title } : bridgeSynthetic?.title ? { title: bridgeSynthetic.title } : {}),
|
|
2909
|
+
...(synthetic?.sessionId ? { sessionId: synthetic.sessionId } : {}),
|
|
2910
|
+
...(explicitSynthetic?.openerTargetId
|
|
2911
|
+
? { openerTargetId: explicitSynthetic.openerTargetId }
|
|
2912
|
+
: target?.openerTargetId
|
|
2913
|
+
? { openerTargetId: target.openerTargetId }
|
|
2914
|
+
: bridgeSynthetic?.openerTargetId
|
|
2915
|
+
? { openerTargetId: bridgeSynthetic.openerTargetId }
|
|
2916
|
+
: {}),
|
|
2917
|
+
debuggee: synthetic?.sessionId
|
|
2918
|
+
? { tabId: synthetic.tabId, sessionId: synthetic.sessionId }
|
|
2919
|
+
: this.cdp.getTabDebuggee?.(targetTabId) ?? { tabId: targetTabId }
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
resolveDebuggerEventTargetId(session, source, resolvedTabId) {
|
|
2923
|
+
const sourceSessionId = typeof source.sessionId === "string"
|
|
2924
|
+
? source.sessionId
|
|
2925
|
+
: null;
|
|
2926
|
+
if (sourceSessionId) {
|
|
2927
|
+
const synthetic = this.sessions.findSyntheticTargetBySessionId(session.id, sourceSessionId);
|
|
2928
|
+
if (synthetic) {
|
|
2929
|
+
return synthetic.targetId;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
const sourceTargetId = typeof source.targetId === "string" ? source.targetId : null;
|
|
2933
|
+
if (sourceTargetId) {
|
|
2934
|
+
const synthetic = this.sessions.getSyntheticTarget(session.id, sourceTargetId);
|
|
2935
|
+
if (synthetic) {
|
|
2936
|
+
return synthetic.targetId;
|
|
2937
|
+
}
|
|
2938
|
+
if (session.targets.has(sourceTargetId)) {
|
|
2939
|
+
return sourceTargetId;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
const tabId = resolvedTabId ?? this.cdp.resolveSourceTabId(source);
|
|
2943
|
+
if (tabId !== null) {
|
|
2944
|
+
return this.sessions.getTargetIdByTabId(session.id, tabId) ?? session.activeTargetId ?? session.targetId;
|
|
2945
|
+
}
|
|
2946
|
+
return session.activeTargetId ?? session.targetId;
|
|
2947
|
+
}
|
|
2948
|
+
resolveRouterEventTargetId(session, event) {
|
|
2949
|
+
if (typeof event.sessionId === "string") {
|
|
2950
|
+
const synthetic = this.sessions.findSyntheticTargetBySessionId(session.id, event.sessionId);
|
|
2951
|
+
if (synthetic) {
|
|
2952
|
+
return synthetic.targetId;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
return this.sessions.getTargetIdByTabId(session.id, event.tabId) ?? session.activeTargetId ?? session.targetId;
|
|
2956
|
+
}
|
|
2957
|
+
applyDialogOpening(session, targetId, params) {
|
|
2958
|
+
const payload = params;
|
|
2959
|
+
this.sessions.setDialog(session.id, targetId, {
|
|
2960
|
+
open: true,
|
|
2961
|
+
targetId,
|
|
2962
|
+
...(typeof payload?.type === "string" ? { type: payload.type } : {}),
|
|
2963
|
+
...(typeof payload?.message === "string" ? { message: payload.message } : {}),
|
|
2964
|
+
...(typeof payload?.defaultPrompt === "string" ? { defaultPrompt: payload.defaultPrompt } : {}),
|
|
2965
|
+
...(typeof payload?.url === "string" ? { url: payload.url } : {}),
|
|
2966
|
+
openedAt: new Date().toISOString()
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
applyDialogClosed(session, targetId) {
|
|
2970
|
+
this.sessions.clearDialog(session.id, targetId);
|
|
2971
|
+
}
|
|
2972
|
+
applyFileChooserOpened(session, targetId, params) {
|
|
2973
|
+
const payload = params;
|
|
2974
|
+
this.sessions.setFileChooser(session.id, targetId, {
|
|
2975
|
+
open: true,
|
|
2976
|
+
targetId,
|
|
2977
|
+
...(typeof payload?.backendNodeId === "number" ? { backendNodeId: payload.backendNodeId } : {}),
|
|
2978
|
+
openedAt: new Date().toISOString()
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
serializeDialogState(session, targetId) {
|
|
2982
|
+
if (!targetId) {
|
|
2983
|
+
return { open: false, targetId: session.activeTargetId ?? session.targetId };
|
|
2984
|
+
}
|
|
2985
|
+
return this.sessions.getDialog(session.id, targetId) ?? { open: false, targetId };
|
|
2986
|
+
}
|
|
2987
|
+
hasUsableDebuggee(target) {
|
|
2988
|
+
if (typeof target.sessionId === "string" && target.sessionId.length > 0) {
|
|
2989
|
+
if (typeof this.cdp.hasDebuggerSession === "function") {
|
|
2990
|
+
return this.cdp.hasDebuggerSession(target.sessionId);
|
|
2991
|
+
}
|
|
2992
|
+
return true;
|
|
2993
|
+
}
|
|
2994
|
+
return this.isConcreteDebuggee(this.cdp.getTabDebuggee?.(target.tabId));
|
|
2995
|
+
}
|
|
2996
|
+
extractPayloadTargetId(payload) {
|
|
2997
|
+
if (!isRecord(payload)) {
|
|
2998
|
+
return null;
|
|
2999
|
+
}
|
|
3000
|
+
return typeof payload.targetId === "string" && payload.targetId.trim().length > 0
|
|
3001
|
+
? payload.targetId.trim()
|
|
3002
|
+
: null;
|
|
3003
|
+
}
|
|
3004
|
+
resolveRequestedTargetContext(session, targetId, explicitTarget) {
|
|
3005
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
3006
|
+
if (!target) {
|
|
3007
|
+
return null;
|
|
3008
|
+
}
|
|
3009
|
+
if (!explicitTarget
|
|
3010
|
+
&& target.targetId !== session.targetId
|
|
3011
|
+
&& !this.hasUsableDebuggee(target)
|
|
3012
|
+
&& (target.synthetic || typeof target.openerTargetId === "string" || typeof target.sessionId === "string")) {
|
|
3013
|
+
const fallbackTarget = this.resolveTargetContext(session, session.targetId);
|
|
3014
|
+
if (fallbackTarget) {
|
|
3015
|
+
session.activeTargetId = fallbackTarget.targetId;
|
|
3016
|
+
return fallbackTarget;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
return target;
|
|
3020
|
+
}
|
|
3021
|
+
async preparePopupTarget(session, targetId) {
|
|
3022
|
+
let target = this.resolveTargetContext(session, targetId);
|
|
3023
|
+
if (!target || this.hasUsableDebuggee(target)) {
|
|
3024
|
+
return target;
|
|
3025
|
+
}
|
|
3026
|
+
const hydratedPopupTarget = typeof target.sessionId !== "string"
|
|
3027
|
+
? await this.hydratePopupOpenerTarget(session, targetId)
|
|
3028
|
+
: null;
|
|
3029
|
+
const popupTarget = hydratedPopupTarget?.openerTargetId
|
|
3030
|
+
? hydratedPopupTarget
|
|
3031
|
+
: target.openerTargetId
|
|
3032
|
+
? {
|
|
3033
|
+
targetId,
|
|
3034
|
+
tabId: target.tabId,
|
|
3035
|
+
...(typeof target.url === "string" ? { url: target.url } : {}),
|
|
3036
|
+
...(typeof target.title === "string" ? { title: target.title } : {}),
|
|
3037
|
+
openerTargetId: target.openerTargetId
|
|
3038
|
+
}
|
|
3039
|
+
: null;
|
|
3040
|
+
if (!popupTarget?.openerTargetId) {
|
|
3041
|
+
return target;
|
|
3042
|
+
}
|
|
3043
|
+
if (this.shouldPreferDirectPopupTabAttach(popupTarget)) {
|
|
3044
|
+
await this.tabs.activateTab(popupTarget.tabId).catch(() => undefined);
|
|
3045
|
+
try {
|
|
3046
|
+
await this.attachTargetTab(popupTarget.tabId);
|
|
3047
|
+
await this.enableTargetDomains(popupTarget.tabId, true);
|
|
3048
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
3049
|
+
target = this.resolveTargetContext(session, targetId) ?? target;
|
|
3050
|
+
if (this.hasUsableDebuggee(target)) {
|
|
3051
|
+
return target;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
catch (error) {
|
|
3055
|
+
if (!isAttachBlockedError(error)) {
|
|
3056
|
+
throw error;
|
|
3057
|
+
}
|
|
3058
|
+
this.cdp.markClientClosed();
|
|
3059
|
+
try {
|
|
3060
|
+
await this.attachTargetTab(popupTarget.tabId);
|
|
3061
|
+
await this.enableTargetDomains(popupTarget.tabId, true);
|
|
3062
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
3063
|
+
target = this.resolveTargetContext(session, targetId) ?? target;
|
|
3064
|
+
if (this.hasUsableDebuggee(target)) {
|
|
3065
|
+
return target;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
catch (resetError) {
|
|
3069
|
+
if (!isAttachBlockedError(resetError)) {
|
|
3070
|
+
throw resetError;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
if (await this.attachTargetViaOpenerSession(session, popupTarget).catch(() => false)) {
|
|
3076
|
+
this.clearPopupAttachDiagnostic(session.id, targetId);
|
|
3077
|
+
}
|
|
3078
|
+
return this.resolveTargetContext(session, targetId) ?? target;
|
|
3079
|
+
}
|
|
3080
|
+
shouldPreferDirectPopupTabAttach(target) {
|
|
3081
|
+
const openerTabId = parseTargetAliasTabId(target.openerTargetId);
|
|
3082
|
+
return openerTabId !== null && openerTabId !== target.tabId;
|
|
3083
|
+
}
|
|
3084
|
+
async activateTargetAndRespond(message, session, targetId) {
|
|
3085
|
+
session.activeTargetId = targetId;
|
|
3086
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
3087
|
+
if (target) {
|
|
3088
|
+
await this.tabs.activateTab(target.tabId).catch(() => undefined);
|
|
3089
|
+
}
|
|
3090
|
+
const tab = target ? await this.tabs.getTab(target.tabId) : null;
|
|
3091
|
+
this.sendResponse(message, {
|
|
3092
|
+
activeTargetId: targetId,
|
|
3093
|
+
url: target ? resolveReportedTargetUrl(target, tab) : undefined,
|
|
3094
|
+
title: target ? resolveReportedTargetTitle(target, tab) : undefined
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
shouldPromotePopupTarget(session, openerTargetId, target) {
|
|
3098
|
+
return ((!!target.openerTargetId || target.targetId !== session.targetId)
|
|
3099
|
+
&& this.hasUsableDebuggee(target)
|
|
3100
|
+
&& (!session.activeTargetId
|
|
3101
|
+
|| session.activeTargetId === session.targetId
|
|
3102
|
+
|| session.activeTargetId === openerTargetId));
|
|
3103
|
+
}
|
|
3104
|
+
promotePopupTarget(session, targetId) {
|
|
3105
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
3106
|
+
if (!target || !target.openerTargetId) {
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
if (this.shouldPromotePopupTarget(session, target.openerTargetId, target)) {
|
|
3110
|
+
session.activeTargetId = targetId;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
rehydrateSyntheticPopupBridge(session, targetId) {
|
|
3114
|
+
const target = session.targets.get(targetId) ?? null;
|
|
3115
|
+
const bridge = this.findSyntheticSessionBridge(session, target);
|
|
3116
|
+
if (!bridge
|
|
3117
|
+
|| typeof bridge.sessionId !== "string"
|
|
3118
|
+
|| bridge.sessionId.length === 0
|
|
3119
|
+
|| typeof this.cdp.registerChildSession !== "function"
|
|
3120
|
+
|| this.cdp.hasDebuggerSession?.(bridge.sessionId) === true) {
|
|
3121
|
+
return this.resolveTargetContext(session, targetId);
|
|
3122
|
+
}
|
|
3123
|
+
this.cdp.registerChildSession(bridge.tabId, {
|
|
3124
|
+
targetId: bridge.targetId,
|
|
3125
|
+
type: bridge.type,
|
|
3126
|
+
...(typeof bridge.url === "string" ? { url: bridge.url } : {}),
|
|
3127
|
+
...(typeof bridge.title === "string" ? { title: bridge.title } : {}),
|
|
3128
|
+
...(typeof bridge.openerTargetId === "string" && !bridge.openerTargetId.startsWith("tab-")
|
|
3129
|
+
? { openerId: bridge.openerTargetId }
|
|
3130
|
+
: {})
|
|
3131
|
+
}, bridge.sessionId);
|
|
3132
|
+
return this.resolveTargetContext(session, targetId);
|
|
3133
|
+
}
|
|
3134
|
+
findSyntheticSessionBridge(session, target) {
|
|
3135
|
+
if (!target) {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
const candidates = this.sessions
|
|
3139
|
+
.listSyntheticTargets(session.id)
|
|
3140
|
+
.filter((candidate) => typeof candidate.sessionId === "string" && candidate.sessionId.length > 0);
|
|
3141
|
+
if (candidates.length === 0) {
|
|
3142
|
+
return null;
|
|
3143
|
+
}
|
|
3144
|
+
const targetUrl = typeof target.url === "string" && target.url.length > 0 ? target.url : null;
|
|
3145
|
+
const targetTitle = typeof target.title === "string" && target.title.length > 0 ? target.title : null;
|
|
3146
|
+
let matches = targetUrl
|
|
3147
|
+
? candidates.filter((candidate) => candidate.url === targetUrl)
|
|
3148
|
+
: [];
|
|
3149
|
+
if (matches.length === 0 && targetTitle) {
|
|
3150
|
+
matches = candidates.filter((candidate) => candidate.title === targetTitle);
|
|
3151
|
+
}
|
|
3152
|
+
else if (matches.length > 1 && targetTitle) {
|
|
3153
|
+
const titledMatches = matches.filter((candidate) => candidate.title === targetTitle);
|
|
3154
|
+
if (titledMatches.length > 0) {
|
|
3155
|
+
matches = titledMatches;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
if (matches.length === 0 && typeof target.openerTargetId === "string" && target.openerTargetId.length > 0) {
|
|
3159
|
+
const targetOpenerTabId = parseTargetAliasTabId(target.openerTargetId);
|
|
3160
|
+
matches = candidates.filter((candidate) => {
|
|
3161
|
+
if (candidate.openerTargetId === target.openerTargetId) {
|
|
3162
|
+
return true;
|
|
3163
|
+
}
|
|
3164
|
+
const candidateOpenerTabId = parseTargetAliasTabId(candidate.openerTargetId);
|
|
3165
|
+
return targetOpenerTabId !== null && candidateOpenerTabId === targetOpenerTabId;
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
if (matches.length === 0) {
|
|
3169
|
+
return null;
|
|
3170
|
+
}
|
|
3171
|
+
if (matches.length === 1) {
|
|
3172
|
+
return matches[0] ?? null;
|
|
3173
|
+
}
|
|
3174
|
+
return matches.sort((left, right) => right.attachedAt - left.attachedAt)[0] ?? null;
|
|
3175
|
+
}
|
|
3176
|
+
async attachTargetViaOpenerSession(session, target) {
|
|
3177
|
+
if (typeof target.openerTargetId !== "string" || target.openerTargetId.length === 0) {
|
|
3178
|
+
return false;
|
|
3179
|
+
}
|
|
3180
|
+
const opener = this.resolveTargetContext(session, target.openerTargetId)
|
|
3181
|
+
?? this.resolveTargetContext(session, session.targetId);
|
|
3182
|
+
if (!opener) {
|
|
3183
|
+
return false;
|
|
3184
|
+
}
|
|
3185
|
+
const openerBridgeDebuggee = { tabId: opener.tabId };
|
|
3186
|
+
let targetsLookupFailedReason = null;
|
|
3187
|
+
let targetInfos = [];
|
|
3188
|
+
try {
|
|
3189
|
+
const rawTargets = await this.cdp.sendCommand(openerBridgeDebuggee, "Target.getTargets", {}, { preserveTab: true });
|
|
3190
|
+
targetInfos = isRecord(rawTargets) && Array.isArray(rawTargets.targetInfos)
|
|
3191
|
+
? rawTargets.targetInfos.map((entry) => extractTargetInfo(entry)).filter((entry) => entry !== null)
|
|
3192
|
+
: [];
|
|
3193
|
+
}
|
|
3194
|
+
catch (error) {
|
|
3195
|
+
targetsLookupFailedReason = error instanceof Error ? error.message : String(error);
|
|
3196
|
+
targetInfos = [];
|
|
3197
|
+
}
|
|
3198
|
+
const pageTargets = targetInfos.filter((info) => info.type === "page");
|
|
3199
|
+
const targetUrl = typeof target.url === "string" && target.url.length > 0 ? target.url : null;
|
|
3200
|
+
const targetTitle = typeof target.title === "string" && target.title.length > 0 ? target.title : null;
|
|
3201
|
+
let matcher = targetUrl ? "url" : undefined;
|
|
3202
|
+
let matches = targetUrl
|
|
3203
|
+
? pageTargets.filter((info) => info.url === targetUrl)
|
|
3204
|
+
: pageTargets;
|
|
3205
|
+
if (matches.length === 0 && targetTitle) {
|
|
3206
|
+
matches = pageTargets.filter((info) => info.title === targetTitle);
|
|
3207
|
+
if (matches.length > 0) {
|
|
3208
|
+
matcher = "title";
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
else if (matches.length > 1 && targetTitle) {
|
|
3212
|
+
const titledMatches = matches.filter((info) => info.title === targetTitle);
|
|
3213
|
+
if (titledMatches.length > 0) {
|
|
3214
|
+
matches = titledMatches;
|
|
3215
|
+
matcher = "title";
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (matches.length === 0 && typeof target.openerTargetId === "string" && target.openerTargetId.length > 0) {
|
|
3219
|
+
const openerUrl = typeof opener.url === "string" && opener.url.length > 0 ? opener.url : null;
|
|
3220
|
+
const openerTitle = typeof opener.title === "string" && opener.title.length > 0 ? opener.title : null;
|
|
3221
|
+
const nonOpenerMatches = pageTargets.filter((info) => {
|
|
3222
|
+
if (openerUrl && info.url === openerUrl) {
|
|
3223
|
+
return false;
|
|
3224
|
+
}
|
|
3225
|
+
if (openerTitle && info.title === openerTitle) {
|
|
3226
|
+
return false;
|
|
3227
|
+
}
|
|
3228
|
+
return true;
|
|
3229
|
+
});
|
|
3230
|
+
if (nonOpenerMatches.length === 1) {
|
|
3231
|
+
matches = nonOpenerMatches;
|
|
3232
|
+
matcher = "non_opener";
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
const popupTargetInfo = matches[0] ?? null;
|
|
3236
|
+
const resolvedTabTargetId = popupTargetInfo?.targetId
|
|
3237
|
+
? null
|
|
3238
|
+
: (typeof this.cdp.resolveTabTargetId === "function"
|
|
3239
|
+
? await this.cdp.resolveTabTargetId(target.tabId)
|
|
3240
|
+
: null);
|
|
3241
|
+
const popupTargetId = popupTargetInfo?.targetId ?? resolvedTabTargetId;
|
|
3242
|
+
if (!popupTargetId) {
|
|
3243
|
+
this.recordPopupAttachDiagnostic(session, target, {
|
|
3244
|
+
stage: targetsLookupFailedReason ? "targets_lookup_failed" : "resolve_tab_target_failed",
|
|
3245
|
+
...(matcher ? { matcher } : {}),
|
|
3246
|
+
...(targetsLookupFailedReason ? { reason: targetsLookupFailedReason, targetsLookupFailed: true } : {})
|
|
3247
|
+
});
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3250
|
+
if (resolvedTabTargetId) {
|
|
3251
|
+
matcher = "resolve_tab_target_id";
|
|
3252
|
+
}
|
|
3253
|
+
const shouldRefreshAfterResolvedFallback = Boolean(resolvedTabTargetId
|
|
3254
|
+
&& typeof this.cdp.refreshTabAttachment === "function"
|
|
3255
|
+
&& ((targetsLookupFailedReason
|
|
3256
|
+
&& this.shouldRefreshPopupOpenerAfterLookupFailure(targetsLookupFailedReason))
|
|
3257
|
+
|| (popupTargetInfo === null && pageTargets.length === 0)));
|
|
3258
|
+
let refreshDiagnostic = null;
|
|
3259
|
+
let refreshReasonOverride = null;
|
|
3260
|
+
if (shouldRefreshAfterResolvedFallback) {
|
|
3261
|
+
try {
|
|
3262
|
+
await this.cdp.refreshTabAttachment(opener.tabId);
|
|
3263
|
+
}
|
|
3264
|
+
catch (error) {
|
|
3265
|
+
refreshDiagnostic = this.cdp.getLastRootRefreshDiagnostic?.(opener.tabId) ?? null;
|
|
3266
|
+
const refreshReason = error instanceof Error ? error.message : String(error);
|
|
3267
|
+
const canProceedWithRetainedRoot = Boolean(refreshDiagnostic?.rootSessionPresentAfterRefresh
|
|
3268
|
+
&& refreshDiagnostic?.rootTargetIdAfterRefresh
|
|
3269
|
+
&& refreshReason.includes("Not allowed"));
|
|
3270
|
+
if (canProceedWithRetainedRoot) {
|
|
3271
|
+
refreshReasonOverride = refreshReason;
|
|
3272
|
+
}
|
|
3273
|
+
else {
|
|
3274
|
+
this.recordPopupAttachDiagnostic(session, target, {
|
|
3275
|
+
stage: "raw_attach_failed",
|
|
3276
|
+
popupTargetId,
|
|
3277
|
+
...(matcher ? { matcher } : {}),
|
|
3278
|
+
...this.toPopupRefreshDiagnostic(refreshDiagnostic),
|
|
3279
|
+
...(targetsLookupFailedReason ? { targetsLookupFailed: true } : {}),
|
|
3280
|
+
reason: refreshReason
|
|
3281
|
+
});
|
|
3282
|
+
return false;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
refreshDiagnostic = this.cdp.getLastRootRefreshDiagnostic?.(opener.tabId) ?? null;
|
|
3286
|
+
}
|
|
3287
|
+
let sessionId = null;
|
|
3288
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3289
|
+
try {
|
|
3290
|
+
sessionId = typeof this.cdp.attachChildTarget === "function"
|
|
3291
|
+
? await this.cdp.attachChildTarget(opener.tabId, popupTargetId)
|
|
3292
|
+
: await this.cdp.sendCommand(openerBridgeDebuggee, "Target.attachToTarget", {
|
|
3293
|
+
targetId: popupTargetId,
|
|
3294
|
+
flatten: true
|
|
3295
|
+
}).then((attached) => isRecord(attached) && typeof attached.sessionId === "string" ? attached.sessionId : null);
|
|
3296
|
+
}
|
|
3297
|
+
catch (error) {
|
|
3298
|
+
const routerDiagnostic = this.cdp.getLastChildAttachDiagnostic(opener.tabId, popupTargetId);
|
|
3299
|
+
const stage = routerDiagnostic?.stage ?? "attached_root_attach_failed";
|
|
3300
|
+
if (attempt === 0 && this.shouldRetryPopupAttachStage(stage)) {
|
|
3301
|
+
await this.waitForPopupAttachRetry();
|
|
3302
|
+
continue;
|
|
3303
|
+
}
|
|
3304
|
+
this.recordPopupAttachDiagnostic(session, target, {
|
|
3305
|
+
stage,
|
|
3306
|
+
popupTargetId,
|
|
3307
|
+
...(matcher ? { matcher } : {}),
|
|
3308
|
+
...this.toPopupChildAttachDiagnostic(routerDiagnostic),
|
|
3309
|
+
...this.toPopupRefreshDiagnostic(refreshDiagnostic),
|
|
3310
|
+
...(refreshReasonOverride ? { refreshReason: refreshReasonOverride } : {}),
|
|
3311
|
+
...(targetsLookupFailedReason ? { targetsLookupFailed: true } : {}),
|
|
3312
|
+
reason: routerDiagnostic?.reason ?? (error instanceof Error ? error.message : String(error))
|
|
3313
|
+
});
|
|
3314
|
+
return false;
|
|
3315
|
+
}
|
|
3316
|
+
if (sessionId) {
|
|
3317
|
+
break;
|
|
3318
|
+
}
|
|
3319
|
+
const routerDiagnostic = this.cdp.getLastChildAttachDiagnostic(opener.tabId, popupTargetId);
|
|
3320
|
+
const stage = routerDiagnostic?.stage ?? "attached_root_attach_null";
|
|
3321
|
+
if (attempt === 0 && this.shouldRetryPopupAttachStage(stage)) {
|
|
3322
|
+
await this.waitForPopupAttachRetry();
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
this.recordPopupAttachDiagnostic(session, target, {
|
|
3326
|
+
stage,
|
|
3327
|
+
popupTargetId,
|
|
3328
|
+
...(matcher ? { matcher } : {}),
|
|
3329
|
+
...this.toPopupChildAttachDiagnostic(routerDiagnostic),
|
|
3330
|
+
...this.toPopupRefreshDiagnostic(refreshDiagnostic),
|
|
3331
|
+
...(refreshReasonOverride ? { refreshReason: refreshReasonOverride } : {}),
|
|
3332
|
+
...(targetsLookupFailedReason ? { targetsLookupFailed: true } : {}),
|
|
3333
|
+
...(routerDiagnostic?.reason ? { reason: routerDiagnostic.reason } : {})
|
|
3334
|
+
});
|
|
3335
|
+
return false;
|
|
3336
|
+
}
|
|
3337
|
+
this.sessions.upsertSyntheticTarget(session.id, {
|
|
3338
|
+
targetId: popupTargetId,
|
|
3339
|
+
tabId: opener.tabId,
|
|
3340
|
+
type: popupTargetInfo?.type ?? "page",
|
|
3341
|
+
...(typeof popupTargetInfo?.url === "string" ? { url: popupTargetInfo.url } : targetUrl ? { url: targetUrl } : {}),
|
|
3342
|
+
...(typeof popupTargetInfo?.title === "string" ? { title: popupTargetInfo.title } : targetTitle ? { title: targetTitle } : {}),
|
|
3343
|
+
sessionId: sessionId ?? undefined,
|
|
3344
|
+
openerTargetId: target.openerTargetId,
|
|
3345
|
+
attachedAt: Date.now()
|
|
3346
|
+
});
|
|
3347
|
+
if (sessionId
|
|
3348
|
+
&& popupTargetInfo
|
|
3349
|
+
&& typeof this.cdp.registerChildSession === "function") {
|
|
3350
|
+
this.cdp.registerChildSession(opener.tabId, popupTargetInfo, sessionId);
|
|
3351
|
+
}
|
|
3352
|
+
this.clearPopupAttachDiagnostic(session.id, target.targetId);
|
|
3353
|
+
return true;
|
|
3354
|
+
}
|
|
3355
|
+
popupAttachDiagnosticKey(sessionId, targetId) {
|
|
3356
|
+
return `${sessionId}:${targetId}`;
|
|
3357
|
+
}
|
|
3358
|
+
shouldRefreshPopupOpenerAfterLookupFailure(reason) {
|
|
3359
|
+
return reason.includes("Debugger is not attached")
|
|
3360
|
+
|| reason.includes("No tab attached")
|
|
3361
|
+
|| reason.includes("Detached while handling command");
|
|
3362
|
+
}
|
|
3363
|
+
shouldRetryPopupAttachStage(stage) {
|
|
3364
|
+
return stage === "raw_attach_failed"
|
|
3365
|
+
|| stage === "attached_root_unavailable"
|
|
3366
|
+
|| stage === "attached_root_attach_null"
|
|
3367
|
+
|| stage === "attached_root_attach_failed";
|
|
3368
|
+
}
|
|
3369
|
+
async waitForPopupAttachRetry() {
|
|
3370
|
+
await new Promise((resolve) => setTimeout(resolve, POPUP_ATTACH_RETRY_DELAY_MS));
|
|
3371
|
+
}
|
|
3372
|
+
toPopupChildAttachDiagnostic(diagnostic) {
|
|
3373
|
+
if (!diagnostic) {
|
|
3374
|
+
return {};
|
|
3375
|
+
}
|
|
3376
|
+
return {
|
|
3377
|
+
...(diagnostic.initialStage ? { initialStage: diagnostic.initialStage } : {}),
|
|
3378
|
+
...(diagnostic.rootTargetRetryStage ? { rootTargetRetryStage: diagnostic.rootTargetRetryStage } : {}),
|
|
3379
|
+
...(diagnostic.attachedRootRecoveryStage
|
|
3380
|
+
? { attachedRootRecoveryStage: diagnostic.attachedRootRecoveryStage }
|
|
3381
|
+
: {}),
|
|
3382
|
+
...(diagnostic.attachedRootRecoverySource
|
|
3383
|
+
? { attachedRootRecoverySource: diagnostic.attachedRootRecoverySource }
|
|
3384
|
+
: {}),
|
|
3385
|
+
...(diagnostic.attachedRootRecoveryAttachTargetId
|
|
3386
|
+
? { attachedRootRecoveryAttachTargetId: diagnostic.attachedRootRecoveryAttachTargetId }
|
|
3387
|
+
: {}),
|
|
3388
|
+
...(typeof diagnostic.attachedRootRecoveryRetriedAfterRegisterRoot === "boolean"
|
|
3389
|
+
? { attachedRootRecoveryRetriedAfterRegisterRoot: diagnostic.attachedRootRecoveryRetriedAfterRegisterRoot }
|
|
3390
|
+
: {}),
|
|
3391
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterRootChanged === "boolean"
|
|
3392
|
+
? { attachedRootRecoveryRegisterRootChanged: diagnostic.attachedRootRecoveryRegisterRootChanged }
|
|
3393
|
+
: {}),
|
|
3394
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterRootAttachTargetChanged === "boolean"
|
|
3395
|
+
? { attachedRootRecoveryRegisterRootAttachTargetChanged: diagnostic.attachedRootRecoveryRegisterRootAttachTargetChanged }
|
|
3396
|
+
: {}),
|
|
3397
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterAttachedRootSessionCalled === "boolean"
|
|
3398
|
+
? {
|
|
3399
|
+
attachedRootRecoveryRegisterAttachedRootSessionCalled: diagnostic.attachedRootRecoveryRegisterAttachedRootSessionCalled
|
|
3400
|
+
}
|
|
3401
|
+
: {}),
|
|
3402
|
+
...(diagnostic.attachedRootUnavailableTerminalBranch
|
|
3403
|
+
? { attachedRootUnavailableTerminalBranch: diagnostic.attachedRootUnavailableTerminalBranch }
|
|
3404
|
+
: {}),
|
|
3405
|
+
...(diagnostic.reattachRecoveryStage
|
|
3406
|
+
? { reattachRecoveryStage: diagnostic.reattachRecoveryStage }
|
|
3407
|
+
: {}),
|
|
3408
|
+
...(diagnostic.reattachRecoveryReason
|
|
3409
|
+
? { reattachRecoveryReason: diagnostic.reattachRecoveryReason }
|
|
3410
|
+
: {}),
|
|
3411
|
+
...(diagnostic.attachedRootRecoveryReason
|
|
3412
|
+
? { attachedRootRecoveryReason: diagnostic.attachedRootRecoveryReason }
|
|
3413
|
+
: {})
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
formatCdpFailureDiagnosticSuffix(details) {
|
|
3417
|
+
if (details?.phase === "strict_enablement" && details.enablementStage) {
|
|
3418
|
+
return ` (phase: ${details.phase}; stage: ${details.enablementStage})`;
|
|
3419
|
+
}
|
|
3420
|
+
if (!details?.stage) {
|
|
3421
|
+
return "";
|
|
3422
|
+
}
|
|
3423
|
+
return ` (origin: ${details.origin}; stage: ${details.stage})`;
|
|
3424
|
+
}
|
|
3425
|
+
toDirectAttachDiagnosticDetails(diagnostic) {
|
|
3426
|
+
if (!diagnostic) {
|
|
3427
|
+
return undefined;
|
|
3428
|
+
}
|
|
3429
|
+
return {
|
|
3430
|
+
origin: diagnostic.origin,
|
|
3431
|
+
stage: diagnostic.stage,
|
|
3432
|
+
attachBy: diagnostic.attachBy,
|
|
3433
|
+
...(diagnostic.probeMethod ? { probeMethod: diagnostic.probeMethod } : {}),
|
|
3434
|
+
...(diagnostic.reason ? { reason: diagnostic.reason } : {})
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
decorateDirectAttachError(error, diagnostic) {
|
|
3438
|
+
const detail = this.getCdpFailureMessage(error);
|
|
3439
|
+
if (!diagnostic) {
|
|
3440
|
+
return error instanceof Error ? error : new Error(detail);
|
|
3441
|
+
}
|
|
3442
|
+
return this.decorateCdpFailure(error, this.toDirectAttachDiagnosticDetails(diagnostic) ?? {});
|
|
3443
|
+
}
|
|
3444
|
+
getCdpFailureMessage(error) {
|
|
3445
|
+
if (error instanceof Error) {
|
|
3446
|
+
return error.message;
|
|
3447
|
+
}
|
|
3448
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
3449
|
+
const message = error.message;
|
|
3450
|
+
if (typeof message === "string") {
|
|
3451
|
+
if ("code" in error) {
|
|
3452
|
+
const code = error.code;
|
|
3453
|
+
return typeof code === "number"
|
|
3454
|
+
? JSON.stringify({ code, message })
|
|
3455
|
+
: message;
|
|
3456
|
+
}
|
|
3457
|
+
return message;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
return "Debugger attach failed";
|
|
3461
|
+
}
|
|
3462
|
+
decorateCdpFailure(error, details) {
|
|
3463
|
+
const detail = this.getCdpFailureMessage(error);
|
|
3464
|
+
const decorated = error instanceof Error ? error : new Error(detail);
|
|
3465
|
+
const mergedDetails = {
|
|
3466
|
+
...(decorated.directAttachDetails ?? {}),
|
|
3467
|
+
...details,
|
|
3468
|
+
...(details.reason ? {} : { reason: detail })
|
|
3469
|
+
};
|
|
3470
|
+
const suffix = this.formatCdpFailureDiagnosticSuffix(mergedDetails);
|
|
3471
|
+
decorated.message = suffix.length > 0 && detail.endsWith(suffix)
|
|
3472
|
+
? detail
|
|
3473
|
+
: `${detail}${suffix}`;
|
|
3474
|
+
decorated.directAttachDetails = mergedDetails;
|
|
3475
|
+
return decorated;
|
|
3476
|
+
}
|
|
3477
|
+
getDirectAttachErrorDetails(error) {
|
|
3478
|
+
if (!(error instanceof Error)) {
|
|
3479
|
+
return undefined;
|
|
3480
|
+
}
|
|
3481
|
+
const decorated = error;
|
|
3482
|
+
return decorated.directAttachDetails;
|
|
3483
|
+
}
|
|
3484
|
+
getPopupAttachDiagnostic(sessionId, targetId) {
|
|
3485
|
+
return this.popupAttachDiagnostics.get(this.popupAttachDiagnosticKey(sessionId, targetId)) ?? null;
|
|
3486
|
+
}
|
|
3487
|
+
clearPopupAttachDiagnostic(sessionId, targetId) {
|
|
3488
|
+
this.popupAttachDiagnostics.delete(this.popupAttachDiagnosticKey(sessionId, targetId));
|
|
3489
|
+
}
|
|
3490
|
+
recordPopupAttachDiagnostic(session, target, diagnostic) {
|
|
3491
|
+
const entry = {
|
|
3492
|
+
targetId: target.targetId,
|
|
3493
|
+
tabId: target.tabId,
|
|
3494
|
+
...(target.openerTargetId ? { openerTargetId: target.openerTargetId } : {}),
|
|
3495
|
+
at: Date.now(),
|
|
3496
|
+
...diagnostic
|
|
3497
|
+
};
|
|
3498
|
+
this.popupAttachDiagnostics.set(this.popupAttachDiagnosticKey(session.id, target.targetId), entry);
|
|
3499
|
+
logError("ops.popup_attach_stage", new Error(entry.stage), {
|
|
3500
|
+
code: "popup_attach_stage",
|
|
3501
|
+
extra: {
|
|
3502
|
+
targetId: entry.targetId,
|
|
3503
|
+
tabId: entry.tabId,
|
|
3504
|
+
...(entry.openerTargetId ? { openerTargetId: entry.openerTargetId } : {}),
|
|
3505
|
+
...(entry.popupTargetId ? { popupTargetId: entry.popupTargetId } : {}),
|
|
3506
|
+
...(entry.matcher ? { matcher: entry.matcher } : {}),
|
|
3507
|
+
...(entry.initialStage ? { initialStage: entry.initialStage } : {}),
|
|
3508
|
+
...(entry.rootTargetRetryStage ? { rootTargetRetryStage: entry.rootTargetRetryStage } : {}),
|
|
3509
|
+
...(entry.attachedRootRecoveryStage ? { attachedRootRecoveryStage: entry.attachedRootRecoveryStage } : {}),
|
|
3510
|
+
...(entry.attachedRootRecoverySource ? { attachedRootRecoverySource: entry.attachedRootRecoverySource } : {}),
|
|
3511
|
+
...(entry.attachedRootRecoveryAttachTargetId
|
|
3512
|
+
? { attachedRootRecoveryAttachTargetId: entry.attachedRootRecoveryAttachTargetId }
|
|
3513
|
+
: {}),
|
|
3514
|
+
...(typeof entry.attachedRootRecoveryRetriedAfterRegisterRoot === "boolean"
|
|
3515
|
+
? { attachedRootRecoveryRetriedAfterRegisterRoot: entry.attachedRootRecoveryRetriedAfterRegisterRoot }
|
|
3516
|
+
: {}),
|
|
3517
|
+
...(typeof entry.attachedRootRecoveryRegisterRootChanged === "boolean"
|
|
3518
|
+
? { attachedRootRecoveryRegisterRootChanged: entry.attachedRootRecoveryRegisterRootChanged }
|
|
3519
|
+
: {}),
|
|
3520
|
+
...(typeof entry.attachedRootRecoveryRegisterRootAttachTargetChanged === "boolean"
|
|
3521
|
+
? { attachedRootRecoveryRegisterRootAttachTargetChanged: entry.attachedRootRecoveryRegisterRootAttachTargetChanged }
|
|
3522
|
+
: {}),
|
|
3523
|
+
...(typeof entry.attachedRootRecoveryRegisterAttachedRootSessionCalled === "boolean"
|
|
3524
|
+
? {
|
|
3525
|
+
attachedRootRecoveryRegisterAttachedRootSessionCalled: entry.attachedRootRecoveryRegisterAttachedRootSessionCalled
|
|
3526
|
+
}
|
|
3527
|
+
: {}),
|
|
3528
|
+
...(entry.attachedRootUnavailableTerminalBranch
|
|
3529
|
+
? { attachedRootUnavailableTerminalBranch: entry.attachedRootUnavailableTerminalBranch }
|
|
3530
|
+
: {}),
|
|
3531
|
+
...(entry.reattachRecoveryStage ? { reattachRecoveryStage: entry.reattachRecoveryStage } : {}),
|
|
3532
|
+
...(entry.reattachRecoveryReason ? { reattachRecoveryReason: entry.reattachRecoveryReason } : {}),
|
|
3533
|
+
...(entry.attachedRootRecoveryReason ? { attachedRootRecoveryReason: entry.attachedRootRecoveryReason } : {}),
|
|
3534
|
+
...(entry.refreshPath ? { refreshPath: entry.refreshPath } : {}),
|
|
3535
|
+
...(typeof entry.refreshCompleted === "boolean" ? { refreshCompleted: entry.refreshCompleted } : {}),
|
|
3536
|
+
...(typeof entry.refreshDebuggeePresent === "boolean" ? { refreshDebuggeePresent: entry.refreshDebuggeePresent } : {}),
|
|
3537
|
+
...(typeof entry.refreshRootSessionPresent === "boolean"
|
|
3538
|
+
? { refreshRootSessionPresent: entry.refreshRootSessionPresent }
|
|
3539
|
+
: {}),
|
|
3540
|
+
...(entry.refreshRootTargetId ? { refreshRootTargetId: entry.refreshRootTargetId } : {}),
|
|
3541
|
+
...(entry.refreshProbeMethod ? { refreshProbeMethod: entry.refreshProbeMethod } : {}),
|
|
3542
|
+
...(entry.refreshProbeStage ? { refreshProbeStage: entry.refreshProbeStage } : {}),
|
|
3543
|
+
...(entry.refreshProbeReason ? { refreshProbeReason: entry.refreshProbeReason } : {}),
|
|
3544
|
+
...(entry.refreshReason ? { refreshReason: entry.refreshReason } : {}),
|
|
3545
|
+
...(entry.targetsLookupFailed ? { targetsLookupFailed: true } : {}),
|
|
3546
|
+
...(entry.reason ? { reason: entry.reason } : {})
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
toPopupRefreshDiagnostic(diagnostic) {
|
|
3551
|
+
if (!diagnostic) {
|
|
3552
|
+
return {};
|
|
3553
|
+
}
|
|
3554
|
+
return {
|
|
3555
|
+
refreshPath: diagnostic.path,
|
|
3556
|
+
refreshCompleted: diagnostic.refreshCompleted,
|
|
3557
|
+
refreshDebuggeePresent: diagnostic.debuggeePresentAfterRefresh,
|
|
3558
|
+
refreshRootSessionPresent: diagnostic.rootSessionPresentAfterRefresh,
|
|
3559
|
+
...(diagnostic.rootTargetIdAfterRefresh ? { refreshRootTargetId: diagnostic.rootTargetIdAfterRefresh } : {}),
|
|
3560
|
+
refreshProbeMethod: diagnostic.probeMethod,
|
|
3561
|
+
refreshProbeStage: diagnostic.probeStage,
|
|
3562
|
+
...(diagnostic.probeReason ? { refreshProbeReason: diagnostic.probeReason } : {}),
|
|
3563
|
+
...(diagnostic.reason ? { refreshReason: diagnostic.reason } : {})
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
requireActiveTarget(session, message) {
|
|
3567
|
+
const explicitTargetId = this.extractPayloadTargetId(message.payload);
|
|
3568
|
+
const targetId = explicitTargetId ?? session.activeTargetId ?? session.targetId;
|
|
3569
|
+
if (!targetId) {
|
|
3570
|
+
this.sendError(message, buildError("invalid_request", "No active target", false));
|
|
3571
|
+
return null;
|
|
3572
|
+
}
|
|
3573
|
+
const target = this.resolveRequestedTargetContext(session, targetId, explicitTargetId !== null);
|
|
3574
|
+
if (!target) {
|
|
3575
|
+
this.sendError(message, buildError("invalid_request", "Active target missing", false));
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
if (target.url) {
|
|
3579
|
+
const restriction = isRestrictedUrl(target.url);
|
|
3580
|
+
if (restriction.restricted && !this.isAllowedCanvasRestrictionTarget(session, targetId, target)) {
|
|
3581
|
+
this.sendError(message, buildError("restricted_url", restriction.message ?? "Restricted tab.", false));
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
if (target.synthetic && !target.sessionId && !this.isSyntheticRootPreviewTarget(session, targetId, target)) {
|
|
3586
|
+
this.sendPopupAttachPendingError(message, session, targetId);
|
|
3587
|
+
return null;
|
|
3588
|
+
}
|
|
3589
|
+
if (target.openerTargetId && !this.hasUsableDebuggee(target)) {
|
|
3590
|
+
this.sendPopupAttachPendingError(message, session, targetId);
|
|
3591
|
+
return null;
|
|
3592
|
+
}
|
|
3593
|
+
return target;
|
|
3594
|
+
}
|
|
3595
|
+
isAllowedCanvasRestrictionTarget(session, targetId, target) {
|
|
3596
|
+
if (this.isAllowedCanvasTargetUrl(target.url)) {
|
|
3597
|
+
return true;
|
|
3598
|
+
}
|
|
3599
|
+
if (isHtmlDataUrl(target.url ?? "") && this.isRegisteredCanvasTarget(session, targetId)) {
|
|
3600
|
+
return true;
|
|
3601
|
+
}
|
|
3602
|
+
return this.isSyntheticRootPreviewTarget(session, targetId, target)
|
|
3603
|
+
&& isHtmlDataUrl(target.url ?? "");
|
|
3604
|
+
}
|
|
3605
|
+
isRegisteredCanvasTarget(session, targetId) {
|
|
3606
|
+
return this.isAllowedCanvasTargetUrl(session.targets.get(targetId)?.url);
|
|
3607
|
+
}
|
|
3608
|
+
isSyntheticRootPreviewTarget(session, _targetId, target) {
|
|
3609
|
+
const rootSynthetic = this.sessions.getSyntheticTarget(session.id, session.targetId);
|
|
3610
|
+
const effectiveUrl = target.url ?? rootSynthetic?.url;
|
|
3611
|
+
return isHtmlDataUrl(rootSynthetic?.url ?? "")
|
|
3612
|
+
&& isHtmlDataUrl(effectiveUrl ?? "")
|
|
3613
|
+
&& !rootSynthetic?.openerTargetId
|
|
3614
|
+
&& rootSynthetic?.tabId === session.tabId
|
|
3615
|
+
&& !target.openerTargetId
|
|
3616
|
+
&& target.tabId === session.tabId;
|
|
3617
|
+
}
|
|
3618
|
+
isAllowedCanvasTargetUrl(rawUrl) {
|
|
3619
|
+
if (typeof rawUrl !== "string" || rawUrl.length === 0) {
|
|
3620
|
+
return false;
|
|
3621
|
+
}
|
|
3622
|
+
try {
|
|
3623
|
+
const allowedUrl = chrome.runtime.getURL("canvas.html");
|
|
3624
|
+
return rawUrl === allowedUrl || rawUrl.startsWith(`${allowedUrl}#`) || rawUrl.startsWith(`${allowedUrl}?`);
|
|
3625
|
+
}
|
|
3626
|
+
catch {
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
async captureCanvasPage(tabId, targetId) {
|
|
3631
|
+
if (!this.getCanvasPageState) {
|
|
1252
3632
|
return null;
|
|
1253
3633
|
}
|
|
1254
|
-
const
|
|
1255
|
-
if (!
|
|
1256
|
-
this.sendError(message, buildError("invalid_session", "Unknown ops session", false));
|
|
3634
|
+
const state = this.getCanvasPageState(targetId);
|
|
3635
|
+
if (!state) {
|
|
1257
3636
|
return null;
|
|
1258
3637
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
3638
|
+
const previewHtml = typeof state.html === "string" && state.html.length > 0
|
|
3639
|
+
? extractBodyHtml(state.html)
|
|
3640
|
+
: null;
|
|
3641
|
+
const shouldProbeLiveStage = Boolean(state.pendingMutation)
|
|
3642
|
+
|| (canvasStateContainsRichMedia(state) && !htmlContainsRichMedia(previewHtml));
|
|
3643
|
+
if (shouldProbeLiveStage) {
|
|
3644
|
+
const liveStageCapture = await this.captureLiveCanvasStage(tabId);
|
|
3645
|
+
if (liveStageCapture) {
|
|
3646
|
+
return liveStageCapture;
|
|
1263
3647
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
return
|
|
3648
|
+
const documentCapture = buildCanvasDocumentCapture(state);
|
|
3649
|
+
if (documentCapture) {
|
|
3650
|
+
return documentCapture;
|
|
1267
3651
|
}
|
|
1268
3652
|
}
|
|
1269
|
-
if (
|
|
1270
|
-
|
|
1271
|
-
return null;
|
|
1272
|
-
}
|
|
1273
|
-
if (typeof message.leaseId !== "string" || message.leaseId !== session.leaseId) {
|
|
1274
|
-
this.sendError(message, buildError("not_owner", "Lease does not match session owner", false));
|
|
1275
|
-
return null;
|
|
3653
|
+
if (!previewHtml) {
|
|
3654
|
+
return buildCanvasDocumentCapture(state);
|
|
1276
3655
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
3656
|
+
return {
|
|
3657
|
+
html: previewHtml,
|
|
3658
|
+
styles: {},
|
|
3659
|
+
warnings: ["canvas_state_capture"],
|
|
3660
|
+
inlineStyles: false
|
|
3661
|
+
};
|
|
1279
3662
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
3663
|
+
async runElementAction(target, selector, action, fallback) {
|
|
3664
|
+
return await this.runCanvasPageAction(target, action, selector, fallback);
|
|
3665
|
+
}
|
|
3666
|
+
async runCanvasPageAction(target, action, selector, fallback) {
|
|
3667
|
+
if (!this.isAllowedCanvasTargetUrl(target.url) || !this.performCanvasPageAction) {
|
|
3668
|
+
return await fallback();
|
|
1284
3669
|
}
|
|
1285
|
-
return
|
|
3670
|
+
return await this.performCanvasPageAction(target.targetId, action, selector ?? null);
|
|
1286
3671
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
3672
|
+
async captureLiveCanvasStage(tabId) {
|
|
3673
|
+
try {
|
|
3674
|
+
const results = await chrome.scripting.executeScript({
|
|
3675
|
+
target: { tabId },
|
|
3676
|
+
func: () => {
|
|
3677
|
+
const stage = document.getElementById("canvas-stage-inner");
|
|
3678
|
+
if (!(stage instanceof HTMLElement)) {
|
|
3679
|
+
return null;
|
|
3680
|
+
}
|
|
3681
|
+
const html = stage.innerHTML.trim();
|
|
3682
|
+
if (!html) {
|
|
3683
|
+
return null;
|
|
3684
|
+
}
|
|
3685
|
+
const width = stage.style.width || `${Math.max(stage.scrollWidth, 320)}px`;
|
|
3686
|
+
const height = stage.style.height || `${Math.max(stage.scrollHeight, 240)}px`;
|
|
3687
|
+
return `<body><main data-surface="canvas" style="position:relative;width:${width};min-height:${height};">${html}</main></body>`;
|
|
3688
|
+
}
|
|
3689
|
+
});
|
|
3690
|
+
const html = typeof results[0]?.result === "string" ? results[0].result : null;
|
|
3691
|
+
if (!html) {
|
|
3692
|
+
return null;
|
|
3693
|
+
}
|
|
3694
|
+
return {
|
|
3695
|
+
html,
|
|
3696
|
+
styles: {},
|
|
3697
|
+
warnings: ["canvas_state_capture"],
|
|
3698
|
+
inlineStyles: true
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
catch {
|
|
1291
3702
|
return null;
|
|
1292
3703
|
}
|
|
1293
|
-
|
|
3704
|
+
}
|
|
3705
|
+
resolveRefContext(session, ref, targetId) {
|
|
3706
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
1294
3707
|
if (!target) {
|
|
1295
|
-
this.sendError(message, buildError("invalid_request", "Active target missing", false));
|
|
1296
3708
|
return null;
|
|
1297
3709
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
3710
|
+
const entry = session.refStore.resolve(targetId, ref);
|
|
3711
|
+
if (!entry) {
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3714
|
+
const snapshotId = session.refStore.getSnapshotId(targetId);
|
|
3715
|
+
if (!snapshotId || entry.snapshotId !== snapshotId) {
|
|
3716
|
+
return null;
|
|
3717
|
+
}
|
|
3718
|
+
return {
|
|
3719
|
+
target,
|
|
3720
|
+
ref,
|
|
3721
|
+
selector: entry.selector,
|
|
3722
|
+
backendNodeId: entry.backendNodeId,
|
|
3723
|
+
snapshotId: entry.snapshotId,
|
|
3724
|
+
...(entry.frameId ? { frameId: entry.frameId } : {}),
|
|
3725
|
+
...(entry.role ? { role: entry.role } : {}),
|
|
3726
|
+
...(entry.name ? { name: entry.name } : {})
|
|
3727
|
+
};
|
|
3728
|
+
}
|
|
3729
|
+
async captureSnapshotPayload(message, session, options) {
|
|
3730
|
+
const explicitTargetId = this.extractPayloadTargetId(message.payload);
|
|
3731
|
+
if (explicitTargetId) {
|
|
3732
|
+
try {
|
|
3733
|
+
await this.preparePopupTarget(session, explicitTargetId);
|
|
3734
|
+
}
|
|
3735
|
+
catch (error) {
|
|
3736
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
3737
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1302
3738
|
return null;
|
|
1303
3739
|
}
|
|
1304
3740
|
}
|
|
1305
|
-
|
|
3741
|
+
const target = this.requireActiveTarget(session, message);
|
|
3742
|
+
if (!target)
|
|
3743
|
+
return null;
|
|
3744
|
+
const start = Date.now();
|
|
3745
|
+
const entriesData = await buildSnapshot((method, params) => this.cdp.sendCommand(target.debuggee, method, params), options.mode, () => session.refStore.nextRef(target.targetId), options.mode !== "actionables", options.maxNodes);
|
|
3746
|
+
const snapshot = session.refStore.setSnapshot(target.targetId, entriesData.entries);
|
|
3747
|
+
const startIndex = parseCursor(options.cursor);
|
|
3748
|
+
const { content, truncated, nextCursor } = paginate(entriesData.lines, startIndex, options.maxChars);
|
|
3749
|
+
const contentBytes = this.encoder.encode(content).length;
|
|
3750
|
+
if (contentBytes > MAX_SNAPSHOT_BYTES) {
|
|
3751
|
+
this.sendError(message, buildError("snapshot_too_large", "Snapshot exceeded max size.", false, {
|
|
3752
|
+
maxSnapshotBytes: MAX_SNAPSHOT_BYTES,
|
|
3753
|
+
actualBytes: contentBytes
|
|
3754
|
+
}));
|
|
3755
|
+
return null;
|
|
3756
|
+
}
|
|
3757
|
+
const tab = await this.tabs.getTab(target.tabId);
|
|
3758
|
+
return {
|
|
3759
|
+
target,
|
|
3760
|
+
snapshotId: snapshot.snapshotId,
|
|
3761
|
+
url: resolveReportedTargetUrl(target, tab),
|
|
3762
|
+
title: resolveReportedTargetTitle(target, tab),
|
|
3763
|
+
content,
|
|
3764
|
+
truncated,
|
|
3765
|
+
...(nextCursor ? { nextCursor } : {}),
|
|
3766
|
+
refCount: snapshot.count,
|
|
3767
|
+
timingMs: Date.now() - start,
|
|
3768
|
+
warnings: entriesData.warnings
|
|
3769
|
+
};
|
|
1306
3770
|
}
|
|
1307
|
-
|
|
3771
|
+
resolveRefFromPayload(session, refOrPayload, message) {
|
|
1308
3772
|
const ref = typeof refOrPayload === "string"
|
|
1309
3773
|
? refOrPayload
|
|
1310
3774
|
: (isRecord(refOrPayload) && typeof refOrPayload.ref === "string" ? refOrPayload.ref : null);
|
|
@@ -1317,17 +3781,188 @@ export class OpsRuntime {
|
|
|
1317
3781
|
this.sendError(message, buildError("invalid_request", "No active target", false));
|
|
1318
3782
|
return null;
|
|
1319
3783
|
}
|
|
1320
|
-
const
|
|
1321
|
-
if (!
|
|
1322
|
-
this.sendError(message, buildError("invalid_request", `Unknown ref: ${ref}
|
|
3784
|
+
const resolved = this.resolveRefContext(session, ref, targetId);
|
|
3785
|
+
if (!resolved) {
|
|
3786
|
+
this.sendError(message, buildError("invalid_request", `Unknown ref: ${ref}. Take a new snapshot first.`, false));
|
|
1323
3787
|
return null;
|
|
1324
3788
|
}
|
|
1325
|
-
|
|
3789
|
+
if (resolved.target.synthetic && !resolved.target.sessionId) {
|
|
3790
|
+
this.sendPopupAttachPendingError(message, session, resolved.target.targetId);
|
|
3791
|
+
return null;
|
|
3792
|
+
}
|
|
3793
|
+
return resolved;
|
|
3794
|
+
}
|
|
3795
|
+
formatPopupAttachDiagnosticSuffix(diagnostic) {
|
|
3796
|
+
if (!diagnostic?.stage) {
|
|
3797
|
+
return "";
|
|
3798
|
+
}
|
|
3799
|
+
const parts = [`stage: ${diagnostic.stage}`];
|
|
3800
|
+
if (diagnostic.rootTargetRetryStage) {
|
|
3801
|
+
parts.push(`root-target-retry: ${diagnostic.rootTargetRetryStage}`);
|
|
3802
|
+
}
|
|
3803
|
+
if (diagnostic.attachedRootRecoveryStage) {
|
|
3804
|
+
const attachedRootPart = diagnostic.attachedRootRecoverySource
|
|
3805
|
+
? `${diagnostic.attachedRootRecoveryStage} via ${diagnostic.attachedRootRecoverySource}`
|
|
3806
|
+
: diagnostic.attachedRootRecoveryStage;
|
|
3807
|
+
parts.push(`attached-root: ${attachedRootPart}`);
|
|
3808
|
+
}
|
|
3809
|
+
if (diagnostic.attachedRootUnavailableTerminalBranch) {
|
|
3810
|
+
parts.push(`terminal: ${diagnostic.attachedRootUnavailableTerminalBranch}`);
|
|
3811
|
+
}
|
|
3812
|
+
return ` (${parts.join("; ")})`;
|
|
3813
|
+
}
|
|
3814
|
+
sendPopupAttachPendingError(message, session, targetId) {
|
|
3815
|
+
const diagnostic = session && typeof targetId === "string"
|
|
3816
|
+
? this.getPopupAttachDiagnostic(session.id, targetId)
|
|
3817
|
+
: null;
|
|
3818
|
+
const stageSuffix = this.formatPopupAttachDiagnosticSuffix(diagnostic);
|
|
3819
|
+
this.sendError(message, buildError("execution_failed", `Popup target has not finished attaching yet${stageSuffix}. Take a new review or snapshot and retry.`, true, diagnostic
|
|
3820
|
+
? {
|
|
3821
|
+
stage: diagnostic.stage,
|
|
3822
|
+
...(diagnostic.popupTargetId ? { popupTargetId: diagnostic.popupTargetId } : {}),
|
|
3823
|
+
...(diagnostic.matcher ? { matcher: diagnostic.matcher } : {}),
|
|
3824
|
+
...(diagnostic.initialStage ? { initialStage: diagnostic.initialStage } : {}),
|
|
3825
|
+
...(diagnostic.rootTargetRetryStage ? { rootTargetRetryStage: diagnostic.rootTargetRetryStage } : {}),
|
|
3826
|
+
...(diagnostic.attachedRootRecoveryStage
|
|
3827
|
+
? { attachedRootRecoveryStage: diagnostic.attachedRootRecoveryStage }
|
|
3828
|
+
: {}),
|
|
3829
|
+
...(diagnostic.attachedRootRecoverySource
|
|
3830
|
+
? { attachedRootRecoverySource: diagnostic.attachedRootRecoverySource }
|
|
3831
|
+
: {}),
|
|
3832
|
+
...(diagnostic.attachedRootRecoveryAttachTargetId
|
|
3833
|
+
? { attachedRootRecoveryAttachTargetId: diagnostic.attachedRootRecoveryAttachTargetId }
|
|
3834
|
+
: {}),
|
|
3835
|
+
...(typeof diagnostic.attachedRootRecoveryRetriedAfterRegisterRoot === "boolean"
|
|
3836
|
+
? { attachedRootRecoveryRetriedAfterRegisterRoot: diagnostic.attachedRootRecoveryRetriedAfterRegisterRoot }
|
|
3837
|
+
: {}),
|
|
3838
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterRootChanged === "boolean"
|
|
3839
|
+
? { attachedRootRecoveryRegisterRootChanged: diagnostic.attachedRootRecoveryRegisterRootChanged }
|
|
3840
|
+
: {}),
|
|
3841
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterRootAttachTargetChanged === "boolean"
|
|
3842
|
+
? {
|
|
3843
|
+
attachedRootRecoveryRegisterRootAttachTargetChanged: diagnostic.attachedRootRecoveryRegisterRootAttachTargetChanged
|
|
3844
|
+
}
|
|
3845
|
+
: {}),
|
|
3846
|
+
...(typeof diagnostic.attachedRootRecoveryRegisterAttachedRootSessionCalled === "boolean"
|
|
3847
|
+
? {
|
|
3848
|
+
attachedRootRecoveryRegisterAttachedRootSessionCalled: diagnostic.attachedRootRecoveryRegisterAttachedRootSessionCalled
|
|
3849
|
+
}
|
|
3850
|
+
: {}),
|
|
3851
|
+
...(diagnostic.attachedRootUnavailableTerminalBranch
|
|
3852
|
+
? { attachedRootUnavailableTerminalBranch: diagnostic.attachedRootUnavailableTerminalBranch }
|
|
3853
|
+
: {}),
|
|
3854
|
+
...(diagnostic.reattachRecoveryStage
|
|
3855
|
+
? { reattachRecoveryStage: diagnostic.reattachRecoveryStage }
|
|
3856
|
+
: {}),
|
|
3857
|
+
...(diagnostic.reattachRecoveryReason
|
|
3858
|
+
? { reattachRecoveryReason: diagnostic.reattachRecoveryReason }
|
|
3859
|
+
: {}),
|
|
3860
|
+
...(diagnostic.attachedRootRecoveryReason
|
|
3861
|
+
? { attachedRootRecoveryReason: diagnostic.attachedRootRecoveryReason }
|
|
3862
|
+
: {}),
|
|
3863
|
+
...(diagnostic.refreshPath ? { refreshPath: diagnostic.refreshPath } : {}),
|
|
3864
|
+
...(typeof diagnostic.refreshCompleted === "boolean" ? { refreshCompleted: diagnostic.refreshCompleted } : {}),
|
|
3865
|
+
...(typeof diagnostic.refreshDebuggeePresent === "boolean"
|
|
3866
|
+
? { refreshDebuggeePresent: diagnostic.refreshDebuggeePresent }
|
|
3867
|
+
: {}),
|
|
3868
|
+
...(typeof diagnostic.refreshRootSessionPresent === "boolean"
|
|
3869
|
+
? { refreshRootSessionPresent: diagnostic.refreshRootSessionPresent }
|
|
3870
|
+
: {}),
|
|
3871
|
+
...(diagnostic.refreshRootTargetId ? { refreshRootTargetId: diagnostic.refreshRootTargetId } : {}),
|
|
3872
|
+
...(diagnostic.refreshProbeMethod ? { refreshProbeMethod: diagnostic.refreshProbeMethod } : {}),
|
|
3873
|
+
...(diagnostic.refreshProbeStage ? { refreshProbeStage: diagnostic.refreshProbeStage } : {}),
|
|
3874
|
+
...(diagnostic.refreshProbeReason ? { refreshProbeReason: diagnostic.refreshProbeReason } : {}),
|
|
3875
|
+
...(diagnostic.refreshReason ? { refreshReason: diagnostic.refreshReason } : {}),
|
|
3876
|
+
...(diagnostic.targetsLookupFailed ? { targetsLookupFailed: true } : {}),
|
|
3877
|
+
...(diagnostic.reason ? { reason: diagnostic.reason } : {})
|
|
3878
|
+
}
|
|
3879
|
+
: undefined));
|
|
3880
|
+
}
|
|
3881
|
+
resolveSelector(session, refOrPayload, message) {
|
|
3882
|
+
return this.resolveRefFromPayload(session, refOrPayload, message)?.selector ?? null;
|
|
3883
|
+
}
|
|
3884
|
+
async callFunctionOnRef(resolved, functionDeclaration, args = [], ref = resolved.ref) {
|
|
3885
|
+
try {
|
|
3886
|
+
const resolvedNode = await this.cdp.sendCommand(resolved.target.debuggee, "DOM.resolveNode", {
|
|
3887
|
+
backendNodeId: resolved.backendNodeId
|
|
3888
|
+
});
|
|
3889
|
+
const objectId = resolvedNode.object?.objectId;
|
|
3890
|
+
if (!objectId) {
|
|
3891
|
+
throw buildStaleSnapshotError(ref);
|
|
3892
|
+
}
|
|
3893
|
+
const result = await this.cdp.sendCommand(resolved.target.debuggee, "Runtime.callFunctionOn", {
|
|
3894
|
+
objectId,
|
|
3895
|
+
functionDeclaration,
|
|
3896
|
+
arguments: args.map((value) => ({ value })),
|
|
3897
|
+
returnByValue: true
|
|
3898
|
+
});
|
|
3899
|
+
if (result.exceptionDetails) {
|
|
3900
|
+
throw new Error(result.exceptionDetails.text ?? "Runtime.callFunctionOn failed");
|
|
3901
|
+
}
|
|
3902
|
+
return result.result?.value;
|
|
3903
|
+
}
|
|
3904
|
+
catch (error) {
|
|
3905
|
+
if (isSnapshotStaleMessage(error)) {
|
|
3906
|
+
throw buildStaleSnapshotError(ref);
|
|
3907
|
+
}
|
|
3908
|
+
throw error;
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
async resolveRefPoint(resolved) {
|
|
3912
|
+
try {
|
|
3913
|
+
const box = await this.cdp.sendCommand(resolved.target.debuggee, "DOM.getBoxModel", {
|
|
3914
|
+
backendNodeId: resolved.backendNodeId
|
|
3915
|
+
});
|
|
3916
|
+
const quad = Array.isArray(box.model?.content) ? box.model?.content : [];
|
|
3917
|
+
if (quad.length >= 8) {
|
|
3918
|
+
const xs = [quad[0], quad[2], quad[4], quad[6]].filter((value) => typeof value === "number");
|
|
3919
|
+
const ys = [quad[1], quad[3], quad[5], quad[7]].filter((value) => typeof value === "number");
|
|
3920
|
+
if (xs.length === 4 && ys.length === 4) {
|
|
3921
|
+
return {
|
|
3922
|
+
x: Math.round((Math.min(...xs) + Math.max(...xs)) / 2),
|
|
3923
|
+
y: Math.round((Math.min(...ys) + Math.max(...ys)) / 2)
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
catch (error) {
|
|
3929
|
+
if (isSnapshotStaleMessage(error)) {
|
|
3930
|
+
throw buildStaleSnapshotError(resolved.ref);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
const point = await this.callFunctionOnRef(resolved, DOM_REF_POINT_DECLARATION);
|
|
3934
|
+
const x = typeof point?.x === "number" && Number.isFinite(point.x) ? Math.round(point.x) : null;
|
|
3935
|
+
const y = typeof point?.y === "number" && Number.isFinite(point.y) ? Math.round(point.y) : null;
|
|
3936
|
+
if (x === null || y === null) {
|
|
3937
|
+
throw new Error(`Could not resolve a clickable point for ref: ${resolved.ref}`);
|
|
3938
|
+
}
|
|
3939
|
+
return { x, y };
|
|
1326
3940
|
}
|
|
1327
|
-
|
|
3941
|
+
normalizeScreenshotClip(value, ref) {
|
|
3942
|
+
const x = typeof value?.x === "number" && Number.isFinite(value.x) ? value.x : null;
|
|
3943
|
+
const y = typeof value?.y === "number" && Number.isFinite(value.y) ? value.y : null;
|
|
3944
|
+
const width = typeof value?.width === "number" && Number.isFinite(value.width) ? value.width : null;
|
|
3945
|
+
const height = typeof value?.height === "number" && Number.isFinite(value.height) ? value.height : null;
|
|
3946
|
+
if (x === null || y === null || width === null || height === null || width <= 0 || height <= 0) {
|
|
3947
|
+
throw new Error(`Could not resolve screenshot bounds for ref: ${ref}`);
|
|
3948
|
+
}
|
|
3949
|
+
return { x, y, width, height, scale: 1 };
|
|
3950
|
+
}
|
|
3951
|
+
async waitForFileChooser(sessionId, targetId, timeoutMs = SCREENSHOT_TIMEOUT_MS) {
|
|
3952
|
+
const startedAt = Date.now();
|
|
3953
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
3954
|
+
const chooser = this.sessions.getFileChooser(sessionId, targetId);
|
|
3955
|
+
if (chooser?.open) {
|
|
3956
|
+
return chooser;
|
|
3957
|
+
}
|
|
3958
|
+
await delay(50);
|
|
3959
|
+
}
|
|
3960
|
+
throw new Error("File chooser did not open");
|
|
3961
|
+
}
|
|
3962
|
+
async waitForSelector(target, selector, state, timeoutMs) {
|
|
1328
3963
|
const start = Date.now();
|
|
1329
3964
|
while (Date.now() - start < timeoutMs) {
|
|
1330
|
-
const snapshot = await this.dom.getSelectorState(tabId, selector);
|
|
3965
|
+
const snapshot = await this.runElementAction(target, selector, { type: "getSelectorState" }, () => this.dom.getSelectorState(target.tabId, selector));
|
|
1331
3966
|
if (state === "attached" && snapshot.attached)
|
|
1332
3967
|
return;
|
|
1333
3968
|
if (state === "visible" && snapshot.visible)
|
|
@@ -1338,6 +3973,22 @@ export class OpsRuntime {
|
|
|
1338
3973
|
}
|
|
1339
3974
|
throw new Error("Wait for selector timed out");
|
|
1340
3975
|
}
|
|
3976
|
+
async waitForRefState(resolved, state, timeoutMs) {
|
|
3977
|
+
const start = Date.now();
|
|
3978
|
+
while (Date.now() - start < timeoutMs) {
|
|
3979
|
+
const snapshot = await this.callFunctionOnRef(resolved, DOM_SELECTOR_STATE_DECLARATION);
|
|
3980
|
+
const attached = snapshot?.attached === true;
|
|
3981
|
+
const visible = snapshot?.visible === true;
|
|
3982
|
+
if (state === "attached" && attached)
|
|
3983
|
+
return;
|
|
3984
|
+
if (state === "visible" && visible)
|
|
3985
|
+
return;
|
|
3986
|
+
if (state === "hidden" && (!attached || !visible))
|
|
3987
|
+
return;
|
|
3988
|
+
await delay(200);
|
|
3989
|
+
}
|
|
3990
|
+
throw new Error("Wait for selector timed out");
|
|
3991
|
+
}
|
|
1341
3992
|
cleanupSession(session, event) {
|
|
1342
3993
|
this.clearClosingTimer(session.id);
|
|
1343
3994
|
const waiters = this.parallelWaiters.get(session.id);
|
|
@@ -1355,13 +4006,7 @@ export class OpsRuntime {
|
|
|
1355
4006
|
for (const target of session.targets.values()) {
|
|
1356
4007
|
void this.cdp.detachTab(target.tabId).catch(() => undefined);
|
|
1357
4008
|
}
|
|
1358
|
-
this.
|
|
1359
|
-
type: "ops_event",
|
|
1360
|
-
clientId: session.ownerClientId,
|
|
1361
|
-
opsSessionId: session.id,
|
|
1362
|
-
event,
|
|
1363
|
-
payload: { tabId: session.tabId, targetId: session.targetId }
|
|
1364
|
-
});
|
|
4009
|
+
this.emitSessionEvent(session, event);
|
|
1365
4010
|
}
|
|
1366
4011
|
handleClosedTarget(tabId, event) {
|
|
1367
4012
|
const session = this.sessions.getByTabId(tabId);
|
|
@@ -1377,7 +4022,7 @@ export class OpsRuntime {
|
|
|
1377
4022
|
this.cleanupSession(session, event);
|
|
1378
4023
|
}
|
|
1379
4024
|
}
|
|
1380
|
-
handleDebuggerDetachForTab(tabId) {
|
|
4025
|
+
async handleDebuggerDetachForTab(tabId) {
|
|
1381
4026
|
const session = this.sessions.getByTabId(tabId);
|
|
1382
4027
|
if (!session)
|
|
1383
4028
|
return;
|
|
@@ -1385,6 +4030,21 @@ export class OpsRuntime {
|
|
|
1385
4030
|
// Root tab detach can be transient during child-target shutdown; tab removal handler owns root teardown.
|
|
1386
4031
|
return;
|
|
1387
4032
|
}
|
|
4033
|
+
const targetId = this.sessions.getTargetIdByTabId(session.id, tabId);
|
|
4034
|
+
const target = targetId ? session.targets.get(targetId) ?? null : null;
|
|
4035
|
+
const liveTab = await this.tabs.getTab(tabId);
|
|
4036
|
+
if (target && this.isAllowedCanvasTargetUrl(target.url ?? liveTab?.url)) {
|
|
4037
|
+
if (liveTab && targetId) {
|
|
4038
|
+
session.targets.set(targetId, {
|
|
4039
|
+
...target,
|
|
4040
|
+
url: liveTab.url ?? target.url,
|
|
4041
|
+
title: liveTab.title ?? target.title
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
// Design tabs can detach transiently while the extension page stays open; retain the target so `/ops`
|
|
4045
|
+
// can reattach it later via `targets.use`.
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
1388
4048
|
this.handleClosedTarget(tabId, "ops_session_closed");
|
|
1389
4049
|
}
|
|
1390
4050
|
async closeTabBestEffort(tabId) {
|
|
@@ -1462,9 +4122,19 @@ export class OpsRuntime {
|
|
|
1462
4122
|
sendEvent(event) {
|
|
1463
4123
|
this.sendEnvelope(event);
|
|
1464
4124
|
}
|
|
4125
|
+
emitSessionEvent(session, event) {
|
|
4126
|
+
this.sendEvent({
|
|
4127
|
+
type: "ops_event",
|
|
4128
|
+
clientId: session.ownerClientId,
|
|
4129
|
+
opsSessionId: session.id,
|
|
4130
|
+
event,
|
|
4131
|
+
payload: { tabId: session.tabId, targetId: session.targetId }
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
1465
4134
|
markSessionClosing(session, reason) {
|
|
1466
|
-
if (session.state === "closing")
|
|
1467
|
-
return;
|
|
4135
|
+
if (session.state === "closing") {
|
|
4136
|
+
return false;
|
|
4137
|
+
}
|
|
1468
4138
|
session.state = "closing";
|
|
1469
4139
|
session.closingReason = reason;
|
|
1470
4140
|
session.expiresAt = Date.now() + SESSION_TTL_MS;
|
|
@@ -1476,13 +4146,18 @@ export class OpsRuntime {
|
|
|
1476
4146
|
}
|
|
1477
4147
|
}, SESSION_TTL_MS);
|
|
1478
4148
|
this.closingTimers.set(session.id, timeoutId);
|
|
4149
|
+
return true;
|
|
1479
4150
|
}
|
|
1480
4151
|
reclaimSession(session, clientId) {
|
|
4152
|
+
const wasClosing = session.state === "closing";
|
|
1481
4153
|
session.ownerClientId = clientId;
|
|
1482
4154
|
session.state = "active";
|
|
1483
4155
|
session.expiresAt = undefined;
|
|
1484
4156
|
session.closingReason = undefined;
|
|
1485
4157
|
this.clearClosingTimer(session.id);
|
|
4158
|
+
if (wasClosing) {
|
|
4159
|
+
this.emitSessionEvent(session, "ops_session_reclaimed");
|
|
4160
|
+
}
|
|
1486
4161
|
}
|
|
1487
4162
|
clearClosingTimer(sessionId) {
|
|
1488
4163
|
const timer = this.closingTimers.get(sessionId);
|
|
@@ -1696,6 +4371,18 @@ const toCookieListRecord = (entry) => {
|
|
|
1696
4371
|
const isRecord = (value) => {
|
|
1697
4372
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1698
4373
|
};
|
|
4374
|
+
const parseCanvasOverlaySelection = (value, targetId) => {
|
|
4375
|
+
const record = isRecord(value) ? value : {};
|
|
4376
|
+
const updatedAt = typeof record.updatedAt === "string" && record.updatedAt.trim().length > 0
|
|
4377
|
+
? record.updatedAt
|
|
4378
|
+
: undefined;
|
|
4379
|
+
return {
|
|
4380
|
+
pageId: typeof record.pageId === "string" && record.pageId.trim().length > 0 ? record.pageId : null,
|
|
4381
|
+
nodeId: typeof record.nodeId === "string" && record.nodeId.trim().length > 0 ? record.nodeId : null,
|
|
4382
|
+
targetId: typeof record.targetId === "string" && record.targetId.trim().length > 0 ? record.targetId : targetId,
|
|
4383
|
+
...(updatedAt ? { updatedAt } : {})
|
|
4384
|
+
};
|
|
4385
|
+
};
|
|
1699
4386
|
const parseCursor = (cursor) => {
|
|
1700
4387
|
if (!cursor)
|
|
1701
4388
|
return 0;
|
|
@@ -1729,6 +4416,342 @@ const paginate = (lines, startIndex, maxChars) => {
|
|
|
1729
4416
|
};
|
|
1730
4417
|
};
|
|
1731
4418
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4419
|
+
const parseTabTargetId = (targetId) => {
|
|
4420
|
+
const match = /^tab-(\d+)$/.exec(targetId);
|
|
4421
|
+
if (!match) {
|
|
4422
|
+
return null;
|
|
4423
|
+
}
|
|
4424
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
4425
|
+
return parsed;
|
|
4426
|
+
};
|
|
4427
|
+
const parseTargetAliasTabId = (targetId) => {
|
|
4428
|
+
if (typeof targetId !== "string" || targetId.length === 0) {
|
|
4429
|
+
return null;
|
|
4430
|
+
}
|
|
4431
|
+
if (targetId.startsWith("target-")) {
|
|
4432
|
+
const parsed = Number.parseInt(targetId.slice(7), 10);
|
|
4433
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
4434
|
+
}
|
|
4435
|
+
return parseTabTargetId(targetId);
|
|
4436
|
+
};
|
|
4437
|
+
const extractBodyHtml = (html) => {
|
|
4438
|
+
const bodyMatch = html.match(/<body\b[^>]*>[\s\S]*<\/body>/i);
|
|
4439
|
+
if (bodyMatch) {
|
|
4440
|
+
return bodyMatch[0];
|
|
4441
|
+
}
|
|
4442
|
+
return html;
|
|
4443
|
+
};
|
|
4444
|
+
const htmlContainsRichMedia = (html) => {
|
|
4445
|
+
return typeof html === "string" && /<(img|video|audio)\b/i.test(html);
|
|
4446
|
+
};
|
|
4447
|
+
const canvasStateContainsRichMedia = (state) => {
|
|
4448
|
+
const document = isRecord(state.document) ? state.document : null;
|
|
4449
|
+
const pages = Array.isArray(document?.pages) ? document.pages : [];
|
|
4450
|
+
const assets = Array.isArray(document?.assets) ? document.assets : [];
|
|
4451
|
+
const assetsById = new Map(assets.flatMap((asset) => typeof asset?.id === "string" ? [[asset.id, asset]] : []));
|
|
4452
|
+
return pages.some((page) => Array.isArray(page?.nodes) && page.nodes.some((node) => nodeContainsRichMedia(node, assetsById)));
|
|
4453
|
+
};
|
|
4454
|
+
const nodeContainsRichMedia = (node, assetsById) => {
|
|
4455
|
+
const tagName = readCanvasMediaTagName(node);
|
|
4456
|
+
if (tagName === "img" || tagName === "video" || tagName === "audio") {
|
|
4457
|
+
return true;
|
|
4458
|
+
}
|
|
4459
|
+
const assetIds = Array.isArray(node.metadata.assetIds)
|
|
4460
|
+
? node.metadata.assetIds.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
4461
|
+
: [];
|
|
4462
|
+
return assetIds.some((assetId) => {
|
|
4463
|
+
const asset = assetsById.get(assetId);
|
|
4464
|
+
const kind = typeof asset?.kind === "string" ? asset.kind.toLowerCase() : "";
|
|
4465
|
+
const mime = typeof asset?.mime === "string" ? asset.mime.toLowerCase() : "";
|
|
4466
|
+
return kind === "image" || kind === "video" || kind === "audio" || mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/");
|
|
4467
|
+
});
|
|
4468
|
+
};
|
|
4469
|
+
const readCanvasMediaTagName = (node) => {
|
|
4470
|
+
if (typeof node.props.tagName === "string" && node.props.tagName.trim().length > 0) {
|
|
4471
|
+
return node.props.tagName.trim().toLowerCase();
|
|
4472
|
+
}
|
|
4473
|
+
const codeSync = isRecord(node.metadata.codeSync) ? node.metadata.codeSync : null;
|
|
4474
|
+
if (codeSync && typeof codeSync.tagName === "string" && codeSync.tagName.trim().length > 0) {
|
|
4475
|
+
return codeSync.tagName.trim().toLowerCase();
|
|
4476
|
+
}
|
|
4477
|
+
return null;
|
|
4478
|
+
};
|
|
4479
|
+
const buildCanvasDocumentCapture = (state) => {
|
|
4480
|
+
const page = Array.isArray(state.document.pages) ? state.document.pages[0] : null;
|
|
4481
|
+
if (!page || !Array.isArray(page.nodes) || page.nodes.length === 0) {
|
|
4482
|
+
return null;
|
|
4483
|
+
}
|
|
4484
|
+
const { width, height } = computeCanvasDocumentBounds(page.nodes);
|
|
4485
|
+
const nodes = [...page.nodes]
|
|
4486
|
+
.sort(compareCanvasCaptureNodes)
|
|
4487
|
+
.map((node) => renderCanvasDocumentNode(state.document, node))
|
|
4488
|
+
.join("");
|
|
4489
|
+
return {
|
|
4490
|
+
html: `<body><main data-surface="canvas" style="position:relative;width:${width}px;min-height:${height}px;">${nodes}</main></body>`,
|
|
4491
|
+
styles: {},
|
|
4492
|
+
warnings: ["canvas_state_capture"],
|
|
4493
|
+
inlineStyles: true
|
|
4494
|
+
};
|
|
4495
|
+
};
|
|
4496
|
+
const computeCanvasDocumentBounds = (nodes) => {
|
|
4497
|
+
if (nodes.length === 0) {
|
|
4498
|
+
return { width: 1600, height: 1200 };
|
|
4499
|
+
}
|
|
4500
|
+
const maxX = Math.max(...nodes.map((node) => node.rect.x + node.rect.width));
|
|
4501
|
+
const maxY = Math.max(...nodes.map((node) => node.rect.y + node.rect.height));
|
|
4502
|
+
return {
|
|
4503
|
+
width: Math.max(maxX + 240, 1600),
|
|
4504
|
+
height: Math.max(maxY + 240, 1200)
|
|
4505
|
+
};
|
|
4506
|
+
};
|
|
4507
|
+
const compareCanvasCaptureNodes = (left, right) => {
|
|
4508
|
+
const rootOrder = Number(left.parentId !== null) - Number(right.parentId !== null);
|
|
4509
|
+
if (rootOrder !== 0) {
|
|
4510
|
+
return rootOrder;
|
|
4511
|
+
}
|
|
4512
|
+
const areaOrder = (right.rect.width * right.rect.height) - (left.rect.width * left.rect.height);
|
|
4513
|
+
if (areaOrder !== 0) {
|
|
4514
|
+
return areaOrder;
|
|
4515
|
+
}
|
|
4516
|
+
const verticalOrder = left.rect.y - right.rect.y;
|
|
4517
|
+
return verticalOrder !== 0 ? verticalOrder : left.rect.x - right.rect.x;
|
|
4518
|
+
};
|
|
4519
|
+
const renderCanvasDocumentNode = (document, node) => {
|
|
4520
|
+
const media = resolveCanvasDocumentMedia(document, node);
|
|
4521
|
+
const text = escapeCanvasHtml(nodeTextForCapture(node) || node.name);
|
|
4522
|
+
const style = serializeCanvasCaptureStyle({
|
|
4523
|
+
position: "absolute",
|
|
4524
|
+
left: `${node.rect.x}px`,
|
|
4525
|
+
top: `${node.rect.y}px`,
|
|
4526
|
+
width: `${Math.max(node.rect.width, 40)}px`,
|
|
4527
|
+
minHeight: `${Math.max(node.rect.height, readCanvasMediaTagName(node) === "audio" ? 64 : 40)}px`,
|
|
4528
|
+
overflow: "hidden",
|
|
4529
|
+
...node.style
|
|
4530
|
+
});
|
|
4531
|
+
const title = escapeCanvasAttribute(`${node.kind} • ${node.name}`);
|
|
4532
|
+
if (media?.kind === "image" && media.src) {
|
|
4533
|
+
return `<div data-node-id="${escapeCanvasAttribute(node.id)}" title="${title}" style="${style}"><img src="${escapeCanvasAttribute(media.src)}" alt="${escapeCanvasAttribute(media.alt ?? node.name)}" loading="lazy" draggable="false" style="width:100%;height:100%;object-fit:cover;display:block;" /></div>`;
|
|
4534
|
+
}
|
|
4535
|
+
if (media?.kind === "video" && media.src) {
|
|
4536
|
+
const poster = media.poster ? ` poster="${escapeCanvasAttribute(media.poster)}"` : "";
|
|
4537
|
+
return `<div data-node-id="${escapeCanvasAttribute(node.id)}" title="${title}" style="${style}"><video src="${escapeCanvasAttribute(media.src)}"${poster} muted loop autoplay playsinline preload="metadata" style="width:100%;height:100%;object-fit:cover;display:block;"></video></div>`;
|
|
4538
|
+
}
|
|
4539
|
+
if (media?.kind === "audio" && media.src) {
|
|
4540
|
+
return `<div data-node-id="${escapeCanvasAttribute(node.id)}" title="${title}" style="${style}"><audio src="${escapeCanvasAttribute(media.src)}" controls preload="metadata" style="width:100%;display:block;"></audio>${text ? `<div style="margin-top:8px;font:500 12px/1.4 sans-serif;">${text}</div>` : ""}</div>`;
|
|
4541
|
+
}
|
|
4542
|
+
return `<div data-node-id="${escapeCanvasAttribute(node.id)}" title="${title}" style="${style}">${text}</div>`;
|
|
4543
|
+
};
|
|
4544
|
+
const nodeTextForCapture = (node) => {
|
|
4545
|
+
const raw = node.props.text ?? node.metadata.text;
|
|
4546
|
+
if (raw !== undefined && raw !== null) {
|
|
4547
|
+
return typeof raw === "string" ? raw : String(raw);
|
|
4548
|
+
}
|
|
4549
|
+
return node.kind === "text" || node.kind === "note" || node.kind === "component-instance"
|
|
4550
|
+
? node.name
|
|
4551
|
+
: "";
|
|
4552
|
+
};
|
|
4553
|
+
const resolveCanvasDocumentMedia = (document, node) => {
|
|
4554
|
+
const tagName = readCanvasMediaTagName(node);
|
|
4555
|
+
const attributes = isRecord(node.props.attributes) ? node.props.attributes : {};
|
|
4556
|
+
const assetIds = Array.isArray(node.metadata.assetIds)
|
|
4557
|
+
? node.metadata.assetIds.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
4558
|
+
: [];
|
|
4559
|
+
const asset = assetIds.length > 0
|
|
4560
|
+
? document.assets.find((entry) => entry.id === assetIds[0])
|
|
4561
|
+
: null;
|
|
4562
|
+
const assetKind = typeof asset?.kind === "string" ? asset.kind.toLowerCase() : null;
|
|
4563
|
+
const assetMime = typeof asset?.mime === "string" ? asset.mime.toLowerCase() : null;
|
|
4564
|
+
const src = typeof node.props.src === "string"
|
|
4565
|
+
? node.props.src
|
|
4566
|
+
: typeof attributes.src === "string"
|
|
4567
|
+
? attributes.src
|
|
4568
|
+
: typeof asset?.url === "string"
|
|
4569
|
+
? asset.url
|
|
4570
|
+
: typeof asset?.repoPath === "string"
|
|
4571
|
+
? asset.repoPath
|
|
4572
|
+
: null;
|
|
4573
|
+
const poster = typeof node.props.poster === "string"
|
|
4574
|
+
? node.props.poster
|
|
4575
|
+
: typeof attributes.poster === "string"
|
|
4576
|
+
? attributes.poster
|
|
4577
|
+
: null;
|
|
4578
|
+
const alt = typeof node.props.alt === "string"
|
|
4579
|
+
? node.props.alt
|
|
4580
|
+
: typeof attributes.alt === "string"
|
|
4581
|
+
? attributes.alt
|
|
4582
|
+
: node.name;
|
|
4583
|
+
if (tagName === "img" || assetKind === "image" || assetMime?.startsWith("image/")) {
|
|
4584
|
+
return { kind: "image", src, poster: null, alt };
|
|
4585
|
+
}
|
|
4586
|
+
if (tagName === "video" || assetKind === "video" || assetMime?.startsWith("video/")) {
|
|
4587
|
+
return { kind: "video", src, poster, alt };
|
|
4588
|
+
}
|
|
4589
|
+
if (tagName === "audio" || assetKind === "audio" || assetMime?.startsWith("audio/")) {
|
|
4590
|
+
return { kind: "audio", src, poster: null, alt };
|
|
4591
|
+
}
|
|
4592
|
+
return null;
|
|
4593
|
+
};
|
|
4594
|
+
const serializeCanvasCaptureStyle = (style) => {
|
|
4595
|
+
return Object.entries(style)
|
|
4596
|
+
.flatMap(([key, value]) => {
|
|
4597
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
4598
|
+
return [];
|
|
4599
|
+
}
|
|
4600
|
+
const cssKey = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
4601
|
+
const cssValue = typeof value === "number" && !CANVAS_CAPTURE_UNITLESS_STYLES.has(key) ? `${value}px` : String(value);
|
|
4602
|
+
return `${cssKey}:${escapeCanvasAttribute(cssValue)};`;
|
|
4603
|
+
})
|
|
4604
|
+
.join("");
|
|
4605
|
+
};
|
|
4606
|
+
const escapeCanvasHtml = (value) => {
|
|
4607
|
+
return value
|
|
4608
|
+
.replaceAll("&", "&")
|
|
4609
|
+
.replaceAll("<", "<")
|
|
4610
|
+
.replaceAll(">", ">");
|
|
4611
|
+
};
|
|
4612
|
+
const escapeCanvasAttribute = (value) => {
|
|
4613
|
+
return escapeCanvasHtml(value)
|
|
4614
|
+
.replaceAll("\"", """)
|
|
4615
|
+
.replaceAll("'", "'");
|
|
4616
|
+
};
|
|
4617
|
+
const CANVAS_CAPTURE_UNITLESS_STYLES = new Set(["fontWeight", "lineHeight", "opacity", "zIndex"]);
|
|
4618
|
+
const buildStaleSnapshotError = (ref) => (new Error(`Unknown ref: ${ref}. ${STALE_REF_ERROR_SUFFIX}`));
|
|
4619
|
+
const isSnapshotStaleMessage = (error) => {
|
|
4620
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
4621
|
+
if (message.includes(STALE_REF_ERROR_SUFFIX)) {
|
|
4622
|
+
return true;
|
|
4623
|
+
}
|
|
4624
|
+
const normalized = message.toLowerCase();
|
|
4625
|
+
return normalized.includes("no node with given id")
|
|
4626
|
+
|| normalized.includes("could not find node with given id")
|
|
4627
|
+
|| normalized.includes("cannot find object with id")
|
|
4628
|
+
|| normalized.includes("cannot find context with specified id")
|
|
4629
|
+
|| normalized.includes("execution context was destroyed")
|
|
4630
|
+
|| normalized.includes("inspected target navigated or closed");
|
|
4631
|
+
};
|
|
4632
|
+
const extractTargetInfo = (params) => {
|
|
4633
|
+
const payload = isRecord(params) && isRecord(params.targetInfo) ? params.targetInfo : params;
|
|
4634
|
+
if (!isRecord(payload) || typeof payload.targetId !== "string" || typeof payload.type !== "string") {
|
|
4635
|
+
return null;
|
|
4636
|
+
}
|
|
4637
|
+
return {
|
|
4638
|
+
targetId: payload.targetId,
|
|
4639
|
+
type: payload.type,
|
|
4640
|
+
...(typeof payload.url === "string" ? { url: payload.url } : {}),
|
|
4641
|
+
...(typeof payload.title === "string" ? { title: payload.title } : {}),
|
|
4642
|
+
...(typeof payload.openerId === "string" ? { openerId: payload.openerId } : {})
|
|
4643
|
+
};
|
|
4644
|
+
};
|
|
4645
|
+
const isSyntheticPageTarget = (session, targetId, type) => {
|
|
4646
|
+
if (type !== "page" || targetId === session.targetId) {
|
|
4647
|
+
return false;
|
|
4648
|
+
}
|
|
4649
|
+
const parsedTabId = parseTabTargetId(targetId);
|
|
4650
|
+
return session.targets.has(targetId) || parsedTabId === null || parsedTabId !== session.tabId;
|
|
4651
|
+
};
|
|
4652
|
+
const resolveReportedTargetUrl = (target, tab) => {
|
|
4653
|
+
if (target?.synthetic === true && typeof target.url === "string" && target.url.length > 0) {
|
|
4654
|
+
return target.url;
|
|
4655
|
+
}
|
|
4656
|
+
if (typeof target?.url === "string" && isHtmlDataUrl(target.url)) {
|
|
4657
|
+
return target.url;
|
|
4658
|
+
}
|
|
4659
|
+
if (typeof target?.url === "string" && isCanvasExtensionUrl(target.url)) {
|
|
4660
|
+
return target.url;
|
|
4661
|
+
}
|
|
4662
|
+
return getReportedTabUrl(tab) ?? target?.url;
|
|
4663
|
+
};
|
|
4664
|
+
const resolveReportedTargetTitle = (target, tab) => {
|
|
4665
|
+
if (target?.synthetic === true && typeof target.title === "string" && target.title.length > 0) {
|
|
4666
|
+
return target.title;
|
|
4667
|
+
}
|
|
4668
|
+
if (typeof target?.url === "string" && isHtmlDataUrl(target.url) && typeof target.title === "string" && target.title.length > 0) {
|
|
4669
|
+
return target.title;
|
|
4670
|
+
}
|
|
4671
|
+
if (typeof target?.url === "string" && isCanvasExtensionUrl(target.url) && typeof target.title === "string" && target.title.length > 0) {
|
|
4672
|
+
return target.title;
|
|
4673
|
+
}
|
|
4674
|
+
if (isTabNavigationPending(tab)) {
|
|
4675
|
+
return undefined;
|
|
4676
|
+
}
|
|
4677
|
+
return getReportedTabTitle(tab) ?? target?.title;
|
|
4678
|
+
};
|
|
4679
|
+
const getReportedTabSeed = (tab) => {
|
|
4680
|
+
return {
|
|
4681
|
+
url: getReportedTabUrl(tab),
|
|
4682
|
+
title: getReportedTabTitle(tab)
|
|
4683
|
+
};
|
|
4684
|
+
};
|
|
4685
|
+
const getReportedTabUrl = (tab) => {
|
|
4686
|
+
if (!tab) {
|
|
4687
|
+
return undefined;
|
|
4688
|
+
}
|
|
4689
|
+
const pendingUrl = typeof tab.pendingUrl === "string" && tab.pendingUrl.length > 0 ? tab.pendingUrl : undefined;
|
|
4690
|
+
const liveUrl = typeof tab.url === "string" && tab.url.length > 0 ? tab.url : undefined;
|
|
4691
|
+
return pendingUrl ?? liveUrl;
|
|
4692
|
+
};
|
|
4693
|
+
const getReportedTabTitle = (tab) => {
|
|
4694
|
+
if (!tab || isTabNavigationPending(tab)) {
|
|
4695
|
+
return undefined;
|
|
4696
|
+
}
|
|
4697
|
+
return typeof tab.title === "string" && tab.title.length > 0 ? tab.title : undefined;
|
|
4698
|
+
};
|
|
4699
|
+
const isTabNavigationPending = (tab) => {
|
|
4700
|
+
return tab?.status === "loading";
|
|
4701
|
+
};
|
|
4702
|
+
const isHtmlDataUrl = (url) => {
|
|
4703
|
+
return url.startsWith("data:text/html");
|
|
4704
|
+
};
|
|
4705
|
+
const isCanvasExtensionUrl = (url) => {
|
|
4706
|
+
try {
|
|
4707
|
+
const canvasUrl = chrome.runtime.getURL("canvas.html");
|
|
4708
|
+
return url === canvasUrl || url.startsWith(`${canvasUrl}#`) || url.startsWith(`${canvasUrl}?`);
|
|
4709
|
+
}
|
|
4710
|
+
catch {
|
|
4711
|
+
return false;
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4714
|
+
const decodeHtmlDataUrl = (url) => {
|
|
4715
|
+
if (!isHtmlDataUrl(url)) {
|
|
4716
|
+
return null;
|
|
4717
|
+
}
|
|
4718
|
+
const commaIndex = url.indexOf(",");
|
|
4719
|
+
if (commaIndex === -1) {
|
|
4720
|
+
return null;
|
|
4721
|
+
}
|
|
4722
|
+
const metadata = url.slice(0, commaIndex).toLowerCase();
|
|
4723
|
+
const payload = url.slice(commaIndex + 1);
|
|
4724
|
+
if (metadata.includes(";base64")) {
|
|
4725
|
+
const decoded = atob(payload);
|
|
4726
|
+
const bytes = Uint8Array.from(decoded, (char) => char.charCodeAt(0));
|
|
4727
|
+
return new TextDecoder().decode(bytes);
|
|
4728
|
+
}
|
|
4729
|
+
try {
|
|
4730
|
+
return decodeURIComponent(payload);
|
|
4731
|
+
}
|
|
4732
|
+
catch {
|
|
4733
|
+
return payload;
|
|
4734
|
+
}
|
|
4735
|
+
};
|
|
4736
|
+
const executeInTab = async (tabId, func, args) => {
|
|
4737
|
+
return await new Promise((resolve, reject) => {
|
|
4738
|
+
chrome.scripting.executeScript({ target: { tabId }, func: func, args }, (results) => {
|
|
4739
|
+
const lastError = chrome.runtime.lastError;
|
|
4740
|
+
if (lastError) {
|
|
4741
|
+
reject(new Error(lastError.message));
|
|
4742
|
+
return;
|
|
4743
|
+
}
|
|
4744
|
+
const [first] = results ?? [];
|
|
4745
|
+
resolve((first?.result ?? null));
|
|
4746
|
+
});
|
|
4747
|
+
});
|
|
4748
|
+
};
|
|
4749
|
+
function replaceDocumentWithHtmlScript(input) {
|
|
4750
|
+
document.open();
|
|
4751
|
+
document.write(input.html);
|
|
4752
|
+
document.close();
|
|
4753
|
+
return { title: document.title };
|
|
4754
|
+
}
|
|
1732
4755
|
const withTimeout = async (promise, timeoutMs, message) => {
|
|
1733
4756
|
return await new Promise((resolve, reject) => {
|
|
1734
4757
|
const timeoutId = setTimeout(() => {
|