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,601 @@
1
+ // Per-script `GM_*` / `GM.*` binding emitter.
2
+ //
3
+ // The injector calls `emitGmBindings` once per script at wrapper-build
4
+ // time. The result is `{ js, prelude, granted }`:
5
+ // - `js` holds the `const GM_X = ...` declarations plus the eager
6
+ // calls that need nothing but `__smReg.gm` and baked
7
+ // literals (`urlchange.enable`, `windowControl.enable` / `.make`,
8
+ // `resource.init`). It is used
9
+ // TWICE: `wrapCombinedEntry` splices it into the main-body wrap
10
+ // function ahead of the body, and `buildSwapEvalBody` prepends it
11
+ // to the run-new swap-eval body. Anything a hot-swapped script
12
+ // needs has to be in here.
13
+ // - `prelude` is spliced into the main-body wrap function only,
14
+ // ahead of the body, and holds the eager calls that reference
15
+ // identifiers only that function has (today: `storage.init`,
16
+ // which reads `__smDecision.gmValues`). The swap path runs its
17
+ // own equivalent from `userscript-prelude.js`. See
18
+ // `GmBindingEmit` below.
19
+ // - Both run after the prelude's awaitDecision + waitForRunAt
20
+ // gates, so the GM runtime is installed by the time either does.
21
+ // When no APIs are granted (or the script is `@grant none`),
22
+ // `emitGmBindings` returns `{ js: '', prelude: '', granted: [] }`.
23
+ //
24
+ // Topology recap:
25
+ // - `js` reaches everything through `__smReg`, the world's registry,
26
+ // which only code holding the channel key can get (channel.ts);
27
+ // the injector puts it in scope. The runtime is `__smReg.gm`.
28
+ // - SYNC API bodies delegate to `__smReg.gm.<method>(...)`,
29
+ // defined by src/wrapper-gm-runtime.js (a separately-registered
30
+ // USER_SCRIPT-world script). The runtime is shared across all
31
+ // userscripts in the document.
32
+ // - ASYNC API bodies route through `__smReg.gm.sendAsync(...)`
33
+ // which wraps chrome.runtime.sendMessage so a sibling userscript
34
+ // reassigning chrome.runtime can't break us.
35
+ // - GM_info is purely compile-time — the script's metadata is
36
+ // baked into the wrapper as a literal object.
37
+ // - The `GM` object (GM4 alias namespace) is built up alongside.
38
+ //
39
+ // Compile-in shape contract: the set of bindings emitted and the
40
+ // call-site shape (`__smReg.gm.<method>(...)` sync, `sendAsync` async)
41
+ // is stable, so landing a new API changes only its handler.
42
+ import { findApi, GM_APIS } from '../gm-api-catalog.js';
43
+ // Single-value headers exposed verbatim on `GM_info.script`, under
44
+ // the keys TM uses. Shared with the injector so the two sides agree.
45
+ export const GM_INFO_PASSTHROUGH_TAGS = [
46
+ 'author', 'homepage', 'icon', 'icon64', 'supportURL', 'updateURL',
47
+ 'downloadURL', 'copyright', 'license',
48
+ ];
49
+ // The name `js` declares a sandboxed script's `window.<method>`
50
+ // replacement under.
51
+ export function windowMethodBinding(method) {
52
+ return `__smWin_${method}`;
53
+ }
54
+ // JSON-encode but inside a JavaScript source context: the standard
55
+ // `JSON.stringify` output is valid JS source for any value our caller
56
+ // would pass in (primitives, arrays of strings, nested objects of
57
+ // primitives). We add one safety wrap — the `</script>` sequence
58
+ // breaks out of an inline <script> if the wrapper is ever serialized
59
+ // into one; we don't currently do that (chrome.userScripts takes raw
60
+ // code), but the extra escape is cheap insurance.
61
+ function jsLit(value) {
62
+ return JSON.stringify(value).replace(/<\/script>/gi, '<\\/script>');
63
+ }
64
+ // Build the literal source for GM_info. Baked at compile time — no
65
+ // runtime lookup, no SW round-trip. Strict-mode safe (no `with`).
66
+ function emitGmInfo(info) {
67
+ // `script.grant` is the canonicalized set we actually emitted
68
+ // bindings for — so `@grant GM.setValue` shows up here as
69
+ // `GM_setValue`, and unknown / typoed names are dropped. This
70
+ // matches VM's behavior (TM exposes the raw declared form). Worth
71
+ // revisiting if real compat reports show up. `matches` / `includes`
72
+ // / `excludes` pass through verbatim.
73
+ //
74
+ // Key names: where TM and VM disagree (`run-at` vs `runAt`,
75
+ // `connects` vs `connect`) we use whichever matches the header tag.
76
+ const scriptObject = {
77
+ // Passthrough first so the canonical keys below always win.
78
+ ...info.passthrough,
79
+ name: info.name,
80
+ version: info.version ?? '',
81
+ namespace: info.namespace ?? '',
82
+ description: info.description ?? '',
83
+ matches: info.matches.slice(),
84
+ includes: info.includes.slice(),
85
+ excludes: info.excludes.slice(),
86
+ grant: info.grants.slice(),
87
+ 'run-at': info.runAt ?? '',
88
+ require: info.requires.slice(),
89
+ resources: info.resourceList.map((r) => ({ name: r.name, url: r.url })),
90
+ connect: info.connects.slice(),
91
+ noframes: info.noframes,
92
+ };
93
+ const gmInfo = {
94
+ script: scriptObject,
95
+ scriptHandler: 'SourceMonkey',
96
+ version: info.scriptHandlerVersion,
97
+ scriptMetaStr: info.scriptMetaStr,
98
+ uuid: info.scriptId,
99
+ // Every SourceMonkey script is re-read from its collection source
100
+ // on refresh, so from the script's point of view it will update.
101
+ scriptWillUpdate: true,
102
+ userAgentData: info.userAgentData,
103
+ };
104
+ // Not frozen, as in TM and VM: a script may add to its own copy.
105
+ return `const GM_info = ${jsLit(gmInfo)};`;
106
+ }
107
+ // Build a single GM_x binding statement.
108
+ //
109
+ // SYNC bodies delegate to __smReg.gm.<subsystem>.<verb>(scriptId, ...args).
110
+ //
111
+ // ASYNC bodies build a message envelope and route it through
112
+ // __smReg.gm.sendAsync. The call-site shape is stable across phases —
113
+ // landing a new API only changes the SW-side dispatch.
114
+ function emitOneBinding(spec, scriptId) {
115
+ // unsafeWindow is a passthrough binding, not a function — the same
116
+ // `const unsafeWindow = window;` in both worlds, because what makes
117
+ // it correct is the WORLD, not the binding.
118
+ //
119
+ // - MAIN world (the default): `window` IS the page's window, so
120
+ // this is the real thing. It's emitted outside the sandbox
121
+ // `window` a sandboxed script gets, so it isn't the sandbox.
122
+ // - USER_SCRIPT world: reachable only when the script declared
123
+ // `@inject-into content` or `@sandbox DOM`, i.e. asked for isolation.
124
+ // `window` is then the isolated realm's, NOT the page's — matching
125
+ // Violentmonkey's content mode. `extractStartupLogs` emits a
126
+ // warning for that combination so it isn't a silent wrong answer.
127
+ //
128
+ // No cross-world proxy is possible: property access on unsafeWindow is
129
+ // synchronous and Chrome exposes no synchronous channel between an
130
+ // isolated world and the page's realm.
131
+ if (spec.subsystem === 'unsafe-window') {
132
+ return `const unsafeWindow = window;`;
133
+ }
134
+ // `window.onurlchange` isn't a callable and isn't a legal binding
135
+ // name — the grant's whole effect is the eager `urlchange.enable`
136
+ // call emitted at the end of `js` (see emitGmBindings).
137
+ if (spec.subsystem === 'url-change') {
138
+ return '';
139
+ }
140
+ // `window.close` / `window.focus` are methods we replace on the
141
+ // script's `window`, not new names — same deal: the grant's effect is
142
+ // the eager `windowControl` call emitted at the end of `js`.
143
+ if (spec.subsystem === 'window-control') {
144
+ return '';
145
+ }
146
+ if (spec.name === 'GM_info') {
147
+ // GM_info is emitted separately by emitGmInfo. Skip here.
148
+ return '';
149
+ }
150
+ // Storage subsystem: route to the page-side cache + batched-write
151
+ // helpers on `__smReg.gm.storage`. The scriptId literal is closed
152
+ // over so the runtime can key per-script state.
153
+ if (spec.subsystem === 'storage') {
154
+ switch (spec.name) {
155
+ case 'GM_getValue':
156
+ return `const GM_getValue = function (key, defaultValue) { return __smReg.gm.storage.get(${jsLit(scriptId)}, key, defaultValue); };`;
157
+ case 'GM_setValue':
158
+ return `const GM_setValue = function (key, value) { return __smReg.gm.storage.set(${jsLit(scriptId)}, key, value); };`;
159
+ case 'GM_deleteValue':
160
+ return `const GM_deleteValue = function (key) { return __smReg.gm.storage.del(${jsLit(scriptId)}, key); };`;
161
+ case 'GM_listValues':
162
+ return `const GM_listValues = function () { return __smReg.gm.storage.list(${jsLit(scriptId)}); };`;
163
+ }
164
+ }
165
+ // Storage-listener subsystem: addValueChangeListener / removeValueChangeListener
166
+ // are page-side, no SW round-trip. Listeners fire from local
167
+ // sets + deletes AND from remote broadcasts.
168
+ if (spec.subsystem === 'storage-listener') {
169
+ switch (spec.name) {
170
+ case 'GM_addValueChangeListener':
171
+ return `const GM_addValueChangeListener = function (key, callback) { return __smReg.gm.storage.addValueChangeListener(${jsLit(scriptId)}, key, callback); };`;
172
+ case 'GM_removeValueChangeListener':
173
+ return `const GM_removeValueChangeListener = function (listenerId) { return __smReg.gm.storage.removeValueChangeListener(${jsLit(scriptId)}, listenerId); };`;
174
+ }
175
+ }
176
+ // DOM subsystem: in-page DOM mutations, no per-script state needed
177
+ // beyond what the bodies pass through. Exempt from the page's CSP
178
+ // in USER_SCRIPT world (scripts follow the world CSP instead; see
179
+ // ensureWorldConfigured in injector.ts).
180
+ if (spec.subsystem === 'dom') {
181
+ switch (spec.name) {
182
+ case 'GM_addStyle':
183
+ return `const GM_addStyle = function (css) { return __smReg.gm.dom.addStyle(${jsLit(scriptId)}, css); };`;
184
+ case 'GM_addElement':
185
+ return `const GM_addElement = function (parentOrTag, tagOrAttrs, attrs) { return __smReg.gm.dom.addElement(${jsLit(scriptId)}, parentOrTag, tagOrAttrs, attrs); };`;
186
+ }
187
+ }
188
+ // Clipboard subsystem: navigator.clipboard.writeText via the
189
+ // page-side runtime. Returns the underlying Promise so callers can
190
+ // await; TM's signature is fire-and-forget undefined and our
191
+ // handing back the Promise is the only behavioral deviation.
192
+ if (spec.subsystem === 'clipboard') {
193
+ switch (spec.name) {
194
+ case 'GM_setClipboard':
195
+ return `const GM_setClipboard = function (text, type) { return __smReg.gm.clipboard.write(text, type); };`;
196
+ }
197
+ }
198
+ // XHR subsystem: GM_xmlhttpRequest returns a `{abort}` control
199
+ // object synchronously (GM.xmlHttpRequest, a Promise: see
200
+ // `emitGmObject`). The runtime stores the callbacks locally
201
+ // and forwards a serializable request to the SW; the SW ships
202
+ // load/error/timeout/abort events back asynchronously via
203
+ // chrome.tabs.sendMessage. The dispatch routes through
204
+ // chrome.runtime.onMessage in the runtime. See docs/GM-APIs.md.
205
+ if (spec.subsystem === 'xhr') {
206
+ switch (spec.name) {
207
+ case 'GM_xmlhttpRequest':
208
+ return `const GM_xmlhttpRequest = function (details) { return __smReg.gm.xhr.start(${jsLit(scriptId)}, details); };`;
209
+ }
210
+ }
211
+ // Log subsystem: GM_log writes to the page console with a per-script
212
+ // prefix AND fires a fire-and-forget sm:gm-log to the SW so the call
213
+ // also lands on the script's Log tab. The scriptId is baked in at
214
+ // compile-in time so each call avoids a per-call lookup.
215
+ if (spec.subsystem === 'log') {
216
+ switch (spec.name) {
217
+ case 'GM_log':
218
+ return `const GM_log = function () { return __smReg.gm.log.write(${jsLit(scriptId)}, arguments); };`;
219
+ }
220
+ }
221
+ // Tab subsystem: GM_openInTab. Returns a `{close, closed, onclose}`
222
+ // control object synchronously. The SW handles chrome.tabs.create
223
+ // and reports close events back to the originating tab.
224
+ if (spec.subsystem === 'tab') {
225
+ switch (spec.name) {
226
+ case 'GM_openInTab':
227
+ return `const GM_openInTab = function (url, options) { return __smReg.gm.tab.open(${jsLit(scriptId)}, url, options); };`;
228
+ }
229
+ }
230
+ // Tab-data subsystem: GM_getTab / GM_saveTab / GM_getTabs. The
231
+ // callback is the last argument, as in TM; with none, the call
232
+ // returns a Promise instead.
233
+ if (spec.subsystem === 'tab-data') {
234
+ switch (spec.name) {
235
+ case 'GM_getTab':
236
+ return `const GM_getTab = function (callback) { return __smReg.gm.tabData.get(${jsLit(scriptId)}, callback); };`;
237
+ case 'GM_saveTab':
238
+ return `const GM_saveTab = function (tab, callback) { return __smReg.gm.tabData.save(${jsLit(scriptId)}, tab, callback); };`;
239
+ case 'GM_getTabs':
240
+ return `const GM_getTabs = function (callback) { return __smReg.gm.tabData.list(${jsLit(scriptId)}, callback); };`;
241
+ }
242
+ }
243
+ // Notification subsystem: GM_notification. Returns the SW round-trip
244
+ // Promise (TM/VM hand back undefined; we hand back the Promise so
245
+ // callers that want to wait for the notification to be created can).
246
+ // Callbacks (onclick / ondone) stay on the page side and are dispatched
247
+ // by chrome.runtime.onMessage from the SW.
248
+ if (spec.subsystem === 'notification') {
249
+ switch (spec.name) {
250
+ case 'GM_notification':
251
+ return `const GM_notification = function () { return __smReg.gm.notification.show(${jsLit(scriptId)}, arguments); };`;
252
+ }
253
+ }
254
+ // Download subsystem: GM_download returns a `{abort}` control object
255
+ // synchronously (TM's documented return). Both TM call shapes —
256
+ // `GM_download(details)` and `GM_download(url, name)` — are
257
+ // normalized by the runtime, so the binding just forwards both
258
+ // positional slots.
259
+ if (spec.subsystem === 'download') {
260
+ switch (spec.name) {
261
+ case 'GM_download':
262
+ return `const GM_download = function (detailsOrUrl, name) { return __smReg.gm.download.start(${jsLit(scriptId)}, detailsOrUrl, name); };`;
263
+ }
264
+ }
265
+ // Menu subsystem: register/unregister context-menu commands. The
266
+ // runtime stashes the callback locally (callbacks aren't
267
+ // serializable) and sends the SW a `{commandId, name}` envelope so
268
+ // the toolbar popup can list the entries. Returns the
269
+ // commandId the caller can later pass to GM_unregisterMenuCommand.
270
+ if (spec.subsystem === 'menu') {
271
+ switch (spec.name) {
272
+ case 'GM_registerMenuCommand':
273
+ return `const GM_registerMenuCommand = function (name, callback, accessKeyOrOpts) { return __smReg.gm.menu.register(${jsLit(scriptId)}, name, callback, accessKeyOrOpts); };`;
274
+ case 'GM_unregisterMenuCommand':
275
+ return `const GM_unregisterMenuCommand = function (commandId) { return __smReg.gm.menu.unregister(${jsLit(scriptId)}, commandId); };`;
276
+ }
277
+ }
278
+ // Resource subsystem: GM_getResourceText decodes the stored bytes
279
+ // as UTF-8 on demand; GM_getResourceURL mints a blob: URL with the
280
+ // stored content-type. Both lookups close over scriptId so the
281
+ // runtime can find the script's prefetched resource map.
282
+ if (spec.subsystem === 'resource') {
283
+ switch (spec.name) {
284
+ case 'GM_getResourceText':
285
+ return `const GM_getResourceText = function (name) { return __smReg.gm.resource.getText(${jsLit(scriptId)}, name); };`;
286
+ case 'GM_getResourceURL':
287
+ return `const GM_getResourceURL = function (name) { return __smReg.gm.resource.getUrl(${jsLit(scriptId)}, name); };`;
288
+ }
289
+ }
290
+ if (spec.kind === 'sync') {
291
+ // A catalog entry with no case above. Caught by the bindings unit
292
+ // test, which emits every catalog API.
293
+ throw new Error(`Internal error: No binding for ${spec.name}`);
294
+ }
295
+ // ASYNC body. Build the message envelope at call time so the args
296
+ // travel as a structured array; the SW handler in later phases
297
+ // unpacks them. Phase-2 SW handler returns {ok:false, error:...}
298
+ // and we re-throw on the caller's side.
299
+ //
300
+ // We pass `scriptId` so the SW can validate @grant + @connect per
301
+ // call (`@grant` re-check is cheap insurance against compile-time
302
+ // / live-mismatch; @connect gating uses the script id to look up
303
+ // the stored @connect list).
304
+ return [
305
+ `const ${spec.name} = function () {`,
306
+ ` var args = Array.prototype.slice.call(arguments);`,
307
+ ` return __smReg.gm.sendAsync({`,
308
+ ` type: "sm:gm-call",`,
309
+ ` api: ${jsLit(spec.name)},`,
310
+ ` scriptId: ${jsLit(scriptId)},`,
311
+ ` args: args`,
312
+ ` }).then(function (reply) {`,
313
+ ` if (reply && reply.ok) return reply.value;`,
314
+ ` var msg = (reply && reply.error) ? reply.error : ${jsLit(spec.name + ' failed')};`,
315
+ ` throw new Error("[SourceMonkey] " + msg);`,
316
+ ` });`,
317
+ `};`,
318
+ ].join(' ');
319
+ }
320
+ // Build the GM4 namespace object — `const GM = { ... }`. Not frozen, as
321
+ // in TM and VM: GM_config and other libraries add to it.
322
+ // For each granted API with a `gmDotName`, expose a property on `GM`.
323
+ // When `gmDotPromised` is true, wrap the GM_x return value in a promise
324
+ // so the GM4 caller can `await` it uniformly. Both go through the
325
+ // registry's copies (`apply`, `resolve`, `reject`): a page that replaced
326
+ // `Function.prototype.apply` or `Promise.resolve` would otherwise be
327
+ // handed the GM function itself.
328
+ function emitGmObject(grantedSpecs, scriptId) {
329
+ const props = [];
330
+ for (const spec of grantedSpecs) {
331
+ if (!spec.gmDotName)
332
+ continue;
333
+ if (spec.subsystem === 'unsafe-window')
334
+ continue;
335
+ if (spec.name === 'GM_info') {
336
+ props.push(`info: GM_info`);
337
+ continue;
338
+ }
339
+ // A Promise of the response, with `abort` on it, as in TM and VM,
340
+ // where GM_xmlhttpRequest returns `{abort}`.
341
+ if (spec.name === 'GM_xmlhttpRequest') {
342
+ props.push(`xmlHttpRequest: function (details) { return __smReg.gm.xhr.start(${jsLit(scriptId)}, details, true); }`);
343
+ continue;
344
+ }
345
+ if (spec.gmDotPromised) {
346
+ // The legacy GM_x can be sync (storage reads from cache) or
347
+ // throw (sync stub). Wrap in a Promise so the GM4 alias
348
+ // matches its documented signature unconditionally.
349
+ props.push(`${spec.gmDotName}: function () { try { return __smReg.resolve(__smReg.apply(${spec.name}, null, arguments)); } catch (e) { return __smReg.reject(e); } }`);
350
+ }
351
+ else {
352
+ // Pass-through: same call shape, same return.
353
+ props.push(`${spec.gmDotName}: function () { return __smReg.apply(${spec.name}, null, arguments); }`);
354
+ }
355
+ }
356
+ if (props.length === 0)
357
+ return '';
358
+ return `const GM = { ${props.join(', ')} };`;
359
+ }
360
+ // Names every script gets, as in TM and VM (measured in TM 5.5.0 and VM
361
+ // 2.49.0), declared here so they stay out of the page:
362
+ // - `exports`, `module`, `define` are undefined, so a bare reference
363
+ // doesn't throw, and a UMD library a script `@require`s sees no
364
+ // module system (not the page's AMD `define`) and sets a global.
365
+ // - `cloneInto`, `exportFunction` and `createObjectIn` are Firefox's
366
+ // for passing values into the page; on Chrome there's nothing to
367
+ // cross, so as in both managers `cloneInto` returns its argument,
368
+ // `exportFunction` its function and `createObjectIn` a new object,
369
+ // the last two put on the target first when given `defineAs`.
370
+ // A script's own declaration of any of them shadows these (its program
371
+ // runs in a nested scope). `let`, not `const`, so a sloppy
372
+ // `define = undefined` works, as in VM, where they're parameters; `let`
373
+ // also keeps them off the page's `window` in the reload swap's global
374
+ // eval, where a `var` would land there.
375
+ const FIREFOX_AND_MODULE_NAMES = 'let exports = void 0, module = void 0, define = void 0, '
376
+ + 'cloneInto = function (obj) { return obj; }, '
377
+ + 'exportFunction = function (fn, target, options) { '
378
+ + 'if (options && options.defineAs != null) target[options.defineAs] = fn; return fn; }, '
379
+ + 'createObjectIn = function (target, options) { var obj = {}; '
380
+ + 'if (options && options.defineAs != null) target[options.defineAs] = obj; return obj; };';
381
+ // Public entry point. Build the binding source string for one script.
382
+ //
383
+ // Behavior:
384
+ // - Always emits GM_info, matching TM/VM (it's available with no
385
+ // `@grant` line at all, with `@grant none`, and alongside any
386
+ // mix of real grants). A script with unknown grants only still
387
+ // gets GM_info — every other emitted artifact is keyed off the
388
+ // resolved-spec set.
389
+ // - For each known granted API: a `const GM_x = ...` binding.
390
+ // - The GM4 `GM` object covers granted APIs with gmDotName, plus
391
+ // `GM.info`.
392
+ // - `exports` / `module` / `define` and the Firefox stand-ins, for
393
+ // every script (`FIREFOX_AND_MODULE_NAMES`).
394
+ // - Granted names not in the catalog (resolveGrants should have
395
+ // filtered these but be defensive) are skipped.
396
+ //
397
+ // The output is concatenated into the wrapper; the bindings sit
398
+ // inside the outer IIFE's scope and are visible to both the baked-in
399
+ // body and the run-new swap-eval branch.
400
+ export function emitGmBindings(granted, info,
401
+ // The script runs with a sandbox `window` (`runsSandboxed`).
402
+ sandboxed = false) {
403
+ const specs = [];
404
+ for (const n of granted) {
405
+ const spec = findApi(n);
406
+ if (spec)
407
+ specs.push(spec);
408
+ }
409
+ // GM_info is always emitted, even with an empty grant set. TM/VM
410
+ // both treat it as implicit so a script can introspect its own
411
+ // metadata without declaring a single grant.
412
+ // Everything here reaches the runtime through `__smReg`, the world's
413
+ // registry, which the injector puts in scope: the wrap function's
414
+ // parameter, or a `const` ahead of the swap eval's body.
415
+ //
416
+ // This code's top level runs in the wrap function, which is sloppy
417
+ // mode (the user program inside it may be), so a page function it
418
+ // called directly could reach it through `.caller`, and its
419
+ // `arguments` hold the script's entry (its stored values among them).
420
+ // So the top level calls nothing the page could have replaced:
421
+ // `console.info` is the registry's copy, taken at document_start
422
+ // (`info`). Calls from inside a GM
423
+ // function don't lead back here: the runtime is strict, and the user
424
+ // program runs through the prelude's strict `callAs`.
425
+ const parts = [emitGmInfo(info), FIREFOX_AND_MODULE_NAMES];
426
+ const declared = ['GM_info'];
427
+ for (const spec of specs) {
428
+ if (spec.name === 'GM_info')
429
+ continue;
430
+ const line = emitOneBinding(spec, info.scriptId);
431
+ if (!line)
432
+ continue;
433
+ parts.push(line);
434
+ // `window.close` and the like call into the runtime; they declare nothing.
435
+ if (/^[A-Za-z_$][\w$]*$/.test(spec.name))
436
+ declared.push(spec.name);
437
+ }
438
+ // GM_info is part of the GM4 alias namespace too.
439
+ const allSpecsIncludingInfo = [findApi('GM_info')].concat(specs.filter((s) => s.name !== 'GM_info'));
440
+ const gmObject = emitGmObject(allSpecsIncludingInfo, info.scriptId);
441
+ if (gmObject) {
442
+ parts.push(gmObject);
443
+ declared.push('GM');
444
+ }
445
+ // `@grant window.onurlchange` — the only entry whose effect is an
446
+ // eager call rather than a declaration. It has to run before the
447
+ // body so the body's `window.onurlchange === null` feature test
448
+ // succeeds. It lives at the END of `js` (not in `prelude`) on
449
+ // purpose:
450
+ // - Ordering safety is identical. Both strings are spliced into
451
+ // the SAME main-body wrap function (`wrapCombinedEntry`), which
452
+ // only runs after the prelude's awaitDecision + waitForRunAt
453
+ // gates — so `__smReg.gm` is installed either way.
454
+ // - `js` is ALSO what `buildSwapEvalBody` prepends for the
455
+ // reload-fast-path swap, and `prelude` is not. Emitting here is
456
+ // what makes a hot-swapped script still get its `urlchange`
457
+ // surface.
458
+ //
459
+ // Guarded on the runtime rather than dereferencing it bare — unlike
460
+ // the other two eager calls, which fail loudly on purpose. An
461
+ // unguarded TypeError in this scope aborts the whole wrap function
462
+ // and the script never runs at all, and degrading to "no urlchange
463
+ // surface" is strictly better: the script's own `=== null` feature
464
+ // test then correctly reports "unsupported", exactly as it would
465
+ // under a manager without the feature. `storage.init` (in
466
+ // `prelude`) and `resource.init` take the opposite trade — running
467
+ // a body against missing values or missing resource bytes is worse
468
+ // than not running it.
469
+ if (specs.some((s) => s.subsystem === 'url-change')) {
470
+ parts.push(
471
+ // Both halves matter: a runtime registered before this feature
472
+ // shipped has `__smReg.gm` but no `urlchange`, and a page can hold
473
+ // a stale registration across an extension update.
474
+ `if (__smReg.gm && __smReg.gm.urlchange) __smReg.gm.urlchange.enable(${jsLit(info.scriptId)});`);
475
+ }
476
+ // `@grant window.close` / `@grant window.focus` — same eager-call
477
+ // shape as `urlchange.enable` above, and in `js` for the same
478
+ // reasons (a hot-swapped script has to get its overrides back, and
479
+ // both strings are spliced into the same wrap function).
480
+ //
481
+ // Guarded on the runtime for the same reason too: degrading to the
482
+ // page's own `window.close()` / `.focus()` — which the browser
483
+ // mostly ignores — is better than a TypeError that stops the script
484
+ // from running at all.
485
+ //
486
+ // A sandboxed script (the page's world) gets its replacement on its
487
+ // own sandbox `window` instead of the world's, so the page and other
488
+ // scripts can't call it: `make` hands it back, and the sandbox takes
489
+ // it from `windowMethods`. Undefined (a frame's `close`, or a runtime
490
+ // from before `make` that a page kept across an extension update)
491
+ // leaves the page's method showing through.
492
+ const windowMethods = [];
493
+ for (const spec of specs) {
494
+ if (spec.subsystem !== 'window-control')
495
+ continue;
496
+ // `window.close` -> "close", `window.focus` -> "focus".
497
+ const method = spec.name.slice('window.'.length);
498
+ const args = `${jsLit(info.scriptId)}, ${jsLit(method)}`;
499
+ if (sandboxed) {
500
+ windowMethods.push(method);
501
+ parts.push(`const ${windowMethodBinding(method)} = __smReg.gm && __smReg.gm.windowControl && __smReg.gm.windowControl.make ? __smReg.gm.windowControl.make(${args}) : undefined;`);
502
+ }
503
+ else {
504
+ parts.push(`if (__smReg.gm && __smReg.gm.windowControl) __smReg.gm.windowControl.enable(${args});`);
505
+ }
506
+ }
507
+ // `resource.init`: emitted whenever GM_getResource* is granted,
508
+ // even when `resources` is empty. Why "even when empty": the
509
+ // runtime's resource box persists across `chrome.userScripts`
510
+ // re-registrations within one document. If a script removed a
511
+ // `@resource` line, skipping init would leave the prior bytes
512
+ // resident in `byName`; the now-stale `GM_getResourceText("foo")`
513
+ // would still resolve. `resource.init` replaces the box, so always
514
+ // emitting it is what makes "removed resource → returns undefined"
515
+ // actually true on the next reload.
516
+ //
517
+ // It lives at the END of `js` rather than in `prelude` for the same
518
+ // reason `urlchange.enable` does: `js` is what `buildSwapEvalBody`
519
+ // prepends for the reload-fast-path swap, and `prelude` is not.
520
+ // The statement references only `__smReg.gm` and baked
521
+ // literals, so it works identically in the wrap function and in the
522
+ // swap eval's global scope.
523
+ //
524
+ // Installation ordering is unchanged for the registered path — both
525
+ // strings are spliced into the SAME wrap function, which only runs
526
+ // after the GM runtime has registered. What did change is the
527
+ // relative order: `js` precedes `prelude`, so this now runs BEFORE
528
+ // `storage.init` rather than after. The only case that can tell is
529
+ // `storage.init`'s hard abort, which now leaves a freshly-replaced
530
+ // resource box behind instead of the previous one — invisible,
531
+ // since the body doesn't run either way and the box is per-document.
532
+ const hasStorageGrant = specs.some((s) => s.subsystem === 'storage');
533
+ const hasResourceGrant = specs.some((s) => s.subsystem === 'resource');
534
+ if (hasResourceGrant) {
535
+ parts.push(`__smReg.gm.resource.init(${jsLit(info.scriptId)}, ${jsLit(info.resources)});`);
536
+ }
537
+ // Eager calls into the runtime at compile time go in the prelude when
538
+ // they need identifiers only the wrap function has. Splicing them in
539
+ // the IIFE top would race with the runtime's own document_start
540
+ // registration in some Chrome builds; the injector splices `prelude`
541
+ // inside the main-body wrap function passed to the registry's run, which
542
+ // only fires after both the GM runtime spec has registered AND the
543
+ // @run-at lifecycle event resolves. See wrapCombinedEntry in
544
+ // src/extension/injector.ts.
545
+ //
546
+ // - storage.init: emitted whenever a storage grant is present; the
547
+ // prelude reads the snapshot off `__smDecision.gmValues` (attached
548
+ // by the SW to the sm:check-pending reply) and hard-aborts the
549
+ // script if the snapshot is missing. That `__smDecision` reference
550
+ // is why this one can't move into `js` — the swap eval's global
551
+ // scope doesn't have it. The swap path runs the equivalent init
552
+ // from `userscript-prelude.js` instead, where the decision object
553
+ // is in scope. See docs/GM-APIs-storage.md.
554
+ const preludeLines = [];
555
+ if (hasStorageGrant) {
556
+ // Single source of truth: the SW-attached snapshot. We do NOT
557
+ // bake a fallback literal — getting the snapshot wrong is more
558
+ // dangerous than failing visibly, because a read-modify-write
559
+ // userscript that started with stale data would silently
560
+ // overwrite the user's real values with defaults.
561
+ //
562
+ // When the snapshot is missing: log via console.info (not .error —
563
+ // see userscript-prelude.js; injected .error/.warn leak into
564
+ // chrome://extensions/?errors), send sm:error so the failure
565
+ // surfaces on the per-script log + badge, and `return` from the
566
+ // @run-at callback so the user body never runs. The user can
567
+ // recover by reloading.
568
+ //
569
+ // Two ways to get here, which is why the message doesn't assert
570
+ // one cause: the SW's snapshot read failed, or — for a MAIN-world
571
+ // script only — the SW's `bridgeMayActAs` gate refused to hand the
572
+ // values over the bridge (see docs/injection.md#gm_-in-main-world).
573
+ // No decision at all never gets here: the script is skipped
574
+ // (docs/injection.md#no-decision).
575
+ //
576
+ // One string in both places. As two literals they had already
577
+ // drifted — the Log copy lost the "reload" half, which is the only
578
+ // part the reader can act on.
579
+ const gmValuesFailure = "Failed reading this script's stored GM values during setup, " +
580
+ 'so the script did not run. Reload the page to retry.';
581
+ preludeLines.push(`var __smGmSnap = (typeof __smDecision === "object" && __smDecision && __smDecision.gmValues);`, `if (!__smGmSnap) {`, ` try { __smReg.info("[SourceMonkey] " + __smName + ": " + ${jsLit(gmValuesFailure)}); } catch (_smSE0) {}`, ` __smSend({ type: "sm:error", id: __smId, name: __smName, loadedAt: __smLoadedAt, url: location.href, message: ${jsLit(gmValuesFailure)} });`, ` return;`, `}`, `__smReg.gm.storage.init(${jsLit(info.scriptId)}, __smGmSnap, __smDecision.gmSeq);`);
582
+ }
583
+ const prelude = preludeLines.join(' ');
584
+ // Join with spaces, not newlines: the injector splices this into
585
+ // the main-body wrap function ahead of the user body, and any
586
+ // newline here would push the body's line 1 onto physical line 2+
587
+ // — breaking the stack-trace line-number contract that
588
+ // `wrapCombinedEntry` in src/extension/injector.ts depends on. Each part is
589
+ // a complete `const … = …;` statement so `;` + ` ` is enough.
590
+ return {
591
+ js: parts.join(' ') + ' ',
592
+ prelude,
593
+ granted: specs.map((s) => s.name),
594
+ needsValues: hasStorageGrant,
595
+ declared,
596
+ windowMethods,
597
+ };
598
+ }
599
+ // Re-export so callers building GmInfoInput don't need a separate
600
+ // import from the catalog just to enumerate all known names.
601
+ export { GM_APIS };