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,253 @@
|
|
|
1
|
+
// Where `chrome.storage.local` goes, script by script and file by file,
|
|
2
|
+
// for the storage debug page (src/extension/debug/storage.ts). Pure:
|
|
3
|
+
// takes the rows a `get(null)` read returns.
|
|
4
|
+
//
|
|
5
|
+
// Sizes are measured as storage-usage.ts measures them (key + JSON
|
|
6
|
+
// value, in UTF-8 bytes), so they agree with the Options page's table.
|
|
7
|
+
//
|
|
8
|
+
// What a script's storage is made of:
|
|
9
|
+
// - `script`: its body, a `blob:` row (docs/storage.md#content-blobs).
|
|
10
|
+
// - `metadata`: the `script:<id>` row itself, without any body.
|
|
11
|
+
// - `require` / `resource`: each file, a `blob:` row.
|
|
12
|
+
// - For a script sm-dev pushed (a scratch script): sm-dev's copy of
|
|
13
|
+
// its text, and each file it pushed from disk.
|
|
14
|
+
// A body still inline in a format 1 row (not yet migrated) is counted
|
|
15
|
+
// as its own file, and taken out of the row's metadata.
|
|
16
|
+
import { blobKey, BLOB_PREFIX } from './script-blobs.js';
|
|
17
|
+
import { collectionKey, SCRIPT_PREFIX } from './structural-keys.js';
|
|
18
|
+
import { VALUE_PREFIX as GM_VALUE_PREFIX } from './gm-values.js';
|
|
19
|
+
import { isScratchUrl, SCRATCH_FILES_KEY_PREFIX, SCRATCH_SOURCE_KEY_PREFIX, SCRATCH_TEXT_KEY_PREFIX, scratchFilesKey, scratchSourceKey, scratchTextKey, } from './location-kinds.js';
|
|
20
|
+
import { effectiveDisplayName } from './collection-display.js';
|
|
21
|
+
import { compareScriptNames, displayLocation, scriptDisplayName } from '../script-display.js';
|
|
22
|
+
import { jsonBytes, rowBytes } from './storage-usage.js';
|
|
23
|
+
const SCRATCH_PREFIXES = [SCRATCH_TEXT_KEY_PREFIX, SCRATCH_FILES_KEY_PREFIX, SCRATCH_SOURCE_KEY_PREFIX];
|
|
24
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
25
|
+
export function buildStorageBreakdown(rows) {
|
|
26
|
+
// File key → its size and uses. A blob's key is its row key; a body
|
|
27
|
+
// stored anywhere else is unique, keyed by where it is.
|
|
28
|
+
const files = new Map();
|
|
29
|
+
const addUse = (key, item, use) => {
|
|
30
|
+
const entry = files.get(key);
|
|
31
|
+
if (entry)
|
|
32
|
+
entry.uses.push(use);
|
|
33
|
+
else
|
|
34
|
+
files.set(key, { key, bytes: item.bytes, uses: [use], ...(item.missing ? { missing: true } : {}) });
|
|
35
|
+
};
|
|
36
|
+
// A blob's size is its row's. A ref to a missing blob is sized from
|
|
37
|
+
// the ref (its content's length, not what storing it would take), so
|
|
38
|
+
// the script still shows roughly what it's meant to hold.
|
|
39
|
+
const blobSize = (ref) => {
|
|
40
|
+
const key = blobKey(str(ref.hash));
|
|
41
|
+
if (key in rows)
|
|
42
|
+
return { bytes: rowBytes(key, rows[key]) };
|
|
43
|
+
return { bytes: typeof ref.size === 'number' ? ref.size : 0, missing: true };
|
|
44
|
+
};
|
|
45
|
+
// sm-dev rows some script accounts for; the rest are leftovers.
|
|
46
|
+
const claimedScratch = new Set();
|
|
47
|
+
const collectionNames = new Map();
|
|
48
|
+
const collectionName = (id) => {
|
|
49
|
+
let name = collectionNames.get(id);
|
|
50
|
+
if (name === undefined) {
|
|
51
|
+
const c = rows[collectionKey(id)];
|
|
52
|
+
name = c ? effectiveDisplayName(c) : '';
|
|
53
|
+
collectionNames.set(id, name);
|
|
54
|
+
}
|
|
55
|
+
return name;
|
|
56
|
+
};
|
|
57
|
+
// Per script: its ref and the file keys of its items, in order.
|
|
58
|
+
const scriptRefs = new Map();
|
|
59
|
+
const pending = [];
|
|
60
|
+
for (const [rowKey, value] of Object.entries(rows)) {
|
|
61
|
+
if (!rowKey.startsWith(SCRIPT_PREFIX) || !value || typeof value !== 'object')
|
|
62
|
+
continue;
|
|
63
|
+
const row = value;
|
|
64
|
+
const collectionId = str(row.collectionId);
|
|
65
|
+
const relativePath = str(row.relativePath);
|
|
66
|
+
const ref = {
|
|
67
|
+
scriptId: rowKey.slice(SCRIPT_PREFIX.length),
|
|
68
|
+
collectionId: collectionId && collectionKey(collectionId) in rows ? collectionId : '',
|
|
69
|
+
collectionName: collectionName(collectionId),
|
|
70
|
+
scriptName: scriptDisplayName({ header: row.header ?? null, relativePath }),
|
|
71
|
+
};
|
|
72
|
+
scriptRefs.set(ref.scriptId, ref);
|
|
73
|
+
const items = [];
|
|
74
|
+
const use = (item, key) => {
|
|
75
|
+
items.push({ item, key });
|
|
76
|
+
addUse(key, item, { ...ref, kind: item.kind, name: item.name, location: item.location });
|
|
77
|
+
};
|
|
78
|
+
// Bodies inline in the row come out of its metadata.
|
|
79
|
+
let inline = 0;
|
|
80
|
+
const scriptLocation = displayLocation(str(row.fileUrl) || relativePath);
|
|
81
|
+
if (typeof row.bodyBlobRef?.hash === 'string') {
|
|
82
|
+
use({ kind: 'script', location: scriptLocation, ...blobSize(row.bodyBlobRef) }, BLOB_PREFIX + row.bodyBlobRef.hash);
|
|
83
|
+
}
|
|
84
|
+
else if (typeof row.bodyText === 'string') {
|
|
85
|
+
const bytes = jsonBytes(row.bodyText);
|
|
86
|
+
inline += bytes;
|
|
87
|
+
use({ kind: 'script', location: scriptLocation, bytes }, `${rowKey}#body`);
|
|
88
|
+
}
|
|
89
|
+
const requires = Array.isArray(row.requires) ? row.requires : [];
|
|
90
|
+
requires.forEach((r, i) => {
|
|
91
|
+
const location = displayLocation(str(r.url));
|
|
92
|
+
if (typeof r.blobRef?.hash === 'string') {
|
|
93
|
+
use({ kind: 'require', location, ...blobSize(r.blobRef) }, BLOB_PREFIX + r.blobRef.hash);
|
|
94
|
+
}
|
|
95
|
+
else if (typeof r.source === 'string') {
|
|
96
|
+
const bytes = jsonBytes(r);
|
|
97
|
+
inline += bytes;
|
|
98
|
+
use({ kind: 'require', location, bytes }, `${rowKey}#require${i}`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const resources = Array.isArray(row.resources) ? row.resources : [];
|
|
102
|
+
resources.forEach((r, i) => {
|
|
103
|
+
const name = str(r.name);
|
|
104
|
+
const location = displayLocation(str(r.url));
|
|
105
|
+
if (typeof r.blobRef?.hash === 'string') {
|
|
106
|
+
use({ kind: 'resource', name, location, ...blobSize(r.blobRef) }, BLOB_PREFIX + r.blobRef.hash);
|
|
107
|
+
}
|
|
108
|
+
else if (typeof r.bytesBase64 === 'string') {
|
|
109
|
+
const bytes = jsonBytes(r);
|
|
110
|
+
inline += bytes;
|
|
111
|
+
use({ kind: 'resource', name, location, bytes }, `${rowKey}#resource${i}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
items.push({ item: { kind: 'metadata', bytes: Math.max(0, rowBytes(rowKey, value) - inline) } });
|
|
115
|
+
// sm-dev's own rows for a scratch script: its text (with the
|
|
116
|
+
// record of where on disk it came from), and the files it pushed.
|
|
117
|
+
if (isScratchUrl(relativePath)) {
|
|
118
|
+
const textKey = scratchTextKey(relativePath);
|
|
119
|
+
const sourceKey = scratchSourceKey(relativePath);
|
|
120
|
+
const filesKey = scratchFilesKey(relativePath);
|
|
121
|
+
for (const key of [textKey, sourceKey, filesKey])
|
|
122
|
+
claimedScratch.add(key);
|
|
123
|
+
if (textKey in rows || sourceKey in rows) {
|
|
124
|
+
const bytes = (textKey in rows ? rowBytes(textKey, rows[textKey]) : 0)
|
|
125
|
+
+ (sourceKey in rows ? rowBytes(sourceKey, rows[sourceKey]) : 0);
|
|
126
|
+
use({ kind: 'smdev-text', bytes }, textKey);
|
|
127
|
+
}
|
|
128
|
+
const pushed = rows[filesKey];
|
|
129
|
+
if (pushed && typeof pushed === 'object') {
|
|
130
|
+
for (const [fileRef, file] of Object.entries(pushed)) {
|
|
131
|
+
// The entry's share of the row: its key and its value.
|
|
132
|
+
const bytes = jsonBytes(fileRef) + 1 + jsonBytes(file);
|
|
133
|
+
use({ kind: 'smdev-file', location: displayLocation(str(file?.url) || fileRef), bytes }, `${filesKey}#${fileRef}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
pending.push({ ref, items });
|
|
138
|
+
}
|
|
139
|
+
const scripts = pending.map(({ ref, items }) => {
|
|
140
|
+
const full = items.map(({ item, key }) => ({
|
|
141
|
+
...item,
|
|
142
|
+
shared: key === undefined ? 1 : files.get(key).uses.length,
|
|
143
|
+
}));
|
|
144
|
+
return {
|
|
145
|
+
...ref,
|
|
146
|
+
bytes: full.reduce((sum, it) => sum + it.bytes, 0),
|
|
147
|
+
shared: Math.max(1, ...full.map((it) => it.shared)),
|
|
148
|
+
items: full,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
const unused = [];
|
|
152
|
+
const values = new Map();
|
|
153
|
+
for (const [key, value] of Object.entries(rows)) {
|
|
154
|
+
if (key.startsWith(BLOB_PREFIX)) {
|
|
155
|
+
if (!files.has(key))
|
|
156
|
+
unused.push({ key, bytes: rowBytes(key, value), uses: [] });
|
|
157
|
+
}
|
|
158
|
+
else if (SCRATCH_PREFIXES.some((p) => key.startsWith(p))) {
|
|
159
|
+
if (!claimedScratch.has(key))
|
|
160
|
+
unused.push({ key, bytes: rowBytes(key, value), uses: [] });
|
|
161
|
+
}
|
|
162
|
+
else if (key.startsWith(GM_VALUE_PREFIX)) {
|
|
163
|
+
// `gmval:<scriptId>:<name>`
|
|
164
|
+
const scriptId = key.slice(GM_VALUE_PREFIX.length).split(':', 1)[0];
|
|
165
|
+
let entry = values.get(scriptId);
|
|
166
|
+
if (!entry) {
|
|
167
|
+
entry = { scriptId, script: scriptRefs.get(scriptId), bytes: 0, count: 0 };
|
|
168
|
+
values.set(scriptId, entry);
|
|
169
|
+
}
|
|
170
|
+
entry.bytes += rowBytes(key, value);
|
|
171
|
+
entry.count++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { scripts, files: [...files.values()], unused, values: [...values.values()] };
|
|
175
|
+
}
|
|
176
|
+
export function fileIdentity(file) {
|
|
177
|
+
const [first, ...rest] = file.uses;
|
|
178
|
+
if (!first || rest.some((u) => u.kind !== first.kind)) {
|
|
179
|
+
return { kind: null, sameName: false, sameLocation: false };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
kind: first.kind,
|
|
183
|
+
sameName: rest.every((u) => u.name === first.name),
|
|
184
|
+
sameLocation: rest.every((u) => u.location === first.location),
|
|
185
|
+
name: first.name,
|
|
186
|
+
location: first.location,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Numbers start largest first; names start A to Z.
|
|
190
|
+
export function defaultSortDir(key) {
|
|
191
|
+
return key === 'collection' || key === 'script' ? 'asc' : 'desc';
|
|
192
|
+
}
|
|
193
|
+
function compareNames(a, b) {
|
|
194
|
+
return compareScriptNames(a.collectionName, b.collectionName) || compareScriptNames(a.scriptName, b.scriptName);
|
|
195
|
+
}
|
|
196
|
+
function compareBy(key, dir) {
|
|
197
|
+
const sign = dir === 'asc' ? 1 : -1;
|
|
198
|
+
return (a, b) => {
|
|
199
|
+
let c = 0;
|
|
200
|
+
if (key === 'collection')
|
|
201
|
+
c = compareScriptNames(a.collectionName, b.collectionName);
|
|
202
|
+
else if (key === 'script')
|
|
203
|
+
c = compareScriptNames(a.scriptName, b.scriptName);
|
|
204
|
+
else if (key === 'size')
|
|
205
|
+
c = a.bytes - b.bytes;
|
|
206
|
+
else if (key === 'shared')
|
|
207
|
+
c = a.shared - b.shared;
|
|
208
|
+
else
|
|
209
|
+
c = a.count - b.count;
|
|
210
|
+
return sign * c || compareNames(a, b);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const KIND_ORDER = ['script', 'require', 'resource', 'smdev-text', 'smdev-file', 'metadata'];
|
|
214
|
+
// Scripts by the column; each script's items largest first.
|
|
215
|
+
export function sortScripts(scripts, key, dir) {
|
|
216
|
+
const cmp = compareBy(key, dir);
|
|
217
|
+
return scripts
|
|
218
|
+
.map((s) => ({
|
|
219
|
+
...s,
|
|
220
|
+
items: [...s.items].sort((a, b) => b.bytes - a.bytes
|
|
221
|
+
|| KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind)
|
|
222
|
+
|| (a.location ?? '').localeCompare(b.location ?? '')),
|
|
223
|
+
}))
|
|
224
|
+
.sort((a, b) => cmp({ ...a, count: 0 }, { ...b, count: 0 }));
|
|
225
|
+
}
|
|
226
|
+
// Files sort only by size or shared copies: a file has no one
|
|
227
|
+
// collection or script to sort by. Ties go to the other of the two,
|
|
228
|
+
// largest first, then to the first use's collection and script. Each
|
|
229
|
+
// file's uses are listed by collection, then script.
|
|
230
|
+
export function sortFiles(files, key, dir) {
|
|
231
|
+
const sign = dir === 'asc' ? 1 : -1;
|
|
232
|
+
const size = (a, b) => a.bytes - b.bytes;
|
|
233
|
+
const shared = (a, b) => a.uses.length - b.uses.length;
|
|
234
|
+
const [primary, secondary] = key === 'size' ? [size, shared] : [shared, size];
|
|
235
|
+
const firstUse = (f) => f.uses[0] ?? { collectionName: '', scriptName: '' };
|
|
236
|
+
return files
|
|
237
|
+
.map((f) => ({ ...f, uses: [...f.uses].sort(compareNames) }))
|
|
238
|
+
.sort((a, b) => sign * primary(a, b)
|
|
239
|
+
|| -secondary(a, b)
|
|
240
|
+
|| compareNames(firstUse(a), firstUse(b))
|
|
241
|
+
|| a.key.localeCompare(b.key));
|
|
242
|
+
}
|
|
243
|
+
export function sortValues(values, key, dir) {
|
|
244
|
+
const asSortable = (v) => ({
|
|
245
|
+
collectionName: v.script?.collectionName ?? '',
|
|
246
|
+
scriptName: v.script?.scriptName ?? v.scriptId,
|
|
247
|
+
bytes: v.bytes,
|
|
248
|
+
shared: 0,
|
|
249
|
+
count: v.count,
|
|
250
|
+
});
|
|
251
|
+
const cmp = compareBy(key, dir);
|
|
252
|
+
return [...values].sort((a, b) => cmp(asSortable(a), asSortable(b)));
|
|
253
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Whole-store reads, in one place: `chrome.storage.local.get(null)`
|
|
2
|
+
// (`scanAllStorage`), and the narrower read of only the rows a caller
|
|
3
|
+
// wants (`scanStorageRows`), which most callers use now.
|
|
4
|
+
//
|
|
5
|
+
// A `get(null)` returns EVERY row the extension owns — every script
|
|
6
|
+
// body, every `@require`/`@resource` payload, every `gmval:` row — even
|
|
7
|
+
// when the caller wants one prefix. On a small profile that is
|
|
8
|
+
// invisible; on a real one it is megabytes, and the cost lands in two
|
|
9
|
+
// places, only one of which is obvious:
|
|
10
|
+
//
|
|
11
|
+
// - the read itself, on Chrome's storage thread; and
|
|
12
|
+
// - deserializing the result into JS objects, which runs on the
|
|
13
|
+
// CALLING context's single thread and blocks it. While that runs,
|
|
14
|
+
// every unrelated pending promise callback in that context waits —
|
|
15
|
+
// so a scan can inflate a phase that has nothing to do with it.
|
|
16
|
+
//
|
|
17
|
+
// The second effect is why these are worth naming individually. A
|
|
18
|
+
// reload fast-path phase measured at ~130ms for a point read of a
|
|
19
|
+
// small row is not explained by that row; it is explained by whatever
|
|
20
|
+
// else was on the thread. Without a per-scan line there is no way to
|
|
21
|
+
// tell those apart from the outside.
|
|
22
|
+
//
|
|
23
|
+
// Gated on the same debug-logging toggle as the phase timers, and the
|
|
24
|
+
// wrapper is a plain pass-through when it's off.
|
|
25
|
+
import { debugLoggingPending, debugLoggingSettled, isDebugLoggingEnabled } from '../debug-log.js';
|
|
26
|
+
// Same rounding as the phase timers on purpose: these lines are read
|
|
27
|
+
// against phase numbers on the same console, and two formats that
|
|
28
|
+
// drift would make that comparison quietly wrong.
|
|
29
|
+
import { formatMs } from '../phase-timing.js';
|
|
30
|
+
import { formatBytes } from '../script-display.js';
|
|
31
|
+
// Runs `chrome.storage.local.get(null)`, reporting what it cost and who
|
|
32
|
+
// asked. `reason` should name the CALLER's purpose ("gm snapshot",
|
|
33
|
+
// "match index"), not the mechanism — the mechanism is the same every
|
|
34
|
+
// time, and the caller is the thing a reader needs to identify.
|
|
35
|
+
export async function scanAllStorage(reason) {
|
|
36
|
+
return timeStorageRead(reason, () => chrome.storage.local.get(null));
|
|
37
|
+
}
|
|
38
|
+
// The rows whose keys `keep` accepts, and nothing else: no script
|
|
39
|
+
// bodies (each in its own `blob:` row) and no GM values. Lists the keys,
|
|
40
|
+
// then reads the wanted ones, which costs far less than a `get(null)`
|
|
41
|
+
// once the rest is megabytes.
|
|
42
|
+
export async function scanStorageRows(reason, keep) {
|
|
43
|
+
return timeStorageRead(reason, async () => {
|
|
44
|
+
const keys = (await chrome.storage.local.getKeys()).filter(keep);
|
|
45
|
+
return keys.length > 0 ? await chrome.storage.local.get(keys) : {};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Same line for a read that is deliberately NARROW, so a caller that
|
|
49
|
+
// used to scan and now doesn't reports in the units it is being
|
|
50
|
+
// compared against. `reason` should carry the shape (`gm snapshot
|
|
51
|
+
// (narrow)`) — the point of the line is the contrast.
|
|
52
|
+
// A `null` result means the read didn't happen (the caller fell back to
|
|
53
|
+
// something else) and nothing is logged — a line reporting `0 row(s)`
|
|
54
|
+
// for a read that never ran would be worse than silence.
|
|
55
|
+
//
|
|
56
|
+
// A read made at a worker's start, before the debug setting has been
|
|
57
|
+
// read, is timed anyway, and its line printed once the setting turns
|
|
58
|
+
// out to be on: the row cache's load is one of those. That line can
|
|
59
|
+
// then come after lines for work that started later.
|
|
60
|
+
export async function timeStorageRead(reason, read) {
|
|
61
|
+
const pending = debugLoggingPending();
|
|
62
|
+
if (!isDebugLoggingEnabled() && !pending)
|
|
63
|
+
return await read();
|
|
64
|
+
const start = performance.now();
|
|
65
|
+
const rows = await read();
|
|
66
|
+
const ms = performance.now() - start;
|
|
67
|
+
if (rows === null)
|
|
68
|
+
return rows;
|
|
69
|
+
if (pending) {
|
|
70
|
+
void debugLoggingSettled().then((on) => { if (on)
|
|
71
|
+
logStorageRead(reason, ms, rows); });
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
74
|
+
logStorageRead(reason, ms, rows);
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
function logStorageRead(reason, ms, rows) {
|
|
78
|
+
const keys = Object.keys(rows);
|
|
79
|
+
const head = `[SourceMonkey] timing: storage scan (${reason}): ${formatMs(ms)}, ${keys.length} keys`;
|
|
80
|
+
// The size comes from Chrome, which keeps it for each stored value,
|
|
81
|
+
// rather than from serializing the rows here: on a store of several
|
|
82
|
+
// MB that took tens of ms on this thread, inside whatever phase the
|
|
83
|
+
// caller was timing. Not awaited, so the caller never waits on it;
|
|
84
|
+
// the line prints when the answer arrives.
|
|
85
|
+
chrome.storage.local.getBytesInUse(keys).then((bytes) => console.log(`${head}, ${formatBytes(bytes)}`), () => console.log(head));
|
|
86
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Breakdown of what SourceMonkey keeps in `chrome.storage.local`, for the
|
|
2
|
+
// Options page's usage table. Pure: takes the rows a `get(null)` read
|
|
3
|
+
// returns and sorts them into the kinds a user would recognize.
|
|
4
|
+
//
|
|
5
|
+
// Sizes are measured the way Chrome charges them: the key plus the
|
|
6
|
+
// JSON-serialized value, in UTF-8 bytes. They are close to
|
|
7
|
+
// `getBytesInUse`, not identical to it, so the page takes its total
|
|
8
|
+
// from Chrome and lets "Other" absorb the difference (see
|
|
9
|
+
// `withRemainder`). `toWholeKb` then rounds so the shown sizes add up.
|
|
10
|
+
//
|
|
11
|
+
// `chrome.storage.session` isn't covered: it holds only this session's
|
|
12
|
+
// logs and tab state, has its own quota, and is nowhere near filling.
|
|
13
|
+
import { COLLECTION_PREFIX, COLLECTIONS_INDEX_KEY, SCRIPT_PREFIX } from './structural-keys.js';
|
|
14
|
+
import { BLOB_PREFIX } from './script-blobs.js';
|
|
15
|
+
import { VALUE_PREFIX as GM_VALUE_PREFIX } from './gm-values.js';
|
|
16
|
+
import { isIconCacheKey } from './site-icon.js';
|
|
17
|
+
import { SCRATCH_FILES_KEY_PREFIX, SCRATCH_SOURCE_KEY_PREFIX, SCRATCH_TEXT_KEY_PREFIX, } from './location-kinds.js';
|
|
18
|
+
const encoder = new TextEncoder();
|
|
19
|
+
export function jsonBytes(value) {
|
|
20
|
+
return encoder.encode(JSON.stringify(value) ?? '').length;
|
|
21
|
+
}
|
|
22
|
+
// What Chrome charges for one row. Shared with the storage debug page
|
|
23
|
+
// (storage-breakdown.ts), so its sizes agree with this table.
|
|
24
|
+
export function rowBytes(key, value) {
|
|
25
|
+
return encoder.encode(key).length + jsonBytes(value);
|
|
26
|
+
}
|
|
27
|
+
function plural(n, one, many = `${one}s`) {
|
|
28
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
29
|
+
}
|
|
30
|
+
// sm-dev's scratch scripts: the text, its pushed files, and its origin.
|
|
31
|
+
const SCRATCH_PREFIXES = [
|
|
32
|
+
SCRATCH_TEXT_KEY_PREFIX, SCRATCH_FILES_KEY_PREFIX, SCRATCH_SOURCE_KEY_PREFIX,
|
|
33
|
+
];
|
|
34
|
+
function startsWithAny(key, prefixes) {
|
|
35
|
+
return prefixes.some((p) => key.startsWith(p));
|
|
36
|
+
}
|
|
37
|
+
// `chrome.storage.local`. Bodies live in shared `blob:` rows
|
|
38
|
+
// (docs/storage.md#content-blobs); each is counted once, under the kind
|
|
39
|
+
// of the first row found pointing at it: a script body, a `@require`,
|
|
40
|
+
// or a `@resource`. A blob nothing points at (left by a remove, or a
|
|
41
|
+
// refresh that replaced a body, until cleanup deletes it)
|
|
42
|
+
// gets its own row. A format 1 row, not yet migrated, carries its
|
|
43
|
+
// `@require` / `@resource` bodies inline, so those are split out of the
|
|
44
|
+
// row instead.
|
|
45
|
+
export function summarizeLocalStorage(rows) {
|
|
46
|
+
let scripts = 0, scriptBytes = 0;
|
|
47
|
+
let requires = 0, requireBytes = 0;
|
|
48
|
+
let resources = 0, resourceBytes = 0;
|
|
49
|
+
let icons = 0, iconBytes = 0;
|
|
50
|
+
let values = 0, valueBytes = 0;
|
|
51
|
+
const valueScripts = new Set();
|
|
52
|
+
let scratch = 0, scratchBytes = 0;
|
|
53
|
+
let collections = 0, collectionBytes = 0;
|
|
54
|
+
let otherBytes = 0;
|
|
55
|
+
// Blob key → the kind whose size it counts under (the first found).
|
|
56
|
+
const claimed = new Map();
|
|
57
|
+
// Per kind: files stored as blobs, and their distinct blobs. A blob
|
|
58
|
+
// shared across kinds is distinct in each, though sized under one.
|
|
59
|
+
const withRef = { script: 0, require: 0, resource: 0 };
|
|
60
|
+
const distinct = { script: new Set(), require: new Set(), resource: new Set() };
|
|
61
|
+
const claim = (ref, kind) => {
|
|
62
|
+
const hash = ref?.hash;
|
|
63
|
+
if (typeof hash !== 'string')
|
|
64
|
+
return;
|
|
65
|
+
const key = BLOB_PREFIX + hash;
|
|
66
|
+
withRef[kind]++;
|
|
67
|
+
distinct[kind].add(key);
|
|
68
|
+
if (!claimed.has(key))
|
|
69
|
+
claimed.set(key, kind);
|
|
70
|
+
};
|
|
71
|
+
for (const [key, value] of Object.entries(rows)) {
|
|
72
|
+
if (!key.startsWith(SCRIPT_PREFIX))
|
|
73
|
+
continue;
|
|
74
|
+
const row = (value ?? {});
|
|
75
|
+
const req = Array.isArray(row.requires) ? row.requires : [];
|
|
76
|
+
const res = Array.isArray(row.resources) ? row.resources : [];
|
|
77
|
+
const inlineReq = req.filter((r) => typeof r.source === 'string');
|
|
78
|
+
const inlineRes = res.filter((r) => typeof r.bytesBase64 === 'string');
|
|
79
|
+
const reqBytes = inlineReq.length ? jsonBytes(inlineReq) : 0;
|
|
80
|
+
const resBytes = inlineRes.length ? jsonBytes(inlineRes) : 0;
|
|
81
|
+
scripts++;
|
|
82
|
+
scriptBytes += rowBytes(key, value) - reqBytes - resBytes;
|
|
83
|
+
requires += req.length;
|
|
84
|
+
requireBytes += reqBytes;
|
|
85
|
+
resources += res.length;
|
|
86
|
+
resourceBytes += resBytes;
|
|
87
|
+
claim(row.bodyBlobRef, 'script');
|
|
88
|
+
for (const r of req)
|
|
89
|
+
claim(r.blobRef, 'require');
|
|
90
|
+
for (const r of res)
|
|
91
|
+
claim(r.blobRef, 'resource');
|
|
92
|
+
}
|
|
93
|
+
let unused = 0, unusedBytes = 0;
|
|
94
|
+
for (const [key, value] of Object.entries(rows)) {
|
|
95
|
+
if (key.startsWith(SCRIPT_PREFIX))
|
|
96
|
+
continue;
|
|
97
|
+
const bytes = rowBytes(key, value);
|
|
98
|
+
if (key.startsWith(BLOB_PREFIX)) {
|
|
99
|
+
const kind = claimed.get(key);
|
|
100
|
+
if (kind === 'script')
|
|
101
|
+
scriptBytes += bytes;
|
|
102
|
+
else if (kind === 'require')
|
|
103
|
+
requireBytes += bytes;
|
|
104
|
+
else if (kind === 'resource')
|
|
105
|
+
resourceBytes += bytes;
|
|
106
|
+
else {
|
|
107
|
+
unused++;
|
|
108
|
+
unusedBytes += bytes;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (isIconCacheKey(key)) {
|
|
112
|
+
icons++;
|
|
113
|
+
iconBytes += bytes;
|
|
114
|
+
}
|
|
115
|
+
else if (key.startsWith(GM_VALUE_PREFIX)) {
|
|
116
|
+
// `gmval:<scriptId>:<name>`
|
|
117
|
+
values++;
|
|
118
|
+
valueBytes += bytes;
|
|
119
|
+
valueScripts.add(key.slice(GM_VALUE_PREFIX.length).split(':', 1)[0]);
|
|
120
|
+
}
|
|
121
|
+
else if (startsWithAny(key, SCRATCH_PREFIXES)) {
|
|
122
|
+
if (key.startsWith(SCRATCH_TEXT_KEY_PREFIX))
|
|
123
|
+
scratch++;
|
|
124
|
+
scratchBytes += bytes;
|
|
125
|
+
}
|
|
126
|
+
else if (key.startsWith(COLLECTION_PREFIX)) {
|
|
127
|
+
collections++;
|
|
128
|
+
collectionBytes += bytes;
|
|
129
|
+
}
|
|
130
|
+
else if (key === COLLECTIONS_INDEX_KEY) {
|
|
131
|
+
collectionBytes += bytes;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
otherBytes += bytes;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// "(N unique)" when files stored as blobs share some. A file not
|
|
138
|
+
// stored as a blob (never loaded, or not yet migrated) counts as
|
|
139
|
+
// unique, so it can't make the count look like sharing.
|
|
140
|
+
const total = { script: scripts, require: requires, resource: resources };
|
|
141
|
+
const withUnique = (text, kind) => {
|
|
142
|
+
const unique = total[kind] - (withRef[kind] - distinct[kind].size);
|
|
143
|
+
return unique < total[kind] ? `${text} (${unique} unique)` : text;
|
|
144
|
+
};
|
|
145
|
+
return [
|
|
146
|
+
{ label: 'Scripts', count: withUnique(plural(scripts, 'script'), 'script'), bytes: scriptBytes },
|
|
147
|
+
{ label: '@require files', count: withUnique(plural(requires, 'file'), 'require'), bytes: requireBytes },
|
|
148
|
+
{ label: '@resource files', count: withUnique(plural(resources, 'file'), 'resource'), bytes: resourceBytes },
|
|
149
|
+
{
|
|
150
|
+
label: 'Script values (GM_setValue)',
|
|
151
|
+
count: `${plural(values, 'value')} in ${plural(valueScripts.size, 'script')}`,
|
|
152
|
+
bytes: valueBytes,
|
|
153
|
+
},
|
|
154
|
+
{ label: 'Site and script icons', count: plural(icons, 'icon'), bytes: iconBytes },
|
|
155
|
+
{ label: 'Scripts run by sm-dev', count: plural(scratch, 'script'), bytes: scratchBytes },
|
|
156
|
+
{ label: 'Collections', count: plural(collections, 'collection'), bytes: collectionBytes },
|
|
157
|
+
{ label: 'Stored files no script uses', count: plural(unused, 'file'), bytes: unusedBytes },
|
|
158
|
+
{ label: 'Other', count: '', bytes: otherBytes },
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
// Makes the rows add up to Chrome's own total: the last row ("Other")
|
|
162
|
+
// takes whatever our measurement and `getBytesInUse` disagree on, so
|
|
163
|
+
// the table never shows parts that sum to more or less than the whole.
|
|
164
|
+
export function withRemainder(rows, total) {
|
|
165
|
+
const measured = rows.slice(0, -1).reduce((sum, r) => sum + r.bytes, 0);
|
|
166
|
+
const last = rows[rows.length - 1];
|
|
167
|
+
return [...rows.slice(0, -1), { ...last, bytes: Math.max(0, total - measured) }];
|
|
168
|
+
}
|
|
169
|
+
// Whole-KB sizes that sum exactly to the rounded total, by giving the
|
|
170
|
+
// leftover KB to the rows that lost most to rounding (largest
|
|
171
|
+
// remainder). Rounding each row on its own makes a column whose parts
|
|
172
|
+
// visibly don't add up to the total under it.
|
|
173
|
+
export function toWholeKb(rows, total) {
|
|
174
|
+
const exact = rows.map((r) => r.bytes / 1024);
|
|
175
|
+
const kb = exact.map(Math.floor);
|
|
176
|
+
let left = Math.round(total / 1024) - kb.reduce((a, b) => a + b, 0);
|
|
177
|
+
const order = exact
|
|
178
|
+
.map((x, i) => ({ i, frac: x - Math.floor(x) }))
|
|
179
|
+
.sort((a, b) => b.frac - a.frac);
|
|
180
|
+
for (let n = 0; left > 0 && n < order.length; n++, left--)
|
|
181
|
+
kb[order[n].i]++;
|
|
182
|
+
// Short of the total only by rounding above. Over it means the rows
|
|
183
|
+
// were measured larger than Chrome's total (`withRemainder` clamps
|
|
184
|
+
// "Other" at 0): take the excess back from the largest rows, which
|
|
185
|
+
// it changes least. The excess is a few KB at most, so one KB at a
|
|
186
|
+
// time is cheap.
|
|
187
|
+
for (; left < 0; left++) {
|
|
188
|
+
const i = kb.indexOf(Math.max(...kb));
|
|
189
|
+
if (i < 0 || kb[i] === 0)
|
|
190
|
+
break;
|
|
191
|
+
kb[i]--;
|
|
192
|
+
}
|
|
193
|
+
return kb;
|
|
194
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The storage key layout of the structural rows (the collection index,
|
|
2
|
+
// collection rows, script rows), and the active-times key the worker's
|
|
3
|
+
// row cache keeps beside them. Split from script-store.ts, which owns
|
|
4
|
+
// those rows, so a page that only watches for changes to them (the
|
|
5
|
+
// toolbar popup) doesn't load the store and everything it imports.
|
|
6
|
+
export const COLLECTIONS_INDEX_KEY = 'collections';
|
|
7
|
+
export const COLLECTION_PREFIX = 'collection:';
|
|
8
|
+
export const SCRIPT_PREFIX = 'script:';
|
|
9
|
+
export const collectionKey = (id) => `${COLLECTION_PREFIX}${id}`;
|
|
10
|
+
export const scriptKey = (id) => `${SCRIPT_PREFIX}${id}`;
|
|
11
|
+
// Every script's active times (`writeEnablement` in script-store.ts). Not
|
|
12
|
+
// a structural row, so `isStructuralKey` leaves it out; here so the
|
|
13
|
+
// worker's row cache can keep it without importing the store.
|
|
14
|
+
export const ENABLEMENT_TIMES_KEY = 'enablementTimes';
|
|
15
|
+
// Does a `chrome.storage.local` change touch the structural rows — the
|
|
16
|
+
// collection index, a collection, or a script? Pages watching for
|
|
17
|
+
// "something I display moved" ask the module that owns the key layout
|
|
18
|
+
// instead of re-deriving the prefixes, where a rename here would leave
|
|
19
|
+
// them silently watching keys that no longer exist.
|
|
20
|
+
export function touchesStructuralRows(keys) {
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
if (isStructuralKey(key))
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
export function isStructuralKey(key) {
|
|
28
|
+
return key === COLLECTIONS_INDEX_KEY || key.startsWith(collectionKey('')) || key.startsWith(scriptKey(''));
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Keep service-worker (SW) teardown noise out of chrome://extensions.
|
|
2
|
+
//
|
|
3
|
+
// When Chrome destroys the extension's SW — on every reload of the
|
|
4
|
+
// unpacked extension, and on idle termination — any `chrome.*` call
|
|
5
|
+
// still in flight rejects with `Error: No SW`. The SW's startup path
|
|
6
|
+
// fires many fire-and-forget storage reads (`void ensureLoaded()`
|
|
7
|
+
// and friends in background.ts), so a reload that lands while they're
|
|
8
|
+
// pending produces one unhandled rejection each, and Chrome lists
|
|
9
|
+
// every one under the extension's Errors panel as if SourceMonkey had
|
|
10
|
+
// crashed. Nothing is wrong: a dead SW has no work left to do.
|
|
11
|
+
//
|
|
12
|
+
// One handler here recognizes that rejection and marks it handled,
|
|
13
|
+
// rather than a try/catch at every call site. Every other rejection
|
|
14
|
+
// still surfaces as before.
|
|
15
|
+
// Exact match on purpose: the string is Chrome's own (the extension
|
|
16
|
+
// API binding's "worker is gone" rejection), it carries nothing else,
|
|
17
|
+
// and a looser match could swallow a real failure that happened to
|
|
18
|
+
// mention it. Accepts any object with a `message` rather than only
|
|
19
|
+
// `Error` instances — the reason is created by the API bindings, not
|
|
20
|
+
// necessarily by this realm's `Error`.
|
|
21
|
+
export function isSwTeardownError(err) {
|
|
22
|
+
const message = typeof err === 'string'
|
|
23
|
+
? err
|
|
24
|
+
: err?.message;
|
|
25
|
+
return message === 'No SW';
|
|
26
|
+
}
|
|
27
|
+
export function installSwTeardownHandler() {
|
|
28
|
+
self.addEventListener('unhandledrejection', (event) => {
|
|
29
|
+
if (!isSwTeardownError(event.reason))
|
|
30
|
+
return;
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
// Stays in the SW's own DevTools console only (console.info is not
|
|
33
|
+
// forwarded to the Errors panel; .warn/.error are).
|
|
34
|
+
console.info('[SourceMonkey] ignoring a chrome.* call that was cut off by service-worker shutdown');
|
|
35
|
+
});
|
|
36
|
+
}
|