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,141 @@
|
|
|
1
|
+
// `@version` ordering.
|
|
2
|
+
//
|
|
3
|
+
// Userscript versions are not semver — managers accept whatever the
|
|
4
|
+
// author writes. Tampermonkey compares them as dot-separated segments
|
|
5
|
+
// with an optional `-prerelease` tail, and that's what we implement:
|
|
6
|
+
// enough structure to order the versions real scripts use, with a
|
|
7
|
+
// deterministic answer for the ones that don't fit.
|
|
8
|
+
//
|
|
9
|
+
// Pure module — no DOM, no chrome.*, no fetching. The only consumer
|
|
10
|
+
// that needs a verdict rather than an ordering is the UI, which uses
|
|
11
|
+
// `versionRelation`.
|
|
12
|
+
// Splits `1.2.3-beta.4` into `{ main: ['1','2','3'], pre: ['beta','4'] }`.
|
|
13
|
+
// Only the FIRST `-` starts the prerelease tail, so `1.0-rc-2` has a
|
|
14
|
+
// two-segment-free prerelease of `rc-2` rather than nesting further.
|
|
15
|
+
//
|
|
16
|
+
// A `+build` tail is dropped first, per semver: build metadata carries
|
|
17
|
+
// no precedence. Without this, `1.0+b2` vs `1.0` would compare its last
|
|
18
|
+
// main segment as `0+b2` vs `0` and report the metadata-only change as
|
|
19
|
+
// a newer version — a false OUTDATED.
|
|
20
|
+
function splitVersion(v) {
|
|
21
|
+
const plus = v.indexOf('+');
|
|
22
|
+
const trimmed = (plus < 0 ? v : v.slice(0, plus)).trim();
|
|
23
|
+
const dash = trimmed.indexOf('-');
|
|
24
|
+
const mainPart = dash < 0 ? trimmed : trimmed.slice(0, dash);
|
|
25
|
+
const prePart = dash < 0 ? '' : trimmed.slice(dash + 1);
|
|
26
|
+
return {
|
|
27
|
+
main: mainPart.split('.'),
|
|
28
|
+
pre: prePart ? prePart.split('.') : [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const NUMERIC_RE = /^\d+$/;
|
|
32
|
+
// Compare one segment pair. Numeric-vs-numeric compares as numbers so
|
|
33
|
+
// `10` beats `9`; anything else falls back to code-unit order, which
|
|
34
|
+
// happens to put digits below letters — the same direction semver
|
|
35
|
+
// specifies for a numeric-vs-alphanumeric identifier.
|
|
36
|
+
//
|
|
37
|
+
// A consequence worth knowing: `1.0-beta` sorts *below* `1.0` (the
|
|
38
|
+
// prerelease rule) but `1.0.beta` sorts *above* it (padded `'0'` vs
|
|
39
|
+
// `'b'` here). Both follow from the rules as stated; only the `-` form
|
|
40
|
+
// is a prerelease.
|
|
41
|
+
function compareSegment(a, b) {
|
|
42
|
+
if (NUMERIC_RE.test(a) && NUMERIC_RE.test(b)) {
|
|
43
|
+
const x = Number(a);
|
|
44
|
+
const y = Number(b);
|
|
45
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
46
|
+
}
|
|
47
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
48
|
+
}
|
|
49
|
+
// Missing trailing segments read as `0`, so `1.2` and `1.2.0` are the
|
|
50
|
+
// same version. Authors bump `1.2` → `1.2.1` all the time and expect
|
|
51
|
+
// exactly that.
|
|
52
|
+
function compareMain(a, b) {
|
|
53
|
+
const len = Math.max(a.length, b.length);
|
|
54
|
+
for (let i = 0; i < len; i++) {
|
|
55
|
+
const cmp = compareSegment(a[i] ?? '0', b[i] ?? '0');
|
|
56
|
+
if (cmp !== 0)
|
|
57
|
+
return cmp;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
// Prerelease tails follow semver's precedence rules: a release outranks
|
|
62
|
+
// any prerelease of the same main version (`1.0` > `1.0-rc1`), and
|
|
63
|
+
// between two prereleases the one that runs out of segments first is
|
|
64
|
+
// lower (`1.0-rc` < `1.0-rc.2`).
|
|
65
|
+
function comparePre(a, b) {
|
|
66
|
+
if (a.length === 0 && b.length === 0)
|
|
67
|
+
return 0;
|
|
68
|
+
if (a.length === 0)
|
|
69
|
+
return 1;
|
|
70
|
+
if (b.length === 0)
|
|
71
|
+
return -1;
|
|
72
|
+
const len = Math.max(a.length, b.length);
|
|
73
|
+
for (let i = 0; i < len; i++) {
|
|
74
|
+
const sa = a[i];
|
|
75
|
+
const sb = b[i];
|
|
76
|
+
if (sa === undefined)
|
|
77
|
+
return -1;
|
|
78
|
+
if (sb === undefined)
|
|
79
|
+
return 1;
|
|
80
|
+
const cmp = compareSegment(sa, sb);
|
|
81
|
+
if (cmp !== 0)
|
|
82
|
+
return cmp;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
// Total order over version strings. Always returns an ordering — there
|
|
87
|
+
// is no "incomparable" verdict, because every caller ultimately has to
|
|
88
|
+
// pick newer / older / same and a null would just push the arbitrary
|
|
89
|
+
// choice up a layer. An empty string sorts below every non-empty one.
|
|
90
|
+
export function compareVersions(a, b) {
|
|
91
|
+
const ta = a.trim();
|
|
92
|
+
const tb = b.trim();
|
|
93
|
+
if (!ta || !tb) {
|
|
94
|
+
if (ta === tb)
|
|
95
|
+
return 0;
|
|
96
|
+
return ta ? 1 : -1;
|
|
97
|
+
}
|
|
98
|
+
const va = splitVersion(ta);
|
|
99
|
+
const vb = splitVersion(tb);
|
|
100
|
+
const main = compareMain(va.main, vb.main);
|
|
101
|
+
if (main !== 0)
|
|
102
|
+
return main;
|
|
103
|
+
return comparePre(va.pre, vb.pre);
|
|
104
|
+
}
|
|
105
|
+
// The prerelease tag a local copy's `@version` carries: `13.2` copied
|
|
106
|
+
// for editing becomes `13.2-local`. Lives here, next to the comparison
|
|
107
|
+
// that has to recognise it — see `versionRelation`.
|
|
108
|
+
export const LOCAL_EDIT_TAG = 'local';
|
|
109
|
+
// The `@version` a local copy is given: the original with `-local`
|
|
110
|
+
// appended. Always a plain append — `splitVersion` keys on the FIRST
|
|
111
|
+
// `-`, so `1.0-beta` becomes `1.0-beta-local`, whose single prerelease
|
|
112
|
+
// segment `beta-local` string-compares above `beta` and stays below
|
|
113
|
+
// the `1.0` release. That copy therefore reads as newer than its
|
|
114
|
+
// source, the ordinary "local edit running ahead" case; only a source
|
|
115
|
+
// with no tail needs the `edited` relation below.
|
|
116
|
+
export function localEditVersion(version) {
|
|
117
|
+
return `${version.trim()}-${LOCAL_EDIT_TAG}`;
|
|
118
|
+
}
|
|
119
|
+
// True when `version` is a local edit per `localEditVersion`: any
|
|
120
|
+
// prerelease segment that is, or ends in, `-local`.
|
|
121
|
+
export function isLocalEditVersion(version) {
|
|
122
|
+
const { pre } = splitVersion(version.trim());
|
|
123
|
+
return pre.some((seg) => seg === LOCAL_EDIT_TAG || seg.endsWith(`-${LOCAL_EDIT_TAG}`));
|
|
124
|
+
}
|
|
125
|
+
export function versionRelation(loaded, other) {
|
|
126
|
+
if (!loaded?.trim() || !other?.trim())
|
|
127
|
+
return 'unknown';
|
|
128
|
+
const cmp = compareVersions(other, loaded);
|
|
129
|
+
if (cmp > 0)
|
|
130
|
+
return isEditOf(loaded, other) ? 'edited' : 'newer';
|
|
131
|
+
if (cmp < 0)
|
|
132
|
+
return 'older';
|
|
133
|
+
return 'same';
|
|
134
|
+
}
|
|
135
|
+
// `loaded` is a local edit of `other`: same main segments, `other` has
|
|
136
|
+
// no prerelease tail, and ours is a `-local` one.
|
|
137
|
+
function isEditOf(loaded, other) {
|
|
138
|
+
const vl = splitVersion(loaded.trim());
|
|
139
|
+
const vo = splitVersion(other.trim());
|
|
140
|
+
return vo.pre.length === 0 && compareMain(vl.main, vo.main) === 0 && isLocalEditVersion(loaded);
|
|
141
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Update probes: "what `@version` does the script's upstream claim?"
|
|
2
|
+
//
|
|
3
|
+
// A probe is deliberately NOT a refresh. A refresh fetches the bytes we
|
|
4
|
+
// actually run, from the URL the user installed from, and can change a
|
|
5
|
+
// script's status. A probe fetches somebody else's copy, reads one
|
|
6
|
+
// header line out of it, and changes nothing but a display annotation.
|
|
7
|
+
// Keeping them apart is the whole design: a `@downloadURL` pointing at
|
|
8
|
+
// an origin the user never approved must never become an update
|
|
9
|
+
// channel, and an unreachable probe host must never disable a working
|
|
10
|
+
// script.
|
|
11
|
+
//
|
|
12
|
+
// What gets probed: only `@updateURL` / `@downloadURL` values that
|
|
13
|
+
// differ from the URL we load the script from. We already auto-update
|
|
14
|
+
// from our own collection — local files via the scan's size+mtime check,
|
|
15
|
+
// http(s) via a conditional GET — so probing it would be a slower way
|
|
16
|
+
// to learn what the refresh that just ran already established.
|
|
17
|
+
//
|
|
18
|
+
// That leaves exactly the interesting case: a copy whose official home
|
|
19
|
+
// is somewhere we don't fetch from. We can't update it automatically,
|
|
20
|
+
// so all we can do is say so. See [docs/updates.md].
|
|
21
|
+
import { getValue } from './userscript-header.js';
|
|
22
|
+
import { versionRelation } from './version-compare.js';
|
|
23
|
+
// How long a recorded probe stays fresh. Probes hit third-party hosts
|
|
24
|
+
// on somebody else's page load, and an upstream release is a
|
|
25
|
+
// once-in-weeks event, so this is deliberately hours rather than the
|
|
26
|
+
// seconds the collection-refresh gate uses.
|
|
27
|
+
export const PROBE_MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
28
|
+
// Canonical form for comparing a probe URL against the script's own
|
|
29
|
+
// location. Returns null for anything that isn't an absolute http(s)
|
|
30
|
+
// URL: a relative `@updateURL` has no meaning (updates come from a
|
|
31
|
+
// fixed home, not from wherever this copy happens to sit), and a
|
|
32
|
+
// `file://` one would turn a header line into a host-file read.
|
|
33
|
+
function canonicalProbeUrl(raw) {
|
|
34
|
+
if (!raw?.trim())
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
const u = new URL(raw.trim());
|
|
38
|
+
return u.protocol === 'http:' || u.protocol === 'https:' ? u.toString() : null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// The distinct upstream URLs worth asking about, in `update`-first
|
|
45
|
+
// order. Deduped by canonical URL, so the common GreasyFork shape
|
|
46
|
+
// (`@downloadURL` = the `.user.js` we installed from, `@updateURL` =
|
|
47
|
+
// the sibling `.meta.js`) yields one target, not two.
|
|
48
|
+
export function probeTargets(header, fileUrl) {
|
|
49
|
+
if (!header)
|
|
50
|
+
return [];
|
|
51
|
+
// A `file://` script canonicalizes to null and simply never matches
|
|
52
|
+
// an http(s) target, which is the right answer.
|
|
53
|
+
const own = canonicalProbeUrl(fileUrl);
|
|
54
|
+
const byUrl = new Map();
|
|
55
|
+
for (const kind of ['update', 'download']) {
|
|
56
|
+
const url = canonicalProbeUrl(getValue(header, `${kind}URL`));
|
|
57
|
+
if (!url || url === own)
|
|
58
|
+
continue;
|
|
59
|
+
const existing = byUrl.get(url);
|
|
60
|
+
if (existing)
|
|
61
|
+
existing.kinds.push(kind);
|
|
62
|
+
else
|
|
63
|
+
byUrl.set(url, { url, kinds: [kind] });
|
|
64
|
+
}
|
|
65
|
+
return [...byUrl.values()];
|
|
66
|
+
}
|
|
67
|
+
// Whether the stored probes still stand in for a fresh check. Stale in
|
|
68
|
+
// either direction, because both mean the stored set no longer
|
|
69
|
+
// describes what the script declares:
|
|
70
|
+
//
|
|
71
|
+
// - a target with no row, or a row aged past the interval — the
|
|
72
|
+
// normal case, and what makes a newly-added `@updateURL` get
|
|
73
|
+
// probed on the next check rather than in six hours;
|
|
74
|
+
// - a row whose URL is no longer a target — the script dropped that
|
|
75
|
+
// header, and leaving the row would keep reporting OUTDATED
|
|
76
|
+
// against a URL it no longer names.
|
|
77
|
+
export function probesAreFresh(probes, targets, now) {
|
|
78
|
+
const stored = probes ?? [];
|
|
79
|
+
if (targets.length === 0)
|
|
80
|
+
return stored.length === 0;
|
|
81
|
+
const targetUrls = new Set(targets.map((t) => t.url));
|
|
82
|
+
if (stored.some((p) => !targetUrls.has(p.url)))
|
|
83
|
+
return false;
|
|
84
|
+
const byUrl = new Map(stored.map((p) => [p.url, p]));
|
|
85
|
+
return targets.every((t) => {
|
|
86
|
+
const probe = byUrl.get(t.url);
|
|
87
|
+
return probe !== undefined && now - probe.checkedAt < PROBE_MIN_INTERVAL_MS;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// The probe that says our copy is behind, or null when none does.
|
|
91
|
+
// Picks the highest version among those ahead of us, so a script whose
|
|
92
|
+
// update and download URLs disagree is judged against the further-ahead
|
|
93
|
+
// one — being told about the smaller gap would understate it.
|
|
94
|
+
export function outdatedProbe(loadedVersion, probes) {
|
|
95
|
+
let furthestAhead = null;
|
|
96
|
+
for (const probe of probes ?? []) {
|
|
97
|
+
if (versionRelation(loadedVersion, probe.version) !== 'newer')
|
|
98
|
+
continue;
|
|
99
|
+
if (!furthestAhead || versionRelation(furthestAhead.version, probe.version) === 'newer') {
|
|
100
|
+
furthestAhead = probe;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return furthestAhead;
|
|
104
|
+
}
|
|
105
|
+
// Row label for a probe: "update URL" or "download URL".
|
|
106
|
+
//
|
|
107
|
+
// When ONE url is named by both headers the label says "update URL"
|
|
108
|
+
// rather than spelling out both. `@updateURL` is the header that means
|
|
109
|
+
// "check here for a new version", so it is the accurate name for what
|
|
110
|
+
// we did; `@downloadURL` naming the same file is a detail of the
|
|
111
|
+
// script's headers, not of the check. Spelling out both made the
|
|
112
|
+
// longest label in the panel, for a distinction the reader can't act
|
|
113
|
+
// on — the row's tooltip carries the URL either way.
|
|
114
|
+
//
|
|
115
|
+
// No ambiguity with a plain update probe: the combined form only
|
|
116
|
+
// exists when the two headers resolve to the same URL, and two probes
|
|
117
|
+
// for one URL is exactly what it's collapsing.
|
|
118
|
+
export function probeLabel(kinds) {
|
|
119
|
+
return kinds.includes('update') ? 'update URL' : 'download URL';
|
|
120
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"source":"/home/jshute/dev/SourceMonkey3"}
|
package/lib/bytes.d.ts
ADDED
package/lib/bytes.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Small byte / crypto helpers shared across modules so the SHA-256 and
|
|
2
|
+
// binary→base64 logic lives in one place (script content hashes in
|
|
3
|
+
// script-store, @require/@resource byte hashes + transport in
|
|
4
|
+
// external-files, GM_xmlhttpRequest response transport in gm-xhr).
|
|
5
|
+
// SHA-256 → lowercase hex. Accepts a string (UTF-8 encoded first) or
|
|
6
|
+
// bytes. Async because SubtleCrypto has no sync form on the web
|
|
7
|
+
// platform.
|
|
8
|
+
export async function sha256Hex(data) {
|
|
9
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
10
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes);
|
|
11
|
+
let out = '';
|
|
12
|
+
for (const b of new Uint8Array(digest))
|
|
13
|
+
out += b.toString(16).padStart(2, '0');
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
// base64→binary. The inverse of `bytesToBase64`, used on the receiving
|
|
17
|
+
// end of message hops that can't carry an ArrayBuffer, and to rebuild a
|
|
18
|
+
// stored `@resource` body when a conditional GET answers 304 and the
|
|
19
|
+
// server sends no bytes of its own.
|
|
20
|
+
export function base64ToBytes(base64) {
|
|
21
|
+
const binary = atob(base64);
|
|
22
|
+
const bytes = new Uint8Array(binary.length);
|
|
23
|
+
for (let i = 0; i < binary.length; i++)
|
|
24
|
+
bytes[i] = binary.charCodeAt(i);
|
|
25
|
+
return bytes.buffer;
|
|
26
|
+
}
|
|
27
|
+
// Chunked binary→base64. `String.fromCharCode(...hugeArray)` throws
|
|
28
|
+
// "Maximum call stack size exceeded" past ~125k args in V8, so we walk
|
|
29
|
+
// the buffer in 32 KiB slices and concatenate. Linear and bounded.
|
|
30
|
+
export function bytesToBase64(bytes) {
|
|
31
|
+
let binary = '';
|
|
32
|
+
const CHUNK = 0x8000;
|
|
33
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
34
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
35
|
+
}
|
|
36
|
+
return btoa(binary);
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CHANNEL_KEY_PLACEHOLDER = "__SM_KEY__";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The stand-in for the channel key in the static wrapper files
|
|
2
|
+
// (src/extension/channel.ts swaps the real key in when it registers
|
|
3
|
+
// them). Its own module so the harness, which loads a wrapper under
|
|
4
|
+
// Node, can call the registry gate with it.
|
|
5
|
+
export const CHANNEL_KEY_PLACEHOLDER = '__SM_KEY__';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Shared scan primitives used by every collection-kind scanner.
|
|
2
|
+
//
|
|
3
|
+
// Lives in its own module because both `directories.ts` (local
|
|
4
|
+
// directory / manifest scanner) and `github-provider.ts` (Contents-API
|
|
5
|
+
// scanner) need them — importing them directly between the two
|
|
6
|
+
// scanner modules would create a runtime ES-module cycle, since
|
|
7
|
+
// `directories.ts` also imports the github dispatcher from
|
|
8
|
+
// `github-provider.ts`.
|
|
9
|
+
// How many subdirectory levels below the root a scanner walks before
|
|
10
|
+
// stopping. Applies to local directory collections AND GitHub directory
|
|
11
|
+
// collections (the cost model is different but the cap keeps both
|
|
12
|
+
// bounded).
|
|
13
|
+
export const MAX_SCAN_DEPTH = 2;
|
|
14
|
+
// The one and only filename SourceMonkey recognises as a userscript
|
|
15
|
+
// manifest. Lives here (not in `directories.ts`) so `github-provider.ts`
|
|
16
|
+
// can import it without forming a runtime cycle with `directories.ts`.
|
|
17
|
+
export const MANIFEST_FILENAME = 'script_manifest.json';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { StoredScript } from './script-build.js';
|
|
2
|
+
import type { MappedSegment } from './source-map.js';
|
|
3
|
+
export interface ComposedSource {
|
|
4
|
+
text: string;
|
|
5
|
+
segments: MappedSegment[];
|
|
6
|
+
lineCount: number;
|
|
7
|
+
mainStart: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ComposeOptions {
|
|
10
|
+
requireUrls?: readonly string[];
|
|
11
|
+
mainUrl?: string;
|
|
12
|
+
startLine?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const USER_SOURCE_START_LINE = 1;
|
|
15
|
+
export declare function composeSource(script: StoredScript, opts?: ComposeOptions): ComposedSource;
|
|
16
|
+
export interface StackLayout {
|
|
17
|
+
entryUrl: string;
|
|
18
|
+
segments: readonly MappedSegment[];
|
|
19
|
+
lineCount: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function stackLayout(script: StoredScript, logLabel: string): StackLayout;
|
|
22
|
+
export declare function composeRequiredBody(script: StoredScript): string;
|
|
23
|
+
export interface ParseCheckSource {
|
|
24
|
+
text: string;
|
|
25
|
+
segments: MappedSegment[];
|
|
26
|
+
lineCount: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildParseCheckSource(script: StoredScript): ParseCheckSource;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// ---------- Concatenated user source (requires + body) ----------
|
|
2
|
+
//
|
|
3
|
+
// Every prefetched `@require` body, in declaration order, in front of
|
|
4
|
+
// the **full on-disk script source** (leadingText + headerText +
|
|
5
|
+
// bodyText). This is the single arrangement every run path uses:
|
|
6
|
+
//
|
|
7
|
+
// - the default registration (`wrapCombinedEntry` → one js[] entry);
|
|
8
|
+
// - the reload-fast-path swap eval (`buildSwapEvalBody`), where
|
|
9
|
+
// `handleCheckPending` in background.ts sends the same text as the
|
|
10
|
+
// run-new body.
|
|
11
|
+
//
|
|
12
|
+
// Concatenation IS the feature, not an implementation detail: one
|
|
13
|
+
// script means one lexical scope, so a library's top-level
|
|
14
|
+
// `const`/`let`/`class` is simply visible to the main body — the same
|
|
15
|
+
// thing TM/VM get by concatenating into one IIFE. See
|
|
16
|
+
// docs/injection-modes.md for what the separate-entries arrangement
|
|
17
|
+
// cost us instead.
|
|
18
|
+
//
|
|
19
|
+
// A `;\n` separator between segments guards the seam against ASI
|
|
20
|
+
// (Automatic Semicolon Insertion). ASI won't fire when the next line
|
|
21
|
+
// could continue the previous statement, so a library ending in an
|
|
22
|
+
// expression followed by a body opening with `(`, `[`, `` ` ``, `+`,
|
|
23
|
+
// `-` or `/` would parse as a single statement. Each file was fine on
|
|
24
|
+
// its own; concatenation is what creates the hazard.
|
|
25
|
+
//
|
|
26
|
+
// The leading `// @require <url>` per require makes the concatenated
|
|
27
|
+
// code easier to scan when no source map is applied. Line comment (not
|
|
28
|
+
// block) so URLs with `*/` in their path don't prematurely close it.
|
|
29
|
+
//
|
|
30
|
+
// ---------- Why this lives in its own module ----------
|
|
31
|
+
//
|
|
32
|
+
// Several callers need to agree on the layout, and they live in
|
|
33
|
+
// different extension contexts:
|
|
34
|
+
//
|
|
35
|
+
// - `src/extension/injector.ts` (service worker) emits the registered entry
|
|
36
|
+
// and its source map.
|
|
37
|
+
// - `background.ts`'s swap path emits the same text for `(0,eval)`.
|
|
38
|
+
// - `src/extension/userscript.ts` (the viewer page) re-derives the SAME
|
|
39
|
+
// segment list to map `Error.stack` line numbers in the Log tab
|
|
40
|
+
// back to the original file — V8 leaves `err.stack` in generated
|
|
41
|
+
// coordinates even when it has the map. See docs/script-log.md.
|
|
42
|
+
//
|
|
43
|
+
// The viewer recomputes rather than reading a persisted map because
|
|
44
|
+
// the inputs are already on the `StoredScript` row it has loaded, and
|
|
45
|
+
// a persisted copy would be one more thing to invalidate. Sharing this
|
|
46
|
+
// one function is what keeps "where line N came from" a single answer
|
|
47
|
+
// instead of two that can drift.
|
|
48
|
+
import { joinFullSource } from './userscript-header.js';
|
|
49
|
+
import { buildRequireSourceUrl, buildSourceUrl } from './script-source-url.js';
|
|
50
|
+
// ---------- The two layouts, and why the viewer can tell them apart ----------
|
|
51
|
+
//
|
|
52
|
+
// **With `@require`s.** The preamble (GM bindings, prelude, wrap head)
|
|
53
|
+
// occupies generated line 0 on its own and the concatenated user
|
|
54
|
+
// source starts at `USER_SOURCE_START_LINE`. Line numbers shift, so
|
|
55
|
+
// the entry carries a source map and the viewer remaps stack refs
|
|
56
|
+
// through the segment list.
|
|
57
|
+
//
|
|
58
|
+
// **Without `@require`s.** Nothing is concatenated, so nothing may
|
|
59
|
+
// shift: the body's first line SHARES generated line 0 with the
|
|
60
|
+
// preamble, making generated line N equal original line N. No map is
|
|
61
|
+
// emitted and the viewer passes refs through untouched.
|
|
62
|
+
//
|
|
63
|
+
// The viewer distinguishes the two by whether there are segments at
|
|
64
|
+
// all — `composeSource` returns none for a script without requires —
|
|
65
|
+
// so it never has to know which run path produced the code. That only
|
|
66
|
+
// holds while `wrapCombinedEntry` and `buildSwapEvalBody` agree on
|
|
67
|
+
// BOTH layouts. They are the only two producers; keep them in step.
|
|
68
|
+
//
|
|
69
|
+
// The no-require layout costs one thing, deliberately: the body's
|
|
70
|
+
// first line shares a physical line with the preamble, so a frame
|
|
71
|
+
// reported at line 1 carries a column offset by the preamble's length.
|
|
72
|
+
// Line 1 of a userscript is its `// ==UserScript==` marker, so this
|
|
73
|
+
// has never been observed; the code view suppresses the column accent
|
|
74
|
+
// there rather than showing a wrong one.
|
|
75
|
+
export const USER_SOURCE_START_LINE = 1;
|
|
76
|
+
export function composeSource(script, opts = {}) {
|
|
77
|
+
const mainSource = joinFullSource(script.leadingText ?? '', script.headerText ?? '', script.bodyText);
|
|
78
|
+
const requires = script.requires ?? [];
|
|
79
|
+
const startLine = opts.startLine ?? 0;
|
|
80
|
+
if (requires.length === 0) {
|
|
81
|
+
return {
|
|
82
|
+
text: mainSource,
|
|
83
|
+
segments: [],
|
|
84
|
+
lineCount: countLines(mainSource),
|
|
85
|
+
mainStart: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Build the text incrementally and derive every line number from
|
|
89
|
+
// what has ACTUALLY been emitted, rather than adding up expected
|
|
90
|
+
// line counts alongside a `join()`. The two drift: a require whose
|
|
91
|
+
// source ends in a lone `\r` fuses with a joining `\n` into a single
|
|
92
|
+
// CRLF, so one terminator appears where the arithmetic assumed two
|
|
93
|
+
// and every mapping from that seam onward — including the whole user
|
|
94
|
+
// body — shifts by a line. CR-only line endings are exactly the
|
|
95
|
+
// population `\r` is in `LINE_TERMINATORS` for, and nothing
|
|
96
|
+
// normalizes newlines on the way in (file-loader stores the decoded
|
|
97
|
+
// bytes verbatim).
|
|
98
|
+
const segments = [];
|
|
99
|
+
let text = '';
|
|
100
|
+
// 0-based generated line that the NEXT character appended will land
|
|
101
|
+
// on. `countLines('')` is 1, so an empty buffer is line 0.
|
|
102
|
+
const nextLine = () => startLine + countLines(text) - 1;
|
|
103
|
+
for (const [i, r] of requires.entries()) {
|
|
104
|
+
text += `// @require ${r.url}\n`;
|
|
105
|
+
const url = opts.requireUrls?.[i];
|
|
106
|
+
if (url) {
|
|
107
|
+
segments.push({
|
|
108
|
+
url,
|
|
109
|
+
content: r.source,
|
|
110
|
+
generatedLine: nextLine(),
|
|
111
|
+
lineCount: countLines(r.source),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// The trailing-`\r` guard: append the extra `\n` here so the
|
|
115
|
+
// source's own last line stays a line of its own, instead of being
|
|
116
|
+
// swallowed by the separator that follows.
|
|
117
|
+
text += r.source.endsWith('\r') ? `${r.source}\n` : r.source;
|
|
118
|
+
// Terminate the source's last line, then the `;` seam guard on a
|
|
119
|
+
// line of its own.
|
|
120
|
+
text += '\n;\n';
|
|
121
|
+
}
|
|
122
|
+
if (opts.mainUrl) {
|
|
123
|
+
segments.push({
|
|
124
|
+
url: opts.mainUrl,
|
|
125
|
+
content: mainSource,
|
|
126
|
+
generatedLine: nextLine(),
|
|
127
|
+
lineCount: countLines(mainSource),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const mainStart = text.length;
|
|
131
|
+
text += mainSource;
|
|
132
|
+
return { text, segments, lineCount: countLines(text), mainStart };
|
|
133
|
+
}
|
|
134
|
+
export function stackLayout(script, logLabel) {
|
|
135
|
+
const entryUrl = buildSourceUrl(logLabel);
|
|
136
|
+
const { segments, lineCount } = composeSource(script, {
|
|
137
|
+
requireUrls: (script.requires ?? []).map((r) => buildRequireSourceUrl(logLabel, r.url)),
|
|
138
|
+
mainUrl: entryUrl,
|
|
139
|
+
startLine: USER_SOURCE_START_LINE,
|
|
140
|
+
});
|
|
141
|
+
return { entryUrl, segments, lineCount };
|
|
142
|
+
}
|
|
143
|
+
// Count lines the way V8 does, which is what the source map has to
|
|
144
|
+
// agree with. Counting only `\n` silently shifts every mapping after
|
|
145
|
+
// the offending character:
|
|
146
|
+
//
|
|
147
|
+
// - a lone `\r` is a line terminator (files with mangled endings);
|
|
148
|
+
// - so are U+2028 / U+2029, and V8 counts a RAW U+2028 as a break
|
|
149
|
+
// even inside a string literal (legal since ES2019, and minified
|
|
150
|
+
// CDN bundles do embed them).
|
|
151
|
+
//
|
|
152
|
+
// `\r\n` must count as one, hence the alternation order.
|
|
153
|
+
const LINE_TERMINATORS = /\r\n|[\n\r\u2028\u2029]/g;
|
|
154
|
+
// `buildParseCheckSource` below measures its own preamble with it: a
|
|
155
|
+
// parse-check line offset that counted terminators differently from the
|
|
156
|
+
// segment list it is compared against would misattribute every error in
|
|
157
|
+
// the file.
|
|
158
|
+
function countLines(text) {
|
|
159
|
+
if (text === '')
|
|
160
|
+
return 1;
|
|
161
|
+
const matches = text.match(LINE_TERMINATORS);
|
|
162
|
+
return matches ? matches.length + 1 : 1;
|
|
163
|
+
}
|
|
164
|
+
// Public entry point for the swap-eval path, which needs the text only.
|
|
165
|
+
export function composeRequiredBody(script) {
|
|
166
|
+
return composeSource(script).text;
|
|
167
|
+
}
|
|
168
|
+
export function buildParseCheckSource(script) {
|
|
169
|
+
// A blank first line of our own, so the user's first line is never at
|
|
170
|
+
// offset 0: a `#!` hashbang is legal only there, so it stays the
|
|
171
|
+
// syntax error it also is in the real injected entry (which wraps the
|
|
172
|
+
// source), and the location probes can't be handed a source that
|
|
173
|
+
// parses as a shebang script. Blank, not a statement, so a leading
|
|
174
|
+
// `"use strict"` still applies, as in the injected entry (see
|
|
175
|
+
// USER_SCOPE_OPEN in injector.ts). The offset below is derived from it rather than
|
|
176
|
+
// assumed to be 1 — the same discipline `composeSource` applies
|
|
177
|
+
// internally, and for the same reason: assumed line counts drift
|
|
178
|
+
// from emitted ones.
|
|
179
|
+
const preamble = '\n';
|
|
180
|
+
const startLine = countLines(preamble) - 1;
|
|
181
|
+
const requires = script.requires ?? [];
|
|
182
|
+
const composed = composeSource(script, {
|
|
183
|
+
requireUrls: requires.map((r) => r.url),
|
|
184
|
+
mainUrl: script.relativePath,
|
|
185
|
+
startLine,
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
text: preamble + composed.text,
|
|
189
|
+
// `composeSource` emits no segments for a script with no
|
|
190
|
+
// `@require`s — the viewer keys "pass stack refs through unmapped"
|
|
191
|
+
// off exactly that, so it stays as it is. Here the opposite is
|
|
192
|
+
// wanted: one segment for the body means every reported line
|
|
193
|
+
// resolves through the same lookup, with no second code path for
|
|
194
|
+
// the majority of scripts.
|
|
195
|
+
segments: composed.segments.length > 0
|
|
196
|
+
? composed.segments
|
|
197
|
+
: [{
|
|
198
|
+
url: script.relativePath,
|
|
199
|
+
content: composed.text,
|
|
200
|
+
generatedLine: startLine,
|
|
201
|
+
lineCount: composed.lineCount,
|
|
202
|
+
}],
|
|
203
|
+
lineCount: startLine + composed.lineCount,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEBUG_LOGGING_KEY = "smDebugLogging";
|
|
2
|
+
export declare function isDebugLoggingEnabled(): boolean;
|
|
3
|
+
export declare function _setDebugLoggingForTests(value: boolean): void;
|
|
4
|
+
export declare function loadDebugLoggingFlag(): Promise<void>;
|
|
5
|
+
export declare function debugLoggingPending(): boolean;
|
|
6
|
+
export declare function debugLoggingSettled(): Promise<boolean>;
|
|
7
|
+
export declare function installDebugLoggingListener(): void;
|
|
8
|
+
export declare function dlog(...args: unknown[]): void;
|
|
9
|
+
export declare function dwarn(...args: unknown[]): void;
|
package/lib/debug-log.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Debug-only console logging gate.
|
|
2
|
+
//
|
|
3
|
+
// Several lifecycle messages (per-navigation match list, per-refresh
|
|
4
|
+
// "update check" / "no change") are useful when diagnosing matcher or
|
|
5
|
+
// updater behavior but far too noisy for normal use — they fire on
|
|
6
|
+
// every click. They're off by default and gated through `dlog` /
|
|
7
|
+
// `dwarn`. Flip on by setting `chrome.storage.local`'s
|
|
8
|
+
// `smDebugLogging` key to `true` (e.g. from the SW DevTools console:
|
|
9
|
+
// `chrome.storage.local.set({ smDebugLogging: true })`).
|
|
10
|
+
export const DEBUG_LOGGING_KEY = 'smDebugLogging';
|
|
11
|
+
let enabled = false;
|
|
12
|
+
export function isDebugLoggingEnabled() {
|
|
13
|
+
return enabled;
|
|
14
|
+
}
|
|
15
|
+
// Test/debug hook. Production callers should toggle the storage key
|
|
16
|
+
// instead so the change persists and is observed via onChanged.
|
|
17
|
+
export function _setDebugLoggingForTests(value) {
|
|
18
|
+
enabled = value;
|
|
19
|
+
}
|
|
20
|
+
// Settles once the setting has been read in this context.
|
|
21
|
+
let loading = null;
|
|
22
|
+
let loaded = false;
|
|
23
|
+
export function loadDebugLoggingFlag() {
|
|
24
|
+
loading = (async () => {
|
|
25
|
+
try {
|
|
26
|
+
const got = await chrome.storage.local.get(DEBUG_LOGGING_KEY);
|
|
27
|
+
enabled = got[DEBUG_LOGGING_KEY] === true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
enabled = false;
|
|
31
|
+
}
|
|
32
|
+
loaded = true;
|
|
33
|
+
})();
|
|
34
|
+
return loading;
|
|
35
|
+
}
|
|
36
|
+
// Whether the setting is still being read: a line about work done before
|
|
37
|
+
// then can wait for `debugLoggingSettled` rather than be dropped.
|
|
38
|
+
export function debugLoggingPending() {
|
|
39
|
+
return loading !== null && !loaded;
|
|
40
|
+
}
|
|
41
|
+
// Whether debug logging is on, once the setting has been read (at once
|
|
42
|
+
// in a context that never reads it).
|
|
43
|
+
export async function debugLoggingSettled() {
|
|
44
|
+
await loading;
|
|
45
|
+
return enabled;
|
|
46
|
+
}
|
|
47
|
+
// Idempotent, matching `match-index.ts`'s storage listener. Several
|
|
48
|
+
// contexts install this (the worker and the extension pages), and a
|
|
49
|
+
// context that called twice would double every flag update.
|
|
50
|
+
let listenerInstalled = false;
|
|
51
|
+
export function installDebugLoggingListener() {
|
|
52
|
+
if (listenerInstalled)
|
|
53
|
+
return;
|
|
54
|
+
listenerInstalled = true;
|
|
55
|
+
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
56
|
+
if (areaName !== 'local')
|
|
57
|
+
return;
|
|
58
|
+
const change = changes[DEBUG_LOGGING_KEY];
|
|
59
|
+
if (!change)
|
|
60
|
+
return;
|
|
61
|
+
enabled = change.newValue === true;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function dlog(...args) {
|
|
65
|
+
if (enabled)
|
|
66
|
+
console.log(...args);
|
|
67
|
+
}
|
|
68
|
+
export function dwarn(...args) {
|
|
69
|
+
// console.info (not console.warn / .error) — see background.ts sm:error
|
|
70
|
+
// handler for the rationale: Chrome surfaces SW warn+error into the
|
|
71
|
+
// chrome://extensions/?errors panel, which should be reserved for
|
|
72
|
+
// actual extension bugs, not SourceMonkey's own diagnostic chatter.
|
|
73
|
+
if (enabled)
|
|
74
|
+
console.info(...args);
|
|
75
|
+
}
|