opendevbrowser 0.0.17 → 0.0.19
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 +172 -73
- 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 +4 -1
- package/dist/browser/annotation-manager.d.ts.map +1 -1
- package/dist/browser/browser-manager.d.ts +147 -47
- package/dist/browser/browser-manager.d.ts.map +1 -1
- package/dist/browser/canvas-client.d.ts +1 -0
- package/dist/browser/canvas-client.d.ts.map +1 -1
- package/dist/browser/canvas-code-sync-manager.d.ts +9 -1
- package/dist/browser/canvas-code-sync-manager.d.ts.map +1 -1
- package/dist/browser/canvas-manager.d.ts +29 -1
- package/dist/browser/canvas-manager.d.ts.map +1 -1
- 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 +167 -1
- package/dist/browser/manager-types.d.ts.map +1 -1
- package/dist/browser/ops-browser-manager.d.ts +103 -3
- 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 +3 -1
- package/dist/canvas/code-sync/apply-tsx.d.ts.map +1 -1
- package/dist/canvas/code-sync/import.d.ts +1 -0
- package/dist/canvas/code-sync/import.d.ts.map +1 -1
- package/dist/canvas/code-sync/manifest.d.ts +2 -1
- package/dist/canvas/code-sync/manifest.d.ts.map +1 -1
- package/dist/canvas/code-sync/tsx-adapter.d.ts.map +1 -1
- package/dist/canvas/code-sync/types.d.ts +102 -10
- package/dist/canvas/code-sync/types.d.ts.map +1 -1
- package/dist/canvas/document-store.d.ts +11 -1
- package/dist/canvas/document-store.d.ts.map +1 -1
- package/dist/canvas/export.d.ts.map +1 -1
- 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 +2 -0
- package/dist/canvas/repo-store.d.ts.map +1 -1
- package/dist/canvas/starters/catalog.d.ts +34 -0
- package/dist/canvas/starters/catalog.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 +345 -6
- package/dist/canvas/types.d.ts.map +1 -1
- 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-W4IHGDXV.js +33519 -0
- package/dist/chunk-W4IHGDXV.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/artifacts.d.ts.map +1 -1
- package/dist/cli/commands/canvas.d.ts +7 -7
- package/dist/cli/commands/canvas.d.ts.map +1 -1
- 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/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 +2 -0
- package/dist/cli/commands/devtools/screenshot.d.ts.map +1 -1
- package/dist/cli/commands/interact/click.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/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 +10 -7
- package/dist/cli/commands/native.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/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 -26
- 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 -14
- 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 +15 -4
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/index.js +2476 -1036
- 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-desktop-runtime.d.ts +15 -0
- package/dist/cli/remote-desktop-runtime.d.ts.map +1 -0
- package/dist/cli/remote-manager.d.ts +24 -2
- package/dist/cli/remote-manager.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 +2 -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 +15 -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/index.d.ts.map +1 -1
- package/dist/index.js +1103 -467
- 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/opendevbrowser.d.ts.map +1 -1
- package/dist/opendevbrowser.js +1103 -467
- 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-G3LRHQXX.js → providers-G36AM3Z2.js} +2 -2
- 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 +25 -3
- 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 +18 -0
- package/dist/relay/relay-server.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-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/connect.d.ts.map +1 -1
- package/dist/tools/deps.d.ts +4 -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/index.d.ts +3 -0
- package/dist/tools/index.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/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/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/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/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/upload.d.ts +4 -0
- package/dist/tools/upload.d.ts.map +1 -0
- package/dist/tools/workflow-runtime.d.ts +4 -1
- 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 +379 -9
- package/extension/dist/annotate-content.js +62 -32
- package/extension/dist/annotation-payload.js +57 -21
- package/extension/dist/background.js +406 -61
- package/extension/dist/canvas/canvas-runtime.js +481 -52
- package/extension/dist/canvas/model.js +129 -1
- package/extension/dist/canvas-page.js +1882 -74
- package/extension/dist/ops/dom-bridge.js +139 -0
- package/extension/dist/ops/ops-runtime.js +2854 -295
- package/extension/dist/ops/ops-session-store.js +83 -5
- 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 +5 -3
- package/extension/dist/popup.js +50 -15
- package/extension/dist/services/CDPRouter.js +1567 -63
- package/extension/dist/services/ConnectionManager.js +436 -78
- package/extension/dist/services/RelayClient.js +70 -30
- package/extension/dist/services/TabManager.js +83 -10
- 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/manifest.json +2 -2
- package/extension/popup.html +7 -6
- package/package.json +15 -7
- package/skills/AGENTS.md +9 -8
- package/skills/opendevbrowser-best-practices/SKILL.md +118 -9
- package/skills/opendevbrowser-best-practices/artifacts/browser-agent-known-issues-matrix.md +1 -0
- package/skills/opendevbrowser-best-practices/artifacts/command-channel-reference.md +26 -12
- 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/skill-runtime-pack-matrix.json +674 -0
- package/skills/opendevbrowser-best-practices/assets/templates/surface-audit-checklist.json +9 -4
- package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +89 -20
- package/skills/opendevbrowser-best-practices/scripts/resolve-odb-cli.sh +100 -0
- package/skills/opendevbrowser-best-practices/scripts/run-robustness-audit.sh +1 -0
- package/skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh +256 -116
- 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-5J3IFL3X.js +0 -16706
- package/dist/chunk-5J3IFL3X.js.map +0 -1
- package/dist/chunk-D633UO34.js +0 -8149
- package/dist/chunk-D633UO34.js.map +0 -1
- package/dist/chunk-V7KUDHDG.js +0 -276
- package/dist/chunk-V7KUDHDG.js.map +0 -1
- package/dist/runtime-factory-BICHDPE7.js +0 -13
- /package/dist/{providers-G3LRHQXX.js.map → providers-G36AM3Z2.js.map} +0 -0
- /package/dist/{runtime-factory-BICHDPE7.js.map → skills/skill-loader.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,12 +223,20 @@ 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",
|
|
37
231
|
"canvas.applyRuntimePreviewBridge",
|
|
38
232
|
"export.clonePage",
|
|
39
233
|
"export.cloneComponent",
|
|
40
234
|
"devtools.perf",
|
|
41
235
|
"page.screenshot"
|
|
42
236
|
]);
|
|
237
|
+
const DIALOG_SCOPED_COMMANDS = new Set([
|
|
238
|
+
"page.dialog"
|
|
239
|
+
]);
|
|
43
240
|
export class OpsRuntime {
|
|
44
241
|
sendEnvelope;
|
|
45
242
|
cdp;
|
|
@@ -49,6 +246,10 @@ export class OpsRuntime {
|
|
|
49
246
|
dom = new DomBridge();
|
|
50
247
|
sessions = new OpsSessionStore();
|
|
51
248
|
encoder = new TextEncoder();
|
|
249
|
+
popupOpenerTabIds = new Map();
|
|
250
|
+
popupAttachDiagnostics = new Map();
|
|
251
|
+
commandCreatedTabs = new Map();
|
|
252
|
+
dialogQueues = new Map();
|
|
52
253
|
closingTimers = new Map();
|
|
53
254
|
parallelWaiters = new Map();
|
|
54
255
|
constructor(options) {
|
|
@@ -56,10 +257,29 @@ export class OpsRuntime {
|
|
|
56
257
|
this.cdp = options.cdp;
|
|
57
258
|
this.getCanvasPageState = options.getCanvasPageState;
|
|
58
259
|
this.performCanvasPageAction = options.performCanvasPageAction;
|
|
260
|
+
chrome.tabs.onCreated.addListener(this.handleTabCreated);
|
|
59
261
|
chrome.tabs.onRemoved.addListener(this.handleTabRemoved);
|
|
60
262
|
chrome.tabs.onUpdated.addListener(this.handleTabUpdated);
|
|
263
|
+
chrome.webNavigation?.onCreatedNavigationTarget?.addListener?.(this.handleCreatedNavigationTarget);
|
|
61
264
|
chrome.debugger.onEvent.addListener(this.handleDebuggerEvent);
|
|
62
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;
|
|
63
283
|
}
|
|
64
284
|
handleMessage(message) {
|
|
65
285
|
if (message.type === "ops_hello") {
|
|
@@ -122,18 +342,125 @@ export class OpsRuntime {
|
|
|
122
342
|
const clientId = message.clientId;
|
|
123
343
|
if (!clientId)
|
|
124
344
|
return;
|
|
345
|
+
this.cdp.markClientClosed();
|
|
125
346
|
const sessions = this.sessions.listOwnedBy(clientId);
|
|
126
347
|
for (const session of sessions) {
|
|
127
|
-
this.markSessionClosing(session, "ops_session_expired")
|
|
348
|
+
if (this.markSessionClosing(session, "ops_session_expired")) {
|
|
349
|
+
this.emitSessionEvent(session, "ops_session_released");
|
|
350
|
+
}
|
|
128
351
|
}
|
|
129
352
|
}
|
|
130
353
|
handleTabRemoved = (tabId) => {
|
|
354
|
+
this.forgetCommandCreatedTab(tabId);
|
|
355
|
+
this.popupOpenerTabIds.delete(tabId);
|
|
131
356
|
this.handleClosedTarget(tabId, "ops_tab_closed");
|
|
132
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
|
+
}
|
|
133
444
|
handleTabUpdated = (tabId, changeInfo, tab) => {
|
|
134
445
|
const session = this.sessions.getByTabId(tabId);
|
|
135
|
-
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
|
+
}
|
|
136
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
|
+
}
|
|
137
464
|
if (changeInfo.discarded === true || tab.discarded === true) {
|
|
138
465
|
session.discardedSignals += 1;
|
|
139
466
|
}
|
|
@@ -148,10 +475,60 @@ export class OpsRuntime {
|
|
|
148
475
|
return;
|
|
149
476
|
void this.handleDebuggerDetachForTab(source.tabId);
|
|
150
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
|
+
}
|
|
151
527
|
handleDebuggerEvent = (source, method, params) => {
|
|
152
|
-
|
|
528
|
+
const eventTabId = this.cdp.resolveSourceTabId(source);
|
|
529
|
+
if (eventTabId === null)
|
|
153
530
|
return;
|
|
154
|
-
const session = this.sessions.getByTabId(
|
|
531
|
+
const session = this.sessions.getByTabId(eventTabId);
|
|
155
532
|
if (!session)
|
|
156
533
|
return;
|
|
157
534
|
if (method === "Runtime.consoleAPICalled") {
|
|
@@ -180,6 +557,29 @@ export class OpsRuntime {
|
|
|
180
557
|
}
|
|
181
558
|
return;
|
|
182
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
|
+
}
|
|
183
583
|
if (method === "Network.requestWillBeSent") {
|
|
184
584
|
const payload = params;
|
|
185
585
|
const requestId = payload.requestId;
|
|
@@ -228,6 +628,130 @@ export class OpsRuntime {
|
|
|
228
628
|
}
|
|
229
629
|
}
|
|
230
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
|
+
}
|
|
231
755
|
async handleRequest(message) {
|
|
232
756
|
const clientId = message.clientId;
|
|
233
757
|
if (!clientId) {
|
|
@@ -284,6 +808,9 @@ export class OpsRuntime {
|
|
|
284
808
|
case "nav.snapshot":
|
|
285
809
|
await this.withSession(message, clientId, (session) => this.handleSnapshot(message, session));
|
|
286
810
|
return;
|
|
811
|
+
case "nav.review":
|
|
812
|
+
await this.withSession(message, clientId, (session) => this.handleReview(message, session));
|
|
813
|
+
return;
|
|
287
814
|
case "interact.click":
|
|
288
815
|
await this.withSession(message, clientId, (session) => this.handleClick(message, session));
|
|
289
816
|
return;
|
|
@@ -311,6 +838,21 @@ export class OpsRuntime {
|
|
|
311
838
|
case "interact.scrollIntoView":
|
|
312
839
|
await this.withSession(message, clientId, (session) => this.handleScrollIntoView(message, session));
|
|
313
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;
|
|
314
856
|
case "dom.getHtml":
|
|
315
857
|
await this.withSession(message, clientId, (session) => this.handleDomGetHtml(message, session));
|
|
316
858
|
return;
|
|
@@ -332,6 +874,21 @@ export class OpsRuntime {
|
|
|
332
874
|
case "dom.isChecked":
|
|
333
875
|
await this.withSession(message, clientId, (session) => this.handleDomIsChecked(message, session));
|
|
334
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;
|
|
335
892
|
case "canvas.applyRuntimePreviewBridge":
|
|
336
893
|
await this.withSession(message, clientId, (session) => this.handleCanvasRuntimePreviewBridge(message, session));
|
|
337
894
|
return;
|
|
@@ -347,6 +904,9 @@ export class OpsRuntime {
|
|
|
347
904
|
case "page.screenshot":
|
|
348
905
|
await this.withSession(message, clientId, (session) => this.handleScreenshot(message, session));
|
|
349
906
|
return;
|
|
907
|
+
case "page.dialog":
|
|
908
|
+
await this.withSession(message, clientId, (session) => this.handleDialog(message, session));
|
|
909
|
+
return;
|
|
350
910
|
case "devtools.consolePoll":
|
|
351
911
|
await this.withSession(message, clientId, (session) => this.handleConsolePoll(message, session));
|
|
352
912
|
return;
|
|
@@ -361,6 +921,13 @@ export class OpsRuntime {
|
|
|
361
921
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
362
922
|
const parallelismPolicy = parseParallelismPolicy(payload.parallelismPolicy);
|
|
363
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;
|
|
364
931
|
if (startUrl) {
|
|
365
932
|
try {
|
|
366
933
|
const restriction = getRestrictionMessage(new URL(startUrl));
|
|
@@ -374,15 +941,31 @@ export class OpsRuntime {
|
|
|
374
941
|
return;
|
|
375
942
|
}
|
|
376
943
|
}
|
|
377
|
-
|
|
944
|
+
let activeTab = startUrl
|
|
378
945
|
? await this.tabs.createTab(startUrl, true)
|
|
379
|
-
:
|
|
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
|
+
}
|
|
380
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
|
+
}
|
|
381
964
|
this.sendError(message, buildError("ops_unavailable", "No active tab to attach.", true));
|
|
382
965
|
return;
|
|
383
966
|
}
|
|
384
967
|
const activeTabId = activeTab.id;
|
|
385
|
-
|
|
968
|
+
let resolvedTab = startUrl
|
|
386
969
|
? await this.tabs.waitForTabComplete(activeTabId)
|
|
387
970
|
.catch(() => undefined)
|
|
388
971
|
.then(async () => await this.tabs.getTab(activeTabId) ?? activeTab)
|
|
@@ -395,16 +978,29 @@ export class OpsRuntime {
|
|
|
395
978
|
}
|
|
396
979
|
}
|
|
397
980
|
try {
|
|
398
|
-
|
|
981
|
+
const refreshedTab = isStartUrlConnect
|
|
982
|
+
? await this.attachStartUrlConnectTab(activeTabId)
|
|
983
|
+
: await this.attachLaunchTargetTab(activeTabId, false);
|
|
984
|
+
if (refreshedTab) {
|
|
985
|
+
resolvedTab = refreshedTab;
|
|
986
|
+
}
|
|
399
987
|
}
|
|
400
988
|
catch (error) {
|
|
401
989
|
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
402
|
-
this.sendError(message, buildError("cdp_attach_failed", detail, false));
|
|
990
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
403
991
|
return;
|
|
404
992
|
}
|
|
405
993
|
if (!startUrl) {
|
|
406
994
|
await this.tabs.waitForTabComplete(activeTab.id).catch(() => undefined);
|
|
407
995
|
}
|
|
996
|
+
try {
|
|
997
|
+
await this.enableTargetDomains(activeTabId, true);
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
const detail = error instanceof Error ? error.message : "Debugger attach failed";
|
|
1001
|
+
this.sendError(message, buildError("cdp_attach_failed", detail, false, this.getDirectAttachErrorDetails(error)));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
408
1004
|
const leaseId = typeof message.leaseId === "string" && message.leaseId.trim().length > 0
|
|
409
1005
|
? message.leaseId.trim()
|
|
410
1006
|
: createId();
|
|
@@ -413,15 +1009,8 @@ export class OpsRuntime {
|
|
|
413
1009
|
title: resolvedTab.title ?? undefined
|
|
414
1010
|
}, {
|
|
415
1011
|
parallelismPolicy
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
this.sendEvent({
|
|
419
|
-
type: "ops_event",
|
|
420
|
-
clientId,
|
|
421
|
-
opsSessionId: session.id,
|
|
422
|
-
event: "ops_session_created",
|
|
423
|
-
payload: { tabId: session.tabId, targetId: session.targetId }
|
|
424
|
-
});
|
|
1012
|
+
}, requestedSessionId);
|
|
1013
|
+
this.emitSessionEvent(session, "ops_session_created");
|
|
425
1014
|
this.sendResponse(message, {
|
|
426
1015
|
opsSessionId: session.id,
|
|
427
1016
|
activeTargetId: session.activeTargetId,
|
|
@@ -441,12 +1030,18 @@ export class OpsRuntime {
|
|
|
441
1030
|
const session = this.getSessionForMessage(message, clientId);
|
|
442
1031
|
if (!session)
|
|
443
1032
|
return;
|
|
444
|
-
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;
|
|
445
1039
|
this.sendResponse(message, {
|
|
446
1040
|
mode: "extension",
|
|
447
|
-
activeTargetId:
|
|
448
|
-
url: tab
|
|
449
|
-
title: tab
|
|
1041
|
+
activeTargetId: reportedTargetId,
|
|
1042
|
+
url: resolveReportedTargetUrl(reportedTarget, tab),
|
|
1043
|
+
title: resolveReportedTargetTitle(reportedTarget, tab),
|
|
1044
|
+
dialog: this.serializeDialogState(session, reportedTargetId),
|
|
450
1045
|
leaseId: session.leaseId,
|
|
451
1046
|
state: session.state
|
|
452
1047
|
});
|
|
@@ -454,14 +1049,26 @@ export class OpsRuntime {
|
|
|
454
1049
|
async handleTargetsList(message, session) {
|
|
455
1050
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
456
1051
|
const includeUrls = payload.includeUrls === true;
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const synthetic = session.syntheticTargets.get(target.targetId);
|
|
460
|
-
return {
|
|
1052
|
+
const targetContexts = [
|
|
1053
|
+
...Array.from(session.targets.values()).map((target) => ({
|
|
461
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) => ({
|
|
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,
|
|
462
1069
|
type: "page",
|
|
463
|
-
title: resolveReportedTargetTitle(target, tab
|
|
464
|
-
url: includeUrls ? resolveReportedTargetUrl(target, tab
|
|
1070
|
+
title: resolveReportedTargetTitle(target, tab),
|
|
1071
|
+
url: includeUrls ? resolveReportedTargetUrl(target, tab) : undefined
|
|
465
1072
|
};
|
|
466
1073
|
}));
|
|
467
1074
|
this.sendResponse(message, { activeTargetId: session.activeTargetId || null, targets });
|
|
@@ -469,22 +1076,125 @@ export class OpsRuntime {
|
|
|
469
1076
|
async handleTargetsUse(message, session) {
|
|
470
1077
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
471
1078
|
const targetId = typeof payload.targetId === "string" ? payload.targetId : null;
|
|
472
|
-
if (!targetId || !
|
|
1079
|
+
if (!targetId || !this.hasOpsTarget(session, targetId)) {
|
|
473
1080
|
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
474
1081
|
return;
|
|
475
1082
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
+
}
|
|
480
1094
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
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
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
await this.activateTargetAndRespond(message, session, targetId);
|
|
488
1198
|
}
|
|
489
1199
|
async handleTargetsRegisterCanvas(message, session) {
|
|
490
1200
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
@@ -493,37 +1203,59 @@ export class OpsRuntime {
|
|
|
493
1203
|
this.sendError(message, buildError("invalid_request", "Missing targetId", false));
|
|
494
1204
|
return;
|
|
495
1205
|
}
|
|
1206
|
+
try {
|
|
1207
|
+
this.sendResponse(message, await this.registerCanvasTarget(session, targetId));
|
|
1208
|
+
}
|
|
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));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async registerCanvasTarget(session, targetId) {
|
|
496
1232
|
const tabId = parseTabTargetId(targetId);
|
|
497
1233
|
if (tabId === null) {
|
|
498
|
-
|
|
499
|
-
return;
|
|
1234
|
+
throw new Error("Canvas targetId must be tab-<id>.");
|
|
500
1235
|
}
|
|
501
1236
|
let tab = await this.tabs.getTab(tabId);
|
|
502
1237
|
if (!tab) {
|
|
503
|
-
|
|
504
|
-
return;
|
|
1238
|
+
throw new Error("Unknown targetId");
|
|
505
1239
|
}
|
|
506
1240
|
await this.tabs.waitForTabComplete(tabId, 5000).catch(() => undefined);
|
|
507
1241
|
tab = await this.tabs.getTab(tabId) ?? tab;
|
|
508
1242
|
if (!this.isAllowedCanvasTargetUrl(tab.url)) {
|
|
509
|
-
|
|
510
|
-
return;
|
|
1243
|
+
throw new Error("Only the extension canvas tab can be registered.");
|
|
511
1244
|
}
|
|
512
1245
|
const existing = session.targets.get(targetId);
|
|
513
1246
|
if (existing) {
|
|
514
1247
|
existing.url = tab.url ?? existing.url;
|
|
515
1248
|
existing.title = tab.title ?? existing.title;
|
|
516
1249
|
session.activeTargetId = targetId;
|
|
517
|
-
|
|
1250
|
+
return {
|
|
518
1251
|
targetId,
|
|
519
1252
|
url: existing.url,
|
|
520
1253
|
title: existing.title,
|
|
521
1254
|
adopted: false
|
|
522
|
-
}
|
|
523
|
-
return;
|
|
1255
|
+
};
|
|
524
1256
|
}
|
|
525
1257
|
try {
|
|
526
|
-
await this.
|
|
1258
|
+
await this.attachTargetTab(tabId);
|
|
527
1259
|
await this.enableTargetDomains(tabId);
|
|
528
1260
|
}
|
|
529
1261
|
catch (error) {
|
|
@@ -534,33 +1266,43 @@ export class OpsRuntime {
|
|
|
534
1266
|
}
|
|
535
1267
|
const target = this.sessions.addTarget(session.id, tabId, { url: tab.url ?? undefined, title: tab.title ?? undefined });
|
|
536
1268
|
session.activeTargetId = target.targetId;
|
|
537
|
-
|
|
1269
|
+
return {
|
|
538
1270
|
targetId: target.targetId,
|
|
539
1271
|
url: target.url,
|
|
540
1272
|
title: target.title,
|
|
541
1273
|
adopted: true
|
|
542
|
-
}
|
|
1274
|
+
};
|
|
543
1275
|
}
|
|
544
1276
|
async handleTargetsNew(message, session) {
|
|
545
1277
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
546
1278
|
const url = typeof payload.url === "string" ? payload.url : undefined;
|
|
547
|
-
const tab = await this.tabs.createTab(url,
|
|
1279
|
+
const tab = await this.tabs.createTab(url, false);
|
|
548
1280
|
if (!tab?.id) {
|
|
549
1281
|
this.sendError(message, buildError("execution_failed", "Target creation failed", false));
|
|
550
1282
|
return;
|
|
551
1283
|
}
|
|
552
|
-
|
|
1284
|
+
this.rememberCommandCreatedTab(session.id, tab.id, "targets.new");
|
|
553
1285
|
try {
|
|
554
|
-
await this.
|
|
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 });
|
|
555
1302
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
this.sendError(message, buildError("cdp_attach_failed", detail, false));
|
|
559
|
-
return;
|
|
1303
|
+
finally {
|
|
1304
|
+
this.forgetCommandCreatedTab(tab.id);
|
|
560
1305
|
}
|
|
561
|
-
const target = this.sessions.addTarget(session.id, tab.id, { url: tab.url ?? undefined, title: tab.title ?? undefined });
|
|
562
|
-
session.activeTargetId = target.targetId;
|
|
563
|
-
this.sendResponse(message, { targetId: target.targetId });
|
|
564
1306
|
}
|
|
565
1307
|
async handleTargetsClose(message, session) {
|
|
566
1308
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
@@ -569,13 +1311,27 @@ export class OpsRuntime {
|
|
|
569
1311
|
this.sendError(message, buildError("invalid_request", "Missing targetId", false));
|
|
570
1312
|
return;
|
|
571
1313
|
}
|
|
572
|
-
const target = session.targets.get(targetId);
|
|
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
|
+
}
|
|
573
1329
|
if (!target) {
|
|
574
1330
|
this.sendError(message, buildError("invalid_request", "Unknown targetId", false));
|
|
575
1331
|
return;
|
|
576
1332
|
}
|
|
577
1333
|
this.sessions.removeTarget(session.id, targetId);
|
|
578
|
-
|
|
1334
|
+
void this.closeTabBestEffort(target.tabId);
|
|
579
1335
|
if (target.targetId === session.targetId || session.targets.size === 0) {
|
|
580
1336
|
this.sendResponse(message, { ok: true });
|
|
581
1337
|
this.scheduleSessionCleanup(session.id, "ops_session_closed");
|
|
@@ -597,35 +1353,44 @@ export class OpsRuntime {
|
|
|
597
1353
|
return;
|
|
598
1354
|
}
|
|
599
1355
|
const url = typeof payload.url === "string" ? payload.url : undefined;
|
|
600
|
-
const tab = await this.tabs.createTab(url,
|
|
1356
|
+
const tab = await this.tabs.createTab(url, false);
|
|
601
1357
|
if (!tab?.id) {
|
|
602
1358
|
this.sendError(message, buildError("execution_failed", "Target creation failed", false));
|
|
603
1359
|
return;
|
|
604
1360
|
}
|
|
605
|
-
|
|
1361
|
+
this.rememberCommandCreatedTab(session.id, tab.id, "page.open");
|
|
606
1362
|
try {
|
|
607
|
-
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 });
|
|
608
1380
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
this.sendError(message, buildError("cdp_attach_failed", detail, false));
|
|
612
|
-
return;
|
|
1381
|
+
finally {
|
|
1382
|
+
this.forgetCommandCreatedTab(tab.id);
|
|
613
1383
|
}
|
|
614
|
-
const target = this.sessions.addTarget(session.id, tab.id, { url: tab.url ?? undefined, title: tab.title ?? undefined });
|
|
615
|
-
this.sessions.setName(session.id, target.targetId, name);
|
|
616
|
-
session.activeTargetId = target.targetId;
|
|
617
|
-
this.sendResponse(message, { targetId: target.targetId, created: true, url: target.url, title: target.title });
|
|
618
1384
|
}
|
|
619
1385
|
async handlePageList(message, session) {
|
|
620
1386
|
const pages = await Promise.all(this.sessions.listNamedTargets(session.id).map(async ({ name, targetId }) => {
|
|
621
|
-
const target =
|
|
1387
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
622
1388
|
const tab = target ? await this.tabs.getTab(target.tabId) : null;
|
|
623
|
-
const synthetic = session.syntheticTargets.get(targetId);
|
|
624
1389
|
return {
|
|
625
1390
|
name,
|
|
626
1391
|
targetId,
|
|
627
|
-
url: resolveReportedTargetUrl(target, tab
|
|
628
|
-
title: resolveReportedTargetTitle(target, tab
|
|
1392
|
+
url: resolveReportedTargetUrl(target, tab),
|
|
1393
|
+
title: resolveReportedTargetTitle(target, tab)
|
|
629
1394
|
};
|
|
630
1395
|
}));
|
|
631
1396
|
this.sendResponse(message, { pages });
|
|
@@ -645,7 +1410,7 @@ export class OpsRuntime {
|
|
|
645
1410
|
const target = session.targets.get(targetId);
|
|
646
1411
|
if (target) {
|
|
647
1412
|
this.sessions.removeTarget(session.id, targetId);
|
|
648
|
-
|
|
1413
|
+
void this.closeTabBestEffort(target.tabId);
|
|
649
1414
|
if (target.targetId === session.targetId || session.targets.size === 0) {
|
|
650
1415
|
this.sendResponse(message, { ok: true });
|
|
651
1416
|
this.scheduleSessionCleanup(session.id, "ops_session_closed");
|
|
@@ -661,11 +1426,14 @@ export class OpsRuntime {
|
|
|
661
1426
|
this.sendError(message, buildError("invalid_request", "Missing url", false));
|
|
662
1427
|
return;
|
|
663
1428
|
}
|
|
1429
|
+
const syntheticHtml = decodeHtmlDataUrl(url);
|
|
664
1430
|
try {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
+
}
|
|
669
1437
|
}
|
|
670
1438
|
}
|
|
671
1439
|
catch {
|
|
@@ -679,15 +1447,18 @@ export class OpsRuntime {
|
|
|
679
1447
|
return;
|
|
680
1448
|
await this.tabs.activateTab(target.tabId).catch(() => undefined);
|
|
681
1449
|
const targetRecord = session.targets.get(target.targetId);
|
|
682
|
-
const syntheticHtml = decodeHtmlDataUrl(url);
|
|
683
1450
|
if (syntheticHtml !== null) {
|
|
684
1451
|
const result = await executeInTab(target.tabId, replaceDocumentWithHtmlScript, [{ html: syntheticHtml }]);
|
|
685
1452
|
session.refStore.clearTarget(target.targetId);
|
|
686
|
-
|
|
1453
|
+
this.sessions.upsertSyntheticTarget(session.id, {
|
|
1454
|
+
targetId: target.targetId,
|
|
1455
|
+
tabId: target.tabId,
|
|
1456
|
+
type: "page",
|
|
687
1457
|
url,
|
|
688
1458
|
title: typeof result?.title === "string" && result.title.trim().length > 0
|
|
689
1459
|
? result.title
|
|
690
|
-
: targetRecord?.title
|
|
1460
|
+
: targetRecord?.title,
|
|
1461
|
+
attachedAt: Date.now()
|
|
691
1462
|
});
|
|
692
1463
|
this.sendResponse(message, {
|
|
693
1464
|
finalUrl: url,
|
|
@@ -703,7 +1474,7 @@ export class OpsRuntime {
|
|
|
703
1474
|
});
|
|
704
1475
|
await this.tabs.waitForTabComplete(target.tabId, timeoutMs).catch(() => undefined);
|
|
705
1476
|
const refreshed = await this.tabs.getTab(target.tabId);
|
|
706
|
-
|
|
1477
|
+
this.sessions.removeSyntheticTarget(session.id, target.targetId);
|
|
707
1478
|
if (targetRecord) {
|
|
708
1479
|
session.targets.set(target.targetId, {
|
|
709
1480
|
...targetRecord,
|
|
@@ -726,11 +1497,16 @@ export class OpsRuntime {
|
|
|
726
1497
|
return;
|
|
727
1498
|
if (typeof payload.ref === "string") {
|
|
728
1499
|
const state = payload.state === "visible" || payload.state === "hidden" ? payload.state : "attached";
|
|
729
|
-
const
|
|
730
|
-
if (!
|
|
1500
|
+
const resolved = this.resolveRefFromPayload(session, payload.ref, message);
|
|
1501
|
+
if (!resolved)
|
|
731
1502
|
return;
|
|
732
1503
|
try {
|
|
733
|
-
|
|
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
|
+
}
|
|
734
1510
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
735
1511
|
}
|
|
736
1512
|
catch (error) {
|
|
@@ -748,64 +1524,90 @@ export class OpsRuntime {
|
|
|
748
1524
|
}
|
|
749
1525
|
async handleSnapshot(message, session) {
|
|
750
1526
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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)
|
|
757
1534
|
return;
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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)
|
|
769
1556
|
return;
|
|
770
|
-
}
|
|
771
|
-
const tab = await this.tabs.getTab(target.tabId);
|
|
772
|
-
const targetRecord = session.targets.get(target.targetId);
|
|
773
|
-
const synthetic = session.syntheticTargets.get(target.targetId);
|
|
774
1557
|
this.sendResponse(message, {
|
|
1558
|
+
sessionId: session.id,
|
|
1559
|
+
targetId: snapshot.target.targetId,
|
|
1560
|
+
mode: "extension",
|
|
775
1561
|
snapshotId: snapshot.snapshotId,
|
|
776
|
-
url:
|
|
777
|
-
title:
|
|
778
|
-
content,
|
|
779
|
-
truncated,
|
|
780
|
-
nextCursor,
|
|
781
|
-
refCount: snapshot.
|
|
782
|
-
timingMs:
|
|
783
|
-
|
|
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 } : {})
|
|
784
1571
|
});
|
|
785
1572
|
}
|
|
786
1573
|
async handleClick(message, session) {
|
|
787
|
-
const
|
|
788
|
-
if (!
|
|
789
|
-
return;
|
|
790
|
-
const target = this.requireActiveTarget(session, message);
|
|
791
|
-
if (!target)
|
|
1574
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1575
|
+
if (!resolved)
|
|
792
1576
|
return;
|
|
793
1577
|
const start = Date.now();
|
|
794
|
-
const before = await this.tabs.getTab(target.tabId);
|
|
795
|
-
await this.
|
|
796
|
-
|
|
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);
|
|
797
1597
|
const navigated = Boolean(before?.url && after?.url && before.url !== after.url);
|
|
798
1598
|
this.sendResponse(message, { timingMs: Date.now() - start, navigated });
|
|
799
1599
|
}
|
|
800
1600
|
async handleHover(message, session) {
|
|
801
|
-
const
|
|
802
|
-
if (!
|
|
803
|
-
return;
|
|
804
|
-
const target = this.requireActiveTarget(session, message);
|
|
805
|
-
if (!target)
|
|
1601
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1602
|
+
if (!resolved)
|
|
806
1603
|
return;
|
|
807
1604
|
const start = Date.now();
|
|
808
|
-
|
|
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
|
+
}
|
|
809
1611
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
810
1612
|
}
|
|
811
1613
|
async handlePress(message, session) {
|
|
@@ -818,22 +1620,33 @@ export class OpsRuntime {
|
|
|
818
1620
|
const target = this.requireActiveTarget(session, message);
|
|
819
1621
|
if (!target)
|
|
820
1622
|
return;
|
|
821
|
-
const
|
|
822
|
-
if (payload.ref && !
|
|
1623
|
+
const resolved = typeof payload.ref === "string" ? this.resolveRefFromPayload(session, payload.ref, message) : null;
|
|
1624
|
+
if (payload.ref && !resolved)
|
|
823
1625
|
return;
|
|
824
1626
|
const start = Date.now();
|
|
825
|
-
|
|
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
|
+
}
|
|
826
1637
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
827
1638
|
}
|
|
828
1639
|
async handleCheck(message, session, checked) {
|
|
829
|
-
const
|
|
830
|
-
if (!
|
|
831
|
-
return;
|
|
832
|
-
const target = this.requireActiveTarget(session, message);
|
|
833
|
-
if (!target)
|
|
1640
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1641
|
+
if (!resolved)
|
|
834
1642
|
return;
|
|
835
1643
|
const start = Date.now();
|
|
836
|
-
|
|
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
|
+
}
|
|
837
1650
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
838
1651
|
}
|
|
839
1652
|
async handleType(message, session) {
|
|
@@ -844,14 +1657,16 @@ export class OpsRuntime {
|
|
|
844
1657
|
this.sendError(message, buildError("invalid_request", "Missing ref or text", false));
|
|
845
1658
|
return;
|
|
846
1659
|
}
|
|
847
|
-
const
|
|
848
|
-
if (!
|
|
849
|
-
return;
|
|
850
|
-
const target = this.requireActiveTarget(session, message);
|
|
851
|
-
if (!target)
|
|
1660
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1661
|
+
if (!resolved)
|
|
852
1662
|
return;
|
|
853
1663
|
const start = Date.now();
|
|
854
|
-
|
|
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
|
+
}
|
|
855
1670
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
856
1671
|
}
|
|
857
1672
|
async handleSelect(message, session) {
|
|
@@ -862,37 +1677,126 @@ export class OpsRuntime {
|
|
|
862
1677
|
this.sendError(message, buildError("invalid_request", "Missing ref or values", false));
|
|
863
1678
|
return;
|
|
864
1679
|
}
|
|
865
|
-
const
|
|
866
|
-
if (!
|
|
867
|
-
return;
|
|
868
|
-
const target = this.requireActiveTarget(session, message);
|
|
869
|
-
if (!target)
|
|
1680
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1681
|
+
if (!resolved)
|
|
870
1682
|
return;
|
|
871
|
-
|
|
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
|
+
}
|
|
872
1689
|
this.sendResponse(message, {});
|
|
873
1690
|
}
|
|
874
1691
|
async handleScroll(message, session) {
|
|
875
1692
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
876
1693
|
const dy = typeof payload.dy === "number" ? payload.dy : 0;
|
|
877
1694
|
const ref = typeof payload.ref === "string" ? payload.ref : undefined;
|
|
878
|
-
const selector = ref ? this.resolveSelector(session, ref, message) ?? undefined : undefined;
|
|
879
|
-
if (ref && !selector)
|
|
880
|
-
return;
|
|
881
1695
|
const target = this.requireActiveTarget(session, message);
|
|
882
1696
|
if (!target)
|
|
883
1697
|
return;
|
|
884
|
-
|
|
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
|
+
}
|
|
885
1708
|
this.sendResponse(message, {});
|
|
886
1709
|
}
|
|
887
1710
|
async handleScrollIntoView(message, session) {
|
|
888
|
-
const
|
|
889
|
-
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));
|
|
890
1787
|
return;
|
|
1788
|
+
}
|
|
891
1789
|
const target = this.requireActiveTarget(session, message);
|
|
892
1790
|
if (!target)
|
|
893
1791
|
return;
|
|
894
1792
|
const start = Date.now();
|
|
895
|
-
|
|
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);
|
|
896
1800
|
this.sendResponse(message, { timingMs: Date.now() - start });
|
|
897
1801
|
}
|
|
898
1802
|
async handleDomGetHtml(message, session) {
|
|
@@ -903,13 +1807,12 @@ export class OpsRuntime {
|
|
|
903
1807
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
904
1808
|
return;
|
|
905
1809
|
}
|
|
906
|
-
const
|
|
907
|
-
if (!
|
|
1810
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1811
|
+
if (!resolved)
|
|
908
1812
|
return;
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
const html = await this.runElementAction(target, selector, { type: "outerHTML" }, () => this.dom.getOuterHtml(target.tabId, selector));
|
|
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);
|
|
913
1816
|
const truncated = html.length > maxChars;
|
|
914
1817
|
const outerHTML = truncated ? html.slice(0, maxChars) : html;
|
|
915
1818
|
this.sendResponse(message, { outerHTML, truncated });
|
|
@@ -922,13 +1825,12 @@ export class OpsRuntime {
|
|
|
922
1825
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
923
1826
|
return;
|
|
924
1827
|
}
|
|
925
|
-
const
|
|
926
|
-
if (!
|
|
927
|
-
return;
|
|
928
|
-
const target = this.requireActiveTarget(session, message);
|
|
929
|
-
if (!target)
|
|
1828
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1829
|
+
if (!resolved)
|
|
930
1830
|
return;
|
|
931
|
-
const text =
|
|
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);
|
|
932
1834
|
const truncated = text.length > maxChars;
|
|
933
1835
|
this.sendResponse(message, { text: truncated ? text.slice(0, maxChars) : text, truncated });
|
|
934
1836
|
}
|
|
@@ -940,13 +1842,12 @@ export class OpsRuntime {
|
|
|
940
1842
|
this.sendError(message, buildError("invalid_request", "Missing ref or name", false));
|
|
941
1843
|
return;
|
|
942
1844
|
}
|
|
943
|
-
const
|
|
944
|
-
if (!
|
|
945
|
-
return;
|
|
946
|
-
const target = this.requireActiveTarget(session, message);
|
|
947
|
-
if (!target)
|
|
1845
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1846
|
+
if (!resolved)
|
|
948
1847
|
return;
|
|
949
|
-
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]);
|
|
950
1851
|
this.sendResponse(message, { value });
|
|
951
1852
|
}
|
|
952
1853
|
async handleDomGetValue(message, session) {
|
|
@@ -956,47 +1857,144 @@ export class OpsRuntime {
|
|
|
956
1857
|
this.sendError(message, buildError("invalid_request", "Missing ref", false));
|
|
957
1858
|
return;
|
|
958
1859
|
}
|
|
959
|
-
const
|
|
960
|
-
if (!
|
|
961
|
-
return;
|
|
962
|
-
const target = this.requireActiveTarget(session, message);
|
|
963
|
-
if (!target)
|
|
1860
|
+
const resolved = this.resolveRefFromPayload(session, ref, message);
|
|
1861
|
+
if (!resolved)
|
|
964
1862
|
return;
|
|
965
|
-
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);
|
|
966
1866
|
this.sendResponse(message, { value });
|
|
967
1867
|
}
|
|
968
1868
|
async handleDomIsVisible(message, session) {
|
|
969
|
-
const
|
|
970
|
-
if (!
|
|
971
|
-
return;
|
|
972
|
-
const target = this.requireActiveTarget(session, message);
|
|
973
|
-
if (!target)
|
|
1869
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1870
|
+
if (!resolved)
|
|
974
1871
|
return;
|
|
975
|
-
const visible =
|
|
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);
|
|
976
1875
|
const isVisible = typeof visible === "object" && visible !== null && "visible" in visible
|
|
977
1876
|
? Boolean(visible.visible)
|
|
978
1877
|
: Boolean(visible);
|
|
979
1878
|
this.sendResponse(message, { value: isVisible });
|
|
980
1879
|
}
|
|
981
1880
|
async handleDomIsEnabled(message, session) {
|
|
982
|
-
const
|
|
983
|
-
if (!
|
|
1881
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1882
|
+
if (!resolved)
|
|
1883
|
+
return;
|
|
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);
|
|
1887
|
+
this.sendResponse(message, { value: enabled });
|
|
1888
|
+
}
|
|
1889
|
+
async handleDomIsChecked(message, session) {
|
|
1890
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1891
|
+
if (!resolved)
|
|
1892
|
+
return;
|
|
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);
|
|
1896
|
+
this.sendResponse(message, { value: checked });
|
|
1897
|
+
}
|
|
1898
|
+
async handleDomRefPoint(message, session) {
|
|
1899
|
+
const resolved = this.resolveRefFromPayload(session, message.payload, message);
|
|
1900
|
+
if (!resolved)
|
|
984
1901
|
return;
|
|
1902
|
+
const point = await this.resolveRefPoint(resolved);
|
|
1903
|
+
this.sendResponse(message, point);
|
|
1904
|
+
}
|
|
1905
|
+
async handleCanvasOverlayMount(message, session) {
|
|
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";
|
|
985
1916
|
const target = this.requireActiveTarget(session, message);
|
|
986
1917
|
if (!target)
|
|
987
1918
|
return;
|
|
988
|
-
const
|
|
989
|
-
this.
|
|
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 }
|
|
1932
|
+
});
|
|
990
1933
|
}
|
|
991
|
-
async
|
|
992
|
-
const
|
|
993
|
-
|
|
1934
|
+
async handleCanvasOverlayUnmount(message, session) {
|
|
1935
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1936
|
+
const mountId = typeof payload.mountId === "string" ? payload.mountId.trim() : "";
|
|
1937
|
+
if (!mountId) {
|
|
1938
|
+
this.sendError(message, buildError("invalid_request", "Missing mountId", false));
|
|
994
1939
|
return;
|
|
1940
|
+
}
|
|
995
1941
|
const target = this.requireActiveTarget(session, message);
|
|
996
1942
|
if (!target)
|
|
997
1943
|
return;
|
|
998
|
-
|
|
999
|
-
this.sendResponse(message, {
|
|
1944
|
+
await this.dom.unmountCanvasOverlay(target.tabId, mountId);
|
|
1945
|
+
this.sendResponse(message, {
|
|
1946
|
+
ok: true,
|
|
1947
|
+
mountId,
|
|
1948
|
+
targetId: target.targetId,
|
|
1949
|
+
overlayState: "idle"
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
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
|
+
}
|
|
1963
|
+
const target = this.requireActiveTarget(session, message);
|
|
1964
|
+
if (!target)
|
|
1965
|
+
return;
|
|
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
|
+
});
|
|
1000
1998
|
}
|
|
1001
1999
|
async handleCanvasRuntimePreviewBridge(message, session) {
|
|
1002
2000
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
@@ -1058,15 +2056,72 @@ export class OpsRuntime {
|
|
|
1058
2056
|
const target = this.requireActiveTarget(session, message);
|
|
1059
2057
|
if (!target)
|
|
1060
2058
|
return;
|
|
1061
|
-
const result = await this.cdp.sendCommand(
|
|
2059
|
+
const result = await this.cdp.sendCommand(target.debuggee, "Performance.getMetrics", {});
|
|
1062
2060
|
this.sendResponse(message, { metrics: Array.isArray(result.metrics) ? result.metrics : [] });
|
|
1063
2061
|
}
|
|
1064
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
|
+
}
|
|
1065
2091
|
const target = this.requireActiveTarget(session, message);
|
|
1066
2092
|
if (!target)
|
|
1067
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
|
+
}
|
|
1068
2123
|
try {
|
|
1069
|
-
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");
|
|
1070
2125
|
if (result?.data) {
|
|
1071
2126
|
this.sendResponse(message, { base64: result.data });
|
|
1072
2127
|
return;
|
|
@@ -1082,16 +2137,112 @@ export class OpsRuntime {
|
|
|
1082
2137
|
}
|
|
1083
2138
|
this.sendError(message, buildError("execution_failed", "Screenshot failed", false));
|
|
1084
2139
|
}
|
|
1085
|
-
async
|
|
2140
|
+
async handleUpload(message, session) {
|
|
1086
2141
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
+
}
|
|
2236
|
+
async handleConsolePoll(message, session) {
|
|
2237
|
+
const payload = isRecord(message.payload) ? message.payload : {};
|
|
2238
|
+
const sinceSeq = typeof payload.sinceSeq === "number" ? payload.sinceSeq : 0;
|
|
2239
|
+
const max = typeof payload.max === "number" ? payload.max : 50;
|
|
2240
|
+
const events = session.consoleEvents.filter((event) => event.seq > sinceSeq).slice(0, max);
|
|
2241
|
+
const lastEvent = events.at(-1);
|
|
2242
|
+
const nextSeq = lastEvent ? lastEvent.seq : sinceSeq;
|
|
2243
|
+
this.sendResponse(message, { events, nextSeq });
|
|
2244
|
+
}
|
|
2245
|
+
async handleNetworkPoll(message, session) {
|
|
1095
2246
|
const payload = isRecord(message.payload) ? message.payload : {};
|
|
1096
2247
|
const sinceSeq = typeof payload.sinceSeq === "number" ? payload.sinceSeq : 0;
|
|
1097
2248
|
const max = typeof payload.max === "number" ? payload.max : 50;
|
|
@@ -1134,7 +2285,7 @@ export class OpsRuntime {
|
|
|
1134
2285
|
if (!target)
|
|
1135
2286
|
return;
|
|
1136
2287
|
try {
|
|
1137
|
-
await this.cdp.sendCommand(
|
|
2288
|
+
await this.cdp.sendCommand(target.debuggee, "Network.setCookies", { cookies: normalized });
|
|
1138
2289
|
}
|
|
1139
2290
|
catch (error) {
|
|
1140
2291
|
const detail = error instanceof Error ? error.message : "Cookie import failed";
|
|
@@ -1167,7 +2318,7 @@ export class OpsRuntime {
|
|
|
1167
2318
|
return;
|
|
1168
2319
|
let rawCookies = [];
|
|
1169
2320
|
try {
|
|
1170
|
-
const response = await this.cdp.sendCommand(
|
|
2321
|
+
const response = await this.cdp.sendCommand(target.debuggee, "Network.getCookies", urls ? { urls } : {});
|
|
1171
2322
|
rawCookies = Array.isArray(response.cookies) ? response.cookies : [];
|
|
1172
2323
|
}
|
|
1173
2324
|
catch (error) {
|
|
@@ -1184,23 +2335,323 @@ export class OpsRuntime {
|
|
|
1184
2335
|
count: cookies.length
|
|
1185
2336
|
});
|
|
1186
2337
|
}
|
|
1187
|
-
async
|
|
1188
|
-
|
|
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
|
+
}
|
|
1189
2365
|
}
|
|
1190
|
-
async
|
|
2366
|
+
async attachCreatedTargetTab(tabId) {
|
|
1191
2367
|
try {
|
|
1192
|
-
await this.
|
|
1193
|
-
|
|
1194
|
-
await this.cdp.sendCommand({ tabId }, "Performance.enable", {});
|
|
2368
|
+
await this.attachTargetTab(tabId);
|
|
2369
|
+
return;
|
|
1195
2370
|
}
|
|
1196
2371
|
catch (error) {
|
|
1197
|
-
|
|
2372
|
+
if (!isAttachBlockedError(error)) {
|
|
2373
|
+
throw error;
|
|
2374
|
+
}
|
|
1198
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) {
|
|
2575
|
+
try {
|
|
2576
|
+
await this.cdp.setDiscoverTargetsEnabled?.(true);
|
|
2577
|
+
}
|
|
2578
|
+
catch (error) {
|
|
2579
|
+
logError("ops.discover_targets", error, {
|
|
2580
|
+
code: "discover_targets_enable_failed",
|
|
2581
|
+
extra: baseDetails
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
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
|
+
});
|
|
1199
2646
|
}
|
|
1200
2647
|
async withSession(message, clientId, handler) {
|
|
1201
2648
|
const session = this.getSessionForMessage(message, clientId);
|
|
1202
2649
|
if (!session)
|
|
1203
2650
|
return;
|
|
2651
|
+
if (DIALOG_SCOPED_COMMANDS.has(message.command)) {
|
|
2652
|
+
await this.withDialogQueue(message, session, handler);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
1204
2655
|
if (!TARGET_SCOPED_COMMANDS.has(message.command)) {
|
|
1205
2656
|
session.queue = session.queue.then(() => handler(session), () => handler(session));
|
|
1206
2657
|
await session.queue;
|
|
@@ -1222,6 +2673,9 @@ export class OpsRuntime {
|
|
|
1222
2673
|
const requested = typeof payload.targetId === "string" ? payload.targetId.trim() : "";
|
|
1223
2674
|
return requested || session.activeTargetId || session.targetId;
|
|
1224
2675
|
}
|
|
2676
|
+
dialogQueueKey(sessionId, targetId) {
|
|
2677
|
+
return `${sessionId}:${targetId}`;
|
|
2678
|
+
}
|
|
1225
2679
|
sessionQueueAgeMs(session) {
|
|
1226
2680
|
let oldest = null;
|
|
1227
2681
|
for (const value of session.targetQueueOldestAt.values()) {
|
|
@@ -1352,83 +2806,814 @@ export class OpsRuntime {
|
|
|
1352
2806
|
acquired = true;
|
|
1353
2807
|
await handler(session);
|
|
1354
2808
|
}
|
|
1355
|
-
finally {
|
|
1356
|
-
if (acquired) {
|
|
1357
|
-
this.releaseParallelSlot(session);
|
|
2809
|
+
finally {
|
|
2810
|
+
if (acquired) {
|
|
2811
|
+
this.releaseParallelSlot(session);
|
|
2812
|
+
}
|
|
2813
|
+
releaseQueue();
|
|
2814
|
+
const depth = (session.targetQueueDepth.get(targetId) ?? 1) - 1;
|
|
2815
|
+
if (depth <= 0) {
|
|
2816
|
+
session.targetQueueDepth.delete(targetId);
|
|
2817
|
+
session.targetQueueOldestAt.delete(targetId);
|
|
2818
|
+
}
|
|
2819
|
+
else {
|
|
2820
|
+
session.targetQueueDepth.set(targetId, depth);
|
|
2821
|
+
}
|
|
2822
|
+
session.pendingParallel = Math.max(0, session.pendingParallel - 1);
|
|
2823
|
+
if (session.targetQueues.get(targetId) === tail) {
|
|
2824
|
+
session.targetQueues.delete(targetId);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
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);
|
|
1358
3296
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
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;
|
|
1364
3315
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
3316
|
+
if (sessionId) {
|
|
3317
|
+
break;
|
|
1367
3318
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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;
|
|
1371
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);
|
|
1372
3351
|
}
|
|
3352
|
+
this.clearPopupAttachDiagnostic(session.id, target.targetId);
|
|
3353
|
+
return true;
|
|
1373
3354
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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 {};
|
|
1379
3375
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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})`;
|
|
1384
3419
|
}
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1387
|
-
if (leaseId && leaseId === session.leaseId) {
|
|
1388
|
-
this.reclaimSession(session, clientId);
|
|
1389
|
-
}
|
|
1390
|
-
else {
|
|
1391
|
-
this.sendError(message, buildError("not_owner", "Client does not own session", false));
|
|
1392
|
-
return null;
|
|
1393
|
-
}
|
|
3420
|
+
if (!details?.stage) {
|
|
3421
|
+
return "";
|
|
1394
3422
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
3423
|
+
return ` (origin: ${details.origin}; stage: ${details.stage})`;
|
|
3424
|
+
}
|
|
3425
|
+
toDirectAttachDiagnosticDetails(diagnostic) {
|
|
3426
|
+
if (!diagnostic) {
|
|
3427
|
+
return undefined;
|
|
1398
3428
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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);
|
|
1402
3441
|
}
|
|
1403
|
-
|
|
1404
|
-
return session;
|
|
3442
|
+
return this.decorateCdpFailure(error, this.toDirectAttachDiagnosticDetails(diagnostic) ?? {});
|
|
1405
3443
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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 {};
|
|
1410
3553
|
}
|
|
1411
|
-
return
|
|
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
|
+
};
|
|
1412
3565
|
}
|
|
1413
3566
|
requireActiveTarget(session, message) {
|
|
1414
|
-
const
|
|
3567
|
+
const explicitTargetId = this.extractPayloadTargetId(message.payload);
|
|
3568
|
+
const targetId = explicitTargetId ?? session.activeTargetId ?? session.targetId;
|
|
1415
3569
|
if (!targetId) {
|
|
1416
3570
|
this.sendError(message, buildError("invalid_request", "No active target", false));
|
|
1417
3571
|
return null;
|
|
1418
3572
|
}
|
|
1419
|
-
const target =
|
|
3573
|
+
const target = this.resolveRequestedTargetContext(session, targetId, explicitTargetId !== null);
|
|
1420
3574
|
if (!target) {
|
|
1421
3575
|
this.sendError(message, buildError("invalid_request", "Active target missing", false));
|
|
1422
3576
|
return null;
|
|
1423
3577
|
}
|
|
1424
3578
|
if (target.url) {
|
|
1425
3579
|
const restriction = isRestrictedUrl(target.url);
|
|
1426
|
-
if (restriction.restricted && !this.
|
|
3580
|
+
if (restriction.restricted && !this.isAllowedCanvasRestrictionTarget(session, targetId, target)) {
|
|
1427
3581
|
this.sendError(message, buildError("restricted_url", restriction.message ?? "Restricted tab.", false));
|
|
1428
3582
|
return null;
|
|
1429
3583
|
}
|
|
1430
3584
|
}
|
|
1431
|
-
|
|
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;
|
|
1432
3617
|
}
|
|
1433
3618
|
isAllowedCanvasTargetUrl(rawUrl) {
|
|
1434
3619
|
if (typeof rawUrl !== "string" || rawUrl.length === 0) {
|
|
@@ -1517,7 +3702,73 @@ export class OpsRuntime {
|
|
|
1517
3702
|
return null;
|
|
1518
3703
|
}
|
|
1519
3704
|
}
|
|
1520
|
-
|
|
3705
|
+
resolveRefContext(session, ref, targetId) {
|
|
3706
|
+
const target = this.resolveTargetContext(session, targetId);
|
|
3707
|
+
if (!target) {
|
|
3708
|
+
return null;
|
|
3709
|
+
}
|
|
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)));
|
|
3738
|
+
return null;
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
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
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
resolveRefFromPayload(session, refOrPayload, message) {
|
|
1521
3772
|
const ref = typeof refOrPayload === "string"
|
|
1522
3773
|
? refOrPayload
|
|
1523
3774
|
: (isRecord(refOrPayload) && typeof refOrPayload.ref === "string" ? refOrPayload.ref : null);
|
|
@@ -1530,12 +3781,183 @@ export class OpsRuntime {
|
|
|
1530
3781
|
this.sendError(message, buildError("invalid_request", "No active target", false));
|
|
1531
3782
|
return null;
|
|
1532
3783
|
}
|
|
1533
|
-
const
|
|
1534
|
-
if (!
|
|
1535
|
-
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));
|
|
3787
|
+
return null;
|
|
3788
|
+
}
|
|
3789
|
+
if (resolved.target.synthetic && !resolved.target.sessionId) {
|
|
3790
|
+
this.sendPopupAttachPendingError(message, session, resolved.target.targetId);
|
|
1536
3791
|
return null;
|
|
1537
3792
|
}
|
|
1538
|
-
return
|
|
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 };
|
|
3940
|
+
}
|
|
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");
|
|
1539
3961
|
}
|
|
1540
3962
|
async waitForSelector(target, selector, state, timeoutMs) {
|
|
1541
3963
|
const start = Date.now();
|
|
@@ -1551,6 +3973,22 @@ export class OpsRuntime {
|
|
|
1551
3973
|
}
|
|
1552
3974
|
throw new Error("Wait for selector timed out");
|
|
1553
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
|
+
}
|
|
1554
3992
|
cleanupSession(session, event) {
|
|
1555
3993
|
this.clearClosingTimer(session.id);
|
|
1556
3994
|
const waiters = this.parallelWaiters.get(session.id);
|
|
@@ -1568,13 +4006,7 @@ export class OpsRuntime {
|
|
|
1568
4006
|
for (const target of session.targets.values()) {
|
|
1569
4007
|
void this.cdp.detachTab(target.tabId).catch(() => undefined);
|
|
1570
4008
|
}
|
|
1571
|
-
this.
|
|
1572
|
-
type: "ops_event",
|
|
1573
|
-
clientId: session.ownerClientId,
|
|
1574
|
-
opsSessionId: session.id,
|
|
1575
|
-
event,
|
|
1576
|
-
payload: { tabId: session.tabId, targetId: session.targetId }
|
|
1577
|
-
});
|
|
4009
|
+
this.emitSessionEvent(session, event);
|
|
1578
4010
|
}
|
|
1579
4011
|
handleClosedTarget(tabId, event) {
|
|
1580
4012
|
const session = this.sessions.getByTabId(tabId);
|
|
@@ -1590,7 +4022,7 @@ export class OpsRuntime {
|
|
|
1590
4022
|
this.cleanupSession(session, event);
|
|
1591
4023
|
}
|
|
1592
4024
|
}
|
|
1593
|
-
handleDebuggerDetachForTab(tabId) {
|
|
4025
|
+
async handleDebuggerDetachForTab(tabId) {
|
|
1594
4026
|
const session = this.sessions.getByTabId(tabId);
|
|
1595
4027
|
if (!session)
|
|
1596
4028
|
return;
|
|
@@ -1598,6 +4030,21 @@ export class OpsRuntime {
|
|
|
1598
4030
|
// Root tab detach can be transient during child-target shutdown; tab removal handler owns root teardown.
|
|
1599
4031
|
return;
|
|
1600
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
|
+
}
|
|
1601
4048
|
this.handleClosedTarget(tabId, "ops_session_closed");
|
|
1602
4049
|
}
|
|
1603
4050
|
async closeTabBestEffort(tabId) {
|
|
@@ -1675,9 +4122,19 @@ export class OpsRuntime {
|
|
|
1675
4122
|
sendEvent(event) {
|
|
1676
4123
|
this.sendEnvelope(event);
|
|
1677
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
|
+
}
|
|
1678
4134
|
markSessionClosing(session, reason) {
|
|
1679
|
-
if (session.state === "closing")
|
|
1680
|
-
return;
|
|
4135
|
+
if (session.state === "closing") {
|
|
4136
|
+
return false;
|
|
4137
|
+
}
|
|
1681
4138
|
session.state = "closing";
|
|
1682
4139
|
session.closingReason = reason;
|
|
1683
4140
|
session.expiresAt = Date.now() + SESSION_TTL_MS;
|
|
@@ -1689,13 +4146,18 @@ export class OpsRuntime {
|
|
|
1689
4146
|
}
|
|
1690
4147
|
}, SESSION_TTL_MS);
|
|
1691
4148
|
this.closingTimers.set(session.id, timeoutId);
|
|
4149
|
+
return true;
|
|
1692
4150
|
}
|
|
1693
4151
|
reclaimSession(session, clientId) {
|
|
4152
|
+
const wasClosing = session.state === "closing";
|
|
1694
4153
|
session.ownerClientId = clientId;
|
|
1695
4154
|
session.state = "active";
|
|
1696
4155
|
session.expiresAt = undefined;
|
|
1697
4156
|
session.closingReason = undefined;
|
|
1698
4157
|
this.clearClosingTimer(session.id);
|
|
4158
|
+
if (wasClosing) {
|
|
4159
|
+
this.emitSessionEvent(session, "ops_session_reclaimed");
|
|
4160
|
+
}
|
|
1699
4161
|
}
|
|
1700
4162
|
clearClosingTimer(sessionId) {
|
|
1701
4163
|
const timer = this.closingTimers.get(sessionId);
|
|
@@ -1909,6 +4371,18 @@ const toCookieListRecord = (entry) => {
|
|
|
1909
4371
|
const isRecord = (value) => {
|
|
1910
4372
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1911
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
|
+
};
|
|
1912
4386
|
const parseCursor = (cursor) => {
|
|
1913
4387
|
if (!cursor)
|
|
1914
4388
|
return 0;
|
|
@@ -1943,13 +4417,23 @@ const paginate = (lines, startIndex, maxChars) => {
|
|
|
1943
4417
|
};
|
|
1944
4418
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1945
4419
|
const parseTabTargetId = (targetId) => {
|
|
1946
|
-
const
|
|
1947
|
-
|
|
1948
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4420
|
+
const match = /^tab-(\d+)$/.exec(targetId);
|
|
4421
|
+
if (!match) {
|
|
1949
4422
|
return null;
|
|
1950
4423
|
}
|
|
4424
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
1951
4425
|
return parsed;
|
|
1952
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
|
+
};
|
|
1953
4437
|
const extractBodyHtml = (html) => {
|
|
1954
4438
|
const bodyMatch = html.match(/<body\b[^>]*>[\s\S]*<\/body>/i);
|
|
1955
4439
|
if (bodyMatch) {
|
|
@@ -2131,27 +4615,102 @@ const escapeCanvasAttribute = (value) => {
|
|
|
2131
4615
|
.replaceAll("'", "'");
|
|
2132
4616
|
};
|
|
2133
4617
|
const CANVAS_CAPTURE_UNITLESS_STYLES = new Set(["fontWeight", "lineHeight", "opacity", "zIndex"]);
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
|
|
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;
|
|
2137
4655
|
}
|
|
2138
4656
|
if (typeof target?.url === "string" && isHtmlDataUrl(target.url)) {
|
|
2139
4657
|
return target.url;
|
|
2140
4658
|
}
|
|
2141
|
-
|
|
4659
|
+
if (typeof target?.url === "string" && isCanvasExtensionUrl(target.url)) {
|
|
4660
|
+
return target.url;
|
|
4661
|
+
}
|
|
4662
|
+
return getReportedTabUrl(tab) ?? target?.url;
|
|
2142
4663
|
};
|
|
2143
|
-
const resolveReportedTargetTitle = (target,
|
|
2144
|
-
if (typeof
|
|
2145
|
-
return
|
|
4664
|
+
const resolveReportedTargetTitle = (target, tab) => {
|
|
4665
|
+
if (target?.synthetic === true && typeof target.title === "string" && target.title.length > 0) {
|
|
4666
|
+
return target.title;
|
|
2146
4667
|
}
|
|
2147
4668
|
if (typeof target?.url === "string" && isHtmlDataUrl(target.url) && typeof target.title === "string" && target.title.length > 0) {
|
|
2148
4669
|
return target.title;
|
|
2149
4670
|
}
|
|
2150
|
-
|
|
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";
|
|
2151
4701
|
};
|
|
2152
4702
|
const isHtmlDataUrl = (url) => {
|
|
2153
4703
|
return url.startsWith("data:text/html");
|
|
2154
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
|
+
};
|
|
2155
4714
|
const decodeHtmlDataUrl = (url) => {
|
|
2156
4715
|
if (!isHtmlDataUrl(url)) {
|
|
2157
4716
|
return null;
|