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,423 @@
1
+ // What a run (and the spec fixture) knows of its scripts: a record of
2
+ // what they reported and did in its tabs, a copy of their stored
3
+ // values, and their menu commands. The extension runs the scripts;
4
+ // `ExtensionHost` (extension-host.ts) fills this from its events, and
5
+ // the run's commands, waits and output read it.
6
+ //
7
+ // - Every record remembers the page (tab) it came from, kept off the
8
+ // records themselves so they stay plain data a test can compare.
9
+ // - Menu commands belong to a page's current document: a new document
10
+ // registers its own.
11
+ // - A script's values here are a copy, kept up to date from the
12
+ // extension's events, for reads that can't wait on a round trip.
13
+ import { EventEmitter } from 'node:events';
14
+ import { WRAPPER_GM_RUNTIME } from '../generated-wrappers.js';
15
+ import { CHANNEL_KEY_PLACEHOLDER } from '../channel-placeholder.js';
16
+ import { NO_RULE_COVERS_URL, explainMatch, preparedNoframes, preparedScriptName } from './prepare.js';
17
+ import { FrameIds } from './frames.js';
18
+ // The runtime's own stored-value codec, loaded once under Node the way
19
+ // the unit tests do, so a test reading `GM_setValue` output gets the
20
+ // value the script stored rather than its wire envelope.
21
+ let codec = null;
22
+ export function valueCodec() {
23
+ if (codec)
24
+ return codec;
25
+ const window = Object.create(globalThis);
26
+ window.addEventListener = () => { };
27
+ const document = { visibilityState: 'visible' };
28
+ new Function('window', 'document', WRAPPER_GM_RUNTIME)(window, document);
29
+ // The runtime puts itself in the registry its gate hands out; loaded
30
+ // as is, its key is the placeholder.
31
+ const gate = document.__smGate;
32
+ codec = gate(CHANNEL_KEY_PLACEHOLDER).gm.storage;
33
+ return codec;
34
+ }
35
+ // Abstract: the actions on the page go through whatever runs the
36
+ // scripts (`ExtensionHost`).
37
+ export class HarnessHost extends EventEmitter {
38
+ scripts = new Map();
39
+ // Which watched pages hold each script. A script stays known while
40
+ // any page still has it.
41
+ owners = new Map();
42
+ // scriptId -> key -> value in wire form, as the extension stores it.
43
+ values = new Map();
44
+ // Which page each record came from, for telling tabs apart.
45
+ recordPages = new WeakMap();
46
+ // `untilReported`'s waits, checked on each start, skip and error.
47
+ // Not emitter listeners: `reload all` over many tabs would pass
48
+ // Node's listener limit, which prints a warning.
49
+ reportWaiters = new Set();
50
+ reports = {
51
+ started: [], errors: [], skipped: [], manual: [], requests: [], tabs: [], menuCommands: [], notifications: [], downloads: [],
52
+ connectPrompts: [],
53
+ };
54
+ // The manual scripts that match each page's current document, which
55
+ // can be run there.
56
+ manualByPage = new Map();
57
+ // Each menu command's page; dropped when that page loads a new
58
+ // document or goes away.
59
+ menuOwners = new Map();
60
+ /** The run's frame ids (`F3`), across all its tabs. */
61
+ frameIds = new FrameIds();
62
+ /** Register a prepared script as running in `owner`'s tab. */
63
+ addScript(prepared, owner) {
64
+ if (!prepared.id)
65
+ throw new Error(`Cannot host an invalid script: ${prepared.diagnostic}`);
66
+ const id = prepared.id;
67
+ this.scripts.set(id, prepared);
68
+ let set = this.owners.get(id);
69
+ if (!set) {
70
+ set = new Set();
71
+ this.owners.set(id, set);
72
+ }
73
+ set.add(owner);
74
+ if (!this.values.has(id))
75
+ this.values.set(id, new Map());
76
+ }
77
+ /** `owner` no longer has the script; forget it once nobody does. */
78
+ removeScript(scriptId, owner) {
79
+ const set = this.owners.get(scriptId);
80
+ if (!set)
81
+ return;
82
+ set.delete(owner);
83
+ if (set.size > 0)
84
+ return;
85
+ this.owners.delete(scriptId);
86
+ this.scripts.delete(scriptId);
87
+ }
88
+ hasScript(scriptId) {
89
+ return this.scripts.has(scriptId);
90
+ }
91
+ /**
92
+ * Whether a script is running in a page's current top-frame document:
93
+ * its last word from there is a start rather than a skip. For its
94
+ * iframes, see `frameRuns`.
95
+ */
96
+ isRunning(page, scriptId) {
97
+ const { started, skipped } = this.lastStartAndSkip(page, scriptId);
98
+ return started !== undefined && (!skipped || started.at >= skipped.at);
99
+ }
100
+ /**
101
+ * Whether the scripts' manager reports their starts, skips and
102
+ * errors. Another manager than SourceMonkey doesn't.
103
+ */
104
+ get getsReports() {
105
+ return true;
106
+ }
107
+ /**
108
+ * Of `scriptIds`, the ones with no word from `page` since `since`:
109
+ * not yet started, skipped or thrown there, nor waiting to be run.
110
+ */
111
+ unreported(page, scriptIds, since) {
112
+ // Nothing to wait for from a manager that never reports.
113
+ if (!this.getsReports)
114
+ return [];
115
+ const { started, skipped, errors, manual } = this.reports;
116
+ const reported = (id) => [started, skipped, errors, manual]
117
+ .some((records) => records.some((r) => r.scriptId === id && r.at >= since && this.recordPages.get(r) === page));
118
+ return scriptIds.filter((id) => !reported(id));
119
+ }
120
+ /**
121
+ * Resolves once each of `scriptIds` has had its word from `page`
122
+ * since `since` (see `unreported`). Every script the extension runs
123
+ * in a new document gives one, so this is when a load's scripts are
124
+ * done starting. Never rejects. Stops waiting, unresolved, on
125
+ * `signal`, for a page that goes away first.
126
+ */
127
+ untilReported(page, scriptIds, since, signal) {
128
+ if (this.unreported(page, scriptIds, since).length === 0)
129
+ return Promise.resolve();
130
+ return new Promise((resolve) => {
131
+ if (signal?.aborted)
132
+ return;
133
+ const stop = () => {
134
+ this.reportWaiters.delete(check);
135
+ signal?.removeEventListener('abort', stop);
136
+ };
137
+ const check = () => {
138
+ if (this.unreported(page, scriptIds, since).length > 0)
139
+ return;
140
+ stop();
141
+ resolve();
142
+ };
143
+ this.reportWaiters.add(check);
144
+ signal?.addEventListener('abort', stop);
145
+ });
146
+ }
147
+ /** A script's latest start and latest skip from one page's top frame. */
148
+ lastStartAndSkip(page, scriptId) {
149
+ const mine = (records) => records.filter((r) => r.scriptId === scriptId && !r.frame && this.recordPages.get(r) === page).at(-1);
150
+ return { started: mine(this.reports.started), skipped: mine(this.reports.skipped) };
151
+ }
152
+ /**
153
+ * The scripts running in each iframe document of a page that reported
154
+ * any, by the extension's document id: those whose last word from
155
+ * there is a start. Documents since gone are included; the caller
156
+ * knows which are live (`liveFrameDocuments`).
157
+ */
158
+ frameRuns(page) {
159
+ const last = new Map();
160
+ const words = [
161
+ ...this.reports.started.map((r) => ({ r, running: true })),
162
+ ...this.reports.skipped.map((r) => ({ r, running: false })),
163
+ ].filter(({ r }) => r.frame && r.documentId && this.recordPages.get(r) === page)
164
+ .sort((a, b) => a.r.at - b.r.at);
165
+ for (const { r, running } of words) {
166
+ let scripts = last.get(r.documentId);
167
+ if (!scripts)
168
+ last.set(r.documentId, scripts = new Map());
169
+ scripts.set(r.scriptId, running);
170
+ }
171
+ return new Map([...last].map(([doc, scripts]) => [doc, new Set([...scripts].filter(([, on]) => on).map(([id]) => id))]));
172
+ }
173
+ /**
174
+ * Whether a script started in any iframe document of a page since
175
+ * `since` (when its top document began), and wasn't skipped there
176
+ * after. Documents since gone count too: for picking the tabs to
177
+ * reload, one too many is better than one missed.
178
+ */
179
+ ranInFrames(page, scriptId, since) {
180
+ const started = new Set(this.reports.started
181
+ .filter((r) => r.frame && r.documentId && r.scriptId === scriptId && r.at >= since && this.recordPages.get(r) === page)
182
+ .map((r) => r.documentId));
183
+ return [...started].some((doc) => this.frameRuns(page).get(doc)?.has(scriptId));
184
+ }
185
+ /**
186
+ * A page's live iframe documents, by the extension's id, each with
187
+ * its Playwright frame. Null where the manager can't say; throws when
188
+ * the extension is too old to report on frames.
189
+ */
190
+ async liveFrameDocuments(_page) {
191
+ return null;
192
+ }
193
+ /** The page a record in `reports` came from, if it is still known. */
194
+ pageOf(record) {
195
+ return this.recordPages.get(record);
196
+ }
197
+ scriptIds() {
198
+ return [...this.scripts.keys()];
199
+ }
200
+ /**
201
+ * The ids of the scripts whose rules match `url`; in an iframe
202
+ * (`inFrame`), leaving out those `@noframes` keeps to the top frame.
203
+ */
204
+ scriptsFor(url, inFrame) {
205
+ return [...this.scripts].filter(([, s]) => explainMatch(s.matching, url).matches && !(inFrame && preparedNoframes(s))).map(([id]) => id);
206
+ }
207
+ /** A watched page went away. */
208
+ detachPage(page) {
209
+ this.forgetDocumentState(page);
210
+ }
211
+ /**
212
+ * The manual scripts that can be run in `page`'s current document, at
213
+ * its URL now: one the page moved away from in place (pushState) can't.
214
+ */
215
+ manualScripts(page) {
216
+ const url = page.url();
217
+ return [...this.manualByPage.get(page) ?? []].filter((id) => {
218
+ const script = this.scripts.get(id);
219
+ return script !== undefined && explainMatch(script.matching, url).matches;
220
+ });
221
+ }
222
+ // What belongs to one document of a page: its menu commands, which a
223
+ // new document registers again, and the manual scripts it matches.
224
+ forgetDocumentState(page) {
225
+ this.manualByPage.delete(page);
226
+ for (const [record, owner] of this.menuOwners) {
227
+ if (owner !== page)
228
+ continue;
229
+ this.menuOwners.delete(record);
230
+ const i = this.reports.menuCommands.indexOf(record);
231
+ if (i >= 0)
232
+ this.reports.menuCommands.splice(i, 1);
233
+ }
234
+ }
235
+ // ---------- Stored values ----------
236
+ /**
237
+ * Values written by the run or the test, as another tab of the script
238
+ * would. Here only the host's copy; `ExtensionHost` writes them into
239
+ * the extension too.
240
+ */
241
+ async setValues(scriptId, values) {
242
+ const { encodeValue } = valueCodec();
243
+ this.applyWrites(scriptId, Object.entries(values).map(([key, v]) => (v === undefined ? { key, deleted: true } : { key, value: encodeValue(v) })));
244
+ }
245
+ /** Every value a script has stored, decoded, from the host's copy. */
246
+ getValues(scriptId) {
247
+ const { decodeValue } = valueCodec();
248
+ const out = {};
249
+ for (const [k, v] of this.storeFor(scriptId))
250
+ out[k] = decodeValue(v);
251
+ return out;
252
+ }
253
+ /**
254
+ * Every value a script has stored, for the run's commands: here the
255
+ * host's copy; `ExtensionHost` asks the extension.
256
+ */
257
+ async valuesOf(scriptId) {
258
+ return this.getValues(scriptId);
259
+ }
260
+ getValue(scriptId, key) {
261
+ const store = this.storeFor(scriptId);
262
+ return store.has(key) ? valueCodec().decodeValue(store.get(key)) : undefined;
263
+ }
264
+ // Writes into the host's copy; last write wins per key.
265
+ applyWrites(scriptId, writes) {
266
+ const store = this.storeFor(scriptId);
267
+ for (const w of writes) {
268
+ if (typeof w?.key !== 'string')
269
+ continue;
270
+ if (w.deleted)
271
+ store.delete(w.key);
272
+ else
273
+ store.set(w.key, w.value);
274
+ }
275
+ }
276
+ storeFor(scriptId) {
277
+ let store = this.values.get(scriptId);
278
+ if (!store) {
279
+ store = new Map();
280
+ this.values.set(scriptId, store);
281
+ }
282
+ return store;
283
+ }
284
+ // ---------- Navigation ----------
285
+ /**
286
+ * A watched page loaded a new document. The extension skips a script
287
+ * whose rules miss the URL silently, so the host records the miss for
288
+ * the developer: which rule kept the script off this URL. `scripts`
289
+ * are the ones the page's tab runs.
290
+ */
291
+ noteNavigation(page, scripts) {
292
+ this.forgetDocumentState(page);
293
+ const url = page.url();
294
+ for (const script of scripts) {
295
+ const id = script.id ?? '';
296
+ const verdict = explainMatch(script.matching, url);
297
+ if (verdict.matches)
298
+ continue;
299
+ const report = {
300
+ scriptId: id,
301
+ name: preparedScriptName(script),
302
+ url,
303
+ at: Date.now(),
304
+ reason: verdict.rule ?? NO_RULE_COVERS_URL,
305
+ ruleMiss: true,
306
+ };
307
+ this.recordPages.set(report, page);
308
+ this.reports.skipped.push(report);
309
+ this.notify('skip', report);
310
+ }
311
+ }
312
+ // ---------- What the scripts reported ----------
313
+ /** A script started in `from`'s current document. */
314
+ recordStart(from, fields) {
315
+ const report = this.baseReport(from, fields);
316
+ this.reports.started.push(report);
317
+ this.notify('start', report);
318
+ }
319
+ /** A script threw in `from`. */
320
+ recordError(from, fields) {
321
+ const report = this.baseReport(from, { ...fields, stack: this.remapStack(fields.stack) });
322
+ this.reports.errors.push(report);
323
+ this.notify('script-error', report);
324
+ }
325
+ /** A script didn't run in `from`, for `reason`. */
326
+ recordSkip(from, fields) {
327
+ const report = this.baseReport(from, fields);
328
+ this.reports.skipped.push(report);
329
+ this.notify('skip', report);
330
+ }
331
+ /**
332
+ * A manual script can be run in `from`'s document, where it waits to
333
+ * be run. Once per document: the extension says it again on each
334
+ * navigation, a pushState included, and a back/forward restore
335
+ * (which `forgetDocumentState` has cleared it for).
336
+ */
337
+ recordManual(from, fields) {
338
+ let set = this.manualByPage.get(from);
339
+ if (set?.has(fields.scriptId))
340
+ return;
341
+ if (!set) {
342
+ set = new Set();
343
+ this.manualByPage.set(from, set);
344
+ }
345
+ set.add(fields.scriptId);
346
+ const report = this.baseReport(from, fields);
347
+ this.reports.manual.push(report);
348
+ this.notify('manual', report);
349
+ }
350
+ /** A script in `from` wrote values. */
351
+ recordValueWrites(from, scriptId, writes, frame = {}) {
352
+ if (!this.scripts.has(scriptId))
353
+ return;
354
+ this.applyWrites(scriptId, writes);
355
+ const write = { scriptId, writes, ...frame };
356
+ this.recordPages.set(write, from);
357
+ this.notify('value-write', write);
358
+ }
359
+ /** A script in `from` registered a menu command. */
360
+ registerMenu(from, scriptId, commandId, name, documentId, frame) {
361
+ if (!this.scripts.has(scriptId) || !commandId)
362
+ return;
363
+ const record = {
364
+ scriptId, commandId, name, at: Date.now(), ...(documentId ? { documentId } : {}),
365
+ ...(frame ? { frame: true, ...(frame.frameNumber !== undefined ? { frameNumber: frame.frameNumber } : {}) } : {}),
366
+ };
367
+ // Re-registering an id replaces the entry in place, as the worker's
368
+ // registry does. Per document: two frames can use the same id.
369
+ const i = this.reports.menuCommands.findIndex((m) => m.scriptId === scriptId && m.commandId === commandId && m.documentId === documentId && this.menuOwners.get(m) === from);
370
+ if (i >= 0) {
371
+ this.menuOwners.delete(this.reports.menuCommands[i]);
372
+ this.reports.menuCommands[i] = record;
373
+ }
374
+ else {
375
+ this.reports.menuCommands.push(record);
376
+ }
377
+ this.menuOwners.set(record, from);
378
+ this.recordPages.set(record, from);
379
+ this.notify('menu-command', record);
380
+ }
381
+ /** A script in `from` removed a menu command. */
382
+ unregisterMenu(from, scriptId, commandId, documentId) {
383
+ const i = this.reports.menuCommands.findIndex((m) => m.scriptId === scriptId && m.commandId === commandId && m.documentId === documentId && this.menuOwners.get(m) === from);
384
+ if (i < 0)
385
+ return;
386
+ this.menuOwners.delete(this.reports.menuCommands[i]);
387
+ this.reports.menuCommands.splice(i, 1);
388
+ }
389
+ // ---------- What a test or command can do ----------
390
+ /**
391
+ * Invoke a registered menu command by its name, or by id, on the page
392
+ * that registered it. The extension runs all the script's
393
+ * registrations of that caption in the tab, as its menu does.
394
+ */
395
+ async fireMenuCommand(nameOrId, scriptId) {
396
+ const entry = [...this.reports.menuCommands].reverse().find((m) => (scriptId === undefined || m.scriptId === scriptId) && (m.name === nameOrId || m.commandId === nameOrId));
397
+ return entry ? this.fireMenuEntry(entry) : false;
398
+ }
399
+ // ---------- Helpers ----------
400
+ baseReport(from, fields) {
401
+ const report = { ...fields, name: fields.name || this.nameOf(fields.scriptId), at: Date.now() };
402
+ this.recordPages.set(report, from);
403
+ return report;
404
+ }
405
+ nameOf(scriptId) {
406
+ const script = this.scripts.get(scriptId);
407
+ return script ? preparedScriptName(script) : scriptId;
408
+ }
409
+ // Every event goes out through here. A test's listener that throws
410
+ // is the test's own failure: it must not cost the host its records.
411
+ notify(event, ...args) {
412
+ try {
413
+ this.emit(event, ...args);
414
+ }
415
+ catch {
416
+ // Dropped on purpose; see above.
417
+ }
418
+ if (event === 'start' || event === 'skip' || event === 'script-error' || event === 'manual') {
419
+ for (const check of [...this.reportWaiters])
420
+ check();
421
+ }
422
+ }
423
+ }
@@ -0,0 +1,8 @@
1
+ export { prepareScript, explainMatch, type PrepareOptions, type PreparedScript, type MatchExplanation, type InjectionWorld, } from './prepare.js';
2
+ export type { StoredScript, ScriptMatching, StartupLog } from '../script-build.js';
3
+ export type { DiagnosticLocation } from '../diagnostic-location.js';
4
+ export { checkParseNode } from './parse-check-node.js';
5
+ export { HarnessHost, type PageLink, type HostReports, type HostEventMap, type StartReport, type ErrorReport, type SkipReport, type XhrRecord, type TabRecord, type MenuCommandRecord, type NotificationRecord, type DownloadRecord, type RequestInterceptor, } from './host.js';
6
+ export { ExtensionHost } from './extension-host.js';
7
+ export { attachPage, PageAttachment, type InjectOptions } from './page-watch.js';
8
+ export { test, expect, type GmApi, type ReportsApi, type LoadOptions, type HarnessFixtures } from './fixture.js';
@@ -0,0 +1,10 @@
1
+ // Public entry point of the development harness: what another repo
2
+ // gets from `import … from 'sourcemonkey/harness'`. See
3
+ // docs/harness-guide.md for the user-facing description and
4
+ // docs/harness-design.md for the design.
5
+ export { prepareScript, explainMatch, } from './prepare.js';
6
+ export { checkParseNode } from './parse-check-node.js';
7
+ export { HarnessHost, } from './host.js';
8
+ export { ExtensionHost } from './extension-host.js';
9
+ export { attachPage, PageAttachment } from './page-watch.js';
10
+ export { test, expect } from './fixture.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The text of `file`, which is `shown` in messages. Throws an Error
3
+ * saying why it couldn't be read, or that it is empty: nothing, or
4
+ * only whitespace unless `blankOk` (text to type may be just that).
5
+ */
6
+ export declare function readInputFile(file: string, shown: string, { blankOk }?: {
7
+ blankOk?: boolean | undefined;
8
+ }): string;
9
+ /** The JSON value in `file`, else an Error as `readInputFile` throws. */
10
+ export declare function readJsonFile(file: string, shown: string): unknown;
11
+ /**
12
+ * Why a file couldn't be written, or null for a failure with no
13
+ * common cause. Playwright's own writes (a screenshot) only name the
14
+ * code in their message, hence the match.
15
+ */
16
+ export declare function writeProblem(err: unknown): string | null;
17
+ /** An error's message as a sentence to follow a `: `. */
18
+ export declare function errorSentence(err: unknown): string;
@@ -0,0 +1,61 @@
1
+ // Files named on a command line for what they hold (`eval --file`,
2
+ // `set --file`, `type --file`, `start --values-file`): read whole, with what went wrong
3
+ // in plain words for an `ERROR:` line.
4
+ import { readFileSync } from 'node:fs';
5
+ /**
6
+ * The text of `file`, which is `shown` in messages. Throws an Error
7
+ * saying why it couldn't be read, or that it is empty: nothing, or
8
+ * only whitespace unless `blankOk` (text to type may be just that).
9
+ */
10
+ export function readInputFile(file, shown, { blankOk = false } = {}) {
11
+ let text;
12
+ try {
13
+ text = readFileSync(file, 'utf8');
14
+ }
15
+ catch (err) {
16
+ throw new Error(`Couldn't read ${shown}: ${readProblem(err)}`);
17
+ }
18
+ if (blankOk ? !text : !text.trim())
19
+ throw new Error(`The file ${shown} is empty`);
20
+ return text;
21
+ }
22
+ /** The JSON value in `file`, else an Error as `readInputFile` throws. */
23
+ export function readJsonFile(file, shown) {
24
+ const text = readInputFile(file, shown);
25
+ try {
26
+ return JSON.parse(text);
27
+ }
28
+ catch (err) {
29
+ throw new Error(`The file ${shown} is not JSON: ${err.message}`);
30
+ }
31
+ }
32
+ // Why a file couldn't be read.
33
+ function readProblem(err) {
34
+ if (err.code === 'ENOENT')
35
+ return 'The file does not exist';
36
+ if (err.code === 'EISDIR')
37
+ return 'It\'s a folder';
38
+ if (err.code === 'EACCES')
39
+ return 'Permission was refused';
40
+ return errorSentence(err);
41
+ }
42
+ /**
43
+ * Why a file couldn't be written, or null for a failure with no
44
+ * common cause. Playwright's own writes (a screenshot) only name the
45
+ * code in their message, hence the match.
46
+ */
47
+ export function writeProblem(err) {
48
+ const code = err.code ?? /\b(ENOENT|EACCES|EISDIR)\b/.exec(err.message)?.[1];
49
+ if (code === 'ENOENT')
50
+ return 'Its folder doesn\'t exist';
51
+ if (code === 'EACCES')
52
+ return 'Permission was refused';
53
+ if (code === 'EISDIR')
54
+ return 'It\'s a folder';
55
+ return null;
56
+ }
57
+ /** An error's message as a sentence to follow a `: `. */
58
+ export function errorSentence(err) {
59
+ const first = err.message.split('\n')[0].replace(/\.$/, '');
60
+ return first.charAt(0).toUpperCase() + first.slice(1);
61
+ }