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,71 @@
|
|
|
1
|
+
import type { CDPSession, Frame, Page } from '@playwright/test';
|
|
2
|
+
/** An execution context: one world in one frame. */
|
|
3
|
+
export interface ContextInfo {
|
|
4
|
+
/** The CDP id of its frame. */
|
|
5
|
+
frameId: string;
|
|
6
|
+
/** The world's name: '' for the page's own. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The frame's own (main) world. */
|
|
9
|
+
isDefault: boolean;
|
|
10
|
+
/** For an extension's world, its origin (`chrome-extension://<id>`). */
|
|
11
|
+
origin: string;
|
|
12
|
+
}
|
|
13
|
+
/** One session: the page's, or an out-of-process iframe's. */
|
|
14
|
+
export interface FrameSession {
|
|
15
|
+
cdp: CDPSession;
|
|
16
|
+
/** The frame it was made for: the page's main frame, or the iframe. */
|
|
17
|
+
root: Frame;
|
|
18
|
+
/** Its execution contexts by id, as `Runtime` reports them. */
|
|
19
|
+
contexts: Map<number, ContextInfo>;
|
|
20
|
+
/** Its CDP target's id, once asked for (`targetIdOf`). */
|
|
21
|
+
targetId?: Promise<string | undefined>;
|
|
22
|
+
}
|
|
23
|
+
/** What a session's setup can listen for besides its contexts. */
|
|
24
|
+
export type SessionSetup = (session: FrameSession) => void | Promise<void>;
|
|
25
|
+
export declare class FrameSessions {
|
|
26
|
+
private readonly page;
|
|
27
|
+
private readonly setup;
|
|
28
|
+
private readonly sessions;
|
|
29
|
+
private readonly inParent;
|
|
30
|
+
private disposed;
|
|
31
|
+
private generation;
|
|
32
|
+
private readonly trees;
|
|
33
|
+
private readonly onAttached;
|
|
34
|
+
private readonly onNavigated;
|
|
35
|
+
private readonly onDetached;
|
|
36
|
+
private constructor();
|
|
37
|
+
/**
|
|
38
|
+
* Sessions for `page` and each out-of-process iframe it has or gets,
|
|
39
|
+
* each set up with `setup` before `Runtime` is on (so it hears the
|
|
40
|
+
* contexts that exist already). Null when the page has closed.
|
|
41
|
+
*/
|
|
42
|
+
static attach(page: Page, setup?: SessionSetup): Promise<FrameSessions | null>;
|
|
43
|
+
/** The page's own session. */
|
|
44
|
+
pageSession(): Promise<FrameSession | null>;
|
|
45
|
+
/** Every session open now. */
|
|
46
|
+
all(): Promise<FrameSession[]>;
|
|
47
|
+
/**
|
|
48
|
+
* The session `frame` runs in, and its CDP frame id there, or null
|
|
49
|
+
* when it can't be found (detached, or between documents).
|
|
50
|
+
*/
|
|
51
|
+
locate(frame: Frame): Promise<{
|
|
52
|
+
session: FrameSession;
|
|
53
|
+
frameId: string;
|
|
54
|
+
} | null>;
|
|
55
|
+
/**
|
|
56
|
+
* The CDP target id of an iframe Chrome runs in a process of its own,
|
|
57
|
+
* which a raw CDP client attaches to (`Target.attachToTarget`).
|
|
58
|
+
* Undefined for one in its parent's process, which is part of the
|
|
59
|
+
* page's target.
|
|
60
|
+
*/
|
|
61
|
+
targetIdOf(frame: Frame): Promise<string | undefined>;
|
|
62
|
+
/** The frame a session's CDP frame id names, or undefined. */
|
|
63
|
+
frameOf(session: FrameSession, frameId: string): Promise<Frame | undefined>;
|
|
64
|
+
/** Detach all its sessions. */
|
|
65
|
+
dispose(): Promise<void>;
|
|
66
|
+
private childNode;
|
|
67
|
+
private treeOf;
|
|
68
|
+
private close;
|
|
69
|
+
private sessionFor;
|
|
70
|
+
private open;
|
|
71
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// A tab's DevTools (CDP) sessions, one per process its frames run in:
|
|
2
|
+
// the page's own, and one for each iframe Chrome runs in a process of
|
|
3
|
+
// its own (an out-of-process iframe): one from another site than its
|
|
4
|
+
// parent (another registrable domain: not just another subdomain or
|
|
5
|
+
// port), with site isolation on, as it is by default. The page's session
|
|
6
|
+
// never sees inside those, so evaluating in such a frame, or telling
|
|
7
|
+
// which frame a console line came from, needs the frame's own session.
|
|
8
|
+
// See docs/sm-dev-frames.md.
|
|
9
|
+
//
|
|
10
|
+
// - Each session has `Runtime` on, and keeps its execution contexts
|
|
11
|
+
// (each world of each frame it covers).
|
|
12
|
+
// - A session's frames are known by CDP frame ids; sm-dev knows frames
|
|
13
|
+
// by Playwright's `Frame`. A session's root is exact (the page, or
|
|
14
|
+
// the frame the session was made for). Below it, a frame is matched
|
|
15
|
+
// by its parent and URL, as `matchFrameTrees` does for the extension.
|
|
16
|
+
export class FrameSessions {
|
|
17
|
+
page;
|
|
18
|
+
setup;
|
|
19
|
+
sessions = new Map();
|
|
20
|
+
// Frames tried and found to be in their parent's process, by the URL
|
|
21
|
+
// they had: a navigation can move a frame into a process of its own.
|
|
22
|
+
inParent = new WeakMap();
|
|
23
|
+
disposed = false;
|
|
24
|
+
// Bumped on each frame change, which can change a session's frame tree.
|
|
25
|
+
generation = 0;
|
|
26
|
+
trees = new WeakMap();
|
|
27
|
+
onAttached = (frame) => {
|
|
28
|
+
this.generation++;
|
|
29
|
+
if (frame !== this.page.mainFrame())
|
|
30
|
+
void this.sessionFor(frame);
|
|
31
|
+
};
|
|
32
|
+
// A navigation can move a frame into a process of its own or back into
|
|
33
|
+
// its parent's, which ends its own target: its session is opened again.
|
|
34
|
+
onNavigated = (frame) => {
|
|
35
|
+
this.generation++;
|
|
36
|
+
if (frame === this.page.mainFrame())
|
|
37
|
+
return;
|
|
38
|
+
this.close(frame);
|
|
39
|
+
void this.sessionFor(frame);
|
|
40
|
+
};
|
|
41
|
+
onDetached = (frame) => {
|
|
42
|
+
this.generation++;
|
|
43
|
+
this.close(frame);
|
|
44
|
+
};
|
|
45
|
+
constructor(page, setup) {
|
|
46
|
+
this.page = page;
|
|
47
|
+
this.setup = setup;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Sessions for `page` and each out-of-process iframe it has or gets,
|
|
51
|
+
* each set up with `setup` before `Runtime` is on (so it hears the
|
|
52
|
+
* contexts that exist already). Null when the page has closed.
|
|
53
|
+
*/
|
|
54
|
+
static async attach(page, setup = () => undefined) {
|
|
55
|
+
const sessions = new FrameSessions(page, setup);
|
|
56
|
+
const main = await sessions.open(page.mainFrame());
|
|
57
|
+
if (typeof main !== 'object' || main === null)
|
|
58
|
+
return null;
|
|
59
|
+
page.on('frameattached', sessions.onAttached);
|
|
60
|
+
page.on('framenavigated', sessions.onNavigated);
|
|
61
|
+
page.on('framedetached', sessions.onDetached);
|
|
62
|
+
for (const frame of page.frames())
|
|
63
|
+
sessions.onAttached(frame);
|
|
64
|
+
return sessions;
|
|
65
|
+
}
|
|
66
|
+
/** The page's own session. */
|
|
67
|
+
async pageSession() {
|
|
68
|
+
return this.sessions.get(this.page.mainFrame()) ?? null;
|
|
69
|
+
}
|
|
70
|
+
/** Every session open now. */
|
|
71
|
+
async all() {
|
|
72
|
+
return (await Promise.all(this.sessions.values())).filter((s) => s !== null);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The session `frame` runs in, and its CDP frame id there, or null
|
|
76
|
+
* when it can't be found (detached, or between documents).
|
|
77
|
+
*/
|
|
78
|
+
async locate(frame) {
|
|
79
|
+
if (frame.isDetached())
|
|
80
|
+
return null;
|
|
81
|
+
// The nearest of the frame and its ancestors with a session.
|
|
82
|
+
const path = [];
|
|
83
|
+
let session = null;
|
|
84
|
+
for (let f = frame; f; f = f.parentFrame()) {
|
|
85
|
+
session = f === this.page.mainFrame() ? await this.pageSession() : await this.sessionFor(f);
|
|
86
|
+
if (session)
|
|
87
|
+
break;
|
|
88
|
+
path.unshift(f);
|
|
89
|
+
}
|
|
90
|
+
if (!session)
|
|
91
|
+
return null;
|
|
92
|
+
const tree = await this.treeOf(session);
|
|
93
|
+
if (!tree)
|
|
94
|
+
return null;
|
|
95
|
+
let node = tree;
|
|
96
|
+
let at = session.root;
|
|
97
|
+
for (const step of path) {
|
|
98
|
+
const child = await this.childNode(node, at, step);
|
|
99
|
+
if (!child)
|
|
100
|
+
return null;
|
|
101
|
+
node = child;
|
|
102
|
+
at = step;
|
|
103
|
+
}
|
|
104
|
+
return { session, frameId: node.frame.id };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The CDP target id of an iframe Chrome runs in a process of its own,
|
|
108
|
+
* which a raw CDP client attaches to (`Target.attachToTarget`).
|
|
109
|
+
* Undefined for one in its parent's process, which is part of the
|
|
110
|
+
* page's target.
|
|
111
|
+
*/
|
|
112
|
+
async targetIdOf(frame) {
|
|
113
|
+
if (frame === this.page.mainFrame())
|
|
114
|
+
return undefined;
|
|
115
|
+
const session = await this.sessionFor(frame);
|
|
116
|
+
if (!session)
|
|
117
|
+
return undefined;
|
|
118
|
+
session.targetId ??= session.cdp.send('Target.getTargetInfo').then((r) => r.targetInfo.targetId, () => undefined);
|
|
119
|
+
return session.targetId;
|
|
120
|
+
}
|
|
121
|
+
/** The frame a session's CDP frame id names, or undefined. */
|
|
122
|
+
async frameOf(session, frameId) {
|
|
123
|
+
const tree = await this.treeOf(session);
|
|
124
|
+
if (!tree)
|
|
125
|
+
return undefined;
|
|
126
|
+
const walk = async (node, frame) => {
|
|
127
|
+
if (node.frame.id === frameId)
|
|
128
|
+
return frame;
|
|
129
|
+
for (const child of frame.childFrames()) {
|
|
130
|
+
const childNode = await this.childNode(node, frame, child);
|
|
131
|
+
if (!childNode)
|
|
132
|
+
continue;
|
|
133
|
+
const found = await walk(childNode, child);
|
|
134
|
+
if (found)
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
};
|
|
139
|
+
return walk(tree, session.root);
|
|
140
|
+
}
|
|
141
|
+
/** Detach all its sessions. */
|
|
142
|
+
async dispose() {
|
|
143
|
+
this.disposed = true;
|
|
144
|
+
this.page.off('frameattached', this.onAttached);
|
|
145
|
+
this.page.off('framenavigated', this.onNavigated);
|
|
146
|
+
this.page.off('framedetached', this.onDetached);
|
|
147
|
+
const all = await this.all();
|
|
148
|
+
await Promise.all(all.map((s) => s.cdp.detach().catch(() => undefined)));
|
|
149
|
+
}
|
|
150
|
+
// The CDP node of `child` (a Playwright child of `frame`) among the
|
|
151
|
+
// children of `node` (`frame`'s CDP node): one in its own process
|
|
152
|
+
// isn't in this tree. Siblings with one URL go by order.
|
|
153
|
+
async childNode(node, frame, child) {
|
|
154
|
+
const local = [];
|
|
155
|
+
for (const c of frame.childFrames())
|
|
156
|
+
if (!await this.sessionFor(c))
|
|
157
|
+
local.push(c);
|
|
158
|
+
const url = child.url();
|
|
159
|
+
const same = local.filter((c) => c.url() === url);
|
|
160
|
+
const nodes = (node.childFrames ?? []).filter((n) => n.frame.url + (n.frame.urlFragment ?? '') === url || n.frame.url === url);
|
|
161
|
+
return nodes[same.indexOf(child)];
|
|
162
|
+
}
|
|
163
|
+
// A session's CDP frame tree, read once until the frames change: a
|
|
164
|
+
// console line from each frame asks for it.
|
|
165
|
+
treeOf(session) {
|
|
166
|
+
const known = this.trees.get(session);
|
|
167
|
+
if (known?.generation === this.generation)
|
|
168
|
+
return known.tree;
|
|
169
|
+
const tree = session.cdp.send('Page.getFrameTree').then((r) => r.frameTree, () => null);
|
|
170
|
+
this.trees.set(session, { generation: this.generation, tree });
|
|
171
|
+
return tree;
|
|
172
|
+
}
|
|
173
|
+
// Ends an iframe's own session, if it has one.
|
|
174
|
+
close(frame) {
|
|
175
|
+
const known = this.sessions.get(frame);
|
|
176
|
+
if (!known)
|
|
177
|
+
return;
|
|
178
|
+
this.sessions.delete(frame);
|
|
179
|
+
void known.then((session) => session?.cdp.detach()).catch(() => undefined);
|
|
180
|
+
}
|
|
181
|
+
// A frame's own session, opened if it runs out of process; null when
|
|
182
|
+
// it shares its parent's. Asked again after the frame navigates
|
|
183
|
+
// elsewhere, which can move it.
|
|
184
|
+
sessionFor(frame) {
|
|
185
|
+
if (frame === this.page.mainFrame())
|
|
186
|
+
return this.sessions.get(frame) ?? Promise.resolve(null);
|
|
187
|
+
const known = this.sessions.get(frame);
|
|
188
|
+
if (known)
|
|
189
|
+
return known;
|
|
190
|
+
if (this.inParent.get(frame) === frame.url())
|
|
191
|
+
return Promise.resolve(null);
|
|
192
|
+
const url = frame.url();
|
|
193
|
+
const opening = this.open(frame).then((session) => {
|
|
194
|
+
if (session === 'in-parent')
|
|
195
|
+
this.inParent.set(frame, url);
|
|
196
|
+
// A failure other than that is tried again next time.
|
|
197
|
+
if (typeof session !== 'object' || session === null) {
|
|
198
|
+
if (this.sessions.get(frame) === opening)
|
|
199
|
+
this.sessions.delete(frame);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
// Its target went (the frame left): the next look opens another.
|
|
203
|
+
session.cdp.on('Inspector.detached', () => { if (this.sessions.get(frame) === opening)
|
|
204
|
+
this.sessions.delete(frame); });
|
|
205
|
+
return session;
|
|
206
|
+
});
|
|
207
|
+
this.sessions.set(frame, opening);
|
|
208
|
+
return opening;
|
|
209
|
+
}
|
|
210
|
+
// A session for `frame`; 'in-parent' when it shares its parent's
|
|
211
|
+
// process, so has none of its own; null when it couldn't be opened.
|
|
212
|
+
async open(frame) {
|
|
213
|
+
if (this.disposed || frame.isDetached())
|
|
214
|
+
return null;
|
|
215
|
+
let cdp;
|
|
216
|
+
try {
|
|
217
|
+
// Playwright refuses a frame that shares its parent's process.
|
|
218
|
+
cdp = await this.page.context().newCDPSession(frame === this.page.mainFrame() ? this.page : frame);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return frame.isDetached() ? null : 'in-parent';
|
|
222
|
+
}
|
|
223
|
+
const session = { cdp, root: frame, contexts: new Map() };
|
|
224
|
+
cdp.on('Runtime.executionContextCreated', ({ context }) => {
|
|
225
|
+
const aux = (context.auxData ?? {});
|
|
226
|
+
session.contexts.set(context.id, { frameId: aux.frameId ?? '', name: context.name, isDefault: aux.isDefault === true, origin: context.origin });
|
|
227
|
+
});
|
|
228
|
+
cdp.on('Runtime.executionContextDestroyed', (ev) => { session.contexts.delete(ev.executionContextId); });
|
|
229
|
+
cdp.on('Runtime.executionContextsCleared', () => { session.contexts.clear(); });
|
|
230
|
+
try {
|
|
231
|
+
await this.setup(session);
|
|
232
|
+
await cdp.send('Page.enable');
|
|
233
|
+
await cdp.send('Runtime.enable');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
await cdp.detach().catch(() => undefined);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
// Disposed meanwhile: nothing would detach it later.
|
|
240
|
+
if (this.disposed) {
|
|
241
|
+
await cdp.detach().catch(() => undefined);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (frame === this.page.mainFrame())
|
|
245
|
+
this.sessions.set(frame, Promise.resolve(session));
|
|
246
|
+
return session;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Frame, Page } from '@playwright/test';
|
|
2
|
+
/** One frame of a tab as the extension lists it (`webNavigation.getAllFrames`). */
|
|
3
|
+
export interface ExtensionFrame {
|
|
4
|
+
/** 0 for the top frame. Kept across the frame's navigations. */
|
|
5
|
+
frameId: number;
|
|
6
|
+
/** -1 for the top frame. */
|
|
7
|
+
parentFrameId: number;
|
|
8
|
+
documentId: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
/** One of a tab's iframes, as `frameNodes` lists them. */
|
|
12
|
+
export interface FrameNode {
|
|
13
|
+
frame: Frame;
|
|
14
|
+
/** Its id's number: `F3` is 3. */
|
|
15
|
+
n: number;
|
|
16
|
+
/** Its parent's id number, or null for a frame in the top frame. */
|
|
17
|
+
parent: number | null;
|
|
18
|
+
/** How deep it is: 1 for a frame in the top frame. */
|
|
19
|
+
depth: number;
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The run's frame ids: `F1`, `F2`, ... across all tabs, in the order
|
|
24
|
+
* frames are first seen (a page's together, in document order). The top
|
|
25
|
+
* frame has none: the tab's id names it.
|
|
26
|
+
*
|
|
27
|
+
* A frame that has gone gives its id to the next one in its place: one
|
|
28
|
+
* with the same parent and URL, as after the tab reloads. So `F2` names
|
|
29
|
+
* the same iframe after a reload, though Playwright makes new `Frame`s
|
|
30
|
+
* for them. Otherwise ids aren't reused.
|
|
31
|
+
*/
|
|
32
|
+
export declare class FrameIds {
|
|
33
|
+
private next;
|
|
34
|
+
private readonly numbers;
|
|
35
|
+
private readonly places;
|
|
36
|
+
private readonly seen;
|
|
37
|
+
number(frame: Frame): number;
|
|
38
|
+
/**
|
|
39
|
+
* Whether `frame` is waiting to be numbered: see `waits`. Listings
|
|
40
|
+
* leave such a frame out until it loads.
|
|
41
|
+
*/
|
|
42
|
+
waiting(frame: Frame): boolean;
|
|
43
|
+
private waits;
|
|
44
|
+
private reused;
|
|
45
|
+
}
|
|
46
|
+
/** `F3` or `T1.F3`: the frame's number, and the tab's if given. */
|
|
47
|
+
export declare function parseFrameRef(text: string): {
|
|
48
|
+
tab?: number;
|
|
49
|
+
frame: number;
|
|
50
|
+
} | null;
|
|
51
|
+
/** A page's iframes, each after its parent, with ids given to new ones. */
|
|
52
|
+
export declare function frameNodes(page: Page, ids: FrameIds): FrameNode[];
|
|
53
|
+
/** The iframe `F<n>` names in `page`, while it's there. */
|
|
54
|
+
export declare function findFrame(page: Page, ids: FrameIds, n: number): Frame | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Which of `nodes` each of the extension's frames is, by tree shape and
|
|
57
|
+
* URL: from the top down, a frame is its parent's child with the same
|
|
58
|
+
* URL. `known` (the last answer) goes first, so that of two sibling
|
|
59
|
+
* frames with one URL, each keeps its match.
|
|
60
|
+
*
|
|
61
|
+
* Returns the extension's frame ids mapped to the node keys. The top
|
|
62
|
+
* frame (frameId 0) maps to null.
|
|
63
|
+
*/
|
|
64
|
+
export declare function matchFrameTrees<K>(frames: readonly ExtensionFrame[], nodes: readonly {
|
|
65
|
+
key: K;
|
|
66
|
+
parent: K | null;
|
|
67
|
+
url: string;
|
|
68
|
+
}[], known?: ReadonlyMap<number, K>): Map<number, K | null>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// A run's iframes (frames, below): their ids, the tree of a tab's
|
|
2
|
+
// frames, and which of the extension's frames each is.
|
|
3
|
+
// See docs/sm-dev-frames.md.
|
|
4
|
+
//
|
|
5
|
+
// - Ids come from Playwright's frames (`mainFrame().childFrames()`,
|
|
6
|
+
// down the tree), which cover every frame of a page, cross-origin
|
|
7
|
+
// ones included (the page's own CDP frame tree leaves those out).
|
|
8
|
+
// Playwright keeps one `Frame` per frame across its navigations, so
|
|
9
|
+
// an id names the frame, not its document.
|
|
10
|
+
// - The extension knows a frame by its `frameId` and documents by
|
|
11
|
+
// `documentId` (`chrome.webNavigation.getAllFrames`); nothing in
|
|
12
|
+
// either side names the other's. So the two trees are matched by
|
|
13
|
+
// their shape and URLs (`matchFrameTrees`).
|
|
14
|
+
/**
|
|
15
|
+
* The run's frame ids: `F1`, `F2`, ... across all tabs, in the order
|
|
16
|
+
* frames are first seen (a page's together, in document order). The top
|
|
17
|
+
* frame has none: the tab's id names it.
|
|
18
|
+
*
|
|
19
|
+
* A frame that has gone gives its id to the next one in its place: one
|
|
20
|
+
* with the same parent and URL, as after the tab reloads. So `F2` names
|
|
21
|
+
* the same iframe after a reload, though Playwright makes new `Frame`s
|
|
22
|
+
* for them. Otherwise ids aren't reused.
|
|
23
|
+
*/
|
|
24
|
+
export class FrameIds {
|
|
25
|
+
next = 1;
|
|
26
|
+
numbers = new WeakMap();
|
|
27
|
+
// Each page's numbered frames, the gone ones' places included.
|
|
28
|
+
places = new WeakMap();
|
|
29
|
+
// When a frame was first found waiting (`waits`).
|
|
30
|
+
seen = new WeakMap();
|
|
31
|
+
number(frame) {
|
|
32
|
+
const known = this.numbers.get(frame);
|
|
33
|
+
if (known !== undefined)
|
|
34
|
+
return known;
|
|
35
|
+
const page = frame.page();
|
|
36
|
+
let places = this.places.get(page);
|
|
37
|
+
if (!places)
|
|
38
|
+
this.places.set(page, places = []);
|
|
39
|
+
const give = (child, parent) => {
|
|
40
|
+
const n = this.reused(places, child, parent) ?? this.next++;
|
|
41
|
+
this.numbers.set(child, n);
|
|
42
|
+
places.push({ frame: child, n, parent });
|
|
43
|
+
// Gone frames' places are kept only so long, for a page that keeps
|
|
44
|
+
// replacing its ad frames.
|
|
45
|
+
const gone = places.filter((p) => p.frame.isDetached());
|
|
46
|
+
for (const old of gone.slice(0, Math.max(0, gone.length - MAX_GONE)))
|
|
47
|
+
places.splice(places.indexOf(old), 1);
|
|
48
|
+
};
|
|
49
|
+
// The page's new frames are numbered together, down its tree, so
|
|
50
|
+
// ids read in document order whichever frame was heard from first.
|
|
51
|
+
const walk = (parent, parentN) => {
|
|
52
|
+
// Once a frame waits, so do its later siblings: of two with one
|
|
53
|
+
// URL, the one loaded first mustn't take the earlier one's id.
|
|
54
|
+
let held = false;
|
|
55
|
+
for (const child of parent.childFrames()) {
|
|
56
|
+
if (!this.numbers.has(child) && !child.isDetached()) {
|
|
57
|
+
held ||= this.waits(places, child, parentN);
|
|
58
|
+
if (!held || child === frame)
|
|
59
|
+
give(child, parentN);
|
|
60
|
+
}
|
|
61
|
+
const n = this.numbers.get(child);
|
|
62
|
+
if (n !== undefined)
|
|
63
|
+
walk(child, n);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (!page.isClosed())
|
|
67
|
+
walk(page.mainFrame(), null);
|
|
68
|
+
// Detached already, or not in the tree yet (under a frame that
|
|
69
|
+
// waits). Its place is kept all the same, for a later frame in it.
|
|
70
|
+
if (!this.numbers.has(frame)) {
|
|
71
|
+
const parent = frame.parentFrame();
|
|
72
|
+
const parentN = parent && parent !== page.mainFrame() ? this.numbers.get(parent) : null;
|
|
73
|
+
if (parentN === undefined || frame.isDetached())
|
|
74
|
+
this.numbers.set(frame, this.next++);
|
|
75
|
+
else
|
|
76
|
+
give(frame, parentN);
|
|
77
|
+
}
|
|
78
|
+
return this.numbers.get(frame);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether `frame` is waiting to be numbered: see `waits`. Listings
|
|
82
|
+
* leave such a frame out until it loads.
|
|
83
|
+
*/
|
|
84
|
+
waiting(frame) {
|
|
85
|
+
if (this.numbers.has(frame) || frame.isDetached())
|
|
86
|
+
return false;
|
|
87
|
+
const parent = frame.parentFrame();
|
|
88
|
+
if (!parent)
|
|
89
|
+
return false;
|
|
90
|
+
const parentN = parent === frame.page().mainFrame() ? null : this.numbers.get(parent);
|
|
91
|
+
if (parentN === undefined)
|
|
92
|
+
return false;
|
|
93
|
+
const places = this.places.get(frame.page()) ?? [];
|
|
94
|
+
// It, or an earlier sibling that holds it back (as in `number`).
|
|
95
|
+
for (const sibling of parent.childFrames()) {
|
|
96
|
+
if (!this.numbers.has(sibling) && !sibling.isDetached() && this.waits(places, sibling, parentN))
|
|
97
|
+
return true;
|
|
98
|
+
if (sibling === frame)
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
// Whether a frame waits to be numbered: it hasn't loaded its first
|
|
104
|
+
// document (no URL yet, or the `about:blank` a new frame starts with),
|
|
105
|
+
// and a gone frame's id could be its once it has. Numbered now, it
|
|
106
|
+
// would take a new id. Only for so long, as some frames stay on
|
|
107
|
+
// `about:blank` (one with no `src`, or one a script writes into).
|
|
108
|
+
waits(places, frame, parent) {
|
|
109
|
+
const url = frame.url();
|
|
110
|
+
if (url !== '' && url !== 'about:blank')
|
|
111
|
+
return false;
|
|
112
|
+
if (!places.some((p) => p.parent === parent && p.frame.isDetached() && p.frame.url() !== url))
|
|
113
|
+
return false;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
let since = this.seen.get(frame);
|
|
116
|
+
if (since === undefined)
|
|
117
|
+
this.seen.set(frame, since = now);
|
|
118
|
+
return now - since < WAIT_MS;
|
|
119
|
+
}
|
|
120
|
+
// The id of a gone frame in `child`'s place: the same parent, and its
|
|
121
|
+
// URL, or failing that the same page at another query or fragment.
|
|
122
|
+
reused(places, child, parent) {
|
|
123
|
+
const url = child.url();
|
|
124
|
+
const gone = places.filter((p) => p.parent === parent && p.frame.isDetached());
|
|
125
|
+
// A frame keeps its last URL once detached.
|
|
126
|
+
const place = gone.find((p) => p.frame.url() === url) ?? gone.find((p) => bareUrl(p.frame.url()) === bareUrl(url));
|
|
127
|
+
if (!place)
|
|
128
|
+
return undefined;
|
|
129
|
+
// Its place is taken: the new frame's from now on.
|
|
130
|
+
places.splice(places.indexOf(place), 1);
|
|
131
|
+
return place.n;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// How many gone frames' places a page keeps.
|
|
135
|
+
const MAX_GONE = 100;
|
|
136
|
+
// How long a frame that hasn't loaded waits to be numbered (`waits`).
|
|
137
|
+
const WAIT_MS = 3000;
|
|
138
|
+
// A URL without its query and fragment.
|
|
139
|
+
function bareUrl(url) {
|
|
140
|
+
return url.replace(/[?#].*$/, '');
|
|
141
|
+
}
|
|
142
|
+
/** `F3` or `T1.F3`: the frame's number, and the tab's if given. */
|
|
143
|
+
export function parseFrameRef(text) {
|
|
144
|
+
const m = /^(?:[Tt](\d+)\.)?[Ff](\d+)$/.exec(text);
|
|
145
|
+
if (!m)
|
|
146
|
+
return null;
|
|
147
|
+
return { ...(m[1] !== undefined ? { tab: Number(m[1]) } : {}), frame: Number(m[2]) };
|
|
148
|
+
}
|
|
149
|
+
/** A page's iframes, each after its parent, with ids given to new ones. */
|
|
150
|
+
export function frameNodes(page, ids) {
|
|
151
|
+
const out = [];
|
|
152
|
+
const walk = (parent, parentN, depth) => {
|
|
153
|
+
for (const frame of parent.childFrames()) {
|
|
154
|
+
if (frame.isDetached() || ids.waiting(frame))
|
|
155
|
+
continue;
|
|
156
|
+
const n = ids.number(frame);
|
|
157
|
+
out.push({ frame, n, parent: parentN, depth, url: frame.url() });
|
|
158
|
+
walk(frame, n, depth + 1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
if (!page.isClosed())
|
|
162
|
+
walk(page.mainFrame(), null, 1);
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
/** The iframe `F<n>` names in `page`, while it's there. */
|
|
166
|
+
export function findFrame(page, ids, n) {
|
|
167
|
+
return frameNodes(page, ids).find((node) => node.n === n)?.frame;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Which of `nodes` each of the extension's frames is, by tree shape and
|
|
171
|
+
* URL: from the top down, a frame is its parent's child with the same
|
|
172
|
+
* URL. `known` (the last answer) goes first, so that of two sibling
|
|
173
|
+
* frames with one URL, each keeps its match.
|
|
174
|
+
*
|
|
175
|
+
* Returns the extension's frame ids mapped to the node keys. The top
|
|
176
|
+
* frame (frameId 0) maps to null.
|
|
177
|
+
*/
|
|
178
|
+
export function matchFrameTrees(frames, nodes, known = new Map()) {
|
|
179
|
+
const out = new Map();
|
|
180
|
+
if (!frames.some((f) => f.frameId === 0))
|
|
181
|
+
return out;
|
|
182
|
+
out.set(0, null);
|
|
183
|
+
const used = new Set();
|
|
184
|
+
// Down the tree a level at a time: a frame is placed once its parent is.
|
|
185
|
+
let level = frames.filter((f) => f.parentFrameId === 0);
|
|
186
|
+
while (level.length > 0) {
|
|
187
|
+
const placed = [];
|
|
188
|
+
// Siblings among nodes under the same parent, in order.
|
|
189
|
+
const free = (f) => {
|
|
190
|
+
const parent = out.get(f.parentFrameId);
|
|
191
|
+
return nodes.filter((n) => n.parent === parent && !used.has(n.key));
|
|
192
|
+
};
|
|
193
|
+
const sorted = level.filter((f) => out.has(f.parentFrameId)).sort((a, b) => a.frameId - b.frameId);
|
|
194
|
+
const take = (f, key) => {
|
|
195
|
+
out.set(f.frameId, key);
|
|
196
|
+
used.add(key);
|
|
197
|
+
placed.push(f);
|
|
198
|
+
};
|
|
199
|
+
for (const f of sorted) {
|
|
200
|
+
const was = known.get(f.frameId);
|
|
201
|
+
const match = free(f).find((n) => n.key === was && n.url === f.url);
|
|
202
|
+
if (match)
|
|
203
|
+
take(f, match.key);
|
|
204
|
+
}
|
|
205
|
+
for (const f of sorted) {
|
|
206
|
+
if (out.has(f.frameId))
|
|
207
|
+
continue;
|
|
208
|
+
const match = free(f).find((n) => n.url === f.url);
|
|
209
|
+
if (match)
|
|
210
|
+
take(f, match.key);
|
|
211
|
+
}
|
|
212
|
+
const ids = new Set(placed.map((f) => f.frameId));
|
|
213
|
+
level = frames.filter((f) => ids.has(f.parentFrameId));
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Text as a code span with no backticks in plain help: a list's term, a usage line. */
|
|
2
|
+
export declare const quiet: (text: string) => string;
|
|
3
|
+
/** Lays out lines for color or not, then paints them: one string, ending in a newline. */
|
|
4
|
+
export declare function render(inColor: boolean, layout: () => string[]): string;
|
|
5
|
+
/** How wide text shows. */
|
|
6
|
+
export declare function visibleLength(text: string): number;
|
|
7
|
+
/** Text padded with spaces to show `width` wide. */
|
|
8
|
+
export declare function padTo(text: string, width: number): string;
|
|
9
|
+
/** Text wrapped to `width`, by the width it shows. A span is never split. */
|
|
10
|
+
export declare function wrap(text: string, width: number): string[];
|
|
11
|
+
/**
|
|
12
|
+
* A line of text, wrapped to `width`. A bullet (`- `) or numbered item
|
|
13
|
+
* (`1. `), after any spaces of its own, wraps under its text. Any other
|
|
14
|
+
* line starting with spaces is a line of a code block, shown as is, two
|
|
15
|
+
* spaces further in.
|
|
16
|
+
*/
|
|
17
|
+
export declare function textLines(text: string, width: number): string[];
|