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,413 @@
|
|
|
1
|
+
// Storage format 2: script rows without their bodies. Each body (a
|
|
2
|
+
// script's `bodyText`, each `@require` source, each `@resource`'s bytes)
|
|
3
|
+
// is a `blob:<sha256>` row, shared by every row whose body has the same
|
|
4
|
+
// bytes, and the script row points at it with a `BlobRef`. See
|
|
5
|
+
// docs/storage.md#content-blobs.
|
|
6
|
+
//
|
|
7
|
+
// This module converts between the two shapes:
|
|
8
|
+
// - `toStorage`: a full `StoredScript` (bodies inline, as the rest of
|
|
9
|
+
// the code uses it) → the small row plus the blobs it needs.
|
|
10
|
+
// - `fromStorage`: small rows → full `StoredScript`s, reading their
|
|
11
|
+
// blobs in one call.
|
|
12
|
+
// and runs the one-time migration of format 1 rows (bodies inline in
|
|
13
|
+
// storage too), and the cleanup of blobs no row points at any more.
|
|
14
|
+
//
|
|
15
|
+
// Both directions take each body as it finds it: one already inline (a
|
|
16
|
+
// format 1 row, or a full script handed to a write) stays as it is.
|
|
17
|
+
import { assertWorkerContext } from '../run-context.js';
|
|
18
|
+
import { base64ToBytes, bytesToBase64, sha256Hex } from '../bytes.js';
|
|
19
|
+
import { SCRIPT_PREFIX } from './structural-keys.js';
|
|
20
|
+
import { scanAllStorage } from './storage-scan.js';
|
|
21
|
+
import { dlog } from '../debug-log.js';
|
|
22
|
+
import { formatMs } from '../phase-timing.js';
|
|
23
|
+
export const BLOB_PREFIX = 'blob:';
|
|
24
|
+
export const STORE_FORMAT_KEY = 'storeFormat';
|
|
25
|
+
export const STORE_FORMAT = 2;
|
|
26
|
+
export const blobKey = (hash) => `${BLOB_PREFIX}${hash}`;
|
|
27
|
+
// Shown on a script whose row points at a blob that isn't there (or
|
|
28
|
+
// that this build can't decode): a row edited by hand, or a cleanup bug
|
|
29
|
+
// (see `sweepUnusedBlobs`). Refresh fetches the file again, as for one
|
|
30
|
+
// that failed to load.
|
|
31
|
+
export const MISSING_BLOB_MESSAGE = 'Part of the stored copy of this script is missing. Refresh the script to load it again.';
|
|
32
|
+
const encoder = new TextEncoder();
|
|
33
|
+
// `ignoreBOM` keeps a leading U+FEFF: the blob's bytes are exactly what
|
|
34
|
+
// was stored, and a require reading a blob a resource wrote first must
|
|
35
|
+
// get back the same text the require itself would have stored.
|
|
36
|
+
const decoder = new TextDecoder('utf-8', { ignoreBOM: true });
|
|
37
|
+
// The ref and stored data behind each object `fromStorage` built (a
|
|
38
|
+
// script, a require or resource entry), so writing it back unchanged
|
|
39
|
+
// doesn't hash its body again. Entries survive the `{...script}` copies
|
|
40
|
+
// the store's writers make; a script object often doesn't, and its body
|
|
41
|
+
// is hashed again then.
|
|
42
|
+
const knownRefs = new WeakMap();
|
|
43
|
+
// Lets `to` reuse the body ref `from` was read with, when its body is
|
|
44
|
+
// the same text: a refresh rebuilds a new script object whose body came
|
|
45
|
+
// from the copy it started from, and would otherwise hash it again.
|
|
46
|
+
export function carryBodyRef(from, to) {
|
|
47
|
+
const known = from ? knownRefs.get(from) : undefined;
|
|
48
|
+
if (known && known.data === to.bodyText)
|
|
49
|
+
knownRefs.set(to, known);
|
|
50
|
+
}
|
|
51
|
+
function reuse(owner, data, encoding, blobs) {
|
|
52
|
+
const known = knownRefs.get(owner);
|
|
53
|
+
if (known?.data !== data)
|
|
54
|
+
return null;
|
|
55
|
+
// Offered again: the write only stores it if it's gone.
|
|
56
|
+
blobs.set(blobKey(known.ref.hash), { encoding, data });
|
|
57
|
+
return known.ref;
|
|
58
|
+
}
|
|
59
|
+
async function textBlob(owner, text, blobs) {
|
|
60
|
+
const known = reuse(owner, text, 'text', blobs);
|
|
61
|
+
if (known)
|
|
62
|
+
return known;
|
|
63
|
+
const bytes = encoder.encode(text);
|
|
64
|
+
const hash = await sha256Hex(bytes);
|
|
65
|
+
blobs.set(blobKey(hash), { encoding: 'text', data: text });
|
|
66
|
+
return { hash, size: bytes.length };
|
|
67
|
+
}
|
|
68
|
+
async function base64Blob(owner, base64, blobs) {
|
|
69
|
+
const known = reuse(owner, base64, 'base64', blobs);
|
|
70
|
+
if (known)
|
|
71
|
+
return known;
|
|
72
|
+
const bytes = base64ToBytes(base64);
|
|
73
|
+
const hash = await sha256Hex(bytes);
|
|
74
|
+
blobs.set(blobKey(hash), { encoding: 'base64', data: base64 });
|
|
75
|
+
return { hash, size: bytes.byteLength };
|
|
76
|
+
}
|
|
77
|
+
// Does a stored row carry any body inline (format 1, or a full script
|
|
78
|
+
// handed to a write)?
|
|
79
|
+
export function hasInlineBodies(value) {
|
|
80
|
+
if (value === null || typeof value !== 'object')
|
|
81
|
+
return false;
|
|
82
|
+
const row = value;
|
|
83
|
+
return typeof row.bodyText === 'string'
|
|
84
|
+
|| (row.requires ?? []).some((r) => typeof r.source === 'string')
|
|
85
|
+
|| (row.resources ?? []).some((r) => typeof r.bytesBase64 === 'string');
|
|
86
|
+
}
|
|
87
|
+
// A full script (or a format 1 row) → its small row, adding the blobs it
|
|
88
|
+
// needs to `blobs`. A body already in blob form passes through.
|
|
89
|
+
export async function toStorage(script, blobs) {
|
|
90
|
+
const { bodyText, requires, resources, ...rest } = script;
|
|
91
|
+
const row = { ...rest };
|
|
92
|
+
// A row that never loaded has no body to point at (`size` is null
|
|
93
|
+
// exactly then); every other row does, an empty body included.
|
|
94
|
+
if (typeof bodyText === 'string' && (bodyText !== '' || script.size !== null)) {
|
|
95
|
+
row.bodyBlobRef = await textBlob(script, bodyText, blobs);
|
|
96
|
+
}
|
|
97
|
+
if (requires) {
|
|
98
|
+
row.requires = await Promise.all(requires.map(async (r) => {
|
|
99
|
+
const { source, ...meta } = r;
|
|
100
|
+
return typeof source === 'string'
|
|
101
|
+
? { ...meta, blobRef: await textBlob(r, source, blobs) }
|
|
102
|
+
: r;
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
if (resources) {
|
|
106
|
+
row.resources = await Promise.all(resources.map(async (r) => {
|
|
107
|
+
const { bytesBase64, ...meta } = r;
|
|
108
|
+
return typeof bytesBase64 === 'string'
|
|
109
|
+
? { ...meta, blobRef: await base64Blob(r, bytesBase64, blobs) }
|
|
110
|
+
: r;
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
return row;
|
|
114
|
+
}
|
|
115
|
+
// A blob's content in the form a reader wants, or null for an encoding
|
|
116
|
+
// this build doesn't know (written by a newer one), which reads like a
|
|
117
|
+
// missing blob rather than as garbage.
|
|
118
|
+
function blobText(blob) {
|
|
119
|
+
if (blob.encoding === 'text')
|
|
120
|
+
return blob.data;
|
|
121
|
+
if (blob.encoding === 'base64')
|
|
122
|
+
return decoder.decode(base64ToBytes(blob.data));
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function blobBase64(blob) {
|
|
126
|
+
if (blob.encoding === 'base64')
|
|
127
|
+
return blob.data;
|
|
128
|
+
if (blob.encoding === 'text')
|
|
129
|
+
return bytesToBase64(encoder.encode(blob.data));
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// The blob keys a stored row points at. A format 1 row has none.
|
|
133
|
+
export function blobKeysOf(value) {
|
|
134
|
+
if (value === null || typeof value !== 'object')
|
|
135
|
+
return [];
|
|
136
|
+
const row = value;
|
|
137
|
+
const keys = [];
|
|
138
|
+
if (row.bodyBlobRef)
|
|
139
|
+
keys.push(blobKey(row.bodyBlobRef.hash));
|
|
140
|
+
for (const r of row.requires ?? [])
|
|
141
|
+
if (r.blobRef)
|
|
142
|
+
keys.push(blobKey(r.blobRef.hash));
|
|
143
|
+
for (const r of row.resources ?? [])
|
|
144
|
+
if (r.blobRef)
|
|
145
|
+
keys.push(blobKey(r.blobRef.hash));
|
|
146
|
+
return keys;
|
|
147
|
+
}
|
|
148
|
+
// A row whose blob is gone keeps its metadata but can't run. As an
|
|
149
|
+
// `invalid` row, the next refresh fetches it whatever its validators
|
|
150
|
+
// say; dropping them (or the file entry, which a refresh then fetches
|
|
151
|
+
// like a new one) is a backstop for that.
|
|
152
|
+
//
|
|
153
|
+
// Only readers that load bodies see this. The registration sync is one,
|
|
154
|
+
// and it saves the row this way (`markMissingBlobs`), so the readers of
|
|
155
|
+
// small rows (pages, the match index) see it from then on.
|
|
156
|
+
//
|
|
157
|
+
// A write of a full script built here stores it as it is: an invalid
|
|
158
|
+
// row with an empty body. That's consistent, and the refresh repairs it
|
|
159
|
+
// the same way.
|
|
160
|
+
function withMissingBlob(script, bodyMissing) {
|
|
161
|
+
const out = { ...script, status: 'invalid', errorMessage: MISSING_BLOB_MESSAGE };
|
|
162
|
+
delete out.errorLocation;
|
|
163
|
+
if (bodyMissing) {
|
|
164
|
+
delete out.contentHash;
|
|
165
|
+
delete out.fsMtime;
|
|
166
|
+
delete out.httpEtag;
|
|
167
|
+
delete out.httpLastModified;
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
function hydrate(row, blobs) {
|
|
172
|
+
const get = (ref) => blobs[blobKey(ref.hash)];
|
|
173
|
+
// `bodyText` is there only on a row with its body inline.
|
|
174
|
+
const { bodyBlobRef, requires, resources, ...rest } = row;
|
|
175
|
+
let missing = false;
|
|
176
|
+
let bodyMissing = false;
|
|
177
|
+
const script = { ...rest, bodyText: rest.bodyText ?? '' };
|
|
178
|
+
// An inline body wins over a ref: it's what a format 1 row, or a test
|
|
179
|
+
// writing one, put there.
|
|
180
|
+
if (typeof rest.bodyText !== 'string' && bodyBlobRef) {
|
|
181
|
+
const blob = get(bodyBlobRef);
|
|
182
|
+
const text = blob ? blobText(blob) : null;
|
|
183
|
+
if (text === null)
|
|
184
|
+
missing = bodyMissing = true;
|
|
185
|
+
else {
|
|
186
|
+
script.bodyText = text;
|
|
187
|
+
knownRefs.set(script, { data: text, ref: bodyBlobRef });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (requires) {
|
|
191
|
+
script.requires = requires.flatMap((r) => {
|
|
192
|
+
const inline = r;
|
|
193
|
+
if (typeof inline.source === 'string' || !r.blobRef)
|
|
194
|
+
return [inline];
|
|
195
|
+
const blob = get(r.blobRef);
|
|
196
|
+
const source = blob ? blobText(blob) : null;
|
|
197
|
+
if (source === null) {
|
|
198
|
+
missing = true;
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const { blobRef, ...meta } = r;
|
|
202
|
+
const entry = { ...meta, source };
|
|
203
|
+
knownRefs.set(entry, { data: source, ref: blobRef });
|
|
204
|
+
return [entry];
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (resources) {
|
|
208
|
+
script.resources = resources.flatMap((r) => {
|
|
209
|
+
const inline = r;
|
|
210
|
+
if (typeof inline.bytesBase64 === 'string' || !r.blobRef)
|
|
211
|
+
return [inline];
|
|
212
|
+
const blob = get(r.blobRef);
|
|
213
|
+
const bytesBase64 = blob ? blobBase64(blob) : null;
|
|
214
|
+
if (bytesBase64 === null) {
|
|
215
|
+
missing = true;
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
const { blobRef, ...meta } = r;
|
|
219
|
+
const entry = { ...meta, bytesBase64 };
|
|
220
|
+
knownRefs.set(entry, { data: bytesBase64, ref: blobRef });
|
|
221
|
+
return [entry];
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return missing ? withMissingBlob(script, bodyMissing) : script;
|
|
225
|
+
}
|
|
226
|
+
// A stored row as reading it shows when a blob it points at is missing
|
|
227
|
+
// or unreadable (see `withMissingBlob`), for saving it that way; null
|
|
228
|
+
// when all of its blobs are there. `blobs` holds what's stored under
|
|
229
|
+
// the row's blob keys. Bodies inline on the row (format 1) count as
|
|
230
|
+
// there.
|
|
231
|
+
export function markMissingBlobs(row, blobs) {
|
|
232
|
+
const there = (ref) => {
|
|
233
|
+
if (!ref)
|
|
234
|
+
return true;
|
|
235
|
+
const blob = blobs[blobKey(ref.hash)];
|
|
236
|
+
return blob?.encoding === 'text' || blob?.encoding === 'base64';
|
|
237
|
+
};
|
|
238
|
+
const bodyMissing = typeof row.bodyText !== 'string' && !there(row.bodyBlobRef);
|
|
239
|
+
const requires = row.requires?.filter((r) => there(r.blobRef));
|
|
240
|
+
const resources = row.resources?.filter((r) => there(r.blobRef));
|
|
241
|
+
if (!bodyMissing && requires?.length === row.requires?.length && resources?.length === row.resources?.length) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const out = withMissingBlob({ ...row }, bodyMissing);
|
|
245
|
+
if (requires)
|
|
246
|
+
out.requires = requires;
|
|
247
|
+
if (resources)
|
|
248
|
+
out.resources = resources;
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
// Small rows → full scripts. `have` is blobs the caller already read
|
|
252
|
+
// (a whole-store scan has them all); the rest are read in one call.
|
|
253
|
+
export async function fromStorage(rows, have = {}) {
|
|
254
|
+
const need = [...new Set(rows.flatMap(blobKeysOf))].filter((k) => !(k in have));
|
|
255
|
+
const blobs = need.length > 0 ? { ...have, ...await chrome.storage.local.get(need) } : have;
|
|
256
|
+
return rows.map((row) => hydrate(row, blobs));
|
|
257
|
+
}
|
|
258
|
+
// In place over a `get` result: every `script:` value becomes a full
|
|
259
|
+
// script. For readers that take rows by key from a mixed read.
|
|
260
|
+
export async function hydrateScriptRows(got) {
|
|
261
|
+
const keys = Object.keys(got).filter((k) => k.startsWith(SCRIPT_PREFIX) && got[k] != null);
|
|
262
|
+
if (keys.length === 0)
|
|
263
|
+
return got;
|
|
264
|
+
const full = await fromStorage(keys.map((k) => got[k]), got);
|
|
265
|
+
keys.forEach((k, i) => { got[k] = full[i]; });
|
|
266
|
+
return got;
|
|
267
|
+
}
|
|
268
|
+
// A write's items with every full `script:` value split into its small
|
|
269
|
+
// row plus blobs. A blob already stored isn't written again: same key,
|
|
270
|
+
// same bytes. Rows and their blobs go out in the caller's one `set`, so
|
|
271
|
+
// no reader sees a row before its blobs.
|
|
272
|
+
export async function withBlobs(items) {
|
|
273
|
+
const keys = Object.keys(items).filter((k) => k.startsWith(SCRIPT_PREFIX) && hasInlineBodies(items[k]));
|
|
274
|
+
if (keys.length === 0)
|
|
275
|
+
return items;
|
|
276
|
+
const blobs = new Map();
|
|
277
|
+
const out = { ...items };
|
|
278
|
+
for (const k of keys)
|
|
279
|
+
out[k] = await toStorage(items[k], blobs);
|
|
280
|
+
return { ...out, ...newBlobItems(blobs, new Set(await chrome.storage.local.getKeys())) };
|
|
281
|
+
}
|
|
282
|
+
// A full script's stored form: its small row, and the blobs it points
|
|
283
|
+
// at. For a writer that compares the row with what's stored before
|
|
284
|
+
// deciding to write: it then writes the row and the blobs not yet
|
|
285
|
+
// stored, rather than the full script, which `withBlobs` would hash
|
|
286
|
+
// again.
|
|
287
|
+
export async function storedForm(script) {
|
|
288
|
+
const blobs = new Map();
|
|
289
|
+
return { row: await toStorage(script, blobs), blobs };
|
|
290
|
+
}
|
|
291
|
+
// The blobs among `blobs` whose keys aren't in `stored` (the keys in
|
|
292
|
+
// storage), as items for a `set`. A stored one has the same bytes under
|
|
293
|
+
// the same key, so it isn't written again.
|
|
294
|
+
export function newBlobItems(blobs, stored) {
|
|
295
|
+
const out = {};
|
|
296
|
+
for (const [k, blob] of blobs)
|
|
297
|
+
if (!stored.has(k))
|
|
298
|
+
out[k] = blob;
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
// What the given scripts' rows take in storage, without their bodies,
|
|
302
|
+
// as Chrome counts it: what a metadata read of them loads. For debug
|
|
303
|
+
// timing lines.
|
|
304
|
+
export async function scriptRowBytes(scriptIds) {
|
|
305
|
+
if (scriptIds.length === 0)
|
|
306
|
+
return 0;
|
|
307
|
+
return await chrome.storage.local.getBytesInUse(scriptIds.map((id) => SCRIPT_PREFIX + id));
|
|
308
|
+
}
|
|
309
|
+
// ---------- Migration from format 1 ----------
|
|
310
|
+
// Rows per `set`. Each call replaces its rows' inline bodies with blobs,
|
|
311
|
+
// so storage use barely moves within one call, and an interrupted run
|
|
312
|
+
// keeps what earlier calls converted.
|
|
313
|
+
export const MIGRATE_BATCH = 25;
|
|
314
|
+
// Set in session storage when a run fails, so a store too full to
|
|
315
|
+
// convert isn't rescanned on every worker wake. The next browser
|
|
316
|
+
// session tries again.
|
|
317
|
+
const MIGRATION_FAILED_KEY = 'storeFormatMigrationFailed';
|
|
318
|
+
// Converts every format 1 row, then records format 2. Safe to re-run:
|
|
319
|
+
// converted rows are skipped. Only the service worker runs it, before
|
|
320
|
+
// anything else there writes (see `storeFormatReady` in script-store).
|
|
321
|
+
//
|
|
322
|
+
// Logged in full whenever it moves anything, since that's once per
|
|
323
|
+
// profile; finding nothing to move, at each worker start or on a new
|
|
324
|
+
// profile, only with debug logging on.
|
|
325
|
+
export async function migrateToBlobs() {
|
|
326
|
+
const format = (await chrome.storage.local.get(STORE_FORMAT_KEY))[STORE_FORMAT_KEY];
|
|
327
|
+
if (typeof format === 'number' && format >= STORE_FORMAT) {
|
|
328
|
+
dlog('[SourceMonkey] storage format 2: already in place');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if ((await chrome.storage.session.get(MIGRATION_FAILED_KEY))[MIGRATION_FAILED_KEY]) {
|
|
332
|
+
console.info('[SourceMonkey] storage format 2: not moving script bodies again until the browser restarts, since it failed earlier');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const started = performance.now();
|
|
336
|
+
let moved = 0;
|
|
337
|
+
let written = 0;
|
|
338
|
+
try {
|
|
339
|
+
const all = await scanAllStorage('blob migration');
|
|
340
|
+
const stored = new Set(Object.keys(all));
|
|
341
|
+
const old = Object.entries(all).filter(([k, v]) => k.startsWith(SCRIPT_PREFIX) && hasInlineBodies(v));
|
|
342
|
+
if (old.length > 0) {
|
|
343
|
+
console.info(`[SourceMonkey] storage format 2: moving the bodies of ${old.length} script(s) to shared storage`);
|
|
344
|
+
}
|
|
345
|
+
for (let i = 0; i < old.length; i += MIGRATE_BATCH) {
|
|
346
|
+
const blobs = new Map();
|
|
347
|
+
const items = {};
|
|
348
|
+
const batch = old.slice(i, i + MIGRATE_BATCH);
|
|
349
|
+
for (const [k, v] of batch)
|
|
350
|
+
items[k] = await toStorage(v, blobs);
|
|
351
|
+
let fresh = 0;
|
|
352
|
+
for (const [k, blob] of blobs) {
|
|
353
|
+
if (stored.has(k))
|
|
354
|
+
continue;
|
|
355
|
+
items[k] = blob;
|
|
356
|
+
stored.add(k);
|
|
357
|
+
fresh++;
|
|
358
|
+
}
|
|
359
|
+
assertWorkerContext('write storage');
|
|
360
|
+
await chrome.storage.local.set(items);
|
|
361
|
+
moved += batch.length;
|
|
362
|
+
written += fresh;
|
|
363
|
+
}
|
|
364
|
+
await chrome.storage.local.set({ [STORE_FORMAT_KEY]: STORE_FORMAT });
|
|
365
|
+
if (old.length === 0) {
|
|
366
|
+
// A new profile, or one whose rows were all converted already.
|
|
367
|
+
dlog('[SourceMonkey] storage format 2: no script bodies to move');
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.info(`[SourceMonkey] storage format 2: moved ${moved} script(s), ${written} stored file(s), `
|
|
371
|
+
+ `in ${formatMs(performance.now() - started)}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
console.info(`[SourceMonkey] storage format 2: stopped after moving ${moved} script(s), ${written} stored file(s); `
|
|
376
|
+
+ 'the rest stay as they were until the browser restarts');
|
|
377
|
+
await chrome.storage.session.set({ [MIGRATION_FAILED_KEY]: true });
|
|
378
|
+
throw err;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Only for the test surface: forget a failed run, as a new browser
|
|
382
|
+
// session would.
|
|
383
|
+
export async function clearMigrationFailureForTests() {
|
|
384
|
+
await chrome.storage.session.remove(MIGRATION_FAILED_KEY);
|
|
385
|
+
}
|
|
386
|
+
// `rows`: every script row, when the caller has them (the worker's row
|
|
387
|
+
// cache); read from storage otherwise.
|
|
388
|
+
export async function sweepUnusedBlobs(rows) {
|
|
389
|
+
const keys = await chrome.storage.local.getKeys();
|
|
390
|
+
const blobKeys = keys.filter((k) => k.startsWith(BLOB_PREFIX));
|
|
391
|
+
if (blobKeys.length === 0)
|
|
392
|
+
return { checked: 0, removed: 0 };
|
|
393
|
+
// The small rows only: refs are all a check needs.
|
|
394
|
+
const scriptRows = rows
|
|
395
|
+
?? await chrome.storage.local.get(keys.filter((k) => k.startsWith(SCRIPT_PREFIX)));
|
|
396
|
+
const used = new Set(Object.values(scriptRows).flatMap(blobKeysOf));
|
|
397
|
+
const remove = blobKeys.filter((k) => !used.has(k));
|
|
398
|
+
if (remove.length > 0) {
|
|
399
|
+
assertWorkerContext('write storage');
|
|
400
|
+
await chrome.storage.local.remove(remove);
|
|
401
|
+
}
|
|
402
|
+
return { checked: blobKeys.length, removed: remove.length };
|
|
403
|
+
}
|
|
404
|
+
// Did a change to a script row leave a blob it pointed at unused by it:
|
|
405
|
+
// a removed row, or one now pointing elsewhere? The worker's cue to
|
|
406
|
+
// sweep.
|
|
407
|
+
export function droppedBlobRefs(oldValue, newValue) {
|
|
408
|
+
const before = blobKeysOf(oldValue);
|
|
409
|
+
if (before.length === 0)
|
|
410
|
+
return false;
|
|
411
|
+
const after = new Set(blobKeysOf(newValue));
|
|
412
|
+
return before.some((k) => !after.has(k));
|
|
413
|
+
}
|