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,439 @@
|
|
|
1
|
+
// Telling locations apart (http, file, container and collection
|
|
2
|
+
// paths), path helpers, reading a `file://` URL, and Chrome's `file://`
|
|
3
|
+
// directory listings.
|
|
4
|
+
//
|
|
5
|
+
// The part of directories.ts that loading a script's local files
|
|
6
|
+
// needs, in its own module so the development harness can use it
|
|
7
|
+
// without the collection scanners and GitHub provider. directories.ts
|
|
8
|
+
// re-exports it for its other callers.
|
|
9
|
+
import { MANIFEST_FILENAME } from './collection-scan.js';
|
|
10
|
+
// ---------- Collection-kind detection ----------
|
|
11
|
+
// All collection-kind checks key off the raw input string the user
|
|
12
|
+
// typed. `http://` / `https://` selects an HTTP collection; everything
|
|
13
|
+
// else is treated as an absolute filesystem path. Suffix tests look at
|
|
14
|
+
// the URL pathname (sans query/fragment) so e.g. `…/foo.user.js?v=2`
|
|
15
|
+
// still classifies as a single-file URL.
|
|
16
|
+
// The shapes a user may type as a location, as a sentence fragment.
|
|
17
|
+
// Shared because two validators reject on it — `isValidLocationShape`
|
|
18
|
+
// (directories.ts) and the viewer's preview (`isPreviewableInput`) — and their
|
|
19
|
+
// messages had already drifted into two different descriptions of one
|
|
20
|
+
// rule.
|
|
21
|
+
export const LOCATION_SHAPES = 'must be a URL or absolute file path';
|
|
22
|
+
export function isHttpUrl(input) {
|
|
23
|
+
return /^https?:\/\//i.test(input);
|
|
24
|
+
}
|
|
25
|
+
export function isFileUrl(input) {
|
|
26
|
+
return /^file:\/\//i.test(input);
|
|
27
|
+
}
|
|
28
|
+
const CONTAINER_PATH_PREFIX = 'container:';
|
|
29
|
+
// The fixed path of the built-in default container for a scheme.
|
|
30
|
+
export function containerPath(kind) {
|
|
31
|
+
return CONTAINER_PATH_PREFIX + kind;
|
|
32
|
+
}
|
|
33
|
+
// A fresh path for a user-created container.
|
|
34
|
+
export function newContainerPath() {
|
|
35
|
+
return CONTAINER_PATH_PREFIX + crypto.randomUUID();
|
|
36
|
+
}
|
|
37
|
+
// A collection that is a local folder scanned for `*.user.js` files —
|
|
38
|
+
// the kind whose listing is its source of truth, so a file it no
|
|
39
|
+
// longer shows is dropped (see `rebuildCollection`). Everything else
|
|
40
|
+
// (http(s) and GitHub, a manifest, a single-file container) names its
|
|
41
|
+
// scripts some other way.
|
|
42
|
+
export function isLocalDirectoryCollection(collectionPath) {
|
|
43
|
+
return !isHttpUrl(collectionPath)
|
|
44
|
+
&& !isManifestCollection(collectionPath)
|
|
45
|
+
&& !isContainerCollection(collectionPath);
|
|
46
|
+
}
|
|
47
|
+
export function isContainerCollection(input) {
|
|
48
|
+
return input.startsWith(CONTAINER_PATH_PREFIX);
|
|
49
|
+
}
|
|
50
|
+
// Which scheme this container is the built-in default for — or null
|
|
51
|
+
// for a user-created container (and for anything that isn't a
|
|
52
|
+
// container). NOT a restriction on what the container may hold.
|
|
53
|
+
export function containerKind(input) {
|
|
54
|
+
if (input === 'container:file')
|
|
55
|
+
return 'file';
|
|
56
|
+
if (input === 'container:http')
|
|
57
|
+
return 'http';
|
|
58
|
+
if (input === 'container:github')
|
|
59
|
+
return 'github';
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
export function collectionKind(path) {
|
|
63
|
+
if (isContainerCollection(path))
|
|
64
|
+
return 'scripts';
|
|
65
|
+
if (isManifestCollection(path))
|
|
66
|
+
return 'manifest';
|
|
67
|
+
return 'directory';
|
|
68
|
+
}
|
|
69
|
+
// Pathname of an http(s) or file:// URL, sans query and fragment.
|
|
70
|
+
// Returns '' if the string doesn't parse as a URL — callers gate on
|
|
71
|
+
// isHttpUrl / isFileUrl first.
|
|
72
|
+
export function urlPathname(input) {
|
|
73
|
+
try {
|
|
74
|
+
return new URL(input).pathname;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Pathname for *any* collection-input form (raw filesystem path,
|
|
81
|
+
// file:// URL, or http(s):// URL). Used by suffix tests that don't care
|
|
82
|
+
// about scheme.
|
|
83
|
+
function locationPathname(input) {
|
|
84
|
+
if (isHttpUrl(input) || isFileUrl(input))
|
|
85
|
+
return urlPathname(input);
|
|
86
|
+
return input;
|
|
87
|
+
}
|
|
88
|
+
// Canonical URL form for a collection input. Raw filesystem paths get
|
|
89
|
+
// percent-encoded and wrapped as file://; URLs pass through. Used as
|
|
90
|
+
// the base for URL.resolution of manifest entries so a local manifest
|
|
91
|
+
// can list relative paths the same way an HTTP one can.
|
|
92
|
+
export function collectionCanonicalUrl(input) {
|
|
93
|
+
if (isHttpUrl(input) || isFileUrl(input))
|
|
94
|
+
return input;
|
|
95
|
+
return pathToFileUrl(input);
|
|
96
|
+
}
|
|
97
|
+
// Inverse of collectionCanonicalUrl for local inputs: a file:// URL
|
|
98
|
+
// turns into the absolute filesystem path it points at; a raw path
|
|
99
|
+
// passes through. Lets the local directory/single-file branches accept
|
|
100
|
+
// the URL form the user typed without each branch having to
|
|
101
|
+
// special-case the parse. Percent-encoded segments (e.g.
|
|
102
|
+
// `my%20scripts`) are decoded so downstream path helpers see the
|
|
103
|
+
// on-disk name verbatim.
|
|
104
|
+
export function locationFsPath(input) {
|
|
105
|
+
if (!isFileUrl(input))
|
|
106
|
+
return input;
|
|
107
|
+
try {
|
|
108
|
+
return decodeURIComponent(new URL(input).pathname);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return input; // unparseable URL → let the caller's I/O fail with a clearer message
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Windows drive-letter (`file:///C:/…`) and UNC (`file://server/…`)
|
|
115
|
+
// URLs. `locationFsPath` mangles both — it strips only the leading POSIX
|
|
116
|
+
// slash and discards `host` — so the canonicalizer below leaves them
|
|
117
|
+
// as URLs rather than producing a path that points somewhere else.
|
|
118
|
+
// The rest of the module is POSIX-only by design (see `pathToFileUrl`,
|
|
119
|
+
// `parentDir`), so this is containment, not Windows support.
|
|
120
|
+
// Exported because the collections page's `?refresh_file=` validator
|
|
121
|
+
// has to refuse the same two shapes before comparing paths — a mangled
|
|
122
|
+
// path there would silently match the wrong local collection.
|
|
123
|
+
export function isNonPosixFileUrl(input) {
|
|
124
|
+
let u;
|
|
125
|
+
try {
|
|
126
|
+
u = new URL(input);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return u.host !== '' || /^\/[A-Za-z]:/.test(u.pathname);
|
|
132
|
+
}
|
|
133
|
+
// One canonical spelling for a collection input, so every entry point
|
|
134
|
+
// agrees on what a given file is called: the collections page's path
|
|
135
|
+
// field, an `addCollection` call, and a captured `file://` navigation
|
|
136
|
+
// all have to produce the same `relativePath`, or one file on disk ends
|
|
137
|
+
// up with two installed rows.
|
|
138
|
+
//
|
|
139
|
+
// The bare filesystem path is the winner because it is what the path
|
|
140
|
+
// field (the oldest and most common route) has always stored. http(s)
|
|
141
|
+
// inputs pass through untouched — there is only one spelling of those.
|
|
142
|
+
export function canonicalCollectionInput(input) {
|
|
143
|
+
if (!isFileUrl(input) || isNonPosixFileUrl(input))
|
|
144
|
+
return input;
|
|
145
|
+
return locationFsPath(input);
|
|
146
|
+
}
|
|
147
|
+
// True for any input pointing at an individual `.user.js` script —
|
|
148
|
+
// http(s):// URL, file:// URL, or absolute local path — based on the
|
|
149
|
+
// pathname suffix. Single-script inputs don't get their own collection
|
|
150
|
+
// row; the install flow puts them into a container collection (by
|
|
151
|
+
// default the one for their scheme — see `containerKindForSingleFileInput`).
|
|
152
|
+
export function isSingleFileInput(input) {
|
|
153
|
+
return locationPathname(input).endsWith('.user.js');
|
|
154
|
+
}
|
|
155
|
+
// True for any manifest collection — http(s):// URL, file:// URL, or an
|
|
156
|
+
// absolute local path — whose pathname's basename is exactly
|
|
157
|
+
// `MANIFEST_FILENAME`. The fixed-filename rule (rather than a generic
|
|
158
|
+
// `.json` suffix) keeps unrelated JSON files from being mistaken for
|
|
159
|
+
// script manifests and gives the convention a single, searchable name.
|
|
160
|
+
export function isManifestCollection(input) {
|
|
161
|
+
const p = locationPathname(input);
|
|
162
|
+
return p === MANIFEST_FILENAME
|
|
163
|
+
|| p.endsWith('/' + MANIFEST_FILENAME);
|
|
164
|
+
}
|
|
165
|
+
// ---------- URL / path helpers ----------
|
|
166
|
+
// Build a file:// URL from an absolute filesystem path, percent-encoding
|
|
167
|
+
// each segment so spaces and other reserved characters produce a valid
|
|
168
|
+
// URL. POSIX paths get the conventional `file://` + leading slash.
|
|
169
|
+
export function pathToFileUrl(absolutePath) {
|
|
170
|
+
const trimmed = absolutePath.replace(/\/+$/, '');
|
|
171
|
+
const encoded = trimmed.split('/').map(encodeURIComponent).join('/');
|
|
172
|
+
return encoded.startsWith('/') ? `file://${encoded}` : `file:///${encoded}`;
|
|
173
|
+
}
|
|
174
|
+
export function joinPath(base, child) {
|
|
175
|
+
return base.replace(/\/+$/, '') + '/' + child;
|
|
176
|
+
}
|
|
177
|
+
// Return the parent directory of an absolute POSIX path. The result
|
|
178
|
+
// has no trailing slash; for a top-level path like `/foo` returns `/`.
|
|
179
|
+
export function parentDir(absolutePath) {
|
|
180
|
+
const trimmed = absolutePath.replace(/\/+$/, '');
|
|
181
|
+
const slash = trimmed.lastIndexOf('/');
|
|
182
|
+
if (slash < 0)
|
|
183
|
+
return '';
|
|
184
|
+
if (slash === 0)
|
|
185
|
+
return '/';
|
|
186
|
+
return trimmed.slice(0, slash);
|
|
187
|
+
}
|
|
188
|
+
// Return the leaf name of an absolute POSIX path (the part after the
|
|
189
|
+
// last `/`). Trailing slashes are stripped first.
|
|
190
|
+
export function baseName(absolutePath) {
|
|
191
|
+
const trimmed = absolutePath.replace(/\/+$/, '');
|
|
192
|
+
const slash = trimmed.lastIndexOf('/');
|
|
193
|
+
return slash < 0 ? trimmed : trimmed.slice(slash + 1);
|
|
194
|
+
}
|
|
195
|
+
// ---------- Reading a file:// URL ----------
|
|
196
|
+
// A `file://` URL's text.
|
|
197
|
+
//
|
|
198
|
+
// Why XMLHttpRequest in page contexts: fetch() treats
|
|
199
|
+
// chrome-extension:// → file:// as cross-origin and returns an opaque
|
|
200
|
+
// response (status 0, unreadable body). XHR doesn't apply that
|
|
201
|
+
// restriction. For file:// XHRs, `status` stays 0 even on success
|
|
202
|
+
// (there's no HTTP layer); a missing file fires `onerror`, while a
|
|
203
|
+
// readable file (including a 0-byte one) fires `onload`.
|
|
204
|
+
//
|
|
205
|
+
// Service-worker fallback: MV3 service workers have no XMLHttpRequest
|
|
206
|
+
// at all, so we can't use the XHR path there. fetch() does work from a
|
|
207
|
+
// SW for `file://` URLs as long as the manifest grants `file:///*` in
|
|
208
|
+
// host_permissions (and the user has flipped "Allow access to file
|
|
209
|
+
// URLs") — the cross-origin restriction above is a document-context
|
|
210
|
+
// behavior, not a SW one. Node (the harness) takes this path too. We
|
|
211
|
+
// pick the path at call time by checking for XHR availability.
|
|
212
|
+
export function readFileUrlText(url) {
|
|
213
|
+
if (typeof XMLHttpRequest !== 'undefined')
|
|
214
|
+
return fetchFileUrlTextXhr(url);
|
|
215
|
+
return fetchFileUrlTextFetch(url);
|
|
216
|
+
}
|
|
217
|
+
// `readFileUrlText`, plus what a listing of the file's folder would
|
|
218
|
+
// report for it: its size in bytes and its modified time. One read
|
|
219
|
+
// instead of a listing and a read, for a check that knows the file's
|
|
220
|
+
// path. `fsMtime` is absent when Chrome sent no `Last-Modified`.
|
|
221
|
+
export async function readFileUrlWithStat(url) {
|
|
222
|
+
const { bytes, lastModified } = typeof XMLHttpRequest !== 'undefined'
|
|
223
|
+
? await fetchFileUrlBytesXhr(url)
|
|
224
|
+
: await fetchFileUrlBytesFetch(url);
|
|
225
|
+
// Decoded as the worker's `response.text()` does: UTF-8, a UTF-8 BOM
|
|
226
|
+
// dropped, bad bytes replaced rather than thrown on. (A page's
|
|
227
|
+
// `responseText` would also detect a UTF-16 BOM; nothing here reads
|
|
228
|
+
// one, and no page calls this today.)
|
|
229
|
+
const text = new TextDecoder().decode(bytes);
|
|
230
|
+
const fsMtime = fsMtimeFromLastModified(lastModified);
|
|
231
|
+
return { text, size: bytes.byteLength, ...(fsMtime !== undefined ? { fsMtime } : {}) };
|
|
232
|
+
}
|
|
233
|
+
// Chrome gives a `file://` response the file's modified time as
|
|
234
|
+
// `Last-Modified`. Its directory listing reports the same time in whole
|
|
235
|
+
// seconds, which `Last-Modified` can't be finer than, so dividing gives
|
|
236
|
+
// the listing's number exactly. That lets a direct read stand in for
|
|
237
|
+
// listing the folder.
|
|
238
|
+
export function fsMtimeFromLastModified(header) {
|
|
239
|
+
if (!header)
|
|
240
|
+
return undefined;
|
|
241
|
+
const ms = Date.parse(header);
|
|
242
|
+
return Number.isFinite(ms) ? Math.floor(ms / 1000) : undefined;
|
|
243
|
+
}
|
|
244
|
+
function fetchFileUrlBytesXhr(url) {
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
const xhr = new XMLHttpRequest();
|
|
247
|
+
xhr.open('GET', url);
|
|
248
|
+
xhr.responseType = 'arraybuffer';
|
|
249
|
+
xhr.onload = () => resolve({
|
|
250
|
+
bytes: xhr.response ?? new ArrayBuffer(0),
|
|
251
|
+
lastModified: xhr.getResponseHeader('last-modified'),
|
|
252
|
+
});
|
|
253
|
+
xhr.onerror = () => reject(new Error(`Could not read ${url}`));
|
|
254
|
+
xhr.send();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
async function fetchFileUrlBytesFetch(url) {
|
|
258
|
+
const response = await fetchFileUrlResponse(url);
|
|
259
|
+
return { bytes: await response.arrayBuffer(), lastModified: response.headers.get('last-modified') };
|
|
260
|
+
}
|
|
261
|
+
function fetchFileUrlTextXhr(url) {
|
|
262
|
+
return new Promise((resolve, reject) => {
|
|
263
|
+
const xhr = new XMLHttpRequest();
|
|
264
|
+
xhr.open('GET', url);
|
|
265
|
+
xhr.responseType = 'text';
|
|
266
|
+
// For file:// XHRs status is always 0. A readable file (including
|
|
267
|
+
// a 0-byte one) fires `onload`; a missing file fires `onerror`.
|
|
268
|
+
// Resolve with whatever responseText we got — '' is a valid result
|
|
269
|
+
// for an empty file and must not be conflated with "missing".
|
|
270
|
+
xhr.onload = () => resolve(xhr.responseText ?? '');
|
|
271
|
+
xhr.onerror = () => reject(new Error(`Could not read ${url}`));
|
|
272
|
+
xhr.send();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// Service-worker path: fetch() works for file:// URLs when the SW has
|
|
276
|
+
// the matching host permission. `response.ok` is true for file://
|
|
277
|
+
// fetches that succeed (Chrome synthesises status 200); a 404-style
|
|
278
|
+
// missing file shows up as a fetch rejection or response.ok === false,
|
|
279
|
+
// not a thrown exception with a specific status code, so we have to
|
|
280
|
+
// inspect both.
|
|
281
|
+
async function fetchFileUrlTextFetch(url) {
|
|
282
|
+
// An empty body here is a 0-byte file, not an error — keep it
|
|
283
|
+
// distinct from "missing" so the row renders Size=0 Status=ERROR
|
|
284
|
+
// (no metadata block) rather than getting flagged as gone.
|
|
285
|
+
return await (await fetchFileUrlResponse(url)).text();
|
|
286
|
+
}
|
|
287
|
+
// The worker's `file://` response, once it is known to have read. Shared
|
|
288
|
+
// by the text and byte readers, so a failure reads the same from both.
|
|
289
|
+
async function fetchFileUrlResponse(url) {
|
|
290
|
+
let response;
|
|
291
|
+
try {
|
|
292
|
+
response = await fetch(url);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
throw new Error(`Could not read ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
296
|
+
}
|
|
297
|
+
if (!response.ok && response.status !== 0) {
|
|
298
|
+
throw new Error(`Could not read ${url}: HTTP ${response.status}`);
|
|
299
|
+
}
|
|
300
|
+
// Opaque responses (response.type === 'opaque') have an unreadable
|
|
301
|
+
// body; surface a distinct error rather than returning empty content,
|
|
302
|
+
// so a manual refresh from a page context can succeed where the SW
|
|
303
|
+
// path fell through.
|
|
304
|
+
if (response.type === 'opaque') {
|
|
305
|
+
throw new Error(`Could not read ${url} in background refresh (file:// fetch is not permitted there). ` +
|
|
306
|
+
`Try Refresh from the script page.`);
|
|
307
|
+
}
|
|
308
|
+
return response;
|
|
309
|
+
}
|
|
310
|
+
// Chrome's file:// directory listing is an HTML page that injects
|
|
311
|
+
// entries via JavaScript calls like:
|
|
312
|
+
// addRow("name", "url", isdir, size, "size_str", date, "date_str")
|
|
313
|
+
// where `isdir` is 0/1 (older Chrome) or true/false. We pull:
|
|
314
|
+
// - the name (first arg) and the isdir flag (third) to classify;
|
|
315
|
+
// - the numeric size (fourth) and date (sixth) so the refresh
|
|
316
|
+
// pipeline can skip refetching files whose size+mtime are
|
|
317
|
+
// unchanged.
|
|
318
|
+
//
|
|
319
|
+
// This is intentionally tolerant: Chrome's exact listing format isn't
|
|
320
|
+
// a stable contract, so the regex matches the common shape and ignores
|
|
321
|
+
// any addRow calls it can't parse. The size/mtime captures are
|
|
322
|
+
// optional — entries without them parse but with size/mtime undefined,
|
|
323
|
+
// at which point the refresh pipeline falls back to always-refetch
|
|
324
|
+
// for those files.
|
|
325
|
+
//
|
|
326
|
+
// An empty result is ambiguous on its own — a listing with no rows, or
|
|
327
|
+
// a page that isn't a listing at all — which is why `listDirectory`
|
|
328
|
+
// checks `isDirectoryListing` first.
|
|
329
|
+
export function parseListing(html) {
|
|
330
|
+
const entries = [];
|
|
331
|
+
const re = /addRow\("((?:\\.|[^"\\])*)"\s*,\s*"(?:\\.|[^"\\])*"\s*,\s*(0|1|true|false)\b(?:\s*,\s*(-?\d+))?(?:\s*,\s*"(?:\\.|[^"\\])*")?(?:\s*,\s*(-?\d+))?/g;
|
|
332
|
+
let m;
|
|
333
|
+
while ((m = re.exec(html)) !== null) {
|
|
334
|
+
let name;
|
|
335
|
+
try {
|
|
336
|
+
// The captured first argument is a JS string literal body —
|
|
337
|
+
// JSON.parse round-trips the standard \" \\ \n \uXXXX escapes.
|
|
338
|
+
name = JSON.parse('"' + m[1] + '"');
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (name === '..' || name === '.')
|
|
344
|
+
continue;
|
|
345
|
+
const isDir = m[2] === '1' || m[2] === 'true';
|
|
346
|
+
const entry = { name, isDir };
|
|
347
|
+
if (m[3] !== undefined)
|
|
348
|
+
entry.size = Number(m[3]);
|
|
349
|
+
if (m[4] !== undefined)
|
|
350
|
+
entry.mtime = Number(m[4]);
|
|
351
|
+
entries.push(entry);
|
|
352
|
+
}
|
|
353
|
+
return entries;
|
|
354
|
+
}
|
|
355
|
+
// How many directory listings this JS context has fetched SUCCESSFULLY
|
|
356
|
+
// since the last reset. Scoped and counted narrowly on purpose:
|
|
357
|
+
//
|
|
358
|
+
// - Per-context, not global. This module is imported by the
|
|
359
|
+
// extension pages too, and a check that runs in the page that
|
|
360
|
+
// clicked never touches the worker's copy of this number.
|
|
361
|
+
// - Every listing the context does, not just batched ones — the
|
|
362
|
+
// recursive `scanForUserScripts` walk and collection refreshes land
|
|
363
|
+
// here too, so a test has to reset around the window it cares about.
|
|
364
|
+
// - Cache hits don't count, which is the point: it is the number a
|
|
365
|
+
// test asserts on to show a batch shared one listing rather than
|
|
366
|
+
// doing N.
|
|
367
|
+
//
|
|
368
|
+
// Exposed through the SW test surface (see src/extension/background.ts).
|
|
369
|
+
let listingFetches = 0;
|
|
370
|
+
export function getDirectoryListingFetches() {
|
|
371
|
+
return listingFetches;
|
|
372
|
+
}
|
|
373
|
+
export function resetDirectoryListingFetches() {
|
|
374
|
+
listingFetches = 0;
|
|
375
|
+
}
|
|
376
|
+
// Is this page Chrome's directory listing at all? The listing page
|
|
377
|
+
// opens with a `start("<path>")` call before any rows, and an empty
|
|
378
|
+
// directory still has that. Anything without it — an error page, a
|
|
379
|
+
// path that is now a file, a format Chrome changed under us — is
|
|
380
|
+
// refused rather than read as "no entries", because those two answers
|
|
381
|
+
// have opposite consequences for a refresh: an empty listing drops
|
|
382
|
+
// every script in the collection, a failed one leaves them all alone.
|
|
383
|
+
export function isDirectoryListing(html) {
|
|
384
|
+
return /\bstart\s*\(\s*"/.test(html);
|
|
385
|
+
}
|
|
386
|
+
export const NOT_A_DIRECTORY_LISTING = 'Chrome did not return a directory listing for this folder';
|
|
387
|
+
export async function listDirectory(absolutePath, cache) {
|
|
388
|
+
const hit = cache?.get(absolutePath);
|
|
389
|
+
if (hit)
|
|
390
|
+
return hit;
|
|
391
|
+
const pending = (async () => {
|
|
392
|
+
// Chrome only serves the directory listing for URLs ending with `/`.
|
|
393
|
+
const url = pathToFileUrl(absolutePath) + '/';
|
|
394
|
+
const html = await readFileUrlText(url);
|
|
395
|
+
if (!isDirectoryListing(html)) {
|
|
396
|
+
throw new Error(`Could not read ${url}: ${NOT_A_DIRECTORY_LISTING}`);
|
|
397
|
+
}
|
|
398
|
+
const entries = parseListing(html);
|
|
399
|
+
// Counted after the fetch resolves, so the number means what its
|
|
400
|
+
// name says. A throw leaves it alone.
|
|
401
|
+
listingFetches += 1;
|
|
402
|
+
return entries;
|
|
403
|
+
})();
|
|
404
|
+
if (cache) {
|
|
405
|
+
cache.set(absolutePath, pending);
|
|
406
|
+
// A failed listing must not be remembered. Otherwise one transient
|
|
407
|
+
// read error is inherited by every later sibling in the batch, none
|
|
408
|
+
// of which ever retries — turning a single script's bad luck into
|
|
409
|
+
// the whole page refetching bodies it didn't need to. (Safe either
|
|
410
|
+
// way: `refreshScript` catches, leaving the scan bare, which means
|
|
411
|
+
// always-fetch. The cost is wasted reads, never a missed change.)
|
|
412
|
+
// The identity check keeps a late rejection from evicting a newer
|
|
413
|
+
// entry that has already replaced it.
|
|
414
|
+
void pending.catch(() => {
|
|
415
|
+
if (cache.get(absolutePath) === pending)
|
|
416
|
+
cache.delete(absolutePath);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return pending;
|
|
420
|
+
}
|
|
421
|
+
// Whether `cache` already holds a listing for this directory — i.e.
|
|
422
|
+
// whether the next `statFromListing` will pay for a fetch. Read by the
|
|
423
|
+
// refresh path's phase timer so a shared listing shows up as a shared
|
|
424
|
+
// listing rather than as a phase that mysteriously got fast.
|
|
425
|
+
export function hasCachedListing(cache, absoluteDir) {
|
|
426
|
+
return cache?.has(absoluteDir) ?? false;
|
|
427
|
+
}
|
|
428
|
+
// Targeted version of listDirectory: list a single directory and return
|
|
429
|
+
// just the entry for `fileName` (or null if absent). Used by the
|
|
430
|
+
// single-file refresh path so it can capture the file's current
|
|
431
|
+
// size/mtime without doing a full recursive scan — keeps the
|
|
432
|
+
// unchanged-file skip honest for the next Refresh All.
|
|
433
|
+
//
|
|
434
|
+
// `cache` is optional and batch-scoped: a navigation's fan-out passes
|
|
435
|
+
// one so N scripts in the same folder list it once between them.
|
|
436
|
+
export async function statFromListing(absoluteDir, fileName, cache) {
|
|
437
|
+
const entries = await listDirectory(absoluteDir, cache);
|
|
438
|
+
return entries.find((e) => e.name === fileName) ?? null;
|
|
439
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Type declarations for the runtime module that the build emits
|
|
2
|
+
// directly into dist/generated-wrappers.js from src/wrapper-*.js and
|
|
3
|
+
// src/userscript-prelude.js.
|
|
4
|
+
//
|
|
5
|
+
// No corresponding .ts file exists — keeping this as a .d.ts means
|
|
6
|
+
// tsc doesn't try to compile it, and the build owns the .js output
|
|
7
|
+
// without a stale source-tree copy.
|
|
8
|
+
|
|
9
|
+
export const WRAPPER_PRELUDE: string;
|
|
10
|
+
export const WRAPPER_BRIDGE: string;
|
|
11
|
+
export const WRAPPER_GM_RUNTIME: string;
|