browser-debugger-cli 0.8.0 → 0.10.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 (304) hide show
  1. package/README.md +7 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +29 -6
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +189 -119
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  27. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  28. package/dist/commands/dom/helpers/query.d.ts +44 -17
  29. package/dist/commands/dom/helpers/query.js +300 -106
  30. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  31. package/dist/commands/dom/helpers/runElementCommand.js +98 -30
  32. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  33. package/dist/commands/dom/helpers/screenshot.js +239 -51
  34. package/dist/commands/dom/index.d.ts +4 -1
  35. package/dist/commands/dom/index.js +22 -7
  36. package/dist/commands/dom/inspect.d.ts +15 -0
  37. package/dist/commands/dom/inspect.js +82 -0
  38. package/dist/commands/dom/layout.d.ts +14 -0
  39. package/dist/commands/dom/layout.js +54 -0
  40. package/dist/commands/dom/listeners.d.ts +5 -1
  41. package/dist/commands/dom/listeners.js +15 -5
  42. package/dist/commands/dom/query.js +2 -3
  43. package/dist/commands/dom/screenshot.d.ts +12 -2
  44. package/dist/commands/dom/screenshot.js +27 -3
  45. package/dist/commands/dom/semanticUtils.d.ts +16 -10
  46. package/dist/commands/dom/semanticUtils.js +53 -16
  47. package/dist/commands/dom/wait.d.ts +13 -0
  48. package/dist/commands/dom/wait.js +83 -0
  49. package/dist/commands/helpJson.js +2 -2
  50. package/dist/commands/network/list.js +17 -13
  51. package/dist/commands/optionBehaviors.js +154 -21
  52. package/dist/commands/page.d.ts +3 -2
  53. package/dist/commands/page.js +100 -5
  54. package/dist/commands/peek.js +4 -11
  55. package/dist/commands/sessions.d.ts +8 -0
  56. package/dist/commands/sessions.js +19 -0
  57. package/dist/commands/shared/CommandRunner.js +4 -4
  58. package/dist/commands/shared/commonOptions.d.ts +4 -0
  59. package/dist/commands/shared/commonOptions.js +9 -0
  60. package/dist/commands/shared/dataFetcher.js +2 -2
  61. package/dist/commands/shared/followMode.d.ts +21 -1
  62. package/dist/commands/shared/followMode.js +29 -2
  63. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  64. package/dist/commands/shared/handleValidationError.js +12 -3
  65. package/dist/commands/shared/optionTypes.d.ts +61 -5
  66. package/dist/commands/shared/startHelpers.d.ts +66 -0
  67. package/dist/commands/shared/startHelpers.js +103 -13
  68. package/dist/commands/shared/validation.d.ts +14 -2
  69. package/dist/commands/shared/validation.js +20 -3
  70. package/dist/commands/start.d.ts +63 -0
  71. package/dist/commands/start.js +115 -15
  72. package/dist/commands/status.js +29 -7
  73. package/dist/commands/stop.js +7 -6
  74. package/dist/commands/tail.js +4 -11
  75. package/dist/commands/types.d.ts +2 -0
  76. package/dist/commands.js +2 -0
  77. package/dist/connection/chromeIdentity.d.ts +65 -0
  78. package/dist/connection/chromeIdentity.js +143 -0
  79. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  80. package/dist/connection/launcher/profilePreferences.js +151 -0
  81. package/dist/connection/launcher.d.ts +21 -2
  82. package/dist/connection/launcher.js +42 -16
  83. package/dist/connection/portReservation.d.ts +14 -4
  84. package/dist/connection/portReservation.js +21 -6
  85. package/dist/connection/startupExit.d.ts +8 -0
  86. package/dist/connection/startupExit.js +15 -6
  87. package/dist/constants.d.ts +6 -2
  88. package/dist/constants.js +9 -2
  89. package/dist/daemon/SessionController.js +23 -7
  90. package/dist/daemon/errors.d.ts +1 -1
  91. package/dist/daemon/errors.js +1 -1
  92. package/dist/daemon/launcher.d.ts +10 -2
  93. package/dist/daemon/launcher.js +8 -7
  94. package/dist/daemon/server/SocketServer.js +1 -2
  95. package/dist/daemon/session/Session.d.ts +20 -0
  96. package/dist/daemon/session/Session.js +80 -9
  97. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  98. package/dist/daemon/session/chromeConnection.js +45 -8
  99. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  100. package/dist/daemon/session/commandRegistry.js +113 -67
  101. package/dist/daemon/session/interactions.d.ts +48 -9
  102. package/dist/daemon/session/interactions.js +46 -9
  103. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  104. package/dist/daemon/session/triggeredRequests.js +157 -0
  105. package/dist/daemon/session/types.d.ts +5 -1
  106. package/dist/daemon.js +10630 -3601
  107. package/dist/errors/messages.d.ts +456 -24
  108. package/dist/errors/messages.js +862 -67
  109. package/dist/index.js +6915 -3401
  110. package/dist/ipc/client.d.ts +21 -1
  111. package/dist/ipc/client.js +35 -3
  112. package/dist/ipc/protocol/commands.d.ts +145 -5
  113. package/dist/ipc/protocol/commands.js +4 -0
  114. package/dist/ipc/protocol/domTypes.d.ts +291 -7
  115. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  116. package/dist/ipc/protocol/inspectTypes.js +10 -0
  117. package/dist/ipc/session/lifecycle.d.ts +8 -1
  118. package/dist/ipc/session/queries.d.ts +5 -1
  119. package/dist/ipc/session/types.d.ts +5 -0
  120. package/dist/ipc/transport/index.d.ts +2 -1
  121. package/dist/ipc/transport/index.js +2 -2
  122. package/dist/runtime/dom/actionEffects.d.ts +185 -0
  123. package/dist/runtime/dom/actionEffects.js +402 -0
  124. package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
  125. package/dist/runtime/dom/actionEffectsScripts.js +426 -0
  126. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  127. package/dist/runtime/dom/elementGeometry.js +553 -0
  128. package/dist/runtime/dom/elementInfo.d.ts +103 -0
  129. package/dist/runtime/dom/elementInfo.js +256 -0
  130. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  131. package/dist/runtime/dom/evalHelpers.js +136 -26
  132. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  133. package/dist/runtime/dom/eventListeners.js +184 -47
  134. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  135. package/dist/runtime/dom/formDiscovery.js +116 -16
  136. package/dist/runtime/dom/formFillHelpers/fill.d.ts +9 -0
  137. package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
  138. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  139. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  140. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  141. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  142. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  143. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  144. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  145. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  146. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  147. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  148. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  149. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  150. package/dist/runtime/dom/frameLayout.js +140 -0
  151. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  152. package/dist/runtime/dom/frameOrigin.js +62 -0
  153. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  154. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  155. package/dist/runtime/dom/frameSelection.d.ts +12 -1
  156. package/dist/runtime/dom/frameSelection.js +22 -3
  157. package/dist/runtime/dom/frames.d.ts +61 -5
  158. package/dist/runtime/dom/frames.js +329 -75
  159. package/dist/runtime/dom/inspect.d.ts +28 -0
  160. package/dist/runtime/dom/inspect.js +557 -0
  161. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  162. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  163. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  164. package/dist/runtime/dom/inspectCascade.js +371 -0
  165. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  166. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  167. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  168. package/dist/runtime/dom/inspectHints.js +305 -0
  169. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  170. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  171. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  172. package/dist/runtime/dom/inspectModel.js +184 -0
  173. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  174. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  175. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  176. package/dist/runtime/dom/inspectRules.js +101 -0
  177. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  178. package/dist/runtime/dom/inspectScripts.js +263 -0
  179. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  180. package/dist/runtime/dom/inspectTree.js +134 -0
  181. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  182. package/dist/runtime/dom/inspectVariables.js +94 -0
  183. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  184. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  185. package/dist/runtime/dom/layout.d.ts +71 -0
  186. package/dist/runtime/dom/layout.js +340 -0
  187. package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
  188. package/dist/runtime/dom/listenerPageScripts.js +365 -0
  189. package/dist/runtime/dom/listenerSummary.d.ts +136 -11
  190. package/dist/runtime/dom/listenerSummary.js +361 -22
  191. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  192. package/dist/runtime/dom/pageActivity.js +123 -0
  193. package/dist/runtime/dom/reactEventHelpers.d.ts +63 -2
  194. package/dist/runtime/dom/reactEventHelpers.js +220 -41
  195. package/dist/runtime/dom/targetNode.d.ts +80 -27
  196. package/dist/runtime/dom/targetNode.js +249 -33
  197. package/dist/runtime/dom/wait.d.ts +25 -0
  198. package/dist/runtime/dom/wait.js +199 -0
  199. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  200. package/dist/runtime/dom/waitCondition.js +75 -0
  201. package/dist/runtime/page/emulation.d.ts +71 -0
  202. package/dist/runtime/page/emulation.js +117 -0
  203. package/dist/runtime/page/loadingState.d.ts +36 -0
  204. package/dist/runtime/page/loadingState.js +86 -0
  205. package/dist/runtime/page/navigation.d.ts +46 -2
  206. package/dist/runtime/page/navigation.js +69 -33
  207. package/dist/session/QueryCacheManager.d.ts +11 -1
  208. package/dist/session/QueryCacheManager.js +25 -3
  209. package/dist/session/chromeOwners.d.ts +34 -0
  210. package/dist/session/chromeOwners.js +51 -0
  211. package/dist/session/cleanup/staleSession.d.ts +11 -1
  212. package/dist/session/cleanup/staleSession.js +17 -6
  213. package/dist/session/cleanup/userCommands.js +2 -4
  214. package/dist/session/metadata.d.ts +5 -1
  215. package/dist/session/metadata.js +2 -1
  216. package/dist/session/paths.d.ts +77 -3
  217. package/dist/session/paths.js +111 -5
  218. package/dist/session/port.d.ts +31 -7
  219. package/dist/session/port.js +50 -43
  220. package/dist/session/portClaims.d.ts +66 -0
  221. package/dist/session/portClaims.js +284 -0
  222. package/dist/session/sessionList.d.ts +58 -0
  223. package/dist/session/sessionList.js +199 -0
  224. package/dist/session/sessionName.d.ts +46 -0
  225. package/dist/session/sessionName.js +97 -0
  226. package/dist/telemetry/a11y.d.ts +18 -3
  227. package/dist/telemetry/a11y.js +170 -29
  228. package/dist/telemetry/console.d.ts +1 -0
  229. package/dist/telemetry/console.js +100 -5
  230. package/dist/telemetry/network.js +3 -1
  231. package/dist/telemetry/requestKinds.d.ts +32 -0
  232. package/dist/telemetry/requestKinds.js +61 -0
  233. package/dist/telemetry/requestState.d.ts +31 -0
  234. package/dist/telemetry/requestState.js +38 -0
  235. package/dist/types.d.ts +112 -3
  236. package/dist/ui/formatters/a11y.js +3 -0
  237. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  238. package/dist/ui/formatters/console/chronological.js +17 -4
  239. package/dist/ui/formatters/console/json.js +3 -4
  240. package/dist/ui/formatters/console/shared.d.ts +12 -0
  241. package/dist/ui/formatters/console.d.ts +2 -2
  242. package/dist/ui/formatters/console.js +1 -1
  243. package/dist/ui/formatters/details.d.ts +8 -0
  244. package/dist/ui/formatters/details.js +61 -4
  245. package/dist/ui/formatters/dom.d.ts +27 -14
  246. package/dist/ui/formatters/dom.js +88 -59
  247. package/dist/ui/formatters/form.js +29 -18
  248. package/dist/ui/formatters/inspect.d.ts +39 -0
  249. package/dist/ui/formatters/inspect.js +596 -0
  250. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  251. package/dist/ui/formatters/keyAttributes.js +84 -0
  252. package/dist/ui/formatters/layout.d.ts +31 -0
  253. package/dist/ui/formatters/layout.js +53 -0
  254. package/dist/ui/formatters/listeners.d.ts +3 -2
  255. package/dist/ui/formatters/listeners.js +73 -9
  256. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  257. package/dist/ui/formatters/networkHeaders.js +36 -3
  258. package/dist/ui/formatters/networkList.d.ts +29 -1
  259. package/dist/ui/formatters/networkList.js +86 -20
  260. package/dist/ui/formatters/preview.js +2 -1
  261. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  262. package/dist/ui/formatters/requestStatus.js +2 -30
  263. package/dist/ui/formatters/sessions.d.ts +12 -0
  264. package/dist/ui/formatters/sessions.js +40 -0
  265. package/dist/ui/formatters/status.d.ts +21 -2
  266. package/dist/ui/formatters/status.js +47 -10
  267. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  268. package/dist/ui/formatters/triggeredRequests.js +65 -0
  269. package/dist/ui/formatting.d.ts +19 -0
  270. package/dist/ui/formatting.js +31 -36
  271. package/dist/ui/messages/chrome.d.ts +9 -0
  272. package/dist/ui/messages/chrome.js +17 -5
  273. package/dist/ui/messages/commands.d.ts +504 -14
  274. package/dist/ui/messages/commands.js +835 -21
  275. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  276. package/dist/ui/messages/consoleMessages.js +17 -0
  277. package/dist/ui/messages/hints.js +2 -1
  278. package/dist/ui/messages/networkMessages.d.ts +14 -0
  279. package/dist/ui/messages/networkMessages.js +18 -0
  280. package/dist/ui/messages/preview.js +5 -4
  281. package/dist/ui/messages/session.d.ts +30 -21
  282. package/dist/ui/messages/session.js +48 -26
  283. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  284. package/dist/ui/messages/sessionCommand.js +52 -0
  285. package/dist/utils/async.d.ts +17 -0
  286. package/dist/utils/async.js +36 -0
  287. package/dist/utils/color.d.ts +84 -0
  288. package/dist/utils/color.js +376 -0
  289. package/dist/utils/cssValues.d.ts +109 -0
  290. package/dist/utils/cssValues.js +236 -0
  291. package/dist/utils/http.d.ts +22 -1
  292. package/dist/utils/http.js +28 -9
  293. package/dist/utils/selectorFilters.d.ts +48 -8
  294. package/dist/utils/selectorFilters.js +296 -53
  295. package/dist/utils/shellDetection.d.ts +8 -2
  296. package/dist/utils/shellDetection.js +120 -33
  297. package/dist/utils/suggestions.d.ts +26 -0
  298. package/dist/utils/suggestions.js +73 -0
  299. package/dist/utils/taskMappings.js +10 -0
  300. package/dist/utils/url.d.ts +12 -2
  301. package/dist/utils/url.js +69 -7
  302. package/package.json +1 -1
  303. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  304. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Layout of an element inside a cross-origin iframe that shares the page's
3
+ * process (an a11y query can return those).
4
+ *
5
+ * The element is measured in its own frame, whose scripts cannot see the top
6
+ * page, so its measurements are relative to the frame's viewport. The
7
+ * iframe element holding the frame is measured in its own document, and the
8
+ * element is placed in the top-level viewport through it: mapped by the
9
+ * frame's position and scale, clipped to the frame's viewport and to what clips the
10
+ * iframe, and fixed, scrolled or hidden as the iframe is.
11
+ */
12
+ import { mapBox } from './frameScopedConnection.js';
13
+ import { createLogger } from '../../ui/logging/index.js';
14
+ import { getErrorMessage } from '../../utils/errors.js';
15
+ const log = createLogger('dom');
16
+ /**
17
+ * Ids of all frames below a frame tree's root.
18
+ *
19
+ * @param tree - Frame tree
20
+ * @returns Frame ids, depth-first
21
+ */
22
+ export function childFrameIds(tree) {
23
+ return (tree?.childFrames ?? []).flatMap((child) => [child.frame.id, ...childFrameIds(child)]);
24
+ }
25
+ /**
26
+ * Backend node id of the document holding an element.
27
+ *
28
+ * @param cdp - CDP connection
29
+ * @param objectId - Remote object of the element
30
+ * @returns The document's backend node id, if it could be read
31
+ */
32
+ async function ownerDocumentId(cdp, objectId) {
33
+ const doc = (await cdp.send('Runtime.callFunctionOn', {
34
+ objectId,
35
+ functionDeclaration: 'function () { return this.ownerDocument; }',
36
+ }));
37
+ if (!doc.result.objectId)
38
+ return undefined;
39
+ const { node } = (await cdp.send('DOM.describeNode', {
40
+ objectId: doc.result.objectId,
41
+ }));
42
+ return node.backendNodeId;
43
+ }
44
+ /**
45
+ * The iframe element whose document holds an element.
46
+ *
47
+ * @param cdp - CDP connection
48
+ * @param objectId - Remote object of the element
49
+ * @returns Backend node id of the iframe element, undefined when not found
50
+ */
51
+ export async function findFrameOwner(cdp, objectId) {
52
+ try {
53
+ const documentId = await ownerDocumentId(cdp, objectId);
54
+ const { frameTree } = (await cdp.send('Page.getFrameTree'));
55
+ for (const frameId of childFrameIds(frameTree)) {
56
+ const owner = (await cdp.send('DOM.getFrameOwner', {
57
+ frameId,
58
+ }));
59
+ const { node } = (await cdp.send('DOM.describeNode', {
60
+ backendNodeId: owner.backendNodeId,
61
+ }));
62
+ if (node.contentDocument?.backendNodeId === documentId)
63
+ return owner.backendNodeId;
64
+ }
65
+ }
66
+ catch (error) {
67
+ log.debug(`Frame owner not found: ${getErrorMessage(error)}`);
68
+ }
69
+ return undefined;
70
+ }
71
+ /**
72
+ * Overlap of boxes.
73
+ *
74
+ * @param boxes - Boxes (null ones are left out)
75
+ * @returns Overlap (zero size when they do not overlap)
76
+ */
77
+ export function intersection(...boxes) {
78
+ const present = boxes.filter((box) => box !== null);
79
+ const left = Math.max(...present.map((box) => box.x));
80
+ const top = Math.max(...present.map((box) => box.y));
81
+ const right = Math.min(...present.map((box) => box.x + box.width));
82
+ const bottom = Math.min(...present.map((box) => box.y + box.height));
83
+ return { x: left, y: top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) };
84
+ }
85
+ /**
86
+ * Whether a box lies inside another.
87
+ *
88
+ * @param inner - Inner box
89
+ * @param outer - Outer box
90
+ * @returns True when it does
91
+ */
92
+ export function inside(inner, outer) {
93
+ return (inner.x >= outer.x &&
94
+ inner.y >= outer.y &&
95
+ inner.x + inner.width <= outer.x + outer.width &&
96
+ inner.y + inner.height <= outer.y + outer.height);
97
+ }
98
+ /**
99
+ * Place an element measured inside a cross-origin frame in the top-level
100
+ * viewport, through the measurements of the frame's iframe element: mapped by
101
+ * the frame's position and scale (border, padding, `transform`, `zoom`),
102
+ * clipped to the frame's viewport and to what clips the iframe.
103
+ *
104
+ * @param raw - The element, measured in its frame
105
+ * @param owner - The iframe element, measured in its document
106
+ * @param mapping - How the frame's viewport maps into the top-level viewport
107
+ * @returns The element's layout in the top-level page (unchanged when the
108
+ * iframe could not be measured)
109
+ */
110
+ export function placeInOwnerFrame(raw, owner, mapping) {
111
+ const frame = owner.elements[0];
112
+ if (!raw.page || !owner.page || !frame)
113
+ return raw;
114
+ const frameView = mapBox(mapping, { x: 0, y: 0, ...raw.page.viewport });
115
+ const elements = raw.elements.map((element) => {
116
+ const inner = element.geometry;
117
+ const outer = frame.geometry;
118
+ const rect = mapBox(mapping, inner.rect);
119
+ const ownClip = inner.clip ? mapBox(mapping, inner.clip) : null;
120
+ return {
121
+ ...element,
122
+ context: [frame.element, element.context].filter(Boolean).join(' > '),
123
+ geometry: {
124
+ ...inner,
125
+ rect,
126
+ clip: intersection(ownClip, frameView, outer.clip),
127
+ clipper: inner.clipper ?? (inside(rect, frameView) ? outer.clipper : frame.element),
128
+ hidden: inner.hidden ?? (outer.hidden ? 'inside a hidden iframe' : null),
129
+ invisible: inner.invisible ?? outer.invisible,
130
+ fixed: outer.fixed,
131
+ sticky: outer.sticky ?? false,
132
+ pageScroll: outer.pageScroll,
133
+ scrollLock: outer.scrollLock ?? null,
134
+ offset: mapping.origin,
135
+ },
136
+ };
137
+ });
138
+ return { ...raw, page: owner.page, elements, crossOriginFrame: false };
139
+ }
140
+ //# sourceMappingURL=frameLayout.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The origin a frame's scripts really run with, for `bdg dom frames`.
3
+ *
4
+ * Chrome reports `securityOrigin: "://"` for frames without an origin of
5
+ * their own (srcdoc, about:blank, data:) and the URL's origin for sandboxed
6
+ * frames, so neither tells whether the parent can reach the frame's
7
+ * document. The frame's default execution context knows: it reports the
8
+ * inherited origin for srcdoc and about:blank, and `"://"` for opaque ones
9
+ * (data: URLs, sandboxes without `allow-same-origin`). A frame that has no
10
+ * context yet falls back to the rules below; a frame inside an opaque one
11
+ * counts as opaque too (sandbox flags are inherited).
12
+ */
13
+ /** Origin of documents that share no origin with anything */
14
+ export declare const OPAQUE_ORIGIN = "null";
15
+ /** What is known about a frame's origin */
16
+ export interface FrameOriginFacts {
17
+ /** Frame URL */
18
+ url: string;
19
+ /** `Page.Frame.securityOrigin` */
20
+ securityOrigin: string;
21
+ /** Origin of the frame's default execution context, when it has one */
22
+ contextOrigin?: string | undefined;
23
+ /** Origin of the parent frame (already resolved) */
24
+ parentOrigin?: string | undefined;
25
+ /** `sandbox` attribute of the frame's element, when present */
26
+ sandbox?: string | undefined;
27
+ }
28
+ /**
29
+ * Whether a `sandbox` attribute makes the frame's origin opaque.
30
+ *
31
+ * @param sandbox - Attribute value (undefined when the attribute is missing)
32
+ * @returns True unless the attribute is missing or allows same-origin
33
+ */
34
+ export declare function isOpaqueSandbox(sandbox: string | undefined): boolean;
35
+ /**
36
+ * The origin a frame's scripts run with.
37
+ *
38
+ * @param facts - What Chrome reports about the frame
39
+ * @returns e.g. `https://example.com`, or `"null"` for an opaque origin
40
+ */
41
+ export declare function effectiveFrameOrigin(facts: FrameOriginFacts): string;
42
+ /**
43
+ * Whether the top page's scripts cannot reach a frame's document.
44
+ *
45
+ * @param origin - The frame's effective origin
46
+ * @param topOrigin - The page's effective origin
47
+ * @returns True for a different or opaque origin
48
+ */
49
+ export declare function isCrossOrigin(origin: string, topOrigin: string): boolean;
50
+ //# sourceMappingURL=frameOrigin.d.ts.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The origin a frame's scripts really run with, for `bdg dom frames`.
3
+ *
4
+ * Chrome reports `securityOrigin: "://"` for frames without an origin of
5
+ * their own (srcdoc, about:blank, data:) and the URL's origin for sandboxed
6
+ * frames, so neither tells whether the parent can reach the frame's
7
+ * document. The frame's default execution context knows: it reports the
8
+ * inherited origin for srcdoc and about:blank, and `"://"` for opaque ones
9
+ * (data: URLs, sandboxes without `allow-same-origin`). A frame that has no
10
+ * context yet falls back to the rules below; a frame inside an opaque one
11
+ * counts as opaque too (sandbox flags are inherited).
12
+ */
13
+ /** Origin of documents that share no origin with anything */
14
+ export const OPAQUE_ORIGIN = 'null';
15
+ /**
16
+ * Whether a reported origin names a real (tuple) origin.
17
+ *
18
+ * @param origin - Origin as Chrome reports it
19
+ * @returns False for `"://"`, `"null"` and empty
20
+ */
21
+ function isTupleOrigin(origin) {
22
+ return origin !== undefined && origin !== '' && origin !== '://' && origin !== OPAQUE_ORIGIN;
23
+ }
24
+ /**
25
+ * Whether a `sandbox` attribute makes the frame's origin opaque.
26
+ *
27
+ * @param sandbox - Attribute value (undefined when the attribute is missing)
28
+ * @returns True unless the attribute is missing or allows same-origin
29
+ */
30
+ export function isOpaqueSandbox(sandbox) {
31
+ if (sandbox === undefined)
32
+ return false;
33
+ return !sandbox.toLowerCase().split(/\s+/).includes('allow-same-origin');
34
+ }
35
+ /**
36
+ * The origin a frame's scripts run with.
37
+ *
38
+ * @param facts - What Chrome reports about the frame
39
+ * @returns e.g. `https://example.com`, or `"null"` for an opaque origin
40
+ */
41
+ export function effectiveFrameOrigin(facts) {
42
+ if (facts.contextOrigin !== undefined) {
43
+ return isTupleOrigin(facts.contextOrigin) ? facts.contextOrigin : OPAQUE_ORIGIN;
44
+ }
45
+ if (isOpaqueSandbox(facts.sandbox) || facts.parentOrigin === OPAQUE_ORIGIN)
46
+ return OPAQUE_ORIGIN;
47
+ if (isTupleOrigin(facts.securityOrigin))
48
+ return facts.securityOrigin;
49
+ const inherits = facts.url === '' || facts.url.startsWith('about:');
50
+ return inherits && isTupleOrigin(facts.parentOrigin) ? facts.parentOrigin : OPAQUE_ORIGIN;
51
+ }
52
+ /**
53
+ * Whether the top page's scripts cannot reach a frame's document.
54
+ *
55
+ * @param origin - The frame's effective origin
56
+ * @param topOrigin - The page's effective origin
57
+ * @returns True for a different or opaque origin
58
+ */
59
+ export function isCrossOrigin(origin, topOrigin) {
60
+ return origin === OPAQUE_ORIGIN || origin !== topOrigin;
61
+ }
62
+ //# sourceMappingURL=frameOrigin.js.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Run the interaction scripts inside the frame of a cached element that the
3
+ * top page cannot reach.
4
+ *
5
+ * An element from `bdg dom a11y query` may be in a cross-origin iframe that
6
+ * shares the page's process (same site, e.g. a consent dialog served from a
7
+ * subdomain). The top page cannot hold it, so its page scripts run in the
8
+ * element's own frame instead: every `Runtime.evaluate` becomes a
9
+ * `Runtime.callFunctionOn` on the element (which runs in the element's
10
+ * context), and mouse events, whose coordinates the scripts measure in the
11
+ * frame's viewport, are mapped into the top-level viewport
12
+ * ({@link FrameMapping}).
13
+ */
14
+ import type { CDPConnection } from '../../connection/cdp.js';
15
+ import type { LayoutBox, LayoutPoint } from '../../ipc/protocol/domTypes.js';
16
+ /**
17
+ * How a point in a frame's viewport maps into the top-level viewport:
18
+ * `top = origin + frame * scale` per axis (the scale is not 1 when the
19
+ * iframe, or an ancestor of it, is scaled with `transform` or `zoom`).
20
+ */
21
+ export interface FrameMapping {
22
+ /** Where the frame viewport's (0, 0) lies in the top-level viewport (border and padding included) */
23
+ origin: LayoutPoint;
24
+ scaleX: number;
25
+ scaleY: number;
26
+ }
27
+ /** A box as the frame and as CDP (top-level viewport) see it */
28
+ interface ReferenceBox {
29
+ /** `getBoundingClientRect()` in the frame */
30
+ rect: LayoutBox;
31
+ /** `DOM.getContentQuads` (top-level viewport), each as x1,y1,…,x4,y4 */
32
+ quads: number[][];
33
+ }
34
+ /**
35
+ * `Runtime.callFunctionOn` parameters that evaluate an expression in the
36
+ * context of the element `objectId` refers to.
37
+ *
38
+ * @param params - `Runtime.evaluate` parameters
39
+ * @param objectId - Remote object of the element
40
+ * @returns Parameters for `Runtime.callFunctionOn`
41
+ * @throws Error for a parameter `Runtime.callFunctionOn` has no counterpart for
42
+ * (e.g. `timeout`), so it is not dropped silently
43
+ */
44
+ export declare function evaluateOnNodeParams(params: Record<string, unknown>, objectId: string): Record<string, unknown>;
45
+ /**
46
+ * How the frame maps into the top-level viewport, from one box seen both ways.
47
+ *
48
+ * @param box - The reference box in the frame and in the top-level viewport
49
+ * @returns The mapping
50
+ * @throws CommandError (83) when the box has no quads or size, or is rotated or skewed
51
+ */
52
+ export declare function frameMappingFrom(box: ReferenceBox): FrameMapping;
53
+ /**
54
+ * Map a point of the frame's viewport into the top-level viewport.
55
+ *
56
+ * @param mapping - Frame mapping
57
+ * @param point - Point in the frame's viewport
58
+ * @returns Point in the top-level viewport
59
+ */
60
+ export declare function mapPoint(mapping: FrameMapping, point: LayoutPoint): LayoutPoint;
61
+ /**
62
+ * Map a box of the frame's viewport into the top-level viewport.
63
+ *
64
+ * @param mapping - Frame mapping
65
+ * @param box - Box in the frame's viewport
66
+ * @returns Box in the top-level viewport
67
+ */
68
+ export declare function mapBox(mapping: FrameMapping, box: LayoutBox): LayoutBox;
69
+ /**
70
+ * How the element's frame maps into the top-level viewport.
71
+ *
72
+ * @param cdp - CDP connection
73
+ * @param objectId - Remote object of the element
74
+ * @returns The mapping
75
+ * @throws CommandError (83) when the frame cannot be measured or is rotated or skewed
76
+ */
77
+ export declare function measureFrameMapping(cdp: CDPConnection, objectId: string): Promise<FrameMapping>;
78
+ /**
79
+ * A view of the session's connection whose page scripts run in the frame of
80
+ * one element and whose mouse events land on that frame.
81
+ *
82
+ * The mapping is measured at the first mouse event (after the scripts scrolled
83
+ * the element into view) and kept for the rest of the action; a failed
84
+ * measurement is not kept (the next event measures again) and fails the event.
85
+ *
86
+ * @param cdp - Session connection
87
+ * @param objectId - Remote object of the element
88
+ * @returns Connection to hand to the interaction functions
89
+ */
90
+ export declare function frameScopedConnection(cdp: CDPConnection, objectId: string): CDPConnection;
91
+ export {};
92
+ //# sourceMappingURL=frameScopedConnection.d.ts.map
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Run the interaction scripts inside the frame of a cached element that the
3
+ * top page cannot reach.
4
+ *
5
+ * An element from `bdg dom a11y query` may be in a cross-origin iframe that
6
+ * shares the page's process (same site, e.g. a consent dialog served from a
7
+ * subdomain). The top page cannot hold it, so its page scripts run in the
8
+ * element's own frame instead: every `Runtime.evaluate` becomes a
9
+ * `Runtime.callFunctionOn` on the element (which runs in the element's
10
+ * context), and mouse events, whose coordinates the scripts measure in the
11
+ * frame's viewport, are mapped into the top-level viewport
12
+ * ({@link FrameMapping}).
13
+ */
14
+ import { CommandError } from '../../errors/index.js';
15
+ import { frameMappingError } from '../../errors/messages.js';
16
+ import { getErrorMessage } from '../../utils/errors.js';
17
+ import { EXIT_CODES } from '../../utils/exitCodes.js';
18
+ /** `Runtime.evaluate` parameters that `Runtime.callFunctionOn` takes too */
19
+ const SHARED_EVALUATE_PARAMS = [
20
+ 'returnByValue',
21
+ 'userGesture',
22
+ 'awaitPromise',
23
+ 'objectGroup',
24
+ 'silent',
25
+ 'generatePreview',
26
+ 'throwOnSideEffect',
27
+ 'serializationOptions',
28
+ ];
29
+ /** `Runtime.evaluate` parameters with no `Runtime.callFunctionOn` counterpart */
30
+ const UNSUPPORTED_EVALUATE_PARAMS = [
31
+ 'timeout',
32
+ 'contextId',
33
+ 'uniqueContextId',
34
+ 'includeCommandLineAPI',
35
+ 'replMode',
36
+ 'disableBreaks',
37
+ 'allowUnsafeEvalBlockedByCSP',
38
+ ];
39
+ /** Slack (CSS px) for rounding when telling a scaled frame from an unscaled one */
40
+ const SCALE_SLACK_PX = 0.5;
41
+ /**
42
+ * `Runtime.callFunctionOn` parameters that evaluate an expression in the
43
+ * context of the element `objectId` refers to.
44
+ *
45
+ * @param params - `Runtime.evaluate` parameters
46
+ * @param objectId - Remote object of the element
47
+ * @returns Parameters for `Runtime.callFunctionOn`
48
+ * @throws Error for a parameter `Runtime.callFunctionOn` has no counterpart for
49
+ * (e.g. `timeout`), so it is not dropped silently
50
+ */
51
+ export function evaluateOnNodeParams(params, objectId) {
52
+ const unsupported = UNSUPPORTED_EVALUATE_PARAMS.filter((key) => params[key] !== undefined);
53
+ if (unsupported.length > 0) {
54
+ throw new Error(`Runtime.evaluate in a frame cannot take ${unsupported.join(', ')}`);
55
+ }
56
+ const shared = Object.fromEntries(SHARED_EVALUATE_PARAMS.filter((key) => params[key] !== undefined).map((key) => [
57
+ key,
58
+ params[key],
59
+ ]));
60
+ return {
61
+ ...shared,
62
+ objectId,
63
+ functionDeclaration: `function () { return (\n${String(params['expression'])}\n); }`,
64
+ };
65
+ }
66
+ /**
67
+ * Error for a frame that cannot be placed in the top-level viewport.
68
+ *
69
+ * @param problem - What went wrong
70
+ * @param detail - Underlying error, if any
71
+ * @returns Not found (83) error
72
+ */
73
+ function mappingError(problem, detail) {
74
+ const err = frameMappingError(problem, detail);
75
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
76
+ }
77
+ /**
78
+ * Whether a quad is an axis-aligned rectangle (not rotated or skewed).
79
+ *
80
+ * @param quad - x1,y1,…,x4,y4 clockwise from the top left
81
+ * @returns True when its edges are horizontal and vertical
82
+ */
83
+ function axisAligned(quad) {
84
+ const [x1, y1, x2, y2, x3, y3, x4, y4] = quad;
85
+ const near = (a, b) => Math.abs(a - b) <= SCALE_SLACK_PX;
86
+ return near(y1, y2) && near(x2, x3) && near(y3, y4) && near(x4, x1);
87
+ }
88
+ /**
89
+ * Scale along one axis: the length in the top-level viewport over the length
90
+ * in the frame, 1 when they differ by no more than rounding.
91
+ *
92
+ * @param top - Length in the top-level viewport
93
+ * @param frame - Length in the frame
94
+ * @returns Scale factor
95
+ */
96
+ function axisScale(top, frame) {
97
+ return Math.abs(top - frame) <= SCALE_SLACK_PX ? 1 : top / frame;
98
+ }
99
+ /**
100
+ * How the frame maps into the top-level viewport, from one box seen both ways.
101
+ *
102
+ * @param box - The reference box in the frame and in the top-level viewport
103
+ * @returns The mapping
104
+ * @throws CommandError (83) when the box has no quads or size, or is rotated or skewed
105
+ */
106
+ export function frameMappingFrom(box) {
107
+ const { rect, quads } = box;
108
+ if (quads.length === 0 || quads.some((quad) => quad.length !== 8))
109
+ throw mappingError('no-box');
110
+ if (rect.width <= 0 || rect.height <= 0)
111
+ throw mappingError('no-box');
112
+ if (!quads.every(axisAligned))
113
+ throw mappingError('rotated');
114
+ const xs = quads.flatMap((quad) => quad.filter((_, i) => i % 2 === 0));
115
+ const ys = quads.flatMap((quad) => quad.filter((_, i) => i % 2 === 1));
116
+ const left = Math.min(...xs);
117
+ const top = Math.min(...ys);
118
+ const scaleX = axisScale(Math.max(...xs) - left, rect.width);
119
+ const scaleY = axisScale(Math.max(...ys) - top, rect.height);
120
+ return { origin: { x: left - rect.x * scaleX, y: top - rect.y * scaleY }, scaleX, scaleY };
121
+ }
122
+ /**
123
+ * Map a point of the frame's viewport into the top-level viewport.
124
+ *
125
+ * @param mapping - Frame mapping
126
+ * @param point - Point in the frame's viewport
127
+ * @returns Point in the top-level viewport
128
+ */
129
+ export function mapPoint(mapping, point) {
130
+ return {
131
+ x: mapping.origin.x + point.x * mapping.scaleX,
132
+ y: mapping.origin.y + point.y * mapping.scaleY,
133
+ };
134
+ }
135
+ /**
136
+ * Map a box of the frame's viewport into the top-level viewport.
137
+ *
138
+ * @param mapping - Frame mapping
139
+ * @param box - Box in the frame's viewport
140
+ * @returns Box in the top-level viewport
141
+ */
142
+ export function mapBox(mapping, box) {
143
+ return {
144
+ ...mapPoint(mapping, box),
145
+ width: box.width * mapping.scaleX,
146
+ height: box.height * mapping.scaleY,
147
+ };
148
+ }
149
+ /**
150
+ * Page-side choice of the box to measure: the element's, or its document's
151
+ * root element when the element has no size (hidden, collapsed).
152
+ */
153
+ const REFERENCE_NODE_FUNCTION = `function () {
154
+ const r = this.getBoundingClientRect();
155
+ return r.width > 0 && r.height > 0 ? this : this.ownerDocument.documentElement;
156
+ }`;
157
+ /** Page-side `getBoundingClientRect()` as a plain box */
158
+ const CLIENT_RECT_FUNCTION = 'function () { const r = this.getBoundingClientRect(); return { x: r.left, y: r.top, width: r.width, height: r.height }; }';
159
+ /**
160
+ * Measure a box of the element's frame both in the frame and through CDP.
161
+ *
162
+ * @param cdp - CDP connection
163
+ * @param objectId - Remote object of the element
164
+ * @returns The reference box
165
+ * @throws CommandError (83) when it cannot be read
166
+ */
167
+ async function measureReferenceBox(cdp, objectId) {
168
+ try {
169
+ const reference = (await cdp.send('Runtime.callFunctionOn', {
170
+ objectId,
171
+ functionDeclaration: REFERENCE_NODE_FUNCTION,
172
+ }));
173
+ const referenceId = reference.result.objectId;
174
+ if (!referenceId)
175
+ throw mappingError('no-box');
176
+ const [quads, rect] = await Promise.all([
177
+ cdp.send('DOM.getContentQuads', {
178
+ objectId: referenceId,
179
+ }),
180
+ cdp.send('Runtime.callFunctionOn', {
181
+ objectId: referenceId,
182
+ functionDeclaration: CLIENT_RECT_FUNCTION,
183
+ returnByValue: true,
184
+ }),
185
+ ]);
186
+ const box = rect.result?.value;
187
+ if (!box)
188
+ throw mappingError('no-box');
189
+ return { rect: box, quads: quads.quads };
190
+ }
191
+ catch (error) {
192
+ if (error instanceof CommandError)
193
+ throw error;
194
+ throw mappingError('unreadable', getErrorMessage(error));
195
+ }
196
+ }
197
+ /**
198
+ * How the element's frame maps into the top-level viewport.
199
+ *
200
+ * @param cdp - CDP connection
201
+ * @param objectId - Remote object of the element
202
+ * @returns The mapping
203
+ * @throws CommandError (83) when the frame cannot be measured or is rotated or skewed
204
+ */
205
+ export async function measureFrameMapping(cdp, objectId) {
206
+ return frameMappingFrom(await measureReferenceBox(cdp, objectId));
207
+ }
208
+ /**
209
+ * A view of the session's connection whose page scripts run in the frame of
210
+ * one element and whose mouse events land on that frame.
211
+ *
212
+ * The mapping is measured at the first mouse event (after the scripts scrolled
213
+ * the element into view) and kept for the rest of the action; a failed
214
+ * measurement is not kept (the next event measures again) and fails the event.
215
+ *
216
+ * @param cdp - Session connection
217
+ * @param objectId - Remote object of the element
218
+ * @returns Connection to hand to the interaction functions
219
+ */
220
+ export function frameScopedConnection(cdp, objectId) {
221
+ let mapping;
222
+ const measured = () => {
223
+ mapping ??= measureFrameMapping(cdp, objectId).catch((error) => {
224
+ mapping = undefined;
225
+ throw error;
226
+ });
227
+ return mapping;
228
+ };
229
+ const send = async (method, params = {}, sessionId) => {
230
+ if (sessionId !== undefined)
231
+ return cdp.send(method, params, sessionId);
232
+ if (method === 'Runtime.evaluate') {
233
+ return cdp.send('Runtime.callFunctionOn', evaluateOnNodeParams(params, objectId));
234
+ }
235
+ if (method === 'Input.dispatchMouseEvent') {
236
+ const point = mapPoint(await measured(), { x: Number(params['x']), y: Number(params['y']) });
237
+ return cdp.send(method, { ...params, ...point });
238
+ }
239
+ return cdp.send(method, params);
240
+ };
241
+ return new Proxy(cdp, {
242
+ get(target, property) {
243
+ if (property === 'send')
244
+ return send;
245
+ const value = Reflect.get(target, property, target);
246
+ return typeof value === 'function'
247
+ ? value.bind(target)
248
+ : value;
249
+ },
250
+ });
251
+ }
252
+ //# sourceMappingURL=frameScopedConnection.js.map
@@ -5,7 +5,7 @@ import { CommandError } from '../../errors/index.js';
5
5
  import type { DomFrame } from '../../ipc/protocol/commands.js';
6
6
  /**
7
7
  * Pick the frame a `--frame` value names: a 0-based index, an exact `name`
8
- * or `id` attribute, or else a case-insensitive part of the URL.
8
+ * or `id` attribute, or else a case-insensitive part of the name, id or URL.
9
9
  *
10
10
  * @param frames - Frames of the page
11
11
  * @param query - Requested frame
@@ -13,6 +13,17 @@ import type { DomFrame } from '../../ipc/protocol/commands.js';
13
13
  * @throws CommandError (81) when empty or ambiguous, (83) when nothing matches
14
14
  */
15
15
  export declare function selectFrame(frames: DomFrame[], query: string): DomFrame;
16
+ /**
17
+ * Check that a `--frame` index still names the frame the last
18
+ * `bdg dom frames` listed at that index (frames added, removed or
19
+ * reordered since shift the indices; a navigation replaces every frame).
20
+ *
21
+ * @param query - Requested frame
22
+ * @param currentIds - Frame id of each frame now, by index
23
+ * @param listedIds - Frame id of each frame when last listed, undefined when never listed
24
+ * @throws CommandError (87) when the index names another frame (or none) now
25
+ */
26
+ export declare function assertFrameIndexCurrent(query: string, currentIds: string[], listedIds: string[] | undefined): void;
16
27
  /**
17
28
  * Build a CommandError from a message and suggestion.
18
29
  *
@@ -2,11 +2,11 @@
2
2
  * Which iframe a `bdg dom eval --frame` value names.
3
3
  */
4
4
  import { CommandError } from '../../errors/index.js';
5
- import { ambiguousFrameError, emptyFrameError, frameNotFoundError } from '../../errors/messages.js';
5
+ import { ambiguousFrameError, emptyFrameError, frameNotFoundError, staleFrameIndexError, } from '../../errors/messages.js';
6
6
  import { EXIT_CODES } from '../../utils/exitCodes.js';
7
7
  /**
8
8
  * Pick the frame a `--frame` value names: a 0-based index, an exact `name`
9
- * or `id` attribute, or else a case-insensitive part of the URL.
9
+ * or `id` attribute, or else a case-insensitive part of the name, id or URL.
10
10
  *
11
11
  * @param frames - Frames of the page
12
12
  * @param query - Requested frame
@@ -19,6 +19,25 @@ export function selectFrame(frames, query) {
19
19
  throw frameError(emptyFrameError(), EXIT_CODES.INVALID_ARGUMENTS);
20
20
  return single(frames, wanted, candidatesFor(frames, wanted));
21
21
  }
22
+ /**
23
+ * Check that a `--frame` index still names the frame the last
24
+ * `bdg dom frames` listed at that index (frames added, removed or
25
+ * reordered since shift the indices; a navigation replaces every frame).
26
+ *
27
+ * @param query - Requested frame
28
+ * @param currentIds - Frame id of each frame now, by index
29
+ * @param listedIds - Frame id of each frame when last listed, undefined when never listed
30
+ * @throws CommandError (87) when the index names another frame (or none) now
31
+ */
32
+ export function assertFrameIndexCurrent(query, currentIds, listedIds) {
33
+ const wanted = query.trim();
34
+ if (!listedIds || !/^\d+$/.test(wanted))
35
+ return;
36
+ const index = Number(wanted);
37
+ if (currentIds[index] === listedIds[index])
38
+ return;
39
+ throw frameError(staleFrameIndexError(index), EXIT_CODES.STALE_CACHE);
40
+ }
22
41
  /**
23
42
  * Frames a non-empty `--frame` value matches, by the first rule that applies.
24
43
  *
@@ -33,7 +52,7 @@ function candidatesFor(frames, wanted) {
33
52
  if (named.length > 0)
34
53
  return named;
35
54
  const needle = wanted.toLowerCase();
36
- return frames.filter((frame) => frame.url.toLowerCase().includes(needle));
55
+ return frames.filter((frame) => [frame.name, frame.id, frame.url].some((text) => text?.toLowerCase().includes(needle)));
37
56
  }
38
57
  /**
39
58
  * The only candidate, or an error listing the frames.