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,601 @@
|
|
|
1
|
+
// Per-script `GM_*` / `GM.*` binding emitter.
|
|
2
|
+
//
|
|
3
|
+
// The injector calls `emitGmBindings` once per script at wrapper-build
|
|
4
|
+
// time. The result is `{ js, prelude, granted }`:
|
|
5
|
+
// - `js` holds the `const GM_X = ...` declarations plus the eager
|
|
6
|
+
// calls that need nothing but `__smReg.gm` and baked
|
|
7
|
+
// literals (`urlchange.enable`, `windowControl.enable` / `.make`,
|
|
8
|
+
// `resource.init`). It is used
|
|
9
|
+
// TWICE: `wrapCombinedEntry` splices it into the main-body wrap
|
|
10
|
+
// function ahead of the body, and `buildSwapEvalBody` prepends it
|
|
11
|
+
// to the run-new swap-eval body. Anything a hot-swapped script
|
|
12
|
+
// needs has to be in here.
|
|
13
|
+
// - `prelude` is spliced into the main-body wrap function only,
|
|
14
|
+
// ahead of the body, and holds the eager calls that reference
|
|
15
|
+
// identifiers only that function has (today: `storage.init`,
|
|
16
|
+
// which reads `__smDecision.gmValues`). The swap path runs its
|
|
17
|
+
// own equivalent from `userscript-prelude.js`. See
|
|
18
|
+
// `GmBindingEmit` below.
|
|
19
|
+
// - Both run after the prelude's awaitDecision + waitForRunAt
|
|
20
|
+
// gates, so the GM runtime is installed by the time either does.
|
|
21
|
+
// When no APIs are granted (or the script is `@grant none`),
|
|
22
|
+
// `emitGmBindings` returns `{ js: '', prelude: '', granted: [] }`.
|
|
23
|
+
//
|
|
24
|
+
// Topology recap:
|
|
25
|
+
// - `js` reaches everything through `__smReg`, the world's registry,
|
|
26
|
+
// which only code holding the channel key can get (channel.ts);
|
|
27
|
+
// the injector puts it in scope. The runtime is `__smReg.gm`.
|
|
28
|
+
// - SYNC API bodies delegate to `__smReg.gm.<method>(...)`,
|
|
29
|
+
// defined by src/wrapper-gm-runtime.js (a separately-registered
|
|
30
|
+
// USER_SCRIPT-world script). The runtime is shared across all
|
|
31
|
+
// userscripts in the document.
|
|
32
|
+
// - ASYNC API bodies route through `__smReg.gm.sendAsync(...)`
|
|
33
|
+
// which wraps chrome.runtime.sendMessage so a sibling userscript
|
|
34
|
+
// reassigning chrome.runtime can't break us.
|
|
35
|
+
// - GM_info is purely compile-time — the script's metadata is
|
|
36
|
+
// baked into the wrapper as a literal object.
|
|
37
|
+
// - The `GM` object (GM4 alias namespace) is built up alongside.
|
|
38
|
+
//
|
|
39
|
+
// Compile-in shape contract: the set of bindings emitted and the
|
|
40
|
+
// call-site shape (`__smReg.gm.<method>(...)` sync, `sendAsync` async)
|
|
41
|
+
// is stable, so landing a new API changes only its handler.
|
|
42
|
+
import { findApi, GM_APIS } from '../gm-api-catalog.js';
|
|
43
|
+
// Single-value headers exposed verbatim on `GM_info.script`, under
|
|
44
|
+
// the keys TM uses. Shared with the injector so the two sides agree.
|
|
45
|
+
export const GM_INFO_PASSTHROUGH_TAGS = [
|
|
46
|
+
'author', 'homepage', 'icon', 'icon64', 'supportURL', 'updateURL',
|
|
47
|
+
'downloadURL', 'copyright', 'license',
|
|
48
|
+
];
|
|
49
|
+
// The name `js` declares a sandboxed script's `window.<method>`
|
|
50
|
+
// replacement under.
|
|
51
|
+
export function windowMethodBinding(method) {
|
|
52
|
+
return `__smWin_${method}`;
|
|
53
|
+
}
|
|
54
|
+
// JSON-encode but inside a JavaScript source context: the standard
|
|
55
|
+
// `JSON.stringify` output is valid JS source for any value our caller
|
|
56
|
+
// would pass in (primitives, arrays of strings, nested objects of
|
|
57
|
+
// primitives). We add one safety wrap — the `</script>` sequence
|
|
58
|
+
// breaks out of an inline <script> if the wrapper is ever serialized
|
|
59
|
+
// into one; we don't currently do that (chrome.userScripts takes raw
|
|
60
|
+
// code), but the extra escape is cheap insurance.
|
|
61
|
+
function jsLit(value) {
|
|
62
|
+
return JSON.stringify(value).replace(/<\/script>/gi, '<\\/script>');
|
|
63
|
+
}
|
|
64
|
+
// Build the literal source for GM_info. Baked at compile time — no
|
|
65
|
+
// runtime lookup, no SW round-trip. Strict-mode safe (no `with`).
|
|
66
|
+
function emitGmInfo(info) {
|
|
67
|
+
// `script.grant` is the canonicalized set we actually emitted
|
|
68
|
+
// bindings for — so `@grant GM.setValue` shows up here as
|
|
69
|
+
// `GM_setValue`, and unknown / typoed names are dropped. This
|
|
70
|
+
// matches VM's behavior (TM exposes the raw declared form). Worth
|
|
71
|
+
// revisiting if real compat reports show up. `matches` / `includes`
|
|
72
|
+
// / `excludes` pass through verbatim.
|
|
73
|
+
//
|
|
74
|
+
// Key names: where TM and VM disagree (`run-at` vs `runAt`,
|
|
75
|
+
// `connects` vs `connect`) we use whichever matches the header tag.
|
|
76
|
+
const scriptObject = {
|
|
77
|
+
// Passthrough first so the canonical keys below always win.
|
|
78
|
+
...info.passthrough,
|
|
79
|
+
name: info.name,
|
|
80
|
+
version: info.version ?? '',
|
|
81
|
+
namespace: info.namespace ?? '',
|
|
82
|
+
description: info.description ?? '',
|
|
83
|
+
matches: info.matches.slice(),
|
|
84
|
+
includes: info.includes.slice(),
|
|
85
|
+
excludes: info.excludes.slice(),
|
|
86
|
+
grant: info.grants.slice(),
|
|
87
|
+
'run-at': info.runAt ?? '',
|
|
88
|
+
require: info.requires.slice(),
|
|
89
|
+
resources: info.resourceList.map((r) => ({ name: r.name, url: r.url })),
|
|
90
|
+
connect: info.connects.slice(),
|
|
91
|
+
noframes: info.noframes,
|
|
92
|
+
};
|
|
93
|
+
const gmInfo = {
|
|
94
|
+
script: scriptObject,
|
|
95
|
+
scriptHandler: 'SourceMonkey',
|
|
96
|
+
version: info.scriptHandlerVersion,
|
|
97
|
+
scriptMetaStr: info.scriptMetaStr,
|
|
98
|
+
uuid: info.scriptId,
|
|
99
|
+
// Every SourceMonkey script is re-read from its collection source
|
|
100
|
+
// on refresh, so from the script's point of view it will update.
|
|
101
|
+
scriptWillUpdate: true,
|
|
102
|
+
userAgentData: info.userAgentData,
|
|
103
|
+
};
|
|
104
|
+
// Not frozen, as in TM and VM: a script may add to its own copy.
|
|
105
|
+
return `const GM_info = ${jsLit(gmInfo)};`;
|
|
106
|
+
}
|
|
107
|
+
// Build a single GM_x binding statement.
|
|
108
|
+
//
|
|
109
|
+
// SYNC bodies delegate to __smReg.gm.<subsystem>.<verb>(scriptId, ...args).
|
|
110
|
+
//
|
|
111
|
+
// ASYNC bodies build a message envelope and route it through
|
|
112
|
+
// __smReg.gm.sendAsync. The call-site shape is stable across phases —
|
|
113
|
+
// landing a new API only changes the SW-side dispatch.
|
|
114
|
+
function emitOneBinding(spec, scriptId) {
|
|
115
|
+
// unsafeWindow is a passthrough binding, not a function — the same
|
|
116
|
+
// `const unsafeWindow = window;` in both worlds, because what makes
|
|
117
|
+
// it correct is the WORLD, not the binding.
|
|
118
|
+
//
|
|
119
|
+
// - MAIN world (the default): `window` IS the page's window, so
|
|
120
|
+
// this is the real thing. It's emitted outside the sandbox
|
|
121
|
+
// `window` a sandboxed script gets, so it isn't the sandbox.
|
|
122
|
+
// - USER_SCRIPT world: reachable only when the script declared
|
|
123
|
+
// `@inject-into content` or `@sandbox DOM`, i.e. asked for isolation.
|
|
124
|
+
// `window` is then the isolated realm's, NOT the page's — matching
|
|
125
|
+
// Violentmonkey's content mode. `extractStartupLogs` emits a
|
|
126
|
+
// warning for that combination so it isn't a silent wrong answer.
|
|
127
|
+
//
|
|
128
|
+
// No cross-world proxy is possible: property access on unsafeWindow is
|
|
129
|
+
// synchronous and Chrome exposes no synchronous channel between an
|
|
130
|
+
// isolated world and the page's realm.
|
|
131
|
+
if (spec.subsystem === 'unsafe-window') {
|
|
132
|
+
return `const unsafeWindow = window;`;
|
|
133
|
+
}
|
|
134
|
+
// `window.onurlchange` isn't a callable and isn't a legal binding
|
|
135
|
+
// name — the grant's whole effect is the eager `urlchange.enable`
|
|
136
|
+
// call emitted at the end of `js` (see emitGmBindings).
|
|
137
|
+
if (spec.subsystem === 'url-change') {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
// `window.close` / `window.focus` are methods we replace on the
|
|
141
|
+
// script's `window`, not new names — same deal: the grant's effect is
|
|
142
|
+
// the eager `windowControl` call emitted at the end of `js`.
|
|
143
|
+
if (spec.subsystem === 'window-control') {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
if (spec.name === 'GM_info') {
|
|
147
|
+
// GM_info is emitted separately by emitGmInfo. Skip here.
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
// Storage subsystem: route to the page-side cache + batched-write
|
|
151
|
+
// helpers on `__smReg.gm.storage`. The scriptId literal is closed
|
|
152
|
+
// over so the runtime can key per-script state.
|
|
153
|
+
if (spec.subsystem === 'storage') {
|
|
154
|
+
switch (spec.name) {
|
|
155
|
+
case 'GM_getValue':
|
|
156
|
+
return `const GM_getValue = function (key, defaultValue) { return __smReg.gm.storage.get(${jsLit(scriptId)}, key, defaultValue); };`;
|
|
157
|
+
case 'GM_setValue':
|
|
158
|
+
return `const GM_setValue = function (key, value) { return __smReg.gm.storage.set(${jsLit(scriptId)}, key, value); };`;
|
|
159
|
+
case 'GM_deleteValue':
|
|
160
|
+
return `const GM_deleteValue = function (key) { return __smReg.gm.storage.del(${jsLit(scriptId)}, key); };`;
|
|
161
|
+
case 'GM_listValues':
|
|
162
|
+
return `const GM_listValues = function () { return __smReg.gm.storage.list(${jsLit(scriptId)}); };`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Storage-listener subsystem: addValueChangeListener / removeValueChangeListener
|
|
166
|
+
// are page-side, no SW round-trip. Listeners fire from local
|
|
167
|
+
// sets + deletes AND from remote broadcasts.
|
|
168
|
+
if (spec.subsystem === 'storage-listener') {
|
|
169
|
+
switch (spec.name) {
|
|
170
|
+
case 'GM_addValueChangeListener':
|
|
171
|
+
return `const GM_addValueChangeListener = function (key, callback) { return __smReg.gm.storage.addValueChangeListener(${jsLit(scriptId)}, key, callback); };`;
|
|
172
|
+
case 'GM_removeValueChangeListener':
|
|
173
|
+
return `const GM_removeValueChangeListener = function (listenerId) { return __smReg.gm.storage.removeValueChangeListener(${jsLit(scriptId)}, listenerId); };`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// DOM subsystem: in-page DOM mutations, no per-script state needed
|
|
177
|
+
// beyond what the bodies pass through. Exempt from the page's CSP
|
|
178
|
+
// in USER_SCRIPT world (scripts follow the world CSP instead; see
|
|
179
|
+
// ensureWorldConfigured in injector.ts).
|
|
180
|
+
if (spec.subsystem === 'dom') {
|
|
181
|
+
switch (spec.name) {
|
|
182
|
+
case 'GM_addStyle':
|
|
183
|
+
return `const GM_addStyle = function (css) { return __smReg.gm.dom.addStyle(${jsLit(scriptId)}, css); };`;
|
|
184
|
+
case 'GM_addElement':
|
|
185
|
+
return `const GM_addElement = function (parentOrTag, tagOrAttrs, attrs) { return __smReg.gm.dom.addElement(${jsLit(scriptId)}, parentOrTag, tagOrAttrs, attrs); };`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Clipboard subsystem: navigator.clipboard.writeText via the
|
|
189
|
+
// page-side runtime. Returns the underlying Promise so callers can
|
|
190
|
+
// await; TM's signature is fire-and-forget undefined and our
|
|
191
|
+
// handing back the Promise is the only behavioral deviation.
|
|
192
|
+
if (spec.subsystem === 'clipboard') {
|
|
193
|
+
switch (spec.name) {
|
|
194
|
+
case 'GM_setClipboard':
|
|
195
|
+
return `const GM_setClipboard = function (text, type) { return __smReg.gm.clipboard.write(text, type); };`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// XHR subsystem: GM_xmlhttpRequest returns a `{abort}` control
|
|
199
|
+
// object synchronously (GM.xmlHttpRequest, a Promise: see
|
|
200
|
+
// `emitGmObject`). The runtime stores the callbacks locally
|
|
201
|
+
// and forwards a serializable request to the SW; the SW ships
|
|
202
|
+
// load/error/timeout/abort events back asynchronously via
|
|
203
|
+
// chrome.tabs.sendMessage. The dispatch routes through
|
|
204
|
+
// chrome.runtime.onMessage in the runtime. See docs/GM-APIs.md.
|
|
205
|
+
if (spec.subsystem === 'xhr') {
|
|
206
|
+
switch (spec.name) {
|
|
207
|
+
case 'GM_xmlhttpRequest':
|
|
208
|
+
return `const GM_xmlhttpRequest = function (details) { return __smReg.gm.xhr.start(${jsLit(scriptId)}, details); };`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Log subsystem: GM_log writes to the page console with a per-script
|
|
212
|
+
// prefix AND fires a fire-and-forget sm:gm-log to the SW so the call
|
|
213
|
+
// also lands on the script's Log tab. The scriptId is baked in at
|
|
214
|
+
// compile-in time so each call avoids a per-call lookup.
|
|
215
|
+
if (spec.subsystem === 'log') {
|
|
216
|
+
switch (spec.name) {
|
|
217
|
+
case 'GM_log':
|
|
218
|
+
return `const GM_log = function () { return __smReg.gm.log.write(${jsLit(scriptId)}, arguments); };`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Tab subsystem: GM_openInTab. Returns a `{close, closed, onclose}`
|
|
222
|
+
// control object synchronously. The SW handles chrome.tabs.create
|
|
223
|
+
// and reports close events back to the originating tab.
|
|
224
|
+
if (spec.subsystem === 'tab') {
|
|
225
|
+
switch (spec.name) {
|
|
226
|
+
case 'GM_openInTab':
|
|
227
|
+
return `const GM_openInTab = function (url, options) { return __smReg.gm.tab.open(${jsLit(scriptId)}, url, options); };`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Tab-data subsystem: GM_getTab / GM_saveTab / GM_getTabs. The
|
|
231
|
+
// callback is the last argument, as in TM; with none, the call
|
|
232
|
+
// returns a Promise instead.
|
|
233
|
+
if (spec.subsystem === 'tab-data') {
|
|
234
|
+
switch (spec.name) {
|
|
235
|
+
case 'GM_getTab':
|
|
236
|
+
return `const GM_getTab = function (callback) { return __smReg.gm.tabData.get(${jsLit(scriptId)}, callback); };`;
|
|
237
|
+
case 'GM_saveTab':
|
|
238
|
+
return `const GM_saveTab = function (tab, callback) { return __smReg.gm.tabData.save(${jsLit(scriptId)}, tab, callback); };`;
|
|
239
|
+
case 'GM_getTabs':
|
|
240
|
+
return `const GM_getTabs = function (callback) { return __smReg.gm.tabData.list(${jsLit(scriptId)}, callback); };`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Notification subsystem: GM_notification. Returns the SW round-trip
|
|
244
|
+
// Promise (TM/VM hand back undefined; we hand back the Promise so
|
|
245
|
+
// callers that want to wait for the notification to be created can).
|
|
246
|
+
// Callbacks (onclick / ondone) stay on the page side and are dispatched
|
|
247
|
+
// by chrome.runtime.onMessage from the SW.
|
|
248
|
+
if (spec.subsystem === 'notification') {
|
|
249
|
+
switch (spec.name) {
|
|
250
|
+
case 'GM_notification':
|
|
251
|
+
return `const GM_notification = function () { return __smReg.gm.notification.show(${jsLit(scriptId)}, arguments); };`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Download subsystem: GM_download returns a `{abort}` control object
|
|
255
|
+
// synchronously (TM's documented return). Both TM call shapes —
|
|
256
|
+
// `GM_download(details)` and `GM_download(url, name)` — are
|
|
257
|
+
// normalized by the runtime, so the binding just forwards both
|
|
258
|
+
// positional slots.
|
|
259
|
+
if (spec.subsystem === 'download') {
|
|
260
|
+
switch (spec.name) {
|
|
261
|
+
case 'GM_download':
|
|
262
|
+
return `const GM_download = function (detailsOrUrl, name) { return __smReg.gm.download.start(${jsLit(scriptId)}, detailsOrUrl, name); };`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Menu subsystem: register/unregister context-menu commands. The
|
|
266
|
+
// runtime stashes the callback locally (callbacks aren't
|
|
267
|
+
// serializable) and sends the SW a `{commandId, name}` envelope so
|
|
268
|
+
// the toolbar popup can list the entries. Returns the
|
|
269
|
+
// commandId the caller can later pass to GM_unregisterMenuCommand.
|
|
270
|
+
if (spec.subsystem === 'menu') {
|
|
271
|
+
switch (spec.name) {
|
|
272
|
+
case 'GM_registerMenuCommand':
|
|
273
|
+
return `const GM_registerMenuCommand = function (name, callback, accessKeyOrOpts) { return __smReg.gm.menu.register(${jsLit(scriptId)}, name, callback, accessKeyOrOpts); };`;
|
|
274
|
+
case 'GM_unregisterMenuCommand':
|
|
275
|
+
return `const GM_unregisterMenuCommand = function (commandId) { return __smReg.gm.menu.unregister(${jsLit(scriptId)}, commandId); };`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Resource subsystem: GM_getResourceText decodes the stored bytes
|
|
279
|
+
// as UTF-8 on demand; GM_getResourceURL mints a blob: URL with the
|
|
280
|
+
// stored content-type. Both lookups close over scriptId so the
|
|
281
|
+
// runtime can find the script's prefetched resource map.
|
|
282
|
+
if (spec.subsystem === 'resource') {
|
|
283
|
+
switch (spec.name) {
|
|
284
|
+
case 'GM_getResourceText':
|
|
285
|
+
return `const GM_getResourceText = function (name) { return __smReg.gm.resource.getText(${jsLit(scriptId)}, name); };`;
|
|
286
|
+
case 'GM_getResourceURL':
|
|
287
|
+
return `const GM_getResourceURL = function (name) { return __smReg.gm.resource.getUrl(${jsLit(scriptId)}, name); };`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (spec.kind === 'sync') {
|
|
291
|
+
// A catalog entry with no case above. Caught by the bindings unit
|
|
292
|
+
// test, which emits every catalog API.
|
|
293
|
+
throw new Error(`Internal error: No binding for ${spec.name}`);
|
|
294
|
+
}
|
|
295
|
+
// ASYNC body. Build the message envelope at call time so the args
|
|
296
|
+
// travel as a structured array; the SW handler in later phases
|
|
297
|
+
// unpacks them. Phase-2 SW handler returns {ok:false, error:...}
|
|
298
|
+
// and we re-throw on the caller's side.
|
|
299
|
+
//
|
|
300
|
+
// We pass `scriptId` so the SW can validate @grant + @connect per
|
|
301
|
+
// call (`@grant` re-check is cheap insurance against compile-time
|
|
302
|
+
// / live-mismatch; @connect gating uses the script id to look up
|
|
303
|
+
// the stored @connect list).
|
|
304
|
+
return [
|
|
305
|
+
`const ${spec.name} = function () {`,
|
|
306
|
+
` var args = Array.prototype.slice.call(arguments);`,
|
|
307
|
+
` return __smReg.gm.sendAsync({`,
|
|
308
|
+
` type: "sm:gm-call",`,
|
|
309
|
+
` api: ${jsLit(spec.name)},`,
|
|
310
|
+
` scriptId: ${jsLit(scriptId)},`,
|
|
311
|
+
` args: args`,
|
|
312
|
+
` }).then(function (reply) {`,
|
|
313
|
+
` if (reply && reply.ok) return reply.value;`,
|
|
314
|
+
` var msg = (reply && reply.error) ? reply.error : ${jsLit(spec.name + ' failed')};`,
|
|
315
|
+
` throw new Error("[SourceMonkey] " + msg);`,
|
|
316
|
+
` });`,
|
|
317
|
+
`};`,
|
|
318
|
+
].join(' ');
|
|
319
|
+
}
|
|
320
|
+
// Build the GM4 namespace object — `const GM = { ... }`. Not frozen, as
|
|
321
|
+
// in TM and VM: GM_config and other libraries add to it.
|
|
322
|
+
// For each granted API with a `gmDotName`, expose a property on `GM`.
|
|
323
|
+
// When `gmDotPromised` is true, wrap the GM_x return value in a promise
|
|
324
|
+
// so the GM4 caller can `await` it uniformly. Both go through the
|
|
325
|
+
// registry's copies (`apply`, `resolve`, `reject`): a page that replaced
|
|
326
|
+
// `Function.prototype.apply` or `Promise.resolve` would otherwise be
|
|
327
|
+
// handed the GM function itself.
|
|
328
|
+
function emitGmObject(grantedSpecs, scriptId) {
|
|
329
|
+
const props = [];
|
|
330
|
+
for (const spec of grantedSpecs) {
|
|
331
|
+
if (!spec.gmDotName)
|
|
332
|
+
continue;
|
|
333
|
+
if (spec.subsystem === 'unsafe-window')
|
|
334
|
+
continue;
|
|
335
|
+
if (spec.name === 'GM_info') {
|
|
336
|
+
props.push(`info: GM_info`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
// A Promise of the response, with `abort` on it, as in TM and VM,
|
|
340
|
+
// where GM_xmlhttpRequest returns `{abort}`.
|
|
341
|
+
if (spec.name === 'GM_xmlhttpRequest') {
|
|
342
|
+
props.push(`xmlHttpRequest: function (details) { return __smReg.gm.xhr.start(${jsLit(scriptId)}, details, true); }`);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (spec.gmDotPromised) {
|
|
346
|
+
// The legacy GM_x can be sync (storage reads from cache) or
|
|
347
|
+
// throw (sync stub). Wrap in a Promise so the GM4 alias
|
|
348
|
+
// matches its documented signature unconditionally.
|
|
349
|
+
props.push(`${spec.gmDotName}: function () { try { return __smReg.resolve(__smReg.apply(${spec.name}, null, arguments)); } catch (e) { return __smReg.reject(e); } }`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// Pass-through: same call shape, same return.
|
|
353
|
+
props.push(`${spec.gmDotName}: function () { return __smReg.apply(${spec.name}, null, arguments); }`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (props.length === 0)
|
|
357
|
+
return '';
|
|
358
|
+
return `const GM = { ${props.join(', ')} };`;
|
|
359
|
+
}
|
|
360
|
+
// Names every script gets, as in TM and VM (measured in TM 5.5.0 and VM
|
|
361
|
+
// 2.49.0), declared here so they stay out of the page:
|
|
362
|
+
// - `exports`, `module`, `define` are undefined, so a bare reference
|
|
363
|
+
// doesn't throw, and a UMD library a script `@require`s sees no
|
|
364
|
+
// module system (not the page's AMD `define`) and sets a global.
|
|
365
|
+
// - `cloneInto`, `exportFunction` and `createObjectIn` are Firefox's
|
|
366
|
+
// for passing values into the page; on Chrome there's nothing to
|
|
367
|
+
// cross, so as in both managers `cloneInto` returns its argument,
|
|
368
|
+
// `exportFunction` its function and `createObjectIn` a new object,
|
|
369
|
+
// the last two put on the target first when given `defineAs`.
|
|
370
|
+
// A script's own declaration of any of them shadows these (its program
|
|
371
|
+
// runs in a nested scope). `let`, not `const`, so a sloppy
|
|
372
|
+
// `define = undefined` works, as in VM, where they're parameters; `let`
|
|
373
|
+
// also keeps them off the page's `window` in the reload swap's global
|
|
374
|
+
// eval, where a `var` would land there.
|
|
375
|
+
const FIREFOX_AND_MODULE_NAMES = 'let exports = void 0, module = void 0, define = void 0, '
|
|
376
|
+
+ 'cloneInto = function (obj) { return obj; }, '
|
|
377
|
+
+ 'exportFunction = function (fn, target, options) { '
|
|
378
|
+
+ 'if (options && options.defineAs != null) target[options.defineAs] = fn; return fn; }, '
|
|
379
|
+
+ 'createObjectIn = function (target, options) { var obj = {}; '
|
|
380
|
+
+ 'if (options && options.defineAs != null) target[options.defineAs] = obj; return obj; };';
|
|
381
|
+
// Public entry point. Build the binding source string for one script.
|
|
382
|
+
//
|
|
383
|
+
// Behavior:
|
|
384
|
+
// - Always emits GM_info, matching TM/VM (it's available with no
|
|
385
|
+
// `@grant` line at all, with `@grant none`, and alongside any
|
|
386
|
+
// mix of real grants). A script with unknown grants only still
|
|
387
|
+
// gets GM_info — every other emitted artifact is keyed off the
|
|
388
|
+
// resolved-spec set.
|
|
389
|
+
// - For each known granted API: a `const GM_x = ...` binding.
|
|
390
|
+
// - The GM4 `GM` object covers granted APIs with gmDotName, plus
|
|
391
|
+
// `GM.info`.
|
|
392
|
+
// - `exports` / `module` / `define` and the Firefox stand-ins, for
|
|
393
|
+
// every script (`FIREFOX_AND_MODULE_NAMES`).
|
|
394
|
+
// - Granted names not in the catalog (resolveGrants should have
|
|
395
|
+
// filtered these but be defensive) are skipped.
|
|
396
|
+
//
|
|
397
|
+
// The output is concatenated into the wrapper; the bindings sit
|
|
398
|
+
// inside the outer IIFE's scope and are visible to both the baked-in
|
|
399
|
+
// body and the run-new swap-eval branch.
|
|
400
|
+
export function emitGmBindings(granted, info,
|
|
401
|
+
// The script runs with a sandbox `window` (`runsSandboxed`).
|
|
402
|
+
sandboxed = false) {
|
|
403
|
+
const specs = [];
|
|
404
|
+
for (const n of granted) {
|
|
405
|
+
const spec = findApi(n);
|
|
406
|
+
if (spec)
|
|
407
|
+
specs.push(spec);
|
|
408
|
+
}
|
|
409
|
+
// GM_info is always emitted, even with an empty grant set. TM/VM
|
|
410
|
+
// both treat it as implicit so a script can introspect its own
|
|
411
|
+
// metadata without declaring a single grant.
|
|
412
|
+
// Everything here reaches the runtime through `__smReg`, the world's
|
|
413
|
+
// registry, which the injector puts in scope: the wrap function's
|
|
414
|
+
// parameter, or a `const` ahead of the swap eval's body.
|
|
415
|
+
//
|
|
416
|
+
// This code's top level runs in the wrap function, which is sloppy
|
|
417
|
+
// mode (the user program inside it may be), so a page function it
|
|
418
|
+
// called directly could reach it through `.caller`, and its
|
|
419
|
+
// `arguments` hold the script's entry (its stored values among them).
|
|
420
|
+
// So the top level calls nothing the page could have replaced:
|
|
421
|
+
// `console.info` is the registry's copy, taken at document_start
|
|
422
|
+
// (`info`). Calls from inside a GM
|
|
423
|
+
// function don't lead back here: the runtime is strict, and the user
|
|
424
|
+
// program runs through the prelude's strict `callAs`.
|
|
425
|
+
const parts = [emitGmInfo(info), FIREFOX_AND_MODULE_NAMES];
|
|
426
|
+
const declared = ['GM_info'];
|
|
427
|
+
for (const spec of specs) {
|
|
428
|
+
if (spec.name === 'GM_info')
|
|
429
|
+
continue;
|
|
430
|
+
const line = emitOneBinding(spec, info.scriptId);
|
|
431
|
+
if (!line)
|
|
432
|
+
continue;
|
|
433
|
+
parts.push(line);
|
|
434
|
+
// `window.close` and the like call into the runtime; they declare nothing.
|
|
435
|
+
if (/^[A-Za-z_$][\w$]*$/.test(spec.name))
|
|
436
|
+
declared.push(spec.name);
|
|
437
|
+
}
|
|
438
|
+
// GM_info is part of the GM4 alias namespace too.
|
|
439
|
+
const allSpecsIncludingInfo = [findApi('GM_info')].concat(specs.filter((s) => s.name !== 'GM_info'));
|
|
440
|
+
const gmObject = emitGmObject(allSpecsIncludingInfo, info.scriptId);
|
|
441
|
+
if (gmObject) {
|
|
442
|
+
parts.push(gmObject);
|
|
443
|
+
declared.push('GM');
|
|
444
|
+
}
|
|
445
|
+
// `@grant window.onurlchange` — the only entry whose effect is an
|
|
446
|
+
// eager call rather than a declaration. It has to run before the
|
|
447
|
+
// body so the body's `window.onurlchange === null` feature test
|
|
448
|
+
// succeeds. It lives at the END of `js` (not in `prelude`) on
|
|
449
|
+
// purpose:
|
|
450
|
+
// - Ordering safety is identical. Both strings are spliced into
|
|
451
|
+
// the SAME main-body wrap function (`wrapCombinedEntry`), which
|
|
452
|
+
// only runs after the prelude's awaitDecision + waitForRunAt
|
|
453
|
+
// gates — so `__smReg.gm` is installed either way.
|
|
454
|
+
// - `js` is ALSO what `buildSwapEvalBody` prepends for the
|
|
455
|
+
// reload-fast-path swap, and `prelude` is not. Emitting here is
|
|
456
|
+
// what makes a hot-swapped script still get its `urlchange`
|
|
457
|
+
// surface.
|
|
458
|
+
//
|
|
459
|
+
// Guarded on the runtime rather than dereferencing it bare — unlike
|
|
460
|
+
// the other two eager calls, which fail loudly on purpose. An
|
|
461
|
+
// unguarded TypeError in this scope aborts the whole wrap function
|
|
462
|
+
// and the script never runs at all, and degrading to "no urlchange
|
|
463
|
+
// surface" is strictly better: the script's own `=== null` feature
|
|
464
|
+
// test then correctly reports "unsupported", exactly as it would
|
|
465
|
+
// under a manager without the feature. `storage.init` (in
|
|
466
|
+
// `prelude`) and `resource.init` take the opposite trade — running
|
|
467
|
+
// a body against missing values or missing resource bytes is worse
|
|
468
|
+
// than not running it.
|
|
469
|
+
if (specs.some((s) => s.subsystem === 'url-change')) {
|
|
470
|
+
parts.push(
|
|
471
|
+
// Both halves matter: a runtime registered before this feature
|
|
472
|
+
// shipped has `__smReg.gm` but no `urlchange`, and a page can hold
|
|
473
|
+
// a stale registration across an extension update.
|
|
474
|
+
`if (__smReg.gm && __smReg.gm.urlchange) __smReg.gm.urlchange.enable(${jsLit(info.scriptId)});`);
|
|
475
|
+
}
|
|
476
|
+
// `@grant window.close` / `@grant window.focus` — same eager-call
|
|
477
|
+
// shape as `urlchange.enable` above, and in `js` for the same
|
|
478
|
+
// reasons (a hot-swapped script has to get its overrides back, and
|
|
479
|
+
// both strings are spliced into the same wrap function).
|
|
480
|
+
//
|
|
481
|
+
// Guarded on the runtime for the same reason too: degrading to the
|
|
482
|
+
// page's own `window.close()` / `.focus()` — which the browser
|
|
483
|
+
// mostly ignores — is better than a TypeError that stops the script
|
|
484
|
+
// from running at all.
|
|
485
|
+
//
|
|
486
|
+
// A sandboxed script (the page's world) gets its replacement on its
|
|
487
|
+
// own sandbox `window` instead of the world's, so the page and other
|
|
488
|
+
// scripts can't call it: `make` hands it back, and the sandbox takes
|
|
489
|
+
// it from `windowMethods`. Undefined (a frame's `close`, or a runtime
|
|
490
|
+
// from before `make` that a page kept across an extension update)
|
|
491
|
+
// leaves the page's method showing through.
|
|
492
|
+
const windowMethods = [];
|
|
493
|
+
for (const spec of specs) {
|
|
494
|
+
if (spec.subsystem !== 'window-control')
|
|
495
|
+
continue;
|
|
496
|
+
// `window.close` -> "close", `window.focus` -> "focus".
|
|
497
|
+
const method = spec.name.slice('window.'.length);
|
|
498
|
+
const args = `${jsLit(info.scriptId)}, ${jsLit(method)}`;
|
|
499
|
+
if (sandboxed) {
|
|
500
|
+
windowMethods.push(method);
|
|
501
|
+
parts.push(`const ${windowMethodBinding(method)} = __smReg.gm && __smReg.gm.windowControl && __smReg.gm.windowControl.make ? __smReg.gm.windowControl.make(${args}) : undefined;`);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
parts.push(`if (__smReg.gm && __smReg.gm.windowControl) __smReg.gm.windowControl.enable(${args});`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// `resource.init`: emitted whenever GM_getResource* is granted,
|
|
508
|
+
// even when `resources` is empty. Why "even when empty": the
|
|
509
|
+
// runtime's resource box persists across `chrome.userScripts`
|
|
510
|
+
// re-registrations within one document. If a script removed a
|
|
511
|
+
// `@resource` line, skipping init would leave the prior bytes
|
|
512
|
+
// resident in `byName`; the now-stale `GM_getResourceText("foo")`
|
|
513
|
+
// would still resolve. `resource.init` replaces the box, so always
|
|
514
|
+
// emitting it is what makes "removed resource → returns undefined"
|
|
515
|
+
// actually true on the next reload.
|
|
516
|
+
//
|
|
517
|
+
// It lives at the END of `js` rather than in `prelude` for the same
|
|
518
|
+
// reason `urlchange.enable` does: `js` is what `buildSwapEvalBody`
|
|
519
|
+
// prepends for the reload-fast-path swap, and `prelude` is not.
|
|
520
|
+
// The statement references only `__smReg.gm` and baked
|
|
521
|
+
// literals, so it works identically in the wrap function and in the
|
|
522
|
+
// swap eval's global scope.
|
|
523
|
+
//
|
|
524
|
+
// Installation ordering is unchanged for the registered path — both
|
|
525
|
+
// strings are spliced into the SAME wrap function, which only runs
|
|
526
|
+
// after the GM runtime has registered. What did change is the
|
|
527
|
+
// relative order: `js` precedes `prelude`, so this now runs BEFORE
|
|
528
|
+
// `storage.init` rather than after. The only case that can tell is
|
|
529
|
+
// `storage.init`'s hard abort, which now leaves a freshly-replaced
|
|
530
|
+
// resource box behind instead of the previous one — invisible,
|
|
531
|
+
// since the body doesn't run either way and the box is per-document.
|
|
532
|
+
const hasStorageGrant = specs.some((s) => s.subsystem === 'storage');
|
|
533
|
+
const hasResourceGrant = specs.some((s) => s.subsystem === 'resource');
|
|
534
|
+
if (hasResourceGrant) {
|
|
535
|
+
parts.push(`__smReg.gm.resource.init(${jsLit(info.scriptId)}, ${jsLit(info.resources)});`);
|
|
536
|
+
}
|
|
537
|
+
// Eager calls into the runtime at compile time go in the prelude when
|
|
538
|
+
// they need identifiers only the wrap function has. Splicing them in
|
|
539
|
+
// the IIFE top would race with the runtime's own document_start
|
|
540
|
+
// registration in some Chrome builds; the injector splices `prelude`
|
|
541
|
+
// inside the main-body wrap function passed to the registry's run, which
|
|
542
|
+
// only fires after both the GM runtime spec has registered AND the
|
|
543
|
+
// @run-at lifecycle event resolves. See wrapCombinedEntry in
|
|
544
|
+
// src/extension/injector.ts.
|
|
545
|
+
//
|
|
546
|
+
// - storage.init: emitted whenever a storage grant is present; the
|
|
547
|
+
// prelude reads the snapshot off `__smDecision.gmValues` (attached
|
|
548
|
+
// by the SW to the sm:check-pending reply) and hard-aborts the
|
|
549
|
+
// script if the snapshot is missing. That `__smDecision` reference
|
|
550
|
+
// is why this one can't move into `js` — the swap eval's global
|
|
551
|
+
// scope doesn't have it. The swap path runs the equivalent init
|
|
552
|
+
// from `userscript-prelude.js` instead, where the decision object
|
|
553
|
+
// is in scope. See docs/GM-APIs-storage.md.
|
|
554
|
+
const preludeLines = [];
|
|
555
|
+
if (hasStorageGrant) {
|
|
556
|
+
// Single source of truth: the SW-attached snapshot. We do NOT
|
|
557
|
+
// bake a fallback literal — getting the snapshot wrong is more
|
|
558
|
+
// dangerous than failing visibly, because a read-modify-write
|
|
559
|
+
// userscript that started with stale data would silently
|
|
560
|
+
// overwrite the user's real values with defaults.
|
|
561
|
+
//
|
|
562
|
+
// When the snapshot is missing: log via console.info (not .error —
|
|
563
|
+
// see userscript-prelude.js; injected .error/.warn leak into
|
|
564
|
+
// chrome://extensions/?errors), send sm:error so the failure
|
|
565
|
+
// surfaces on the per-script log + badge, and `return` from the
|
|
566
|
+
// @run-at callback so the user body never runs. The user can
|
|
567
|
+
// recover by reloading.
|
|
568
|
+
//
|
|
569
|
+
// Two ways to get here, which is why the message doesn't assert
|
|
570
|
+
// one cause: the SW's snapshot read failed, or — for a MAIN-world
|
|
571
|
+
// script only — the SW's `bridgeMayActAs` gate refused to hand the
|
|
572
|
+
// values over the bridge (see docs/injection.md#gm_-in-main-world).
|
|
573
|
+
// No decision at all never gets here: the script is skipped
|
|
574
|
+
// (docs/injection.md#no-decision).
|
|
575
|
+
//
|
|
576
|
+
// One string in both places. As two literals they had already
|
|
577
|
+
// drifted — the Log copy lost the "reload" half, which is the only
|
|
578
|
+
// part the reader can act on.
|
|
579
|
+
const gmValuesFailure = "Failed reading this script's stored GM values during setup, " +
|
|
580
|
+
'so the script did not run. Reload the page to retry.';
|
|
581
|
+
preludeLines.push(`var __smGmSnap = (typeof __smDecision === "object" && __smDecision && __smDecision.gmValues);`, `if (!__smGmSnap) {`, ` try { __smReg.info("[SourceMonkey] " + __smName + ": " + ${jsLit(gmValuesFailure)}); } catch (_smSE0) {}`, ` __smSend({ type: "sm:error", id: __smId, name: __smName, loadedAt: __smLoadedAt, url: location.href, message: ${jsLit(gmValuesFailure)} });`, ` return;`, `}`, `__smReg.gm.storage.init(${jsLit(info.scriptId)}, __smGmSnap, __smDecision.gmSeq);`);
|
|
582
|
+
}
|
|
583
|
+
const prelude = preludeLines.join(' ');
|
|
584
|
+
// Join with spaces, not newlines: the injector splices this into
|
|
585
|
+
// the main-body wrap function ahead of the user body, and any
|
|
586
|
+
// newline here would push the body's line 1 onto physical line 2+
|
|
587
|
+
// — breaking the stack-trace line-number contract that
|
|
588
|
+
// `wrapCombinedEntry` in src/extension/injector.ts depends on. Each part is
|
|
589
|
+
// a complete `const … = …;` statement so `;` + ` ` is enough.
|
|
590
|
+
return {
|
|
591
|
+
js: parts.join(' ') + ' ',
|
|
592
|
+
prelude,
|
|
593
|
+
granted: specs.map((s) => s.name),
|
|
594
|
+
needsValues: hasStorageGrant,
|
|
595
|
+
declared,
|
|
596
|
+
windowMethods,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
// Re-export so callers building GmInfoInput don't need a separate
|
|
600
|
+
// import from the catalog just to enumerate all known names.
|
|
601
|
+
export { GM_APIS };
|