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,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,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
+ }
@@ -0,0 +1,84 @@
1
+ // What the extension and sm-dev both use. Their own module so the
2
+ // harness can name them without loading the claim code in
3
+ // sm-dev-link.ts.
4
+ /** The first argument of every event's console line. */
5
+ export const SM_DEV_EVENT = 'sm-dev-event';
6
+ /**
7
+ * Refusals of the `managed` calls (managed-changes.ts), which sm-dev
8
+ * also checks before asking for `--force`.
9
+ */
10
+ export const RUN_COLLECTION_LOCKED = 'An sm-dev run\'s test collection is changed only by its run';
11
+ export const LISTED_BY_LOCATION = 'This script is listed by a directory or manifest collection, so it stays there while its file does. Only a script in a container collection can be moved or uninstalled.';
12
+ export const CONTAINER_TARGET_ONLY = 'Scripts can only be installed or moved into a container collection';
13
+ /**
14
+ * Why a manual script (`@run-at context-menu`) can't be run on a tab
15
+ * now: `runManualScript`'s answers, which the popup logs and sm-dev
16
+ * prints after the script's name. See docs/manual-scripts.md.
17
+ */
18
+ export const MANUAL_RUN_NOT_INSTALLED = 'This script is not installed';
19
+ export const MANUAL_RUN_NOT_MANUAL = 'This script runs when a page loads. Only a script with @run-at context-menu is run on request.';
20
+ export const MANUAL_RUN_SCRIPT_OFF = 'This script is turned off';
21
+ export const MANUAL_RUN_COLLECTION_OFF = 'This script\'s collection is turned off';
22
+ export const MANUAL_RUN_NOT_HERE = 'This script\'s @match and @include rules don\'t cover this page';
23
+ export const MANUAL_RUN_PAGE = 'SourceMonkey can\'t run scripts on this page';
24
+ export const MANUAL_RUN_TAB_GONE = 'The tab was closed';
25
+ export const MANUAL_RUN_PAGE_CHANGED = 'The page changed before the script could run. Try again.';
26
+ export const MANUAL_RUN_SM_DEV = 'The sm-dev run controlling this tab keeps this script out';
27
+ /**
28
+ * The event types of the markers sm-dev puts in its browser's pages,
29
+ * which tell a `document-start` wrapper whether it can run without
30
+ * asking the extension (`smDevMarker` in userscript-prelude.js, which
31
+ * has its own copy). See docs/sm-dev-design.md#making-wrappers-ask.
32
+ * - `present`: in every page. sm-dev is connected, so any tab may be
33
+ * claimed.
34
+ * - `vouchPrefix` + the run's id: in a tab that run claims, while its
35
+ * scripts are on.
36
+ */
37
+ export const SM_DEV_MARKERS = {
38
+ present: 'sourcemonkey-sm-dev',
39
+ vouchPrefix: 'sourcemonkey-sm-dev-run:',
40
+ };
41
+ /** The reason a skipped wrapper reports for a tab an sm-dev run controls. */
42
+ export const SKIP_REASON_SM_DEV = 'tab controlled by sm-dev';
43
+ /**
44
+ * The version of the calls and events between sm-dev and the extension,
45
+ * which each side has its own copy of: sm-dev from a repo's install, the
46
+ * extension from the person's browser. A run compares them as it
47
+ * connects (extension-link.ts). See CLAUDE.md for when to bump which.
48
+ * - `major`: a change that needs both sides updated. A run refuses an
49
+ * extension whose major differs.
50
+ * - `minor`: an addition. An extension ahead is fine. An sm-dev ahead
51
+ * checks the extension's minor before using what came later
52
+ * (`ExtensionLink.requireMinor`).
53
+ */
54
+ // 1.1: `scriptsOnTab` rows carry the script's number.
55
+ // 1.2: `managedCollections` and `managedScripts`; `writeRunScript`
56
+ // takes the script's `source` file, which `managedScripts` gives back.
57
+ // 1.3: permission prompts: `connect-prompt` events, `answerConnectPrompt`.
58
+ // 1.4: a report event can carry `detail`: why a script had no decision.
59
+ // 1.5: `managed`, the calls that change collections and installed
60
+ // scripts (`managed.setScriptEnabled` and the rest), and
61
+ // `managed.scriptDetails` / `managed.scriptText` (managed-changes.ts).
62
+ // 1.6: manual scripts (`@run-at context-menu`): `runManualScript`, and
63
+ // the `sm:manual` report for one waiting in a new document.
64
+ // 1.7: a script's badge status can be `UNTRACKED`: its page loaded
65
+ // before the extension started.
66
+ // 2.0: wrappers no longer all ask while a claim is live; sm-dev marks
67
+ // the pages of its browser instead (`SM_DEV_MARKERS`), which an older
68
+ // sm-dev doesn't, so the extension would run scripts in its tabs.
69
+ // 2.1: scripts run in iframes, so report and menu events now come from
70
+ // frames too (`topFrame: false`); `fireActionContextMenuItem` takes
71
+ // the command's `documentId`.
72
+ // 2.2: `request`, `download`, `values` and a shown `notification` event
73
+ // carry the sending document's `documentId`, which says which frame.
74
+ // 2.3: `managed.scriptFiles`, a script's `@require` and `@resource`
75
+ // files as installed.
76
+ // 2.4: `managed.scriptIcons`, a script's `@icon` and its site icons.
77
+ // 3.0: every script runs in the page's world unless it asks to be
78
+ // isolated. sm-dev works out each script's world itself, for
79
+ // `eval --script` and for whose error a page error could be, so an
80
+ // older one would look in the wrong world. On a page whose CSP
81
+ // `sandbox` turns off script in its own world, the page-world scripts
82
+ // run isolated instead, which sm-dev notes rather than warning that
83
+ // they can't run.
84
+ export const SM_DEV_PROTOCOL = { major: 3, minor: 0 };
@@ -0,0 +1,177 @@
1
+ // Minimal Source Map v3 generator for the concatenated injection entry.
2
+ //
3
+ // Why: in Mode A (see docs/injection-modes.md) a script's `@require`
4
+ // bodies and its own body are concatenated into ONE `js[]` entry so
5
+ // they share a lexical scope. That's what makes a library's top-level
6
+ // `const`/`class` visible to the main body, exactly as in TM/VM — but
7
+ // it also means the main body's line N no longer lands at physical
8
+ // line N, and every require loses the devtools file it used to have.
9
+ //
10
+ // A source map buys both back: devtools splits the single generated
11
+ // script into the original files and reports original line numbers.
12
+ //
13
+ // Scope of this generator:
14
+ //
15
+ // - **Line-level only.** One mapping per generated line, always at
16
+ // column 0. We never move code within a line, so column fidelity
17
+ // would be busywork; line granularity is what stack traces and
18
+ // breakpoints need.
19
+ // - **`sourcesContent` is always embedded.** Our sources are
20
+ // synthetic `sourcemonkey://…` URLs, which devtools cannot fetch,
21
+ // so embedding the text is the only way the mapped files are
22
+ // viewable. It is also the expensive part: the sources ship a
23
+ // second time, base64'd, so the entry grows by ~1.6× their size.
24
+ //
25
+ // There is deliberately no size cap on this. The only constraint
26
+ // that actually binds is whether `chrome.userScripts.register()`
27
+ // accepts the payload, and that failure is already visible:
28
+ // `registerWithFallback` in src/extension/injector.ts drops from batch to
29
+ // per-script registration on rejection and records the reason
30
+ // against that script (which then reads REJECTED, with Chrome's
31
+ // wording), so an oversized script fails loudly and by name. A preemptive cap would trade that for silently degrading
32
+ // the feature the map exists to provide. The map does NOT touch
33
+ // our `chrome.storage.local` quota — it's generated at
34
+ // registration time from sources already stored.
35
+ //
36
+ // What V8 does NOT do for us: map `Error.stack` *strings*. It maps
37
+ // source positions for the devtools UI, but `err.stack` keeps
38
+ // generated coordinates, and SourceMonkey parses those strings for the
39
+ // viewer's Log tab (src/stack-link.ts). `remapStackRef` below is the
40
+ // other half — the Log tab applies the same segment list itself, so a
41
+ // logged frame names the same file and line devtools shows. See
42
+ // docs/script-log.md#stack-refs-are-source-mapped-first.
43
+ // Base64 VLQ, per the Source Map v3 spec.
44
+ const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
45
+ // Domain: |value| < 2^30. Above that the `<< 1` wraps negative and the
46
+ // loop emits a single wrong digit. Every caller here passes a line or
47
+ // source index, so this is unreachable — noted so nobody reuses the
48
+ // function for byte offsets.
49
+ export function encodeVlq(value) {
50
+ // Sign goes in the low bit, magnitude above it.
51
+ let vlq = value < 0 ? ((-value) << 1) | 1 : value << 1;
52
+ let out = '';
53
+ do {
54
+ let digit = vlq & 0b11111;
55
+ vlq >>>= 5;
56
+ // Continuation bit: more 5-bit groups follow.
57
+ if (vlq > 0)
58
+ digit |= 0b100000;
59
+ out += B64[digit];
60
+ } while (vlq > 0);
61
+ return out;
62
+ }
63
+ // Build the map object. `totalLines` is the generated entry's line
64
+ // count; generated lines not covered by any segment (the wrap's head
65
+ // and tail, the `;` separators) get an empty group, which devtools
66
+ // reads as "no original position" and leaves attributed to the
67
+ // generated file.
68
+ //
69
+ // `file` is deliberately the same URL as the entry's own
70
+ // `//# sourceURL=`, which is also `sources[last]` (the script's own
71
+ // body). Bundlers avoid that collision — devtools keys files by URL,
72
+ // so a mapped source sharing its generated script's URL could in
73
+ // principle show up twice, or empty. Verified in Chrome's Sources
74
+ // panel that it doesn't: the mapped file shadows the generated one,
75
+ // and a script with one `@require` shows exactly two entries — the
76
+ // `.user.js` and the require nested under a `<host>/<path>` subtree —
77
+ // both with their original text. Keeping the URLs identical is what
78
+ // lets the Log tab's stack-ref linkification match on one string.
79
+ export function buildSourceMap(args) {
80
+ const sources = args.segments.map((s) => s.url);
81
+ // generatedLine → {sourceIndex, originalLine}. Later segments win on
82
+ // overlap, which can't happen for the layouts we generate but keeps
83
+ // the function total.
84
+ const perLine = new Map();
85
+ args.segments.forEach((seg, srcIdx) => {
86
+ for (let k = 0; k < seg.lineCount; k++) {
87
+ perLine.set(seg.generatedLine + k, { srcIdx, origLine: k });
88
+ }
89
+ });
90
+ // VLQ fields after the first are deltas against the previous
91
+ // segment, carried across line groups (only the generated column
92
+ // resets per line — and ours is always 0, so it never contributes).
93
+ let prevSrcIdx = 0;
94
+ let prevOrigLine = 0;
95
+ const groups = [];
96
+ for (let gen = 0; gen < args.totalLines; gen++) {
97
+ const hit = perLine.get(gen);
98
+ if (!hit) {
99
+ groups.push('');
100
+ continue;
101
+ }
102
+ groups.push(encodeVlq(0) +
103
+ encodeVlq(hit.srcIdx - prevSrcIdx) +
104
+ encodeVlq(hit.origLine - prevOrigLine) +
105
+ encodeVlq(0));
106
+ prevSrcIdx = hit.srcIdx;
107
+ prevOrigLine = hit.origLine;
108
+ }
109
+ return {
110
+ version: 3,
111
+ file: args.file,
112
+ sources,
113
+ sourcesContent: args.segments.map((seg) => seg.content),
114
+ names: [],
115
+ mappings: groups.join(';'),
116
+ };
117
+ }
118
+ // Look up the original file + line for a 1-based generated line.
119
+ // Returns null for lines no segment covers. In 1-based terms those are
120
+ // line 1 (the wrap head), each `// @require` marker line, each `;` ASI
121
+ // guard between requires, and the closing `});`. They are ours, not
122
+ // the user's, so leaving them unmapped is correct — the caller drops
123
+ // the link rather than pointing at a neighbouring line.
124
+ export function lookupGeneratedLine(segments, generatedLine) {
125
+ const gen0 = generatedLine - 1;
126
+ for (const seg of segments) {
127
+ if (gen0 >= seg.generatedLine && gen0 < seg.generatedLine + seg.lineCount) {
128
+ return { url: seg.url, line: gen0 - seg.generatedLine + 1 };
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ // Rewrite a stack ref that points into the concatenated entry so it
134
+ // names the original `@require` (or the body) at its own line.
135
+ //
136
+ // Every frame from the combined entry carries `entryUrl` — the
137
+ // script's own `//# sourceURL=`, which V8 stamps on the whole entry
138
+ // regardless of which concatenated file the code came from. So the
139
+ // URL match is the test for "is this frame ours to remap"; refs to
140
+ // anything else (the page, another userscript, `__prelude.js`) pass
141
+ // through untouched.
142
+ //
143
+ // Columns pass through unchanged, and that is exact rather than
144
+ // approximate: composition only ever moves whole lines, never code
145
+ // within a line. A script with NO requires has no segments at all, so
146
+ // it returns early — see the layout notes in composed-source.ts for
147
+ // the column caveat that applies to that case.
148
+ export function remapStackRef(ref, entryUrl, segments) {
149
+ if (segments.length === 0)
150
+ return null;
151
+ if (ref.url !== entryUrl)
152
+ return null;
153
+ const orig = lookupGeneratedLine(segments, ref.line);
154
+ if (!orig)
155
+ return null;
156
+ return { url: orig.url, line: orig.line, col: ref.col };
157
+ }
158
+ // UTF-8 → base64. `btoa` alone throws on any code point above U+00FF,
159
+ // and userscripts are full of them (the script that prompted Mode A is
160
+ // half Chinese). Encode to bytes first, then base64 the byte string.
161
+ function base64Utf8(text) {
162
+ const bytes = new TextEncoder().encode(text);
163
+ let binary = '';
164
+ // Chunked so a large source doesn't blow the argument limit on
165
+ // String.fromCharCode.apply.
166
+ const CHUNK = 0x8000;
167
+ for (let i = 0; i < bytes.length; i += CHUNK) {
168
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
169
+ }
170
+ return btoa(binary);
171
+ }
172
+ // The `//# sourceMappingURL=` line to append to the generated entry.
173
+ // Inline data URI because a `sourcemonkey://` map URL would be no more
174
+ // fetchable than the sources themselves.
175
+ export function inlineSourceMapComment(map) {
176
+ return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Utf8(JSON.stringify(map))}`;
177
+ }