sourcemonkey 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,204 @@
1
+ // The worker's one store for `GM_setValue` values: every read and write
2
+ // of a script's values in the worker goes through here, so what it
3
+ // hands out always includes every write it has accepted, whether or
4
+ // not that write has reached disk yet.
5
+ //
6
+ // - State: each script's whole value map, read from storage the first
7
+ // time it's needed and kept until the worker shuts down.
8
+ // - Writes change the map at once and get a sequence number, then go
9
+ // to disk behind it: one `chrome.storage.local` write in flight per
10
+ // script, with writes arriving meanwhile combined into the next one.
11
+ // - Sequence numbers are how documents order what they hear about a
12
+ // key (their own write's reply, other tabs' broadcasts, snapshots).
13
+ // They come from one counter, started from the clock, so a later
14
+ // worker's numbers are above an earlier one's unless the clock has
15
+ // stepped back by more than the earlier worker's lifetime (TODO.md).
16
+ //
17
+ // `gm-values.ts` is the row layer underneath; nothing else in the
18
+ // worker may use it for values (`tests/unit/value-store.test.mjs`).
19
+ // See docs/GM-APIs-storage.md.
20
+ import { applyBatchValues, countStoredValues, DELETED_SENTINEL, loadAllValues, wipeScriptValues, } from './gm-values.js';
21
+ // Microseconds since the epoch at worker start: a worker would need a
22
+ // million writes a second, sustained, to run into its successor's
23
+ // numbers. Well inside a double's exact integers.
24
+ let lastSeq = Date.now() * 1000;
25
+ // Numbers at or below this were handed out by an earlier worker, whose
26
+ // writes this one knows nothing about.
27
+ const workerStartSeq = lastSeq;
28
+ function nextSeq() {
29
+ return ++lastSeq;
30
+ }
31
+ const DELETED = Symbol('sm:value-deleted');
32
+ let scripts = new Map();
33
+ function stateFor(scriptId) {
34
+ let st = scripts.get(scriptId);
35
+ if (!st) {
36
+ st = { values: null, loading: null, unsaved: new Map(), saving: null, lastChange: 0, saveFailed: false };
37
+ scripts.set(scriptId, st);
38
+ }
39
+ return st;
40
+ }
41
+ // The script's map, loading it on first use. Callers that arrive while
42
+ // it loads share the load, and resume in the order they asked.
43
+ async function loaded(scriptId, st) {
44
+ if (st.values)
45
+ return st.values;
46
+ if (!st.loading) {
47
+ const load = loadAllValues(scriptId).then((obj) => {
48
+ // A reset while this read was out leaves `st` orphaned; the
49
+ // caller still gets the map, but nothing new lands in it.
50
+ st.values = new Map(Object.entries(obj));
51
+ st.loading = null;
52
+ return st.values;
53
+ }, (err) => {
54
+ st.loading = null;
55
+ throw err;
56
+ });
57
+ st.loading = load;
58
+ }
59
+ return st.loading;
60
+ }
61
+ // The script's values and the sequence number they're current to.
62
+ export async function readValues(scriptId) {
63
+ const map = await loaded(scriptId, stateFor(scriptId));
64
+ return { values: Object.fromEntries(map), seq: lastSeq };
65
+ }
66
+ // Whether a script's values may have changed after sequence number
67
+ // `seq`. True when `seq` predates this worker, which can't say.
68
+ export function valuesChangedSince(scriptId, seq) {
69
+ if (seq <= workerStartSeq)
70
+ return true;
71
+ return (scripts.get(scriptId)?.lastChange ?? 0) > seq;
72
+ }
73
+ // A script's values, without keeping a map loaded for it: the loaded
74
+ // map when there is one, which holds writes not yet saved, else a read
75
+ // of storage. For reading many scripts at once (the backup export).
76
+ export async function peekValues(scriptId) {
77
+ const st = scripts.get(scriptId);
78
+ if (st?.values)
79
+ return Object.fromEntries(st.values);
80
+ if (st?.loading)
81
+ return Object.fromEntries(await st.loading);
82
+ return loadAllValues(scriptId);
83
+ }
84
+ // How many values a script has, without keeping a map loaded for it.
85
+ export async function countValues(scriptId) {
86
+ const st = scripts.get(scriptId);
87
+ if (st?.values)
88
+ return st.values.size;
89
+ if (st?.loading)
90
+ return (await st.loading).size;
91
+ return countStoredValues(scriptId);
92
+ }
93
+ let onSaveFailed = () => undefined;
94
+ export function onValueSaveFailed(handler) {
95
+ onSaveFailed = handler;
96
+ }
97
+ // Apply one document's writes, in order, and return their sequence
98
+ // number. The map changes before this returns; disk follows.
99
+ export async function writeValues(scriptId, writes) {
100
+ const st = stateFor(scriptId);
101
+ let map;
102
+ try {
103
+ map = await loaded(scriptId, st);
104
+ }
105
+ catch (err) {
106
+ // No map to apply them to, so the writes are refused: the page
107
+ // hears so in the reply, and the script's Log the same way as a
108
+ // failed save.
109
+ console.info(`[SourceMonkey] Reading stored values for script ${scriptId} failed, so its writes were not saved:`, err);
110
+ reportSaveFailure(st, scriptId, err, 'load');
111
+ throw err;
112
+ }
113
+ const seq = nextSeq();
114
+ st.lastChange = seq;
115
+ for (const w of writes) {
116
+ if (w.deleted) {
117
+ map.delete(w.key);
118
+ st.unsaved.set(w.key, DELETED);
119
+ }
120
+ else {
121
+ map.set(w.key, w.value);
122
+ st.unsaved.set(w.key, w.value);
123
+ }
124
+ }
125
+ if (!st.saving)
126
+ st.saving = saveLoop(scriptId, st);
127
+ return seq;
128
+ }
129
+ function reportSaveFailure(st, scriptId, err, kind) {
130
+ if (st.saveFailed)
131
+ return;
132
+ st.saveFailed = true;
133
+ onSaveFailed(scriptId, err, kind);
134
+ }
135
+ // Hands `unsaved` to storage until it stays empty. Started only with
136
+ // something to save, so the first `await` comes before it can finish
137
+ // and clear `saving`.
138
+ async function saveLoop(scriptId, st) {
139
+ try {
140
+ while (st.unsaved.size > 0) {
141
+ const batch = st.unsaved;
142
+ st.unsaved = new Map();
143
+ const writes = [];
144
+ for (const [key, value] of batch) {
145
+ writes.push({ key, value: value === DELETED ? DELETED_SENTINEL : value });
146
+ }
147
+ try {
148
+ await applyBatchValues(scriptId, writes);
149
+ st.saveFailed = false;
150
+ }
151
+ catch (err) {
152
+ // Most likely the storage quota. The documents running the
153
+ // script, and this map, keep the values until the worker shuts
154
+ // down; only disk is behind. Not `console.warn`: see CLAUDE.md.
155
+ console.info(`[SourceMonkey] Saving stored values for script ${scriptId} failed:`, err);
156
+ reportSaveFailure(st, scriptId, err, 'save');
157
+ }
158
+ }
159
+ }
160
+ finally {
161
+ st.saving = null;
162
+ }
163
+ }
164
+ // Remove all of a script's values, saved and unsaved.
165
+ export async function wipeValues(scriptId) {
166
+ const st = stateFor(scriptId);
167
+ // Let a load or save in flight finish first, so neither can put
168
+ // values back.
169
+ if (st.loading)
170
+ await st.loading.catch(() => undefined);
171
+ while (st.saving)
172
+ await st.saving;
173
+ st.unsaved.clear();
174
+ st.values = new Map();
175
+ st.lastChange = nextSeq();
176
+ return wipeScriptValues(scriptId);
177
+ }
178
+ // Saves still running for states `forgetLoadedValues` dropped.
179
+ const forgottenSaves = new Set();
180
+ // Resolves once every accepted write is on disk.
181
+ export async function valueWritesSaved() {
182
+ for (;;) {
183
+ const pending = [
184
+ ...[...scripts.values()].map((st) => st.saving).filter((p) => p != null),
185
+ ...forgottenSaves,
186
+ ];
187
+ if (pending.length === 0)
188
+ return;
189
+ await Promise.all(pending);
190
+ }
191
+ }
192
+ // Tests only: forget every loaded map, after something changed
193
+ // `gmval:` rows behind the store's back (clearing or seeding storage).
194
+ export function forgetLoadedValues() {
195
+ // Their saves still land, so `valueWritesSaved` keeps waiting for them.
196
+ for (const st of scripts.values()) {
197
+ const saving = st.saving;
198
+ if (!saving)
199
+ continue;
200
+ forgottenSaves.add(saving);
201
+ void saving.finally(() => forgottenSaves.delete(saving));
202
+ }
203
+ scripts = new Map();
204
+ }
@@ -0,0 +1,68 @@
1
+ // The fetching half of update probes. Split from `version-probe.ts` so
2
+ // the pure half — target selection, the outdated verdict, the staleness
3
+ // gate — can be imported by display code (`script-display.ts`,
4
+ // `collections.ts`, `script-details.ts`) without dragging the whole fetch
5
+ // stack into every extension page. Only `script-updater.ts` needs this
6
+ // module. See [docs/updates.md].
7
+ import { mapWithConcurrency, PROBE_URLS_PER_SCRIPT_CONCURRENCY } from './concurrency.js';
8
+ import { fetchResourceText } from '../file-loader.js';
9
+ import { getValue, parseHeader } from '../userscript-header.js';
10
+ // A `.meta.js` is a couple of KB; a `@downloadURL` is a whole
11
+ // userscript. Past 1 MB we're not reading a userscript header, we're
12
+ // downloading something else.
13
+ const PROBE_MAX_BYTES = 1024 * 1024;
14
+ const PROBE_TIMEOUT_MS = 5000;
15
+ // Fetch one target and read its `@version`. Never throws and never
16
+ // reports anything but a `VersionProbe` — a probe failure is a display
17
+ // annotation, not an error the refresh pipeline should react to.
18
+ //
19
+ // `prior` is this target's previous row, when there is one. A failed
20
+ // attempt carries its `version` forward: retracting a verdict we
21
+ // already established, because the host happened to 500 this
22
+ // afternoon, would flap the OUTDATED badge and re-log the transition
23
+ // every time the host recovered.
24
+ export async function runProbe(target, now, prior) {
25
+ const failed = (error) => ({
26
+ kinds: target.kinds,
27
+ url: target.url,
28
+ checkedAt: now,
29
+ error,
30
+ ...(prior?.version ? { version: prior.version } : {}),
31
+ });
32
+ // The bytes fetcher does report which failure it was, but
33
+ // `fetchResourceText` drops that on purpose: `probe.error` renders as
34
+ // a value in a definition-list cell (`script-details.ts`) with no room
35
+ // for a sentence, so this row's job is "we couldn't check", not
36
+ // diagnosis. Tracked in TODO.md with the rest of the version-probe
37
+ // wording exception.
38
+ const text = await fetchResourceText(target.url, {
39
+ maxBytes: PROBE_MAX_BYTES,
40
+ timeoutMs: PROBE_TIMEOUT_MS,
41
+ });
42
+ if (text === null)
43
+ return failed('could not fetch');
44
+ // Both `.meta.js` (header only) and a full `.user.js` parse the same
45
+ // way — parseHeader reads the metadata block wherever it sits and
46
+ // ignores everything after it. Managers accept either shape at an
47
+ // `@updateURL`, so we do too.
48
+ const header = parseHeader(text);
49
+ if (!header)
50
+ return failed('no metadata block');
51
+ const version = getValue(header, 'version');
52
+ if (!version?.trim())
53
+ return failed('no @version');
54
+ return { kinds: target.kinds, url: target.url, checkedAt: now, version: version.trim() };
55
+ }
56
+ // Probe every target of ONE script, at most
57
+ // PROBE_URLS_PER_SCRIPT_CONCURRENCY at a time — a guard that cannot bind
58
+ // at today's two-targets-per-script maximum. What actually caps a
59
+ // Refresh All burst is PROBE_SCRIPTS_CONCURRENCY, one level up in
60
+ // `runVersionProbes`.
61
+ //
62
+ // Takes the targets rather than re-deriving them from the header,
63
+ // because the caller already needed them for the staleness check and the
64
+ // two must agree. Result order matches `targets`.
65
+ export async function probeVersions(targets, now, prior) {
66
+ const priorByUrl = new Map((prior ?? []).map((p) => [p.url, p]));
67
+ return await mapWithConcurrency(targets, PROBE_URLS_PER_SCRIPT_CONCURRENCY, (target) => runProbe(target, now, priorByUrl.get(target.url)));
68
+ }
@@ -0,0 +1,109 @@
1
+ // Page side of the worker's operations: every write an extension page
2
+ // makes goes through here, to run in the service worker
3
+ // (docs/worker-writes.md). Each export has the name and arguments of the
4
+ // worker function it runs, so a call site only changes its import.
5
+ //
6
+ // One port per call:
7
+ // - The page sends the operation's name and arguments, then a
8
+ // heartbeat while it waits. A port's messages keep the worker alive,
9
+ // which a long refresh needs (Chrome stops an idle worker after
10
+ // 30 s).
11
+ // - The worker answers once, with the result or the error's message,
12
+ // which is thrown here as a new Error with that message.
13
+ // - Closing the page doesn't stop the work: the worker carries on and
14
+ // drops the answer.
15
+ //
16
+ // Arguments and results cross as JSON: no Maps, Sets or functions, and
17
+ // an `undefined` argument or array entry arrives as `null`.
18
+ // worker-ops.ts adapts any function whose own signature has them.
19
+ import { isPageContext } from '../run-context.js';
20
+ export const WORKER_OP_PORT = 'sm:op';
21
+ // Under Chrome's 30 s idle limit, with room to spare.
22
+ export const WORKER_OP_HEARTBEAT_MS = 20_000;
23
+ // The worker went away mid-call: it crashed, or Chrome stopped it.
24
+ // It may have saved the change first.
25
+ export const WORKER_STOPPED = 'Internal error: SourceMonkey\'s background worker stopped before answering. The change may not have been saved.';
26
+ export function callWorker(op, ...args) {
27
+ // A port the worker opens reaches no listener of its own: code running
28
+ // in the worker calls the store directly.
29
+ if (!isPageContext())
30
+ return Promise.reject(new Error(`Internal error: The background worker called its own ${op} through a port`));
31
+ return new Promise((resolve, reject) => {
32
+ const port = chrome.runtime.connect({ name: WORKER_OP_PORT });
33
+ let settled = false;
34
+ const beat = setInterval(() => {
35
+ try {
36
+ port.postMessage({ beat: true });
37
+ }
38
+ catch { /* port gone */ }
39
+ }, WORKER_OP_HEARTBEAT_MS);
40
+ const finish = () => {
41
+ settled = true;
42
+ clearInterval(beat);
43
+ try {
44
+ port.disconnect();
45
+ }
46
+ catch { /* already gone */ }
47
+ };
48
+ port.onMessage.addListener((message) => {
49
+ if (settled)
50
+ return;
51
+ finish();
52
+ if (message.ok)
53
+ resolve(message.result);
54
+ else
55
+ reject(new Error(message.error));
56
+ });
57
+ port.onDisconnect.addListener(() => {
58
+ if (settled)
59
+ return;
60
+ finish();
61
+ reject(new Error(WORKER_STOPPED));
62
+ });
63
+ port.postMessage({ op, args });
64
+ });
65
+ }
66
+ // Trailing `undefined` arguments are left off, so the worker's default
67
+ // parameters apply: JSON would send them as `null`.
68
+ function op(name) {
69
+ return (...args) => {
70
+ let n = args.length;
71
+ while (n > 0 && args[n - 1] === undefined)
72
+ n--;
73
+ return callWorker(name, ...args.slice(0, n));
74
+ };
75
+ }
76
+ // ---------- Enable toggles ----------
77
+ export const setScriptEnabledExclusive = op('setScriptEnabledExclusive');
78
+ export const setCollectionEnabledExclusive = op('setCollectionEnabledExclusive');
79
+ export const setAllScriptsEnabled = op('setAllScriptsEnabled');
80
+ export const enableWithin = op('enableWithin');
81
+ export const setScriptsEnabled = op('setScriptsEnabled');
82
+ export const turnOffOtherCopies = op('turnOffOtherCopies');
83
+ // ---------- Small writes ----------
84
+ export const removeScript = op('removeScript');
85
+ export const removeCollection = op('removeCollection');
86
+ export const moveCollection = op('moveCollection');
87
+ export const moveScript = op('moveScript');
88
+ export const createCollection = op('createCollection');
89
+ export const setCollectionOptions = op('setCollectionOptions');
90
+ export const revokeConnectHost = op('revokeConnectHost');
91
+ export const forgetDeniedHost = op('forgetDeniedHost');
92
+ export const clearScriptLog = op('clearScriptLog');
93
+ export const markFirstRunComplete = op('markFirstRunComplete');
94
+ export const writeSetting = op('writeSetting');
95
+ export const backfillCollectionDisplayNames = op('backfillCollectionDisplayNames');
96
+ export const reconcileScriptMembership = op('reconcileScriptMembership');
97
+ export const syncAfterUserScriptsAllowed = op('syncAfterUserScriptsAllowed');
98
+ // ---------- Refresh, add, install ----------
99
+ export const updateScript = op('updateScript');
100
+ export const updateCollection = op('updateCollection');
101
+ export const updateAllCollections = op('updateAllCollections');
102
+ export const addCollection = op('addCollection');
103
+ export const installScript = op('installScript');
104
+ // ---------- Reads ----------
105
+ export const readScriptValues = op('readScriptValues');
106
+ // ---------- Site icons (the debug icons page) ----------
107
+ export const refreshCachedIcon = op('refreshCachedIcon');
108
+ export const deleteCachedIcon = op('deleteCachedIcon');
109
+ export const deleteAllCachedIcons = op('deleteAllCachedIcons');
@@ -0,0 +1,171 @@
1
+ // Worker side of worker-client.ts: the operations extension pages ask
2
+ // the service worker to run, since only it writes storage
3
+ // (docs/worker-writes.md).
4
+ //
5
+ // Each entry is the function a page would have called itself, or an
6
+ // adapter where that function's own arguments or result can't cross as
7
+ // JSON (a timer, a Map) or carry more than the page uses (script
8
+ // bodies).
9
+ import { addCollection, backfillCollectionDisplayNames, createCollection, describeError, installScript, markFirstRunComplete, migrateCollectionKeysForPage, withoutBodies, moveCollection, moveScript, reconcileScriptMembership, removeCollection, removeScript, setCollectionOptions, setScriptsEnabled, } from './script-store.js';
10
+ import { enableWithin, setAllScriptsEnabled, setCollectionEnabledExclusive, setScriptEnabledExclusive, } from './script-enablement.js';
11
+ import { turnOffOtherCopies } from './script-identity.js';
12
+ import { syncRegistrations } from './injector.js';
13
+ import { probeInstalledScript, updateAllCollections, updateCollection, updateScript, } from './script-updater.js';
14
+ import { forgetDeniedHost, revokeConnectHost } from './connect-grants.js';
15
+ import { clearScriptLog } from './script-log.js';
16
+ import { peekValues } from './value-store.js';
17
+ import { DEBUG_LOGGING_KEY } from '../debug-log.js';
18
+ import { GITHUB_AUTH_TOKEN_KEY, setGithubAuthToken } from '../github-auth.js';
19
+ import { LOCAL_ROOTS_KEY, readRootsSetting, setConfiguredLocalRoots } from '../local-file-overrides.js';
20
+ import { INSTALL_CAPTURE_KEY } from './install-capture.js';
21
+ import { DOWNLOADS_COPIES_KEY, registerLocalCopy } from './local-copy.js';
22
+ import { deleteAllCachedIcons, deleteCachedIcon, ensureCachedIcon, refreshCachedIcon } from './site-icon.js';
23
+ import { startPhaseTimer } from '../phase-timing.js';
24
+ import { WORKER_OP_PORT } from './worker-client.js';
25
+ // The settings a page may change: Options, and the local copy's
26
+ // Downloads folder. Each a whole value, not a read-modify-write, so
27
+ // writing it is all the worker does.
28
+ const SETTING_KEYS = [
29
+ INSTALL_CAPTURE_KEY, DEBUG_LOGGING_KEY, LOCAL_ROOTS_KEY, GITHUB_AUTH_TOKEN_KEY, DOWNLOADS_COPIES_KEY,
30
+ ];
31
+ const WORKER_OPS = {
32
+ // ---------- Enable toggles ----------
33
+ setScriptEnabledExclusive,
34
+ setCollectionEnabledExclusive,
35
+ setAllScriptsEnabled,
36
+ // The viewer's versions table: `scope` is the copies it shows.
37
+ async enableWithin(id, scope) {
38
+ const timer = startPhaseTimer('enable script');
39
+ await enableWithin(id, scope, timer);
40
+ timer.done();
41
+ },
42
+ async setScriptsEnabled(updates) {
43
+ await setScriptsEnabled(updates);
44
+ },
45
+ async turnOffOtherCopies(scriptId) {
46
+ await turnOffOtherCopies(scriptId);
47
+ },
48
+ // ---------- Small writes ----------
49
+ removeScript,
50
+ removeCollection,
51
+ moveCollection,
52
+ // The moved row, without the bodies a refresh of it would carry.
53
+ async moveScript(scriptId, target) {
54
+ const moved = await moveScript(scriptId, target);
55
+ return moved && 'bodyText' in moved ? withoutBodies(moved) : moved;
56
+ },
57
+ createCollection,
58
+ setCollectionOptions,
59
+ revokeConnectHost,
60
+ forgetDeniedHost,
61
+ clearScriptLog,
62
+ markFirstRunComplete,
63
+ // `null` removes the setting.
64
+ async writeSetting(key, value) {
65
+ if (!SETTING_KEYS.includes(key))
66
+ throw new Error(`Internal error: ${key} is not a setting a page may change`);
67
+ if (value === null)
68
+ await chrome.storage.local.remove(key);
69
+ else
70
+ await chrome.storage.local.set({ [key]: value });
71
+ // Settings the worker keeps in memory take the new value now, not
72
+ // when the change event arrives: a page saving the roots refreshes
73
+ // straight after.
74
+ if (key === LOCAL_ROOTS_KEY)
75
+ setConfiguredLocalRoots(readRootsSetting(value));
76
+ if (key === GITHUB_AUTH_TOKEN_KEY)
77
+ setGithubAuthToken(typeof value === 'string' ? value : '');
78
+ },
79
+ // ---------- Repairs the collections page asks for ----------
80
+ backfillCollectionDisplayNames,
81
+ // From the worker's own read of the rows, not the page's.
82
+ async reconcileScriptMembership() {
83
+ return await reconcileScriptMembership();
84
+ },
85
+ // A page that finds rows in the old spelling waits for this before
86
+ // reading.
87
+ migrateCollectionKeys: migrateCollectionKeysForPage,
88
+ // The collections page saw "Allow User Scripts" turned on. Chrome
89
+ // tells the worker nothing when that happens, and the syncs that ran
90
+ // while it was off registered nothing, so this places the whole set.
91
+ async syncAfterUserScriptsAllowed() {
92
+ await syncRegistrations();
93
+ },
94
+ // ---------- Refresh, add, install ----------
95
+ // The page reads storage for what changed, so these answer with no
96
+ // rows: a refresh's carry every body it loaded.
97
+ //
98
+ // Whether the script is still there afterwards: a refresh drops a
99
+ // row whose file is gone.
100
+ async updateScript(scriptId, mode, probe) {
101
+ return (await updateScript(scriptId, mode, probe)) !== null;
102
+ },
103
+ async updateCollection(collectionId, mode, probe) {
104
+ await updateCollection(collectionId, mode, probe);
105
+ },
106
+ async updateAllCollections(mode, probe) {
107
+ await updateAllCollections(mode, probe);
108
+ },
109
+ // A single script installed (or a `.user.js` added) is checked for a
110
+ // newer version at once, without the page waiting for it.
111
+ async addCollection(rawPath) {
112
+ const result = await addCollection(rawPath);
113
+ probeInstalledScript(result);
114
+ return result;
115
+ },
116
+ async installScript(rawLocation, target) {
117
+ const result = await installScript(rawLocation, target);
118
+ probeInstalledScript(result);
119
+ return result;
120
+ },
121
+ // ---------- Reads ----------
122
+ // GM values come from the worker's value store, which can hold writes
123
+ // not yet saved to storage, so a page reads them here rather than
124
+ // from storage (the backup export).
125
+ async readScriptValues(scriptIds) {
126
+ const read = await Promise.all(scriptIds.map((id) => peekValues(id)));
127
+ return Object.fromEntries(scriptIds.map((id, i) => [id, read[i]]));
128
+ },
129
+ // ---------- Local copy and site icons ----------
130
+ // After the page has written the files: it alone can download.
131
+ registerLocalCopy,
132
+ // One icon row's data URL: cached if still usable, else fetched and
133
+ // cached. `refreshCachedIcon` always fetches (the debug icons page).
134
+ ensureCachedIcon,
135
+ refreshCachedIcon,
136
+ deleteCachedIcon,
137
+ deleteAllCachedIcons,
138
+ };
139
+ function isWorkerOp(name) {
140
+ return Object.hasOwn(WORKER_OPS, name);
141
+ }
142
+ async function run(request) {
143
+ if (!isWorkerOp(request.op) || !Array.isArray(request.args)) {
144
+ throw new Error(`Internal error: There is no worker operation named ${request.op}`);
145
+ }
146
+ const fn = WORKER_OPS[request.op];
147
+ return await fn(...request.args);
148
+ }
149
+ // Answer pages' calls. `fromPage` is background.ts's sender check: only
150
+ // our own pages may ask, as for every page relay.
151
+ export function installWorkerOps(fromPage) {
152
+ chrome.runtime.onConnect.addListener((port) => {
153
+ if (port.name !== WORKER_OP_PORT || !port.sender || !fromPage(port.sender))
154
+ return;
155
+ let started = false;
156
+ port.onMessage.addListener((message) => {
157
+ // Later messages are the page's heartbeat: receiving them is what
158
+ // keeps this worker alive.
159
+ if (started || 'beat' in message)
160
+ return;
161
+ started = true;
162
+ const reply = (answer) => {
163
+ try {
164
+ port.postMessage(answer);
165
+ }
166
+ catch { /* the page closed */ }
167
+ };
168
+ run(message).then((result) => reply({ ok: true, result: result ?? null }), (err) => reply({ ok: false, error: describeError(err) }));
169
+ });
170
+ });
171
+ }
@@ -0,0 +1,24 @@
1
+ // Writes the backup export's zip (docs/export-format.md), through the
2
+ // vendored fflate library (src/vendor/fflate.d.ts).
3
+ //
4
+ // fflate deflates each entry and marks non-ASCII names as UTF-8, which
5
+ // Tampermonkey's and Violentmonkey's readers both honor.
6
+ import { zipSync } from '../vendor/fflate.js';
7
+ // The archive's bytes. A repeated path is the caller's bug, refused
8
+ // rather than letting the later one replace the earlier.
9
+ //
10
+ // fflate takes the entries as an object, so an integer-like path (`7`)
11
+ // is written before the others; the order otherwise holds.
12
+ export function buildZip(entries, now = Date.now()) {
13
+ // fflate writes no zip64, so past this the entry count would wrap.
14
+ if (entries.length > 0xffff)
15
+ throw new Error('The export has more files than a zip file can hold (65,535)');
16
+ // No prototype, so a path like `__proto__` is an ordinary key.
17
+ const files = Object.create(null);
18
+ for (const entry of entries) {
19
+ if (Object.hasOwn(files, entry.path))
20
+ throw new Error(`Internal error: The zip has two entries named ${entry.path}`);
21
+ files[entry.path] = [entry.data, { mtime: entry.modified ?? now }];
22
+ }
23
+ return zipSync(files);
24
+ }