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,19 @@
1
+ // Keeps Chrome's "ResizeObserver loop completed with undelivered
2
+ // notifications" out of chrome://extensions/?errors.
3
+ //
4
+ // Chrome raises it when a ResizeObserver callback resizes something it
5
+ // watches again within the same frame. The notification isn't lost: it
6
+ // is delivered on the next frame, so it is harmless. Web pages don't log
7
+ // it, but on an extension page it lands in the Errors panel, which makes
8
+ // SourceMonkey look broken.
9
+ //
10
+ // Canceling the window `error` event stops Chrome from reporting it. The
11
+ // text is Chrome's own, so matching on it is the only way to tell it
12
+ // apart.
13
+ const LOOP_MESSAGE = 'ResizeObserver loop';
14
+ export function ignoreResizeObserverLoopErrors() {
15
+ window.addEventListener('error', (ev) => {
16
+ if (ev.message?.startsWith(LOOP_MESSAGE))
17
+ ev.preventDefault();
18
+ });
19
+ }
@@ -0,0 +1,387 @@
1
+ // The service worker's copy of the structural rows (the collection
2
+ // index, collection rows, script rows) and the active times, so a lookup
3
+ // across every script (finding a copy's related copies on an enable
4
+ // click, say) reads memory instead of storage. See
5
+ // docs/worker-writes.md#the-row-cache.
6
+ //
7
+ // Right by construction, not by expiry:
8
+ // - Only the worker writes these rows (docs/worker-writes.md#rules),
9
+ // and `installRowCache` wraps this context's `chrome.storage.local`
10
+ // writes, so every write it makes lands here as it lands in storage.
11
+ // - As a safety net, a change event this context didn't cause (a write
12
+ // from anywhere else) drops the copy, and the next full read loads
13
+ // it again from storage.
14
+ //
15
+ // Loaded by the first read that wants every row: at a worker start, the
16
+ // match index, the log labels, sticky numbers or the registration sync,
17
+ // which share the one load. A point read before then goes to storage.
18
+ //
19
+ // Rows are stored frozen, as the JSON storage would keep. Most reads hand
20
+ // out copies, which a caller may change. `sharedRows` hands out the
21
+ // frozen rows themselves, for the worker's own readers that only read
22
+ // (the registration sync, the match index, the log labels, the
23
+ // numbers): a change to one throws, so it can't reach the cache.
24
+ import { ENABLEMENT_TIMES_KEY, isStructuralKey, SCRIPT_PREFIX } from './structural-keys.js';
25
+ import { identityKey, locationKey } from './script-keys.js';
26
+ import { scanStorageRows } from './storage-scan.js';
27
+ import { dlog } from '../debug-log.js';
28
+ // The active times too: every enable re-reads them inside its lock,
29
+ // and from storage that read waits behind whatever else is reading it.
30
+ function isCachedKey(key) {
31
+ return isStructuralKey(key) || key === ENABLEMENT_TIMES_KEY;
32
+ }
33
+ let installed = false;
34
+ let rows = null;
35
+ // Bumped by every change to the cached rows, so a load that overlapped
36
+ // one knows its read may predate it.
37
+ let generation = 0;
38
+ let loading = null;
39
+ // How many times it was read from storage, for tests.
40
+ let loads = 0;
41
+ // Every write this context makes to a cached key gets the next number.
42
+ // A write lands in the cache when its change event arrives or its call
43
+ // returns, whichever is first, and an older write never replaces a
44
+ // newer one (`applied`).
45
+ let nextSeq = 0;
46
+ // Writes at or below this number were in storage when the cache loaded.
47
+ let loadedAtSeq = 0;
48
+ const applied = new Map();
49
+ // Writes to cached keys started and not yet returned: a load waits for
50
+ // none.
51
+ let inFlight = 0;
52
+ let idleWaiters = [];
53
+ const pending = new Map();
54
+ const PENDING_TTL_MS = 30_000;
55
+ const MAX_PENDING_PER_KEY = 16;
56
+ const REMOVED = '\u0000removed';
57
+ // Key order doesn't count: Chrome keeps an object's keys sorted, so a
58
+ // change event's `newValue` can list them in another order than the
59
+ // value written.
60
+ function stableJson(value) {
61
+ if (value === undefined)
62
+ return REMOVED;
63
+ return JSON.stringify(value, (_key, v) => {
64
+ if (!v || typeof v !== 'object' || Array.isArray(v))
65
+ return v;
66
+ const sorted = {};
67
+ for (const k of Object.keys(v).sort())
68
+ sorted[k] = v[k];
69
+ return sorted;
70
+ });
71
+ }
72
+ function apply(key, json, seq) {
73
+ if (!rows || seq <= loadedAtSeq || seq <= (applied.get(key) ?? 0))
74
+ return;
75
+ unindex(key);
76
+ if (json === REMOVED)
77
+ rows.delete(key);
78
+ else {
79
+ rows.set(key, deepFreeze(JSON.parse(json)));
80
+ index(key);
81
+ }
82
+ applied.set(key, seq);
83
+ }
84
+ function drop(reason) {
85
+ if (rows)
86
+ dlog(`[SourceMonkey] row cache dropped: ${reason}`);
87
+ rows = null;
88
+ applied.clear();
89
+ byLocation.clear();
90
+ byIdentity.clear();
91
+ byCollection.clear();
92
+ generation++;
93
+ }
94
+ // ---------- Indexes ----------
95
+ //
96
+ // Script ids by the two keys that make copies of one script related
97
+ // (script-identity.ts): the canonical location, and `@namespace` +
98
+ // `@name`. Kept with the rows, so they change exactly when a row does.
99
+ const byLocation = new Map();
100
+ const byIdentity = new Map();
101
+ // And by the collection each row names (`collectionId`), which isn't
102
+ // always one that lists it (see `reconcileScriptMembership`).
103
+ const byCollection = new Map();
104
+ function keysOf(row) {
105
+ return {
106
+ location: typeof row.fileUrl === 'string' ? locationKey(row.fileUrl) : null,
107
+ identity: identityKey(row.header ?? null),
108
+ };
109
+ }
110
+ function addTo(map, key, id) {
111
+ if (key === null)
112
+ return;
113
+ let ids = map.get(key);
114
+ if (!ids)
115
+ map.set(key, ids = new Set());
116
+ ids.add(id);
117
+ }
118
+ function removeFrom(map, key, id) {
119
+ if (key === null)
120
+ return;
121
+ const ids = map.get(key);
122
+ if (!ids)
123
+ return;
124
+ ids.delete(id);
125
+ if (ids.size === 0)
126
+ map.delete(key);
127
+ }
128
+ function index(key) {
129
+ if (!key.startsWith(SCRIPT_PREFIX))
130
+ return;
131
+ const row = rows?.get(key);
132
+ if (!row?.id)
133
+ return;
134
+ const { location, identity } = keysOf(row);
135
+ addTo(byLocation, location, row.id);
136
+ addTo(byIdentity, identity, row.id);
137
+ addTo(byCollection, row.collectionId ?? null, row.id);
138
+ }
139
+ function unindex(key) {
140
+ if (!key.startsWith(SCRIPT_PREFIX))
141
+ return;
142
+ const row = rows?.get(key);
143
+ if (!row?.id)
144
+ return;
145
+ const { location, identity } = keysOf(row);
146
+ removeFrom(byLocation, location, row.id);
147
+ removeFrom(byIdentity, identity, row.id);
148
+ removeFrom(byCollection, row.collectionId ?? null, row.id);
149
+ }
150
+ // The ids of every script row naming collection `id`, loading the cache
151
+ // first if needed. Null when the cache isn't on in this context.
152
+ export async function scriptIdsInCollection(id) {
153
+ if (!(await loaded()))
154
+ return null;
155
+ return [...(byCollection.get(id) ?? [])];
156
+ }
157
+ // The ids of every stored copy related to any of `scripts` (the same
158
+ // location, or the same `@namespace` + `@name`), themselves included,
159
+ // loading the cache first if needed. Null when the cache isn't on in
160
+ // this context: the caller scans the rows itself.
161
+ export async function relatedScriptIds(scripts) {
162
+ if (!(await loaded()))
163
+ return null;
164
+ const out = new Set();
165
+ for (const script of scripts) {
166
+ if (script.id)
167
+ out.add(script.id);
168
+ const { location, identity } = keysOf(script);
169
+ for (const id of (location !== null && byLocation.get(location)) || [])
170
+ out.add(id);
171
+ for (const id of (identity !== null && byIdentity.get(identity)) || [])
172
+ out.add(id);
173
+ }
174
+ return out;
175
+ }
176
+ // Run one of this context's writes, `entries` being its cached keys and
177
+ // their values (`REMOVED` for a removal).
178
+ async function track(entries, write) {
179
+ if (entries.length === 0)
180
+ return await write();
181
+ const now = Date.now();
182
+ const mine = entries.map(([key, json]) => {
183
+ const entry = { json, seq: ++nextSeq, at: now };
184
+ const list = (pending.get(key) ?? []).filter((p) => now - p.at < PENDING_TTL_MS);
185
+ list.push(entry);
186
+ if (list.length > MAX_PENDING_PER_KEY)
187
+ list.shift();
188
+ pending.set(key, list);
189
+ return [key, entry];
190
+ });
191
+ inFlight++;
192
+ try {
193
+ await write();
194
+ }
195
+ catch (err) {
196
+ for (const [key, entry] of mine) {
197
+ const list = pending.get(key);
198
+ const at = list ? list.indexOf(entry) : -1;
199
+ if (at >= 0)
200
+ list.splice(at, 1);
201
+ }
202
+ throw err;
203
+ }
204
+ finally {
205
+ if (--inFlight === 0) {
206
+ const waiters = idleWaiters;
207
+ idleWaiters = [];
208
+ for (const wake of waiters)
209
+ wake();
210
+ }
211
+ }
212
+ generation++;
213
+ for (const [key, entry] of mine)
214
+ apply(key, entry.json, entry.seq);
215
+ }
216
+ // Wrap this context's writes, and watch for others'. Called once, by the
217
+ // service worker at startup, before anything writes. Its change listener
218
+ // runs before every listener installed after it (one installed at module
219
+ // load, the popup's script cache, only drops entries). Nothing else
220
+ // turns the cache on, so pages and unit tests read storage as before.
221
+ //
222
+ // `checkWrite` sees the keys of every `set` first, and throws to refuse
223
+ // it: the store's check that script rows are written under its lock
224
+ // (`checkScriptRowWrite`).
225
+ export function installRowCache(checkWrite = () => undefined) {
226
+ if (installed)
227
+ return;
228
+ installed = true;
229
+ const area = chrome.storage.local;
230
+ const set = area.set.bind(area);
231
+ const remove = area.remove.bind(area);
232
+ const clear = area.clear.bind(area);
233
+ area.set = ((items) => {
234
+ try {
235
+ checkWrite(Object.keys(items));
236
+ }
237
+ catch (err) {
238
+ return Promise.reject(err);
239
+ }
240
+ // A key set to `undefined` is left as it is by Chrome.
241
+ const entries = Object.entries(items)
242
+ .filter(([key, value]) => isCachedKey(key) && value !== undefined)
243
+ .map(([key, value]) => [key, stableJson(value)]);
244
+ return track(entries, () => set(items));
245
+ });
246
+ area.remove = ((keys) => {
247
+ const entries = (Array.isArray(keys) ? keys : [keys])
248
+ .filter(isCachedKey)
249
+ .map((key) => [key, REMOVED]);
250
+ return track(entries, () => remove(keys));
251
+ });
252
+ area.clear = (async () => {
253
+ await clear();
254
+ pending.clear();
255
+ drop('storage cleared');
256
+ });
257
+ chrome.storage.onChanged.addListener((changes, areaName) => {
258
+ if (areaName !== 'local')
259
+ return;
260
+ const now = Date.now();
261
+ for (const [key, change] of Object.entries(changes)) {
262
+ if (!isCachedKey(key))
263
+ continue;
264
+ const json = stableJson(change.newValue);
265
+ const list = (pending.get(key) ?? []).filter((p) => now - p.at < PENDING_TTL_MS);
266
+ const at = list.findIndex((p) => p.json === json);
267
+ if (at >= 0) {
268
+ // Ours. Earlier writes to this key that got no event of their
269
+ // own are passed now too.
270
+ apply(key, json, list[at].seq);
271
+ list.splice(0, at + 1);
272
+ }
273
+ if (list.length > 0)
274
+ pending.set(key, list);
275
+ else
276
+ pending.delete(key);
277
+ if (at >= 0)
278
+ continue;
279
+ if (rows)
280
+ drop(`${key} written elsewhere`);
281
+ else
282
+ generation++;
283
+ }
284
+ });
285
+ }
286
+ // Every cached row, loaded first when needed. Null when the cache isn't
287
+ // on in this context, or a write kept landing during the load (three
288
+ // tries): the caller reads storage itself then, and the next read tries
289
+ // to load again. Under constant writes (a Refresh All) that is a scan
290
+ // per read until the writes pause.
291
+ async function loaded() {
292
+ if (!installed)
293
+ return null;
294
+ if (rows)
295
+ return rows;
296
+ loading ??= (async () => {
297
+ try {
298
+ for (let attempt = 0; attempt < 3; attempt++) {
299
+ // With no write in flight, every write numbered so far is in
300
+ // what the scan reads.
301
+ while (inFlight > 0)
302
+ await new Promise((wake) => idleWaiters.push(wake));
303
+ const start = generation;
304
+ const startSeq = nextSeq;
305
+ loads++;
306
+ const read = await scanStorageRows('row cache', isCachedKey);
307
+ if (generation !== start)
308
+ continue;
309
+ rows = new Map(Object.entries(read).map(([k, v]) => [k, deepFreeze(v)]));
310
+ byLocation.clear();
311
+ byIdentity.clear();
312
+ byCollection.clear();
313
+ for (const key of rows.keys())
314
+ index(key);
315
+ loadedAtSeq = startSeq;
316
+ applied.clear();
317
+ return rows;
318
+ }
319
+ return null;
320
+ }
321
+ finally {
322
+ loading = null;
323
+ }
324
+ })();
325
+ return await loading;
326
+ }
327
+ // `keys`' rows, as copies, for the keys the cache holds, when it is
328
+ // loaded: `rest` is the keys it doesn't keep, for the caller to read
329
+ // from storage. Null when it isn't loaded, without loading it.
330
+ export function cachedRows(keys) {
331
+ if (!rows)
332
+ return null;
333
+ const found = {};
334
+ const rest = [];
335
+ for (const key of keys) {
336
+ if (!isCachedKey(key))
337
+ rest.push(key);
338
+ else if (rows.has(key))
339
+ found[key] = structuredClone(rows.get(key));
340
+ }
341
+ return { found, rest };
342
+ }
343
+ // Every script row, as copies, loading the cache first if needed. Null
344
+ // when the cache isn't on in this context.
345
+ export async function cachedScriptRows() {
346
+ return await cachedRowsWhere((key) => key.startsWith(SCRIPT_PREFIX));
347
+ }
348
+ // Every cached row (index, collections, scripts, active times), as
349
+ // copies, likewise.
350
+ export async function allCachedRows() {
351
+ return await cachedRowsWhere(() => true);
352
+ }
353
+ async function cachedRowsWhere(keep) {
354
+ const all = await loaded();
355
+ if (!all)
356
+ return null;
357
+ const out = {};
358
+ for (const [key, value] of all) {
359
+ if (keep(key))
360
+ out[key] = structuredClone(value);
361
+ }
362
+ return out;
363
+ }
364
+ // Every cached row whose key `keep` accepts, the frozen rows themselves
365
+ // rather than copies, loading the cache first if needed. Null when the
366
+ // cache isn't on in this context: the caller reads storage then.
367
+ export async function sharedRows(keep) {
368
+ const all = await loaded();
369
+ if (!all)
370
+ return null;
371
+ const out = {};
372
+ for (const [key, value] of all)
373
+ if (keep(key))
374
+ out[key] = value;
375
+ return out;
376
+ }
377
+ function deepFreeze(value) {
378
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
379
+ Object.freeze(value);
380
+ for (const v of Object.values(value))
381
+ deepFreeze(v);
382
+ }
383
+ return value;
384
+ }
385
+ export function rowCacheLoadsForTests() {
386
+ return loads;
387
+ }