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,214 @@
|
|
|
1
|
+
// Canonical `sourcemonkey://SourceMonkey/...` URLs the injector
|
|
2
|
+
// emits as `//# sourceURL=` directives, and that the viewer matches
|
|
3
|
+
// stack-trace refs against.
|
|
4
|
+
//
|
|
5
|
+
// Why this URL shape:
|
|
6
|
+
//
|
|
7
|
+
// - The `sourcemonkey:` scheme is arbitrary — it just has to be
|
|
8
|
+
// URL-shaped, unique to us (the wrapper's async-error filter
|
|
9
|
+
// greps stack frames for it), and recognizable in stack traces.
|
|
10
|
+
// - The literal `SourceMonkey` host puts everything we register
|
|
11
|
+
// under a single top folder in devtools' Sources tree. Devtools
|
|
12
|
+
// strips the scheme and uses the URL's authority as the first
|
|
13
|
+
// visible folder; without the host slot, our scripts would
|
|
14
|
+
// scatter at the top level.
|
|
15
|
+
// - The next folder is the script's human-readable log label
|
|
16
|
+
// (`bar`, `bar%232`, etc.) — see `script-log-id.ts`. We use the
|
|
17
|
+
// log label rather than the script's UUID because the UUID is a
|
|
18
|
+
// debug-grade hash that's useless when you're scanning the tree.
|
|
19
|
+
// Per-script artifacts (`__config.js`, the main body, each
|
|
20
|
+
// `@require`) live under that folder so they cluster together
|
|
21
|
+
// by script.
|
|
22
|
+
// - Page-wide URLs (`__bridge.js`, `__gm-runtime.js`, `__prelude.js`)
|
|
23
|
+
// live directly under `SourceMonkey/`. The bridge and GM runtime
|
|
24
|
+
// are true singletons (one chrome.userScripts registration each);
|
|
25
|
+
// the prelude is inlined per-script but every inclusion uses the
|
|
26
|
+
// same `__prelude.js` URL so devtools' Sources tree dedups them
|
|
27
|
+
// into a single visible entry.
|
|
28
|
+
//
|
|
29
|
+
// Why we percent-encode the label: it is a *display* string
|
|
30
|
+
// (`script-log-id.ts` decodes URL-derived filenames so the user sees
|
|
31
|
+
// `My Script.user.js`), and several of the characters that survive
|
|
32
|
+
// that decode would break a URL. A bare `#` is the fragment
|
|
33
|
+
// separator, so the `#N` dedup suffix would split the path; a space
|
|
34
|
+
// or a paren ends the URL run that `stack-link.ts` matches, so a
|
|
35
|
+
// stack-trace ref would stop linkifying to its tab. Everything
|
|
36
|
+
// outside an unreserved-character whitelist is re-encoded — devtools
|
|
37
|
+
// shows the encoded form, same as it always has. The viewer's Log tab
|
|
38
|
+
// decodes it back for display (`displayRefText` in `stack-link.ts`),
|
|
39
|
+
// so the two disagree on purpose: devtools indexes the file under
|
|
40
|
+
// `main%232`, the Log tab reads `main#2` and keeps the wire form in a
|
|
41
|
+
// tooltip.
|
|
42
|
+
// Exported so consumers can recognize our own URLs by the exact prefix
|
|
43
|
+
// rather than by the scheme alone — the scheme is unregistered, so a
|
|
44
|
+
// hostile script can put `sourcemonkey://anything/…` in a log message
|
|
45
|
+
// and have it parsed as a ref.
|
|
46
|
+
export const ROOT = 'sourcemonkey://SourceMonkey';
|
|
47
|
+
// Strip characters that would break out of the `//# sourceURL=<url>`
|
|
48
|
+
// comment these URLs are spliced into. The log label is derived from
|
|
49
|
+
// the on-disk filename (`script-log-id.ts`), which is attacker-
|
|
50
|
+
// controlled for an untrusted script bundle. A JS line terminator
|
|
51
|
+
// (LF/CR/U+2028/U+2029) ends the single-line `//` comment, so anything
|
|
52
|
+
// after it on the line would become live code in the config blob —
|
|
53
|
+
// which runs at document_start, before the script's own gating. Other
|
|
54
|
+
// control chars can't break the comment but have no business in a
|
|
55
|
+
// devtools tree path either; replace the lot with `_`. (The @require
|
|
56
|
+
// URL builder already does per-segment whitelisting; this covers the
|
|
57
|
+
// main/config/prelude labels that don't go through it.)
|
|
58
|
+
function sanitizeLabel(label) {
|
|
59
|
+
// eslint-disable-next-line no-control-regex
|
|
60
|
+
return label.replace(/[\u0000-\u001f\u007f\u2028\u2029]/g, '_');
|
|
61
|
+
}
|
|
62
|
+
// Percent-encode everything outside the URL unreserved set, so the
|
|
63
|
+
// segment is safe in a path AND carries nothing that would terminate
|
|
64
|
+
// the stack-trace ref pattern in `stack-link.ts`. Applied last, after
|
|
65
|
+
// the `.user.js` / `#N` surgery below has matched against the literal
|
|
66
|
+
// characters.
|
|
67
|
+
// The `u` flag matters: without it the character class matches single
|
|
68
|
+
// UTF-16 code units, so an emoji (surrogate pair — GreasyFork names
|
|
69
|
+
// are full of them) would be handed to encodeURIComponent half at a
|
|
70
|
+
// time and throw URIError on the lone surrogate.
|
|
71
|
+
function encodeSegment(segment) {
|
|
72
|
+
return segment.replace(/[^A-Za-z0-9._~-]/gu, (c) => {
|
|
73
|
+
let enc;
|
|
74
|
+
try {
|
|
75
|
+
enc = encodeURIComponent(c);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// An UNPAIRED surrogate — `u` makes the class match it as a
|
|
79
|
+
// single code point, but there's no UTF-8 encoding for it and
|
|
80
|
+
// encodeURIComponent throws. A manifest entry is user-authored
|
|
81
|
+
// JSON, so `"\ud800.user.js"` really can reach us. Letting the
|
|
82
|
+
// throw escape would reject the whole `Promise.all` in
|
|
83
|
+
// collectActiveSpecs and register NOTHING.
|
|
84
|
+
return '_';
|
|
85
|
+
}
|
|
86
|
+
// encodeURIComponent passes `!'()*` through untouched, and parens
|
|
87
|
+
// are exactly what would truncate a `at fn (URL:L:C)` frame — so
|
|
88
|
+
// hex-encode anything it declined to. All such characters are
|
|
89
|
+
// single-byte ASCII.
|
|
90
|
+
return enc === c ? `%${c.charCodeAt(0).toString(16).toUpperCase()}` : enc;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Folder name for a script's artifacts. Strips the conventional
|
|
94
|
+
// `.user.js` extension from the log label (the folder sits ABOVE the
|
|
95
|
+
// main-body file, so the `.js` looks misleading on a folder), then
|
|
96
|
+
// encodes what's left — which is where the `#E4` script-number suffix
|
|
97
|
+
// (or an unnumbered script's `#N`) becomes `%23E4`.
|
|
98
|
+
//
|
|
99
|
+
// Examples:
|
|
100
|
+
// `foo.user.js` → `foo`
|
|
101
|
+
// `foo.user.js#E4` → `foo%23E4`
|
|
102
|
+
// `foo.user.js#2` → `foo%232`
|
|
103
|
+
// `My foo.user.js` → `My%20foo`
|
|
104
|
+
// `foo` → `foo` (no .user.js to strip)
|
|
105
|
+
function folderForLabel(label) {
|
|
106
|
+
return encodeSegment(sanitizeLabel(label).replace(/\.user\.js(?=$|#)/, ''));
|
|
107
|
+
}
|
|
108
|
+
// Main-body file basename for a script. Same as the folder but with
|
|
109
|
+
// the suffix dropped and `.user.js` restored — so the file's name
|
|
110
|
+
// reflects the script's natural filename without the disambiguator
|
|
111
|
+
// (each script lives in a different folder anyway).
|
|
112
|
+
//
|
|
113
|
+
// Examples:
|
|
114
|
+
// `foo.user.js` → `foo.user.js`
|
|
115
|
+
// `foo.user.js#E4` → `foo.user.js`
|
|
116
|
+
// `foo.user.js#2` → `foo.user.js`
|
|
117
|
+
// `foo` → `foo.user.js`
|
|
118
|
+
function mainBaseForLabel(label) {
|
|
119
|
+
// Suffix first, extension second. The other order would eat a `#2`
|
|
120
|
+
// that's part of the name itself (`Chapter #2.user.js`) — which a
|
|
121
|
+
// decoded `%23` now makes reachable.
|
|
122
|
+
const base = sanitizeLabel(label).replace(/#E?\d+$/, '').replace(/\.user\.js$/, '');
|
|
123
|
+
return encodeSegment(base) + '.user.js';
|
|
124
|
+
}
|
|
125
|
+
// Per-script folder root.
|
|
126
|
+
function scriptRoot(logLabel) {
|
|
127
|
+
return `${ROOT}/${folderForLabel(logLabel)}`;
|
|
128
|
+
}
|
|
129
|
+
// Canonical URL for the main script body.
|
|
130
|
+
export function buildSourceUrl(logLabel) {
|
|
131
|
+
return `${scriptRoot(logLabel)}/${mainBaseForLabel(logLabel)}`;
|
|
132
|
+
}
|
|
133
|
+
// Canonical URL for the per-script config blob — the tiny {code}
|
|
134
|
+
// entry the injector emits as the first js[] entry.
|
|
135
|
+
export function buildConfigSourceUrl(logLabel) {
|
|
136
|
+
return `${scriptRoot(logLabel)}/__config.js`;
|
|
137
|
+
}
|
|
138
|
+
// Canonical URL for the per-script parse-failure sentinel — the last
|
|
139
|
+
// js[] entry. Distinct from the config blob's URL on purpose: devtools
|
|
140
|
+
// dedups Sources-tree nodes by URL, which is safe for the prelude only
|
|
141
|
+
// because every copy of it is byte-identical. The sentinel and the
|
|
142
|
+
// config blob are different code, so sharing a URL would show one node
|
|
143
|
+
// whose text matches just one of them.
|
|
144
|
+
export function buildSealSourceUrl(logLabel) {
|
|
145
|
+
return `${scriptRoot(logLabel)}/__seal.js`;
|
|
146
|
+
}
|
|
147
|
+
// Canonical URL for the prelude. Bytes (WRAPPER_PRELUDE) are
|
|
148
|
+
// byte-identical for every script, so every per-script inclusion
|
|
149
|
+
// tags itself with this same URL — devtools' Sources tree groups
|
|
150
|
+
// the N inclusions into a single visible entry alongside the
|
|
151
|
+
// bridge / GM runtime at the SourceMonkey/ top level. See
|
|
152
|
+
// wrapPreludeEntry in injector.ts.
|
|
153
|
+
export const PRELUDE_SOURCE_URL = `${ROOT}/__prelude.js`;
|
|
154
|
+
// Canonical URL for the page-wide MAIN-world bridge.
|
|
155
|
+
export const BRIDGE_SOURCE_URL = `${ROOT}/__bridge.js`;
|
|
156
|
+
// Canonical URL for the page-wide GM_* runtime.
|
|
157
|
+
export const GM_RUNTIME_SOURCE_URL = `${ROOT}/__gm-runtime.js`;
|
|
158
|
+
// Canonical URL for one of a script's `@require` entries. We map
|
|
159
|
+
// the require's real fetch URL into our synthetic scheme so:
|
|
160
|
+
// - the entry clusters with its owning script in devtools, and
|
|
161
|
+
// - stack frames are honest about the fact that we stored a
|
|
162
|
+
// snapshot of the bytes (we're not re-fetching at run time).
|
|
163
|
+
//
|
|
164
|
+
// Shape: `sourcemonkey://SourceMonkey/<folder>/<host>/<path>` — the
|
|
165
|
+
// original URL's host + path become subfolders so the user can still
|
|
166
|
+
// see the provenance in the Sources tree. Query strings and fragments
|
|
167
|
+
// are dropped (the cached bytes don't depend on them). Two requires
|
|
168
|
+
// on the same script with the same host+path but different `?…`
|
|
169
|
+
// query strings would land at the same synthetic URL. The viewer's
|
|
170
|
+
// tab lookup takes the FIRST match, so a mapped stack frame from the
|
|
171
|
+
// second require opens the first require's tab — the source map picks
|
|
172
|
+
// the right segment, only the tab lookup is ambiguous. Rare in
|
|
173
|
+
// practice; the file-loader already dedups by canonical URL with
|
|
174
|
+
// query, so it'd take two legitimately different `@require` lines
|
|
175
|
+
// pointing at distinct stored copies. Worth revisiting if it ever
|
|
176
|
+
// shows up: including a hash of the query in the path would fix it at
|
|
177
|
+
// the cost of an uglier devtools tree.
|
|
178
|
+
//
|
|
179
|
+
// `file://` requires (resolved local files) use the synthetic host
|
|
180
|
+
// `file` so they land under `<folder>/file/<path>` rather than
|
|
181
|
+
// dropping out of the tree.
|
|
182
|
+
//
|
|
183
|
+
// On a URL we can't parse (shouldn't happen — the file-loader
|
|
184
|
+
// validates before storage) we fall back to `<folder>/require` so we
|
|
185
|
+
// still emit *something* parseable.
|
|
186
|
+
export function buildRequireSourceUrl(logLabel, requireUrl) {
|
|
187
|
+
let host;
|
|
188
|
+
let path;
|
|
189
|
+
try {
|
|
190
|
+
const u = new URL(requireUrl);
|
|
191
|
+
host = u.protocol === 'file:' ? 'file' : u.hostname;
|
|
192
|
+
if (!host)
|
|
193
|
+
host = u.protocol.replace(/:$/, '');
|
|
194
|
+
path = u.pathname.replace(/^\/+/, '');
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return `${scriptRoot(logLabel)}/require`;
|
|
198
|
+
}
|
|
199
|
+
// Sanitize each path segment. `new URL()` above already normalized
|
|
200
|
+
// `a/../b` to `b`, so the `..` / `.` filter is belt-and-suspenders
|
|
201
|
+
// — kept in case the input ever reaches us by a path that bypasses
|
|
202
|
+
// URL parsing (it doesn't today). The real work here is the
|
|
203
|
+
// per-segment character whitelist, which keeps spaces / special
|
|
204
|
+
// chars from confusing the devtools tree.
|
|
205
|
+
const cleanPath = path
|
|
206
|
+
.split('/')
|
|
207
|
+
.map((seg) => seg.replace(/[^a-zA-Z0-9._-]/g, '_'))
|
|
208
|
+
.filter((seg) => seg && seg !== '.' && seg !== '..')
|
|
209
|
+
.join('/');
|
|
210
|
+
const safeHost = host.replace(/[^a-zA-Z0-9._-]/g, '_') || 'require';
|
|
211
|
+
return cleanPath
|
|
212
|
+
? `${scriptRoot(logLabel)}/${safeHost}/${cleanPath}`
|
|
213
|
+
: `${scriptRoot(logLabel)}/${safeHost}/require`;
|
|
214
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Which execution world a script runs in, from its header. Its own
|
|
2
|
+
// module so the harness can ask without loading the injector.
|
|
3
|
+
import { getValue, getValues } from './userscript-header.js';
|
|
4
|
+
import { resolveGrants } from './gm-api-catalog.js';
|
|
5
|
+
// Decide which execution world a script should run in. Like
|
|
6
|
+
// Tampermonkey and Violentmonkey, every script starts in the page's own
|
|
7
|
+
// world (MAIN) and leaves it only when its header asks:
|
|
8
|
+
//
|
|
9
|
+
// - `@inject-into page` → MAIN
|
|
10
|
+
// - `@inject-into content` → USER_SCRIPT (isolated)
|
|
11
|
+
// - `@sandbox raw` → MAIN
|
|
12
|
+
// - `@sandbox JavaScript` → MAIN (TM's choice on Chrome too; only
|
|
13
|
+
// Firefox has a separate world for it)
|
|
14
|
+
// - `@sandbox DOM` → USER_SCRIPT
|
|
15
|
+
// - anything else, unset, `@inject-into auto` → MAIN
|
|
16
|
+
//
|
|
17
|
+
// `@inject-into` (VM's) is read first when a script has both.
|
|
18
|
+
//
|
|
19
|
+
// Why the page by default: scripts written against TM and VM read the
|
|
20
|
+
// page's globals (its `$`, an app's state, `customElements`) without
|
|
21
|
+
// `unsafeWindow`, since there they are in the page. In USER_SCRIPT world
|
|
22
|
+
// those read as `undefined` and the script silently breaks. Any script
|
|
23
|
+
// but a `@grant none` one still keeps its writes off the page's
|
|
24
|
+
// `window`: it runs in a sandbox `window` (`runsSandboxed` below).
|
|
25
|
+
//
|
|
26
|
+
// What the page costs a script, and why `@inject-into content` exists:
|
|
27
|
+
// see docs/injection.md, "Execution world".
|
|
28
|
+
//
|
|
29
|
+
// `@inject-into content` with `unsafeWindow` (by grant or by use) gets
|
|
30
|
+
// that world's own `window` as `unsafeWindow`, matching Violentmonkey's
|
|
31
|
+
// content mode, plus a startup-log warning; see `extractStartupLogs`.
|
|
32
|
+
export function resolveWorld(header) {
|
|
33
|
+
if (!header)
|
|
34
|
+
return 'MAIN';
|
|
35
|
+
const injectInto = (getValue(header, 'inject-into') ?? '').trim().toLowerCase();
|
|
36
|
+
if (injectInto === 'page')
|
|
37
|
+
return 'MAIN';
|
|
38
|
+
if (injectInto === 'content')
|
|
39
|
+
return 'USER_SCRIPT';
|
|
40
|
+
const sandbox = (getValue(header, 'sandbox') ?? '').trim().toLowerCase();
|
|
41
|
+
if (sandbox === 'dom')
|
|
42
|
+
return 'USER_SCRIPT';
|
|
43
|
+
return 'MAIN';
|
|
44
|
+
}
|
|
45
|
+
// The world for a stored row, the form every production caller uses:
|
|
46
|
+
// the spec build, the match index and the reload-swap world check all
|
|
47
|
+
// have to agree.
|
|
48
|
+
export function resolveScriptWorld(script) {
|
|
49
|
+
return resolveWorld(script.header);
|
|
50
|
+
}
|
|
51
|
+
// Whether the script's user program runs inside a sandbox `window`
|
|
52
|
+
// (the prelude's `sandbox`): a page-world script, unless it has
|
|
53
|
+
// `@grant none`. A script with no `@grant` line at all is sandboxed
|
|
54
|
+
// too, as Tampermonkey 5.5.0 and Violentmonkey 2.49.0 run it
|
|
55
|
+
// (measured). That's how both run a script in the page: bare names
|
|
56
|
+
// reach the page's globals, but the sandbox `window` shows only the
|
|
57
|
+
// browser's built-ins, and the script's own writes, a `@require`d
|
|
58
|
+
// library's `window.jQuery = …` included, stay off the page's
|
|
59
|
+
// `window`. A `@grant none` script gets the page's `window` itself, as
|
|
60
|
+
// under both. See docs/injection.md, "The sandbox window".
|
|
61
|
+
export function runsSandboxed(script) {
|
|
62
|
+
if (!script.header || resolveScriptWorld(script) !== 'MAIN')
|
|
63
|
+
return false;
|
|
64
|
+
return !resolveGrants(getValues(script.header, 'grant')).hasNone;
|
|
65
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// What the extension and sm-dev both use. Their own module so the
|
|
2
|
+
// harness can name them without loading the claim code in
|
|
3
|
+
// sm-dev-link.ts.
|
|
4
|
+
/** The first argument of every event's console line. */
|
|
5
|
+
export const SM_DEV_EVENT = 'sm-dev-event';
|
|
6
|
+
/**
|
|
7
|
+
* Refusals of the `managed` calls (managed-changes.ts), which sm-dev
|
|
8
|
+
* also checks before asking for `--force`.
|
|
9
|
+
*/
|
|
10
|
+
export const RUN_COLLECTION_LOCKED = 'An sm-dev run\'s test collection is changed only by its run';
|
|
11
|
+
export const LISTED_BY_LOCATION = 'This script is listed by a directory or manifest collection, so it stays there while its file does. Only a script in a container collection can be moved or uninstalled.';
|
|
12
|
+
export const CONTAINER_TARGET_ONLY = 'Scripts can only be installed or moved into a container collection';
|
|
13
|
+
/**
|
|
14
|
+
* Why a manual script (`@run-at context-menu`) can't be run on a tab
|
|
15
|
+
* now: `runManualScript`'s answers, which the popup logs and sm-dev
|
|
16
|
+
* prints after the script's name. See docs/manual-scripts.md.
|
|
17
|
+
*/
|
|
18
|
+
export const MANUAL_RUN_NOT_INSTALLED = 'This script is not installed';
|
|
19
|
+
export const MANUAL_RUN_NOT_MANUAL = 'This script runs when a page loads. Only a script with @run-at context-menu is run on request.';
|
|
20
|
+
export const MANUAL_RUN_SCRIPT_OFF = 'This script is turned off';
|
|
21
|
+
export const MANUAL_RUN_COLLECTION_OFF = 'This script\'s collection is turned off';
|
|
22
|
+
export const MANUAL_RUN_NOT_HERE = 'This script\'s @match and @include rules don\'t cover this page';
|
|
23
|
+
export const MANUAL_RUN_PAGE = 'SourceMonkey can\'t run scripts on this page';
|
|
24
|
+
export const MANUAL_RUN_TAB_GONE = 'The tab was closed';
|
|
25
|
+
export const MANUAL_RUN_PAGE_CHANGED = 'The page changed before the script could run. Try again.';
|
|
26
|
+
export const MANUAL_RUN_SM_DEV = 'The sm-dev run controlling this tab keeps this script out';
|
|
27
|
+
/**
|
|
28
|
+
* The event types of the markers sm-dev puts in its browser's pages,
|
|
29
|
+
* which tell a `document-start` wrapper whether it can run without
|
|
30
|
+
* asking the extension (`smDevMarker` in userscript-prelude.js, which
|
|
31
|
+
* has its own copy). See docs/sm-dev-design.md#making-wrappers-ask.
|
|
32
|
+
* - `present`: in every page. sm-dev is connected, so any tab may be
|
|
33
|
+
* claimed.
|
|
34
|
+
* - `vouchPrefix` + the run's id: in a tab that run claims, while its
|
|
35
|
+
* scripts are on.
|
|
36
|
+
*/
|
|
37
|
+
export const SM_DEV_MARKERS = {
|
|
38
|
+
present: 'sourcemonkey-sm-dev',
|
|
39
|
+
vouchPrefix: 'sourcemonkey-sm-dev-run:',
|
|
40
|
+
};
|
|
41
|
+
/** The reason a skipped wrapper reports for a tab an sm-dev run controls. */
|
|
42
|
+
export const SKIP_REASON_SM_DEV = 'tab controlled by sm-dev';
|
|
43
|
+
/**
|
|
44
|
+
* The version of the calls and events between sm-dev and the extension,
|
|
45
|
+
* which each side has its own copy of: sm-dev from a repo's install, the
|
|
46
|
+
* extension from the person's browser. A run compares them as it
|
|
47
|
+
* connects (extension-link.ts). See CLAUDE.md for when to bump which.
|
|
48
|
+
* - `major`: a change that needs both sides updated. A run refuses an
|
|
49
|
+
* extension whose major differs.
|
|
50
|
+
* - `minor`: an addition. An extension ahead is fine. An sm-dev ahead
|
|
51
|
+
* checks the extension's minor before using what came later
|
|
52
|
+
* (`ExtensionLink.requireMinor`).
|
|
53
|
+
*/
|
|
54
|
+
// 1.1: `scriptsOnTab` rows carry the script's number.
|
|
55
|
+
// 1.2: `managedCollections` and `managedScripts`; `writeRunScript`
|
|
56
|
+
// takes the script's `source` file, which `managedScripts` gives back.
|
|
57
|
+
// 1.3: permission prompts: `connect-prompt` events, `answerConnectPrompt`.
|
|
58
|
+
// 1.4: a report event can carry `detail`: why a script had no decision.
|
|
59
|
+
// 1.5: `managed`, the calls that change collections and installed
|
|
60
|
+
// scripts (`managed.setScriptEnabled` and the rest), and
|
|
61
|
+
// `managed.scriptDetails` / `managed.scriptText` (managed-changes.ts).
|
|
62
|
+
// 1.6: manual scripts (`@run-at context-menu`): `runManualScript`, and
|
|
63
|
+
// the `sm:manual` report for one waiting in a new document.
|
|
64
|
+
// 1.7: a script's badge status can be `UNTRACKED`: its page loaded
|
|
65
|
+
// before the extension started.
|
|
66
|
+
// 2.0: wrappers no longer all ask while a claim is live; sm-dev marks
|
|
67
|
+
// the pages of its browser instead (`SM_DEV_MARKERS`), which an older
|
|
68
|
+
// sm-dev doesn't, so the extension would run scripts in its tabs.
|
|
69
|
+
// 2.1: scripts run in iframes, so report and menu events now come from
|
|
70
|
+
// frames too (`topFrame: false`); `fireActionContextMenuItem` takes
|
|
71
|
+
// the command's `documentId`.
|
|
72
|
+
// 2.2: `request`, `download`, `values` and a shown `notification` event
|
|
73
|
+
// carry the sending document's `documentId`, which says which frame.
|
|
74
|
+
// 2.3: `managed.scriptFiles`, a script's `@require` and `@resource`
|
|
75
|
+
// files as installed.
|
|
76
|
+
// 2.4: `managed.scriptIcons`, a script's `@icon` and its site icons.
|
|
77
|
+
// 3.0: every script runs in the page's world unless it asks to be
|
|
78
|
+
// isolated. sm-dev works out each script's world itself, for
|
|
79
|
+
// `eval --script` and for whose error a page error could be, so an
|
|
80
|
+
// older one would look in the wrong world. On a page whose CSP
|
|
81
|
+
// `sandbox` turns off script in its own world, the page-world scripts
|
|
82
|
+
// run isolated instead, which sm-dev notes rather than warning that
|
|
83
|
+
// they can't run.
|
|
84
|
+
export const SM_DEV_PROTOCOL = { major: 3, minor: 0 };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Minimal Source Map v3 generator for the concatenated injection entry.
|
|
2
|
+
//
|
|
3
|
+
// Why: in Mode A (see docs/injection-modes.md) a script's `@require`
|
|
4
|
+
// bodies and its own body are concatenated into ONE `js[]` entry so
|
|
5
|
+
// they share a lexical scope. That's what makes a library's top-level
|
|
6
|
+
// `const`/`class` visible to the main body, exactly as in TM/VM — but
|
|
7
|
+
// it also means the main body's line N no longer lands at physical
|
|
8
|
+
// line N, and every require loses the devtools file it used to have.
|
|
9
|
+
//
|
|
10
|
+
// A source map buys both back: devtools splits the single generated
|
|
11
|
+
// script into the original files and reports original line numbers.
|
|
12
|
+
//
|
|
13
|
+
// Scope of this generator:
|
|
14
|
+
//
|
|
15
|
+
// - **Line-level only.** One mapping per generated line, always at
|
|
16
|
+
// column 0. We never move code within a line, so column fidelity
|
|
17
|
+
// would be busywork; line granularity is what stack traces and
|
|
18
|
+
// breakpoints need.
|
|
19
|
+
// - **`sourcesContent` is always embedded.** Our sources are
|
|
20
|
+
// synthetic `sourcemonkey://…` URLs, which devtools cannot fetch,
|
|
21
|
+
// so embedding the text is the only way the mapped files are
|
|
22
|
+
// viewable. It is also the expensive part: the sources ship a
|
|
23
|
+
// second time, base64'd, so the entry grows by ~1.6× their size.
|
|
24
|
+
//
|
|
25
|
+
// There is deliberately no size cap on this. The only constraint
|
|
26
|
+
// that actually binds is whether `chrome.userScripts.register()`
|
|
27
|
+
// accepts the payload, and that failure is already visible:
|
|
28
|
+
// `registerWithFallback` in src/extension/injector.ts drops from batch to
|
|
29
|
+
// per-script registration on rejection and records the reason
|
|
30
|
+
// against that script (which then reads REJECTED, with Chrome's
|
|
31
|
+
// wording), so an oversized script fails loudly and by name. A preemptive cap would trade that for silently degrading
|
|
32
|
+
// the feature the map exists to provide. The map does NOT touch
|
|
33
|
+
// our `chrome.storage.local` quota — it's generated at
|
|
34
|
+
// registration time from sources already stored.
|
|
35
|
+
//
|
|
36
|
+
// What V8 does NOT do for us: map `Error.stack` *strings*. It maps
|
|
37
|
+
// source positions for the devtools UI, but `err.stack` keeps
|
|
38
|
+
// generated coordinates, and SourceMonkey parses those strings for the
|
|
39
|
+
// viewer's Log tab (src/stack-link.ts). `remapStackRef` below is the
|
|
40
|
+
// other half — the Log tab applies the same segment list itself, so a
|
|
41
|
+
// logged frame names the same file and line devtools shows. See
|
|
42
|
+
// docs/script-log.md#stack-refs-are-source-mapped-first.
|
|
43
|
+
// Base64 VLQ, per the Source Map v3 spec.
|
|
44
|
+
const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
45
|
+
// Domain: |value| < 2^30. Above that the `<< 1` wraps negative and the
|
|
46
|
+
// loop emits a single wrong digit. Every caller here passes a line or
|
|
47
|
+
// source index, so this is unreachable — noted so nobody reuses the
|
|
48
|
+
// function for byte offsets.
|
|
49
|
+
export function encodeVlq(value) {
|
|
50
|
+
// Sign goes in the low bit, magnitude above it.
|
|
51
|
+
let vlq = value < 0 ? ((-value) << 1) | 1 : value << 1;
|
|
52
|
+
let out = '';
|
|
53
|
+
do {
|
|
54
|
+
let digit = vlq & 0b11111;
|
|
55
|
+
vlq >>>= 5;
|
|
56
|
+
// Continuation bit: more 5-bit groups follow.
|
|
57
|
+
if (vlq > 0)
|
|
58
|
+
digit |= 0b100000;
|
|
59
|
+
out += B64[digit];
|
|
60
|
+
} while (vlq > 0);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
// Build the map object. `totalLines` is the generated entry's line
|
|
64
|
+
// count; generated lines not covered by any segment (the wrap's head
|
|
65
|
+
// and tail, the `;` separators) get an empty group, which devtools
|
|
66
|
+
// reads as "no original position" and leaves attributed to the
|
|
67
|
+
// generated file.
|
|
68
|
+
//
|
|
69
|
+
// `file` is deliberately the same URL as the entry's own
|
|
70
|
+
// `//# sourceURL=`, which is also `sources[last]` (the script's own
|
|
71
|
+
// body). Bundlers avoid that collision — devtools keys files by URL,
|
|
72
|
+
// so a mapped source sharing its generated script's URL could in
|
|
73
|
+
// principle show up twice, or empty. Verified in Chrome's Sources
|
|
74
|
+
// panel that it doesn't: the mapped file shadows the generated one,
|
|
75
|
+
// and a script with one `@require` shows exactly two entries — the
|
|
76
|
+
// `.user.js` and the require nested under a `<host>/<path>` subtree —
|
|
77
|
+
// both with their original text. Keeping the URLs identical is what
|
|
78
|
+
// lets the Log tab's stack-ref linkification match on one string.
|
|
79
|
+
export function buildSourceMap(args) {
|
|
80
|
+
const sources = args.segments.map((s) => s.url);
|
|
81
|
+
// generatedLine → {sourceIndex, originalLine}. Later segments win on
|
|
82
|
+
// overlap, which can't happen for the layouts we generate but keeps
|
|
83
|
+
// the function total.
|
|
84
|
+
const perLine = new Map();
|
|
85
|
+
args.segments.forEach((seg, srcIdx) => {
|
|
86
|
+
for (let k = 0; k < seg.lineCount; k++) {
|
|
87
|
+
perLine.set(seg.generatedLine + k, { srcIdx, origLine: k });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// VLQ fields after the first are deltas against the previous
|
|
91
|
+
// segment, carried across line groups (only the generated column
|
|
92
|
+
// resets per line — and ours is always 0, so it never contributes).
|
|
93
|
+
let prevSrcIdx = 0;
|
|
94
|
+
let prevOrigLine = 0;
|
|
95
|
+
const groups = [];
|
|
96
|
+
for (let gen = 0; gen < args.totalLines; gen++) {
|
|
97
|
+
const hit = perLine.get(gen);
|
|
98
|
+
if (!hit) {
|
|
99
|
+
groups.push('');
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
groups.push(encodeVlq(0) +
|
|
103
|
+
encodeVlq(hit.srcIdx - prevSrcIdx) +
|
|
104
|
+
encodeVlq(hit.origLine - prevOrigLine) +
|
|
105
|
+
encodeVlq(0));
|
|
106
|
+
prevSrcIdx = hit.srcIdx;
|
|
107
|
+
prevOrigLine = hit.origLine;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
version: 3,
|
|
111
|
+
file: args.file,
|
|
112
|
+
sources,
|
|
113
|
+
sourcesContent: args.segments.map((seg) => seg.content),
|
|
114
|
+
names: [],
|
|
115
|
+
mappings: groups.join(';'),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// Look up the original file + line for a 1-based generated line.
|
|
119
|
+
// Returns null for lines no segment covers. In 1-based terms those are
|
|
120
|
+
// line 1 (the wrap head), each `// @require` marker line, each `;` ASI
|
|
121
|
+
// guard between requires, and the closing `});`. They are ours, not
|
|
122
|
+
// the user's, so leaving them unmapped is correct — the caller drops
|
|
123
|
+
// the link rather than pointing at a neighbouring line.
|
|
124
|
+
export function lookupGeneratedLine(segments, generatedLine) {
|
|
125
|
+
const gen0 = generatedLine - 1;
|
|
126
|
+
for (const seg of segments) {
|
|
127
|
+
if (gen0 >= seg.generatedLine && gen0 < seg.generatedLine + seg.lineCount) {
|
|
128
|
+
return { url: seg.url, line: gen0 - seg.generatedLine + 1 };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// Rewrite a stack ref that points into the concatenated entry so it
|
|
134
|
+
// names the original `@require` (or the body) at its own line.
|
|
135
|
+
//
|
|
136
|
+
// Every frame from the combined entry carries `entryUrl` — the
|
|
137
|
+
// script's own `//# sourceURL=`, which V8 stamps on the whole entry
|
|
138
|
+
// regardless of which concatenated file the code came from. So the
|
|
139
|
+
// URL match is the test for "is this frame ours to remap"; refs to
|
|
140
|
+
// anything else (the page, another userscript, `__prelude.js`) pass
|
|
141
|
+
// through untouched.
|
|
142
|
+
//
|
|
143
|
+
// Columns pass through unchanged, and that is exact rather than
|
|
144
|
+
// approximate: composition only ever moves whole lines, never code
|
|
145
|
+
// within a line. A script with NO requires has no segments at all, so
|
|
146
|
+
// it returns early — see the layout notes in composed-source.ts for
|
|
147
|
+
// the column caveat that applies to that case.
|
|
148
|
+
export function remapStackRef(ref, entryUrl, segments) {
|
|
149
|
+
if (segments.length === 0)
|
|
150
|
+
return null;
|
|
151
|
+
if (ref.url !== entryUrl)
|
|
152
|
+
return null;
|
|
153
|
+
const orig = lookupGeneratedLine(segments, ref.line);
|
|
154
|
+
if (!orig)
|
|
155
|
+
return null;
|
|
156
|
+
return { url: orig.url, line: orig.line, col: ref.col };
|
|
157
|
+
}
|
|
158
|
+
// UTF-8 → base64. `btoa` alone throws on any code point above U+00FF,
|
|
159
|
+
// and userscripts are full of them (the script that prompted Mode A is
|
|
160
|
+
// half Chinese). Encode to bytes first, then base64 the byte string.
|
|
161
|
+
function base64Utf8(text) {
|
|
162
|
+
const bytes = new TextEncoder().encode(text);
|
|
163
|
+
let binary = '';
|
|
164
|
+
// Chunked so a large source doesn't blow the argument limit on
|
|
165
|
+
// String.fromCharCode.apply.
|
|
166
|
+
const CHUNK = 0x8000;
|
|
167
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
168
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
169
|
+
}
|
|
170
|
+
return btoa(binary);
|
|
171
|
+
}
|
|
172
|
+
// The `//# sourceMappingURL=` line to append to the generated entry.
|
|
173
|
+
// Inline data URI because a `sourcemonkey://` map URL would be no more
|
|
174
|
+
// fetchable than the sources themselves.
|
|
175
|
+
export function inlineSourceMapComment(map) {
|
|
176
|
+
return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Utf8(JSON.stringify(map))}`;
|
|
177
|
+
}
|