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,53 @@
1
+ "use strict";
2
+ // Prism grammar extension for the `// ==UserScript== … //
3
+ // ==/UserScript==` metadata block.
4
+ //
5
+ // Importing this module is what installs the grammar — it runs the
6
+ // `insertBefore` at load. It lives next to the code that depends on it
7
+ // (`code-tabs.ts` calls `Prism.highlightElement`, `code-links.ts` keys
8
+ // its header linkification off the token classes below), so a page
9
+ // that reuses the tabbed code view can't end up with the extension
10
+ // silently missing.
11
+ //
12
+ // The outer token keeps the `comment` alias so any non-tag line inside
13
+ // the block still reads as comment-grey. Inner tokens use existing
14
+ // Prism theme classes via `alias`:
15
+ // - markers (`==UserScript==`, `==/UserScript==`, sans `//`) → `bold`
16
+ // - `@tag` names → `constant` (matches identifier color in the body)
17
+ // - values after the tag → `string` (matches string-literal color)
18
+ // Order inside matters: `header-tag-value` must run before
19
+ // `header-tag-name`, because the value's lookbehind needs the `@tag`
20
+ // substring to still be present in the unconsumed text when it scans.
21
+ Prism.languages.insertBefore('javascript', 'comment', {
22
+ 'userscript-header': {
23
+ // Trailing `[ \t]*\r?\n?` swallows whitespace + the line-terminating
24
+ // newline after `==/UserScript==` (CRLF and LF). We need the
25
+ // newline inside the span because we render the span as
26
+ // `display: block` for the tinted band, and a newline left in
27
+ // the text node *after* the block would render as an extra blank
28
+ // line on top of the block break the browser already inserts.
29
+ pattern: /\/\/[ \t]*==UserScript==[\s\S]*?\/\/[ \t]*==\/UserScript==[ \t]*\r?\n?/,
30
+ alias: 'comment',
31
+ inside: {
32
+ 'header-marker': {
33
+ pattern: /(\/\/[ \t]*)==\/?UserScript==/,
34
+ lookbehind: true,
35
+ alias: 'bold',
36
+ },
37
+ 'header-tag-value': {
38
+ // `[^\r\n]*` (not `[^\n]*`) so a CRLF file doesn't sweep the
39
+ // line-terminating `\r` into the value span, which would
40
+ // leak a trailing `\r` into anchor hrefs / `matchTargetInfo`
41
+ // input downstream.
42
+ pattern: /(\/\/[ \t]*@[A-Za-z][\w-]*(?::[\w-]+)?[ \t]+)\S[^\r\n]*/,
43
+ lookbehind: true,
44
+ alias: 'string',
45
+ },
46
+ 'header-tag-name': {
47
+ pattern: /(\/\/[ \t]*)@[A-Za-z][\w-]*(?::[\w-]+)?/,
48
+ lookbehind: true,
49
+ alias: 'constant',
50
+ },
51
+ },
52
+ },
53
+ });
@@ -0,0 +1,136 @@
1
+ // Per-collection record of a refresh that could not be SAVED.
2
+ //
3
+ // A distinct failure from `scanError`, and the distinction is the point:
4
+ //
5
+ // - `scanError` (on the collection row) means we couldn't list the
6
+ // collection. Nothing was fetched, every script row is left exactly
7
+ // as it was, and the reason is written next to the collection it
8
+ // belongs to.
9
+ // - This map means the listing and every fetch SUCCEEDED and the
10
+ // write at the end is what failed. The scripts on disk and the rows
11
+ // in storage now disagree, and nothing in `chrome.storage.local`
12
+ // records that — because the write to `chrome.storage.local` is
13
+ // precisely what didn't work.
14
+ //
15
+ // The motivating case is an exhausted `chrome.storage.local` quota
16
+ // (10 MB, shared by the whole extension — see [docs/storage.md]). A
17
+ // collection large enough to blow the budget fails at the write on
18
+ // every refresh, and before this map the user got a Refresh button that
19
+ // went busy, came back, and said nothing at all.
20
+ //
21
+ // Why `chrome.storage.session`:
22
+ // - It is a SEPARATE quota from `chrome.storage.local`, so it still
23
+ // has room in the exact situation this exists to report. (A
24
+ // rejected write also consumes nothing, so the budget the failed
25
+ // refresh would have needed is still free either way — but not
26
+ // sharing the failing bucket is what makes that reliable rather
27
+ // than merely likely.)
28
+ // - The lifetime is right. "Your last refresh didn't save" describes
29
+ // this session's attempt, not a property of the collection. A new
30
+ // browser session re-refreshes from scratch, and a stale banner
31
+ // about a write that a later run may well have completed would be
32
+ // worse than none.
33
+ // - It outlives the service worker, which MV3 evicts freely.
34
+ //
35
+ // Shaped as one map key rather than a key per collection for the same
36
+ // reason as `injector-rejections` (`src/extension/script-rejection.ts`): the set
37
+ // is small, the readers want all of it, and a single key means one
38
+ // `storage.onChanged` event rather than a fan-out.
39
+ import { assertWorkerContext } from '../run-context.js';
40
+ // Session-storage key holding the whole `RefreshErrorMap`.
41
+ export const REFRESH_ERRORS_KEY = 'refresh-errors';
42
+ // The one sentence for a refresh that couldn't be saved, shared by the
43
+ // two surfaces that report the failure itself: the collection's section
44
+ // message on the collections page, and the Log row fanned out to every
45
+ // script under it (from either refresh entry point). One message per
46
+ // problem, so those can't drift.
47
+ //
48
+ // The page banner deliberately does NOT render this. It reports the
49
+ // aggregate — that some collection failed, and how full storage is —
50
+ // which is a different statement from what happened to one collection.
51
+ //
52
+ // Names the consequence rather than the mechanism: what matters to the
53
+ // reader is that the scripts they can see are not the scripts that are
54
+ // stored, and that retrying is the action.
55
+ export function describeRefreshError(reason) {
56
+ return `This collection was read but could not be saved, so its scripts are unchanged: ${reason}`;
57
+ }
58
+ // Coerce an unknown storage value into a RefreshErrorMap, dropping any
59
+ // entry whose reason isn't a string. Used for both the direct read and
60
+ // the `storage.onChanged` newValue, so the two paths can't disagree
61
+ // about what a well-formed record looks like.
62
+ export function coerceRefreshErrorMap(value) {
63
+ if (!value || typeof value !== 'object')
64
+ return {};
65
+ const out = {};
66
+ for (const [id, message] of Object.entries(value)) {
67
+ if (typeof message === 'string')
68
+ out[id] = message;
69
+ }
70
+ return out;
71
+ }
72
+ // Every collection whose last refresh failed to save. Empty on a
73
+ // healthy profile, which is the overwhelmingly common case.
74
+ export async function readRefreshErrors() {
75
+ try {
76
+ const got = await chrome.storage.session.get(REFRESH_ERRORS_KEY);
77
+ return coerceRefreshErrorMap(got[REFRESH_ERRORS_KEY]);
78
+ }
79
+ catch {
80
+ // Session storage being unavailable must not turn a reportable
81
+ // failure into a thrown one — the caller is already handling an
82
+ // error. Worst case the page doesn't show the banner.
83
+ return {};
84
+ }
85
+ }
86
+ // Record (or clear, with `reason: null`) one collection's write failure.
87
+ //
88
+ // Never throws. Every call site is already on a failure path or a
89
+ // success path that must not be derailed by bookkeeping, so a session
90
+ // write that fails leaves the map as it was and says so only to the
91
+ // console.
92
+ //
93
+ // Skips the write when nothing changes, so the overwhelmingly common
94
+ // case — a healthy refresh clearing an error that was never set — costs
95
+ // one read and no `storage.onChanged` fan-out to the open pages.
96
+ export async function setRefreshError(collectionId, reason) {
97
+ assertWorkerContext('write storage');
98
+ try {
99
+ const prior = await readRefreshErrors();
100
+ if (reason === null && !(collectionId in prior))
101
+ return;
102
+ if (reason !== null && prior[collectionId] === reason)
103
+ return;
104
+ const next = { ...prior };
105
+ if (reason === null)
106
+ delete next[collectionId];
107
+ else
108
+ next[collectionId] = reason;
109
+ await chrome.storage.session.set({ [REFRESH_ERRORS_KEY]: next });
110
+ }
111
+ catch (err) {
112
+ console.info('[SourceMonkey] could not record a refresh error', err);
113
+ }
114
+ }
115
+ // Drop entries for collections that no longer exist, so a removed
116
+ // collection can't leave a banner counting a section that isn't on the
117
+ // page. Called after a removal rather than on a timer; `liveIds` is the
118
+ // authoritative set.
119
+ export async function pruneRefreshErrors(liveIds) {
120
+ assertWorkerContext('write storage');
121
+ try {
122
+ const prior = await readRefreshErrors();
123
+ const live = new Set(liveIds);
124
+ const next = {};
125
+ for (const [id, reason] of Object.entries(prior)) {
126
+ if (live.has(id))
127
+ next[id] = reason;
128
+ }
129
+ if (Object.keys(next).length === Object.keys(prior).length)
130
+ return;
131
+ await chrome.storage.session.set({ [REFRESH_ERRORS_KEY]: next });
132
+ }
133
+ catch (err) {
134
+ console.info('[SourceMonkey] could not prune refresh errors', err);
135
+ }
136
+ }
@@ -0,0 +1,160 @@
1
+ // Which collections / scripts a targeted refresh should touch.
2
+ //
3
+ // The collections page's `?refresh` does a full Refresh All, which
4
+ // re-reads every http(s) and GitHub collection too — slow, and pointless
5
+ // when the thing that changed was a file on disk. The two narrower
6
+ // triggers (`?refresh_local`, `?refresh_file=<path>`) pick their targets
7
+ // with the pure helpers here, so the selection logic is testable without
8
+ // a page.
9
+ //
10
+ // Local collections come in two shapes and each needs its own treatment:
11
+ // - A scanned collection (a directory, or a local `script_manifest.json`)
12
+ // refreshes as a WHOLE, because only a re-scan notices files that
13
+ // were added, renamed, or deleted.
14
+ // - A container (`container:*`) has no directory of its own — each
15
+ // script row carries its own absolute location, and a container may
16
+ // hold web scripts alongside local ones — so its local rows are
17
+ // selected individually.
18
+ //
19
+ // "Local" is decided by where the COLLECTION lives, not by where its
20
+ // rows point. So an http(s) manifest listing a `file://` entry is never
21
+ // selected (refreshing it would mean re-reading the manifest over the
22
+ // network, which is the cost these triggers exist to avoid), and a
23
+ // local manifest listing http(s) entries refreshes them along with the
24
+ // rest of its collection. See TODO.md.
25
+ import { isContainerCollection, isFileUrl, isManifestCollection, isNonPosixFileUrl, locationFsPath, parentDir, } from '../file-locations.js';
26
+ import { inferCollectionType } from './collection-display.js';
27
+ // True for collections that can hold content on the local disk —
28
+ // scanned directories, local manifests, and every container (whose
29
+ // rows are then filtered one by one). Shares `inferCollectionType` with
30
+ // the UI's type bubble so "local" means the same thing the user sees on
31
+ // the page.
32
+ export function isLocalCollection(collection) {
33
+ return inferCollectionType(collection.path) === 'file';
34
+ }
35
+ // Absolute filesystem path for a `file://` URL or a raw path, with any
36
+ // trailing slash removed so directory and file spellings compare the
37
+ // same way. Non-local inputs (http(s) URLs, the synthetic
38
+ // `container:*` paths) have no filesystem form — they come back as-is
39
+ // and simply fail every comparison below.
40
+ export function toFsPath(input) {
41
+ const p = locationFsPath(input.trim());
42
+ return p.length > 1 ? p.replace(/\/+$/, '') : p;
43
+ }
44
+ // True when `child` is `parent` itself or sits somewhere beneath it.
45
+ // The `/` boundary check keeps `/a/scripts-old` from reading as a child
46
+ // of `/a/scripts`. An empty `parent` is nobody's ancestor — without the
47
+ // guard its `base` would be `/`, which every absolute path is under.
48
+ function isAtOrUnder(child, parent) {
49
+ if (parent === '')
50
+ return false;
51
+ if (child === parent)
52
+ return true;
53
+ const base = parent === '/' ? '/' : parent + '/';
54
+ return child.startsWith(base);
55
+ }
56
+ // True when the two paths refer to the same place or one contains the
57
+ // other. Both directions matter: a `?refresh_file=/a/b/x.user.js` has to
58
+ // find the `/a/b` collection that scans it, and a
59
+ // `?refresh_file=/a` (the user pointing at a whole tree) has to find the
60
+ // `/a/b` collections inside it.
61
+ export function pathsOverlap(a, b) {
62
+ return isAtOrUnder(a, b) || isAtOrUnder(b, a);
63
+ }
64
+ // Directory a scanned collection covers: the manifest's own directory
65
+ // for a manifest collection, the path itself otherwise.
66
+ function collectionScanDir(path) {
67
+ const fs = toFsPath(path);
68
+ return isManifestCollection(path) ? parentDir(fs) : fs;
69
+ }
70
+ // Every local collection, refreshed whole — except containers, whose
71
+ // local rows are picked out individually so their web rows aren't
72
+ // refetched. Used by `?refresh_local`.
73
+ export function localRefreshTargets(entries) {
74
+ const collectionIds = [];
75
+ const scripts = [];
76
+ for (const { collection, scripts: rows } of entries) {
77
+ if (!isLocalCollection(collection))
78
+ continue;
79
+ if (isContainerCollection(collection.path)) {
80
+ for (const s of rows) {
81
+ if (isFileUrl(s.fileUrl))
82
+ scripts.push({ id: s.id, collectionId: collection.id });
83
+ }
84
+ continue;
85
+ }
86
+ collectionIds.push(collection.id);
87
+ }
88
+ return { collectionIds, scripts };
89
+ }
90
+ // Local collections and scripts touching `target` — a file or directory
91
+ // path (raw or `file://`). Used by `?refresh_file=<path>`.
92
+ //
93
+ // A scanned collection is selected when its directory overlaps the
94
+ // target, so a rename or a brand-new file in that directory is picked
95
+ // up even though no existing row points at it. It's also selected when
96
+ // one of its rows points at the target even if the directory test
97
+ // missed — a manifest may list entries outside its own directory.
98
+ export function pathRefreshTargets(entries, targetPath) {
99
+ const target = toFsPath(targetPath);
100
+ const collectionIds = [];
101
+ const scriptTargets = [];
102
+ for (const { collection, scripts } of entries) {
103
+ if (!isLocalCollection(collection))
104
+ continue;
105
+ const matchingScripts = scripts.filter((s) => scriptMatchesPath(s, target));
106
+ if (isContainerCollection(collection.path)) {
107
+ // No directory to re-scan: the container's rows ARE its contents,
108
+ // so refresh exactly the ones that point at the target.
109
+ for (const s of matchingScripts) {
110
+ scriptTargets.push({ id: s.id, collectionId: collection.id });
111
+ }
112
+ continue;
113
+ }
114
+ if (pathsOverlap(collectionScanDir(collection.path), target) || matchingScripts.length > 0) {
115
+ collectionIds.push(collection.id);
116
+ }
117
+ }
118
+ return { collectionIds, scripts: scriptTargets };
119
+ }
120
+ // True when a stored row's file is the target or lives under it. Only
121
+ // local rows can match — a container holds http(s) and GitHub scripts
122
+ // alongside the local ones.
123
+ function scriptMatchesPath(script, target) {
124
+ if (!isFileUrl(script.fileUrl))
125
+ return false;
126
+ return isAtOrUnder(toFsPath(script.fileUrl), target);
127
+ }
128
+ // What every `?refresh_file=` rejection leads with, so the line on the
129
+ // page says which action didn't happen before it says why. The tail is
130
+ // a message in its own right, hence its capital.
131
+ export const REFRESH_FAILED_PREFIX = 'Refresh failed: ';
132
+ // Shapes `?refresh_file=` accepts, as the tail of "Refresh path …".
133
+ // Narrower than `LOCATION_SHAPES`: the trigger exists for files on
134
+ // local disk, so an http(s) URL has nothing to refresh against.
135
+ export const REFRESH_PATH_SHAPES = 'must be a file:// URL, or an absolute path starting with /';
136
+ // Why a `?refresh_file=` value can't be used, or null when it can.
137
+ // Rejects the shapes `toFsPath` would mangle rather than comparing a
138
+ // path that points somewhere other than what was asked for:
139
+ // - Windows drive-letter and UNC `file://` URLs (`locationFsPath`
140
+ // drops the host — the same hazard `canonicalCollectionInput`
141
+ // sidesteps), and
142
+ // - anything resolving to bare `/`, which would overlap every local
143
+ // collection. `isValidLocationShape` refuses the root in the Add
144
+ // field for the same reason, and the two shouldn't disagree.
145
+ export function refreshPathProblem(input) {
146
+ const value = input.trim();
147
+ if (!value.startsWith('/') && !isFileUrl(value)) {
148
+ // Every rejection echoes the value: the caller here is usually a
149
+ // tool that built the URL, and the path it actually sent is the
150
+ // thing that needs fixing.
151
+ return `Refresh path ${REFRESH_PATH_SHAPES}: ${value}`;
152
+ }
153
+ if (isFileUrl(value) && isNonPosixFileUrl(value)) {
154
+ return `Refresh path must be a POSIX file:// URL, without a host: ${value}`;
155
+ }
156
+ if (toFsPath(value).length <= 1) {
157
+ return `Refresh path must name something below the filesystem root: ${value}`;
158
+ }
159
+ return null;
160
+ }