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,413 @@
1
+ // Storage format 2: script rows without their bodies. Each body (a
2
+ // script's `bodyText`, each `@require` source, each `@resource`'s bytes)
3
+ // is a `blob:<sha256>` row, shared by every row whose body has the same
4
+ // bytes, and the script row points at it with a `BlobRef`. See
5
+ // docs/storage.md#content-blobs.
6
+ //
7
+ // This module converts between the two shapes:
8
+ // - `toStorage`: a full `StoredScript` (bodies inline, as the rest of
9
+ // the code uses it) → the small row plus the blobs it needs.
10
+ // - `fromStorage`: small rows → full `StoredScript`s, reading their
11
+ // blobs in one call.
12
+ // and runs the one-time migration of format 1 rows (bodies inline in
13
+ // storage too), and the cleanup of blobs no row points at any more.
14
+ //
15
+ // Both directions take each body as it finds it: one already inline (a
16
+ // format 1 row, or a full script handed to a write) stays as it is.
17
+ import { assertWorkerContext } from '../run-context.js';
18
+ import { base64ToBytes, bytesToBase64, sha256Hex } from '../bytes.js';
19
+ import { SCRIPT_PREFIX } from './structural-keys.js';
20
+ import { scanAllStorage } from './storage-scan.js';
21
+ import { dlog } from '../debug-log.js';
22
+ import { formatMs } from '../phase-timing.js';
23
+ export const BLOB_PREFIX = 'blob:';
24
+ export const STORE_FORMAT_KEY = 'storeFormat';
25
+ export const STORE_FORMAT = 2;
26
+ export const blobKey = (hash) => `${BLOB_PREFIX}${hash}`;
27
+ // Shown on a script whose row points at a blob that isn't there (or
28
+ // that this build can't decode): a row edited by hand, or a cleanup bug
29
+ // (see `sweepUnusedBlobs`). Refresh fetches the file again, as for one
30
+ // that failed to load.
31
+ export const MISSING_BLOB_MESSAGE = 'Part of the stored copy of this script is missing. Refresh the script to load it again.';
32
+ const encoder = new TextEncoder();
33
+ // `ignoreBOM` keeps a leading U+FEFF: the blob's bytes are exactly what
34
+ // was stored, and a require reading a blob a resource wrote first must
35
+ // get back the same text the require itself would have stored.
36
+ const decoder = new TextDecoder('utf-8', { ignoreBOM: true });
37
+ // The ref and stored data behind each object `fromStorage` built (a
38
+ // script, a require or resource entry), so writing it back unchanged
39
+ // doesn't hash its body again. Entries survive the `{...script}` copies
40
+ // the store's writers make; a script object often doesn't, and its body
41
+ // is hashed again then.
42
+ const knownRefs = new WeakMap();
43
+ // Lets `to` reuse the body ref `from` was read with, when its body is
44
+ // the same text: a refresh rebuilds a new script object whose body came
45
+ // from the copy it started from, and would otherwise hash it again.
46
+ export function carryBodyRef(from, to) {
47
+ const known = from ? knownRefs.get(from) : undefined;
48
+ if (known && known.data === to.bodyText)
49
+ knownRefs.set(to, known);
50
+ }
51
+ function reuse(owner, data, encoding, blobs) {
52
+ const known = knownRefs.get(owner);
53
+ if (known?.data !== data)
54
+ return null;
55
+ // Offered again: the write only stores it if it's gone.
56
+ blobs.set(blobKey(known.ref.hash), { encoding, data });
57
+ return known.ref;
58
+ }
59
+ async function textBlob(owner, text, blobs) {
60
+ const known = reuse(owner, text, 'text', blobs);
61
+ if (known)
62
+ return known;
63
+ const bytes = encoder.encode(text);
64
+ const hash = await sha256Hex(bytes);
65
+ blobs.set(blobKey(hash), { encoding: 'text', data: text });
66
+ return { hash, size: bytes.length };
67
+ }
68
+ async function base64Blob(owner, base64, blobs) {
69
+ const known = reuse(owner, base64, 'base64', blobs);
70
+ if (known)
71
+ return known;
72
+ const bytes = base64ToBytes(base64);
73
+ const hash = await sha256Hex(bytes);
74
+ blobs.set(blobKey(hash), { encoding: 'base64', data: base64 });
75
+ return { hash, size: bytes.byteLength };
76
+ }
77
+ // Does a stored row carry any body inline (format 1, or a full script
78
+ // handed to a write)?
79
+ export function hasInlineBodies(value) {
80
+ if (value === null || typeof value !== 'object')
81
+ return false;
82
+ const row = value;
83
+ return typeof row.bodyText === 'string'
84
+ || (row.requires ?? []).some((r) => typeof r.source === 'string')
85
+ || (row.resources ?? []).some((r) => typeof r.bytesBase64 === 'string');
86
+ }
87
+ // A full script (or a format 1 row) → its small row, adding the blobs it
88
+ // needs to `blobs`. A body already in blob form passes through.
89
+ export async function toStorage(script, blobs) {
90
+ const { bodyText, requires, resources, ...rest } = script;
91
+ const row = { ...rest };
92
+ // A row that never loaded has no body to point at (`size` is null
93
+ // exactly then); every other row does, an empty body included.
94
+ if (typeof bodyText === 'string' && (bodyText !== '' || script.size !== null)) {
95
+ row.bodyBlobRef = await textBlob(script, bodyText, blobs);
96
+ }
97
+ if (requires) {
98
+ row.requires = await Promise.all(requires.map(async (r) => {
99
+ const { source, ...meta } = r;
100
+ return typeof source === 'string'
101
+ ? { ...meta, blobRef: await textBlob(r, source, blobs) }
102
+ : r;
103
+ }));
104
+ }
105
+ if (resources) {
106
+ row.resources = await Promise.all(resources.map(async (r) => {
107
+ const { bytesBase64, ...meta } = r;
108
+ return typeof bytesBase64 === 'string'
109
+ ? { ...meta, blobRef: await base64Blob(r, bytesBase64, blobs) }
110
+ : r;
111
+ }));
112
+ }
113
+ return row;
114
+ }
115
+ // A blob's content in the form a reader wants, or null for an encoding
116
+ // this build doesn't know (written by a newer one), which reads like a
117
+ // missing blob rather than as garbage.
118
+ function blobText(blob) {
119
+ if (blob.encoding === 'text')
120
+ return blob.data;
121
+ if (blob.encoding === 'base64')
122
+ return decoder.decode(base64ToBytes(blob.data));
123
+ return null;
124
+ }
125
+ function blobBase64(blob) {
126
+ if (blob.encoding === 'base64')
127
+ return blob.data;
128
+ if (blob.encoding === 'text')
129
+ return bytesToBase64(encoder.encode(blob.data));
130
+ return null;
131
+ }
132
+ // The blob keys a stored row points at. A format 1 row has none.
133
+ export function blobKeysOf(value) {
134
+ if (value === null || typeof value !== 'object')
135
+ return [];
136
+ const row = value;
137
+ const keys = [];
138
+ if (row.bodyBlobRef)
139
+ keys.push(blobKey(row.bodyBlobRef.hash));
140
+ for (const r of row.requires ?? [])
141
+ if (r.blobRef)
142
+ keys.push(blobKey(r.blobRef.hash));
143
+ for (const r of row.resources ?? [])
144
+ if (r.blobRef)
145
+ keys.push(blobKey(r.blobRef.hash));
146
+ return keys;
147
+ }
148
+ // A row whose blob is gone keeps its metadata but can't run. As an
149
+ // `invalid` row, the next refresh fetches it whatever its validators
150
+ // say; dropping them (or the file entry, which a refresh then fetches
151
+ // like a new one) is a backstop for that.
152
+ //
153
+ // Only readers that load bodies see this. The registration sync is one,
154
+ // and it saves the row this way (`markMissingBlobs`), so the readers of
155
+ // small rows (pages, the match index) see it from then on.
156
+ //
157
+ // A write of a full script built here stores it as it is: an invalid
158
+ // row with an empty body. That's consistent, and the refresh repairs it
159
+ // the same way.
160
+ function withMissingBlob(script, bodyMissing) {
161
+ const out = { ...script, status: 'invalid', errorMessage: MISSING_BLOB_MESSAGE };
162
+ delete out.errorLocation;
163
+ if (bodyMissing) {
164
+ delete out.contentHash;
165
+ delete out.fsMtime;
166
+ delete out.httpEtag;
167
+ delete out.httpLastModified;
168
+ }
169
+ return out;
170
+ }
171
+ function hydrate(row, blobs) {
172
+ const get = (ref) => blobs[blobKey(ref.hash)];
173
+ // `bodyText` is there only on a row with its body inline.
174
+ const { bodyBlobRef, requires, resources, ...rest } = row;
175
+ let missing = false;
176
+ let bodyMissing = false;
177
+ const script = { ...rest, bodyText: rest.bodyText ?? '' };
178
+ // An inline body wins over a ref: it's what a format 1 row, or a test
179
+ // writing one, put there.
180
+ if (typeof rest.bodyText !== 'string' && bodyBlobRef) {
181
+ const blob = get(bodyBlobRef);
182
+ const text = blob ? blobText(blob) : null;
183
+ if (text === null)
184
+ missing = bodyMissing = true;
185
+ else {
186
+ script.bodyText = text;
187
+ knownRefs.set(script, { data: text, ref: bodyBlobRef });
188
+ }
189
+ }
190
+ if (requires) {
191
+ script.requires = requires.flatMap((r) => {
192
+ const inline = r;
193
+ if (typeof inline.source === 'string' || !r.blobRef)
194
+ return [inline];
195
+ const blob = get(r.blobRef);
196
+ const source = blob ? blobText(blob) : null;
197
+ if (source === null) {
198
+ missing = true;
199
+ return [];
200
+ }
201
+ const { blobRef, ...meta } = r;
202
+ const entry = { ...meta, source };
203
+ knownRefs.set(entry, { data: source, ref: blobRef });
204
+ return [entry];
205
+ });
206
+ }
207
+ if (resources) {
208
+ script.resources = resources.flatMap((r) => {
209
+ const inline = r;
210
+ if (typeof inline.bytesBase64 === 'string' || !r.blobRef)
211
+ return [inline];
212
+ const blob = get(r.blobRef);
213
+ const bytesBase64 = blob ? blobBase64(blob) : null;
214
+ if (bytesBase64 === null) {
215
+ missing = true;
216
+ return [];
217
+ }
218
+ const { blobRef, ...meta } = r;
219
+ const entry = { ...meta, bytesBase64 };
220
+ knownRefs.set(entry, { data: bytesBase64, ref: blobRef });
221
+ return [entry];
222
+ });
223
+ }
224
+ return missing ? withMissingBlob(script, bodyMissing) : script;
225
+ }
226
+ // A stored row as reading it shows when a blob it points at is missing
227
+ // or unreadable (see `withMissingBlob`), for saving it that way; null
228
+ // when all of its blobs are there. `blobs` holds what's stored under
229
+ // the row's blob keys. Bodies inline on the row (format 1) count as
230
+ // there.
231
+ export function markMissingBlobs(row, blobs) {
232
+ const there = (ref) => {
233
+ if (!ref)
234
+ return true;
235
+ const blob = blobs[blobKey(ref.hash)];
236
+ return blob?.encoding === 'text' || blob?.encoding === 'base64';
237
+ };
238
+ const bodyMissing = typeof row.bodyText !== 'string' && !there(row.bodyBlobRef);
239
+ const requires = row.requires?.filter((r) => there(r.blobRef));
240
+ const resources = row.resources?.filter((r) => there(r.blobRef));
241
+ if (!bodyMissing && requires?.length === row.requires?.length && resources?.length === row.resources?.length) {
242
+ return null;
243
+ }
244
+ const out = withMissingBlob({ ...row }, bodyMissing);
245
+ if (requires)
246
+ out.requires = requires;
247
+ if (resources)
248
+ out.resources = resources;
249
+ return out;
250
+ }
251
+ // Small rows → full scripts. `have` is blobs the caller already read
252
+ // (a whole-store scan has them all); the rest are read in one call.
253
+ export async function fromStorage(rows, have = {}) {
254
+ const need = [...new Set(rows.flatMap(blobKeysOf))].filter((k) => !(k in have));
255
+ const blobs = need.length > 0 ? { ...have, ...await chrome.storage.local.get(need) } : have;
256
+ return rows.map((row) => hydrate(row, blobs));
257
+ }
258
+ // In place over a `get` result: every `script:` value becomes a full
259
+ // script. For readers that take rows by key from a mixed read.
260
+ export async function hydrateScriptRows(got) {
261
+ const keys = Object.keys(got).filter((k) => k.startsWith(SCRIPT_PREFIX) && got[k] != null);
262
+ if (keys.length === 0)
263
+ return got;
264
+ const full = await fromStorage(keys.map((k) => got[k]), got);
265
+ keys.forEach((k, i) => { got[k] = full[i]; });
266
+ return got;
267
+ }
268
+ // A write's items with every full `script:` value split into its small
269
+ // row plus blobs. A blob already stored isn't written again: same key,
270
+ // same bytes. Rows and their blobs go out in the caller's one `set`, so
271
+ // no reader sees a row before its blobs.
272
+ export async function withBlobs(items) {
273
+ const keys = Object.keys(items).filter((k) => k.startsWith(SCRIPT_PREFIX) && hasInlineBodies(items[k]));
274
+ if (keys.length === 0)
275
+ return items;
276
+ const blobs = new Map();
277
+ const out = { ...items };
278
+ for (const k of keys)
279
+ out[k] = await toStorage(items[k], blobs);
280
+ return { ...out, ...newBlobItems(blobs, new Set(await chrome.storage.local.getKeys())) };
281
+ }
282
+ // A full script's stored form: its small row, and the blobs it points
283
+ // at. For a writer that compares the row with what's stored before
284
+ // deciding to write: it then writes the row and the blobs not yet
285
+ // stored, rather than the full script, which `withBlobs` would hash
286
+ // again.
287
+ export async function storedForm(script) {
288
+ const blobs = new Map();
289
+ return { row: await toStorage(script, blobs), blobs };
290
+ }
291
+ // The blobs among `blobs` whose keys aren't in `stored` (the keys in
292
+ // storage), as items for a `set`. A stored one has the same bytes under
293
+ // the same key, so it isn't written again.
294
+ export function newBlobItems(blobs, stored) {
295
+ const out = {};
296
+ for (const [k, blob] of blobs)
297
+ if (!stored.has(k))
298
+ out[k] = blob;
299
+ return out;
300
+ }
301
+ // What the given scripts' rows take in storage, without their bodies,
302
+ // as Chrome counts it: what a metadata read of them loads. For debug
303
+ // timing lines.
304
+ export async function scriptRowBytes(scriptIds) {
305
+ if (scriptIds.length === 0)
306
+ return 0;
307
+ return await chrome.storage.local.getBytesInUse(scriptIds.map((id) => SCRIPT_PREFIX + id));
308
+ }
309
+ // ---------- Migration from format 1 ----------
310
+ // Rows per `set`. Each call replaces its rows' inline bodies with blobs,
311
+ // so storage use barely moves within one call, and an interrupted run
312
+ // keeps what earlier calls converted.
313
+ export const MIGRATE_BATCH = 25;
314
+ // Set in session storage when a run fails, so a store too full to
315
+ // convert isn't rescanned on every worker wake. The next browser
316
+ // session tries again.
317
+ const MIGRATION_FAILED_KEY = 'storeFormatMigrationFailed';
318
+ // Converts every format 1 row, then records format 2. Safe to re-run:
319
+ // converted rows are skipped. Only the service worker runs it, before
320
+ // anything else there writes (see `storeFormatReady` in script-store).
321
+ //
322
+ // Logged in full whenever it moves anything, since that's once per
323
+ // profile; finding nothing to move, at each worker start or on a new
324
+ // profile, only with debug logging on.
325
+ export async function migrateToBlobs() {
326
+ const format = (await chrome.storage.local.get(STORE_FORMAT_KEY))[STORE_FORMAT_KEY];
327
+ if (typeof format === 'number' && format >= STORE_FORMAT) {
328
+ dlog('[SourceMonkey] storage format 2: already in place');
329
+ return;
330
+ }
331
+ if ((await chrome.storage.session.get(MIGRATION_FAILED_KEY))[MIGRATION_FAILED_KEY]) {
332
+ console.info('[SourceMonkey] storage format 2: not moving script bodies again until the browser restarts, since it failed earlier');
333
+ return;
334
+ }
335
+ const started = performance.now();
336
+ let moved = 0;
337
+ let written = 0;
338
+ try {
339
+ const all = await scanAllStorage('blob migration');
340
+ const stored = new Set(Object.keys(all));
341
+ const old = Object.entries(all).filter(([k, v]) => k.startsWith(SCRIPT_PREFIX) && hasInlineBodies(v));
342
+ if (old.length > 0) {
343
+ console.info(`[SourceMonkey] storage format 2: moving the bodies of ${old.length} script(s) to shared storage`);
344
+ }
345
+ for (let i = 0; i < old.length; i += MIGRATE_BATCH) {
346
+ const blobs = new Map();
347
+ const items = {};
348
+ const batch = old.slice(i, i + MIGRATE_BATCH);
349
+ for (const [k, v] of batch)
350
+ items[k] = await toStorage(v, blobs);
351
+ let fresh = 0;
352
+ for (const [k, blob] of blobs) {
353
+ if (stored.has(k))
354
+ continue;
355
+ items[k] = blob;
356
+ stored.add(k);
357
+ fresh++;
358
+ }
359
+ assertWorkerContext('write storage');
360
+ await chrome.storage.local.set(items);
361
+ moved += batch.length;
362
+ written += fresh;
363
+ }
364
+ await chrome.storage.local.set({ [STORE_FORMAT_KEY]: STORE_FORMAT });
365
+ if (old.length === 0) {
366
+ // A new profile, or one whose rows were all converted already.
367
+ dlog('[SourceMonkey] storage format 2: no script bodies to move');
368
+ }
369
+ else {
370
+ console.info(`[SourceMonkey] storage format 2: moved ${moved} script(s), ${written} stored file(s), `
371
+ + `in ${formatMs(performance.now() - started)}`);
372
+ }
373
+ }
374
+ catch (err) {
375
+ console.info(`[SourceMonkey] storage format 2: stopped after moving ${moved} script(s), ${written} stored file(s); `
376
+ + 'the rest stay as they were until the browser restarts');
377
+ await chrome.storage.session.set({ [MIGRATION_FAILED_KEY]: true });
378
+ throw err;
379
+ }
380
+ }
381
+ // Only for the test surface: forget a failed run, as a new browser
382
+ // session would.
383
+ export async function clearMigrationFailureForTests() {
384
+ await chrome.storage.session.remove(MIGRATION_FAILED_KEY);
385
+ }
386
+ // `rows`: every script row, when the caller has them (the worker's row
387
+ // cache); read from storage otherwise.
388
+ export async function sweepUnusedBlobs(rows) {
389
+ const keys = await chrome.storage.local.getKeys();
390
+ const blobKeys = keys.filter((k) => k.startsWith(BLOB_PREFIX));
391
+ if (blobKeys.length === 0)
392
+ return { checked: 0, removed: 0 };
393
+ // The small rows only: refs are all a check needs.
394
+ const scriptRows = rows
395
+ ?? await chrome.storage.local.get(keys.filter((k) => k.startsWith(SCRIPT_PREFIX)));
396
+ const used = new Set(Object.values(scriptRows).flatMap(blobKeysOf));
397
+ const remove = blobKeys.filter((k) => !used.has(k));
398
+ if (remove.length > 0) {
399
+ assertWorkerContext('write storage');
400
+ await chrome.storage.local.remove(remove);
401
+ }
402
+ return { checked: blobKeys.length, removed: remove.length };
403
+ }
404
+ // Did a change to a script row leave a blob it pointed at unused by it:
405
+ // a removed row, or one now pointing elsewhere? The worker's cue to
406
+ // sweep.
407
+ export function droppedBlobRefs(oldValue, newValue) {
408
+ const before = blobKeysOf(oldValue);
409
+ if (before.length === 0)
410
+ return false;
411
+ const after = new Set(blobKeysOf(newValue));
412
+ return before.some((k) => !after.has(k));
413
+ }