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,558 @@
|
|
|
1
|
+
// Local stand-ins for the files a script declares by absolute URL.
|
|
2
|
+
//
|
|
3
|
+
// The problem: a `@require https://cdn.example.com/lib/util.js` is the
|
|
4
|
+
// only form other managers understand, so a script meant to be portable
|
|
5
|
+
// has to write it — but while you are *developing* that library you want
|
|
6
|
+
// the copy on your own disk, not the published one (which may not even
|
|
7
|
+
// exist yet).
|
|
8
|
+
//
|
|
9
|
+
// The rule: given an ordered list of local root directories, try
|
|
10
|
+
// progressively shorter suffixes of `<host>/<path>` under each root,
|
|
11
|
+
// longest first. The first readable file wins; if none is readable, the
|
|
12
|
+
// declared URL is fetched as usual.
|
|
13
|
+
//
|
|
14
|
+
// root /home/me/dev/mystuff
|
|
15
|
+
// ref https://cdn.example.com/lib/util.js
|
|
16
|
+
// → /home/me/dev/mystuff/cdn.example.com/lib/util.js
|
|
17
|
+
// → /home/me/dev/mystuff/lib/util.js
|
|
18
|
+
// → /home/me/dev/mystuff/util.js
|
|
19
|
+
//
|
|
20
|
+
// The host is included so a mirror-style tree (one directory per site,
|
|
21
|
+
// as scraped-copy layouts and `wget -x` produce) matches, and matches
|
|
22
|
+
// first — it is the most specific thing the ref can say.
|
|
23
|
+
//
|
|
24
|
+
// The same-directory case is not a separate rule — it is this rule with
|
|
25
|
+
// an implicit root of the host script's own folder, where the file
|
|
26
|
+
// sitting next to the script is the suffix that usually matches. One
|
|
27
|
+
// code path, so the implicit and configured forms can't drift.
|
|
28
|
+
//
|
|
29
|
+
// The COLLECTION's own root is an implicit root too, and for the same
|
|
30
|
+
// reason: a local directory (or manifest) collection is a tree the user
|
|
31
|
+
// named, its scripts sit at relative paths inside it, and a shared
|
|
32
|
+
// `lib/` next to those scripts is where a repo of userscripts actually
|
|
33
|
+
// keeps its libraries. Without it a script two levels down
|
|
34
|
+
// (`sites/example.com/foo.user.js`) could never see `lib/util.js`,
|
|
35
|
+
// because a root is a floor and the search never climbs.
|
|
36
|
+
//
|
|
37
|
+
// ## What keeps this safe
|
|
38
|
+
//
|
|
39
|
+
// Not which tag is being resolved (`@require` vs `@resource`), but WHO
|
|
40
|
+
// the host script is:
|
|
41
|
+
//
|
|
42
|
+
// - Inferred substitution (this module) only applies when the host
|
|
43
|
+
// script's own URL is `file://`. Its OWN folder grants nothing new:
|
|
44
|
+
// a relative `@require ./util.js` already resolves there today, and
|
|
45
|
+
// `file-loader.ts` confines a relative ref to exactly that folder.
|
|
46
|
+
// - An http(s)-loaded script never gets inference. That is the case
|
|
47
|
+
// where a hostile `@resource file:///home/you/.ssh/id_rsa` followed
|
|
48
|
+
// by `GM_getResourceText` would actually matter.
|
|
49
|
+
// - The collection root and a configured root ARE reach the script
|
|
50
|
+
// didn't otherwise have — a relative ref cannot climb into either —
|
|
51
|
+
// and a script picks the path suffix it looks for, so everything
|
|
52
|
+
// under one is readable by any local script in it, including via
|
|
53
|
+
// `@resource` + `GM_getResourceText`. What justifies each is that
|
|
54
|
+
// the user named that directory: a configured root on the options
|
|
55
|
+
// page, a collection root by adding the collection. Both say so
|
|
56
|
+
// where they're set (the options page's note, the collection
|
|
57
|
+
// dialog's local-overrides option) — so point a collection at a directory of
|
|
58
|
+
// script content, not at a home directory.
|
|
59
|
+
//
|
|
60
|
+
// On top of that:
|
|
61
|
+
//
|
|
62
|
+
// - Path segments come from `URL.pathname`, which the parser has
|
|
63
|
+
// already normalized — no `..` and no `%2e%2e` survives into one.
|
|
64
|
+
// - The HOST segment gets no such normalization: `new URL('https://../
|
|
65
|
+
// x.js').host` really is `..`. So for that one segment the prefix
|
|
66
|
+
// re-check in `localFileCandidates` is not belt-and-braces, it is
|
|
67
|
+
// the enforcement. Don't remove it.
|
|
68
|
+
// - Any segment carrying an ENCODED separator (`%2F`, `%5C`) makes the
|
|
69
|
+
// whole URL ineligible — the parser leaves those alone and the
|
|
70
|
+
// filesystem layer may not. The host is checked too, since it is
|
|
71
|
+
// unshifted onto the segment list before that guard runs.
|
|
72
|
+
// - Every candidate is re-checked against its root prefix before being
|
|
73
|
+
// offered.
|
|
74
|
+
//
|
|
75
|
+
// So a search root is a floor, not a starting point for climbing.
|
|
76
|
+
//
|
|
77
|
+
// Explicit per-file overrides (a path the user typed for one specific
|
|
78
|
+
// ref) are a separate, later mechanism: those are allowed for any host
|
|
79
|
+
// script, because naming the file IS the authorization. See TODO.md.
|
|
80
|
+
import { isHttpUrl, isFileUrl, isContainerCollection, pathToFileUrl, isManifestCollection, isSingleFileInput, collectionCanonicalUrl, locationFsPath, isNonPosixFileUrl, listDirectory, } from './file-locations.js';
|
|
81
|
+
// An encoded path separator. The URL parser resolves `..` and `%2e%2e`
|
|
82
|
+
// but deliberately leaves these alone inside a segment, while Chrome's
|
|
83
|
+
// file-URL → filesystem-path conversion may decode them back into real
|
|
84
|
+
// separators. Both the declared URL's segments and a configured root
|
|
85
|
+
// are checked against it, so neither can move the floor.
|
|
86
|
+
const ENCODED_SEPARATOR_RE = /%2f|%5c/i;
|
|
87
|
+
// ---------- Configured search roots ----------
|
|
88
|
+
// Stored as an ORDERED ARRAY of canonical `file://` directory URLs —
|
|
89
|
+
// already parsed, already validated, already trailing-slashed. The
|
|
90
|
+
// options page won't save a list with a malformed line in it, so
|
|
91
|
+
// everything downstream can treat the stored value as ready to use;
|
|
92
|
+
// nothing re-parses at resolve time.
|
|
93
|
+
export const LOCAL_ROOTS_KEY = 'smLocalFileRoots';
|
|
94
|
+
// The shapes a search root may take, as a sentence fragment — the same
|
|
95
|
+
// role `LOCATION_SHAPES` plays for collection inputs. http(s) is absent
|
|
96
|
+
// on purpose: a root names a directory on this machine. Unexported:
|
|
97
|
+
// unlike `LOCATION_SHAPES` there is only one validator, so the whole
|
|
98
|
+
// message is what callers see.
|
|
99
|
+
const ROOT_SHAPES = 'must be an absolute path starting with / or a file:// URL';
|
|
100
|
+
// Split the options-page textarea into one entry per non-blank line.
|
|
101
|
+
// Blank lines vanish (they're just spacing the user added); everything
|
|
102
|
+
// else comes back either canonicalized or carrying an error, so the UI
|
|
103
|
+
// can render both the valid list and the complaints from one pass.
|
|
104
|
+
export function parseLocalRootLines(text) {
|
|
105
|
+
const out = [];
|
|
106
|
+
const lines = text.split('\n');
|
|
107
|
+
for (let i = 0; i < lines.length; i++) {
|
|
108
|
+
const trimmed = lines[i].trim();
|
|
109
|
+
if (!trimmed)
|
|
110
|
+
continue;
|
|
111
|
+
const entry = { lineNumber: i + 1, text: trimmed };
|
|
112
|
+
const url = canonicalRootUrl(trimmed);
|
|
113
|
+
if (url)
|
|
114
|
+
entry.url = url;
|
|
115
|
+
else
|
|
116
|
+
entry.error = `Local file root ${ROOT_SHAPES}`;
|
|
117
|
+
out.push(entry);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
// A typed root as a directory URL: always `file://`, always ending in
|
|
122
|
+
// `/` so it can be concatenated with a path suffix directly, and run
|
|
123
|
+
// through the URL parser so `.` / `..` the user typed are resolved
|
|
124
|
+
// before anything is matched against it. Null when the shape is wrong.
|
|
125
|
+
//
|
|
126
|
+
// The root is the floor every containment check is measured against, so
|
|
127
|
+
// it has to be at least as clean as the candidates built on top of it.
|
|
128
|
+
// Three things a bare `protocol` check would let through:
|
|
129
|
+
// - A fragment or query. `fetch`/XHR strip those before reaching the
|
|
130
|
+
// filesystem, so `file:///a/b#x/` reads `/a/b` — every candidate
|
|
131
|
+
// under such a root would read one and the same file, and the
|
|
132
|
+
// options page's directory probe would call a plain file a
|
|
133
|
+
// directory.
|
|
134
|
+
// - An encoded separator, rejected here for exactly the reason
|
|
135
|
+
// `localFileCandidates` rejects it in a declared URL: the parser
|
|
136
|
+
// leaves `%2F` alone and the filesystem layer may not, so the real
|
|
137
|
+
// floor would sit somewhere above the stated one.
|
|
138
|
+
// - A host. `file://host/share` is not a local absolute path, which
|
|
139
|
+
// is what this setting takes; reads would silently never succeed.
|
|
140
|
+
function canonicalRootUrl(typed) {
|
|
141
|
+
if (isHttpUrl(typed))
|
|
142
|
+
return null;
|
|
143
|
+
let raw;
|
|
144
|
+
if (isFileUrl(typed))
|
|
145
|
+
raw = typed;
|
|
146
|
+
else if (typed.startsWith('/'))
|
|
147
|
+
raw = pathToFileUrl(typed);
|
|
148
|
+
else
|
|
149
|
+
return null;
|
|
150
|
+
try {
|
|
151
|
+
const url = new URL(raw.endsWith('/') ? raw : raw + '/');
|
|
152
|
+
if (url.protocol !== 'file:')
|
|
153
|
+
return null;
|
|
154
|
+
if (url.hostname)
|
|
155
|
+
return null;
|
|
156
|
+
if (url.hash || url.search)
|
|
157
|
+
return null;
|
|
158
|
+
if (ENCODED_SEPARATOR_RE.test(url.pathname))
|
|
159
|
+
return null;
|
|
160
|
+
// Rebuild from `pathname` rather than `toString()` so nothing but
|
|
161
|
+
// the path can survive into the stored root.
|
|
162
|
+
return `file://${url.pathname}`;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// The canonical URLs of every line, in the order typed — or null if any
|
|
169
|
+
// line is malformed. Null is the options page's "don't save" signal:
|
|
170
|
+
// the stored array is the parsed form, so it must never be built from
|
|
171
|
+
// text that didn't fully parse.
|
|
172
|
+
export function rootsFromText(text) {
|
|
173
|
+
const entries = parseLocalRootLines(text);
|
|
174
|
+
if (entries.some((e) => !e.url))
|
|
175
|
+
return null;
|
|
176
|
+
return entries.map((e) => e.url);
|
|
177
|
+
}
|
|
178
|
+
// The stored array, rendered back into textarea lines.
|
|
179
|
+
export function textFromRoots(roots) {
|
|
180
|
+
return roots.join('\n');
|
|
181
|
+
}
|
|
182
|
+
// Cached copy of the setting, mirroring `debug-log.ts`:
|
|
183
|
+
// `localSearchRoots` is called synchronously from render paths in three
|
|
184
|
+
// contexts (service worker, collections page, viewer), so each loads
|
|
185
|
+
// once at startup and then tracks `storage.onChanged`. Defaults to
|
|
186
|
+
// none, which is also what any caller outside the extension (the
|
|
187
|
+
// corpus validator) sees.
|
|
188
|
+
let configuredRoots = [];
|
|
189
|
+
let loadOnce = null;
|
|
190
|
+
// Bumped by anything that publishes a newer value, so an initial read
|
|
191
|
+
// still in flight knows not to overwrite it.
|
|
192
|
+
let loadGeneration = 0;
|
|
193
|
+
// Memoized so it can be both "kick this off at startup" and "make sure
|
|
194
|
+
// it finished" — `localRootsReady` is awaited on the resolve path, and
|
|
195
|
+
// without the memo that await would re-read storage on every script.
|
|
196
|
+
export function loadLocalRootsSetting() {
|
|
197
|
+
if (loadOnce)
|
|
198
|
+
return loadOnce;
|
|
199
|
+
const token = ++loadGeneration;
|
|
200
|
+
loadOnce = (async () => {
|
|
201
|
+
let next;
|
|
202
|
+
try {
|
|
203
|
+
const got = await chrome.storage.local.get(LOCAL_ROOTS_KEY);
|
|
204
|
+
next = readRootsSetting(got[LOCAL_ROOTS_KEY]);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
next = [];
|
|
208
|
+
}
|
|
209
|
+
// Only publish if nothing newer landed while the read was in
|
|
210
|
+
// flight. A `storage.onChanged` (or a test hook) holds a NEWER
|
|
211
|
+
// value, and what these roots resolve to gets PERSISTED into
|
|
212
|
+
// `StoredRequire.localUrl` — so losing that write isn't merely
|
|
213
|
+
// stale console noise, unlike the same pattern in `debug-log.ts`.
|
|
214
|
+
if (loadGeneration === token)
|
|
215
|
+
configuredRoots = next;
|
|
216
|
+
})();
|
|
217
|
+
return loadOnce;
|
|
218
|
+
}
|
|
219
|
+
// Await before calling `localSearchRoots` from a path where reading the
|
|
220
|
+
// setting late would silently change the answer. `localSearchRoots`
|
|
221
|
+
// itself is synchronous (render paths call it), so the ordering has to
|
|
222
|
+
// be the caller's job: a refresh that ran before the initial load
|
|
223
|
+
// resolved would resolve every dependency with no configured roots and
|
|
224
|
+
// then cache that as the current state.
|
|
225
|
+
export function localRootsReady() {
|
|
226
|
+
return loadLocalRootsSetting();
|
|
227
|
+
}
|
|
228
|
+
export function installLocalRootsListener() {
|
|
229
|
+
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
230
|
+
if (areaName !== 'local')
|
|
231
|
+
return;
|
|
232
|
+
const change = changes[LOCAL_ROOTS_KEY];
|
|
233
|
+
if (!change)
|
|
234
|
+
return;
|
|
235
|
+
configuredRoots = readRootsSetting(change.newValue);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// The stored value is already canonical, so this is a shape check, not
|
|
239
|
+
// a parse: keep the strings, drop anything that isn't one. A hand-edited
|
|
240
|
+
// storage key shouldn't be able to put a non-string into a search root.
|
|
241
|
+
// Exported because the options page reads the same key directly.
|
|
242
|
+
export function readRootsSetting(raw) {
|
|
243
|
+
if (!Array.isArray(raw))
|
|
244
|
+
return [];
|
|
245
|
+
return raw.filter((r) => typeof r === 'string');
|
|
246
|
+
}
|
|
247
|
+
// Set the configured roots directly, bypassing storage. For the contexts
|
|
248
|
+
// with no `chrome.storage` at all (unit tests, and the development
|
|
249
|
+
// harness, which runs the same resolve pipeline under Node with roots
|
|
250
|
+
// the caller names), and for the worker's own `writeSetting`, so a
|
|
251
|
+
// refresh it runs next doesn't wait for the change event.
|
|
252
|
+
export function setConfiguredLocalRoots(roots) {
|
|
253
|
+
configuredRoots = roots.slice();
|
|
254
|
+
loadGeneration++;
|
|
255
|
+
// Also satisfy `localRootsReady`, or the first resolve after this
|
|
256
|
+
// would read storage and overwrite what was just set.
|
|
257
|
+
loadOnce = Promise.resolve();
|
|
258
|
+
}
|
|
259
|
+
// Test hook, kept under its old name for the tests and the SW test
|
|
260
|
+
// surface that call it.
|
|
261
|
+
export function _setLocalRootsForTests(roots) {
|
|
262
|
+
setConfiguredLocalRoots(roots);
|
|
263
|
+
}
|
|
264
|
+
// The directory a collection is rooted at, as a search root — or null
|
|
265
|
+
// when it has no single tree to search:
|
|
266
|
+
//
|
|
267
|
+
// - Container collections (`container:file` and friends) aggregate
|
|
268
|
+
// individually added scripts that share no directory. Each script's
|
|
269
|
+
// own folder, which `localSearchRoots` adds anyway, is the whole of
|
|
270
|
+
// what they have in common. (So this is a narrower question than
|
|
271
|
+
// `collectionSupportsLocalFiles` below, which says yes to a file
|
|
272
|
+
// container — its scripts do get their own folders.)
|
|
273
|
+
// - http(s) collections aren't local. (Their scripts can't get roots
|
|
274
|
+
// either — see the `file://` gate below — so this is belt and
|
|
275
|
+
// braces.)
|
|
276
|
+
//
|
|
277
|
+
// A manifest names a FILE, and its relative entries resolve against that
|
|
278
|
+
// file's own directory, so the root is the parent. (Only *relative*
|
|
279
|
+
// ones: `resolveManifestEntryUrl` applies no containment, so an entry
|
|
280
|
+
// may name a script outside the tree. Such a script still gets the
|
|
281
|
+
// manifest's directory as a root — the user wrote the manifest, which
|
|
282
|
+
// is the same authorization the directory case rests on.) Legacy
|
|
283
|
+
// single-file collection rows (`*.user.js` as the path) take the parent
|
|
284
|
+
// too, which is always the script's own folder and so always deduped
|
|
285
|
+
// away; the branch exists so the classification reads completely.
|
|
286
|
+
// Everything else is a local directory: itself.
|
|
287
|
+
function collectionRootUrl(collectionPath) {
|
|
288
|
+
if (!collectionPath)
|
|
289
|
+
return null;
|
|
290
|
+
if (isContainerCollection(collectionPath))
|
|
291
|
+
return null;
|
|
292
|
+
if (isHttpUrl(collectionPath))
|
|
293
|
+
return null;
|
|
294
|
+
const canonical = collectionCanonicalUrl(collectionPath);
|
|
295
|
+
if (!isFileUrl(canonical))
|
|
296
|
+
return null;
|
|
297
|
+
if (isManifestCollection(collectionPath) || isSingleFileInput(collectionPath)) {
|
|
298
|
+
try {
|
|
299
|
+
return canonicalRootUrl(new URL('./', canonical).toString());
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Same validation a configured root gets — a collection path is user
|
|
306
|
+
// input too, and it becomes a floor here exactly as one does.
|
|
307
|
+
return canonicalRootUrl(canonical);
|
|
308
|
+
}
|
|
309
|
+
// Ordered local directories to search for a stand-in, most specific
|
|
310
|
+
// first. Empty means "no substitution" — every caller short-circuits on
|
|
311
|
+
// that, so a script with no local roots costs exactly what it did
|
|
312
|
+
// before this feature existed.
|
|
313
|
+
//
|
|
314
|
+
// The order, most specific first:
|
|
315
|
+
// 1. The host script's own folder — the file sitting next to the
|
|
316
|
+
// script is the least surprising answer.
|
|
317
|
+
// 2. The collection's own root, so a repo-wide `lib/` is reachable
|
|
318
|
+
// from a script nested inside the tree the user added.
|
|
319
|
+
// 3. Configured roots (options page, in the order typed).
|
|
320
|
+
//
|
|
321
|
+
// The `file://`-host gate covers the collection root and the configured
|
|
322
|
+
// roots too. A global root is a development directory; a script you
|
|
323
|
+
// installed from the web has no business resolving its dependencies out
|
|
324
|
+
// of it, and the intended answer for that case is an explicit per-file
|
|
325
|
+
// override — one path, named by the user, for one ref. See TODO.md.
|
|
326
|
+
export function localSearchRoots(scriptFileUrl, scope) {
|
|
327
|
+
if (!scope.enabled)
|
|
328
|
+
return [];
|
|
329
|
+
// The gate: inference is for locally-loaded scripts only.
|
|
330
|
+
if (!isFileUrl(scriptFileUrl))
|
|
331
|
+
return [];
|
|
332
|
+
let parent;
|
|
333
|
+
try {
|
|
334
|
+
parent = new URL('./', scriptFileUrl).toString();
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// Consistent with every other "we don't know" exit in this module:
|
|
338
|
+
// if the host script's own URL won't parse, we can't establish the
|
|
339
|
+
// gate's premise, so nothing is searched.
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const collectionRoot = scope.collectionPath
|
|
343
|
+
? collectionRootUrl(scope.collectionPath)
|
|
344
|
+
: null;
|
|
345
|
+
// De-dupe: a script at the top of its collection has its own folder
|
|
346
|
+
// AS the collection root, and either is a perfectly reasonable thing
|
|
347
|
+
// to ALSO list as a global root. `localFileCandidates` would already
|
|
348
|
+
// collapse the duplicate candidates, so this is about the list
|
|
349
|
+
// reading as what it claims to be — the *additional* places searched,
|
|
350
|
+
// each appearing once.
|
|
351
|
+
const roots = [parent];
|
|
352
|
+
for (const root of [collectionRoot, ...configuredRoots]) {
|
|
353
|
+
if (root && !roots.includes(root))
|
|
354
|
+
roots.push(root);
|
|
355
|
+
}
|
|
356
|
+
return roots;
|
|
357
|
+
}
|
|
358
|
+
// Can a collection's scripts ever have local stand-ins? Used only to
|
|
359
|
+
// decide whether the collection dialog offers the per-collection
|
|
360
|
+
// option — the real gate is `localSearchRoots`, which looks at each
|
|
361
|
+
// script's own URL. Not the same question as `collectionRootUrl` above:
|
|
362
|
+
// a container supports stand-ins but contributes no root of its own.
|
|
363
|
+
//
|
|
364
|
+
// http(s) collections are excluded: every script under them loads over
|
|
365
|
+
// the network, so the option would be a control that does nothing.
|
|
366
|
+
// Every container qualifies — even the built-in web / GitHub defaults —
|
|
367
|
+
// because a container may hold scripts from any scheme, and the option
|
|
368
|
+
// applies to whichever of them came from disk.
|
|
369
|
+
export function collectionSupportsLocalFiles(collectionPath) {
|
|
370
|
+
return !isHttpUrl(collectionPath);
|
|
371
|
+
}
|
|
372
|
+
// Every local file that could stand in for `declaredUrl`, in the order
|
|
373
|
+
// they should be tried. Readers gate each on its root's listing
|
|
374
|
+
// (`localCandidateMayExist`, or `presentLocalFileCandidates` for the
|
|
375
|
+
// whole list), which drops the ones that can't exist, then fetch each
|
|
376
|
+
// in turn and take the first that reads.
|
|
377
|
+
//
|
|
378
|
+
// Suffixes run longest-first so a deliberate layout (`lib/util.js` under
|
|
379
|
+
// the root) beats an incidental basename collision (`util.js` sitting
|
|
380
|
+
// loose in the root). The declared URL's HOST counts as the outermost
|
|
381
|
+
// segment, so a mirror-style tree — the layout `wget -x` and every
|
|
382
|
+
// scraped-copy directory uses — matches before any bare path suffix:
|
|
383
|
+
//
|
|
384
|
+
// root /home/me/files
|
|
385
|
+
// ref https://code.jquery.com/jquery-latest.js
|
|
386
|
+
// → /home/me/files/code.jquery.com/jquery-latest.js
|
|
387
|
+
// → /home/me/files/jquery-latest.js
|
|
388
|
+
export function localFileCandidates(declaredUrl, roots) {
|
|
389
|
+
return localFileCandidatesDetailed(declaredUrl, roots).map((c) => c.url);
|
|
390
|
+
}
|
|
391
|
+
export function localFileCandidatesDetailed(declaredUrl, roots) {
|
|
392
|
+
if (roots.length === 0)
|
|
393
|
+
return [];
|
|
394
|
+
let segments;
|
|
395
|
+
try {
|
|
396
|
+
const parsed = new URL(declaredUrl);
|
|
397
|
+
// `pathname` is already normalized and percent-encoded by the URL
|
|
398
|
+
// parser: `..` and `%2e%2e` have been resolved away, so splitting on
|
|
399
|
+
// `/` here can't manufacture a climbing component. What it does NOT
|
|
400
|
+
// resolve is an encoded separator — see the guard just below.
|
|
401
|
+
segments = parsed.pathname.split('/').filter(Boolean);
|
|
402
|
+
// A ref with no path names no file, so there is nothing to stand in
|
|
403
|
+
// for — checked BEFORE the host goes on, or the host alone would be
|
|
404
|
+
// offered as a candidate and Chrome answers a directory read with a
|
|
405
|
+
// listing rather than a failure.
|
|
406
|
+
if (segments.length === 0)
|
|
407
|
+
return [];
|
|
408
|
+
// Same reason, one level down: `…/assets/` names a directory, and
|
|
409
|
+
// with a host segment in play `<root>/example.com/assets` is a
|
|
410
|
+
// directory that plausibly EXISTS in a mirror tree. `@require` has a
|
|
411
|
+
// `.js` gate, but `@resource` and `@icon` don't — a hit there would
|
|
412
|
+
// bind a directory listing as the file's body.
|
|
413
|
+
if (parsed.pathname.endsWith('/'))
|
|
414
|
+
return [];
|
|
415
|
+
// `host`, not `hostname`, so a non-default port survives into the
|
|
416
|
+
// directory name the way a mirroring tool would write it. Empty for
|
|
417
|
+
// a `file://` ref, which then just yields the plain path suffixes.
|
|
418
|
+
if (parsed.host)
|
|
419
|
+
segments.unshift(parsed.host);
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
// `pathname` normalizes `..` and `%2e%2e` away, but it deliberately
|
|
425
|
+
// leaves an ENCODED separator (`%2F`, `%5C`) inside a segment — so
|
|
426
|
+
// `/a/..%2f../secret.js` survives as a single segment `..%2f..` and
|
|
427
|
+
// would pass both the path-segment reasoning and the prefix re-check below,
|
|
428
|
+
// while Chrome's file-URL → filesystem-path conversion may well decode
|
|
429
|
+
// it back into a real separator on the way to the OS.
|
|
430
|
+
//
|
|
431
|
+
// Bail on the whole URL rather than filter segment by segment: an
|
|
432
|
+
// encoded separator inside a path suffix has no legitimate use in
|
|
433
|
+
// matching a local file, and a partial answer here would be a
|
|
434
|
+
// half-trusted one.
|
|
435
|
+
if (segments.some((s) => ENCODED_SEPARATOR_RE.test(s)))
|
|
436
|
+
return [];
|
|
437
|
+
const bases = roots.map((root) => (root.endsWith('/') ? root : root + '/'));
|
|
438
|
+
// A ref that already resolves inside a search root IS the local file —
|
|
439
|
+
// the ordinary relative-path case, which needs no stand-in. Bailing on
|
|
440
|
+
// the whole URL rather than filtering candidate-by-candidate also
|
|
441
|
+
// avoids the nonsense a local declared URL would otherwise generate,
|
|
442
|
+
// where its own directory segments get re-appended to the root
|
|
443
|
+
// (`…/scripts/` + `home/me/scripts/util.js`).
|
|
444
|
+
if (bases.some((base) => declaredUrl.startsWith(base)))
|
|
445
|
+
return [];
|
|
446
|
+
const out = [];
|
|
447
|
+
const seen = new Set();
|
|
448
|
+
for (const base of bases) {
|
|
449
|
+
for (let k = segments.length; k >= 1; k--) {
|
|
450
|
+
const candidate = base + segments.slice(-k).join('/');
|
|
451
|
+
// Containment. Belt-and-braces for a PATH segment, which can't
|
|
452
|
+
// climb — but load-bearing for the host segment, which the URL
|
|
453
|
+
// parser does not normalize (`https://../x.js` really does have a
|
|
454
|
+
// host of `..`). This is what keeps the root a floor for it.
|
|
455
|
+
let normalized;
|
|
456
|
+
try {
|
|
457
|
+
normalized = new URL(candidate).toString();
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (!normalized.startsWith(base))
|
|
463
|
+
continue;
|
|
464
|
+
// Keyed on the NORMALIZED form, since that is what's returned: a
|
|
465
|
+
// host of `.` makes `<root>/./x.js` and `<root>/x.js` two distinct
|
|
466
|
+
// candidate strings that name one file.
|
|
467
|
+
if (seen.has(normalized))
|
|
468
|
+
continue;
|
|
469
|
+
seen.add(normalized);
|
|
470
|
+
// Taken from the NORMALIZED form, not `suffix`: a host of `.`
|
|
471
|
+
// makes `<root>/./x.js` normalize to `<root>/x.js`, and the gate
|
|
472
|
+
// has to describe the URL that is actually read.
|
|
473
|
+
const rel = normalized.slice(base.length).split('/');
|
|
474
|
+
out.push({ url: normalized, root: base, firstSegment: rel[0], isLeaf: rel.length === 1 });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
// `localFileCandidates`, minus the ones the root's directory listing
|
|
480
|
+
// rules out: a candidate whose first segment under the root isn't
|
|
481
|
+
// listed can't exist, so it is never probed.
|
|
482
|
+
//
|
|
483
|
+
// Why: each probe of a missing file is a failed request in the worker's
|
|
484
|
+
// DevTools Network panel — a couple of dozen per script per refresh
|
|
485
|
+
// before this, which reads like breakage. Only the first level is
|
|
486
|
+
// checked; `<root>/lib/` existing says nothing about `<root>/lib/x.js`,
|
|
487
|
+
// so that one is still probed. See docs/local-file-overrides.md.
|
|
488
|
+
//
|
|
489
|
+
// A root that can't be listed, or lists as empty, keeps every candidate
|
|
490
|
+
// — "can't tell" must fall through to the probe, or a stand-in would
|
|
491
|
+
// silently stop being found. (Empty is also what an unrecognized
|
|
492
|
+
// listing format parses to.)
|
|
493
|
+
export async function presentLocalFileCandidates(declaredUrl, roots, listings) {
|
|
494
|
+
const out = [];
|
|
495
|
+
for (const c of localFileCandidatesDetailed(declaredUrl, roots)) {
|
|
496
|
+
if (await localCandidateMayExist(c, listings))
|
|
497
|
+
out.push(c.url);
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
// The same gate for one candidate, for a reader that tries candidates
|
|
502
|
+
// in order and stops at the first that reads: it lists a root only
|
|
503
|
+
// when the search reaches it, so a hit in an early root costs no
|
|
504
|
+
// listings of the later ones.
|
|
505
|
+
export async function localCandidateMayExist(c, listings) {
|
|
506
|
+
return mayExist(c, await listRoot(c.root, listings));
|
|
507
|
+
}
|
|
508
|
+
// Roots whose listing failed, per listing cache. `DirectoryListingCache`
|
|
509
|
+
// deliberately forgets a failed read (see `listDirectory`), which is
|
|
510
|
+
// right for the scan it was built for but wrong here: a configured root
|
|
511
|
+
// that isn't on disk would be re-listed for every dependency of every
|
|
512
|
+
// script in the pass, on top of the probes it can't rule out — MORE
|
|
513
|
+
// failed requests than before the gate, not fewer. One attempt per root
|
|
514
|
+
// per cache; the memo dies with the cache it describes.
|
|
515
|
+
const failedRoots = new WeakMap();
|
|
516
|
+
// The root's listing, or `null` for "can't tell" — every candidate
|
|
517
|
+
// under it is then kept.
|
|
518
|
+
async function listRoot(root, listings) {
|
|
519
|
+
// `locationFsPath` drops the host of a UNC / drive-letter URL, so the
|
|
520
|
+
// listing would describe some other directory. Those roots skip the
|
|
521
|
+
// gate rather than filter against the wrong tree.
|
|
522
|
+
if (isNonPosixFileUrl(root))
|
|
523
|
+
return null;
|
|
524
|
+
let failed = failedRoots.get(listings);
|
|
525
|
+
if (!failed) {
|
|
526
|
+
failed = new Set();
|
|
527
|
+
failedRoots.set(listings, failed);
|
|
528
|
+
}
|
|
529
|
+
if (failed.has(root))
|
|
530
|
+
return null;
|
|
531
|
+
// No trailing slash (the batch keys its listings by the bare directory
|
|
532
|
+
// path, and this has to hit the same entry), except that the
|
|
533
|
+
// filesystem root stays `/` — the batch's own convention.
|
|
534
|
+
const dir = locationFsPath(root).replace(/\/+$/, '') || '/';
|
|
535
|
+
try {
|
|
536
|
+
return await listDirectory(dir, listings);
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
failed.add(root);
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function mayExist(c, entries) {
|
|
544
|
+
if (entries === null || entries.length === 0)
|
|
545
|
+
return true;
|
|
546
|
+
let name;
|
|
547
|
+
try {
|
|
548
|
+
name = decodeURIComponent(c.firstSegment);
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
const entry = entries.find((e) => e.name === name);
|
|
554
|
+
// A directory entry when the segment names a directory, a file entry
|
|
555
|
+
// when it names the file itself. A symlink is listed as whatever it
|
|
556
|
+
// points at, so it passes the same way the read would.
|
|
557
|
+
return entry !== undefined && entry.isDir !== c.isLeaf;
|
|
558
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXQa9FWfBzP8EUxjcUDofdECH+A5rpR13/mlhF/jmlhYDRNEUvU9TaQlgCMdAfJqBdpiCJ6qjOgmySkfp7wmab1KV/F1G6Sn8aYM+GWTdj3z33OwRKwv5B7QdTZoAEkGe99LoGPMqPkGb+eY3TxkV/UlMQ3diaIrivkcPtYcLRAgAKQ7GwuPua3FDg+Lq5XreaMvK5X9mhOtRukRfis/7I/kqt0prIOqVES3TboNsgc4WtlGFaREdzyscUUorsA/Rb4Z6twRotaz4m6HROWD5cQ1MF2YJP1Q2QCPhE8gcAlii6uLClSj1wL/Z/McYeXOlCyOd30W/qGXAqSQJMjS8wIDAQAB",
|
|
4
|
+
"name": "SourceMonkey",
|
|
5
|
+
"version": "0.5.0",
|
|
6
|
+
"description": "A userscript manager that treats userscripts like source code, built and managed with real developer tools, including coding agents",
|
|
7
|
+
"homepage_url": "https://github.com/jshute96/SourceMonkey",
|
|
8
|
+
"minimum_chrome_version": "152",
|
|
9
|
+
"permissions": ["alarms", "contextMenus", "declarativeNetRequestWithHostAccess", "downloads", "favicon", "notifications", "offscreen", "scripting", "storage", "userScripts", "webNavigation"],
|
|
10
|
+
"sandbox": {
|
|
11
|
+
"pages": ["parse-sandbox.html"]
|
|
12
|
+
},
|
|
13
|
+
"content_security_policy": {
|
|
14
|
+
"sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'none'"
|
|
15
|
+
},
|
|
16
|
+
"host_permissions": ["<all_urls>", "file:///*"],
|
|
17
|
+
"background": {
|
|
18
|
+
"service_worker": "extension/background.js",
|
|
19
|
+
"type": "module"
|
|
20
|
+
},
|
|
21
|
+
"action": {
|
|
22
|
+
"default_title": "SourceMonkey",
|
|
23
|
+
"default_icon": {
|
|
24
|
+
"16": "icons/icon-16.png",
|
|
25
|
+
"48": "icons/icon-48.png",
|
|
26
|
+
"128": "icons/icon-128.png"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"icons": {
|
|
30
|
+
"16": "icons/icon-16.png",
|
|
31
|
+
"48": "icons/icon-48.png",
|
|
32
|
+
"128": "icons/icon-128.png"
|
|
33
|
+
},
|
|
34
|
+
"options_ui": {
|
|
35
|
+
"page": "collections.html",
|
|
36
|
+
"open_in_tab": true
|
|
37
|
+
}
|
|
38
|
+
}
|