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.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,126 @@
1
+ // Recognizing an HTML page served where JavaScript was expected.
2
+ //
3
+ // The failure this catches is a *soft* one: the request succeeded, the
4
+ // status was 200, and the bytes are a perfectly good web page — just
5
+ // not the script we asked for. Left alone it flows onward and is
6
+ // reported as a syntax error in the user's script, which blames the
7
+ // wrong file. The classic shapes:
8
+ // - a forge's file-browser page (`github.com/<o>/<r>/blob/…`) instead
9
+ // of the raw bytes;
10
+ // - a login / SSO wall or captive portal answering at 200;
11
+ // - a `@require` URL that has rotted into a 200-with-error-page;
12
+ // - a local *directory* whose name matches the ref — Chrome answers
13
+ // `file://` directory reads with a generated HTML listing.
14
+ //
15
+ // Two independent tests, because each catches what the other misses:
16
+ // - `isMarkupContentType` — the server told us. Free, and right even
17
+ // when the body is unreadable.
18
+ // - `looksLikeMarkup` — the bytes tell us. Needed when there is no
19
+ // Content-Type at all (the `file://` XHR leg often has none) or the
20
+ // server mislabels HTML as `text/plain`.
21
+ //
22
+ // Deliberately NOT applied to `@resource` (an HTML resource is a
23
+ // legitimate thing to fetch and hand to `GM_getResourceText`), to
24
+ // `@icon` (already gated on `image/*`), or to `file://` directory
25
+ // listings (which are HTML *by design* — see `parseListing` in
26
+ // directories.ts). The rule is "this text is about to be treated as
27
+ // JavaScript", not "this text arrived over the network".
28
+ //
29
+ // See [docs/requires.md] "Markup gate" for the two tests written up
30
+ // together, plus [docs/install-capture.md] and [docs/diagnostics.md].
31
+ // True for the Content-Type families that mean markup. A denylist, not
32
+ // an allowlist: `application/octet-stream` and friends are common and
33
+ // legitimate for JavaScript (GitHub serves every release asset that
34
+ // way), so only the types that can't be a script are rejected.
35
+ // `text/html-sandboxed` is caught by the prefix and deserves to be;
36
+ // XHTML is spelled `application/xhtml+xml`, so it needs its own clause
37
+ // to match the same intent.
38
+ //
39
+ // `application/json` is deliberately absent: a JSON error envelope at
40
+ // 200 is a real soft-failure shape, but so is a legitimate JS module
41
+ // served by a mislabelling host, and the eval-side try/catch already
42
+ // contains the damage.
43
+ //
44
+ // Trims defensively so the rule holds for a caller that hands over a
45
+ // raw header rather than the lowercased/trimmed one every producer in
46
+ // file-loader.ts passes.
47
+ export function isMarkupContentType(contentType) {
48
+ const type = contentType.trim().toLowerCase();
49
+ return type.startsWith('text/html') || type.startsWith('application/xhtml');
50
+ }
51
+ // True when the text itself is a markup document: the first thing in
52
+ // it, past any BOM and leading whitespace, is `<`.
53
+ //
54
+ // Why the FIRST character and not "does it contain HTML" — a forge's
55
+ // blob page embeds the file's own lines, metadata block and all, so a
56
+ // search anywhere in the body finds `// ==UserScript==` and concludes
57
+ // the page IS the script. Position is the only reliable signal, and it
58
+ // is exactly the signal a served `.js` file gives: JavaScript starts
59
+ // with code or a comment, never with a tag.
60
+ //
61
+ // The check is on the leading character, not on the metadata block's
62
+ // position, because a script may legitimately carry leading text — a
63
+ // license banner above the block is common, is preserved
64
+ // (`leadingText`, userscript-header.ts), and must keep working.
65
+ //
66
+ // Known false positive, accepted: `<!--` at the very top of a file is
67
+ // valid JavaScript (Annex B HTML-like comments), so a script opening
68
+ // with one is called markup. That spelling belongs to 1990s inline
69
+ // `<script>` blocks and does not occur in userscripts.
70
+ export function looksLikeMarkup(text) {
71
+ // A leading BOM needs no special handling: ECMAScript's `WhiteSpace`
72
+ // production includes U+FEFF, so `\s` matches it here exactly as
73
+ // `trim` would. (`userscript-header.ts` strips one explicitly for a
74
+ // different reason — a `/m` regex's `^` won't match past it.)
75
+ //
76
+ // Anchored regex rather than `trimStart().startsWith('<')`: the
77
+ // rejected inputs are whole web pages, and trimming one allocates a
78
+ // second copy of it to look at a single character.
79
+ return /^\s*</.test(text);
80
+ }
81
+ // The one wording for "we got a web page instead of JavaScript",
82
+ // shared by every caller so the same problem never gets two spellings.
83
+ //
84
+ // `localUrl` splits it into two sentences on purpose: blaming the
85
+ // server when the bytes came off local disk would be a flatly wrong
86
+ // diagnosis, and the common way to hit the local case is a directory
87
+ // whose name matches the ref.
88
+ //
89
+ // `contentType` is the *evidence*, not decoration — pass it only when
90
+ // the header is what proved this, and leave it out when the body did.
91
+ // A parenthetical reading `(Content-Type "text/plain")` next to a
92
+ // claim that the response is HTML reads as a contradiction.
93
+ //
94
+ // `expected` names what we wanted instead. Almost always JavaScript,
95
+ // but the same fetcher reads collection manifests, and telling someone
96
+ // their manifest isn't JavaScript names the wrong thing.
97
+ export function markupResponseMessage(opts = {}) {
98
+ const evidence = opts.contentType ? ` (Content-Type "${opts.contentType}")` : '';
99
+ const expected = opts.expected ?? 'JavaScript';
100
+ return opts.localUrl
101
+ ? `${LOCAL_MARKUP_PREFIX}${opts.localUrl}${LOCAL_MARKUP_MIDDLE}${expected}${evidence}; a folder with that name would look like this`
102
+ : `${REMOTE_MARKUP_PREFIX}${expected}${evidence}; the link may be dead or behind a login`;
103
+ }
104
+ const REMOTE_MARKUP_PREFIX = 'The server returned an HTML page, not ';
105
+ const LOCAL_MARKUP_PREFIX = 'The local file ';
106
+ const LOCAL_MARKUP_MIDDLE = ' is an HTML page, not ';
107
+ // Did this text come out of `markupResponseMessage`? For the corpus
108
+ // validator, which tags each invalid script with why it failed.
109
+ export function isMarkupResponseMessage(text) {
110
+ return text.startsWith(REMOTE_MARKUP_PREFIX)
111
+ || (text.startsWith(LOCAL_MARKUP_PREFIX) && text.includes(LOCAL_MARKUP_MIDDLE));
112
+ }
113
+ // Thrown by the script-text fetchers so a caller can tell this apart
114
+ // from every other read failure without matching on wording (which
115
+ // docs/diagnostics.md forbids, and which would go stale the first time
116
+ // the message is reworded).
117
+ //
118
+ // The preview is the caller that cares: for a captured navigation,
119
+ // "this is a web page" is not an error to report but a sign the
120
+ // capture should never have fired, so it continues to the page instead.
121
+ export class MarkupResponseError extends Error {
122
+ constructor(message) {
123
+ super(message);
124
+ this.name = 'MarkupResponseError';
125
+ }
126
+ }
@@ -0,0 +1,305 @@
1
+ // Pure helpers for converting a Chrome `@match` pattern into a
2
+ // "target" the rest of the app can act on: the pattern's representative
3
+ // origin and a click-through / page URL (the same URL serves both
4
+ // purposes — favicon HTML discovery and open-in-new-tab click).
5
+ //
6
+ // Used by:
7
+ // - site-icon.ts — takes `representativeMatches(matching)` (the
8
+ // first few distinct sites) to drive favicon discovery (origin
9
+ // caching, page URL for HTML probe, click target) — one icon per
10
+ // site.
11
+ // - script-details.ts / code-links.ts — call `matchTargetInfo(pattern)`
12
+ // per-pattern when linkifying `@match`/`@exclude`/`@include` values,
13
+ // falling back to `sameDirLinkUrl` for patterns that name no site.
14
+ //
15
+ // No fetching, no DOM, no chrome.storage — that all lives in the
16
+ // modules that consume these primitives.
17
+ import { canonicalMatchPatterns } from './url-match.js';
18
+ // Pure regex rewrite, no fetching — imported so the same-directory
19
+ // fallback below points a github-hosted script's neighbour at the
20
+ // github.com page instead of its raw bytes.
21
+ import { githubRawToBlobUrl } from './github-urls.js';
22
+ // Pull a Chrome match pattern apart and dewildcard its host. Returns
23
+ // null when the pattern can't identify a single site (`<all_urls>`,
24
+ // host of `*`, non-http(s) schemes, embedded `*` after dewildcard,
25
+ // or syntactically invalid).
26
+ //
27
+ // Port handling: an explicit `:port` in the pattern is preserved
28
+ // verbatim. The favicon almost certainly is served from the same
29
+ // port the page is on (`localhost:3000`, a dev server, etc.), and
30
+ // the cache key needs to include the port to avoid collisions
31
+ // between two different servers sharing a hostname on different
32
+ // ports. Patterns without a port stay portless — the icon resolver
33
+ // will hit the default port, which is the right behavior for the
34
+ // common case.
35
+ function parseMatchPattern(pattern) {
36
+ if (pattern === '<all_urls>')
37
+ return null;
38
+ // Read the pattern the way the matcher and the registration do — a
39
+ // scheme-less `*.example.com/*` names a site, and should get an icon
40
+ // and a click-through like any other. An expansion only varies the
41
+ // scheme, and this picks one representative origin, so prefer a form
42
+ // that isn't `http://`: `*` is read as https below, and a port-pinned
43
+ // `*://host:8443/*` (which canonicalizes to the http + https pair)
44
+ // must keep pointing at the https member — fetching the icon over
45
+ // http would fail on a TLS-only port.
46
+ const canonical = canonicalMatchPatterns(pattern);
47
+ const preferred = canonical.find((p) => !p.startsWith('http://')) ?? canonical[0] ?? pattern;
48
+ const parts = /^(\*|https?|file|ftp):\/\/([^/]*)(\/.*)?$/i.exec(preferred);
49
+ if (!parts)
50
+ return null;
51
+ const [, rawScheme, rawHost] = parts;
52
+ const scheme = rawScheme === '*' ? 'https' : rawScheme.toLowerCase();
53
+ if (scheme !== 'http' && scheme !== 'https')
54
+ return null;
55
+ // Split host:port so the host-wildcard rules apply only to the
56
+ // host portion. A numeric port is preserved verbatim. A `:*` port
57
+ // (any port) covers the default one, so the icon comes from there.
58
+ const colon = rawHost.lastIndexOf(':');
59
+ const hasPort = colon > 0 && /^(\d+|\*)$/.test(rawHost.slice(colon + 1));
60
+ const port = hasPort && rawHost.slice(colon + 1) !== '*' ? rawHost.slice(colon) : '';
61
+ let host = (hasPort ? rawHost.slice(0, colon) : rawHost).toLowerCase();
62
+ if (host === '*' || host === '')
63
+ return null;
64
+ if (host.startsWith('*.'))
65
+ host = host.slice(2);
66
+ if (!host || host.includes('*'))
67
+ return null;
68
+ // `.tld` means "any public suffix" (see url-match.ts); `.com` is
69
+ // the one most such sites answer on, so the icon and link use it.
70
+ if (host.endsWith('.tld'))
71
+ host = host.slice(0, -4) + '.com';
72
+ // The host slot has to hold a host we could actually navigate to.
73
+ // Worth checking since the scheme-less reading above will supply a
74
+ // scheme for any text at all (`not a pattern` → `*://not a pattern`),
75
+ // and this origin becomes a fetch and an href.
76
+ try {
77
+ new URL(`${scheme}://${host}${port}/`);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ return { scheme, host: host + port, path: parts[3] ?? '/' };
83
+ }
84
+ export function originFromMatchPattern(pattern) {
85
+ const p = parseMatchPattern(pattern);
86
+ return p ? `${p.scheme}://${p.host}` : null;
87
+ }
88
+ // Iterate `matching.matches` and return the first origin we can
89
+ // extract. Input is `@match`-only by construction (`@include` /
90
+ // `@exclude` live in separate fields on `UrlMatching`); we'd skip
91
+ // them here anyway since they're free-form globs/regexes over the
92
+ // whole URL, not URL templates.
93
+ export function representativeOrigin(matching) {
94
+ return representativeMatch(matching)?.origin ?? null;
95
+ }
96
+ // Like representativeOrigin, but also yields a representative URL —
97
+ // the page to fetch for icon discovery AND the click-through target.
98
+ // Built segment-aware (see `linkPathFromMatchPath`):
99
+ // https://example.com/* → /
100
+ // https://example.com/app/home/* → /app/home/
101
+ // https://example.com/posts/*/edit → /posts/
102
+ // https://example.com/foo/bar* → /foo/bar
103
+ export function representativeMatch(matching) {
104
+ return representativeMatches(matching, 1)[0] ?? null;
105
+ }
106
+ // How many distinct sites a script's icon slot shows by default — the
107
+ // collections-table limit, where the slot sits in a column beside the
108
+ // name. The viewer heading has room for more and passes its own.
109
+ export const MAX_SITE_ICONS = 3;
110
+ // The first `limit` DISTINCT sites named by `matching.matches`, in
111
+ // header order. Two patterns are the same site when their hosts match
112
+ // after dropping a leading `www.`; the scheme is ignored, so
113
+ // `http://www.example.com/*` and `https://example.com/*` yield one
114
+ // entry (the first seen). Ports still distinguish — `localhost:3000`
115
+ // and `localhost:8080` are different servers with different icons.
116
+ export function representativeMatches(matching, limit = MAX_SITE_ICONS) {
117
+ const out = [];
118
+ const seen = new Set();
119
+ for (const m of matching.matches) {
120
+ if (out.length >= limit)
121
+ break;
122
+ const got = matchTargetInfo(m);
123
+ if (!got)
124
+ continue;
125
+ const key = siteKey(got.origin);
126
+ if (seen.has(key))
127
+ continue;
128
+ seen.add(key);
129
+ out.push(got);
130
+ }
131
+ return out;
132
+ }
133
+ // Identity of a site for the purpose of "one icon per site": the
134
+ // host[:port] with a leading `www.` removed and the scheme dropped.
135
+ function siteKey(origin) {
136
+ const host = origin.slice(origin.indexOf('://') + 3).toLowerCase();
137
+ return host.startsWith('www.') ? host.slice(4) : host;
138
+ }
139
+ // Per-pattern variant of `representativeMatch`. The linkifier calls
140
+ // this once per `@match`/`@exclude`/`@include` value rather than
141
+ // taking the first usable one.
142
+ export function matchTargetInfo(pattern) {
143
+ const p = parseMatchPattern(pattern);
144
+ if (!p)
145
+ return null;
146
+ const origin = `${p.scheme}://${p.host}`;
147
+ const linkUrl = origin + linkPathFromMatchPath(p.path);
148
+ return { origin, linkUrl };
149
+ }
150
+ // Fallback for patterns that name no site: link to a page sitting in
151
+ // the same directory as the script itself. Full rule, worked examples
152
+ // and rationale in docs/script-viewer.md#same-directory-fallback; in
153
+ // short, a pattern with no host whose literal trailing directories
154
+ // (and any literal text ending a wildcard directory, like `*gm-menu`)
155
+ // match the tail of the script's own directory D links to D + its
156
+ // filename.
157
+ //
158
+ // It exists for the site-less patterns a script uses to name the page
159
+ // it ships beside, such as:
160
+ //
161
+ // @match *://*/gm-menu/gm-menu.html
162
+ // @match *://*/*gm-menu/gm-menu.html
163
+ // @match file:///*/gm-menu/gm-menu.html
164
+ //
165
+ // None yields a target above (the `*://` ones have a host of `*`, the
166
+ // other a `file:` scheme), so their rows rendered as plain text even
167
+ // though the page is sitting next to the script we're viewing.
168
+ //
169
+ // Two properties worth keeping in mind when editing:
170
+ // - The result is D plus one literal filename, so no pattern can
171
+ // walk out of the script's own directory.
172
+ // - Existence is never checked, so a script linkifies identically
173
+ // whether it was loaded from disk or from a server.
174
+ export function sameDirLinkUrl(pattern, scriptUrl) {
175
+ if (!scriptUrl)
176
+ return null;
177
+ // Only the schemes a site-less pattern is written with. Anchoring
178
+ // the scheme (rather than accepting any `x://`) keeps
179
+ // `/^https?:\/\/x/`-style regex literals — legal in `@include` /
180
+ // `@exclude` — out of here, and `https?\*?` admits the
181
+ // `http*://` shorthand those tags are often written with.
182
+ const parts = /^(?:\*|https?\*?|file):\/\/([^/]*)(\/.*)$/i.exec(pattern.trim());
183
+ if (!parts)
184
+ return null;
185
+ const [, host, path] = parts;
186
+ if (host !== '' && host !== '*')
187
+ return null;
188
+ const segments = path.split('/').slice(1);
189
+ const filename = segments.pop() ?? '';
190
+ // A query or fragment means the page is parametrised, not a plain
191
+ // file next door — and `?` is legal in a `@match` path glob
192
+ // (`ol.php?type=precip*`), so it has to be rejected explicitly
193
+ // rather than left to the extension check.
194
+ if (!filename || /[*?#]/.test(filename) || !/\.[A-Za-z0-9]+$/.test(filename))
195
+ return null;
196
+ // Everything at or before the last wildcard segment is absorbed by
197
+ // that wildcard; what follows has to be matched literally.
198
+ // (A hand-rolled findLastIndex — the project's lib target predates it.)
199
+ let lastWildcard = -1;
200
+ segments.forEach((seg, i) => {
201
+ if (seg.includes('*'))
202
+ lastWildcard = i;
203
+ });
204
+ const literalDirs = segments.slice(lastWildcard + 1);
205
+ // The text after that segment's last `*` (`gm-menu` in `*gm-menu`)
206
+ // still has to end the directory the wildcard stands in for.
207
+ const wildSuffix = lastWildcard < 0
208
+ ? ''
209
+ : segments[lastWildcard].slice(segments[lastWildcard].lastIndexOf('*') + 1);
210
+ if (literalDirs.length === 0 && !wildSuffix)
211
+ return null;
212
+ if (literalDirs.some((s) => s === ''))
213
+ return null;
214
+ const dir = scriptDirUrl(scriptUrl);
215
+ if (!dir)
216
+ return null;
217
+ const dirSegments = dir.pathname.split('/').filter(Boolean).map(decodeSegment);
218
+ const needed = literalDirs.length + (wildSuffix ? 1 : 0);
219
+ if (dirSegments.length < needed)
220
+ return null;
221
+ const tail = dirSegments.slice(dirSegments.length - literalDirs.length);
222
+ if (tail.some((seg, i) => seg !== decodeSegment(literalDirs[i])))
223
+ return null;
224
+ if (wildSuffix
225
+ && !dirSegments[dirSegments.length - needed].endsWith(decodeSegment(wildSuffix))) {
226
+ return null;
227
+ }
228
+ // Resolved through the URL parser rather than concatenated, so a
229
+ // filename carrying a space or other raw character is escaped the
230
+ // same way the directory around it already is. Safe against
231
+ // traversal: the checks above leave a filename with no `/`.
232
+ let href;
233
+ try {
234
+ href = new URL(filename, dir).href;
235
+ }
236
+ catch {
237
+ return null;
238
+ }
239
+ // Same reasoning as `collectionClickUrl`: a github collection fetches
240
+ // `raw.githubusercontent.com`, which serves HTML as `text/plain`, so
241
+ // the raw neighbour of a script is a plaintext dump. Humans clicking
242
+ // want the github.com page — the same URL the Location row shows.
243
+ return githubRawToBlobUrl(href) ?? href;
244
+ }
245
+ // The script's parent directory as a URL ending in `/`. Only the
246
+ // schemes a script can be loaded from — the href becomes an anchor.
247
+ function scriptDirUrl(scriptUrl) {
248
+ try {
249
+ const dir = new URL('.', scriptUrl);
250
+ if (!['http:', 'https:', 'file:'].includes(dir.protocol))
251
+ return null;
252
+ return dir;
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }
258
+ // A directory read off a URL is percent-encoded; the same directory
259
+ // written into a `@match` pattern usually isn't. Compare both decoded
260
+ // so `my dir` and `my%20dir` are the same segment.
261
+ function decodeSegment(seg) {
262
+ try {
263
+ return decodeURIComponent(seg);
264
+ }
265
+ catch {
266
+ return seg;
267
+ }
268
+ }
269
+ // Build the click-through path for a `@match` pattern. Walk segments
270
+ // left-to-right and stop at the first one containing a `*`:
271
+ //
272
+ // - Last segment, `*` at the end with a non-empty literal prefix
273
+ // (`foo*`, `ol.php?type=precip*`): keep the literal prefix
274
+ // verbatim. The user is targeting one specific page parametrised
275
+ // on the tail, so the prefix typically is the real URL minus a
276
+ // fragment and navigating to it works.
277
+ // - Otherwise (whole-segment wildcard, mid-segment wildcard, or
278
+ // wildcard segment followed by more path): trim back to the last
279
+ // clean directory boundary — a partial prefix in the middle of a
280
+ // path isn't likely to navigate, but the parent directory often
281
+ // does.
282
+ //
283
+ // Pure-literal paths (no `*` anywhere) pass through unchanged.
284
+ export function linkPathFromMatchPath(path) {
285
+ const segments = path.split('/');
286
+ const kept = [];
287
+ for (let i = 1; i < segments.length; i++) {
288
+ const seg = segments[i];
289
+ const star = seg.indexOf('*');
290
+ if (star < 0) {
291
+ kept.push(seg);
292
+ continue;
293
+ }
294
+ const isLastSegment = i === segments.length - 1;
295
+ const starAtSegmentEnd = star === seg.length - 1;
296
+ if (isLastSegment && starAtSegmentEnd && star > 0) {
297
+ kept.push(seg.slice(0, star));
298
+ return '/' + kept.join('/');
299
+ }
300
+ // Trailing slash when we kept at least one full segment; bare `/`
301
+ // when the wildcard segment was the first one.
302
+ return '/' + (kept.length ? kept.join('/') + '/' : '');
303
+ }
304
+ return '/' + kept.join('/');
305
+ }
@@ -0,0 +1,22 @@
1
+ // Which of a script's menu commands show, shared by the worker's
2
+ // registry (`gm-menu.ts`) and sm-dev's `menu` listing.
3
+ //
4
+ // As in TM: a script's commands with one caption show once in a tab,
5
+ // however many times it registered that caption (each with its own id)
6
+ // and in however many of the tab's frames. Picking the entry runs all of
7
+ // them (`menuCommandGroup` in `gm-menu.ts`). Every registration is kept,
8
+ // so unregistering one leaves the entry for the rest. The entry shows
9
+ // the caption's first registration, so the menu doesn't reorder.
10
+ //
11
+ // `key` says which commands share an entry: the caller's list may hold
12
+ // several scripts' commands, which never merge.
13
+ export function visibleCommands(list, key) {
14
+ const seen = new Set();
15
+ return list.filter((command) => {
16
+ const k = key(command);
17
+ if (seen.has(k))
18
+ return false;
19
+ seen.add(k);
20
+ return true;
21
+ });
22
+ }
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>SourceMonkey offscreen host</title>
6
+ </head>
7
+ <body>
8
+ <!--
9
+ Offscreen document: the extension's only way to get a DOM without a
10
+ visible tab. It exists solely to embed the sandboxed parse page and
11
+ relay messages to it, because the two halves each lack something the
12
+ other has:
13
+
14
+ - The service worker can call `chrome.*` but has no DOM, so it
15
+ cannot host an iframe — and its CSP forbids `eval`.
16
+ - The sandboxed page may use `eval` (so it can compile) but has an
17
+ opaque origin and no `chrome.*`, so the service worker cannot
18
+ message it directly.
19
+
20
+ This document has both a DOM and `chrome.runtime`, so it bridges
21
+ them: `chrome.runtime.onMessage` in, `postMessage` to the iframe,
22
+ and back.
23
+
24
+ The iframe is `src`-loaded rather than `srcdoc` so it resolves to the
25
+ URL declared in the manifest's `sandbox.pages` list — a srcdoc frame
26
+ would inherit this document's CSP and lose the `'unsafe-eval'` that
27
+ is the entire point.
28
+ -->
29
+ <!--
30
+ The `sandbox` attribute is belt-and-braces. The page is already
31
+ sandboxed by the manifest's `sandbox.pages` key; if that key were
32
+ ever dropped in an edit, this attribute keeps the frame from
33
+ silently becoming a fully privileged extension page with `chrome.*`
34
+ available to whatever it compiles.
35
+ -->
36
+ <iframe id="parse-sandbox" src="parse-sandbox.html" sandbox="allow-scripts"></iframe>
37
+ <script type="module" src="extension/offscreen.js"></script>
38
+ </body>
39
+ </html>