sourcemonkey 0.0.0-stage → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- package/skills/SourceMonkey-friction/references/template.md +59 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// The host of a run, or of the spec fixture's test: the real extension
|
|
2
|
+
// runs the scripts, and this fills the host's reports, values and menus
|
|
3
|
+
// (host.ts) from its events (extension-link.ts), and answers the run's
|
|
4
|
+
// commands from it.
|
|
5
|
+
//
|
|
6
|
+
// - The run knows its scripts by their prepared ids; the extension by
|
|
7
|
+
// its own. `setPushed` maps one to the other.
|
|
8
|
+
// - A tab is known by its `chrome.tabs` id once set up (`addTab`).
|
|
9
|
+
// Events from a tab the run is still setting up wait for it; others
|
|
10
|
+
// are not the run's.
|
|
11
|
+
// - A tab's events come over the worker's connection, its navigations
|
|
12
|
+
// over the page's, and nothing orders the two. So an event from a
|
|
13
|
+
// top-frame document the attachment hasn't seen arrive yet waits for
|
|
14
|
+
// it: a report stamped before its document began would not count for
|
|
15
|
+
// it, and a menu command recorded then would be cleared by the
|
|
16
|
+
// navigation that follows. Only a top-frame event's document counts:
|
|
17
|
+
// an iframe's events carry the iframe's document, which no navigation
|
|
18
|
+
// of the tab's would ever match.
|
|
19
|
+
// - The run reports only on its own tabs: events from others, and Log
|
|
20
|
+
// entries made in them, are left out.
|
|
21
|
+
import { SM_ABANDONED_MSG, SM_ERROR_MSG, SM_MANUAL_MSG, SM_START_MSG, SM_SWAPPED_MSG } from '../runtime-messages.js';
|
|
22
|
+
import { evalHookKey } from '../eval-hook.js';
|
|
23
|
+
import { SKIP_REASON_SM_DEV } from '../sm-dev-protocol.js';
|
|
24
|
+
import { remapStackText } from '../stack-link.js';
|
|
25
|
+
import { decodeOwnRefs } from './page-output.js';
|
|
26
|
+
import { HarnessHost, valueCodec, } from './host.js';
|
|
27
|
+
import { frameNodes, matchFrameTrees } from './frames.js';
|
|
28
|
+
// How long a tab's event waits for its document, or for the tab to be
|
|
29
|
+
// set up, before it's handled anyway or dropped.
|
|
30
|
+
const DOCUMENT_WAIT_MS = 3000;
|
|
31
|
+
const SETUP_WAIT_MS = 10_000;
|
|
32
|
+
export class ExtensionHost extends HarnessHost {
|
|
33
|
+
link;
|
|
34
|
+
printLog;
|
|
35
|
+
// The extension's script id by the run's, and back.
|
|
36
|
+
extensionIds = new Map();
|
|
37
|
+
runIds = new Map();
|
|
38
|
+
// Each pushed script's line layout, by the extension's id.
|
|
39
|
+
layouts = new Map();
|
|
40
|
+
tabs = new Map();
|
|
41
|
+
tabsByPage = new Map();
|
|
42
|
+
// Events from tabs the run is setting up, by tab id, with when they came.
|
|
43
|
+
early = new Map();
|
|
44
|
+
// The SourceMonkey-managed scripts the run has listed, by label (`E1`), and
|
|
45
|
+
// each one's label by its id, kept for the run.
|
|
46
|
+
others = new Map();
|
|
47
|
+
otherLabels = new Map();
|
|
48
|
+
// The newest Log entry's time seen for each pushed script, by the
|
|
49
|
+
// extension's id, so catching up after a restart prints only the rest.
|
|
50
|
+
logSeen = new Map();
|
|
51
|
+
// While catchUpLogs reads a Log back: the first entry heard live since.
|
|
52
|
+
liveFrom = new Map();
|
|
53
|
+
// The extension's notification id for each shown one, by request id,
|
|
54
|
+
// to click or close it.
|
|
55
|
+
notificationIds = new Map();
|
|
56
|
+
constructor(link,
|
|
57
|
+
/**
|
|
58
|
+
* Logs a Log entry of one of the run's scripts (`scriptId`: the
|
|
59
|
+
* run's id), with the tab it was made in, if any.
|
|
60
|
+
*/
|
|
61
|
+
printLog) {
|
|
62
|
+
super();
|
|
63
|
+
this.link = link;
|
|
64
|
+
this.printLog = printLog;
|
|
65
|
+
link.onEvent((event) => this.ingest(event));
|
|
66
|
+
}
|
|
67
|
+
/** The run's script `runId` is the extension's `extensionId`. */
|
|
68
|
+
setPushed(runId, extensionId) {
|
|
69
|
+
const before = this.extensionIds.get(runId);
|
|
70
|
+
if (before) {
|
|
71
|
+
this.runIds.delete(before);
|
|
72
|
+
this.layouts.delete(before);
|
|
73
|
+
}
|
|
74
|
+
this.extensionIds.set(runId, extensionId);
|
|
75
|
+
this.runIds.set(extensionId, runId);
|
|
76
|
+
// Entries from before the push (a replaced copy's) are no part of the run.
|
|
77
|
+
this.logSeen.set(extensionId, Math.max(this.logSeen.get(extensionId) ?? 0, Date.now()));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* After the extension's worker restarted, logs the Log entries of the
|
|
81
|
+
* run's scripts made meanwhile, whose events were lost.
|
|
82
|
+
*/
|
|
83
|
+
async catchUpLogs() {
|
|
84
|
+
for (const [extensionId, runId] of [...this.runIds]) {
|
|
85
|
+
// The gap is from the last entry seen to the first the new worker
|
|
86
|
+
// sends live while the Log is read, which is logged already.
|
|
87
|
+
const seen = this.logSeen.get(extensionId) ?? 0;
|
|
88
|
+
this.liveFrom.set(extensionId, Infinity);
|
|
89
|
+
const entries = await this.link.scriptLog(extensionId).catch(() => []);
|
|
90
|
+
const live = this.liveFrom.get(extensionId) ?? Infinity;
|
|
91
|
+
this.liveFrom.delete(extensionId);
|
|
92
|
+
for (const entry of entries)
|
|
93
|
+
if (entry.timestamp > seen && entry.timestamp < live)
|
|
94
|
+
this.logEntry(extensionId, runId, entry);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// A Log entry of one of the run's scripts: logged when it was made in
|
|
98
|
+
// one of the run's tabs, or outside any (a `@require` that failed to
|
|
99
|
+
// load).
|
|
100
|
+
logEntry(extensionId, runId, entry) {
|
|
101
|
+
this.logSeen.set(extensionId, Math.max(this.logSeen.get(extensionId) ?? 0, entry.timestamp));
|
|
102
|
+
const tabId = entry.tabId;
|
|
103
|
+
if (tabId !== undefined && !this.tabs.has(tabId) && !this.early.has(tabId))
|
|
104
|
+
return;
|
|
105
|
+
const page = tabId === undefined ? undefined : this.tabs.get(tabId)?.page;
|
|
106
|
+
this.printLog({ scriptId: runId, name: this.nameOf(runId), level: entry.level, message: decodeOwnRefs(this.remapStack(entry.message)), page });
|
|
107
|
+
}
|
|
108
|
+
/** The extension's id for the run's script `runId`, once pushed. */
|
|
109
|
+
extensionIdOf(runId) {
|
|
110
|
+
return this.extensionIds.get(runId);
|
|
111
|
+
}
|
|
112
|
+
/** How the extension's script `extensionId` maps back to its files, if known. */
|
|
113
|
+
setLayout(extensionId, layout) {
|
|
114
|
+
if (layout)
|
|
115
|
+
this.layouts.set(extensionId, layout);
|
|
116
|
+
else
|
|
117
|
+
this.layouts.delete(extensionId);
|
|
118
|
+
}
|
|
119
|
+
// The extension runs the scripts under its own entry URLs, so their
|
|
120
|
+
// stack lines map by the layouts it reports (`setLayout`). Another
|
|
121
|
+
// manager maps its own.
|
|
122
|
+
remapStack(text) {
|
|
123
|
+
if (this.external)
|
|
124
|
+
return this.external.remap?.(text) ?? text;
|
|
125
|
+
return remapStackText(text, this.layouts.values());
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The run's script whose code one of `urls` is (a stack frame's, or
|
|
129
|
+
* a stack's text), by the entry URLs the extension runs them under.
|
|
130
|
+
*/
|
|
131
|
+
scriptOf(urls) {
|
|
132
|
+
for (const url of urls) {
|
|
133
|
+
for (const [extensionId, layout] of this.layouts) {
|
|
134
|
+
if (url.includes(layout.entryUrl))
|
|
135
|
+
return this.runIds.get(extensionId);
|
|
136
|
+
}
|
|
137
|
+
const external = this.external?.idOf(url);
|
|
138
|
+
if (external !== undefined)
|
|
139
|
+
return this.runIds.get(external);
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Under another manager (Violentmonkey, Tampermonkey), which reports
|
|
145
|
+
* nothing: how its code's URLs name scripts, by the manager's id
|
|
146
|
+
* (`setPushed`), what every URL it runs code under starts with, and
|
|
147
|
+
* how it maps stack lines it moved.
|
|
148
|
+
*/
|
|
149
|
+
external;
|
|
150
|
+
get getsReports() {
|
|
151
|
+
return this.external === undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `page` is the run's tab `tabId`. `loaded`: the tab already shows the
|
|
155
|
+
* document its first events are from (a tab adopted after it loaded),
|
|
156
|
+
* so they needn't wait for a navigation. `scriptsOff`: its claim runs
|
|
157
|
+
* no scripts (opened from a `--noscript` tab), to keep if restored.
|
|
158
|
+
*/
|
|
159
|
+
addTab(page, tabId, loaded = false, scriptsOff = false) {
|
|
160
|
+
const state = {
|
|
161
|
+
page, seenStart: loaded ? -Infinity : page.documentStart(), queue: Promise.resolve(), frames: new Map(), documents: new Map(), unmatched: new Set(),
|
|
162
|
+
};
|
|
163
|
+
this.tabs.set(tabId, state);
|
|
164
|
+
this.tabsByPage.set(page, tabId);
|
|
165
|
+
this.link.holdTab(tabId, scriptsOff);
|
|
166
|
+
const early = this.early.get(tabId);
|
|
167
|
+
this.early.delete(tabId);
|
|
168
|
+
for (const event of early?.events ?? [])
|
|
169
|
+
this.enqueue(state, event);
|
|
170
|
+
}
|
|
171
|
+
/** A tab the run will set up: its events wait for `addTab`. */
|
|
172
|
+
expectTab(tabId) {
|
|
173
|
+
if (!this.tabs.has(tabId) && !this.early.has(tabId))
|
|
174
|
+
this.early.set(tabId, { at: Date.now(), events: [] });
|
|
175
|
+
}
|
|
176
|
+
tabIdOf(page) {
|
|
177
|
+
return this.tabsByPage.get(page);
|
|
178
|
+
}
|
|
179
|
+
async liveFrameDocuments(page) {
|
|
180
|
+
const tabId = this.tabsByPage.get(page);
|
|
181
|
+
const state = tabId === undefined ? undefined : this.tabs.get(tabId);
|
|
182
|
+
if (tabId === undefined || !state)
|
|
183
|
+
return null;
|
|
184
|
+
return this.matchFrames(tabId, state);
|
|
185
|
+
}
|
|
186
|
+
// Lists the tab's frames from the extension and matches them to the
|
|
187
|
+
// page's: the live iframe documents, each with its frame. When the
|
|
188
|
+
// listing fails (the worker restarting, say), the last answer stands:
|
|
189
|
+
// an empty one would show every frame's scripts as gone.
|
|
190
|
+
async matchFrames(tabId, state) {
|
|
191
|
+
const listed = await this.link.allFrames(tabId).catch(() => null);
|
|
192
|
+
if (!listed)
|
|
193
|
+
return state.documents;
|
|
194
|
+
const nodes = frameNodes(state.page.page, this.frameIds).map((n) => ({ key: n.frame, parent: n.parent === null ? null : n.frame.parentFrame(), url: n.url }));
|
|
195
|
+
const matched = new Map([...matchFrameTrees(listed, nodes, state.frames)].filter((e) => e[1] !== null));
|
|
196
|
+
// A frame left out this time only because its URL is between two
|
|
197
|
+
// (mid-navigation) keeps its match, so same-URL siblings don't swap.
|
|
198
|
+
const taken = new Set(matched.values());
|
|
199
|
+
for (const f of listed) {
|
|
200
|
+
const was = state.frames.get(f.frameId);
|
|
201
|
+
if (was && !matched.has(f.frameId) && !was.isDetached() && !taken.has(was))
|
|
202
|
+
matched.set(f.frameId, was);
|
|
203
|
+
}
|
|
204
|
+
state.frames = matched;
|
|
205
|
+
state.topDocument = listed.find((f) => f.frameId === 0)?.documentId;
|
|
206
|
+
const live = new Map();
|
|
207
|
+
for (const f of listed) {
|
|
208
|
+
const frame = matched.get(f.frameId);
|
|
209
|
+
if (frame)
|
|
210
|
+
live.set(f.documentId, frame);
|
|
211
|
+
}
|
|
212
|
+
state.documents = live;
|
|
213
|
+
return live;
|
|
214
|
+
}
|
|
215
|
+
// The frame number of the iframe document an event came from, for
|
|
216
|
+
// its line and menu label. A document new since the last match lists
|
|
217
|
+
// the frames again, and once more after a moment: Playwright can hear
|
|
218
|
+
// of a frame after the extension does. One that still matches none
|
|
219
|
+
// isn't looked for again.
|
|
220
|
+
async frameNumberOf(tabId, state, documentId) {
|
|
221
|
+
const known = state.documents.get(documentId);
|
|
222
|
+
if (known && !known.isDetached())
|
|
223
|
+
return this.frameIds.number(known);
|
|
224
|
+
if (state.unmatched.has(documentId) || documentId === state.topDocument)
|
|
225
|
+
return undefined;
|
|
226
|
+
for (const wait of [0, 200]) {
|
|
227
|
+
if (wait)
|
|
228
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
229
|
+
const frame = (await this.matchFrames(tabId, state)).get(documentId);
|
|
230
|
+
if (frame)
|
|
231
|
+
return this.frameIds.number(frame);
|
|
232
|
+
// The top frame's, which names no frame.
|
|
233
|
+
if (documentId === state.topDocument)
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
state.unmatched.add(documentId);
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
detachPage(page) {
|
|
240
|
+
const tabId = this.tabsByPage.get(page);
|
|
241
|
+
if (tabId !== undefined) {
|
|
242
|
+
this.tabs.delete(tabId);
|
|
243
|
+
this.link.forgetTab(tabId);
|
|
244
|
+
}
|
|
245
|
+
this.tabsByPage.delete(page);
|
|
246
|
+
super.detachPage(page);
|
|
247
|
+
}
|
|
248
|
+
// The extension gives a test collection's scripts their run's hook,
|
|
249
|
+
// under the script's id there.
|
|
250
|
+
evalHook(scriptId) {
|
|
251
|
+
const id = this.extensionIds.get(scriptId);
|
|
252
|
+
return id ? { key: evalHookKey(this.link.runId), id } : null;
|
|
253
|
+
}
|
|
254
|
+
async valuesOf(scriptId) {
|
|
255
|
+
const id = this.extensionIds.get(scriptId);
|
|
256
|
+
return id ? this.extensionValues(id) : {};
|
|
257
|
+
}
|
|
258
|
+
// Into the extension, and the host's own copy, which `getValue` reads
|
|
259
|
+
// without a round trip (the fixture's `gm.get`). The tabs' own writes
|
|
260
|
+
// reach that copy as events.
|
|
261
|
+
async setValues(scriptId, values) {
|
|
262
|
+
const id = this.extensionIds.get(scriptId);
|
|
263
|
+
if (!id)
|
|
264
|
+
throw new Error('Internal error: The script was never pushed into the extension');
|
|
265
|
+
await this.writeExtensionValues(id, values);
|
|
266
|
+
await super.setValues(scriptId, values);
|
|
267
|
+
}
|
|
268
|
+
// A script's values by the extension's id, decoded.
|
|
269
|
+
async extensionValues(id) {
|
|
270
|
+
const { decodeValue } = valueCodec();
|
|
271
|
+
const wire = await this.link.values(id);
|
|
272
|
+
return Object.fromEntries(Object.entries(wire).map(([k, v]) => [k, decodeValue(v)]));
|
|
273
|
+
}
|
|
274
|
+
async writeExtensionValues(id, values) {
|
|
275
|
+
const { encodeValue } = valueCodec();
|
|
276
|
+
await this.link.writeValues(id, Object.entries(values).map(([key, v]) => (v === undefined ? { key, deleted: true } : { key, value: encodeValue(v) })));
|
|
277
|
+
}
|
|
278
|
+
// One object per script, brought up to date, so one picked earlier
|
|
279
|
+
// (`scripts E1`) shows what the latest listing found.
|
|
280
|
+
other(id, label, facts) {
|
|
281
|
+
const other = this.others.get(label) ?? {
|
|
282
|
+
label, name: '', collection: '', status: '',
|
|
283
|
+
values: () => this.extensionValues(id),
|
|
284
|
+
setValues: (values) => this.writeExtensionValues(id, values),
|
|
285
|
+
};
|
|
286
|
+
Object.assign(other, facts);
|
|
287
|
+
this.others.set(label, other);
|
|
288
|
+
return other;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The SourceMonkey-managed scripts, for the commands (`ConsoleContext.others`).
|
|
292
|
+
* `guarded`: `set` on one changes the person's real data.
|
|
293
|
+
*/
|
|
294
|
+
otherScripts(guarded) {
|
|
295
|
+
return {
|
|
296
|
+
guarded,
|
|
297
|
+
byLabel: (label) => this.others.get(label),
|
|
298
|
+
onTab: async (tab) => {
|
|
299
|
+
const tabId = this.tabsByPage.get(tab.link);
|
|
300
|
+
if (tabId === undefined)
|
|
301
|
+
return [];
|
|
302
|
+
const rows = await this.link.scriptsOnTab(tabId);
|
|
303
|
+
return rows.filter((row) => !this.runIds.has(row.id)).map((row) => {
|
|
304
|
+
// SourceMonkey's own number for the script (`E12`), the same
|
|
305
|
+
// in every run. An extension before protocol 1.1 numbers none,
|
|
306
|
+
// and gets the run's own numbering, in the order first seen.
|
|
307
|
+
let label = row.number !== undefined ? `E${row.number}` : this.otherLabels.get(row.id);
|
|
308
|
+
if (!label)
|
|
309
|
+
label = `E${this.otherLabels.size + 1}`;
|
|
310
|
+
this.otherLabels.set(row.id, label);
|
|
311
|
+
return this.other(row.id, label, { name: row.name, collection: row.collection, status: row.status });
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
installed: (id, label, name, collection) => this.other(id, label, { name, collection }),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
async answerConnectPrompt(promptId, choice) {
|
|
318
|
+
return this.link.answerConnectPrompt(promptId, choice);
|
|
319
|
+
}
|
|
320
|
+
async clickNotification(requestId) {
|
|
321
|
+
const id = this.notificationIds.get(requestId);
|
|
322
|
+
return id ? this.link.clickNotification(id) : false;
|
|
323
|
+
}
|
|
324
|
+
async closeNotification(requestId) {
|
|
325
|
+
const id = this.notificationIds.get(requestId);
|
|
326
|
+
return id ? this.link.closeNotification(id) : false;
|
|
327
|
+
}
|
|
328
|
+
async runManualScript(page, scriptId) {
|
|
329
|
+
const tabId = this.tabsByPage.get(page);
|
|
330
|
+
const id = this.extensionIds.get(scriptId);
|
|
331
|
+
if (tabId === undefined || !id)
|
|
332
|
+
throw new Error('Internal error: sm-dev has no tab or script to run it in');
|
|
333
|
+
await this.link.runManualScript(tabId, id);
|
|
334
|
+
}
|
|
335
|
+
async fireMenuEntry(entry) {
|
|
336
|
+
const page = this.pageOf(entry);
|
|
337
|
+
const tabId = page && this.tabsByPage.get(page);
|
|
338
|
+
const id = this.extensionIds.get(entry.scriptId);
|
|
339
|
+
if (tabId === undefined || !id)
|
|
340
|
+
return false;
|
|
341
|
+
return this.link.fireMenu(tabId, id, entry.commandId, entry.documentId);
|
|
342
|
+
}
|
|
343
|
+
ingest(event) {
|
|
344
|
+
if (event.kind === 'log') {
|
|
345
|
+
const runId = this.runIds.get(event.scriptId);
|
|
346
|
+
if (!runId)
|
|
347
|
+
return;
|
|
348
|
+
const live = this.liveFrom.get(event.scriptId);
|
|
349
|
+
if (live !== undefined)
|
|
350
|
+
this.liveFrom.set(event.scriptId, Math.min(live, event.entry.timestamp));
|
|
351
|
+
// A report's own entry (started, threw, skipped) prints as the
|
|
352
|
+
// report, but counts as seen.
|
|
353
|
+
if (event.fromReport) {
|
|
354
|
+
this.logSeen.set(event.scriptId, Math.max(this.logSeen.get(event.scriptId) ?? 0, event.entry.timestamp));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.logEntry(event.scriptId, runId, event.entry);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (event.kind === 'tab-opened') {
|
|
361
|
+
this.recordOpenedTab(event);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (event.kind === 'tab-closed') {
|
|
365
|
+
const runId = this.runIds.get(event.scriptId);
|
|
366
|
+
const record = runId ? this.reports.tabs.find((t) => t.scriptId === runId && t.requestId === event.requestId) : undefined;
|
|
367
|
+
if (record)
|
|
368
|
+
record.closed = true;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
// A write with no tab is the run's own `set` (or another run's),
|
|
372
|
+
// which says so itself.
|
|
373
|
+
if (event.kind === 'values' && event.tabId === undefined)
|
|
374
|
+
return;
|
|
375
|
+
const tabId = event.tabId;
|
|
376
|
+
if (!this.runIds.has(event.scriptId))
|
|
377
|
+
return;
|
|
378
|
+
const state = this.tabs.get(tabId);
|
|
379
|
+
if (state) {
|
|
380
|
+
this.enqueue(state, event);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const early = this.early.get(tabId);
|
|
384
|
+
if (!early)
|
|
385
|
+
return;
|
|
386
|
+
// A tab whose setup never finished doesn't hold events for good.
|
|
387
|
+
if (Date.now() - early.at > SETUP_WAIT_MS)
|
|
388
|
+
this.early.delete(tabId);
|
|
389
|
+
else
|
|
390
|
+
early.events.push(event);
|
|
391
|
+
}
|
|
392
|
+
enqueue(state, event) {
|
|
393
|
+
state.queue = state.queue.then(() => this.handleTabEvent(state, event)).catch(() => undefined);
|
|
394
|
+
}
|
|
395
|
+
async handleTabEvent(state, event) {
|
|
396
|
+
if ((event.kind === 'report' || event.kind === 'menu') && event.topFrame !== false && event.documentId && event.documentId !== state.documentId) {
|
|
397
|
+
// A new document: wait for the attachment to have entered one
|
|
398
|
+
// after the last, so what this records counts for it.
|
|
399
|
+
const deadline = Date.now() + DOCUMENT_WAIT_MS;
|
|
400
|
+
while (state.page.documentStart() <= state.seenStart && Date.now() < deadline) {
|
|
401
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
402
|
+
}
|
|
403
|
+
state.documentId = event.documentId;
|
|
404
|
+
state.seenStart = state.page.documentStart();
|
|
405
|
+
}
|
|
406
|
+
const runId = this.runIds.get(event.scriptId);
|
|
407
|
+
if (!runId || !this.tabs.has(event.tabId))
|
|
408
|
+
return;
|
|
409
|
+
// What came from an iframe names its frame. A report or menu event
|
|
410
|
+
// says whether it did; the others name their document, which may
|
|
411
|
+
// be the top frame's. Looked up only for
|
|
412
|
+
// what's recorded, after the events that are dropped.
|
|
413
|
+
const documentId = 'documentId' in event ? event.documentId : undefined;
|
|
414
|
+
const inFrame = (event.kind === 'report' || event.kind === 'menu') ? event.topFrame === false
|
|
415
|
+
: documentId !== undefined && documentId !== state.documentId;
|
|
416
|
+
const frameNumber = async () => {
|
|
417
|
+
const n = inFrame && documentId ? await this.frameNumberOf(event.tabId, state, documentId) : undefined;
|
|
418
|
+
return n === undefined ? {} : { frameNumber: n };
|
|
419
|
+
};
|
|
420
|
+
if (event.kind === 'request' || event.kind === 'download' || event.kind === 'notification') {
|
|
421
|
+
// Its frame is looked up only for the event that makes its record.
|
|
422
|
+
await this.recordActivity(runId, state.page, event, frameNumber);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (event.kind === 'connect-prompt') {
|
|
426
|
+
this.recordConnectPrompt(runId, state.page, event);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (event.kind === 'values') {
|
|
430
|
+
this.recordValueWrites(state.page, runId, event.writes, await frameNumber());
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (event.kind === 'menu') {
|
|
434
|
+
if (event.op === 'add')
|
|
435
|
+
this.registerMenu(state.page, runId, event.commandId, event.name ?? '', event.documentId || undefined, inFrame ? await frameNumber() : undefined);
|
|
436
|
+
else
|
|
437
|
+
this.unregisterMenu(state.page, runId, event.commandId, event.documentId || undefined);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// A tab with its scripts turned off (`--noscript`) skips them on
|
|
441
|
+
// each load, as asked: nothing to say.
|
|
442
|
+
if (event.type === SM_ABANDONED_MSG && event.reason === SKIP_REASON_SM_DEV)
|
|
443
|
+
return;
|
|
444
|
+
const fields = {
|
|
445
|
+
scriptId: runId, name: event.name, url: event.url,
|
|
446
|
+
...(inFrame ? { frame: true } : {}),
|
|
447
|
+
...(event.documentId ? { documentId: event.documentId } : {}),
|
|
448
|
+
...await frameNumber(),
|
|
449
|
+
};
|
|
450
|
+
if (event.type === SM_START_MSG || event.type === SM_SWAPPED_MSG) {
|
|
451
|
+
this.recordStart(state.page, fields);
|
|
452
|
+
}
|
|
453
|
+
else if (event.type === SM_ERROR_MSG) {
|
|
454
|
+
this.recordError(state.page, { ...fields, message: event.message ?? '', stack: event.stack ?? '' });
|
|
455
|
+
}
|
|
456
|
+
else if (event.type === SM_MANUAL_MSG) {
|
|
457
|
+
this.recordManual(state.page, fields);
|
|
458
|
+
}
|
|
459
|
+
else if (event.type === SM_ABANDONED_MSG) {
|
|
460
|
+
// With no decision, the why (`detail`, from protocol 1.4) rather
|
|
461
|
+
// than its token.
|
|
462
|
+
this.recordSkip(state.page, { ...fields, reason: event.detail?.replace(/\.+$/, '') || event.reason || 'skip' });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// A `GM_openInTab` of one of the run's scripts, from one of its tabs.
|
|
466
|
+
recordOpenedTab(event) {
|
|
467
|
+
const runId = event.scriptId === undefined ? undefined : this.runIds.get(event.scriptId);
|
|
468
|
+
const from = this.tabs.get(event.sourceTabId)?.page;
|
|
469
|
+
if (!runId || !from)
|
|
470
|
+
return;
|
|
471
|
+
const record = {
|
|
472
|
+
scriptId: runId, requestId: event.requestId ?? '', url: event.url, active: event.active === true, at: event.at, closed: false,
|
|
473
|
+
};
|
|
474
|
+
this.reports.tabs.push(record);
|
|
475
|
+
this.recordPages.set(record, from);
|
|
476
|
+
this.notify('tab', record);
|
|
477
|
+
}
|
|
478
|
+
// A connection prompt for one of the run's scripts: one record, which
|
|
479
|
+
// the answer fills in.
|
|
480
|
+
recordConnectPrompt(runId, page, event) {
|
|
481
|
+
let record = this.reports.connectPrompts.find((r) => r.promptId === event.promptId);
|
|
482
|
+
if (event.op === 'shown') {
|
|
483
|
+
if (record)
|
|
484
|
+
return;
|
|
485
|
+
record = {
|
|
486
|
+
scriptId: runId, name: this.nameOf(runId), promptId: event.promptId, host: event.host,
|
|
487
|
+
url: event.url ?? '', method: event.method ?? 'GET', at: event.at,
|
|
488
|
+
};
|
|
489
|
+
this.reports.connectPrompts.push(record);
|
|
490
|
+
this.recordPages.set(record, page);
|
|
491
|
+
this.notify('connect-prompt', record);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (!record || record.answer || record.closed)
|
|
495
|
+
return;
|
|
496
|
+
if (event.op === 'closed') {
|
|
497
|
+
record.closed = true;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (!event.answer)
|
|
501
|
+
return;
|
|
502
|
+
record.answer = event.answer;
|
|
503
|
+
this.notify('connect-prompt', record);
|
|
504
|
+
}
|
|
505
|
+
// A request, download or notification, recorded as the harness host
|
|
506
|
+
// records its own: one record each, filled in as it settles, and
|
|
507
|
+
// announced once it has (a notification as it shows).
|
|
508
|
+
async recordActivity(runId, page, event, frame) {
|
|
509
|
+
if (event.kind === 'request') {
|
|
510
|
+
let record = this.reports.requests.find((r) => r.id === event.id && r.scriptId === runId);
|
|
511
|
+
if (!record) {
|
|
512
|
+
record = { scriptId: runId, ...await frame(), id: event.id, url: event.url, method: event.method, at: event.at };
|
|
513
|
+
this.reports.requests.push(record);
|
|
514
|
+
this.recordPages.set(record, page);
|
|
515
|
+
}
|
|
516
|
+
if (!event.outcome)
|
|
517
|
+
return;
|
|
518
|
+
Object.assign(record, { outcome: event.outcome }, event.status !== undefined ? { status: event.status } : {}, event.error ? { error: event.error } : {});
|
|
519
|
+
this.notify('request', record);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (event.kind === 'download') {
|
|
523
|
+
let record = this.reports.downloads.find((r) => r.requestId === event.requestId && r.scriptId === runId);
|
|
524
|
+
if (!record) {
|
|
525
|
+
record = { scriptId: runId, ...await frame(), requestId: event.requestId, url: event.url, name: event.name, at: event.at };
|
|
526
|
+
this.reports.downloads.push(record);
|
|
527
|
+
this.recordPages.set(record, page);
|
|
528
|
+
}
|
|
529
|
+
if (!event.outcome)
|
|
530
|
+
return;
|
|
531
|
+
Object.assign(record, { outcome: event.outcome }, event.filename ? { filename: event.filename } : {}, event.error ? { error: event.error } : {});
|
|
532
|
+
this.notify('download', record);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (event.op === 'shown') {
|
|
536
|
+
const record = {
|
|
537
|
+
scriptId: runId, ...await frame(), requestId: event.requestId, text: event.text ?? '', title: event.title ?? '', at: event.at,
|
|
538
|
+
};
|
|
539
|
+
if (event.notificationId)
|
|
540
|
+
this.notificationIds.set(event.requestId, event.notificationId);
|
|
541
|
+
this.reports.notifications.push(record);
|
|
542
|
+
this.recordPages.set(record, page);
|
|
543
|
+
this.notify('notification', record);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const record = this.reports.notifications.find((n) => n.requestId === event.requestId && n.scriptId === runId);
|
|
547
|
+
if (record)
|
|
548
|
+
record.event = event.op;
|
|
549
|
+
if (event.op === 'closed')
|
|
550
|
+
this.notificationIds.delete(event.requestId);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { BrowserContext } from '@playwright/test';
|
|
2
|
+
import { type ManagerName } from './managers.js';
|
|
3
|
+
export declare const EXTENSION_DIR: string;
|
|
4
|
+
export interface ExtensionLaunchOptions {
|
|
5
|
+
/** The debugging port, which must be free. Chrome picks one when not given. */
|
|
6
|
+
port?: number;
|
|
7
|
+
/** A visible window. Hidden otherwise. */
|
|
8
|
+
headed?: boolean;
|
|
9
|
+
/** Without sound (the default), or with it when false. */
|
|
10
|
+
muted?: boolean;
|
|
11
|
+
/** More Chrome flags. */
|
|
12
|
+
args?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Chrome's own download manager saves downloads here, rather than
|
|
15
|
+
* Playwright's, which hides their real names and paths from
|
|
16
|
+
* `chrome.downloads`.
|
|
17
|
+
*/
|
|
18
|
+
downloadsDir?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Another Playwright client drives the browser, over `endpoint`. The
|
|
21
|
+
* launch's own client then leaves the pages' dialogs to it; otherwise
|
|
22
|
+
* it would dismiss them first.
|
|
23
|
+
*/
|
|
24
|
+
connectedElsewhere?: boolean;
|
|
25
|
+
/** The manager to load in place of SourceMonkey, already fetched. */
|
|
26
|
+
manager?: ManagerName;
|
|
27
|
+
/**
|
|
28
|
+
* Keep Chrome's back/forward cache, which Playwright turns off by
|
|
29
|
+
* default. Tests of pages restored from it ask for it.
|
|
30
|
+
*/
|
|
31
|
+
backForwardCache?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface LaunchedExtensionBrowser {
|
|
34
|
+
/** The launched browser's CDP endpoint, for a client to connect to. */
|
|
35
|
+
endpoint: string;
|
|
36
|
+
/** The launch's own Playwright context: the browser's default one. */
|
|
37
|
+
context: BrowserContext;
|
|
38
|
+
/** The temp profile, deleted by `close`. */
|
|
39
|
+
profile: string;
|
|
40
|
+
/** Closes the browser and deletes its profile. */
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Launches Chromium with SourceMonkey loaded, or `options.manager`.
|
|
45
|
+
* Throws an error saying why when it can't. It has one blank tab,
|
|
46
|
+
* opened after the warm-up (see `warmUp`), for a run's first tab.
|
|
47
|
+
*/
|
|
48
|
+
export declare function launchExtensionBrowser(options?: ExtensionLaunchOptions): Promise<LaunchedExtensionBrowser>;
|
|
49
|
+
/**
|
|
50
|
+
* Installs a script and removes it again, before the run opens its tab.
|
|
51
|
+
*
|
|
52
|
+
* - In a fresh profile, a tab opened before the extension's first script
|
|
53
|
+
* install can't message the extension, even after a reload: a script
|
|
54
|
+
* that runs there reports that it couldn't read its stored GM values.
|
|
55
|
+
* Only a tab in a new renderer process (another site) recovers.
|
|
56
|
+
* - The run pushes its scripts after its tab is open, so it needs a tab
|
|
57
|
+
* opened after this. See TODO.md.
|
|
58
|
+
*/
|
|
59
|
+
export declare function warmUp(context: BrowserContext): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Readies a new profile for SourceMonkey (Preferences, first run done),
|
|
62
|
+
* for a browser started with `extensionArgs`. Also for a test's own
|
|
63
|
+
* browser, launched outside Playwright.
|
|
64
|
+
*/
|
|
65
|
+
export declare function prepareProfile(profile: string, downloadsDir?: string): Promise<void>;
|
|
66
|
+
/** The Chrome flags that load the package's SourceMonkey (or the extension in `dir`), and only it. */
|
|
67
|
+
export declare function extensionArgs(dir?: string): string[];
|