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,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Sandboxed compile-only parse check. Loaded by `parse-sandbox.html`,
|
|
3
|
+
// which is declared under `sandbox.pages` in the manifest — the one
|
|
4
|
+
// context in an MV3 extension allowed to declare `'unsafe-eval'`.
|
|
5
|
+
//
|
|
6
|
+
// See the comment in `parse-sandbox.html` for why the compile has to
|
|
7
|
+
// happen in a sandbox at all, and why compiling untrusted source here
|
|
8
|
+
// is safe.
|
|
9
|
+
//
|
|
10
|
+
// This file deliberately imports nothing. tsc would emit an ES module
|
|
11
|
+
// for a file with imports, and a sandboxed page loads it as a classic
|
|
12
|
+
// `<script src>`. The message shapes below are mirrored (not shared)
|
|
13
|
+
// from `src/parse-check-protocol.ts`, which is the source of truth;
|
|
14
|
+
// they are small and change together.
|
|
15
|
+
// `new Function` compiles the body and returns; it never runs it. A
|
|
16
|
+
// SyntaxError anywhere in the text is the answer we want.
|
|
17
|
+
function checkOne(source) {
|
|
18
|
+
try {
|
|
19
|
+
// eslint-disable-next-line no-new-func
|
|
20
|
+
new Function(source);
|
|
21
|
+
return { status: 'ok' };
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
// ONLY a SyntaxError means "this source is broken". Everything
|
|
26
|
+
// else means "we couldn't tell", and the difference matters more
|
|
27
|
+
// than it looks:
|
|
28
|
+
//
|
|
29
|
+
// - A `RangeError: Maximum call stack size exceeded` on a
|
|
30
|
+
// pathologically nested source is the parser running out of
|
|
31
|
+
// budget HERE — inside a message handler inside a promise
|
|
32
|
+
// chain — which is not the budget `chrome.userScripts` will
|
|
33
|
+
// compile the real entry with. Reporting it as a syntax error
|
|
34
|
+
// would disable a script V8 is perfectly willing to run.
|
|
35
|
+
// - If the sandbox CSP ever stopped applying, `new Function`
|
|
36
|
+
// would throw `EvalError` and every script in the profile
|
|
37
|
+
// would be branded broken at once. That is the exact outcome
|
|
38
|
+
// the design says cannot happen.
|
|
39
|
+
if (!(err instanceof SyntaxError))
|
|
40
|
+
return { status: 'unknown', message };
|
|
41
|
+
return { status: 'syntax-error', message, probes: locate(source) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ---------- Finding out WHERE ----------
|
|
45
|
+
//
|
|
46
|
+
// `new Function` answers "does this parse?" and nothing else: no line,
|
|
47
|
+
// no column, not even a position in `err.stack`, which stops at
|
|
48
|
+
// `at new Function (<anonymous>)`. V8 does report a position when a
|
|
49
|
+
// classic `<script>` fails to parse — the window `error` event carries
|
|
50
|
+
// `lineno` and `colno` — so once the verdict is in we compile the same
|
|
51
|
+
// text a second way, purely to locate the error it already found.
|
|
52
|
+
//
|
|
53
|
+
// **Appending a `<script>` normally runs it.** What keeps this
|
|
54
|
+
// compile-only is the sentinel below, not luck; see `PROBE_SENTINEL`.
|
|
55
|
+
//
|
|
56
|
+
// Two probes, because neither framing is faithful on its own:
|
|
57
|
+
//
|
|
58
|
+
// - **wrapped** reproduces `new Function`'s semantics exactly — the
|
|
59
|
+
// text is a function body, so a top-level `return` is legal, as it
|
|
60
|
+
// is in the entry we actually inject. Its weakness is that a
|
|
61
|
+
// source with unbalanced braces swallows the wrapper's closing
|
|
62
|
+
// `}`, and the parse then runs on to a later, different error.
|
|
63
|
+
// - **raw** has no wrapper to swallow, so it places a stray or
|
|
64
|
+
// missing brace exactly — but it is a *Script*, where a top-level
|
|
65
|
+
// `return` is an error V8 would never have raised for real.
|
|
66
|
+
//
|
|
67
|
+
// The caller picks. See `selectParseLocation` in parse-location.ts.
|
|
68
|
+
function locate(source) {
|
|
69
|
+
try {
|
|
70
|
+
return {
|
|
71
|
+
wrapped: runProbe(`${WRAPPER_HEAD}${source}${WRAPPER_TAIL}${PROBE_SENTINEL}`, 1),
|
|
72
|
+
raw: runProbe(`${source}${PROBE_SENTINEL}`, 0),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Locating is a nicety; the verdict is the product. Never let a
|
|
77
|
+
// DOM failure here turn a usable `syntax-error` into a throw.
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// An unmatched OPENER, appended to whatever we compile, so the probe
|
|
82
|
+
// script can never parse — and therefore can never execute a single
|
|
83
|
+
// statement, since a parse failure aborts the whole script before
|
|
84
|
+
// evaluation begins.
|
|
85
|
+
//
|
|
86
|
+
// It has to be an opener. A closing `}` or `)` would be *closed by*
|
|
87
|
+
// whatever the source left dangling: appending `}` to a source ending
|
|
88
|
+
// in `if (1) {` produces a valid program, and it runs. An opener at
|
|
89
|
+
// end of input has nothing after it to close it.
|
|
90
|
+
//
|
|
91
|
+
// The leading newline matters too — without it a source whose last
|
|
92
|
+
// line is a `//` comment swallows the sentinel.
|
|
93
|
+
//
|
|
94
|
+
// V8 reports the FIRST error it hits, so a real error in the source is
|
|
95
|
+
// still the one reported; the sentinel only speaks when the source had
|
|
96
|
+
// nothing to say. A probe that comes back with NO error at all would
|
|
97
|
+
// therefore mean the sentinel failed to do its job and the source ran —
|
|
98
|
+
// so it reports no position, and `selectParseLocation` simply has one
|
|
99
|
+
// fewer probe to choose from.
|
|
100
|
+
const PROBE_SENTINEL = '\n(';
|
|
101
|
+
const WRAPPER_HEAD = 'function __smParseProbe__() {\n';
|
|
102
|
+
const WRAPPER_TAIL = '\n}';
|
|
103
|
+
// Compile `text` as a classic script and report where it failed.
|
|
104
|
+
// `lineOffset` is how many lines of our own framing precede the
|
|
105
|
+
// source, so the caller gets source coordinates back.
|
|
106
|
+
function runProbe(text, lineOffset) {
|
|
107
|
+
let hit;
|
|
108
|
+
const onError = (event) => {
|
|
109
|
+
hit = {
|
|
110
|
+
// `event.error.message` is the clean one; `event.message` has
|
|
111
|
+
// "Uncaught SyntaxError: " in front of it as well.
|
|
112
|
+
message: stripDomPrefix(event.error instanceof Error ? event.error.message : event.message),
|
|
113
|
+
line: event.lineno - lineOffset,
|
|
114
|
+
column: event.colno,
|
|
115
|
+
};
|
|
116
|
+
// Marks the error handled, which is what stops Chrome reporting it
|
|
117
|
+
// as an uncaught page error — and an uncaught error in an
|
|
118
|
+
// extension context is forwarded into chrome://extensions/?errors,
|
|
119
|
+
// making SourceMonkey look broken when it is working exactly as
|
|
120
|
+
// designed. Verified: without this the error surfaces, with it the
|
|
121
|
+
// console stays clean. See CLAUDE.md.
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
};
|
|
124
|
+
window.addEventListener('error', onError);
|
|
125
|
+
const el = document.createElement('script');
|
|
126
|
+
el.textContent = text;
|
|
127
|
+
try {
|
|
128
|
+
// The parse — and the `error` event it fires — both happen
|
|
129
|
+
// synchronously inside this call, which is why `hit` is readable
|
|
130
|
+
// immediately after it and no async plumbing is needed.
|
|
131
|
+
document.body.append(el);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
el.remove();
|
|
135
|
+
window.removeEventListener('error', onError);
|
|
136
|
+
}
|
|
137
|
+
return hit;
|
|
138
|
+
}
|
|
139
|
+
// Inserting the script means the SyntaxError arrives wrapped in the
|
|
140
|
+
// DOM method that raised it: "Failed to execute 'append' on 'Element':
|
|
141
|
+
// Unexpected token ';'". Strip it so the message we may end up showing
|
|
142
|
+
// is about the user's code and not about our insertion trick. Matched
|
|
143
|
+
// loosely rather than by exact string so a different insertion method
|
|
144
|
+
// (or wording) degrades to a cosmetic prefix rather than a wrong
|
|
145
|
+
// message — and `selectParseLocation` compares with `endsWith`, so an
|
|
146
|
+
// unstripped prefix still matches.
|
|
147
|
+
function stripDomPrefix(message) {
|
|
148
|
+
return message.replace(/^Failed to execute '[^']*' on '[^']*': /, '');
|
|
149
|
+
}
|
|
150
|
+
window.addEventListener('message', (event) => {
|
|
151
|
+
// Only our embedder may ask.
|
|
152
|
+
if (event.source !== window.parent)
|
|
153
|
+
return;
|
|
154
|
+
const data = event.data;
|
|
155
|
+
if (!data || data.type !== 'sm:parse-check')
|
|
156
|
+
return;
|
|
157
|
+
if (!Array.isArray(data.sources) || typeof data.id !== 'string')
|
|
158
|
+
return;
|
|
159
|
+
const verdicts = data.sources.map((s) => checkOne(typeof s === 'string' ? s : ''));
|
|
160
|
+
// Reply to the embedder only, and to nothing else. A sandboxed page
|
|
161
|
+
// has an opaque origin, so the target origin has to be '*' — which
|
|
162
|
+
// makes the `event.source` check the only thing standing between us
|
|
163
|
+
// and replying to an arbitrary window. Nothing can reach this page
|
|
164
|
+
// today (the manifest declares no `web_accessible_resources`, so no
|
|
165
|
+
// web page can frame or navigate to it), so this enforces the
|
|
166
|
+
// property rather than relying on it staying true.
|
|
167
|
+
window.parent.postMessage({ type: 'sm:parse-check-result', id: data.id, verdicts }, '*');
|
|
168
|
+
});
|
|
169
|
+
// Announce readiness. The embedder cannot detect it any other way: a
|
|
170
|
+
// sandboxed frame is opaque-origin, so its `contentDocument` is always
|
|
171
|
+
// null and `readyState` is unreadable from outside. Without this the
|
|
172
|
+
// embedder's only signal is the iframe's `load` event, and a listener
|
|
173
|
+
// attached even a moment late would miss it — leaving every request to
|
|
174
|
+
// time out silently for the rest of the session.
|
|
175
|
+
window.parent.postMessage({ type: 'sm:parse-sandbox-ready' }, '*');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Which scripts' menu command lists the person opened or closed in a
|
|
2
|
+
// tab's popup, kept for the tab's lifetime: someone using one of a
|
|
3
|
+
// script's commands is likely to come back for another, so a list they
|
|
4
|
+
// opened stays open the next time the popup shows, across reloads and
|
|
5
|
+
// navigations in the tab.
|
|
6
|
+
//
|
|
7
|
+
// One `popupopen:<tabId>` row per tab in `chrome.storage.session`
|
|
8
|
+
// ({ [scriptId]: open }), which outlasts the worker going idle between
|
|
9
|
+
// popups and clears with the browser. Only the worker writes it (the
|
|
10
|
+
// popup asks, `POPUP_SET_COMMANDS_OPEN_MESSAGE`); background.ts's
|
|
11
|
+
// `tabs.onRemoved` drops a closed tab's row.
|
|
12
|
+
const KEY_PREFIX = 'popupopen:';
|
|
13
|
+
function rowKey(tabId) {
|
|
14
|
+
return `${KEY_PREFIX}${tabId}`;
|
|
15
|
+
}
|
|
16
|
+
// Writes are chained, so two quick toggles in a row don't both read the
|
|
17
|
+
// row before either writes it.
|
|
18
|
+
let writes = Promise.resolve();
|
|
19
|
+
async function readRow(tabId) {
|
|
20
|
+
const row = (await chrome.storage.session.get(rowKey(tabId)))[rowKey(tabId)];
|
|
21
|
+
return row && typeof row === 'object' ? row : {};
|
|
22
|
+
}
|
|
23
|
+
// After the writes already asked for, so a popup opened just after a
|
|
24
|
+
// toggle sees it.
|
|
25
|
+
export async function getCommandsOpen(tabId) {
|
|
26
|
+
await writes;
|
|
27
|
+
return readRow(tabId);
|
|
28
|
+
}
|
|
29
|
+
export function setCommandsOpen(tabId, scriptId, open) {
|
|
30
|
+
const write = writes.then(async () => {
|
|
31
|
+
const row = await readRow(tabId);
|
|
32
|
+
await chrome.storage.session.set({ [rowKey(tabId)]: { ...row, [scriptId]: open } });
|
|
33
|
+
});
|
|
34
|
+
writes = write.catch(() => undefined);
|
|
35
|
+
return write;
|
|
36
|
+
}
|
|
37
|
+
export function dropCommandsOpen(tabId) {
|
|
38
|
+
const write = writes.then(() => chrome.storage.session.remove(rowKey(tabId)));
|
|
39
|
+
writes = write.catch(() => undefined);
|
|
40
|
+
return write;
|
|
41
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// Pure model behind the toolbar popup ("Active scripts", popup.html):
|
|
2
|
+
// which scripts it lists for a tab, under which collection, with what
|
|
3
|
+
// status, tooltip, and reload banner. Built in the service worker
|
|
4
|
+
// (`popupViewForTab` in background.ts), which owns the inputs, and
|
|
5
|
+
// rendered as-is by src/extension/popup.ts. Kept free of chrome.* so the
|
|
6
|
+
// rules can be unit tested. Design: docs/status-popup.md.
|
|
7
|
+
import { RUN_SCRIPT_LABEL } from '../userscript-header.js';
|
|
8
|
+
import { compareScriptNames } from '../script-display.js';
|
|
9
|
+
import { comparePreferred, preferredCopy } from './copy-preference.js';
|
|
10
|
+
import { isSettling, runsHere, skippedOnDocument } from './tab-status.js';
|
|
11
|
+
export const POPUP_STATUS_LABELS = {
|
|
12
|
+
OK: 'Running',
|
|
13
|
+
ERROR: 'Error',
|
|
14
|
+
REJECTED: 'Rejected',
|
|
15
|
+
RELOAD: 'Updated',
|
|
16
|
+
MISSING: 'Missing',
|
|
17
|
+
UNEXPECTED: 'Unexpected',
|
|
18
|
+
NOT_LOADED: 'Not loaded',
|
|
19
|
+
UNTRACKED: 'Reload',
|
|
20
|
+
PENDING: 'Reload to run',
|
|
21
|
+
STILL_RUNNING: 'Still running',
|
|
22
|
+
WARNING: 'Warning',
|
|
23
|
+
INVALID: 'Invalid',
|
|
24
|
+
NOT_RUN: 'Not run',
|
|
25
|
+
};
|
|
26
|
+
// Every pill's tooltip ends with what a click does: a Reload to run or
|
|
27
|
+
// Reload pill reloads the page, as the banner's Reload does; the rest open
|
|
28
|
+
// the Log.
|
|
29
|
+
export const POPUP_PILL_CLICK_HINT = 'Click to open the script log.';
|
|
30
|
+
export const POPUP_PILL_RELOAD_HINT = 'Click to reload the page.';
|
|
31
|
+
// The page menu's item, and the icon's right-click item doing the same.
|
|
32
|
+
export const RELOAD_NO_SCRIPTS_LABEL = 'Reload page with no userscripts';
|
|
33
|
+
export const COLLECTION_OFF_LABEL = 'Collection off';
|
|
34
|
+
export const COLLECTION_OFF_TOOLTIP = 'This collection is disabled, so none of its scripts run';
|
|
35
|
+
export const POPUP_NO_SCRIPTS = 'No scripts match this page.';
|
|
36
|
+
export const POPUP_NOT_INJECTABLE = 'SourceMonkey can\'t run scripts on this page.';
|
|
37
|
+
// The group for scripts removed since the page loaded that still run in
|
|
38
|
+
// it: no stored row or collection is left to list them under.
|
|
39
|
+
export const REMOVED_GROUP_ID = 'sm:removed';
|
|
40
|
+
export const REMOVED_GROUP_NAME = 'Removed scripts';
|
|
41
|
+
export { RUN_SCRIPT_LABEL };
|
|
42
|
+
// ---- Messages between the popup page and the service worker ----
|
|
43
|
+
// Popup → worker: the view for a tab. Answered with a `PopupView`.
|
|
44
|
+
export const POPUP_VIEW_MESSAGE = 'sm:popup-view';
|
|
45
|
+
// Popup → worker: reload the tab, with some of its scripts kept out.
|
|
46
|
+
export const POPUP_RELOAD_MESSAGE = 'sm:popup-reload';
|
|
47
|
+
// Popup → worker: run one of a script's menu commands.
|
|
48
|
+
export const POPUP_COMMAND_MESSAGE = 'sm:popup-command';
|
|
49
|
+
// Popup → worker: run a manual script now ("Run this script").
|
|
50
|
+
export const POPUP_RUN_SCRIPT_MESSAGE = 'sm:popup-run-script';
|
|
51
|
+
// Popup → worker: tick or untick a script's or a collection's box. The
|
|
52
|
+
// worker writes it, so the popup needn't load the script store.
|
|
53
|
+
export const POPUP_SET_ENABLED_MESSAGE = 'sm:popup-set-enabled';
|
|
54
|
+
// Popup → worker: a script's command list was opened or closed, which
|
|
55
|
+
// lasts for the tab's lifetime.
|
|
56
|
+
export const POPUP_SET_COMMANDS_OPEN_MESSAGE = 'sm:popup-set-commands-open';
|
|
57
|
+
// Worker → popup: something the view for `tabId` shows changed
|
|
58
|
+
// (badge state, menu commands). The popup asks for a fresh view. Sent
|
|
59
|
+
// over the port an open popup holds, named this prefix + its tab id.
|
|
60
|
+
export const POPUP_CHANGED_MESSAGE = 'sm:popup-changed';
|
|
61
|
+
// Worker → popup, over the same port: close. The click that opened it
|
|
62
|
+
// turned out to be the first of a double click.
|
|
63
|
+
export const POPUP_CLOSE_MESSAGE = 'sm:popup-close';
|
|
64
|
+
// Popup → worker, over the port: closing after an action (a link, a
|
|
65
|
+
// menu command). Its close is then no double click's second click.
|
|
66
|
+
export const POPUP_CLOSING_MESSAGE = 'sm:popup-closing';
|
|
67
|
+
export const POPUP_PORT_PREFIX = 'sm-popup:';
|
|
68
|
+
const DEFAULT_TOOLTIPS = {
|
|
69
|
+
OK: 'Running on this page',
|
|
70
|
+
ERROR: 'The script reported an error on this page',
|
|
71
|
+
REJECTED: 'Chrome refused to register this script, so it can\'t run',
|
|
72
|
+
RELOAD: 'The script changed since this page loaded. Reload to run the new version.',
|
|
73
|
+
MISSING: 'Expected on this page, but it hasn\'t started',
|
|
74
|
+
UNEXPECTED: 'Started on this page, but its rules don\'t match this page',
|
|
75
|
+
NOT_LOADED: 'Not run on this page load',
|
|
76
|
+
UNTRACKED: 'This page loaded before SourceMonkey started, so its status is unknown. Reload to restart.',
|
|
77
|
+
PENDING: 'Enabled since this page loaded. Reload to run it.',
|
|
78
|
+
STILL_RUNNING: 'Disabled since this page loaded. It keeps running until a reload.',
|
|
79
|
+
WARNING: 'Running on this page, with warnings',
|
|
80
|
+
INVALID: 'The script failed to load, so it can\'t run',
|
|
81
|
+
NOT_RUN: `This script runs only when you choose ${RUN_SCRIPT_LABEL}`,
|
|
82
|
+
};
|
|
83
|
+
// A manual script's OK tooltip: how many times it was run here.
|
|
84
|
+
export function manualRunsText(runs) {
|
|
85
|
+
return runs === 1 ? 'Ran once on this page' : `Ran ${runs} times on this page`;
|
|
86
|
+
}
|
|
87
|
+
// OK's tooltip for a script running only in the page's iframes.
|
|
88
|
+
const FRAMES_OK_TOOLTIP = 'Running in frames on this page';
|
|
89
|
+
// The heading of every surface that says user scripts are disabled: the
|
|
90
|
+
// collections page's banner, the popup's bar and the toolbar tooltip.
|
|
91
|
+
export const USER_SCRIPTS_OFF_HEADING = 'User scripts are not allowed yet.';
|
|
92
|
+
// NOT_LOADED's tooltip on such a page while they still are.
|
|
93
|
+
const NOT_RUN_USERSCRIPTS_DISABLED = 'Not run: user scripts are disabled';
|
|
94
|
+
// UNTRACKED's tooltip on a page that loaded while user scripts were disabled.
|
|
95
|
+
const LOADED_WITHOUT_USERSCRIPTS_TOOLTIP = 'This page loaded while user scripts were disabled, so its scripts didn\'t run. Reload to run them.';
|
|
96
|
+
// STILL_RUNNING's tooltip for a script removed since the page loaded.
|
|
97
|
+
const REMOVED_TOOLTIP = 'Removed since this page loaded. It keeps running until a reload.';
|
|
98
|
+
// PENDING's tooltip for a script that matches only the URL the page
|
|
99
|
+
// moved to in place: Chrome injects at a document load, not on pushState.
|
|
100
|
+
const MOVED_ONLY_TOOLTIP = 'This page changed its address since it loaded, and the script matches only the new one. Reload to run it.';
|
|
101
|
+
// Why a NOT_LOADED script didn't run: which kind of reload, or the
|
|
102
|
+
// sm-dev claim, kept it out.
|
|
103
|
+
const SM_DEV_KEPT_OUT = 'Not run: the sm-dev run controlling this tab kept it out';
|
|
104
|
+
function notLoadedReason(state, nameOf) {
|
|
105
|
+
if (state.loadedWithoutUserscripts)
|
|
106
|
+
return NOT_RUN_USERSCRIPTS_DISABLED;
|
|
107
|
+
if (state.scriptsSkipped)
|
|
108
|
+
return 'Not run: the page was reloaded with no userscripts';
|
|
109
|
+
if (state.skipOnly !== undefined)
|
|
110
|
+
return `Not run: the page was reloaded with only ${nameOf(state.skipOnly)}`;
|
|
111
|
+
if (state.skipWithout !== undefined)
|
|
112
|
+
return 'Not run: the page was reloaded without this script';
|
|
113
|
+
if (state.smDevAllow !== undefined)
|
|
114
|
+
return SM_DEV_KEPT_OUT;
|
|
115
|
+
return DEFAULT_TOOLTIPS.NOT_LOADED;
|
|
116
|
+
}
|
|
117
|
+
// The reload banner's text, naming the most specific cause.
|
|
118
|
+
export function bannerText(c) {
|
|
119
|
+
// First: it explains every other cause there may be.
|
|
120
|
+
if (c.loadedBefore)
|
|
121
|
+
return 'Reload required: Page loaded before SourceMonkey started';
|
|
122
|
+
if (c.loadedWithoutUserscripts)
|
|
123
|
+
return 'Reload required: Page loaded while user scripts were disabled';
|
|
124
|
+
if (c.enabled && (c.disabled || c.removed))
|
|
125
|
+
return 'Reload required: Enabled scripts changed';
|
|
126
|
+
if (c.enabled)
|
|
127
|
+
return 'Reload required: Scripts enabled';
|
|
128
|
+
if (c.disabled && c.removed)
|
|
129
|
+
return 'Reload required: Scripts disabled and removed';
|
|
130
|
+
if (c.disabled)
|
|
131
|
+
return 'Reload required: Scripts disabled';
|
|
132
|
+
if (c.removed)
|
|
133
|
+
return 'Reload required: Scripts removed';
|
|
134
|
+
if (c.updated)
|
|
135
|
+
return 'Reload required: Scripts updated';
|
|
136
|
+
if (c.notYetRun)
|
|
137
|
+
return 'Reload required: Some scripts haven\'t run on this page yet';
|
|
138
|
+
if (c.missing && c.unexpected)
|
|
139
|
+
return 'Reload required: Some scripts are missing or unexpected';
|
|
140
|
+
if (c.missing)
|
|
141
|
+
return 'Reload required: Some scripts are missing';
|
|
142
|
+
if (c.unexpected)
|
|
143
|
+
return 'Reload required: Unexpected scripts are running';
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
export function buildPopupView(input) {
|
|
147
|
+
const { state } = input;
|
|
148
|
+
const smDev = state?.smDevAllow !== undefined;
|
|
149
|
+
const view = {
|
|
150
|
+
url: input.url,
|
|
151
|
+
...(input.userScriptsDisabled ? { userScriptsDisabled: true } : {}),
|
|
152
|
+
injectable: input.injectable,
|
|
153
|
+
smDev,
|
|
154
|
+
banner: null,
|
|
155
|
+
bannerRecheckAt: null,
|
|
156
|
+
perScriptReload: !smDev && state != null,
|
|
157
|
+
enabled: [],
|
|
158
|
+
frames: [],
|
|
159
|
+
manual: [],
|
|
160
|
+
disabled: [],
|
|
161
|
+
collections: [...input.collections],
|
|
162
|
+
};
|
|
163
|
+
if (!input.injectable)
|
|
164
|
+
return view;
|
|
165
|
+
const collections = new Map(input.collections.map((c) => [c.id, c]));
|
|
166
|
+
const scripts = new Map(input.scripts.map((s) => [s.id, s]));
|
|
167
|
+
const badgeById = new Map(input.badgeRows.map((r) => [r.id, r]));
|
|
168
|
+
const nameOf = (id) => scripts.get(id)?.name ?? badgeById.get(id)?.name ?? id;
|
|
169
|
+
const isActive = (s) => s.enabled && (collections.get(s.collectionId)?.enabled ?? false);
|
|
170
|
+
const isRunning = (id) => state != null && runsHere(state, id);
|
|
171
|
+
const settling = state != null && isSettling(state, input.now ?? Date.now());
|
|
172
|
+
// Every candidate, then one identity at a time: the copies that are
|
|
173
|
+
// active or running here all show; with none of those, one copy
|
|
174
|
+
// stands for the script in Disabled.
|
|
175
|
+
const keep = input.keepIds ?? new Set();
|
|
176
|
+
const candidates = new Set([...input.matchingIds, ...badgeById.keys(), ...keep]);
|
|
177
|
+
const byIdentity = new Map();
|
|
178
|
+
// Scripts deleted since they ran that still run here: listed in a
|
|
179
|
+
// group of their own, since they have no collection left.
|
|
180
|
+
const removed = [];
|
|
181
|
+
for (const id of candidates) {
|
|
182
|
+
const s = scripts.get(id);
|
|
183
|
+
if (!s || !collections.has(s.collectionId)) {
|
|
184
|
+
if (isRunning(id) && !input.hiddenIds?.has(id))
|
|
185
|
+
removed.push(id);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const key = s.identity ?? `id:${s.id}`;
|
|
189
|
+
const list = byIdentity.get(key);
|
|
190
|
+
if (list)
|
|
191
|
+
list.push(s);
|
|
192
|
+
else
|
|
193
|
+
byIdentity.set(key, [s]);
|
|
194
|
+
}
|
|
195
|
+
const shown = [];
|
|
196
|
+
for (const copies of byIdentity.values()) {
|
|
197
|
+
const otherCopies = copies.length > 1;
|
|
198
|
+
const live = copies.filter((s) => isActive(s) || isRunning(s.id));
|
|
199
|
+
const kept = copies.filter((s) => keep.has(s.id) && !live.includes(s));
|
|
200
|
+
if (live.length > 0 || kept.length > 0) {
|
|
201
|
+
const activeCount = live.filter(isActive).length;
|
|
202
|
+
for (const s of [...live, ...kept])
|
|
203
|
+
shown.push({ script: s, dup: activeCount > 1 && isActive(s), otherCopies });
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// The representative: the copy closest to running and most likely
|
|
207
|
+
// to be turned back on (`comparePreferred`, docs/script-enablement.md).
|
|
208
|
+
const pick = preferredCopy(copies, (s) => {
|
|
209
|
+
const c = collections.get(s.collectionId);
|
|
210
|
+
return {
|
|
211
|
+
enabled: s.enabled,
|
|
212
|
+
collectionOn: c.enabled,
|
|
213
|
+
collectionName: c.name,
|
|
214
|
+
activeSince: s.activeSince,
|
|
215
|
+
inactiveSince: s.inactiveSince,
|
|
216
|
+
version: s.version,
|
|
217
|
+
location: s.location,
|
|
218
|
+
};
|
|
219
|
+
}, comparePreferred);
|
|
220
|
+
shown.push({ script: pick, dup: false, otherCopies });
|
|
221
|
+
}
|
|
222
|
+
const causes = {};
|
|
223
|
+
const rowsByCollection = {
|
|
224
|
+
enabled: new Map(),
|
|
225
|
+
frames: new Map(),
|
|
226
|
+
manual: new Map(),
|
|
227
|
+
disabled: new Map(),
|
|
228
|
+
};
|
|
229
|
+
for (const { script: s, dup, otherCopies } of shown) {
|
|
230
|
+
const active = isActive(s);
|
|
231
|
+
const badge = badgeById.get(s.id);
|
|
232
|
+
let status = null;
|
|
233
|
+
// PENDING only because the page changed its URL in place.
|
|
234
|
+
let movedOnly = false;
|
|
235
|
+
if (s.invalid !== undefined && !isRunning(s.id)) {
|
|
236
|
+
// Never registered, so no badge row, and a reload won't run it:
|
|
237
|
+
// not PENDING, and no banner. Disabled ones say so too, so it's
|
|
238
|
+
// clear that turning one on won't make it run.
|
|
239
|
+
status = 'INVALID';
|
|
240
|
+
}
|
|
241
|
+
else if (s.manual && active) {
|
|
242
|
+
// Nothing expects a manual script to run, so a reload has nothing
|
|
243
|
+
// to say about it: no PENDING, RELOAD or banner. In a tab an
|
|
244
|
+
// sm-dev run controls that keeps it out, it's Not loaded, as a
|
|
245
|
+
// script kept out of the load would be.
|
|
246
|
+
if (badge && (badge.status === 'OK' || badge.status === 'ERROR'))
|
|
247
|
+
status = badge.status;
|
|
248
|
+
else if (smDev && !input.manualRunnableIds?.has(s.id))
|
|
249
|
+
status = 'NOT_LOADED';
|
|
250
|
+
else
|
|
251
|
+
status = 'NOT_RUN';
|
|
252
|
+
}
|
|
253
|
+
else if (active) {
|
|
254
|
+
status = badge?.status ?? (state ? 'PENDING' : null);
|
|
255
|
+
// Matched now but not when the page loaded, and never reported:
|
|
256
|
+
// enabled (or installed) since, or matching only the URL the page
|
|
257
|
+
// moved to in place. Not in a tab an sm-dev run controls, where a
|
|
258
|
+
// reload still wouldn't run it, nor over a refusal, which a
|
|
259
|
+
// reload doesn't cure.
|
|
260
|
+
const atLoad = state?.loadMatchIds;
|
|
261
|
+
if (state && atLoad && !atLoad.includes(s.id) && !isRunning(s.id)
|
|
262
|
+
&& status !== 'REJECTED' && !(smDev && skippedOnDocument(state, s.id))) {
|
|
263
|
+
status = 'PENDING';
|
|
264
|
+
movedOnly = input.loadUrlMatchingIds !== undefined && !input.loadUrlMatchingIds.has(s.id);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else if (isRunning(s.id)) {
|
|
268
|
+
status = 'STILL_RUNNING';
|
|
269
|
+
}
|
|
270
|
+
const warned = isRunning(s.id) && (s.headerWarnings || (state?.warnedIds?.includes(s.id) ?? false));
|
|
271
|
+
if (status === 'OK' && warned)
|
|
272
|
+
status = 'WARNING';
|
|
273
|
+
if (status === 'RELOAD')
|
|
274
|
+
causes.updated = true;
|
|
275
|
+
if (status === 'PENDING') {
|
|
276
|
+
if (movedOnly)
|
|
277
|
+
causes.notYetRun = true;
|
|
278
|
+
else
|
|
279
|
+
causes.enabled = true;
|
|
280
|
+
}
|
|
281
|
+
if (status === 'STILL_RUNNING')
|
|
282
|
+
causes.disabled = true;
|
|
283
|
+
// Not while the page is still settling: its scripts may be on their way.
|
|
284
|
+
if (status === 'MISSING') {
|
|
285
|
+
if (settling)
|
|
286
|
+
view.bannerRecheckAt = state.settleBy;
|
|
287
|
+
else
|
|
288
|
+
causes.missing = true;
|
|
289
|
+
}
|
|
290
|
+
if (status === 'UNEXPECTED')
|
|
291
|
+
causes.unexpected = true;
|
|
292
|
+
if (status === 'UNTRACKED') {
|
|
293
|
+
if (state?.loadedWithoutUserscripts && !state.loadedBefore)
|
|
294
|
+
causes.loadedWithoutUserscripts = true;
|
|
295
|
+
else
|
|
296
|
+
causes.loadedBefore = true;
|
|
297
|
+
}
|
|
298
|
+
let tooltip = null;
|
|
299
|
+
if (status) {
|
|
300
|
+
let text = DEFAULT_TOOLTIPS[status];
|
|
301
|
+
if (status === 'OK' && badge?.framesOnly)
|
|
302
|
+
text = FRAMES_OK_TOOLTIP;
|
|
303
|
+
if (status === 'ERROR' && badge?.message)
|
|
304
|
+
text = badge.message;
|
|
305
|
+
if (status === 'REJECTED' && input.rejected[s.id])
|
|
306
|
+
text = input.rejected[s.id];
|
|
307
|
+
if (status === 'NOT_LOADED' && state) {
|
|
308
|
+
// A manual script isn't kept out by a reload mode, only by the claim.
|
|
309
|
+
text = s.manual ? SM_DEV_KEPT_OUT : notLoadedReason(state, nameOf);
|
|
310
|
+
}
|
|
311
|
+
if (status === 'UNTRACKED' && state?.loadedWithoutUserscripts && !state.loadedBefore)
|
|
312
|
+
text = LOADED_WITHOUT_USERSCRIPTS_TOOLTIP;
|
|
313
|
+
if (status === 'INVALID' && s.invalid)
|
|
314
|
+
text = `${DEFAULT_TOOLTIPS.INVALID}: ${s.invalid}`;
|
|
315
|
+
if (movedOnly)
|
|
316
|
+
text = MOVED_ONLY_TOOLTIP;
|
|
317
|
+
if (status === 'STILL_RUNNING' && s.enabled) {
|
|
318
|
+
text = 'Its collection was disabled since this page loaded. It keeps running until a reload.';
|
|
319
|
+
}
|
|
320
|
+
const runs = state?.manualRuns?.[s.id];
|
|
321
|
+
if (s.manual && status === 'OK' && runs)
|
|
322
|
+
text = manualRunsText(runs);
|
|
323
|
+
const reloads = status === 'PENDING' || status === 'UNTRACKED';
|
|
324
|
+
tooltip = `${text}\n${reloads ? POPUP_PILL_RELOAD_HINT : POPUP_PILL_CLICK_HINT}`;
|
|
325
|
+
}
|
|
326
|
+
const row = {
|
|
327
|
+
id: s.id,
|
|
328
|
+
name: s.name,
|
|
329
|
+
checked: s.enabled,
|
|
330
|
+
active,
|
|
331
|
+
status,
|
|
332
|
+
warned,
|
|
333
|
+
tooltip,
|
|
334
|
+
versionLine: dup && s.version ? `Version ${s.version}` : null,
|
|
335
|
+
otherCopies,
|
|
336
|
+
commands: active || isRunning(s.id) ? [...(input.menuCommands.get(s.id) ?? [])] : [],
|
|
337
|
+
...(s.manual ? { manual: true } : {}),
|
|
338
|
+
...(s.manual && active && s.invalid === undefined && input.manualRunnableIds?.has(s.id) ? { runnable: true } : {}),
|
|
339
|
+
};
|
|
340
|
+
// Anything running in the page is listed with the active scripts,
|
|
341
|
+
// even when it's disabled now (Still running) or unexpected. Manual
|
|
342
|
+
// scripts have their own section, by the same rule, and so do
|
|
343
|
+
// scripts running only in frames.
|
|
344
|
+
const into = !(active || isRunning(s.id))
|
|
345
|
+
? rowsByCollection.disabled
|
|
346
|
+
: s.manual ? rowsByCollection.manual
|
|
347
|
+
: badge?.framesOnly ? rowsByCollection.frames : rowsByCollection.enabled;
|
|
348
|
+
const list = into.get(s.collectionId);
|
|
349
|
+
if (list)
|
|
350
|
+
list.push(row);
|
|
351
|
+
else
|
|
352
|
+
into.set(s.collectionId, [row]);
|
|
353
|
+
}
|
|
354
|
+
const toGroups = (byColl) => input.collections
|
|
355
|
+
.filter((c) => byColl.has(c.id))
|
|
356
|
+
.map((c) => ({
|
|
357
|
+
collection: c,
|
|
358
|
+
rows: byColl.get(c.id).sort((a, b) => compareScriptNames(a.name, b.name)),
|
|
359
|
+
}));
|
|
360
|
+
view.enabled = toGroups(rowsByCollection.enabled);
|
|
361
|
+
view.frames = toGroups(rowsByCollection.frames);
|
|
362
|
+
view.manual = toGroups(rowsByCollection.manual);
|
|
363
|
+
view.disabled = toGroups(rowsByCollection.disabled);
|
|
364
|
+
if (removed.length > 0) {
|
|
365
|
+
causes.removed = true;
|
|
366
|
+
const collection = { id: REMOVED_GROUP_ID, name: REMOVED_GROUP_NAME, path: '', enabled: true };
|
|
367
|
+
view.collections.push(collection);
|
|
368
|
+
view.enabled.push({
|
|
369
|
+
collection,
|
|
370
|
+
rows: removed.map((id) => ({
|
|
371
|
+
id,
|
|
372
|
+
name: nameOf(id),
|
|
373
|
+
checked: false,
|
|
374
|
+
active: false,
|
|
375
|
+
status: 'STILL_RUNNING',
|
|
376
|
+
warned: false,
|
|
377
|
+
// No click hint: there is no script page left to open.
|
|
378
|
+
tooltip: REMOVED_TOOLTIP,
|
|
379
|
+
versionLine: null,
|
|
380
|
+
otherCopies: false,
|
|
381
|
+
commands: [...(input.menuCommands.get(id) ?? [])],
|
|
382
|
+
})).sort((a, b) => compareScriptNames(a.name, b.name)),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
view.banner = bannerText(causes);
|
|
386
|
+
return view;
|
|
387
|
+
}
|