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,21 @@
1
+ import type { Browser } from '@playwright/test';
2
+ import { type ControlledTab } from './console-commands.js';
3
+ export interface DownloadWatch {
4
+ /** Stop listening; no line prints after this. */
5
+ stop(): Promise<void>;
6
+ }
7
+ /**
8
+ * Start reporting the run's downloads: each through `said`, with the
9
+ * number of the tab it came from (the run logs it as an event), and
10
+ * a failure to watch at all through `out`. `tabs` is read as each
11
+ * download starts, so a tab opened later counts.
12
+ *
13
+ * With `downloadsDir` (a launched browser), downloads are saved there.
14
+ * Without, they keep the browser's own setting, as its person set it.
15
+ *
16
+ * Never throws: reporting downloads is a side feature, so a browser
17
+ * that refuses it gets a note, and the run goes on without it.
18
+ */
19
+ export declare function watchDownloads(browser: Browser, tabs: () => ControlledTab[], said: (tab: number, text: string) => void, out: (line: string) => void, { downloadsDir }?: {
20
+ downloadsDir?: string;
21
+ }): Promise<DownloadWatch>;
@@ -0,0 +1,156 @@
1
+ // Says where a download from one of the run's tabs went, as it finishes.
2
+ // A page's own downloads (a link, a script's `<a download>` click) pass
3
+ // SourceMonkey by, so without this a run has no record of them at all.
4
+ // An http(s) `GM_download` gets its own line from the extension's report;
5
+ // a `blob:` or `data:` one is an `<a download>` click in the page, so it
6
+ // gets this line.
7
+ //
8
+ // Chrome reports downloads over CDP only to a client that asks
9
+ // (`Browser.setDownloadBehavior` with `eventsEnabled`), and to all such
10
+ // clients for all tabs, so each is checked against the run's tabs.
11
+ import { withoutQuery } from './console-commands.js';
12
+ /**
13
+ * Start reporting the run's downloads: each through `said`, with the
14
+ * number of the tab it came from (the run logs it as an event), and
15
+ * a failure to watch at all through `out`. `tabs` is read as each
16
+ * download starts, so a tab opened later counts.
17
+ *
18
+ * With `downloadsDir` (a launched browser), downloads are saved there.
19
+ * Without, they keep the browser's own setting, as its person set it.
20
+ *
21
+ * Never throws: reporting downloads is a side feature, so a browser
22
+ * that refuses it gets a note, and the run goes on without it.
23
+ */
24
+ export async function watchDownloads(browser, tabs, said, out, { downloadsDir } = {}) {
25
+ let cdp;
26
+ let stopped = false;
27
+ const say = (tab, text) => {
28
+ if (!stopped)
29
+ said(tab, text);
30
+ };
31
+ try {
32
+ const session = cdp = await browser.newBrowserCDPSession();
33
+ // Ours, by guid, from start to end; a download of another client's
34
+ // tab is null. A promise, since finding the tab takes round trips
35
+ // and a small download can finish first.
36
+ const started = new Map();
37
+ // Each target's parent or opener frame, recorded as it's created: a
38
+ // new tab that turns into a download closes at once, often before
39
+ // its download could be traced back through it. Kept for the run
40
+ // (a few ids per tab), since a closed one may still be asked about.
41
+ const parents = new Map();
42
+ const record = ({ targetInfo: t }) => {
43
+ const up = t.parentFrameId ?? t.openerFrameId ?? t.openerId;
44
+ if (up)
45
+ parents.set(t.targetId, up);
46
+ };
47
+ session.on('Target.targetCreated', record);
48
+ session.on('Target.targetInfoChanged', record);
49
+ await session.send('Target.setDiscoverTargets', { discover: true });
50
+ session.on('Browser.downloadWillBegin', (event) => {
51
+ started.set(event.guid, tabOf(session, parents, tabs(), event.frameId).then((tab) => tab
52
+ ? { url: event.url, tab: tab.n }
53
+ : null));
54
+ });
55
+ session.on('Browser.downloadProgress', (event) => {
56
+ if (event.state === 'inProgress')
57
+ return;
58
+ const pending = started.get(event.guid);
59
+ if (!pending)
60
+ return;
61
+ started.delete(event.guid);
62
+ void pending.then((download) => {
63
+ if (!download)
64
+ return;
65
+ const from = shownUrl(download.url);
66
+ if (event.state === 'completed') {
67
+ // Chrome doesn't promise the path on all platforms.
68
+ say(download.tab, `Download saved: ${event.filePath || 'in the browser\'s download folder'} (from ${from})`);
69
+ }
70
+ else {
71
+ // Chrome reports a download cancelled in its UI and one that
72
+ // failed the same way.
73
+ say(download.tab, `Download from ${from} did not finish: It was cancelled or failed`);
74
+ }
75
+ });
76
+ });
77
+ // `allow` into a folder of ours for a launched browser: unlike
78
+ // `default`, it skips Chrome's prompts for a dangerous or insecure
79
+ // download, which nobody could answer in a hidden browser, so the
80
+ // download would wait forever. It keeps the file's own name, where
81
+ // Playwright's `allowAndName` (which its connection sets up) uses a
82
+ // GUID.
83
+ //
84
+ // `default` for a connected browser: its own setting, and its own
85
+ // prompts for a person to answer.
86
+ await session.send('Browser.setDownloadBehavior', downloadsDir
87
+ ? { behavior: 'allow', downloadPath: downloadsDir, eventsEnabled: true }
88
+ : { behavior: 'default', eventsEnabled: true });
89
+ }
90
+ catch (err) {
91
+ out(`[sm-dev] Downloads from the run's tabs won't be reported: ${err.message}`);
92
+ if (cdp)
93
+ await detach(cdp);
94
+ cdp = undefined;
95
+ }
96
+ return {
97
+ stop: async () => {
98
+ stopped = true;
99
+ if (cdp)
100
+ await detach(cdp);
101
+ },
102
+ };
103
+ }
104
+ // The run's tab a download came from, by its frame:
105
+ // - a tab's top frame has the tab's target id;
106
+ // - a new tab that became the download (a `target=_blank` link, a
107
+ // `window.open`) names the frame that opened it;
108
+ // - a cross-site iframe is a target of its own, naming its parent frame;
109
+ // - a same-site iframe is in a tab's frame tree.
110
+ // Followed up a few steps, since each of those can lead to another.
111
+ async function tabOf(cdp, parents, tabs, frameId, depth = 0) {
112
+ // An extension's download (`chrome.downloads`) has no frame.
113
+ if (!frameId || depth > 5)
114
+ return undefined;
115
+ const top = tabs.find((t) => t.targetId === frameId);
116
+ if (top)
117
+ return top;
118
+ const up = parents.get(frameId) ?? await cdp.send('Target.getTargetInfo', { targetId: frameId }).then(({ targetInfo: t }) => t.parentFrameId ?? t.openerFrameId ?? t.openerId, () => undefined);
119
+ if (up)
120
+ return tabOf(cdp, parents, tabs, up, depth + 1);
121
+ for (const tab of tabs) {
122
+ if (tab.page.isClosed())
123
+ continue;
124
+ const ids = await frameIds(tab).catch(() => new Set());
125
+ if (ids.has(frameId))
126
+ return tab;
127
+ }
128
+ return undefined;
129
+ }
130
+ async function frameIds(tab) {
131
+ const session = await tab.page.context().newCDPSession(tab.page);
132
+ try {
133
+ const { frameTree } = await session.send('Page.getFrameTree');
134
+ const ids = new Set();
135
+ const walk = (node) => {
136
+ ids.add(node.frame.id);
137
+ node.childFrames?.forEach(walk);
138
+ };
139
+ walk(frameTree);
140
+ return ids;
141
+ }
142
+ finally {
143
+ await detach(session);
144
+ }
145
+ }
146
+ // A `data:` URL can be megabytes, so only its kind is shown.
147
+ function shownUrl(url) {
148
+ if (url.startsWith('data:'))
149
+ return 'a data: URL';
150
+ if (url.startsWith('blob:'))
151
+ return url;
152
+ return withoutQuery(url);
153
+ }
154
+ async function detach(session) {
155
+ await session.detach().catch(() => undefined);
156
+ }
@@ -0,0 +1,41 @@
1
+ import type { BrowserContext, Worker } from '@playwright/test';
2
+ export interface PushResult {
3
+ scriptId: string;
4
+ collectionId: string;
5
+ status: 'ok' | 'invalid';
6
+ errorMessage?: string;
7
+ errorLocation?: {
8
+ file: string;
9
+ line?: number;
10
+ column?: number;
11
+ };
12
+ replaced: boolean;
13
+ }
14
+ export interface LogEntry {
15
+ timestamp: number;
16
+ level: string;
17
+ source: string;
18
+ message: string;
19
+ tabId?: number;
20
+ }
21
+ export declare function extensionIdFromKey(base64Key: string): string;
22
+ export declare function sourceMonkeyExtensionId(): Promise<string>;
23
+ /** The origin of the extension's pages and worker: `chrome-extension://<id>`. */
24
+ export declare function sourceMonkeyOrigin(): Promise<string>;
25
+ /**
26
+ * Whether `url` is the extension's service worker. Matched by origin,
27
+ * not path: an extension has one worker, and its path differs between
28
+ * builds (it moved into extension/), so a browser's older build is still
29
+ * found and told it is too old rather than timing out.
30
+ */
31
+ export declare function isSourceMonkeyWorker(url: string, origin: string): boolean;
32
+ /** `findExtensionWorker` finding no SourceMonkey in the browser. */
33
+ export declare class ExtensionMissingError extends Error {
34
+ }
35
+ /**
36
+ * The way out of a browser the run connects to, for advice: a temp
37
+ * browser is the default, so it's the mode flag to drop rather than
38
+ * `--temp-browser` to add, which the line would refuse beside it.
39
+ */
40
+ export declare const DROP_BROWSER_MODE = "drop `--existing-browser` or `--open-browser` to use a temp browser";
41
+ export declare function findExtensionWorker(ctx: BrowserContext, timeoutMs?: number): Promise<Worker>;
@@ -0,0 +1,100 @@
1
+ // Finding the real SourceMonkey in sm-dev's browser: whether it is
2
+ // installed and turned on, and its service worker, which every call
3
+ // into it goes through (extension-link.ts). Playwright exposes the
4
+ // worker on a CDP-connected browser as well as a launched one, so the
5
+ // same `worker.evaluate` reaches it either way.
6
+ import crypto from 'node:crypto';
7
+ import { readFile } from 'node:fs/promises';
8
+ // SourceMonkey's extension id. The manifest carries a fixed `key`, so
9
+ // Chrome derives the same id for every install of it, unpacked or not:
10
+ // the SHA-256 of the key bytes, first sixteen bytes, each hex digit
11
+ // mapped to a letter. Read from the package's own manifest so a
12
+ // changed key follows along.
13
+ let cachedId = null;
14
+ export function extensionIdFromKey(base64Key) {
15
+ const digest = crypto.createHash('sha256').update(Buffer.from(base64Key, 'base64')).digest('hex');
16
+ return digest.slice(0, 32).split('').map((c) => String.fromCharCode('a'.charCodeAt(0) + parseInt(c, 16))).join('');
17
+ }
18
+ export async function sourceMonkeyExtensionId() {
19
+ if (cachedId)
20
+ return cachedId;
21
+ const manifest = await readManifest();
22
+ if (typeof manifest.key !== 'string')
23
+ throw new Error('Internal error: the manifest has no key to derive the extension id from');
24
+ cachedId = extensionIdFromKey(manifest.key);
25
+ return cachedId;
26
+ }
27
+ /** The origin of the extension's pages and worker: `chrome-extension://<id>`. */
28
+ export async function sourceMonkeyOrigin() {
29
+ return `chrome-extension://${await sourceMonkeyExtensionId()}`;
30
+ }
31
+ /**
32
+ * Whether `url` is the extension's service worker. Matched by origin,
33
+ * not path: an extension has one worker, and its path differs between
34
+ * builds (it moved into extension/), so a browser's older build is still
35
+ * found and told it is too old rather than timing out.
36
+ */
37
+ export function isSourceMonkeyWorker(url, origin) {
38
+ return url.startsWith(`${origin}/`);
39
+ }
40
+ async function readManifest() {
41
+ return JSON.parse(await readFile(new URL('../manifest.json', import.meta.url), 'utf8'));
42
+ }
43
+ /** `findExtensionWorker` finding no SourceMonkey in the browser. */
44
+ export class ExtensionMissingError extends Error {
45
+ }
46
+ /**
47
+ * The way out of a browser the run connects to, for advice: a temp
48
+ * browser is the default, so it's the mode flag to drop rather than
49
+ * `--temp-browser` to add, which the line would refuse beside it.
50
+ */
51
+ export const DROP_BROWSER_MODE = 'drop `--existing-browser` or `--open-browser` to use a temp browser';
52
+ const NOT_INSTALLED = 'SourceMonkey is not installed in this browser. Load it there as an unpacked extension, with Allow User Scripts on '
53
+ + `(see https://github.com/jshute96/SourceMonkey/blob/main/docs/harness-guide.md#setting-it-up), or ${DROP_BROWSER_MODE}, which loads it.`;
54
+ // The extension's worker in this context, once its `self.SourceMonkey`
55
+ // surface is there. A worker can be listed before its module has run
56
+ // that far, most likely just after the browser starts.
57
+ export async function findExtensionWorker(ctx, timeoutMs = 10000) {
58
+ const deadline = Date.now() + timeoutMs;
59
+ const worker = await listedWorker(ctx, timeoutMs);
60
+ const ready = () => worker.evaluate(() => typeof self.SourceMonkey !== 'undefined');
61
+ while (!await ready()) {
62
+ if (Date.now() > deadline)
63
+ throw new Error(`SourceMonkey did not respond within ${Math.round(timeoutMs / 1000)} seconds`);
64
+ await new Promise((r) => setTimeout(r, 50));
65
+ }
66
+ return worker;
67
+ }
68
+ // The extension's worker, as listed. An idle worker is not listed, so
69
+ // when it is missing an extension page is opened to wake it: any page
70
+ // of the extension will do, and the collections page has the least
71
+ // side effects.
72
+ async function listedWorker(ctx, timeoutMs) {
73
+ const origin = await sourceMonkeyOrigin();
74
+ const listed = () => ctx.serviceWorkers().find((w) => isSourceMonkeyWorker(w.url(), origin));
75
+ const found = listed();
76
+ if (found)
77
+ return found;
78
+ const page = await ctx.newPage();
79
+ try {
80
+ const deadline = Date.now() + timeoutMs;
81
+ // A navigation to a page of an extension that isn't installed is
82
+ // refused at once; that is the "not installed" answer.
83
+ try {
84
+ await page.goto(`${origin}/collections.html`);
85
+ }
86
+ catch {
87
+ throw new ExtensionMissingError(NOT_INSTALLED);
88
+ }
89
+ while (Date.now() < deadline) {
90
+ const w = listed();
91
+ if (w)
92
+ return w;
93
+ await new Promise((r) => setTimeout(r, 200));
94
+ }
95
+ }
96
+ finally {
97
+ await page.close().catch(() => undefined);
98
+ }
99
+ throw new Error(`SourceMonkey did not respond within ${Math.round(timeoutMs / 1000)} seconds`);
100
+ }
@@ -0,0 +1,112 @@
1
+ import type { Frame, Page } from '@playwright/test';
2
+ import type { StackLayout } from '../composed-source.js';
3
+ import { HarnessHost, type ConnectChoice, type MenuCommandRecord, type PageLink } from './host.js';
4
+ import type { ConsoleContext } from './console-commands.js';
5
+ import type { ExtensionLink } from './extension-link.js';
6
+ /** What the host needs of a tab's attachment, to order its events. */
7
+ export interface WatchedPage extends PageLink {
8
+ /** When the page's current document began (see `PageAttachment.documentStart`). */
9
+ documentStart(): number;
10
+ /** The tab's page, whose frames the extension's are matched to. */
11
+ readonly page: Page;
12
+ }
13
+ export declare class ExtensionHost extends HarnessHost {
14
+ private readonly link;
15
+ /**
16
+ * Logs a Log entry of one of the run's scripts (`scriptId`: the
17
+ * run's id), with the tab it was made in, if any.
18
+ */
19
+ private readonly printLog;
20
+ private readonly extensionIds;
21
+ private readonly runIds;
22
+ private readonly layouts;
23
+ private readonly tabs;
24
+ private readonly tabsByPage;
25
+ private readonly early;
26
+ private readonly others;
27
+ private readonly otherLabels;
28
+ private readonly logSeen;
29
+ private readonly liveFrom;
30
+ private readonly notificationIds;
31
+ constructor(link: ExtensionLink,
32
+ /**
33
+ * Logs a Log entry of one of the run's scripts (`scriptId`: the
34
+ * run's id), with the tab it was made in, if any.
35
+ */
36
+ printLog: (entry: {
37
+ scriptId: string;
38
+ name: string;
39
+ level: string;
40
+ message: string;
41
+ page?: PageLink;
42
+ }) => void);
43
+ /** The run's script `runId` is the extension's `extensionId`. */
44
+ setPushed(runId: string, extensionId: string): void;
45
+ /**
46
+ * After the extension's worker restarted, logs the Log entries of the
47
+ * run's scripts made meanwhile, whose events were lost.
48
+ */
49
+ catchUpLogs(): Promise<void>;
50
+ private logEntry;
51
+ /** The extension's id for the run's script `runId`, once pushed. */
52
+ extensionIdOf(runId: string): string | undefined;
53
+ /** How the extension's script `extensionId` maps back to its files, if known. */
54
+ setLayout(extensionId: string, layout: StackLayout | null): void;
55
+ remapStack(text: string): string;
56
+ /**
57
+ * The run's script whose code one of `urls` is (a stack frame's, or
58
+ * a stack's text), by the entry URLs the extension runs them under.
59
+ */
60
+ scriptOf(urls: string[]): string | undefined;
61
+ /**
62
+ * Under another manager (Violentmonkey, Tampermonkey), which reports
63
+ * nothing: how its code's URLs name scripts, by the manager's id
64
+ * (`setPushed`), what every URL it runs code under starts with, and
65
+ * how it maps stack lines it moved.
66
+ */
67
+ external?: {
68
+ idOf(url: string): string | undefined;
69
+ prefix: string;
70
+ remap?(text: string): string;
71
+ };
72
+ get getsReports(): boolean;
73
+ /**
74
+ * `page` is the run's tab `tabId`. `loaded`: the tab already shows the
75
+ * document its first events are from (a tab adopted after it loaded),
76
+ * so they needn't wait for a navigation. `scriptsOff`: its claim runs
77
+ * no scripts (opened from a `--noscript` tab), to keep if restored.
78
+ */
79
+ addTab(page: WatchedPage, tabId: number, loaded?: boolean, scriptsOff?: boolean): void;
80
+ /** A tab the run will set up: its events wait for `addTab`. */
81
+ expectTab(tabId: number): void;
82
+ tabIdOf(page: PageLink): number | undefined;
83
+ liveFrameDocuments(page: PageLink): Promise<Map<string, Frame> | null>;
84
+ private matchFrames;
85
+ private frameNumberOf;
86
+ detachPage(page: PageLink): void;
87
+ evalHook(scriptId: string): {
88
+ key: string;
89
+ id: string;
90
+ } | null;
91
+ valuesOf(scriptId: string): Promise<Record<string, unknown>>;
92
+ setValues(scriptId: string, values: Record<string, unknown>): Promise<void>;
93
+ private extensionValues;
94
+ private writeExtensionValues;
95
+ private other;
96
+ /**
97
+ * The SourceMonkey-managed scripts, for the commands (`ConsoleContext.others`).
98
+ * `guarded`: `set` on one changes the person's real data.
99
+ */
100
+ otherScripts(guarded: boolean): NonNullable<ConsoleContext['others']>;
101
+ answerConnectPrompt(promptId: string, choice: ConnectChoice): Promise<boolean>;
102
+ clickNotification(requestId: string): Promise<boolean>;
103
+ closeNotification(requestId: string): Promise<boolean>;
104
+ runManualScript(page: PageLink, scriptId: string): Promise<void>;
105
+ fireMenuEntry(entry: MenuCommandRecord): Promise<boolean>;
106
+ private ingest;
107
+ private enqueue;
108
+ private handleTabEvent;
109
+ private recordOpenedTab;
110
+ private recordConnectPrompt;
111
+ private recordActivity;
112
+ }