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,19 @@
|
|
|
1
|
+
// Keeps Chrome's "ResizeObserver loop completed with undelivered
|
|
2
|
+
// notifications" out of chrome://extensions/?errors.
|
|
3
|
+
//
|
|
4
|
+
// Chrome raises it when a ResizeObserver callback resizes something it
|
|
5
|
+
// watches again within the same frame. The notification isn't lost: it
|
|
6
|
+
// is delivered on the next frame, so it is harmless. Web pages don't log
|
|
7
|
+
// it, but on an extension page it lands in the Errors panel, which makes
|
|
8
|
+
// SourceMonkey look broken.
|
|
9
|
+
//
|
|
10
|
+
// Canceling the window `error` event stops Chrome from reporting it. The
|
|
11
|
+
// text is Chrome's own, so matching on it is the only way to tell it
|
|
12
|
+
// apart.
|
|
13
|
+
const LOOP_MESSAGE = 'ResizeObserver loop';
|
|
14
|
+
export function ignoreResizeObserverLoopErrors() {
|
|
15
|
+
window.addEventListener('error', (ev) => {
|
|
16
|
+
if (ev.message?.startsWith(LOOP_MESSAGE))
|
|
17
|
+
ev.preventDefault();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// The service worker's copy of the structural rows (the collection
|
|
2
|
+
// index, collection rows, script rows) and the active times, so a lookup
|
|
3
|
+
// across every script (finding a copy's related copies on an enable
|
|
4
|
+
// click, say) reads memory instead of storage. See
|
|
5
|
+
// docs/worker-writes.md#the-row-cache.
|
|
6
|
+
//
|
|
7
|
+
// Right by construction, not by expiry:
|
|
8
|
+
// - Only the worker writes these rows (docs/worker-writes.md#rules),
|
|
9
|
+
// and `installRowCache` wraps this context's `chrome.storage.local`
|
|
10
|
+
// writes, so every write it makes lands here as it lands in storage.
|
|
11
|
+
// - As a safety net, a change event this context didn't cause (a write
|
|
12
|
+
// from anywhere else) drops the copy, and the next full read loads
|
|
13
|
+
// it again from storage.
|
|
14
|
+
//
|
|
15
|
+
// Loaded by the first read that wants every row: at a worker start, the
|
|
16
|
+
// match index, the log labels, sticky numbers or the registration sync,
|
|
17
|
+
// which share the one load. A point read before then goes to storage.
|
|
18
|
+
//
|
|
19
|
+
// Rows are stored frozen, as the JSON storage would keep. Most reads hand
|
|
20
|
+
// out copies, which a caller may change. `sharedRows` hands out the
|
|
21
|
+
// frozen rows themselves, for the worker's own readers that only read
|
|
22
|
+
// (the registration sync, the match index, the log labels, the
|
|
23
|
+
// numbers): a change to one throws, so it can't reach the cache.
|
|
24
|
+
import { ENABLEMENT_TIMES_KEY, isStructuralKey, SCRIPT_PREFIX } from './structural-keys.js';
|
|
25
|
+
import { identityKey, locationKey } from './script-keys.js';
|
|
26
|
+
import { scanStorageRows } from './storage-scan.js';
|
|
27
|
+
import { dlog } from '../debug-log.js';
|
|
28
|
+
// The active times too: every enable re-reads them inside its lock,
|
|
29
|
+
// and from storage that read waits behind whatever else is reading it.
|
|
30
|
+
function isCachedKey(key) {
|
|
31
|
+
return isStructuralKey(key) || key === ENABLEMENT_TIMES_KEY;
|
|
32
|
+
}
|
|
33
|
+
let installed = false;
|
|
34
|
+
let rows = null;
|
|
35
|
+
// Bumped by every change to the cached rows, so a load that overlapped
|
|
36
|
+
// one knows its read may predate it.
|
|
37
|
+
let generation = 0;
|
|
38
|
+
let loading = null;
|
|
39
|
+
// How many times it was read from storage, for tests.
|
|
40
|
+
let loads = 0;
|
|
41
|
+
// Every write this context makes to a cached key gets the next number.
|
|
42
|
+
// A write lands in the cache when its change event arrives or its call
|
|
43
|
+
// returns, whichever is first, and an older write never replaces a
|
|
44
|
+
// newer one (`applied`).
|
|
45
|
+
let nextSeq = 0;
|
|
46
|
+
// Writes at or below this number were in storage when the cache loaded.
|
|
47
|
+
let loadedAtSeq = 0;
|
|
48
|
+
const applied = new Map();
|
|
49
|
+
// Writes to cached keys started and not yet returned: a load waits for
|
|
50
|
+
// none.
|
|
51
|
+
let inFlight = 0;
|
|
52
|
+
let idleWaiters = [];
|
|
53
|
+
const pending = new Map();
|
|
54
|
+
const PENDING_TTL_MS = 30_000;
|
|
55
|
+
const MAX_PENDING_PER_KEY = 16;
|
|
56
|
+
const REMOVED = '\u0000removed';
|
|
57
|
+
// Key order doesn't count: Chrome keeps an object's keys sorted, so a
|
|
58
|
+
// change event's `newValue` can list them in another order than the
|
|
59
|
+
// value written.
|
|
60
|
+
function stableJson(value) {
|
|
61
|
+
if (value === undefined)
|
|
62
|
+
return REMOVED;
|
|
63
|
+
return JSON.stringify(value, (_key, v) => {
|
|
64
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
65
|
+
return v;
|
|
66
|
+
const sorted = {};
|
|
67
|
+
for (const k of Object.keys(v).sort())
|
|
68
|
+
sorted[k] = v[k];
|
|
69
|
+
return sorted;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function apply(key, json, seq) {
|
|
73
|
+
if (!rows || seq <= loadedAtSeq || seq <= (applied.get(key) ?? 0))
|
|
74
|
+
return;
|
|
75
|
+
unindex(key);
|
|
76
|
+
if (json === REMOVED)
|
|
77
|
+
rows.delete(key);
|
|
78
|
+
else {
|
|
79
|
+
rows.set(key, deepFreeze(JSON.parse(json)));
|
|
80
|
+
index(key);
|
|
81
|
+
}
|
|
82
|
+
applied.set(key, seq);
|
|
83
|
+
}
|
|
84
|
+
function drop(reason) {
|
|
85
|
+
if (rows)
|
|
86
|
+
dlog(`[SourceMonkey] row cache dropped: ${reason}`);
|
|
87
|
+
rows = null;
|
|
88
|
+
applied.clear();
|
|
89
|
+
byLocation.clear();
|
|
90
|
+
byIdentity.clear();
|
|
91
|
+
byCollection.clear();
|
|
92
|
+
generation++;
|
|
93
|
+
}
|
|
94
|
+
// ---------- Indexes ----------
|
|
95
|
+
//
|
|
96
|
+
// Script ids by the two keys that make copies of one script related
|
|
97
|
+
// (script-identity.ts): the canonical location, and `@namespace` +
|
|
98
|
+
// `@name`. Kept with the rows, so they change exactly when a row does.
|
|
99
|
+
const byLocation = new Map();
|
|
100
|
+
const byIdentity = new Map();
|
|
101
|
+
// And by the collection each row names (`collectionId`), which isn't
|
|
102
|
+
// always one that lists it (see `reconcileScriptMembership`).
|
|
103
|
+
const byCollection = new Map();
|
|
104
|
+
function keysOf(row) {
|
|
105
|
+
return {
|
|
106
|
+
location: typeof row.fileUrl === 'string' ? locationKey(row.fileUrl) : null,
|
|
107
|
+
identity: identityKey(row.header ?? null),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function addTo(map, key, id) {
|
|
111
|
+
if (key === null)
|
|
112
|
+
return;
|
|
113
|
+
let ids = map.get(key);
|
|
114
|
+
if (!ids)
|
|
115
|
+
map.set(key, ids = new Set());
|
|
116
|
+
ids.add(id);
|
|
117
|
+
}
|
|
118
|
+
function removeFrom(map, key, id) {
|
|
119
|
+
if (key === null)
|
|
120
|
+
return;
|
|
121
|
+
const ids = map.get(key);
|
|
122
|
+
if (!ids)
|
|
123
|
+
return;
|
|
124
|
+
ids.delete(id);
|
|
125
|
+
if (ids.size === 0)
|
|
126
|
+
map.delete(key);
|
|
127
|
+
}
|
|
128
|
+
function index(key) {
|
|
129
|
+
if (!key.startsWith(SCRIPT_PREFIX))
|
|
130
|
+
return;
|
|
131
|
+
const row = rows?.get(key);
|
|
132
|
+
if (!row?.id)
|
|
133
|
+
return;
|
|
134
|
+
const { location, identity } = keysOf(row);
|
|
135
|
+
addTo(byLocation, location, row.id);
|
|
136
|
+
addTo(byIdentity, identity, row.id);
|
|
137
|
+
addTo(byCollection, row.collectionId ?? null, row.id);
|
|
138
|
+
}
|
|
139
|
+
function unindex(key) {
|
|
140
|
+
if (!key.startsWith(SCRIPT_PREFIX))
|
|
141
|
+
return;
|
|
142
|
+
const row = rows?.get(key);
|
|
143
|
+
if (!row?.id)
|
|
144
|
+
return;
|
|
145
|
+
const { location, identity } = keysOf(row);
|
|
146
|
+
removeFrom(byLocation, location, row.id);
|
|
147
|
+
removeFrom(byIdentity, identity, row.id);
|
|
148
|
+
removeFrom(byCollection, row.collectionId ?? null, row.id);
|
|
149
|
+
}
|
|
150
|
+
// The ids of every script row naming collection `id`, loading the cache
|
|
151
|
+
// first if needed. Null when the cache isn't on in this context.
|
|
152
|
+
export async function scriptIdsInCollection(id) {
|
|
153
|
+
if (!(await loaded()))
|
|
154
|
+
return null;
|
|
155
|
+
return [...(byCollection.get(id) ?? [])];
|
|
156
|
+
}
|
|
157
|
+
// The ids of every stored copy related to any of `scripts` (the same
|
|
158
|
+
// location, or the same `@namespace` + `@name`), themselves included,
|
|
159
|
+
// loading the cache first if needed. Null when the cache isn't on in
|
|
160
|
+
// this context: the caller scans the rows itself.
|
|
161
|
+
export async function relatedScriptIds(scripts) {
|
|
162
|
+
if (!(await loaded()))
|
|
163
|
+
return null;
|
|
164
|
+
const out = new Set();
|
|
165
|
+
for (const script of scripts) {
|
|
166
|
+
if (script.id)
|
|
167
|
+
out.add(script.id);
|
|
168
|
+
const { location, identity } = keysOf(script);
|
|
169
|
+
for (const id of (location !== null && byLocation.get(location)) || [])
|
|
170
|
+
out.add(id);
|
|
171
|
+
for (const id of (identity !== null && byIdentity.get(identity)) || [])
|
|
172
|
+
out.add(id);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
// Run one of this context's writes, `entries` being its cached keys and
|
|
177
|
+
// their values (`REMOVED` for a removal).
|
|
178
|
+
async function track(entries, write) {
|
|
179
|
+
if (entries.length === 0)
|
|
180
|
+
return await write();
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
const mine = entries.map(([key, json]) => {
|
|
183
|
+
const entry = { json, seq: ++nextSeq, at: now };
|
|
184
|
+
const list = (pending.get(key) ?? []).filter((p) => now - p.at < PENDING_TTL_MS);
|
|
185
|
+
list.push(entry);
|
|
186
|
+
if (list.length > MAX_PENDING_PER_KEY)
|
|
187
|
+
list.shift();
|
|
188
|
+
pending.set(key, list);
|
|
189
|
+
return [key, entry];
|
|
190
|
+
});
|
|
191
|
+
inFlight++;
|
|
192
|
+
try {
|
|
193
|
+
await write();
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
for (const [key, entry] of mine) {
|
|
197
|
+
const list = pending.get(key);
|
|
198
|
+
const at = list ? list.indexOf(entry) : -1;
|
|
199
|
+
if (at >= 0)
|
|
200
|
+
list.splice(at, 1);
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
if (--inFlight === 0) {
|
|
206
|
+
const waiters = idleWaiters;
|
|
207
|
+
idleWaiters = [];
|
|
208
|
+
for (const wake of waiters)
|
|
209
|
+
wake();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
generation++;
|
|
213
|
+
for (const [key, entry] of mine)
|
|
214
|
+
apply(key, entry.json, entry.seq);
|
|
215
|
+
}
|
|
216
|
+
// Wrap this context's writes, and watch for others'. Called once, by the
|
|
217
|
+
// service worker at startup, before anything writes. Its change listener
|
|
218
|
+
// runs before every listener installed after it (one installed at module
|
|
219
|
+
// load, the popup's script cache, only drops entries). Nothing else
|
|
220
|
+
// turns the cache on, so pages and unit tests read storage as before.
|
|
221
|
+
//
|
|
222
|
+
// `checkWrite` sees the keys of every `set` first, and throws to refuse
|
|
223
|
+
// it: the store's check that script rows are written under its lock
|
|
224
|
+
// (`checkScriptRowWrite`).
|
|
225
|
+
export function installRowCache(checkWrite = () => undefined) {
|
|
226
|
+
if (installed)
|
|
227
|
+
return;
|
|
228
|
+
installed = true;
|
|
229
|
+
const area = chrome.storage.local;
|
|
230
|
+
const set = area.set.bind(area);
|
|
231
|
+
const remove = area.remove.bind(area);
|
|
232
|
+
const clear = area.clear.bind(area);
|
|
233
|
+
area.set = ((items) => {
|
|
234
|
+
try {
|
|
235
|
+
checkWrite(Object.keys(items));
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return Promise.reject(err);
|
|
239
|
+
}
|
|
240
|
+
// A key set to `undefined` is left as it is by Chrome.
|
|
241
|
+
const entries = Object.entries(items)
|
|
242
|
+
.filter(([key, value]) => isCachedKey(key) && value !== undefined)
|
|
243
|
+
.map(([key, value]) => [key, stableJson(value)]);
|
|
244
|
+
return track(entries, () => set(items));
|
|
245
|
+
});
|
|
246
|
+
area.remove = ((keys) => {
|
|
247
|
+
const entries = (Array.isArray(keys) ? keys : [keys])
|
|
248
|
+
.filter(isCachedKey)
|
|
249
|
+
.map((key) => [key, REMOVED]);
|
|
250
|
+
return track(entries, () => remove(keys));
|
|
251
|
+
});
|
|
252
|
+
area.clear = (async () => {
|
|
253
|
+
await clear();
|
|
254
|
+
pending.clear();
|
|
255
|
+
drop('storage cleared');
|
|
256
|
+
});
|
|
257
|
+
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
258
|
+
if (areaName !== 'local')
|
|
259
|
+
return;
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
for (const [key, change] of Object.entries(changes)) {
|
|
262
|
+
if (!isCachedKey(key))
|
|
263
|
+
continue;
|
|
264
|
+
const json = stableJson(change.newValue);
|
|
265
|
+
const list = (pending.get(key) ?? []).filter((p) => now - p.at < PENDING_TTL_MS);
|
|
266
|
+
const at = list.findIndex((p) => p.json === json);
|
|
267
|
+
if (at >= 0) {
|
|
268
|
+
// Ours. Earlier writes to this key that got no event of their
|
|
269
|
+
// own are passed now too.
|
|
270
|
+
apply(key, json, list[at].seq);
|
|
271
|
+
list.splice(0, at + 1);
|
|
272
|
+
}
|
|
273
|
+
if (list.length > 0)
|
|
274
|
+
pending.set(key, list);
|
|
275
|
+
else
|
|
276
|
+
pending.delete(key);
|
|
277
|
+
if (at >= 0)
|
|
278
|
+
continue;
|
|
279
|
+
if (rows)
|
|
280
|
+
drop(`${key} written elsewhere`);
|
|
281
|
+
else
|
|
282
|
+
generation++;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
// Every cached row, loaded first when needed. Null when the cache isn't
|
|
287
|
+
// on in this context, or a write kept landing during the load (three
|
|
288
|
+
// tries): the caller reads storage itself then, and the next read tries
|
|
289
|
+
// to load again. Under constant writes (a Refresh All) that is a scan
|
|
290
|
+
// per read until the writes pause.
|
|
291
|
+
async function loaded() {
|
|
292
|
+
if (!installed)
|
|
293
|
+
return null;
|
|
294
|
+
if (rows)
|
|
295
|
+
return rows;
|
|
296
|
+
loading ??= (async () => {
|
|
297
|
+
try {
|
|
298
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
299
|
+
// With no write in flight, every write numbered so far is in
|
|
300
|
+
// what the scan reads.
|
|
301
|
+
while (inFlight > 0)
|
|
302
|
+
await new Promise((wake) => idleWaiters.push(wake));
|
|
303
|
+
const start = generation;
|
|
304
|
+
const startSeq = nextSeq;
|
|
305
|
+
loads++;
|
|
306
|
+
const read = await scanStorageRows('row cache', isCachedKey);
|
|
307
|
+
if (generation !== start)
|
|
308
|
+
continue;
|
|
309
|
+
rows = new Map(Object.entries(read).map(([k, v]) => [k, deepFreeze(v)]));
|
|
310
|
+
byLocation.clear();
|
|
311
|
+
byIdentity.clear();
|
|
312
|
+
byCollection.clear();
|
|
313
|
+
for (const key of rows.keys())
|
|
314
|
+
index(key);
|
|
315
|
+
loadedAtSeq = startSeq;
|
|
316
|
+
applied.clear();
|
|
317
|
+
return rows;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
loading = null;
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
325
|
+
return await loading;
|
|
326
|
+
}
|
|
327
|
+
// `keys`' rows, as copies, for the keys the cache holds, when it is
|
|
328
|
+
// loaded: `rest` is the keys it doesn't keep, for the caller to read
|
|
329
|
+
// from storage. Null when it isn't loaded, without loading it.
|
|
330
|
+
export function cachedRows(keys) {
|
|
331
|
+
if (!rows)
|
|
332
|
+
return null;
|
|
333
|
+
const found = {};
|
|
334
|
+
const rest = [];
|
|
335
|
+
for (const key of keys) {
|
|
336
|
+
if (!isCachedKey(key))
|
|
337
|
+
rest.push(key);
|
|
338
|
+
else if (rows.has(key))
|
|
339
|
+
found[key] = structuredClone(rows.get(key));
|
|
340
|
+
}
|
|
341
|
+
return { found, rest };
|
|
342
|
+
}
|
|
343
|
+
// Every script row, as copies, loading the cache first if needed. Null
|
|
344
|
+
// when the cache isn't on in this context.
|
|
345
|
+
export async function cachedScriptRows() {
|
|
346
|
+
return await cachedRowsWhere((key) => key.startsWith(SCRIPT_PREFIX));
|
|
347
|
+
}
|
|
348
|
+
// Every cached row (index, collections, scripts, active times), as
|
|
349
|
+
// copies, likewise.
|
|
350
|
+
export async function allCachedRows() {
|
|
351
|
+
return await cachedRowsWhere(() => true);
|
|
352
|
+
}
|
|
353
|
+
async function cachedRowsWhere(keep) {
|
|
354
|
+
const all = await loaded();
|
|
355
|
+
if (!all)
|
|
356
|
+
return null;
|
|
357
|
+
const out = {};
|
|
358
|
+
for (const [key, value] of all) {
|
|
359
|
+
if (keep(key))
|
|
360
|
+
out[key] = structuredClone(value);
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
// Every cached row whose key `keep` accepts, the frozen rows themselves
|
|
365
|
+
// rather than copies, loading the cache first if needed. Null when the
|
|
366
|
+
// cache isn't on in this context: the caller reads storage then.
|
|
367
|
+
export async function sharedRows(keep) {
|
|
368
|
+
const all = await loaded();
|
|
369
|
+
if (!all)
|
|
370
|
+
return null;
|
|
371
|
+
const out = {};
|
|
372
|
+
for (const [key, value] of all)
|
|
373
|
+
if (keep(key))
|
|
374
|
+
out[key] = value;
|
|
375
|
+
return out;
|
|
376
|
+
}
|
|
377
|
+
function deepFreeze(value) {
|
|
378
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
379
|
+
Object.freeze(value);
|
|
380
|
+
for (const v of Object.values(value))
|
|
381
|
+
deepFreeze(v);
|
|
382
|
+
}
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
export function rowCacheLoadsForTests() {
|
|
386
|
+
return loads;
|
|
387
|
+
}
|