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,346 @@
|
|
|
1
|
+
// Noticing, at paint time, that a local file a page is naming has
|
|
2
|
+
// changed on disk — deleted, or edited — before any refresh has run
|
|
3
|
+
// and updated the stored row. And, for a directory collection, that
|
|
4
|
+
// files have appeared which a refresh would pick up.
|
|
5
|
+
//
|
|
6
|
+
// Every surface that shows a `file://` path paints from storage, and
|
|
7
|
+
// storage only learns about the disk when the next refresh lists a
|
|
8
|
+
// directory. Until then the viewer's Location row, its Local overrides
|
|
9
|
+
// row, the versions table and the collections page all describe the
|
|
10
|
+
// file as it was last read. This module lets them say otherwise: a
|
|
11
|
+
// row tags the element carrying the path (`tagLocalFile`: the path,
|
|
12
|
+
// the size and mtime the row was read at, and the tooltips to use),
|
|
13
|
+
// and `markFileChanges` probes the disk and adds a chip beside each
|
|
14
|
+
// one that has gone or changed.
|
|
15
|
+
//
|
|
16
|
+
// See docs/file-changes.md.
|
|
17
|
+
import { baseName, collectionCanonicalUrl, isFileUrl, isLocalDirectoryCollection, isManifestCollection, listDirectory, locationFsPath, parentDir, } from '../file-locations.js';
|
|
18
|
+
import { dlog } from '../debug-log.js';
|
|
19
|
+
import { pendingSummary } from '../script-display.js';
|
|
20
|
+
// Probe `files` against their directory listings — one listing per
|
|
21
|
+
// distinct parent directory, rather than a read per file: a table can
|
|
22
|
+
// name many files in one folder, and a listing is one small read
|
|
23
|
+
// however many rows it settles.
|
|
24
|
+
//
|
|
25
|
+
// Conservative by construction. A file is reported missing only when
|
|
26
|
+
// its directory listed successfully and the name is not in it, and
|
|
27
|
+
// modified only when the listing supplies a size and mtime that differ
|
|
28
|
+
// from the stored ones. A listing that fails — the directory itself is
|
|
29
|
+
// gone, file access is off, a transient read error — settles nothing:
|
|
30
|
+
// its files come back `unknown`. That is the right way round for every
|
|
31
|
+
// caller: a chip that says "deleted" about a file that is there would
|
|
32
|
+
// be worse than no chip, and the delete dialog acts on the answer.
|
|
33
|
+
//
|
|
34
|
+
// `maxAgeMs` lets a caller that re-probes on every repaint reuse a
|
|
35
|
+
// listing read moments ago: the collections page repaints on every
|
|
36
|
+
// storage write, an enable toggle included, and nothing about the disk
|
|
37
|
+
// changed for that. A listing older than `maxAgeMs` is read again.
|
|
38
|
+
//
|
|
39
|
+
// A remembered listing is only ever trusted for the answer "present":
|
|
40
|
+
// a listing that agrees with the row is right whatever its age, but
|
|
41
|
+
// one that disagrees may simply predate the refresh that just updated
|
|
42
|
+
// the row (the refresh lists through its own cache, not this memo), so
|
|
43
|
+
// a folder with any other answer is listed again before it is given.
|
|
44
|
+
// The common case — nothing changed — pays no extra read; a change
|
|
45
|
+
// costs one, and the fresh listing replaces the memo.
|
|
46
|
+
//
|
|
47
|
+
// Answers one state per input, in input order — not per path: the same
|
|
48
|
+
// file can be held by several rows (one collection per copy), each
|
|
49
|
+
// read at a different time with a different stat, and each is its own
|
|
50
|
+
// question.
|
|
51
|
+
export async function probeFiles(files, maxAgeMs = 0) {
|
|
52
|
+
const byDir = new Map();
|
|
53
|
+
files.forEach((file, i) => {
|
|
54
|
+
const dir = parentDir(file.path);
|
|
55
|
+
const list = byDir.get(dir);
|
|
56
|
+
if (list)
|
|
57
|
+
list.push(i);
|
|
58
|
+
else
|
|
59
|
+
byDir.set(dir, [i]);
|
|
60
|
+
});
|
|
61
|
+
const states = new Array(files.length).fill('unknown');
|
|
62
|
+
const cache = new Map();
|
|
63
|
+
await Promise.all([...byDir].map(async ([dir, indexes]) => {
|
|
64
|
+
let listed = await listedDir(dir, cache, maxAgeMs);
|
|
65
|
+
if (listed?.remembered && indexes.some((i) => stateOf(files[i], listed) !== 'present')) {
|
|
66
|
+
listed = await listedDir(dir, cache, 0);
|
|
67
|
+
}
|
|
68
|
+
if (!listed)
|
|
69
|
+
return;
|
|
70
|
+
for (const i of indexes)
|
|
71
|
+
states[i] = stateOf(files[i], listed);
|
|
72
|
+
}));
|
|
73
|
+
return states;
|
|
74
|
+
}
|
|
75
|
+
function stateOf(file, listed) {
|
|
76
|
+
const entry = listed.files.get(baseName(file.path));
|
|
77
|
+
if (!entry)
|
|
78
|
+
return 'missing';
|
|
79
|
+
// The same test the refresh uses to decide whether to re-read the
|
|
80
|
+
// file (`canSkip` in script-store.ts), so a `modified` chip predicts
|
|
81
|
+
// exactly what a refresh will do. Equality on mtime, not ordering:
|
|
82
|
+
// the value is an opaque listing timestamp.
|
|
83
|
+
if (file.size !== undefined && file.mtime !== undefined
|
|
84
|
+
&& entry.size !== undefined && entry.mtime !== undefined
|
|
85
|
+
&& (entry.size !== file.size || entry.mtime !== file.mtime)) {
|
|
86
|
+
return 'modified';
|
|
87
|
+
}
|
|
88
|
+
return 'present';
|
|
89
|
+
}
|
|
90
|
+
// The subset of `paths` that are no longer on disk. The delete dialog's
|
|
91
|
+
// question; a convenience over `probeFiles`.
|
|
92
|
+
export async function missingFiles(paths) {
|
|
93
|
+
const files = [...paths].map((path) => ({ path }));
|
|
94
|
+
const states = await probeFiles(files);
|
|
95
|
+
return new Set(files.filter((_, i) => states[i] === 'missing').map((f) => f.path));
|
|
96
|
+
}
|
|
97
|
+
// `.user.js` files on disk in a directory collection that no row
|
|
98
|
+
// accounts for — what a refresh would add. Looks in the collection's
|
|
99
|
+
// root and in every directory a known script sits in, which is where
|
|
100
|
+
// new files land in practice; a new subdirectory with nothing known in
|
|
101
|
+
// it is not seen until a refresh scans it (docs/file-changes.md).
|
|
102
|
+
//
|
|
103
|
+
// Returns paths relative to the collection root, so they read the way
|
|
104
|
+
// the rows do.
|
|
105
|
+
export async function newScriptFiles(collectionPath, knownRelativePaths, maxAgeMs = 0) {
|
|
106
|
+
const root = locationFsPath(collectionPath).replace(/\/+$/, '');
|
|
107
|
+
const known = new Set(knownRelativePaths);
|
|
108
|
+
const dirs = new Set([root]);
|
|
109
|
+
for (const rel of known)
|
|
110
|
+
dirs.add(parentDir(`${root}/${rel}`));
|
|
111
|
+
const out = [];
|
|
112
|
+
const cache = new Map();
|
|
113
|
+
await Promise.all([...dirs].map(async (dir) => {
|
|
114
|
+
const listed = await listedDir(dir, cache, maxAgeMs);
|
|
115
|
+
if (!listed)
|
|
116
|
+
return;
|
|
117
|
+
const prefix = dir === root ? '' : `${dir.slice(root.length + 1)}/`;
|
|
118
|
+
for (const name of listed.files.keys()) {
|
|
119
|
+
if (!name.endsWith('.user.js'))
|
|
120
|
+
continue;
|
|
121
|
+
const rel = prefix + name;
|
|
122
|
+
if (!known.has(rel))
|
|
123
|
+
out.push(rel);
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
return out.sort();
|
|
127
|
+
}
|
|
128
|
+
// Recent successful listings, kept for `maxAgeMs`. Failures are not
|
|
129
|
+
// remembered: the next probe retries. Bounded by the number of
|
|
130
|
+
// folders a page names.
|
|
131
|
+
const recentListings = new Map();
|
|
132
|
+
// The listing for `dir`, or null when it could not be read (logged,
|
|
133
|
+
// not thrown: callers treat it as "nothing known").
|
|
134
|
+
async function listedDir(dir, cache, maxAgeMs) {
|
|
135
|
+
const recent = recentListings.get(dir);
|
|
136
|
+
if (recent && Date.now() - recent.at < maxAgeMs)
|
|
137
|
+
return { ...recent.listed, remembered: true };
|
|
138
|
+
try {
|
|
139
|
+
const entries = await listDirectory(dir, cache);
|
|
140
|
+
const files = new Map();
|
|
141
|
+
for (const e of entries) {
|
|
142
|
+
if (!e.isDir)
|
|
143
|
+
files.set(e.name, { size: e.size, mtime: e.mtime });
|
|
144
|
+
}
|
|
145
|
+
const listed = { files, remembered: false };
|
|
146
|
+
recentListings.set(dir, { at: Date.now(), listed });
|
|
147
|
+
return listed;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
dlog(`[SourceMonkey] could not list ${dir} to check for file changes: ${err instanceof Error ? err.message : String(err)}`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// The tag for a script's own file, or null when the script isn't a
|
|
155
|
+
// local file. One builder for every surface that shows one (the
|
|
156
|
+
// viewer's Location row, the versions table, the collections page), so
|
|
157
|
+
// they can't disagree on the path, the stat, or the wording.
|
|
158
|
+
export function scriptFileTag(script, collectionPath) {
|
|
159
|
+
if (!isFileUrl(script.fileUrl))
|
|
160
|
+
return null;
|
|
161
|
+
const tag = {
|
|
162
|
+
fsPath: locationFsPath(script.fileUrl),
|
|
163
|
+
missingHint: scriptFileMissingHint(collectionPath),
|
|
164
|
+
modifiedHint: SCRIPT_FILE_MODIFIED_HINT,
|
|
165
|
+
};
|
|
166
|
+
// Both or neither: a row with a size but no listing mtime (its
|
|
167
|
+
// folder didn't list when it was read) can't be compared, and a lone
|
|
168
|
+
// size would miss same-size edits — worse than saying nothing.
|
|
169
|
+
if (script.size != null && script.fsMtime !== undefined) {
|
|
170
|
+
tag.size = script.size;
|
|
171
|
+
tag.mtime = script.fsMtime;
|
|
172
|
+
}
|
|
173
|
+
return tag;
|
|
174
|
+
}
|
|
175
|
+
// Tag an element as naming a local file, for `markFileChanges`.
|
|
176
|
+
export function tagLocalFile(el, tag) {
|
|
177
|
+
el.dataset.fsPath = tag.fsPath;
|
|
178
|
+
if (tag.size !== undefined)
|
|
179
|
+
el.dataset.fsSize = String(tag.size);
|
|
180
|
+
if (tag.mtime !== undefined)
|
|
181
|
+
el.dataset.fsMtime = String(tag.mtime);
|
|
182
|
+
el.dataset.missingHint = tag.missingHint;
|
|
183
|
+
if (tag.modifiedHint)
|
|
184
|
+
el.dataset.modifiedHint = tag.modifiedHint;
|
|
185
|
+
}
|
|
186
|
+
export const FILE_CHIP_CLASS = 'file-change-chip';
|
|
187
|
+
// Probe every tagged element under `root` and put a chip after each
|
|
188
|
+
// one whose file is gone or changed. Safe to call after every repaint:
|
|
189
|
+
// an earlier chip is removed first, so a file that came back (or was
|
|
190
|
+
// re-read) loses its chip, and an element that left the document
|
|
191
|
+
// before the probe returns is simply annotated where nobody can see
|
|
192
|
+
// it. Resolves with the state of every tagged element, for a caller
|
|
193
|
+
// that wants to summarize.
|
|
194
|
+
//
|
|
195
|
+
// Async and deliberately not awaited by renders: the probe is a disk
|
|
196
|
+
// read, and a synchronous paint must not wait on it.
|
|
197
|
+
export async function markFileChanges(root, maxAgeMs = 0) {
|
|
198
|
+
const tagged = [...root.querySelectorAll('[data-fs-path]')];
|
|
199
|
+
const states = new Map();
|
|
200
|
+
if (tagged.length === 0)
|
|
201
|
+
return states;
|
|
202
|
+
const probed = await probeFiles(tagged.map((el) => ({
|
|
203
|
+
path: el.dataset.fsPath,
|
|
204
|
+
size: el.dataset.fsSize === undefined ? undefined : Number(el.dataset.fsSize),
|
|
205
|
+
mtime: el.dataset.fsMtime === undefined ? undefined : Number(el.dataset.fsMtime),
|
|
206
|
+
})), maxAgeMs);
|
|
207
|
+
tagged.forEach((el, i) => states.set(el, probed[i]));
|
|
208
|
+
for (const el of tagged) {
|
|
209
|
+
// The chip follows the path — and a copy button that directly
|
|
210
|
+
// follows the path, since that button belongs beside the text it
|
|
211
|
+
// copies (the viewer's Location and Local overrides rows).
|
|
212
|
+
let anchor = el;
|
|
213
|
+
if (anchor.nextElementSibling?.classList.contains('copy-btn'))
|
|
214
|
+
anchor = anchor.nextElementSibling;
|
|
215
|
+
const prior = anchor.nextElementSibling;
|
|
216
|
+
if (prior?.classList.contains(FILE_CHIP_CLASS))
|
|
217
|
+
prior.remove();
|
|
218
|
+
const state = states.get(el);
|
|
219
|
+
const hint = state === 'missing'
|
|
220
|
+
? el.dataset.missingHint
|
|
221
|
+
: state === 'modified' ? el.dataset.modifiedHint : undefined;
|
|
222
|
+
// A modified file with no hint is one this surface doesn't report
|
|
223
|
+
// edits for.
|
|
224
|
+
if (!hint)
|
|
225
|
+
continue;
|
|
226
|
+
const chip = document.createElement('span');
|
|
227
|
+
chip.className = `${FILE_CHIP_CLASS} ${FILE_CHIP_CLASS}-${state}`;
|
|
228
|
+
chip.textContent = state === 'missing' ? 'deleted' : 'modified';
|
|
229
|
+
chip.title = hint;
|
|
230
|
+
anchor.after(chip);
|
|
231
|
+
}
|
|
232
|
+
return states;
|
|
233
|
+
}
|
|
234
|
+
// ---------- Wording ----------
|
|
235
|
+
//
|
|
236
|
+
// The hints, in one place so the surfaces that name the same file agree
|
|
237
|
+
// on what its deletion means. Keyed by the collection the row sits in,
|
|
238
|
+
// because that is what decides what happens next:
|
|
239
|
+
//
|
|
240
|
+
// - A directory collection mirrors the disk, so the next refresh drops
|
|
241
|
+
// the row.
|
|
242
|
+
// - A manifest entry is the manifest's to keep or drop: the row stays,
|
|
243
|
+
// running from its last copy, until the entry leaves the manifest.
|
|
244
|
+
// - A file added on its own is the user's: the row stays until they
|
|
245
|
+
// remove it (the trash can on its row).
|
|
246
|
+
export function scriptFileMissingHint(collectionPath) {
|
|
247
|
+
if (isLocalDirectoryCollection(collectionPath)) {
|
|
248
|
+
return 'This file has been deleted. The next refresh will remove this script.';
|
|
249
|
+
}
|
|
250
|
+
if (isManifestCollection(collectionPath)) {
|
|
251
|
+
return 'This file has been deleted. The last copy of the script keeps running '
|
|
252
|
+
+ 'until the entry is removed from the manifest.';
|
|
253
|
+
}
|
|
254
|
+
return 'This file has been deleted. The last copy of the script keeps running until you remove it.';
|
|
255
|
+
}
|
|
256
|
+
// An edited script file: the same for every collection kind, since a
|
|
257
|
+
// refresh re-reads it wherever it sits.
|
|
258
|
+
export const SCRIPT_FILE_MODIFIED_HINT = 'This file has changed on disk. The next refresh will load the new version.';
|
|
259
|
+
// A local stand-in for a `@require` / `@resource`. Deleted: the
|
|
260
|
+
// declared URL is still there, so the next refresh loads it from
|
|
261
|
+
// there instead. Edited: the next refresh (opportunistic ones
|
|
262
|
+
// included — a stand-in is re-read every pass) picks it up.
|
|
263
|
+
export const STAND_IN_MISSING_HINT = 'This file has been deleted. The next refresh will reload the remote version.';
|
|
264
|
+
// The same words as for a script file: the outcome is the same.
|
|
265
|
+
export const STAND_IN_MODIFIED_HINT = SCRIPT_FILE_MODIFIED_HINT;
|
|
266
|
+
// A file sm-dev pushed with a scratch script: a snapshot no refresh
|
|
267
|
+
// rereads. It has no stored mtime, so only a deletion can be told.
|
|
268
|
+
export const PUSHED_FILE_MISSING_HINT = 'This file has been deleted since sm-dev pushed it. The script still runs the snapshot it pushed.';
|
|
269
|
+
// The tag for a stand-in, given its path and the stored entry. The
|
|
270
|
+
// entry's `size` is the bytes read, which is the file's size; with the
|
|
271
|
+
// listing mtime recorded beside it, an edit can be told apart.
|
|
272
|
+
// `pushed` is for a snapshot sm-dev pushed.
|
|
273
|
+
export function standInTag(fsPath, entry, pushed = false) {
|
|
274
|
+
const tag = {
|
|
275
|
+
fsPath,
|
|
276
|
+
missingHint: pushed ? PUSHED_FILE_MISSING_HINT : STAND_IN_MISSING_HINT,
|
|
277
|
+
...(pushed ? {} : { modifiedHint: STAND_IN_MODIFIED_HINT }),
|
|
278
|
+
};
|
|
279
|
+
if (entry.size !== undefined && entry.fsMtime !== undefined) {
|
|
280
|
+
tag.size = entry.size;
|
|
281
|
+
tag.mtime = entry.fsMtime;
|
|
282
|
+
}
|
|
283
|
+
return tag;
|
|
284
|
+
}
|
|
285
|
+
// Counts a directory or `file://` manifest collection's changes on
|
|
286
|
+
// disk: its rows' probed `files`, plus the new files a directory holds,
|
|
287
|
+
// or whether the manifest file changed (`manifest`: its path and the
|
|
288
|
+
// stat its last scan recorded). Null for any other collection.
|
|
289
|
+
export async function pendingOnDisk(collectionPath, manifest, files, maxAgeMs = 0) {
|
|
290
|
+
const isDirectory = isLocalDirectoryCollection(collectionPath);
|
|
291
|
+
if (!isDirectory && !(isManifestCollection(collectionPath) && manifest))
|
|
292
|
+
return null;
|
|
293
|
+
const counts = { modified: 0, deleted: 0, added: 0 };
|
|
294
|
+
for (const f of files) {
|
|
295
|
+
if (f.state === 'missing')
|
|
296
|
+
counts.deleted += 1;
|
|
297
|
+
else if (f.state === 'modified')
|
|
298
|
+
counts.modified += 1;
|
|
299
|
+
}
|
|
300
|
+
if (isDirectory) {
|
|
301
|
+
const root = locationFsPath(collectionPath).replace(/\/+$/, '');
|
|
302
|
+
const known = files.map((f) => f.fsPath.slice(root.length + 1));
|
|
303
|
+
counts.added = (await newScriptFiles(collectionPath, known, maxAgeMs)).length;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
const [state] = await probeFiles([manifest], maxAgeMs);
|
|
307
|
+
if (state === 'modified')
|
|
308
|
+
counts.manifest = true;
|
|
309
|
+
}
|
|
310
|
+
return counts;
|
|
311
|
+
}
|
|
312
|
+
// The manifest file to probe for a `file://` manifest collection, with
|
|
313
|
+
// the stat its last scan recorded; null for any other.
|
|
314
|
+
export function manifestToProbe(collection) {
|
|
315
|
+
if (!isManifestCollection(collection.path) || !isFileUrl(collectionCanonicalUrl(collection.path)))
|
|
316
|
+
return null;
|
|
317
|
+
return { path: locationFsPath(collection.path), size: collection.manifestSize, mtime: collection.manifestMtime };
|
|
318
|
+
}
|
|
319
|
+
// The tooltip of a collection's "refresh pending" chip: the summary, and
|
|
320
|
+
// what to do. Empty when nothing differs.
|
|
321
|
+
export function refreshPendingHint(counts) {
|
|
322
|
+
const summary = pendingSummary(counts);
|
|
323
|
+
return summary ? `${summary} on disk. Refresh to load.` : '';
|
|
324
|
+
}
|
|
325
|
+
export const REFRESH_PENDING_CHIP_CLASS = `${FILE_CHIP_CLASS} ${FILE_CHIP_CLASS}-pending`;
|
|
326
|
+
// Put a "refresh pending" chip at the end of `nameEl` (a collection's
|
|
327
|
+
// display name), or remove the one there, to match `hint` (empty =
|
|
328
|
+
// nothing pending). Inside the name rather than after it: the heading
|
|
329
|
+
// is a two-column grid (bubble, name), where a third child would start
|
|
330
|
+
// a new row.
|
|
331
|
+
export function syncRefreshPendingChip(nameEl, hint) {
|
|
332
|
+
const existing = nameEl.querySelector(`:scope > .${FILE_CHIP_CLASS}-pending`);
|
|
333
|
+
if (!hint) {
|
|
334
|
+
existing?.remove();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (existing) {
|
|
338
|
+
existing.title = hint;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const chip = document.createElement('span');
|
|
342
|
+
chip.className = REFRESH_PENDING_CHIP_CLASS;
|
|
343
|
+
chip.textContent = 'refresh pending';
|
|
344
|
+
chip.title = hint;
|
|
345
|
+
nameEl.appendChild(chip);
|
|
346
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Where a message from injected code came from: one of a page's
|
|
2
|
+
// iframes or a page's own (top-level) frame, and which page. See
|
|
3
|
+
// docs/frames.md.
|
|
4
|
+
//
|
|
5
|
+
// `sender.frameId > 0` alone can't tell: Chrome numbers a prerendered
|
|
6
|
+
// page's top frame like an iframe (its `frameType` is `outermost_frame`,
|
|
7
|
+
// which `MessageSender` doesn't carry). So a document with a frame id
|
|
8
|
+
// other than 0 is looked up with `chrome.webNavigation.getFrame`, and
|
|
9
|
+
// walked up through its parents to the page's document.
|
|
10
|
+
//
|
|
11
|
+
// Answers are cached by document id, which Chrome never reuses. A
|
|
12
|
+
// document's place doesn't change, but whether it is still in the tab
|
|
13
|
+
// does, so `live` is asked afresh when the caller needs it.
|
|
14
|
+
// Bounded: an entry per document of every frame that ever messaged us.
|
|
15
|
+
const CACHE_CAP = 1000;
|
|
16
|
+
const cache = new Map();
|
|
17
|
+
function remember(documentId, placed) {
|
|
18
|
+
if (cache.size >= CACHE_CAP)
|
|
19
|
+
cache.delete(cache.keys().next().value);
|
|
20
|
+
cache.set(documentId, placed);
|
|
21
|
+
}
|
|
22
|
+
// The frame `documentId` is, or null when Chrome no longer has it (it
|
|
23
|
+
// answers null, or refuses). `documentId` isn't in our @types/chrome
|
|
24
|
+
// version of `GetFrameDetails`, hence the cast.
|
|
25
|
+
async function getFrame(tabId, documentId) {
|
|
26
|
+
try {
|
|
27
|
+
const details = { tabId, documentId };
|
|
28
|
+
return (await chrome.webNavigation.getFrame(details));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Frames nest, but not this deep in practice; the bound keeps a
|
|
35
|
+
// misbehaving answer from looping.
|
|
36
|
+
const MAX_DEPTH = 32;
|
|
37
|
+
/**
|
|
38
|
+
* Where `sender`'s document is. `checkLive` asks Chrome whether it is
|
|
39
|
+
* still in the tab even when its place is cached.
|
|
40
|
+
*/
|
|
41
|
+
export async function frameInfo(sender, checkLive = false) {
|
|
42
|
+
const tabId = sender.tab?.id;
|
|
43
|
+
const documentId = sender.documentId ?? '';
|
|
44
|
+
const frameId = sender.frameId;
|
|
45
|
+
if (frameId === 0)
|
|
46
|
+
return { subframe: false, topDocId: documentId, live: true };
|
|
47
|
+
// Nothing to look up with: by the frame id, as before.
|
|
48
|
+
if (tabId == null || !documentId || typeof frameId !== 'number') {
|
|
49
|
+
return { subframe: typeof frameId === 'number' && frameId > 0, topDocId: '', live: true };
|
|
50
|
+
}
|
|
51
|
+
const cached = cache.get(documentId);
|
|
52
|
+
if (cached && !checkLive)
|
|
53
|
+
return { ...cached, live: true };
|
|
54
|
+
const frame = await getFrame(tabId, documentId);
|
|
55
|
+
if (!frame)
|
|
56
|
+
return { ...(cached ?? { subframe: true, topDocId: '' }), live: false };
|
|
57
|
+
if (cached)
|
|
58
|
+
return { ...cached, live: true };
|
|
59
|
+
let placed;
|
|
60
|
+
if (frame.parentFrameId === -1) {
|
|
61
|
+
placed = { subframe: false, topDocId: documentId, topUrl: frame.url };
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
placed = { subframe: true, topDocId: '' };
|
|
65
|
+
let parent = frame.parentDocumentId;
|
|
66
|
+
for (let depth = 0; parent && depth < MAX_DEPTH; depth++) {
|
|
67
|
+
const up = cache.get(parent);
|
|
68
|
+
if (up && !up.subframe) {
|
|
69
|
+
placed = { subframe: true, topDocId: up.topDocId, topUrl: up.topUrl };
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
const next = await getFrame(tabId, parent);
|
|
73
|
+
if (!next)
|
|
74
|
+
break;
|
|
75
|
+
if (next.parentFrameId === -1) {
|
|
76
|
+
placed = { subframe: true, topDocId: parent, topUrl: next.url };
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
parent = next.parentDocumentId;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (placed.topDocId)
|
|
83
|
+
remember(documentId, placed);
|
|
84
|
+
return { ...placed, live: true };
|
|
85
|
+
}
|