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,607 @@
1
+ // Watching a page the extension runs scripts in: a run's tab, or the
2
+ // spec fixture's page. Adds nothing the page or its scripts can see.
3
+ //
4
+ // - Tracks the page's documents (when each began), for the waits and
5
+ // for ordering the extension's events against navigations
6
+ // (extension-host.ts).
7
+ // - Records which scripts the tab runs, for the host's skip reports and
8
+ // the waits; the extension runs them.
9
+ // - Reads what `wait` needs (DOM activity, requests in flight), from a
10
+ // CDP-made world of its own, and evaluates `eval` in the page's world
11
+ // or the scripts' (the extension's user-script world).
12
+ import { FrameSessions } from './frame-sessions.js';
13
+ // The world the activity tracker runs in, created by CDP: its own, so
14
+ // neither the page nor the scripts see it.
15
+ const WATCH_WORLD = 'sm-dev-watch';
16
+ // When the DOM last changed, and where, for `wait` (see wait.ts): a
17
+ // MutationObserver from the document's start, in the watch world.
18
+ const ACTIVITY_KEY = 'sm-dev-activity';
19
+ const ACTIVITY_TRACKER = `(function(){
20
+ if (self !== top) return;
21
+ var key = Symbol.for(${JSON.stringify(ACTIVITY_KEY)});
22
+ if (globalThis[key]) return;
23
+ var state = globalThis[key] = { last: Date.now(), target: '' };
24
+ function describe(node) {
25
+ var el = node.nodeType === 1 ? node : node.parentElement;
26
+ if (!el) return '';
27
+ var classes = typeof el.className === 'string' && el.className.trim()
28
+ ? '.' + el.className.trim().split(/\\s+/).slice(0, 2).join('.') : '';
29
+ return el.localName + (el.id ? '#' + el.id : '') + classes;
30
+ }
31
+ new MutationObserver(function(records){
32
+ state.last = Date.now();
33
+ state.target = describe(records[records.length - 1].target);
34
+ }).observe(document, { childList: true, subtree: true, attributes: true, characterData: true });
35
+ })()`;
36
+ // Results JSON can't show, printed as the console describes them.
37
+ const DESCRIBED = new Set(['node', 'error', 'map', 'set', 'weakmap', 'weakset', 'nodelist', 'htmlcollection', 'regexp', 'date', 'proxy', 'promise', 'generator', 'iterator']);
38
+ /** What an expression `evaluate` ran threw, as opposed to evaluate failing. */
39
+ export class ScriptThrew extends Error {
40
+ }
41
+ /** `evaluate` found no eval hook for the script: it hasn't run in this document. */
42
+ export class NoEvalHook extends Error {
43
+ }
44
+ /** `evaluate` found no document to run in: the page stayed between documents. */
45
+ export class NoDocument extends Error {
46
+ }
47
+ // How long `evaluate` waits for a page that is between documents (a
48
+ // reload or navigation under way) to have one again.
49
+ const DOCUMENT_WAIT_MS = 5000;
50
+ // How long `drain` waits for the page to run a timer.
51
+ const DRAIN_WAIT_MS = 500;
52
+ const BETWEEN_DOCUMENTS = 'The page is between documents (reloading or navigating), so there is none to evaluate in';
53
+ // What an expression threw: an Error's description, else the value
54
+ // thrown (`throw 'boom'`).
55
+ function threw(details) {
56
+ const exception = details.exception;
57
+ const thrown = exception?.description ?? (exception && 'value' in exception ? JSON.stringify(exception.value) : details.text);
58
+ return new ScriptThrew(thrown.split('\n')[0]);
59
+ }
60
+ // A top-level `return` in `eval`'s code: V8's syntax error for it.
61
+ // Only as the code compiled, which has no stack trace. One thrown as it
62
+ // ran (by an inner `eval("return")`) had its side effects already.
63
+ function isIllegalReturn(details) {
64
+ const exception = details.exception;
65
+ return !details.stackTrace && exception?.className === 'SyntaxError' && (exception.description ?? '').startsWith('SyntaxError: Illegal return statement');
66
+ }
67
+ // Code that's only valid as a function body (a top-level `return`, or
68
+ // `await`), from the syntax error compiling it as a script gave.
69
+ function needsFunctionBody(details) {
70
+ const exception = details.exception;
71
+ return /Illegal return statement|await is only valid/.test(exception?.description ?? details.text);
72
+ }
73
+ // Pages that already have an attachment.
74
+ const attached = new WeakSet();
75
+ /**
76
+ * One page watched for a host. Create with `attachPage`, then record the
77
+ * scripts its tab runs with `inject`.
78
+ */
79
+ export class PageAttachment {
80
+ page;
81
+ host;
82
+ cdp;
83
+ options;
84
+ injected = new Map();
85
+ disposing = false;
86
+ closed = false;
87
+ mainFrameId = '';
88
+ // The main frame's recent documents by loader id: one restored from
89
+ // the back/forward cache is the same document again.
90
+ documents = new Map();
91
+ currentDocument = { url: '', spans: [[0, Infinity]] };
92
+ // Execution contexts by id, to find a world's context in the main frame.
93
+ contexts = new Map();
94
+ // Sessions for evaluating in iframes, opened on the first `eval` in one.
95
+ frameSessions;
96
+ // Requests in flight, by when each started; see `network`.
97
+ requests = new Map();
98
+ lastRequestEnd = 0;
99
+ onRequest = (request) => { this.requests.set(request, Date.now()); };
100
+ onRequestEnd = (request) => {
101
+ if (this.requests.delete(request))
102
+ this.lastRequestEnd = Date.now();
103
+ };
104
+ onClose = () => {
105
+ this.closed = true;
106
+ this.host.detachPage(this);
107
+ };
108
+ constructor(page, host, cdp, options) {
109
+ this.page = page;
110
+ this.host = host;
111
+ this.cdp = cdp;
112
+ this.options = options;
113
+ }
114
+ static async create(page, host, options) {
115
+ if (attached.has(page))
116
+ throw new Error('This page already has an attachment');
117
+ // Claimed before the first await, so two concurrent attaches can't
118
+ // both pass the check; released if setup fails, so the page can be
119
+ // attached again.
120
+ attached.add(page);
121
+ let cdp = null;
122
+ try {
123
+ cdp = await page.context().newCDPSession(page);
124
+ const attachment = new PageAttachment(page, host, cdp, options);
125
+ await attachment.install();
126
+ return attachment;
127
+ }
128
+ catch (err) {
129
+ attached.delete(page);
130
+ await cdp?.detach().catch(() => undefined);
131
+ throw err;
132
+ }
133
+ }
134
+ async install() {
135
+ // Before `Runtime.enable`, which reports the contexts that already exist.
136
+ this.cdp.on('Runtime.executionContextCreated', ({ context }) => {
137
+ const aux = (context.auxData ?? {});
138
+ this.contexts.set(context.id, { frameId: aux.frameId ?? '', name: context.name, isDefault: aux.isDefault === true, origin: context.origin });
139
+ });
140
+ this.cdp.on('Runtime.executionContextDestroyed', (ev) => { this.contexts.delete(ev.executionContextId); });
141
+ this.cdp.on('Runtime.executionContextsCleared', () => { this.contexts.clear(); });
142
+ await this.cdp.send('Page.enable');
143
+ await this.cdp.send('Runtime.enable');
144
+ await this.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
145
+ source: ACTIVITY_TRACKER,
146
+ runImmediately: true,
147
+ worldName: WATCH_WORLD,
148
+ });
149
+ // The page's requests in flight, and when the last one ended, for
150
+ // `wait`. Playwright's events cover every frame of the page.
151
+ this.page.on('request', this.onRequest);
152
+ this.page.on('requestfinished', this.onRequestEnd);
153
+ this.page.on('requestfailed', this.onRequestEnd);
154
+ // New documents only. Playwright's `framenavigated` also fires for
155
+ // `pushState` and hash changes, where the scripts are still running
156
+ // from the earlier document and a "skipped" would be false.
157
+ this.cdp.on('Page.frameNavigated', (ev) => {
158
+ if (ev.frame.parentId)
159
+ return;
160
+ this.enterDocument(ev.frame.loaderId, ev.frame.url);
161
+ this.host.noteNavigation(this, this.injectedScripts());
162
+ });
163
+ const { frameTree } = await this.cdp.send('Page.getFrameTree');
164
+ this.mainFrameId = frameTree.frame.id;
165
+ this.currentDocument.url = frameTree.frame.url;
166
+ this.documents.set(frameTree.frame.loaderId, this.currentDocument);
167
+ this.page.on('close', this.onClose);
168
+ // Closed during the awaits above, before the listener was on: the
169
+ // event has come and gone.
170
+ if (this.page.isClosed())
171
+ this.onClose();
172
+ }
173
+ /**
174
+ * When the main frame entered its current document, by the host's
175
+ * clock, as reports are stamped. Changes only when a new document
176
+ * loads, not on a same-document (hash or `pushState`) navigation.
177
+ */
178
+ documentStart() {
179
+ return this.currentDocument.spans[this.currentDocument.spans.length - 1][0];
180
+ }
181
+ /**
182
+ * Resolves once the page has run the tasks it already had queued, and
183
+ * the console lines they printed have arrived. A round trip through
184
+ * Playwright's own session, as those lines are, so none comes after
185
+ * it. Then one through the attachment's session, so the events it
186
+ * was sent meanwhile have arrived too. For a
187
+ * command to end after what it set off in the page, such as a menu
188
+ * handler, or a script body, which runs after its start report.
189
+ */
190
+ async drain() {
191
+ // Bounded: a page with its own scripting off (a CSP `sandbox`) never
192
+ // runs the timer, and has no console lines of its own to wait for.
193
+ await Promise.race([
194
+ this.page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0))).catch(() => undefined),
195
+ new Promise((resolve) => setTimeout(resolve, DRAIN_WAIT_MS)),
196
+ ]);
197
+ await this.caughtUp();
198
+ }
199
+ /**
200
+ * Resolves once the events the attachment's session had already been
201
+ * sent have arrived: a navigation's new document. Playwright's own
202
+ * session can run ahead of it.
203
+ */
204
+ async caughtUp() {
205
+ if (this.closed)
206
+ return;
207
+ await this.cdp.send('Runtime.evaluate', { expression: '0' }).catch(() => undefined);
208
+ }
209
+ // The main frame moved to the document `loaderId` names.
210
+ enterDocument(loaderId, url) {
211
+ const now = Date.now();
212
+ this.currentDocument.spans[this.currentDocument.spans.length - 1][1] = now;
213
+ let doc = this.documents.get(loaderId);
214
+ if (!doc) {
215
+ doc = { url, spans: [] };
216
+ this.documents.set(loaderId, doc);
217
+ // Only the last few: the back/forward cache holds no more.
218
+ for (const key of this.documents.keys()) {
219
+ if (this.documents.size <= 10)
220
+ break;
221
+ this.documents.delete(key);
222
+ }
223
+ }
224
+ doc.spans.push([now, Infinity]);
225
+ this.currentDocument = doc;
226
+ }
227
+ /**
228
+ * The page's document state and its DOM's last change, for `wait`.
229
+ * Null when there is no document to ask. Can wait on a page busy in
230
+ * a long script, so the caller bounds it.
231
+ */
232
+ async activity() {
233
+ const contextId = this.contextFor(WATCH_WORLD);
234
+ if (contextId === null || this.closed)
235
+ return null;
236
+ const { result, exceptionDetails } = await this.cdp.send('Runtime.evaluate', {
237
+ contextId,
238
+ returnByValue: true,
239
+ expression: `(() => {
240
+ const s = globalThis[Symbol.for(${JSON.stringify(ACTIVITY_KEY)})];
241
+ return { readyState: document.readyState, sinceDom: s ? Date.now() - s.last : 0, lastChanged: s ? s.target : '' };
242
+ })()`,
243
+ });
244
+ if (exceptionDetails)
245
+ return null;
246
+ return result.value;
247
+ }
248
+ /**
249
+ * Resolves once the page's main thread has a moment with nothing to
250
+ * do (`requestIdleCallback`), or after `maxMs`.
251
+ */
252
+ async idle(maxMs) {
253
+ const contextId = this.contextFor(WATCH_WORLD);
254
+ if (contextId === null || this.closed)
255
+ return;
256
+ await this.cdp.send('Runtime.evaluate', {
257
+ contextId,
258
+ awaitPromise: true,
259
+ expression: `new Promise((resolve) => requestIdleCallback(() => resolve(true), { timeout: ${maxMs} }))`,
260
+ });
261
+ }
262
+ /** The page's requests in flight, and milliseconds since one last ended. */
263
+ network() {
264
+ const now = Date.now();
265
+ return {
266
+ pending: [...this.requests].map(([request, at]) => ({ url: request.url(), ms: now - at })),
267
+ sinceLast: this.lastRequestEnd === 0 ? Infinity : now - this.lastRequestEnd,
268
+ };
269
+ }
270
+ // ---------- PageLink ----------
271
+ url() {
272
+ return this.page.url();
273
+ }
274
+ // The top frame's context the isolated scripts (`@inject-into
275
+ // content`, `@sandbox DOM`) run in: the extension's user-script world.
276
+ scriptsContext() {
277
+ const origin = this.options.watchOnly.extensionOrigin;
278
+ let found = null;
279
+ for (const [id, info] of this.contexts) {
280
+ if (info.frameId === this.mainFrameId && !info.isDefault && info.origin === origin)
281
+ found = id;
282
+ }
283
+ return found;
284
+ }
285
+ // The top frame's context for a world: '' is the page's own (MAIN).
286
+ // The newest match, should a stale one linger beside it (Map order is
287
+ // creation order).
288
+ contextFor(world) {
289
+ let found = null;
290
+ for (const [id, info] of this.contexts) {
291
+ if (info.frameId !== this.mainFrameId)
292
+ continue;
293
+ if (world === '' ? info.isDefault : info.name === world)
294
+ found = id;
295
+ }
296
+ return found;
297
+ }
298
+ /**
299
+ * Evaluates `expression` in the main frame, or in `frame` (one of the
300
+ * page's iframes), in the page's own world or one script's own
301
+ * scope, for `eval`. Resolves to the result as a
302
+ * line to print; rejects with a `ScriptThrew` for what the expression
303
+ * threw, a `NoEvalHook` for a script that hasn't run here, a
304
+ * `NoDocument` for a frame that has gone, or an error of the
305
+ * evaluation's own (the page gone, say).
306
+ */
307
+ async evaluate(expression, world, documentWaitMs = DOCUMENT_WAIT_MS, frame) {
308
+ const at = frame && frame !== this.page.mainFrame() ? await this.frameContexts(frame, documentWaitMs) : await this.topContexts(documentWaitMs);
309
+ if (typeof world === 'object')
310
+ return this.evaluateInScript(at, expression, world);
311
+ const contextId = at.page;
312
+ // The page context can go again between the wait and here.
313
+ if (contextId === null)
314
+ throw new NoDocument(BETWEEN_DOCUMENTS);
315
+ const { cdp } = at;
316
+ let { result, exceptionDetails } = await cdp.send('Runtime.evaluate', {
317
+ expression, contextId, awaitPromise: true, replMode: true, userGesture: true,
318
+ });
319
+ // Code written as a function body (`return` at the top level) is a
320
+ // syntax error as console input. Nothing ran, so it's safe to run
321
+ // again as an async function's body, whose `return` is the value.
322
+ // The newline keeps a trailing `// comment` off the closing `})`.
323
+ if (exceptionDetails && isIllegalReturn(exceptionDetails)) {
324
+ ({ result, exceptionDetails } = await cdp.send('Runtime.evaluate', {
325
+ expression: `(async () => {\n${expression}\n})()`, contextId, awaitPromise: true, userGesture: true,
326
+ }));
327
+ }
328
+ return this.evaluated(cdp, result, exceptionDetails);
329
+ }
330
+ // The top frame's page and scripts' worlds, once it has a document.
331
+ async topContexts(documentWaitMs) {
332
+ await this.documentReady(documentWaitMs);
333
+ return { cdp: this.cdp, page: this.contextFor(''), scripts: this.scriptsContext() };
334
+ }
335
+ /**
336
+ * The CDP target id of an iframe in a process of its own, for a raw
337
+ * CDP client; undefined for one in its parent's (see frame-sessions.ts).
338
+ */
339
+ async frameTargetId(frame) {
340
+ this.frameSessions ??= FrameSessions.attach(this.page);
341
+ return (await this.frameSessions)?.targetIdOf(frame);
342
+ }
343
+ /** An iframe's CDP frame id in the session that covers it, if found. */
344
+ async frameCdpId(frame) {
345
+ this.frameSessions ??= FrameSessions.attach(this.page);
346
+ return (await (await this.frameSessions)?.locate(frame))?.frameId;
347
+ }
348
+ // An iframe's page and scripts' worlds, through the session of the
349
+ // process it runs in (see frame-sessions.ts), opened the first time.
350
+ // A frame between documents (loading, or just moved to a process of
351
+ // its own) is looked for again until one comes, as for the top frame.
352
+ async frameContexts(frame, waitMs) {
353
+ this.frameSessions ??= FrameSessions.attach(this.page);
354
+ const sessions = await this.frameSessions;
355
+ const deadline = Date.now() + waitMs;
356
+ for (;;) {
357
+ const located = frame.isDetached() ? null : await sessions?.locate(frame);
358
+ if (located) {
359
+ const { session, frameId } = located;
360
+ let page = null;
361
+ let scripts = null;
362
+ for (const [id, info] of session.contexts) {
363
+ if (info.frameId !== frameId)
364
+ continue;
365
+ if (info.isDefault)
366
+ page = id;
367
+ else if (info.origin === this.options.watchOnly.extensionOrigin)
368
+ scripts = id;
369
+ }
370
+ if (page !== null)
371
+ return { cdp: session.cdp, page, scripts };
372
+ }
373
+ if (frame.isDetached() || Date.now() >= deadline) {
374
+ const name = `F${this.host.frameIds.number(frame)}`;
375
+ throw new NoDocument(frame.isDetached() ? `${name} has gone from the page` : `${name} has no document now. It is between pages.`);
376
+ }
377
+ await new Promise((r) => setTimeout(r, 100));
378
+ }
379
+ }
380
+ // A page that is reloading or navigating has no document for a moment,
381
+ // which a site that reloads itself often makes likely. Waits for the
382
+ // next one, and throws `NoDocument` if none comes. The new document's
383
+ // scripts may not have run yet; a world or hook they set up is looked
384
+ // for after this.
385
+ async documentReady(waitMs) {
386
+ if (this.contextFor('') !== null)
387
+ return;
388
+ const ready = await new Promise((resolve) => {
389
+ const timer = setTimeout(() => finish(false), waitMs);
390
+ const created = () => {
391
+ if (this.contextFor('') !== null)
392
+ finish(true);
393
+ };
394
+ // The tab closing ends the wait; the caller says it closed.
395
+ const closed = () => finish(false);
396
+ const finish = (found) => {
397
+ clearTimeout(timer);
398
+ this.cdp.off('Runtime.executionContextCreated', created);
399
+ this.page.off('close', closed);
400
+ resolve(found);
401
+ };
402
+ // After the listener install() added, which records the context.
403
+ this.cdp.on('Runtime.executionContextCreated', created);
404
+ this.page.once('close', closed);
405
+ });
406
+ if (!ready)
407
+ throw new NoDocument(`${BETWEEN_DOCUMENTS}. No new one loaded within ${Number((waitMs / 1000).toFixed(1))} seconds.`);
408
+ }
409
+ // Through the script's eval hook, a direct `eval` in its scope. That
410
+ // isn't console input: `await` and a top-level `return` are syntax
411
+ // errors there. So the code is compiled first (which runs nothing),
412
+ // and code that needs it runs in an async arrow: as its expression
413
+ // when it is one (`await x` gives x, as in the console), else as its
414
+ // body, whose `return` is the value.
415
+ //
416
+ // A page-world script's hook `eval`s under the page's Content Security
417
+ // Policy, but within a CDP evaluation, which Chrome exempts from it.
418
+ //
419
+ // A page-world script runs isolated on a page that turns off script in
420
+ // its own world (docs/injection.md#csp-sandbox-pages), so its hook is
421
+ // looked for in the scripts' world too, after its own.
422
+ async evaluateInScript(at, expression, scope) {
423
+ const { cdp } = at;
424
+ const hook = `globalThis[${JSON.stringify(scope.key)}]?.[${JSON.stringify(scope.id)}]`;
425
+ const hasHook = async (id) => {
426
+ if (id === null)
427
+ return false;
428
+ const { result: found } = await cdp.send('Runtime.evaluate', {
429
+ expression: `typeof ${hook} === 'function'`, contextId: id, returnByValue: true,
430
+ });
431
+ return found.value === true;
432
+ };
433
+ const tried = scope.world === 'MAIN' ? [at.page, at.scripts] : [at.scripts];
434
+ let contextId = null;
435
+ for (const id of tried) {
436
+ if (await hasHook(id)) {
437
+ contextId = id;
438
+ break;
439
+ }
440
+ }
441
+ if (contextId === null)
442
+ throw new NoEvalHook();
443
+ const { exceptionDetails: compiled } = await cdp.send('Runtime.compileScript', {
444
+ expression, sourceURL: '', persistScript: false, executionContextId: contextId,
445
+ });
446
+ let code = expression;
447
+ if (compiled && needsFunctionBody(compiled)) {
448
+ const asExpression = `(async () => (\n${expression}\n))()`;
449
+ const { exceptionDetails: notExpression } = await cdp.send('Runtime.compileScript', {
450
+ expression: asExpression, sourceURL: '', persistScript: false, executionContextId: contextId,
451
+ });
452
+ code = notExpression ? `(async () => {\n${expression}\n})()` : asExpression;
453
+ }
454
+ const { result, exceptionDetails } = await cdp.send('Runtime.evaluate', {
455
+ // Called detached, so its stack frame isn't named `[as <script id>]`.
456
+ expression: `(0, ${hook})(${JSON.stringify(code)})`, contextId, awaitPromise: true, userGesture: true,
457
+ });
458
+ return this.evaluated(cdp, result, exceptionDetails);
459
+ }
460
+ // An evaluation's result as a line to print, or what it threw.
461
+ async evaluated(cdp, result, exceptionDetails) {
462
+ if (exceptionDetails)
463
+ throw threw(exceptionDetails);
464
+ // A promise, as `eval fetch(url).then(...)` gives: what it settles
465
+ // to, where the console would show the promise itself.
466
+ if (result.subtype === 'promise' && result.objectId) {
467
+ const settled = await cdp.send('Runtime.awaitPromise', { promiseObjectId: result.objectId });
468
+ if (settled.exceptionDetails)
469
+ throw threw(settled.exceptionDetails);
470
+ return this.describe(cdp, settled.result);
471
+ }
472
+ return this.describe(cdp, result);
473
+ }
474
+ // A result as `eval` prints it: JSON where it has one, else what
475
+ // DevTools' console would show (an element, a Map, a function).
476
+ async describe(cdp, result) {
477
+ if (result.type === 'undefined')
478
+ return 'undefined';
479
+ if (!result.objectId)
480
+ return result.unserializableValue ?? JSON.stringify(result.value);
481
+ try {
482
+ if (result.type !== 'object' || (result.subtype && DESCRIBED.has(result.subtype)))
483
+ return result.description ?? result.type;
484
+ // Elements inside it as the console names them, not `{}`.
485
+ const { result: json } = await cdp.send('Runtime.callFunctionOn', {
486
+ objectId: result.objectId,
487
+ functionDeclaration: `function () {
488
+ return JSON.stringify(this, (key, value) => {
489
+ if (value instanceof Element) {
490
+ const classes = typeof value.className === 'string' && value.className.trim() ? '.' + value.className.trim().split(/\\s+/).join('.') : '';
491
+ return value.localName + (value.id ? '#' + value.id : '') + classes;
492
+ }
493
+ if (value instanceof Node) return value.nodeName;
494
+ if (value instanceof Map) return Object.fromEntries(value);
495
+ if (value instanceof Set) return [...value];
496
+ return value;
497
+ }, 2);
498
+ }`,
499
+ returnByValue: true,
500
+ });
501
+ const text = typeof json.value === 'string' ? json.value : result.description ?? result.type;
502
+ return text.length > 20_000 ? `${text.slice(0, 20_000)}…` : text;
503
+ }
504
+ catch {
505
+ // Circular, or a getter that throws: what the console would show.
506
+ return result.description ?? result.type;
507
+ }
508
+ finally {
509
+ await cdp.send('Runtime.releaseObject', { objectId: result.objectId }).catch(() => undefined);
510
+ }
511
+ }
512
+ // ---------- The tab's scripts ----------
513
+ /**
514
+ * Records a script the tab runs. Re-recording the same script
515
+ * replaces it.
516
+ */
517
+ async inject(prepared, options = {}) {
518
+ if (!prepared.id)
519
+ throw new Error(`Cannot record an invalid script: ${prepared.diagnostic}`);
520
+ const id = prepared.id;
521
+ if (this.injected.has(id))
522
+ await this.remove(id);
523
+ this.host.addScript(prepared, this);
524
+ this.injected.set(id, { script: prepared, at: Date.now(), into: options.immediately ? this.currentDocument : undefined });
525
+ }
526
+ /** The tab no longer runs a script, from its next navigation. */
527
+ async remove(scriptId) {
528
+ if (!this.injected.delete(scriptId))
529
+ return;
530
+ this.host.removeScript(scriptId, this);
531
+ }
532
+ injectedScripts() {
533
+ return [...this.injected.values()].map((e) => e.script);
534
+ }
535
+ /**
536
+ * The scripts the current document runs, and so hears from: recorded
537
+ * before it started, or run in it at once. One added later (a `load`
538
+ * into a tab a dialog kept from reloading) waits for the next load.
539
+ */
540
+ scriptsInDocument() {
541
+ const start = this.documentFirstStart();
542
+ return [...this.injected.values()]
543
+ .filter((e) => e.at <= start || e.into === this.currentDocument)
544
+ .map((e) => e.script);
545
+ }
546
+ /**
547
+ * When the main frame first entered its current document. Unlike
548
+ * `documentStart`, the same after the back/forward cache restores it,
549
+ * when its scripts don't run again.
550
+ */
551
+ documentFirstStart() {
552
+ return this.currentDocument.spans[0][0];
553
+ }
554
+ /** Stop watching, and drop the CDP session. */
555
+ async dispose() {
556
+ if (this.disposing)
557
+ return;
558
+ this.disposing = true;
559
+ this.page.off('request', this.onRequest);
560
+ this.page.off('requestfinished', this.onRequestEnd);
561
+ this.page.off('requestfailed', this.onRequestEnd);
562
+ this.requests.clear();
563
+ for (const id of [...this.injected.keys()])
564
+ await this.remove(id);
565
+ this.closed = true;
566
+ this.page.off('close', this.onClose);
567
+ this.host.detachPage(this);
568
+ attached.delete(this.page);
569
+ // A page stopped on a dialog answers nothing until it is closed, so
570
+ // this is not waited on for long; the session goes with the page.
571
+ const frames = this.frameSessions?.then((f) => f?.dispose());
572
+ await Promise.race([
573
+ Promise.all([this.cdp.detach().catch(() => undefined), frames?.catch(() => undefined)]),
574
+ new Promise((r) => setTimeout(r, 1000).unref()),
575
+ ]);
576
+ }
577
+ }
578
+ // A page's CDP target id, remembered: a page keeps its id for life.
579
+ const targetIds = new WeakMap();
580
+ /**
581
+ * The page's CDP target id (the `id` in `/json/list`, and what CDP's
582
+ * `Target.*` calls take), or null when it can't be read, as once the
583
+ * page has closed.
584
+ */
585
+ export async function targetIdOf(page) {
586
+ const known = targetIds.get(page);
587
+ if (known)
588
+ return known;
589
+ try {
590
+ const session = await page.context().newCDPSession(page);
591
+ try {
592
+ const { targetInfo } = await session.send('Target.getTargetInfo');
593
+ targetIds.set(page, targetInfo.targetId);
594
+ return targetInfo.targetId;
595
+ }
596
+ finally {
597
+ await session.detach().catch(() => undefined);
598
+ }
599
+ }
600
+ catch {
601
+ // Closed while being asked.
602
+ return null;
603
+ }
604
+ }
605
+ export async function attachPage(page, host, options) {
606
+ return PageAttachment.create(page, host, options);
607
+ }
@@ -0,0 +1,3 @@
1
+ import type { ParseVerdict } from '../parse-check-protocol.js';
2
+ /** The verdict, with probes attached when it's a syntax error. */
3
+ export declare function checkParseNode(source: string): ParseVerdict;