browser-debugger-cli 0.8.0 → 0.9.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 (251) hide show
  1. package/README.md +4 -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 +27 -5
  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 +152 -113
  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/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -14,8 +14,10 @@
14
14
  * all, not even to `Runtime.terminateExecution`.
15
15
  */
16
16
  import { CDPConnection } from '../../connection/cdp.js';
17
- import { frameNotReadyError } from '../../errors/messages.js';
18
- import { evaluateScript, withBusyPageRecovery } from './evalHelpers.js';
17
+ import { CDPProtocolError } from '../../connection/errors.js';
18
+ import { frameLostDuringEvalError, frameNavigatedDuringEvalError, frameNotReadyError, frameRemovedDuringEvalError, pageClosedDuringEvalError, } from '../../errors/messages.js';
19
+ import { evaluateScript, isContextLostError, pageStillOpen, settledWithin, withBusyPageRecovery, } from './evalHelpers.js';
20
+ import { effectiveFrameOrigin, isCrossOrigin } from './frameOrigin.js';
19
21
  import { frameError, selectFrame } from './frameSelection.js';
20
22
  import { attachedSessionOf } from '../../telemetry/attachedTargets.js';
21
23
  import { senderFor } from '../../telemetry/objectExpander.js';
@@ -70,18 +72,45 @@ function recoverySender(fc, sessionId) {
70
72
  */
71
73
  async function sendToSession(fc, method, params, sessionId) {
72
74
  const command = fc.conn.send(method, params, sessionId);
73
- return (await withBusyPageRecovery(recoverySender(fc, sessionId), command));
75
+ const scope = sessionId === undefined ? 'page' : 'frame';
76
+ return (await withBusyPageRecovery(recoverySender(fc, sessionId), command, { scope }));
74
77
  }
75
78
  /**
76
- * Frame tree of a session.
79
+ * Default execution contexts of a session's frames, reported when Runtime is
80
+ * enabled.
81
+ *
82
+ * @param fc - Frame connection
83
+ * @param sessionId - Session, undefined for the page
84
+ * @returns Context of each frame, by frame id
85
+ */
86
+ async function defaultContexts(fc, sessionId) {
87
+ const contexts = new Map();
88
+ const stop = fc.conn.on('Runtime.executionContextCreated', ({ context }, eventSession) => {
89
+ const auxData = (context.auxData ?? {});
90
+ const frameId = auxData['frameId'];
91
+ if (eventSession !== sessionId || !auxData['isDefault'] || typeof frameId !== 'string')
92
+ return;
93
+ contexts.set(frameId, { uniqueId: context.uniqueId, origin: context.origin });
94
+ });
95
+ try {
96
+ await sendToSession(fc, 'Runtime.enable', {}, sessionId);
97
+ }
98
+ finally {
99
+ stop();
100
+ }
101
+ return contexts;
102
+ }
103
+ /**
104
+ * Frame tree of a session and the default contexts of its frames.
77
105
  *
78
106
  * @param fc - Frame connection
79
107
  * @param sessionId - Session, undefined for the page
80
108
  * @returns The tree
81
109
  */
82
- async function frameTree(fc, sessionId) {
110
+ async function readSession(fc, sessionId) {
111
+ const contexts = await defaultContexts(fc, sessionId);
83
112
  const { frameTree: tree } = await sendToSession(fc, 'Page.getFrameTree', {}, sessionId);
84
- return { tree, ...(sessionId && { sessionId }) };
113
+ return { tree, contexts, ...(sessionId && { sessionId }) };
85
114
  }
86
115
  /**
87
116
  * Attach to the out-of-process iframes directly below a session. Chrome
@@ -116,25 +145,46 @@ async function attachChildFrames(fc, parentSession) {
116
145
  */
117
146
  async function outOfProcessTrees(fc, parentSession) {
118
147
  const sessions = await attachChildFrames(fc, parentSession);
119
- const nested = await Promise.all(sessions.map(async (sessionId) => [
120
- await frameTree(fc, sessionId),
121
- ...(await outOfProcessTrees(fc, sessionId)),
122
- ]));
148
+ const nested = await Promise.all(sessions.map((sessionId) => childSessionTrees(fc, sessionId)));
123
149
  return nested.flat();
124
150
  }
125
151
  /**
126
- * All frames of the trees, each with its session.
152
+ * Trees of an out-of-process iframe and the ones nested in it. A frame
153
+ * removed while the page is being listed (its session is gone) is skipped.
154
+ *
155
+ * @param fc - Frame connection
156
+ * @param sessionId - The iframe's session
157
+ * @returns Its tree and the nested ones, or none when it went away
158
+ */
159
+ async function childSessionTrees(fc, sessionId) {
160
+ try {
161
+ return [await readSession(fc, sessionId), ...(await outOfProcessTrees(fc, sessionId))];
162
+ }
163
+ catch (error) {
164
+ if (!(error instanceof CDPProtocolError))
165
+ throw error;
166
+ log.debug(`Frame session ${sessionId} went away while listing: ${error.message}`);
167
+ return [];
168
+ }
169
+ }
170
+ /**
171
+ * All frames of the trees, each with its session and default context.
127
172
  *
128
173
  * @param trees - Frame trees
129
174
  * @returns Frames in tree order
130
175
  */
131
176
  function flattenTrees(trees) {
132
177
  const nodes = [];
133
- const walk = (tree, sessionId) => {
134
- nodes.push({ frame: tree.frame, ...(sessionId && { sessionId }) });
135
- tree.childFrames?.forEach((child) => walk(child, sessionId));
178
+ const walk = (tree, owner) => {
179
+ const context = owner.contexts.get(tree.frame.id);
180
+ nodes.push({
181
+ frame: tree.frame,
182
+ ...(owner.sessionId && { sessionId: owner.sessionId }),
183
+ ...(context && { context }),
184
+ });
185
+ tree.childFrames?.forEach((child) => walk(child, owner));
136
186
  };
137
- trees.forEach(({ tree, sessionId }) => walk(tree, sessionId));
187
+ trees.forEach((owner) => walk(owner.tree, owner));
138
188
  return nodes;
139
189
  }
140
190
  /**
@@ -175,7 +225,7 @@ function attributeValue(attributes = [], key) {
175
225
  * @param fc - Frame connection
176
226
  * @param frameId - Frame
177
227
  * @param ownerSession - Session of the parent frame
178
- * @returns `name` and `id` attributes when set
228
+ * @returns `name`, `id` and `sandbox` attributes when set
179
229
  */
180
230
  async function ownerAttributes(fc, frameId, ownerSession) {
181
231
  try {
@@ -183,78 +233,104 @@ async function ownerAttributes(fc, frameId, ownerSession) {
183
233
  const { node } = await sendToSession(fc, 'DOM.describeNode', { backendNodeId }, ownerSession);
184
234
  const name = attributeValue(node.attributes, 'name');
185
235
  const id = attributeValue(node.attributes, 'id');
186
- return { ...(name && { name }), ...(id && { id }) };
236
+ const sandbox = attributeValue(node.attributes, 'sandbox');
237
+ return { ...(name && { name }), ...(id && { id }), ...(sandbox !== undefined && { sandbox }) };
187
238
  }
188
239
  catch (error) {
189
240
  log.debug(`No iframe element for frame ${frameId}: ${getErrorMessage(error)}`);
190
241
  return {};
191
242
  }
192
243
  }
244
+ /**
245
+ * Where a frame sits in the listing: its origin, and the index of its parent.
246
+ * Parents are placed before their children, so a child can inherit its
247
+ * parent's origin.
248
+ *
249
+ * @param node - The frame
250
+ * @param index - Its position
251
+ * @param owner - Attributes of its iframe element
252
+ * @param state - Frames placed so far (updated)
253
+ * @returns Origin and parent index
254
+ */
255
+ function placeFrame(node, index, owner, state) {
256
+ const { frame, context } = node;
257
+ const parentId = frame.parentId ?? '';
258
+ const origin = effectiveFrameOrigin({
259
+ url: frame.url,
260
+ securityOrigin: frame.securityOrigin,
261
+ contextOrigin: context?.origin,
262
+ parentOrigin: state.originOf.get(parentId),
263
+ sandbox: owner.sandbox,
264
+ });
265
+ const parentIndex = state.indexOf.get(parentId);
266
+ state.originOf.set(frame.id, origin);
267
+ state.indexOf.set(frame.id, index);
268
+ return { origin, ...(parentIndex !== undefined && { parentIndex }) };
269
+ }
193
270
  /**
194
271
  * Describe an iframe for listing. Chrome names a frame after its element's
195
272
  * `id` when it has no `name`, so such a name is not repeated.
196
273
  *
197
- * @param fc - Frame connection
198
274
  * @param node - The frame
199
275
  * @param index - Its position
200
- * @param context - The page's origin and the session of each frame
276
+ * @param owner - Attributes of its iframe element
277
+ * @param state - Frames described so far (updated)
201
278
  * @returns The located frame
202
279
  */
203
- async function locateFrame(fc, node, index, context) {
204
- const { frame, sessionId } = node;
205
- const owner = await ownerAttributes(fc, frame.id, context.sessionOf.get(frame.parentId ?? ''));
280
+ function describeFrame(node, index, owner, state) {
281
+ const { frame, sessionId, context } = node;
206
282
  const name = owner.name ?? (frame.name === owner.id ? undefined : frame.name);
283
+ const { origin, parentIndex } = placeFrame(node, index, owner, state);
207
284
  const info = {
208
285
  index,
209
286
  url: frame.url + (frame.urlFragment ?? ''),
210
287
  ...(name && { name }),
211
288
  ...(owner.id && { id: owner.id }),
212
- origin: frame.securityOrigin,
213
- crossOrigin: frame.securityOrigin !== context.topOrigin,
289
+ origin,
290
+ crossOrigin: isCrossOrigin(origin, state.topOrigin),
214
291
  outOfProcess: sessionId !== undefined,
292
+ ...(parentIndex !== undefined && { parentIndex }),
293
+ };
294
+ const ownerSession = state.sessionOf.get(frame.parentId ?? '');
295
+ return {
296
+ info,
297
+ frameId: frame.id,
298
+ ...(sessionId && { sessionId }),
299
+ ...(ownerSession && { ownerSession }),
300
+ ...(context && { context }),
215
301
  };
216
- return { info, frameId: frame.id, ...(sessionId && { sessionId }) };
302
+ }
303
+ /**
304
+ * The listing state before the first iframe: the page's own origin.
305
+ *
306
+ * @param page - The page's session tree
307
+ * @param sessionOf - Session of each frame
308
+ * @returns Initial state
309
+ */
310
+ function initialListingState(page, sessionOf) {
311
+ const top = page.tree.frame;
312
+ const topOrigin = effectiveFrameOrigin({
313
+ url: top.url,
314
+ securityOrigin: top.securityOrigin,
315
+ contextOrigin: page.contexts.get(top.id)?.origin,
316
+ });
317
+ return { topOrigin, originOf: new Map([[top.id, topOrigin]]), indexOf: new Map(), sessionOf };
217
318
  }
218
319
  /**
219
320
  * Find every iframe of the page, nested and out-of-process ones included.
321
+ * Frames that go away while they are being listed are skipped.
220
322
  *
221
323
  * @param fc - Frame connection
222
324
  * @returns Iframes in listing order
223
325
  */
224
326
  async function discoverFrames(fc) {
225
- const [page, outOfProcess] = await Promise.all([frameTree(fc), outOfProcessTrees(fc)]);
327
+ const [page, outOfProcess] = await Promise.all([readSession(fc), outOfProcessTrees(fc)]);
226
328
  const nodes = flattenTrees([page, ...outOfProcess]);
227
329
  const sessionOf = new Map(nodes.map((node) => [node.frame.id, node.sessionId]));
228
- const context = { topOrigin: page.tree.frame.securityOrigin, sessionOf };
229
330
  const iframes = iframesInOrder(nodes, page.tree.frame.id);
230
- return Promise.all(iframes.map((node, index) => locateFrame(fc, node, index, context)));
231
- }
232
- /**
233
- * The default (main-world) execution context of a frame, so the script sees
234
- * the frame's own globals.
235
- *
236
- * @param fc - Frame connection
237
- * @param frame - The frame
238
- * @returns The context's unique id
239
- * @throws CommandError (83) when the frame has no context yet
240
- */
241
- async function defaultContext(fc, frame) {
242
- const contexts = [];
243
- const stop = fc.conn.on('Runtime.executionContextCreated', ({ context }, sessionId) => {
244
- if (sessionId === frame.sessionId)
245
- contexts.push(context);
246
- });
247
- try {
248
- await sendToSession(fc, 'Runtime.enable', {}, frame.sessionId);
249
- }
250
- finally {
251
- stop();
252
- }
253
- const auxData = (c) => (c.auxData ?? {});
254
- const context = contexts.find((c) => auxData(c)['frameId'] === frame.frameId && auxData(c)['isDefault']);
255
- if (!context)
256
- throw frameError(frameNotReadyError(frame.info.url), EXIT_CODES.RESOURCE_NOT_FOUND);
257
- return context.uniqueId;
331
+ const owners = await Promise.all(iframes.map((node) => ownerAttributes(fc, node.frame.id, sessionOf.get(node.frame.parentId ?? ''))));
332
+ const state = initialListingState(page, sessionOf);
333
+ return iframes.map((node, index) => describeFrame(node, index, owners[index] ?? {}, state));
258
334
  }
259
335
  /**
260
336
  * List the page's iframes.
@@ -279,7 +355,57 @@ async function resolveFrame(fc, query) {
279
355
  const frames = await discoverFrames(fc);
280
356
  const selected = selectFrame(frames.map((frame) => frame.info), query);
281
357
  const frame = frames[selected.index];
282
- return { frame, uniqueContextId: await defaultContext(fc, frame) };
358
+ if (!frame.context) {
359
+ throw frameError(frameNotReadyError(frame.info.url), EXIT_CODES.RESOURCE_NOT_FOUND);
360
+ }
361
+ return { frame, uniqueContextId: frame.context.uniqueId };
362
+ }
363
+ /** Chrome's messages when a frame (or the session of its parent) no longer exists */
364
+ const FRAME_GONE_PATTERN = /not found|no frame|no node/i;
365
+ /** How long the frame's parent gets to say whether the frame is still there */
366
+ const OWNER_CHECK_MS = 2_000;
367
+ /**
368
+ * Whether the frame's iframe element is still in its parent.
369
+ *
370
+ * @param conn - Connection the frame was found on
371
+ * @param frame - The frame
372
+ * @returns `attached`, `removed`, or `unknown` when the parent did not answer in time
373
+ * @throws Errors other than Chrome saying the frame is gone (e.g. a lost connection)
374
+ */
375
+ async function frameOwnerState(conn, frame) {
376
+ try {
377
+ const owner = conn.send('DOM.getFrameOwner', { frameId: frame.frameId }, frame.ownerSession);
378
+ return (await settledWithin(owner, OWNER_CHECK_MS)).settled ? 'attached' : 'unknown';
379
+ }
380
+ catch (error) {
381
+ if (!(error instanceof CDPProtocolError) || !FRAME_GONE_PATTERN.test(error.message))
382
+ throw error;
383
+ log.debug(`Frame ${frame.frameId} is gone: ${error.message}`);
384
+ return 'removed';
385
+ }
386
+ }
387
+ /**
388
+ * The error for a script whose frame context went away: the page was
389
+ * closed, the frame navigated (its iframe element is still there) or was
390
+ * removed.
391
+ *
392
+ * @param conn - Connection the frame was found on
393
+ * @param page - The session's connection to the page
394
+ * @param frame - The frame
395
+ * @returns Command error (83)
396
+ * @throws Errors of the checks other than Chrome saying the frame is gone
397
+ */
398
+ export async function frameContextLostError(conn, page, frame) {
399
+ if (!(await pageStillOpen(page))) {
400
+ return frameError(pageClosedDuringEvalError(), EXIT_CODES.RESOURCE_NOT_FOUND);
401
+ }
402
+ const messages = {
403
+ attached: frameNavigatedDuringEvalError,
404
+ removed: frameRemovedDuringEvalError,
405
+ unknown: frameLostDuringEvalError,
406
+ };
407
+ const err = messages[await frameOwnerState(conn, frame)](frame.url);
408
+ return frameError(err, EXIT_CODES.RESOURCE_NOT_FOUND);
283
409
  }
284
410
  /**
285
411
  * Evaluate a `bdg dom eval` script in one iframe, like {@link evaluateScript}
@@ -288,18 +414,31 @@ async function resolveFrame(fc, query) {
288
414
  * @param page - The session's connection
289
415
  * @param wsUrl - WebSocket URL of the page target
290
416
  * @param script - JavaScript expression
291
- * @param query - Requested frame (index, name/id attribute, or part of the URL)
417
+ * @param query - Requested frame (index, name/id attribute, or part of the name, id or URL)
292
418
  * @returns Value, type and the frame's URL
293
- * @throws CommandError (81/83) when the frame is ambiguous or missing, else as evaluateScript
419
+ * @throws CommandError (81/83) when the frame is ambiguous or missing, (83)
420
+ * when it navigated or was removed while the script ran, else as evaluateScript
294
421
  */
295
422
  export async function evaluateInFrame(page, wsUrl, script, query) {
296
423
  return withFrameConnection(page, wsUrl, async (fc) => {
297
424
  const { frame, uniqueContextId } = await resolveFrame(fc, query);
298
- const result = await evaluateScript(fc.conn, script, {
299
- ...(frame.sessionId && { sessionId: frame.sessionId }),
300
- uniqueContextId,
301
- });
302
- return { ...result, frame: frame.info.url };
425
+ try {
426
+ const result = await evaluateScript(fc.conn, script, {
427
+ ...(frame.sessionId && { sessionId: frame.sessionId }),
428
+ uniqueContextId,
429
+ recovery: recoverySender(fc, frame.sessionId),
430
+ });
431
+ return { ...result, frame: frame.info.url };
432
+ }
433
+ catch (error) {
434
+ if (!isContextLostError(error))
435
+ throw error;
436
+ throw await frameContextLostError(fc.conn, fc.page, {
437
+ frameId: frame.frameId,
438
+ url: frame.info.url,
439
+ ...(frame.ownerSession && { ownerSession: frame.ownerSession }),
440
+ });
441
+ }
303
442
  });
304
443
  }
305
444
  //# sourceMappingURL=frames.js.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Positions, sizes and visibility of elements (`bdg dom layout`).
3
+ *
4
+ * One page-side pass per command measures every match: its box in page and
5
+ * viewport coordinates (iframe offsets included), what clips it, whether it
6
+ * is rendered, what covers its center and the styles that decide how it
7
+ * shows. The measurements are classified outside the page
8
+ * ({@link classifyViewportPosition}).
9
+ */
10
+ import type { CDPConnection } from '../../connection/cdp.js';
11
+ import type { DomLayoutCommand } from '../../ipc/protocol/commands.js';
12
+ import type { LayoutComputedStyle, LayoutResult, PageLayout } from '../../ipc/protocol/domTypes.js';
13
+ import { type ElementGeometry } from './elementGeometry.js';
14
+ /** Matches measured per command (the rest are counted as omitted) */
15
+ export declare const LAYOUT_ELEMENT_LIMIT = 100;
16
+ /**
17
+ * Page-side end of a visible span kept clear of an overlay scrollbar along
18
+ * an edge (16 CSS px wide): overlay scrollbars (macOS, mobile) show for about a
19
+ * second after a scroll and catch hit tests, which then return the scroller.
20
+ * The span ends before the strip when it reaches into it and also shows
21
+ * outside it; without overlay scrollbars along the edge it is unchanged.
22
+ * Arguments: span start and end, the edge, whether overlay scrollbars show there.
23
+ */
24
+ export declare const CLEAR_OF_SCROLLBAR_JS = "(start, end, edge, overlay) =>\n overlay && end > edge - 16 && edge - 16 > start ? edge - 16 : end";
25
+ /** One element as {@link LAYOUT_JS} measures it. */
26
+ export interface RawElementLayout {
27
+ index: number;
28
+ tag: string;
29
+ element: string;
30
+ text: string;
31
+ context: string;
32
+ geometry: ElementGeometry;
33
+ coveredBy: string | null;
34
+ computed: LayoutComputedStyle;
35
+ }
36
+ /** What {@link LAYOUT_JS} returns. */
37
+ export interface RawLayout {
38
+ /** Elements the selector matched */
39
+ count: number;
40
+ /** Set when at least one element was measured */
41
+ page?: PageLayout;
42
+ elements: RawElementLayout[];
43
+ /** Measured inside a cross-origin iframe: `page` and the coordinates are that frame's */
44
+ crossOriginFrame?: boolean;
45
+ }
46
+ /**
47
+ * Measure the elements a selector (or cached index) refers to.
48
+ *
49
+ * @param cdp - CDP connection
50
+ * @param params - Selector (and index) or backend node id
51
+ * @returns Layout report
52
+ * @throws CommandError (83) no match, (81) index out of range or invalid
53
+ * selector, (87) the cached element left the page
54
+ */
55
+ export declare function inspectLayout(cdp: CDPConnection, params: DomLayoutCommand): Promise<LayoutResult>;
56
+ /**
57
+ * Turn page-side measurements into the layout report: rounded page and
58
+ * viewport coordinates, viewport position and text preview per element.
59
+ *
60
+ * @param raw - Measurements with at least one element
61
+ * @param params - Request (selector and index)
62
+ * @returns Layout report
63
+ */
64
+ export declare function buildLayoutResult(raw: RawLayout & {
65
+ page: PageLayout;
66
+ }, params: Pick<DomLayoutCommand, 'selector' | 'index'>): LayoutResult;
67
+ //# sourceMappingURL=layout.d.ts.map