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,155 @@
|
|
|
1
|
+
// Per-script "log ID" assignment.
|
|
2
|
+
//
|
|
3
|
+
// Goal: every script that appears in a log line or UI label can be
|
|
4
|
+
// referred to by a short, file-derived handle: its basename and its
|
|
5
|
+
// script number (`bold-on-hover.user.js#E4`), the number sm-dev names
|
|
6
|
+
// it by (see sticky-numbers.ts). The number keeps two scripts sharing a
|
|
7
|
+
// basename apart, and never shifts when other scripts come and go.
|
|
8
|
+
//
|
|
9
|
+
// A script with no number yet (one just added, before the service
|
|
10
|
+
// worker numbers it, or one in an sm-dev test collection) is labelled
|
|
11
|
+
// by its basename alone, or `#1`, `#2`, ... among other unnumbered
|
|
12
|
+
// scripts sharing it.
|
|
13
|
+
//
|
|
14
|
+
// The basename comes from `scriptBasename`, so a script added by URL
|
|
15
|
+
// contributes its decoded filename (`My Script.user.js`), not the
|
|
16
|
+
// percent-encoded URL tail. `script-source-url.ts` re-encodes the
|
|
17
|
+
// label on its way into a `//# sourceURL=` directive.
|
|
18
|
+
//
|
|
19
|
+
// Unnumbered dupe ordering: sorted by their stable UUID `id`, so the
|
|
20
|
+
// assignment is deterministic, though adding or removing another such
|
|
21
|
+
// script can reshuffle them.
|
|
22
|
+
import { scriptBasename } from '../script-display.js';
|
|
23
|
+
import { numberMap, numbersFrom, SCRIPT_NUMBERS_KEY } from './sticky-numbers.js';
|
|
24
|
+
import { scanStorageRows } from './storage-scan.js';
|
|
25
|
+
import { sharedRows } from './row-cache.js';
|
|
26
|
+
const SCRIPT_PREFIX = 'script:';
|
|
27
|
+
// ---------- Pure helpers ----------
|
|
28
|
+
/**
|
|
29
|
+
* Each script's log ID, by id. `numbers`: the script numbers (from the
|
|
30
|
+
* `scriptNumbers` row); a script with one is `<basename>#E<number>`.
|
|
31
|
+
*/
|
|
32
|
+
export function computeLogIds(scripts, numbers = new Map()) {
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
const out = new Map();
|
|
35
|
+
for (const s of scripts) {
|
|
36
|
+
const base = scriptBasename(s.relativePath);
|
|
37
|
+
const n = numbers.get(s.id);
|
|
38
|
+
if (n !== undefined) {
|
|
39
|
+
out.set(s.id, `${base}#E${n}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const arr = groups.get(base);
|
|
43
|
+
if (arr)
|
|
44
|
+
arr.push(s);
|
|
45
|
+
else
|
|
46
|
+
groups.set(base, [s]);
|
|
47
|
+
}
|
|
48
|
+
for (const [base, arr] of groups) {
|
|
49
|
+
if (arr.length === 1) {
|
|
50
|
+
out.set(arr[0].id, base);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Plain byte-order comparison rather than localeCompare so the
|
|
54
|
+
// assignment is locale-independent — a Turkish-locale SW must agree
|
|
55
|
+
// with a US-locale collections page, and UUIDs are ASCII anyway.
|
|
56
|
+
arr.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
57
|
+
arr.forEach((s, i) => out.set(s.id, `${base}#${i + 1}`));
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
// ---------- SW-side runtime ----------
|
|
62
|
+
//
|
|
63
|
+
// The SW maintains its own copy of {id → relativePath} for every
|
|
64
|
+
// stored script (regardless of status — error/missing scripts can
|
|
65
|
+
// still show up in update-check log lines). One full read at startup,
|
|
66
|
+
// then kept fresh via chrome.storage.onChanged.
|
|
67
|
+
let allInfo = new Map();
|
|
68
|
+
let numbers = new Map();
|
|
69
|
+
let logIds = new Map();
|
|
70
|
+
let loaded = null;
|
|
71
|
+
export function ensureLogIdsLoaded() {
|
|
72
|
+
if (!loaded)
|
|
73
|
+
loaded = loadAll();
|
|
74
|
+
return loaded;
|
|
75
|
+
}
|
|
76
|
+
async function loadAll() {
|
|
77
|
+
// From the worker's row cache when it's on (frozen rows, read only),
|
|
78
|
+
// with the numbers row, which it doesn't keep, read beside it.
|
|
79
|
+
const cached = await sharedRows((k) => k.startsWith(SCRIPT_PREFIX));
|
|
80
|
+
const all = cached
|
|
81
|
+
? { ...cached, ...await chrome.storage.local.get(SCRIPT_NUMBERS_KEY) }
|
|
82
|
+
: await scanStorageRows('log id table', (k) => k.startsWith(SCRIPT_PREFIX) || k === SCRIPT_NUMBERS_KEY);
|
|
83
|
+
const next = new Map();
|
|
84
|
+
for (const [key, value] of Object.entries(all)) {
|
|
85
|
+
if (!key.startsWith(SCRIPT_PREFIX))
|
|
86
|
+
continue;
|
|
87
|
+
const s = value;
|
|
88
|
+
next.set(s.id, { id: s.id, relativePath: s.relativePath });
|
|
89
|
+
}
|
|
90
|
+
allInfo = next;
|
|
91
|
+
numbers = numberMap(numbersFrom(all[SCRIPT_NUMBERS_KEY]));
|
|
92
|
+
recompute();
|
|
93
|
+
}
|
|
94
|
+
function recompute() {
|
|
95
|
+
logIds = computeLogIds([...allInfo.values()], numbers);
|
|
96
|
+
}
|
|
97
|
+
let listenerInstalled = false;
|
|
98
|
+
export function installScriptLogIdListener() {
|
|
99
|
+
if (listenerInstalled)
|
|
100
|
+
return;
|
|
101
|
+
listenerInstalled = true;
|
|
102
|
+
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
103
|
+
if (areaName !== 'local')
|
|
104
|
+
return;
|
|
105
|
+
let touched = false;
|
|
106
|
+
if (SCRIPT_NUMBERS_KEY in changes) {
|
|
107
|
+
numbers = numberMap(numbersFrom(changes[SCRIPT_NUMBERS_KEY].newValue));
|
|
108
|
+
touched = true;
|
|
109
|
+
}
|
|
110
|
+
for (const [key, change] of Object.entries(changes)) {
|
|
111
|
+
if (!key.startsWith(SCRIPT_PREFIX))
|
|
112
|
+
continue;
|
|
113
|
+
const id = key.slice(SCRIPT_PREFIX.length);
|
|
114
|
+
if (change.newValue === undefined) {
|
|
115
|
+
if (allInfo.delete(id))
|
|
116
|
+
touched = true;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const s = change.newValue;
|
|
120
|
+
const prior = allInfo.get(id);
|
|
121
|
+
if (!prior || prior.relativePath !== s.relativePath) {
|
|
122
|
+
allInfo.set(id, { id: s.id, relativePath: s.relativePath });
|
|
123
|
+
touched = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (touched)
|
|
127
|
+
recompute();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The script numbers, straight from the service worker's numbering, so
|
|
132
|
+
* a label asked for right after a sync already has its `#E<n>`, rather
|
|
133
|
+
* than waiting for the storage change event to bring it.
|
|
134
|
+
*/
|
|
135
|
+
export function noteScriptNumbers(next) {
|
|
136
|
+
numbers = next;
|
|
137
|
+
recompute();
|
|
138
|
+
}
|
|
139
|
+
// Short human-readable label for SW log messages. Falls back to the
|
|
140
|
+
// first 8 chars of the UUID when the script isn't tracked — happens
|
|
141
|
+
// only for a brief window before ensureLogIdsLoaded() resolves, or
|
|
142
|
+
// for late sm:* reports from scripts that have since been removed.
|
|
143
|
+
export function getScriptLogLabel(id) {
|
|
144
|
+
return logIds.get(id) ?? id.slice(0, 8);
|
|
145
|
+
}
|
|
146
|
+
// Test/debug hook. Note: we do NOT flip listenerInstalled back to
|
|
147
|
+
// false — chrome.storage.onChanged.addListener can't be cleanly
|
|
148
|
+
// undone for an anonymous listener, and re-installing would queue
|
|
149
|
+
// duplicate callbacks.
|
|
150
|
+
export function _resetScriptLogIdsForTests() {
|
|
151
|
+
allInfo = new Map();
|
|
152
|
+
numbers = new Map();
|
|
153
|
+
logIds = new Map();
|
|
154
|
+
loaded = null;
|
|
155
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// Per-script event log.
|
|
2
|
+
//
|
|
3
|
+
// Each StoredScript gets its own log keyed by script ID and stored in
|
|
4
|
+
// chrome.storage.session (so logs survive SW restarts but are cleared
|
|
5
|
+
// when Chrome shuts down — same lifetime as the per-tab badge state).
|
|
6
|
+
//
|
|
7
|
+
// We chose a single per-script array (rather than one storage key per
|
|
8
|
+
// entry) because chrome.storage.session is a flat key-value store and
|
|
9
|
+
// scanning N keys to read N log lines would dominate the cost of
|
|
10
|
+
// rendering the log pane. The array IS a read/modify/write append, so
|
|
11
|
+
// concurrent appendScriptLog calls for the same id are serialized
|
|
12
|
+
// through an in-memory promise chain to keep them from clobbering each
|
|
13
|
+
// other. Cross-id appends still run concurrently.
|
|
14
|
+
//
|
|
15
|
+
// Entries are append-only with a fixed cap; once the count passes
|
|
16
|
+
// LOG_CAP the oldest entries are dropped. The cap is generous enough
|
|
17
|
+
// to cover a normal day of refreshes + a few crashy injections but
|
|
18
|
+
// small enough that a runaway error loop can't fill storage.
|
|
19
|
+
//
|
|
20
|
+
// Alongside the log we maintain a sibling `loglevel:<scriptId>` row
|
|
21
|
+
// recording the highest level any SCRIPT-ORIGINATED entry has carried
|
|
22
|
+
// so far — a runtime error, a refused `GM_download`, a parse-time
|
|
23
|
+
// diagnostic.
|
|
24
|
+
// Bumped only when an append carries a strictly higher level; cleared
|
|
25
|
+
// on `clearScriptLog` and by an entry that `resetsLogLevel` (new content
|
|
26
|
+
// loaded). Entries that record a load-state transition
|
|
27
|
+
// (`state` set — see `LogEntry.state`) never touch it: their badge
|
|
28
|
+
// comes from the row and clears with it. It lives in session storage
|
|
29
|
+
// (not on the StoredScript row) because:
|
|
30
|
+
// - Lifetime matches the log itself — Chrome shutdown drops both
|
|
31
|
+
// together, so a WARNING/ERROR badge can't outlive its entries.
|
|
32
|
+
// - One bulk `get(null)` yields every script's level in O(scripts)
|
|
33
|
+
// rather than O(scripts × entries) — the collections page needs this
|
|
34
|
+
// to render the Status column cheaply.
|
|
35
|
+
import { validLocation } from '../diagnostic-location.js';
|
|
36
|
+
import { isPageContext } from '../run-context.js';
|
|
37
|
+
import { isLoadState } from '../script-display.js';
|
|
38
|
+
// Ordinal compare for LogLevel: error > warning > info. Used to decide
|
|
39
|
+
// whether an append should bump the stored max.
|
|
40
|
+
const LEVEL_RANK = { info: 1, warning: 2, error: 3 };
|
|
41
|
+
function isHigher(candidate, prior) {
|
|
42
|
+
if (prior === undefined)
|
|
43
|
+
return true;
|
|
44
|
+
return LEVEL_RANK[candidate] > LEVEL_RANK[prior];
|
|
45
|
+
}
|
|
46
|
+
// Hard cap on retained entries per script. Older entries are dropped
|
|
47
|
+
// on append. 500 × ~200 bytes ≈ 100 KB per script — well inside the
|
|
48
|
+
// chrome.storage.session quota (10 MB total).
|
|
49
|
+
export const LOG_CAP = 500;
|
|
50
|
+
const logKey = (scriptId) => `log:${scriptId}`;
|
|
51
|
+
const levelKey = (scriptId) => `loglevel:${scriptId}`;
|
|
52
|
+
// Prefix used by the bulk-read path. Kept here so callers don't have
|
|
53
|
+
// to hardcode it.
|
|
54
|
+
export const LOG_LEVEL_KEY_PREFIX = 'loglevel:';
|
|
55
|
+
// Per-script promise chain. chrome.storage.session has no atomic
|
|
56
|
+
// append, so two concurrent appendScriptLog calls for the same id
|
|
57
|
+
// would both read the same prior array and the second .set would
|
|
58
|
+
// overwrite the first append. Chaining serializes them; the map entry
|
|
59
|
+
// is dropped once its chain drains so long-running sessions don't
|
|
60
|
+
// accumulate dead Promises.
|
|
61
|
+
//
|
|
62
|
+
// The map is module state, so the chain only orders writes made in THIS
|
|
63
|
+
// context. Only the worker writes the log today: it logs every
|
|
64
|
+
// injection and refresh event, and the viewer's Clear button goes
|
|
65
|
+
// through the worker's ops port. Pages share `script-store` code with
|
|
66
|
+
// the worker, though, and a page that logged through it would run a
|
|
67
|
+
// second chain on the same key, losing entries or resurrecting ones a
|
|
68
|
+
// clear had just removed. So, as a guard, a MUTATION made in a page
|
|
69
|
+
// hops to the worker (`writeViaWorker`) and the worker's chain stays
|
|
70
|
+
// the only one that touches the key. Reads don't hop: they aren't
|
|
71
|
+
// read-modify-write.
|
|
72
|
+
//
|
|
73
|
+
// What that guarantees is "the worker's chain orders everything that
|
|
74
|
+
// reaches it", NOT "a page's appends are ordered": two writes from one
|
|
75
|
+
// page are two messages, ordered by the IPC rather than by a chain.
|
|
76
|
+
const queues = new Map();
|
|
77
|
+
function enqueue(scriptId, task) {
|
|
78
|
+
const prev = queues.get(scriptId) ?? Promise.resolve();
|
|
79
|
+
// .then(task, task) so a thrown task doesn't poison the chain — the
|
|
80
|
+
// next queued task still runs against the post-throw state.
|
|
81
|
+
const next = prev.then(task, task);
|
|
82
|
+
queues.set(scriptId, next);
|
|
83
|
+
void next.finally(() => {
|
|
84
|
+
if (queues.get(scriptId) === next)
|
|
85
|
+
queues.delete(scriptId);
|
|
86
|
+
});
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
// Told of each entry the worker writes, for the events sm-dev runs
|
|
90
|
+
// hear (sm-dev-link.ts). Set by the worker only.
|
|
91
|
+
let entryListener = null;
|
|
92
|
+
export function onScriptLogEntry(listener) {
|
|
93
|
+
entryListener = listener;
|
|
94
|
+
}
|
|
95
|
+
// Append an entry. Every caller that reaches here observed something,
|
|
96
|
+
// and the Log tab is a transcript of observations, so the entry is
|
|
97
|
+
// always written. A caller that fires on its own schedule rather than
|
|
98
|
+
// on an observation — the injector's per-sync registration pass — is
|
|
99
|
+
// responsible for deciding whether anything happened before it calls.
|
|
100
|
+
export async function appendScriptLog(scriptId, input) {
|
|
101
|
+
if (isPageContext())
|
|
102
|
+
return writeViaWorker({ op: 'append', scriptId, input });
|
|
103
|
+
return enqueue(scriptId, () => writeEntry(scriptId, input));
|
|
104
|
+
}
|
|
105
|
+
// ---------- Page → worker relay ----------
|
|
106
|
+
export const SCRIPT_LOG_WRITE_MESSAGE = 'sm:script-log-write';
|
|
107
|
+
// Worker side. Runs the write on the worker's own per-script chain,
|
|
108
|
+
// which is the point of the hop.
|
|
109
|
+
export async function handleScriptLogWrite(req) {
|
|
110
|
+
if (req.op === 'append')
|
|
111
|
+
return enqueue(req.scriptId, () => writeEntry(req.scriptId, req.input));
|
|
112
|
+
return enqueue(req.scriptId, () => removeEntries(req.scriptId));
|
|
113
|
+
}
|
|
114
|
+
// Page side. Only the worker writes storage (docs/worker-writes.md).
|
|
115
|
+
// No page code logs today; this is the guard for one that does, through
|
|
116
|
+
// code it shares with the worker (see `queues` above).
|
|
117
|
+
// An entry the worker didn't ACKNOWLEDGE is sent once more: a worker
|
|
118
|
+
// stopped mid-message closes the channel unanswered, and the second
|
|
119
|
+
// message wakes a new one. Then it is dropped, and said so in this
|
|
120
|
+
// page's console.
|
|
121
|
+
//
|
|
122
|
+
// The ack has to be READ, not just awaited: a message nothing handled,
|
|
123
|
+
// and a handler that threw, both RESOLVE `sendMessage` (with
|
|
124
|
+
// `undefined`); only an unreachable worker rejects it. So `true` (sent
|
|
125
|
+
// by the listener once the write has landed) is the one answer that
|
|
126
|
+
// means it was written. A retry after a write that landed unacknowledged
|
|
127
|
+
// can log the entry twice, which beats losing it.
|
|
128
|
+
async function writeViaWorker(req) {
|
|
129
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
const ack = await chrome.runtime.sendMessage({ type: SCRIPT_LOG_WRITE_MESSAGE, ...req });
|
|
132
|
+
if (ack === true)
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
catch { /* no receiving end */ }
|
|
136
|
+
}
|
|
137
|
+
console.info(`[SourceMonkey] the background worker did not save a log ${req.op} for script ${req.scriptId}`);
|
|
138
|
+
}
|
|
139
|
+
// Shared read-modify-write body. Always called from inside `enqueue`.
|
|
140
|
+
async function writeEntry(scriptId, input) {
|
|
141
|
+
const lk = logKey(scriptId);
|
|
142
|
+
const mk = levelKey(scriptId);
|
|
143
|
+
const got = await chrome.storage.session.get([lk, mk]);
|
|
144
|
+
const prior = Array.isArray(got[lk]) ? got[lk] : [];
|
|
145
|
+
const priorLevel = got[mk];
|
|
146
|
+
if (input.collapse) {
|
|
147
|
+
const { key, message } = input.collapse;
|
|
148
|
+
let at = prior.length - 1;
|
|
149
|
+
while (at >= 0 && prior[at].collapseKey !== key)
|
|
150
|
+
at--;
|
|
151
|
+
if (at >= 0) {
|
|
152
|
+
const old = prior[at];
|
|
153
|
+
const count = (old.count ?? 1) + 1;
|
|
154
|
+
const level = isHigher(input.level, old.level) ? input.level : old.level;
|
|
155
|
+
const next = prior.slice();
|
|
156
|
+
next[at] = { ...old, level, count, message: message(count) };
|
|
157
|
+
const toSet = { [lk]: next };
|
|
158
|
+
if (isHigher(input.level, priorLevel))
|
|
159
|
+
toSet[mk] = input.level;
|
|
160
|
+
// Not a new entry, so the entry listener isn't told.
|
|
161
|
+
await chrome.storage.session.set(toSet);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const entry = {
|
|
166
|
+
timestamp: input.timestamp ?? Date.now(),
|
|
167
|
+
level: input.level,
|
|
168
|
+
source: input.source,
|
|
169
|
+
message: input.collapse ? input.collapse.message(1) : input.message,
|
|
170
|
+
};
|
|
171
|
+
if (input.tabId !== undefined)
|
|
172
|
+
entry.tabId = input.tabId;
|
|
173
|
+
if (input.collapse)
|
|
174
|
+
entry.collapseKey = input.collapse.key;
|
|
175
|
+
const location = validLocation(input.location);
|
|
176
|
+
if (location)
|
|
177
|
+
entry.location = location;
|
|
178
|
+
// Checked like `location`: the input may have crossed the page →
|
|
179
|
+
// worker message boundary, and a bad value here would render an
|
|
180
|
+
// empty badge.
|
|
181
|
+
if (isLoadState(input.state))
|
|
182
|
+
entry.state = input.state;
|
|
183
|
+
if (input.resetsLogLevel === true)
|
|
184
|
+
entry.resetsLogLevel = true;
|
|
185
|
+
const next = prior.length >= LOG_CAP
|
|
186
|
+
? [...prior.slice(prior.length - LOG_CAP + 1), entry]
|
|
187
|
+
: [...prior, entry];
|
|
188
|
+
const toSet = { [lk]: next };
|
|
189
|
+
// A load-state transition is badged by the row, not the sentinel —
|
|
190
|
+
// otherwise a `refresh-failed` entry would leave WARNING behind for
|
|
191
|
+
// the rest of the session after the row had already recovered. A
|
|
192
|
+
// resetting entry doesn't count itself either: it's the boundary,
|
|
193
|
+
// not the first event past it.
|
|
194
|
+
const counts = entry.state === undefined && !entry.resetsLogLevel;
|
|
195
|
+
if (counts && isHigher(input.level, priorLevel))
|
|
196
|
+
toSet[mk] = input.level;
|
|
197
|
+
await chrome.storage.session.set(toSet);
|
|
198
|
+
try {
|
|
199
|
+
entryListener?.(scriptId, entry, input.fromReport === true);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// A listener's failure is no reason to lose the entry's other effects.
|
|
203
|
+
}
|
|
204
|
+
// The reset rides on the same queued write as the entry, so it lands
|
|
205
|
+
// exactly between the rows before it and the rows after it: parse
|
|
206
|
+
// diagnostics replayed for the new version come next and bump the
|
|
207
|
+
// sentinel afresh. Skipped when there is nothing to clear — the
|
|
208
|
+
// common case, since most new content lands on a clean badge.
|
|
209
|
+
if (entry.resetsLogLevel && priorLevel !== undefined)
|
|
210
|
+
await chrome.storage.session.remove(mk);
|
|
211
|
+
}
|
|
212
|
+
export async function getScriptLog(scriptId) {
|
|
213
|
+
const key = logKey(scriptId);
|
|
214
|
+
const got = await chrome.storage.session.get(key);
|
|
215
|
+
return Array.isArray(got[key]) ? got[key] : [];
|
|
216
|
+
}
|
|
217
|
+
export async function clearScriptLog(scriptId) {
|
|
218
|
+
if (isPageContext())
|
|
219
|
+
return writeViaWorker({ op: 'clear', scriptId });
|
|
220
|
+
return enqueue(scriptId, () => removeEntries(scriptId));
|
|
221
|
+
}
|
|
222
|
+
// Shared removal body, the counterpart of `writeEntry`. Always called
|
|
223
|
+
// from inside `enqueue`.
|
|
224
|
+
async function removeEntries(scriptId) {
|
|
225
|
+
await chrome.storage.session.remove([logKey(scriptId), levelKey(scriptId)]);
|
|
226
|
+
}
|
|
227
|
+
// Drop the logs of scripts whose rows are going away — otherwise an
|
|
228
|
+
// orphaned session-storage entry would outlive the script it described
|
|
229
|
+
// and confuse any later renderer that scans log levels. Goes through
|
|
230
|
+
// `clearScriptLog` (and therefore the per-script queue, in whichever
|
|
231
|
+
// context owns it), so an append still in flight can't land after the
|
|
232
|
+
// removal and resurrect the row.
|
|
233
|
+
// Best-effort: a script whose log was never written isn't an error, and
|
|
234
|
+
// nothing that deletes a row should fail because of its log.
|
|
235
|
+
export async function forgetScriptLogs(scriptIds) {
|
|
236
|
+
await Promise.all(scriptIds.map((id) => clearScriptLog(id).catch(() => { })));
|
|
237
|
+
}
|
|
238
|
+
// Read the persisted "highest log level seen" sentinel for one script.
|
|
239
|
+
// Returns undefined when no script-originated entry has been appended
|
|
240
|
+
// since the last reset — a `clearScriptLog`, a `resetsLogLevel` entry
|
|
241
|
+
// (new content loaded), or the start of the Chrome session. Load-state
|
|
242
|
+
// entries don't count (see `LogEntry.state`). Between resets it only
|
|
243
|
+
// goes up — LOG_CAP rotation does NOT decrease it. Once a script has
|
|
244
|
+
// hit ERROR, rotation that drops the only error entry leaves the badge
|
|
245
|
+
// at ERROR until the next reset. We accept that drift because
|
|
246
|
+
// recomputing on every rotation would dominate the cost of an append
|
|
247
|
+
// and the log itself is session-scoped anyway.
|
|
248
|
+
export async function getMaxLogLevel(scriptId) {
|
|
249
|
+
const key = levelKey(scriptId);
|
|
250
|
+
const got = await chrome.storage.session.get(key);
|
|
251
|
+
const v = got[key];
|
|
252
|
+
return v === 'info' || v === 'warning' || v === 'error' ? v : undefined;
|
|
253
|
+
}
|
|
254
|
+
// Read every script's max log level in one storage round trip. The
|
|
255
|
+
// collections page uses this to render the Status column without N
|
|
256
|
+
// per-script reads. Returns a Map keyed by scriptId.
|
|
257
|
+
export async function getAllMaxLogLevels() {
|
|
258
|
+
const all = await chrome.storage.session.get(null);
|
|
259
|
+
const out = new Map();
|
|
260
|
+
for (const [key, value] of Object.entries(all)) {
|
|
261
|
+
if (!key.startsWith(LOG_LEVEL_KEY_PREFIX))
|
|
262
|
+
continue;
|
|
263
|
+
if (value === 'info' || value === 'warning' || value === 'error') {
|
|
264
|
+
out.set(key.slice(LOG_LEVEL_KEY_PREFIX.length), value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
// Convenience: fire-and-forget. Used by call sites that already have a
|
|
270
|
+
// console.log next to the event and don't want to await an extra
|
|
271
|
+
// promise. Errors swallow silently — the log itself isn't load-bearing
|
|
272
|
+
// and a chrome.storage.session failure shouldn't break the refresh
|
|
273
|
+
// path that's emitting the event.
|
|
274
|
+
export function logScriptEvent(scriptId, input) {
|
|
275
|
+
appendScriptLog(scriptId, input).catch(() => { });
|
|
276
|
+
}
|
|
277
|
+
// Test/debug hook to drain in-flight appends before asserting on the
|
|
278
|
+
// stored log. Used by unit tests directly, and by the SW's e2e
|
|
279
|
+
// `getScriptLog` accessor — both would otherwise race `logScriptEvent`,
|
|
280
|
+
// which returns before its write lands.
|
|
281
|
+
export async function _flushScriptLogQueueForTests(scriptId) {
|
|
282
|
+
// Loop rather than awaiting once: an append that chains on while we
|
|
283
|
+
// wait becomes the new tail, and returning after the old one would
|
|
284
|
+
// leave exactly the entry the caller is about to read still unwritten.
|
|
285
|
+
let awaited;
|
|
286
|
+
for (let tail = queues.get(scriptId); tail && tail !== awaited; tail = queues.get(scriptId)) {
|
|
287
|
+
awaited = tail;
|
|
288
|
+
await tail.catch(() => { });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Per-script record of a registration `chrome.userScripts` refused.
|
|
2
|
+
//
|
|
3
|
+
// A rejected spec is a state of its own: the script loaded fine (it has
|
|
4
|
+
// valid content, a valid header, and a status of `ok`), but Chrome
|
|
5
|
+
// would not accept the registration — a `@match` pattern it considers
|
|
6
|
+
// malformed is the usual cause — so the script silently never runs.
|
|
7
|
+
// The collections page and the viewer render that as REJECTED. See
|
|
8
|
+
// [docs/diagnostics.md](../docs/diagnostics.md#rejected-registrations).
|
|
9
|
+
//
|
|
10
|
+
// Rarer than it used to be: `matchPatternProblem` (src/url-match.ts)
|
|
11
|
+
// catches the malformed shapes we are sure about at parse time, so what
|
|
12
|
+
// reaches here is the rest of Chrome's grammar — a malformed port, a
|
|
13
|
+
// host its canonicalizer rejects, or whatever it tightens next.
|
|
14
|
+
//
|
|
15
|
+
// Written only by `src/extension/injector.ts` — `registerWithFallback` and the
|
|
16
|
+
// targeted retry are the only places that ever learn a spec was
|
|
17
|
+
// refused. Read by the collections page and the viewer for the row's
|
|
18
|
+
// status, and by `src/extension/background.ts` for the tab badge; all three
|
|
19
|
+
// watch `chrome.storage.session.onChanged` so they repaint without a
|
|
20
|
+
// reload.
|
|
21
|
+
//
|
|
22
|
+
// Why `chrome.storage.session`:
|
|
23
|
+
// - The fact describes Chrome's CURRENT registration set, which is
|
|
24
|
+
// itself dropped on browser shutdown. Persisting it to local
|
|
25
|
+
// storage would let a REJECTED badge outlive the registration
|
|
26
|
+
// attempt that produced it.
|
|
27
|
+
// - It has to outlive the service worker: MV3 evicts the SW freely
|
|
28
|
+
// and `syncRegistrations` runs again on every wake-up, so an
|
|
29
|
+
// in-memory Map would lose the state on every eviction.
|
|
30
|
+
//
|
|
31
|
+
// Why one map key rather than a key per script (the shape
|
|
32
|
+
// `script-log.ts` uses for log levels): the injector rewrites the whole
|
|
33
|
+
// set on every sync so that a script which left the active registration
|
|
34
|
+
// set — disabled, deleted, or newly INVALID — can't leave a stale
|
|
35
|
+
// REJECTED badge behind. Authoritative-set semantics want a single
|
|
36
|
+
// read/write; the alternative would need a full session scan to find
|
|
37
|
+
// the strays.
|
|
38
|
+
import { assertWorkerContext } from '../run-context.js';
|
|
39
|
+
// Session-storage key holding the whole `RejectionMap`.
|
|
40
|
+
export const REJECTIONS_KEY = 'injector-rejections';
|
|
41
|
+
// The one sentence every surface uses for a refused registration: the
|
|
42
|
+
// Log row, and the viewer's message bar. One message per problem —
|
|
43
|
+
// two copies would drift the moment either is reworded.
|
|
44
|
+
//
|
|
45
|
+
// `reason` is Chrome's own wording, appended rather than rephrased
|
|
46
|
+
// because it names the offending header value. See
|
|
47
|
+
// [docs/diagnostics.md](../docs/diagnostics.md#rejected-registrations).
|
|
48
|
+
export function describeRejection(reason) {
|
|
49
|
+
return `Chrome rejected this script's registration, so it will not run: ${reason}`;
|
|
50
|
+
}
|
|
51
|
+
// Coerce an unknown storage value into a RejectionMap, dropping any
|
|
52
|
+
// entry whose reason isn't a string. Used for both the direct read and
|
|
53
|
+
// the `storage.onChanged` newValue, so the two paths can't disagree
|
|
54
|
+
// about what a well-formed record looks like.
|
|
55
|
+
export function coerceRejectionMap(value) {
|
|
56
|
+
if (!value || typeof value !== 'object')
|
|
57
|
+
return {};
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const [id, message] of Object.entries(value)) {
|
|
60
|
+
if (typeof message === 'string')
|
|
61
|
+
out[id] = message;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
// Every registration the last sync could not place. Empty when nothing
|
|
66
|
+
// is rejected, which is the overwhelmingly common case.
|
|
67
|
+
export async function readRejections() {
|
|
68
|
+
try {
|
|
69
|
+
const got = await chrome.storage.session.get(REJECTIONS_KEY);
|
|
70
|
+
return coerceRejectionMap(got[REJECTIONS_KEY]);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Session storage being unavailable shouldn't break a sync; the
|
|
74
|
+
// worst case is a status cell that doesn't say REJECTED.
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Replace the stored map with `next`, but only when it actually differs
|
|
79
|
+
// from `prior`. The no-rejections case — every sync on a healthy
|
|
80
|
+
// profile — then costs one read and no write, and no needless
|
|
81
|
+
// `storage.onChanged` event fans out to the open UI pages.
|
|
82
|
+
//
|
|
83
|
+
// Note this is about the STORAGE write, not about logging: a rejection
|
|
84
|
+
// that persists across syncs writes nothing here but still gets a fresh
|
|
85
|
+
// Log row each time, because "we tried again and Chrome said no again"
|
|
86
|
+
// is a fact about this sync. See `registerWithFallback`.
|
|
87
|
+
export async function writeRejections(prior, next) {
|
|
88
|
+
if (sameRejections(prior, next))
|
|
89
|
+
return;
|
|
90
|
+
assertWorkerContext('write storage');
|
|
91
|
+
try {
|
|
92
|
+
await chrome.storage.session.set({ [REJECTIONS_KEY]: next });
|
|
93
|
+
}
|
|
94
|
+
catch { /* best-effort: the badge isn't load-bearing */ }
|
|
95
|
+
}
|
|
96
|
+
function sameRejections(a, b) {
|
|
97
|
+
const keys = Object.keys(a);
|
|
98
|
+
if (keys.length !== Object.keys(b).length)
|
|
99
|
+
return false;
|
|
100
|
+
for (const id of keys)
|
|
101
|
+
if (a[id] !== b[id])
|
|
102
|
+
return false;
|
|
103
|
+
return true;
|
|
104
|
+
}
|