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,204 @@
1
+ // Pure helpers for the userscript viewer's link rendering. Kept
2
+ // DOM-free so the URL-detection + tag-classification rules can be
3
+ // unit tested in Node without a Prism-shaped DOM.
4
+ //
5
+ // Two consumers in src/extension/userscript.ts:
6
+ // - The Details panel renderer (Match / Location / etc.).
7
+ // - The post-Prism walk over the highlighted code box.
8
+ // Both call the same classifiers so a header line in the body is
9
+ // linkified by the same rules as the corresponding details row.
10
+ // Tags whose value is a single resource reference. The body-linkifier
11
+ // passes the value through file-loader's `resolveScriptResource`,
12
+ // which both linkifies absolute http(s) URLs and resolves relative
13
+ // paths against the script's own URL (the same rule the `@icon`
14
+ // favicon path uses). Anything the resolver rejects falls
15
+ // back to plain text.
16
+ //
17
+ // `@resource` is *not* in this set even though its second token is a
18
+ // URL — its grammar is two args (`<name> <url>`), so it gets its own
19
+ // carve-out in `linkForHeaderTag` (src/extension/userscript.ts) that linkifies
20
+ // only the URL portion. Treating it here would mis-resolve
21
+ // `myCSS https://example.com/x.css` as a single relative path
22
+ // `<parent>/myCSS%20https://...`.
23
+ export const RESOLVABLE_HEADER_TAGS = new Set([
24
+ 'icon', 'iconurl', 'defaulticon', 'icon64', 'icon64url',
25
+ 'require',
26
+ 'updateurl', 'downloadurl',
27
+ 'homepage', 'homepageurl', 'website', 'source',
28
+ 'supporturl',
29
+ ]);
30
+ // Tags whose value is shaped like a Chrome `@match` pattern. Formally
31
+ // `@include` and `@exclude` are free-form globs over the whole URL
32
+ // (or `/regex/` literals), but in practice almost every excluded /
33
+ // included pattern is written as a match pattern, and giving the
34
+ // user a click target is more useful than rendering plain text.
35
+ // `matchTargetInfo` returns null for shapes it can't dewildcard
36
+ // (regex literals, `<all_urls>`, host wildcards) and those still
37
+ // fall back to plain text.
38
+ export const MATCH_HEADER_TAGS = new Set([
39
+ 'match', 'exclude', 'include',
40
+ ]);
41
+ // Word-boundary anchor keeps `https://…` inside a longer identifier
42
+ // (rare but possible inside a string literal) from being picked up
43
+ // mid-token. The character class excludes whitespace, quotes, angle
44
+ // brackets, and backticks so a URL inside `"…"` or `<…>` stops at
45
+ // the delimiter. `*` is also excluded so a `@match` / `@exclude`
46
+ // pattern like `https://example.com/athletic/*` yields a clickable
47
+ // `https://example.com/athletic/` followed by a literal `*` — `*` is
48
+ // technically a legal URL character but in userscript headers it's
49
+ // always a glob wildcard, and including it in the href produces a
50
+ // link that doesn't navigate anywhere useful.
51
+ const URL_RE = /\b(?:https?|file):\/\/[^\s'"<>`*]+/g;
52
+ // Trailing punctuation that's commonly adjacent to URLs in prose.
53
+ // `)` and `]` are conditional (see peelTrailingPunctuation) so a
54
+ // URL with balanced brackets like `…/Foo_(disambig)` keeps them.
55
+ const ALWAYS_PEEL_RE = /[.,;:!?]+$/;
56
+ export function splitTextByUrls(text) {
57
+ const out = [];
58
+ let cursor = 0;
59
+ for (const m of text.matchAll(URL_RE)) {
60
+ const start = m.index ?? 0;
61
+ if (start > cursor) {
62
+ out.push({ kind: 'text', value: text.slice(cursor, start) });
63
+ }
64
+ const { url, trailing } = peelTrailingPunctuation(m[0]);
65
+ out.push({ kind: 'url', value: url });
66
+ if (trailing)
67
+ out.push({ kind: 'text', value: trailing });
68
+ cursor = start + m[0].length;
69
+ }
70
+ if (cursor < text.length) {
71
+ out.push({ kind: 'text', value: text.slice(cursor) });
72
+ }
73
+ return out;
74
+ }
75
+ // Peel trailing prose punctuation off a candidate URL. Closing
76
+ // brackets `)` and `]` only peel when the URL has no matching open
77
+ // bracket — so `(https://example.com/x)` peels the `)` (likely
78
+ // surrounding parens in prose) but `https://en.wikipedia.org/wiki/Foo_(disambig)`
79
+ // keeps it (URL contains its own `(`). Always-peelable chars
80
+ // (`.,;:!?`) come off first.
81
+ function peelTrailingPunctuation(raw) {
82
+ let url = raw;
83
+ let trailing = '';
84
+ // Always-peelable run first; close-brackets one-at-a-time after,
85
+ // since a single URL might end in `…)?` and we want both gone.
86
+ let progressed = true;
87
+ while (progressed) {
88
+ progressed = false;
89
+ const m = ALWAYS_PEEL_RE.exec(url);
90
+ if (m) {
91
+ trailing = m[0] + trailing;
92
+ url = url.slice(0, url.length - m[0].length);
93
+ progressed = true;
94
+ }
95
+ const last = url.charAt(url.length - 1);
96
+ if ((last === ')' && !url.includes('(')) ||
97
+ (last === ']' && !url.includes('['))) {
98
+ trailing = last + trailing;
99
+ url = url.slice(0, url.length - 1);
100
+ progressed = true;
101
+ }
102
+ }
103
+ return { url, trailing };
104
+ }
105
+ // Trailing-whitespace class includes `\r` so a CRLF-encoded line
106
+ // that reaches us with a stray `\r` at the end still matches. The
107
+ // rest of the SourceMonkey pipeline appears LF-normalised but the
108
+ // regex stays cheap, and the `\r` fallback keeps `parseTagLine`
109
+ // robust against any future input that bypasses normalisation.
110
+ const TAG_LINE_RE = /^([ \t]*\/\/[ \t]*@([A-Za-z][\w-]*)(?::([\w-]+))?[ \t]+)(\S.*?)([ \t\r]*)$/;
111
+ export function parseTagLine(line) {
112
+ const m = TAG_LINE_RE.exec(line);
113
+ if (!m)
114
+ return null;
115
+ const [, before, tag, locale, value, after] = m;
116
+ const out = {
117
+ before,
118
+ tag: tag.toLowerCase(),
119
+ value,
120
+ after,
121
+ };
122
+ if (locale)
123
+ out.locale = locale;
124
+ return out;
125
+ }
126
+ export function splitResourceValue(value) {
127
+ const gap = value.search(/[ \t]/);
128
+ if (gap < 0)
129
+ return null;
130
+ const afterGap = value.slice(gap);
131
+ const gapLen = afterGap.match(/^[ \t]+/)[0].length;
132
+ const urlStart = gap + gapLen;
133
+ const rest = value.slice(urlStart);
134
+ const trail = rest.search(/[ \t]/);
135
+ const urlEnd = trail < 0 ? value.length : urlStart + trail;
136
+ const url = value.slice(urlStart, urlEnd);
137
+ if (!url)
138
+ return null;
139
+ return {
140
+ prefix: value.slice(0, urlStart),
141
+ url,
142
+ suffix: value.slice(urlEnd),
143
+ };
144
+ }
145
+ // Build a click-through URL for one `@connect` value. The
146
+ // body-linkifier wraps the verbatim value text in an anchor pointing
147
+ // here; the anchor's `href` is normalised via the URL parser so the
148
+ // click goes somewhere navigable even when the header value was just
149
+ // a bare host.
150
+ //
151
+ // Rules:
152
+ // - `*` and `self` → null (no single site to point at).
153
+ // - `<scheme>://…` → verbatim URL (only http/https accepted).
154
+ // - `*.example.com` → `https://example.com/` (dewildcarded apex,
155
+ // matching the same rule `matchTargetInfo` applies for @match
156
+ // subdomain wildcards).
157
+ // - bare host (`example.com`, `localhost`, `1.2.3.4`) →
158
+ // `https://<host>/`.
159
+ // - Anything that doesn't parse as a URL after that → null.
160
+ //
161
+ // HTTPS is the assumed default for bare hosts even though the
162
+ // userscript may issue HTTP requests via GM_xmlhttpRequest — the
163
+ // click-through is just "visit this site to look it up", and
164
+ // HTTPS is the right default for that.
165
+ export function connectLinkUrl(raw) {
166
+ const t = raw.trim();
167
+ if (!t || t === '*')
168
+ return null;
169
+ if (t.toLowerCase() === 'self')
170
+ return null;
171
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) {
172
+ try {
173
+ const u = new URL(t);
174
+ if (u.protocol === 'http:' || u.protocol === 'https:') {
175
+ return u.toString();
176
+ }
177
+ return null;
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ }
183
+ let host = t;
184
+ if (host.startsWith('*.'))
185
+ host = host.slice(2);
186
+ if (!host || host.includes('*'))
187
+ return null;
188
+ try {
189
+ return new URL(`https://${host}/`).toString();
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
195
+ // Sentinel line shapes that bracket the metadata block. Used by the
196
+ // body-linkifier to flip its "inside header" state.
197
+ const HEADER_OPEN_RE = /^[ \t]*\/\/[ \t]*==UserScript==[ \t]*$/;
198
+ const HEADER_CLOSE_RE = /^[ \t]*\/\/[ \t]*==\/UserScript==[ \t]*$/;
199
+ export function isHeaderOpenLine(line) {
200
+ return HEADER_OPEN_RE.test(line);
201
+ }
202
+ export function isHeaderCloseLine(line) {
203
+ return HEADER_CLOSE_RE.test(line);
204
+ }
@@ -0,0 +1,8 @@
1
+ export declare const ROOT = "sourcemonkey://SourceMonkey";
2
+ export declare function buildSourceUrl(logLabel: string): string;
3
+ export declare function buildConfigSourceUrl(logLabel: string): string;
4
+ export declare function buildSealSourceUrl(logLabel: string): string;
5
+ export declare const PRELUDE_SOURCE_URL = "sourcemonkey://SourceMonkey/__prelude.js";
6
+ export declare const BRIDGE_SOURCE_URL = "sourcemonkey://SourceMonkey/__bridge.js";
7
+ export declare const GM_RUNTIME_SOURCE_URL = "sourcemonkey://SourceMonkey/__gm-runtime.js";
8
+ export declare function buildRequireSourceUrl(logLabel: string, requireUrl: string): string;
@@ -0,0 +1,214 @@
1
+ // Canonical `sourcemonkey://SourceMonkey/...` URLs the injector
2
+ // emits as `//# sourceURL=` directives, and that the viewer matches
3
+ // stack-trace refs against.
4
+ //
5
+ // Why this URL shape:
6
+ //
7
+ // - The `sourcemonkey:` scheme is arbitrary — it just has to be
8
+ // URL-shaped, unique to us (the wrapper's async-error filter
9
+ // greps stack frames for it), and recognizable in stack traces.
10
+ // - The literal `SourceMonkey` host puts everything we register
11
+ // under a single top folder in devtools' Sources tree. Devtools
12
+ // strips the scheme and uses the URL's authority as the first
13
+ // visible folder; without the host slot, our scripts would
14
+ // scatter at the top level.
15
+ // - The next folder is the script's human-readable log label
16
+ // (`bar`, `bar%232`, etc.) — see `script-log-id.ts`. We use the
17
+ // log label rather than the script's UUID because the UUID is a
18
+ // debug-grade hash that's useless when you're scanning the tree.
19
+ // Per-script artifacts (`__config.js`, the main body, each
20
+ // `@require`) live under that folder so they cluster together
21
+ // by script.
22
+ // - Page-wide URLs (`__bridge.js`, `__gm-runtime.js`, `__prelude.js`)
23
+ // live directly under `SourceMonkey/`. The bridge and GM runtime
24
+ // are true singletons (one chrome.userScripts registration each);
25
+ // the prelude is inlined per-script but every inclusion uses the
26
+ // same `__prelude.js` URL so devtools' Sources tree dedups them
27
+ // into a single visible entry.
28
+ //
29
+ // Why we percent-encode the label: it is a *display* string
30
+ // (`script-log-id.ts` decodes URL-derived filenames so the user sees
31
+ // `My Script.user.js`), and several of the characters that survive
32
+ // that decode would break a URL. A bare `#` is the fragment
33
+ // separator, so the `#N` dedup suffix would split the path; a space
34
+ // or a paren ends the URL run that `stack-link.ts` matches, so a
35
+ // stack-trace ref would stop linkifying to its tab. Everything
36
+ // outside an unreserved-character whitelist is re-encoded — devtools
37
+ // shows the encoded form, same as it always has. The viewer's Log tab
38
+ // decodes it back for display (`displayRefText` in `stack-link.ts`),
39
+ // so the two disagree on purpose: devtools indexes the file under
40
+ // `main%232`, the Log tab reads `main#2` and keeps the wire form in a
41
+ // tooltip.
42
+ // Exported so consumers can recognize our own URLs by the exact prefix
43
+ // rather than by the scheme alone — the scheme is unregistered, so a
44
+ // hostile script can put `sourcemonkey://anything/…` in a log message
45
+ // and have it parsed as a ref.
46
+ export const ROOT = 'sourcemonkey://SourceMonkey';
47
+ // Strip characters that would break out of the `//# sourceURL=<url>`
48
+ // comment these URLs are spliced into. The log label is derived from
49
+ // the on-disk filename (`script-log-id.ts`), which is attacker-
50
+ // controlled for an untrusted script bundle. A JS line terminator
51
+ // (LF/CR/U+2028/U+2029) ends the single-line `//` comment, so anything
52
+ // after it on the line would become live code in the config blob —
53
+ // which runs at document_start, before the script's own gating. Other
54
+ // control chars can't break the comment but have no business in a
55
+ // devtools tree path either; replace the lot with `_`. (The @require
56
+ // URL builder already does per-segment whitelisting; this covers the
57
+ // main/config/prelude labels that don't go through it.)
58
+ function sanitizeLabel(label) {
59
+ // eslint-disable-next-line no-control-regex
60
+ return label.replace(/[\u0000-\u001f\u007f\u2028\u2029]/g, '_');
61
+ }
62
+ // Percent-encode everything outside the URL unreserved set, so the
63
+ // segment is safe in a path AND carries nothing that would terminate
64
+ // the stack-trace ref pattern in `stack-link.ts`. Applied last, after
65
+ // the `.user.js` / `#N` surgery below has matched against the literal
66
+ // characters.
67
+ // The `u` flag matters: without it the character class matches single
68
+ // UTF-16 code units, so an emoji (surrogate pair — GreasyFork names
69
+ // are full of them) would be handed to encodeURIComponent half at a
70
+ // time and throw URIError on the lone surrogate.
71
+ function encodeSegment(segment) {
72
+ return segment.replace(/[^A-Za-z0-9._~-]/gu, (c) => {
73
+ let enc;
74
+ try {
75
+ enc = encodeURIComponent(c);
76
+ }
77
+ catch {
78
+ // An UNPAIRED surrogate — `u` makes the class match it as a
79
+ // single code point, but there's no UTF-8 encoding for it and
80
+ // encodeURIComponent throws. A manifest entry is user-authored
81
+ // JSON, so `"\ud800.user.js"` really can reach us. Letting the
82
+ // throw escape would reject the whole `Promise.all` in
83
+ // collectActiveSpecs and register NOTHING.
84
+ return '_';
85
+ }
86
+ // encodeURIComponent passes `!'()*` through untouched, and parens
87
+ // are exactly what would truncate a `at fn (URL:L:C)` frame — so
88
+ // hex-encode anything it declined to. All such characters are
89
+ // single-byte ASCII.
90
+ return enc === c ? `%${c.charCodeAt(0).toString(16).toUpperCase()}` : enc;
91
+ });
92
+ }
93
+ // Folder name for a script's artifacts. Strips the conventional
94
+ // `.user.js` extension from the log label (the folder sits ABOVE the
95
+ // main-body file, so the `.js` looks misleading on a folder), then
96
+ // encodes what's left — which is where the `#E4` script-number suffix
97
+ // (or an unnumbered script's `#N`) becomes `%23E4`.
98
+ //
99
+ // Examples:
100
+ // `foo.user.js` → `foo`
101
+ // `foo.user.js#E4` → `foo%23E4`
102
+ // `foo.user.js#2` → `foo%232`
103
+ // `My foo.user.js` → `My%20foo`
104
+ // `foo` → `foo` (no .user.js to strip)
105
+ function folderForLabel(label) {
106
+ return encodeSegment(sanitizeLabel(label).replace(/\.user\.js(?=$|#)/, ''));
107
+ }
108
+ // Main-body file basename for a script. Same as the folder but with
109
+ // the suffix dropped and `.user.js` restored — so the file's name
110
+ // reflects the script's natural filename without the disambiguator
111
+ // (each script lives in a different folder anyway).
112
+ //
113
+ // Examples:
114
+ // `foo.user.js` → `foo.user.js`
115
+ // `foo.user.js#E4` → `foo.user.js`
116
+ // `foo.user.js#2` → `foo.user.js`
117
+ // `foo` → `foo.user.js`
118
+ function mainBaseForLabel(label) {
119
+ // Suffix first, extension second. The other order would eat a `#2`
120
+ // that's part of the name itself (`Chapter #2.user.js`) — which a
121
+ // decoded `%23` now makes reachable.
122
+ const base = sanitizeLabel(label).replace(/#E?\d+$/, '').replace(/\.user\.js$/, '');
123
+ return encodeSegment(base) + '.user.js';
124
+ }
125
+ // Per-script folder root.
126
+ function scriptRoot(logLabel) {
127
+ return `${ROOT}/${folderForLabel(logLabel)}`;
128
+ }
129
+ // Canonical URL for the main script body.
130
+ export function buildSourceUrl(logLabel) {
131
+ return `${scriptRoot(logLabel)}/${mainBaseForLabel(logLabel)}`;
132
+ }
133
+ // Canonical URL for the per-script config blob — the tiny {code}
134
+ // entry the injector emits as the first js[] entry.
135
+ export function buildConfigSourceUrl(logLabel) {
136
+ return `${scriptRoot(logLabel)}/__config.js`;
137
+ }
138
+ // Canonical URL for the per-script parse-failure sentinel — the last
139
+ // js[] entry. Distinct from the config blob's URL on purpose: devtools
140
+ // dedups Sources-tree nodes by URL, which is safe for the prelude only
141
+ // because every copy of it is byte-identical. The sentinel and the
142
+ // config blob are different code, so sharing a URL would show one node
143
+ // whose text matches just one of them.
144
+ export function buildSealSourceUrl(logLabel) {
145
+ return `${scriptRoot(logLabel)}/__seal.js`;
146
+ }
147
+ // Canonical URL for the prelude. Bytes (WRAPPER_PRELUDE) are
148
+ // byte-identical for every script, so every per-script inclusion
149
+ // tags itself with this same URL — devtools' Sources tree groups
150
+ // the N inclusions into a single visible entry alongside the
151
+ // bridge / GM runtime at the SourceMonkey/ top level. See
152
+ // wrapPreludeEntry in injector.ts.
153
+ export const PRELUDE_SOURCE_URL = `${ROOT}/__prelude.js`;
154
+ // Canonical URL for the page-wide MAIN-world bridge.
155
+ export const BRIDGE_SOURCE_URL = `${ROOT}/__bridge.js`;
156
+ // Canonical URL for the page-wide GM_* runtime.
157
+ export const GM_RUNTIME_SOURCE_URL = `${ROOT}/__gm-runtime.js`;
158
+ // Canonical URL for one of a script's `@require` entries. We map
159
+ // the require's real fetch URL into our synthetic scheme so:
160
+ // - the entry clusters with its owning script in devtools, and
161
+ // - stack frames are honest about the fact that we stored a
162
+ // snapshot of the bytes (we're not re-fetching at run time).
163
+ //
164
+ // Shape: `sourcemonkey://SourceMonkey/<folder>/<host>/<path>` — the
165
+ // original URL's host + path become subfolders so the user can still
166
+ // see the provenance in the Sources tree. Query strings and fragments
167
+ // are dropped (the cached bytes don't depend on them). Two requires
168
+ // on the same script with the same host+path but different `?…`
169
+ // query strings would land at the same synthetic URL. The viewer's
170
+ // tab lookup takes the FIRST match, so a mapped stack frame from the
171
+ // second require opens the first require's tab — the source map picks
172
+ // the right segment, only the tab lookup is ambiguous. Rare in
173
+ // practice; the file-loader already dedups by canonical URL with
174
+ // query, so it'd take two legitimately different `@require` lines
175
+ // pointing at distinct stored copies. Worth revisiting if it ever
176
+ // shows up: including a hash of the query in the path would fix it at
177
+ // the cost of an uglier devtools tree.
178
+ //
179
+ // `file://` requires (resolved local files) use the synthetic host
180
+ // `file` so they land under `<folder>/file/<path>` rather than
181
+ // dropping out of the tree.
182
+ //
183
+ // On a URL we can't parse (shouldn't happen — the file-loader
184
+ // validates before storage) we fall back to `<folder>/require` so we
185
+ // still emit *something* parseable.
186
+ export function buildRequireSourceUrl(logLabel, requireUrl) {
187
+ let host;
188
+ let path;
189
+ try {
190
+ const u = new URL(requireUrl);
191
+ host = u.protocol === 'file:' ? 'file' : u.hostname;
192
+ if (!host)
193
+ host = u.protocol.replace(/:$/, '');
194
+ path = u.pathname.replace(/^\/+/, '');
195
+ }
196
+ catch {
197
+ return `${scriptRoot(logLabel)}/require`;
198
+ }
199
+ // Sanitize each path segment. `new URL()` above already normalized
200
+ // `a/../b` to `b`, so the `..` / `.` filter is belt-and-suspenders
201
+ // — kept in case the input ever reaches us by a path that bypasses
202
+ // URL parsing (it doesn't today). The real work here is the
203
+ // per-segment character whitelist, which keeps spaces / special
204
+ // chars from confusing the devtools tree.
205
+ const cleanPath = path
206
+ .split('/')
207
+ .map((seg) => seg.replace(/[^a-zA-Z0-9._-]/g, '_'))
208
+ .filter((seg) => seg && seg !== '.' && seg !== '..')
209
+ .join('/');
210
+ const safeHost = host.replace(/[^a-zA-Z0-9._-]/g, '_') || 'require';
211
+ return cleanPath
212
+ ? `${scriptRoot(logLabel)}/${safeHost}/${cleanPath}`
213
+ : `${scriptRoot(logLabel)}/${safeHost}/require`;
214
+ }
@@ -0,0 +1,6 @@
1
+ import { type ParsedHeader } from './userscript-header.js';
2
+ import type { StoredScript } from './script-build.js';
3
+ export type InjectionWorld = 'MAIN' | 'USER_SCRIPT';
4
+ export declare function resolveWorld(header: ParsedHeader | null | undefined): InjectionWorld;
5
+ export declare function resolveScriptWorld(script: Pick<StoredScript, 'header'>): InjectionWorld;
6
+ export declare function runsSandboxed(script: Pick<StoredScript, 'header'>): boolean;
@@ -0,0 +1,65 @@
1
+ // Which execution world a script runs in, from its header. Its own
2
+ // module so the harness can ask without loading the injector.
3
+ import { getValue, getValues } from './userscript-header.js';
4
+ import { resolveGrants } from './gm-api-catalog.js';
5
+ // Decide which execution world a script should run in. Like
6
+ // Tampermonkey and Violentmonkey, every script starts in the page's own
7
+ // world (MAIN) and leaves it only when its header asks:
8
+ //
9
+ // - `@inject-into page` → MAIN
10
+ // - `@inject-into content` → USER_SCRIPT (isolated)
11
+ // - `@sandbox raw` → MAIN
12
+ // - `@sandbox JavaScript` → MAIN (TM's choice on Chrome too; only
13
+ // Firefox has a separate world for it)
14
+ // - `@sandbox DOM` → USER_SCRIPT
15
+ // - anything else, unset, `@inject-into auto` → MAIN
16
+ //
17
+ // `@inject-into` (VM's) is read first when a script has both.
18
+ //
19
+ // Why the page by default: scripts written against TM and VM read the
20
+ // page's globals (its `$`, an app's state, `customElements`) without
21
+ // `unsafeWindow`, since there they are in the page. In USER_SCRIPT world
22
+ // those read as `undefined` and the script silently breaks. Any script
23
+ // but a `@grant none` one still keeps its writes off the page's
24
+ // `window`: it runs in a sandbox `window` (`runsSandboxed` below).
25
+ //
26
+ // What the page costs a script, and why `@inject-into content` exists:
27
+ // see docs/injection.md, "Execution world".
28
+ //
29
+ // `@inject-into content` with `unsafeWindow` (by grant or by use) gets
30
+ // that world's own `window` as `unsafeWindow`, matching Violentmonkey's
31
+ // content mode, plus a startup-log warning; see `extractStartupLogs`.
32
+ export function resolveWorld(header) {
33
+ if (!header)
34
+ return 'MAIN';
35
+ const injectInto = (getValue(header, 'inject-into') ?? '').trim().toLowerCase();
36
+ if (injectInto === 'page')
37
+ return 'MAIN';
38
+ if (injectInto === 'content')
39
+ return 'USER_SCRIPT';
40
+ const sandbox = (getValue(header, 'sandbox') ?? '').trim().toLowerCase();
41
+ if (sandbox === 'dom')
42
+ return 'USER_SCRIPT';
43
+ return 'MAIN';
44
+ }
45
+ // The world for a stored row, the form every production caller uses:
46
+ // the spec build, the match index and the reload-swap world check all
47
+ // have to agree.
48
+ export function resolveScriptWorld(script) {
49
+ return resolveWorld(script.header);
50
+ }
51
+ // Whether the script's user program runs inside a sandbox `window`
52
+ // (the prelude's `sandbox`): a page-world script, unless it has
53
+ // `@grant none`. A script with no `@grant` line at all is sandboxed
54
+ // too, as Tampermonkey 5.5.0 and Violentmonkey 2.49.0 run it
55
+ // (measured). That's how both run a script in the page: bare names
56
+ // reach the page's globals, but the sandbox `window` shows only the
57
+ // browser's built-ins, and the script's own writes, a `@require`d
58
+ // library's `window.jQuery = …` included, stay off the page's
59
+ // `window`. A `@grant none` script gets the page's `window` itself, as
60
+ // under both. See docs/injection.md, "The sandbox window".
61
+ export function runsSandboxed(script) {
62
+ if (!script.header || resolveScriptWorld(script) !== 'MAIN')
63
+ return false;
64
+ return !resolveGrants(getValues(script.header, 'grant')).hasNone;
65
+ }