browser-debugger-cli 0.14.0 → 0.16.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 (167) hide show
  1. package/.claude/skills/bdg/SKILL.md +3 -2
  2. package/dist/cdp/methodTarget.d.ts +92 -0
  3. package/dist/cdp/methodTarget.js +159 -0
  4. package/dist/cdp/protocol.d.ts +16 -1
  5. package/dist/cdp/protocol.js +21 -0
  6. package/dist/cdp/schema.d.ts +55 -1
  7. package/dist/cdp/schema.js +134 -25
  8. package/dist/cdp/types.d.ts +3 -1
  9. package/dist/commands/cdp.d.ts +38 -1
  10. package/dist/commands/cdp.js +201 -133
  11. package/dist/commands/cleanup.js +21 -4
  12. package/dist/commands/dom/eval.d.ts +2 -1
  13. package/dist/commands/dom/eval.js +6 -21
  14. package/dist/commands/dom/formInteraction.js +8 -4
  15. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  16. package/dist/commands/dom/helpers/evalResult.js +59 -0
  17. package/dist/commands/dom/helpers/index.d.ts +4 -4
  18. package/dist/commands/dom/helpers/index.js +3 -3
  19. package/dist/commands/dom/helpers/query.d.ts +2 -2
  20. package/dist/commands/dom/helpers/query.js +2 -2
  21. package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
  22. package/dist/commands/dom/helpers/screenshot.js +50 -668
  23. package/dist/commands/dom/screenshot.js +56 -36
  24. package/dist/commands/helpJson.d.ts +1 -1
  25. package/dist/commands/helpJson.js +3 -3
  26. package/dist/commands/helpTopic.js +10 -4
  27. package/dist/commands/network/har.js +18 -14
  28. package/dist/commands/optionBehaviors.js +24 -9
  29. package/dist/commands/shared/CommandRunner.d.ts +5 -0
  30. package/dist/commands/shared/CommandRunner.js +18 -3
  31. package/dist/commands/shared/interrupt.d.ts +40 -0
  32. package/dist/commands/shared/interrupt.js +73 -0
  33. package/dist/commands/shared/optionTypes.d.ts +3 -0
  34. package/dist/commands/shared/outputFile.d.ts +2 -1
  35. package/dist/commands/shared/outputFile.js +7 -4
  36. package/dist/commands/shared/startHelpers.d.ts +26 -3
  37. package/dist/commands/shared/startHelpers.js +145 -23
  38. package/dist/commands/status.js +3 -1
  39. package/dist/commands/stop.js +2 -1
  40. package/dist/commands/types.d.ts +5 -0
  41. package/dist/connection/cdp.js +1 -16
  42. package/dist/connection/chromeIdentity.d.ts +24 -5
  43. package/dist/connection/chromeIdentity.js +53 -22
  44. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  45. package/dist/connection/launcher/flagsBuilder.js +107 -23
  46. package/dist/connection/launcher.d.ts +35 -2
  47. package/dist/connection/launcher.js +99 -12
  48. package/dist/connection/typed-cdp.d.ts +3 -2
  49. package/dist/constants.d.ts +3 -5
  50. package/dist/constants.js +3 -5
  51. package/dist/daemon/SessionController.d.ts +10 -5
  52. package/dist/daemon/SessionController.js +15 -8
  53. package/dist/daemon/ipcServer.js +1 -1
  54. package/dist/daemon/launcher.d.ts +22 -3
  55. package/dist/daemon/launcher.js +45 -8
  56. package/dist/daemon/session/Session.d.ts +5 -1
  57. package/dist/daemon/session/Session.js +9 -8
  58. package/dist/daemon/session/TelemetryStore.d.ts +5 -0
  59. package/dist/daemon/session/TelemetryStore.js +4 -0
  60. package/dist/daemon/session/captureGate.d.ts +59 -0
  61. package/dist/daemon/session/captureGate.js +96 -0
  62. package/dist/daemon/session/chromeConnection.d.ts +16 -1
  63. package/dist/daemon/session/chromeConnection.js +34 -4
  64. package/dist/daemon/session/collectors.d.ts +15 -0
  65. package/dist/daemon/session/collectors.js +39 -2
  66. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  67. package/dist/daemon/session/commandRegistry.js +48 -13
  68. package/dist/daemon/session/downloads.d.ts +32 -0
  69. package/dist/daemon/session/downloads.js +96 -0
  70. package/dist/daemon/session/interactions.d.ts +3 -2
  71. package/dist/daemon/session/interactions.js +7 -2
  72. package/dist/daemon/session/plugins.js +6 -0
  73. package/dist/daemon.js +18520 -17014
  74. package/dist/errors/CommandError.d.ts +2 -0
  75. package/dist/errors/issues.d.ts +1 -1
  76. package/dist/errors/messages.d.ts +81 -0
  77. package/dist/errors/messages.js +198 -6
  78. package/dist/index.js +1446 -1078
  79. package/dist/ipc/client.d.ts +20 -2
  80. package/dist/ipc/client.js +32 -6
  81. package/dist/ipc/protocol/commands.d.ts +36 -2
  82. package/dist/ipc/protocol/commands.js +1 -0
  83. package/dist/ipc/protocol/domTypes.d.ts +24 -1
  84. package/dist/ipc/session/queries.d.ts +3 -0
  85. package/dist/ipc/session/types.d.ts +5 -0
  86. package/dist/ipc/transport/IPCError.d.ts +9 -0
  87. package/dist/ipc/transport/IPCError.js +12 -0
  88. package/dist/ipc/transport/errors.d.ts +2 -1
  89. package/dist/ipc/transport/errors.js +4 -1
  90. package/dist/ipc/transport/index.d.ts +10 -2
  91. package/dist/ipc/transport/index.js +29 -4
  92. package/dist/runtime/dom/actionEffects.d.ts +48 -9
  93. package/dist/runtime/dom/actionEffects.js +269 -34
  94. package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
  95. package/dist/runtime/dom/actionEffectsScripts.js +101 -2
  96. package/dist/runtime/dom/captureArea.d.ts +35 -0
  97. package/dist/runtime/dom/captureArea.js +203 -0
  98. package/dist/runtime/dom/elementInfo.d.ts +13 -4
  99. package/dist/runtime/dom/elementInfo.js +12 -3
  100. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  101. package/dist/runtime/dom/evalHelpers.js +40 -12
  102. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  103. package/dist/runtime/dom/frames.d.ts +2 -1
  104. package/dist/runtime/dom/frames.js +3 -1
  105. package/dist/runtime/page/bdgWorld.d.ts +9 -0
  106. package/dist/runtime/page/bdgWorld.js +11 -0
  107. package/dist/runtime/page/captureEmulation.d.ts +119 -0
  108. package/dist/runtime/page/captureEmulation.js +189 -0
  109. package/dist/runtime/page/captureScroll.d.ts +24 -0
  110. package/dist/runtime/page/captureScroll.js +124 -0
  111. package/dist/runtime/page/emulation.js +6 -5
  112. package/dist/runtime/page/screenshot.d.ts +41 -0
  113. package/dist/runtime/page/screenshot.js +394 -0
  114. package/dist/runtime/page/userAgent.d.ts +86 -2
  115. package/dist/runtime/page/userAgent.js +154 -33
  116. package/dist/session/paths.d.ts +52 -3
  117. package/dist/session/paths.js +179 -7
  118. package/dist/session/portClaims.d.ts +0 -8
  119. package/dist/session/portClaims.js +1 -22
  120. package/dist/session/sessionList.d.ts +5 -1
  121. package/dist/session/sessionList.js +5 -1
  122. package/dist/telemetry/downloads.d.ts +127 -0
  123. package/dist/telemetry/downloads.js +265 -0
  124. package/dist/telemetry/har/builder.d.ts +12 -1
  125. package/dist/telemetry/har/builder.js +32 -9
  126. package/dist/telemetry/har/sanitize.d.ts +28 -0
  127. package/dist/telemetry/har/sanitize.js +184 -0
  128. package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
  129. package/dist/telemetry/har/sanitizeBody.js +541 -0
  130. package/dist/telemetry/har/types.d.ts +2 -0
  131. package/dist/telemetry/network.d.ts +4 -4
  132. package/dist/telemetry/network.js +38 -4
  133. package/dist/telemetry/networkRetention.d.ts +35 -14
  134. package/dist/telemetry/networkRetention.js +62 -26
  135. package/dist/types.d.ts +9 -14
  136. package/dist/ui/OutputBuilder.d.ts +3 -2
  137. package/dist/ui/OutputBuilder.js +4 -3
  138. package/dist/ui/formatters/cdp.d.ts +32 -9
  139. package/dist/ui/formatters/cdp.js +77 -6
  140. package/dist/ui/formatters/details.js +7 -15
  141. package/dist/ui/formatters/preview.d.ts +2 -0
  142. package/dist/ui/formatters/preview.js +7 -1
  143. package/dist/ui/formatters/sessions.d.ts +3 -2
  144. package/dist/ui/formatters/sessions.js +10 -3
  145. package/dist/ui/formatters/status.js +6 -1
  146. package/dist/ui/formatting.d.ts +7 -0
  147. package/dist/ui/formatting.js +13 -0
  148. package/dist/ui/logging/logger.d.ts +1 -1
  149. package/dist/ui/messages/chrome.d.ts +27 -6
  150. package/dist/ui/messages/chrome.js +78 -12
  151. package/dist/ui/messages/commands.d.ts +71 -3
  152. package/dist/ui/messages/commands.js +98 -3
  153. package/dist/ui/messages/networkMessages.d.ts +50 -5
  154. package/dist/ui/messages/networkMessages.js +50 -6
  155. package/dist/ui/messages/session.d.ts +8 -0
  156. package/dist/ui/messages/session.js +10 -0
  157. package/dist/utils/async.d.ts +3 -2
  158. package/dist/utils/async.js +16 -3
  159. package/dist/utils/atomicFile.d.ts +2 -1
  160. package/dist/utils/atomicFile.js +5 -2
  161. package/dist/utils/directories.d.ts +41 -0
  162. package/dist/utils/directories.js +48 -0
  163. package/dist/utils/http.d.ts +11 -4
  164. package/dist/utils/http.js +5 -3
  165. package/package.json +18 -4
  166. /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
  167. /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Screenshots of the page or one element (`bdg dom screenshot`), taken in the
3
+ * daemon: measuring, the emulation changes the capture needs, the capture and
4
+ * putting the emulation back all happen here, inside one `try`/`finally`, so
5
+ * a CLI interrupted mid-capture (Ctrl-C) cannot leave the page changed. The
6
+ * CLI only writes the returned image.
7
+ *
8
+ * The image travels base64-encoded in one IPC line: Chrome sends it the same
9
+ * way over its WebSocket (100 MiB at most), well below the IPC line limit
10
+ * (`MAX_JSONL_BUFFER_SIZE`).
11
+ */
12
+ import { TypedCDPConnection } from '../../connection/typed-cdp.js';
13
+ import { captureArea, getElementBounds } from '../dom/captureArea.js';
14
+ import { withBusyPageRecovery } from '../dom/evalHelpers.js';
15
+ import { CaptureEmulation, evaluateValue, scrollPosition, } from './captureEmulation.js';
16
+ import { scrollIntoViewAgain, scrollToElement } from './captureScroll.js';
17
+ import { calculateImageTokens, calculateResizeScale, isTallPage, shouldResize, } from './screenshotResize.js';
18
+ import { createLogger } from '../../ui/logging/index.js';
19
+ import { getErrorMessage } from '../../utils/errors.js';
20
+ const log = createLogger('dom');
21
+ /** JPEG quality when none is given */
22
+ const DEFAULT_JPEG_QUALITY = 90;
23
+ /**
24
+ * Take a screenshot: of the element `backendNodeId` names, else of the page.
25
+ * Whatever emulation the capture changed is put back before this returns or
26
+ * throws, from the session's record of its emulation at that time.
27
+ *
28
+ * A page whose scripts keep it busy gets them terminated
29
+ * ({@link withBusyPageRecovery}, exit 102); the capture then ends, and the
30
+ * error is reported once its emulation is back, so the next command does not
31
+ * race the restore. A capture whose client left (Ctrl-C) is not taken once
32
+ * that is known: only the restore runs.
33
+ *
34
+ * @param cdp - Session connection
35
+ * @param params - What to capture and how
36
+ * @param sessionViewport - Reads the session's emulated viewport, if any
37
+ * @param options - When the client left, and when a busy page is checked (tests shorten it)
38
+ * @returns The image (base64) and what was captured
39
+ */
40
+ export async function takeScreenshot(cdp, params, sessionViewport, options = {}) {
41
+ const { abandoned, recovery = {} } = options;
42
+ const emulation = new CaptureEmulation(cdp, sessionViewport);
43
+ const shot = captureAndRestore(cdp, params, emulation, abandoned);
44
+ try {
45
+ return await withBusyPageRecovery(cdp, shot, recovery);
46
+ }
47
+ catch (error) {
48
+ await shot.catch((captureError) => log.debug(`Capture ended after the busy page: ${getErrorMessage(captureError)}`));
49
+ throw error;
50
+ }
51
+ }
52
+ /**
53
+ * Capture, then put back the emulation the capture changed. A failed restore
54
+ * is reported only when the capture worked; otherwise the capture's error is.
55
+ *
56
+ * @param cdp - Session connection
57
+ * @param params - What to capture and how
58
+ * @param emulation - Emulation changes of this capture
59
+ * @param abandoned - Aborted when the client left
60
+ * @returns The image (base64) and what was captured
61
+ */
62
+ async function captureAndRestore(cdp, params, emulation, abandoned) {
63
+ let shot;
64
+ try {
65
+ shot =
66
+ params.backendNodeId === undefined
67
+ ? await capturePage(cdp, params, emulation, abandoned)
68
+ : await captureElement(cdp, { backendNodeId: params.backendNodeId }, params, emulation, abandoned);
69
+ }
70
+ catch (error) {
71
+ await emulation
72
+ .restore()
73
+ .catch((restoreError) => log.debug(`Restore after a failed capture: ${getErrorMessage(restoreError)}`));
74
+ throw error;
75
+ }
76
+ await emulation.restore();
77
+ return shot;
78
+ }
79
+ /**
80
+ * JPEG quality of a capture (none for PNG).
81
+ *
82
+ * @param params - Format and requested quality
83
+ * @returns Quality, or undefined for PNG
84
+ */
85
+ function jpegQuality(params) {
86
+ return params.format === 'jpeg' ? (params.quality ?? DEFAULT_JPEG_QUALITY) : undefined;
87
+ }
88
+ /**
89
+ * The page's pixel ratio.
90
+ *
91
+ * @param cdp - Session connection
92
+ * @returns `devicePixelRatio`, 1 when the page does not answer
93
+ */
94
+ async function pixelRatio(cdp) {
95
+ const value = await evaluateValue(cdp, 'window.devicePixelRatio');
96
+ return typeof value === 'number' ? value : 1;
97
+ }
98
+ /**
99
+ * The page's layout metrics.
100
+ *
101
+ * @param cdp - Session connection
102
+ * @returns `Page.getLayoutMetrics` result
103
+ */
104
+ function layoutMetrics(cdp) {
105
+ return new TypedCDPConnection(cdp).send('Page.getLayoutMetrics', {});
106
+ }
107
+ /**
108
+ * Capture an image, unless the client left: a capture of a tall page takes
109
+ * seconds, and nobody would get it.
110
+ *
111
+ * @param cdp - Session connection
112
+ * @param params - Format, quality, clip
113
+ * @param abandoned - Aborted when the client left
114
+ * @returns The image (base64) and its size in bytes
115
+ * @throws The abort reason when the client left
116
+ */
117
+ async function captureImage(cdp, params, abandoned) {
118
+ abandoned?.throwIfAborted();
119
+ const { data } = await new TypedCDPConnection(cdp).send('Page.captureScreenshot', params);
120
+ return { image: data, size: Buffer.byteLength(data, 'base64') };
121
+ }
122
+ /**
123
+ * Decide what a page capture covers: the whole page unless it is too tall
124
+ * (then the viewport), the viewport with `--no-full-page` or `--scroll`;
125
+ * scaled down to the token budget unless `--no-resize`.
126
+ *
127
+ * @param params - Capture options
128
+ * @param contentSize - Page size
129
+ * @param viewport - Viewport size
130
+ * @returns The plan
131
+ */
132
+ function planPageCapture(params, contentSize, viewport) {
133
+ const noResize = params.noResize ?? false;
134
+ const requestedFullPage = params.fullPage ?? true;
135
+ const pageIsTooTall = !noResize && requestedFullPage && isTallPage(contentSize.width, contentSize.height);
136
+ const fullPage = params.scroll === undefined && !pageIsTooTall && requestedFullPage;
137
+ const { width, height } = fullPage ? contentSize : viewport;
138
+ const resized = shouldResize(width, height, noResize);
139
+ const scale = resized ? calculateResizeScale(width, height) : 1;
140
+ return { fullPage, width, height, scale, resized, pageIsTooTall };
141
+ }
142
+ /**
143
+ * Page coordinates of the visible area's top-left corner. A capture clip is
144
+ * in page coordinates, so a viewport capture must start at the scroll
145
+ * position, not at the page origin (which shows nothing once scrolled). Read
146
+ * after any metrics override, which can move the scroll position.
147
+ *
148
+ * @param cdp - Session connection
149
+ * @returns Scroll offset of the visual viewport in CSS pixels
150
+ */
151
+ async function visibleAreaOrigin(cdp) {
152
+ const { cssVisualViewport } = await layoutMetrics(cdp);
153
+ return { x: cssVisualViewport.pageX, y: cssVisualViewport.pageY };
154
+ }
155
+ /**
156
+ * Capture the page. Auto-resizes oversized pages by default to keep Claude
157
+ * Vision token cost bounded; falls back to viewport capture when the page is
158
+ * taller than the tall-page threshold.
159
+ *
160
+ * @param cdp - Session connection
161
+ * @param params - Capture options
162
+ * @param emulation - Emulation changes of this capture
163
+ * @param abandoned - Aborted when the client left
164
+ * @returns Image and what was captured
165
+ */
166
+ async function capturePage(cdp, params, emulation, abandoned) {
167
+ if (params.scroll)
168
+ emulation.scrolledAwayFrom(await scrollToElement(cdp, params.scroll));
169
+ const devicePixelRatio = await pixelRatio(cdp);
170
+ const { contentSize, visualViewport } = await layoutMetrics(cdp);
171
+ const viewport = { width: visualViewport.clientWidth, height: visualViewport.clientHeight };
172
+ const plan = planPageCapture(params, contentSize, viewport);
173
+ await emulation.useUnitPixelRatio(devicePixelRatio, viewport);
174
+ if (devicePixelRatio !== 1 && params.scroll)
175
+ await scrollIntoViewAgain(cdp, params.scroll);
176
+ const origin = plan.fullPage ? { x: 0, y: 0 } : await visibleAreaOrigin(cdp);
177
+ const quality = jpegQuality(params);
178
+ const { image, size } = await captureImage(cdp, {
179
+ format: params.format,
180
+ ...(quality !== undefined && { quality }),
181
+ captureBeyondViewport: plan.fullPage,
182
+ clip: { ...origin, width: plan.width, height: plan.height, scale: plan.scale },
183
+ }, abandoned);
184
+ const screenshot = pageScreenshot(params, plan, size, viewport, contentSize);
185
+ return { image, screenshot };
186
+ }
187
+ /**
188
+ * What a page capture reports.
189
+ *
190
+ * @param params - Capture options
191
+ * @param plan - How it was taken
192
+ * @param size - Image size in bytes
193
+ * @param viewport - Viewport size
194
+ * @param contentSize - Page size
195
+ * @returns The report
196
+ */
197
+ function pageScreenshot(params, plan, size, viewport, contentSize) {
198
+ const width = Math.round(plan.width * plan.scale);
199
+ const height = Math.round(plan.height * plan.scale);
200
+ const quality = jpegQuality(params);
201
+ return {
202
+ format: params.format,
203
+ width,
204
+ height,
205
+ size,
206
+ fullPage: plan.fullPage,
207
+ captureMode: plan.fullPage ? 'full_page' : 'viewport',
208
+ finalTokens: calculateImageTokens(width, height),
209
+ ...(quality !== undefined && { quality }),
210
+ ...(!plan.fullPage && { viewport }),
211
+ ...resizeReport(plan.resized, plan.width, plan.height),
212
+ ...(plan.pageIsTooTall && params.scroll === undefined && tallPageReport(contentSize)),
213
+ ...(params.scroll !== undefined && { scrolledTo: params.scroll }),
214
+ };
215
+ }
216
+ /**
217
+ * Report of an auto-resize: the size before it.
218
+ *
219
+ * @param resized - Whether the image was scaled down
220
+ * @param width - Width before (CSS px)
221
+ * @param height - Height before (CSS px)
222
+ * @returns Fields to add, none when not resized
223
+ */
224
+ function resizeReport(resized, width, height) {
225
+ if (!resized)
226
+ return {};
227
+ return {
228
+ resized: true,
229
+ originalWidth: width,
230
+ originalHeight: height,
231
+ originalTokens: calculateImageTokens(width, height),
232
+ };
233
+ }
234
+ /**
235
+ * Report of a full-page capture skipped for a page too tall to read.
236
+ *
237
+ * @param contentSize - Page size
238
+ * @returns `fullPageSkipped` and a warning
239
+ */
240
+ function tallPageReport(contentSize) {
241
+ const aspectRatio = Math.round((contentSize.height / contentSize.width) * 10) / 10;
242
+ return {
243
+ fullPageSkipped: {
244
+ reason: 'page_too_tall',
245
+ originalHeight: contentSize.height,
246
+ aspectRatio,
247
+ },
248
+ warning: `Full page capture skipped: page too tall (${aspectRatio}:1 aspect ratio). Only viewport captured.`,
249
+ };
250
+ }
251
+ /**
252
+ * Whether an area (viewport coordinates) lies inside the viewport.
253
+ *
254
+ * @param area - Area
255
+ * @param view - Viewport size
256
+ * @returns True when fully inside
257
+ */
258
+ function insideView(area, view) {
259
+ return (area.x >= 0 &&
260
+ area.y >= 0 &&
261
+ area.x + area.width <= view.width &&
262
+ area.y + area.height <= view.height);
263
+ }
264
+ /**
265
+ * Measure the area to capture and, when it fits in the viewport but is not
266
+ * in view, scroll it to the middle first (put back by the emulation's
267
+ * restore). A capture inside the viewport keeps the page as it is; one
268
+ * beyond it makes Chrome lay the page out without its scrollbar, so for an
269
+ * area larger than the viewport the scrollbars are hidden first and the
270
+ * area measured in that layout (centered content would else move by half
271
+ * the scrollbar's width).
272
+ *
273
+ * @param cdp - Session connection
274
+ * @param ref - The element
275
+ * @param padding - Extra space around the area (CSS px)
276
+ * @param emulation - Emulation changes of this capture
277
+ * @returns The measurements
278
+ */
279
+ async function measureInView(cdp, ref, padding, emulation) {
280
+ const { cssVisualViewport } = await layoutMetrics(cdp);
281
+ const view = { width: cssVisualViewport.clientWidth, height: cssVisualViewport.clientHeight };
282
+ const first = await measureArea(cdp, ref, padding);
283
+ const { bounds } = first;
284
+ if (bounds.width > view.width || bounds.height > view.height) {
285
+ await emulation.keepLayoutWithoutScrollbars(view);
286
+ return { ...(await measureArea(cdp, ref, padding)), inView: false };
287
+ }
288
+ if (insideView(bounds, view))
289
+ return { ...first, inView: true };
290
+ emulation.scrolledAwayFrom(await scrollPosition(cdp));
291
+ const dx = bounds.x + bounds.width / 2 - view.width / 2;
292
+ const dy = bounds.y + bounds.height / 2 - view.height / 2;
293
+ await evaluateValue(cdp, `window.scrollBy(${dx}, ${dy})`);
294
+ const moved = await measureArea(cdp, ref, padding);
295
+ return { ...moved, inView: insideView(moved.bounds, view) };
296
+ }
297
+ /**
298
+ * Measure an element's border box and the area to capture.
299
+ *
300
+ * @param cdp - Session connection
301
+ * @param ref - The element
302
+ * @param padding - Extra space around the area (CSS px)
303
+ * @returns Border box and area (viewport coordinates)
304
+ */
305
+ async function measureArea(cdp, ref, padding) {
306
+ const box = await getElementBounds(cdp, ref);
307
+ return { box, bounds: await captureArea(cdp, ref, box, padding) };
308
+ }
309
+ /**
310
+ * Capture a single element: its border box, grown to include content
311
+ * overflowing it ({@link captureArea}). The box model is relative to the
312
+ * viewport and the capture clip to the page, so the page scroll is added
313
+ * (the reported bounds are page coordinates, like `dom layout`'s).
314
+ *
315
+ * @param cdp - Session connection
316
+ * @param ref - The element
317
+ * @param params - Capture options
318
+ * @param emulation - Emulation changes of this capture
319
+ * @param abandoned - Aborted when the client left
320
+ * @returns Image and what was captured
321
+ */
322
+ async function captureElement(cdp, ref, params, emulation, abandoned) {
323
+ const devicePixelRatio = await pixelRatio(cdp);
324
+ const { visualViewport } = await layoutMetrics(cdp);
325
+ await emulation.useUnitPixelRatio(devicePixelRatio, {
326
+ width: visualViewport.clientWidth,
327
+ height: visualViewport.clientHeight,
328
+ });
329
+ const measured = await measureInView(cdp, ref, params.padding ?? 0, emulation);
330
+ const { cssLayoutViewport } = await layoutMetrics(cdp);
331
+ const onPage = (area) => ({
332
+ ...area,
333
+ x: area.x + cssLayoutViewport.pageX,
334
+ y: area.y + cssLayoutViewport.pageY,
335
+ });
336
+ const clip = onPage(measured.bounds);
337
+ const resized = shouldResize(clip.width, clip.height, params.noResize ?? false);
338
+ const scale = resized ? calculateResizeScale(clip.width, clip.height) : 1;
339
+ const quality = jpegQuality(params);
340
+ const { image, size } = await captureImage(cdp, {
341
+ format: params.format,
342
+ ...(quality !== undefined && { quality }),
343
+ clip: { ...clip, scale },
344
+ captureBeyondViewport: !measured.inView,
345
+ }, abandoned);
346
+ const element = {
347
+ bounds: roundBounds(onPage(measured.box)),
348
+ ...(measured.bounds !== measured.box && { captured: roundBounds(clip) }),
349
+ ...(params.padding && { padding: params.padding }),
350
+ };
351
+ return { image, screenshot: elementScreenshot(params, clip, scale, size, element) };
352
+ }
353
+ /**
354
+ * What an element capture reports.
355
+ *
356
+ * @param params - Capture options
357
+ * @param clip - Area captured (CSS px)
358
+ * @param scale - Resize scale
359
+ * @param size - Image size in bytes
360
+ * @param element - Element bounds as reported
361
+ * @returns The report
362
+ */
363
+ function elementScreenshot(params, clip, scale, size, element) {
364
+ const width = Math.round(clip.width * scale);
365
+ const height = Math.round(clip.height * scale);
366
+ const quality = jpegQuality(params);
367
+ return {
368
+ format: params.format,
369
+ width,
370
+ height,
371
+ size,
372
+ fullPage: false,
373
+ captureMode: 'element',
374
+ finalTokens: calculateImageTokens(width, height),
375
+ element,
376
+ ...(quality !== undefined && { quality }),
377
+ ...resizeReport(scale !== 1, clip.width, clip.height),
378
+ };
379
+ }
380
+ /**
381
+ * Bounds in whole pixels.
382
+ *
383
+ * @param bounds - Bounds
384
+ * @returns Rounded bounds
385
+ */
386
+ function roundBounds(bounds) {
387
+ return {
388
+ x: Math.round(bounds.x),
389
+ y: Math.round(bounds.y),
390
+ width: Math.round(bounds.width),
391
+ height: Math.round(bounds.height),
392
+ };
393
+ }
394
+ //# sourceMappingURL=screenshot.js.map
@@ -3,15 +3,99 @@
3
3
  * client hints, so sites serve the page a user sees.
4
4
  */
5
5
  import type { CDPConnection } from '../../connection/cdp.js';
6
- import type { Logger } from '../../ui/logging/index.js';
6
+ import { type Logger } from '../../ui/logging/index.js';
7
+ /** A brand in the client hints (`Sec-CH-UA`, `navigator.userAgentData.brands`) */
8
+ export interface BrandVersion {
9
+ brand: string;
10
+ version: string;
11
+ }
12
+ /** CDP `Emulation.UserAgentMetadata`: the client hints Chrome sends and reports */
13
+ export interface UserAgentMetadata {
14
+ brands: BrandVersion[];
15
+ fullVersionList: BrandVersion[];
16
+ fullVersion: string;
17
+ platform: string;
18
+ platformVersion: string;
19
+ architecture: string;
20
+ bitness: string;
21
+ model: string;
22
+ mobile: boolean;
23
+ wow64: boolean;
24
+ formFactors: string[];
25
+ }
26
+ /** The machine the daemon runs on, as client hints name it */
27
+ export interface HostPlatform {
28
+ /** Client-hint platform (`macOS`, `Windows`, `Linux`) */
29
+ platform: string;
30
+ /** OS version as Chrome reports it */
31
+ platformVersion: string;
32
+ /** `arm` or `x86` */
33
+ architecture: string;
34
+ /** `64` or `32` */
35
+ bitness: string;
36
+ }
37
+ /** `Browser.getVersion` fields the metadata is built from */
38
+ export interface BrowserVersion {
39
+ product: string;
40
+ userAgent: string;
41
+ }
42
+ /**
43
+ * Chrome's brand list for a version, built the way Chrome builds it
44
+ * (`GenerateBrandVersionList` in Chromium's `user_agent_utils.cc`): a
45
+ * made-up brand, Chromium and the browser's brand, in an order and with a
46
+ * made-up name and version that depend on the major version.
47
+ *
48
+ * @param major - Major version (the seed)
49
+ * @param chromium - Chromium version to list
50
+ * @param browser - Browser brand and version
51
+ * @param greaseSuffix - Appended to the made-up version (`.0.0.0` in full versions)
52
+ * @returns Brand list
53
+ */
54
+ export declare function chromeBrandList(major: number, chromium: string, browser: BrandVersion, greaseSuffix?: string): BrandVersion[];
55
+ /**
56
+ * The client hints regular Chrome sends, for the browser `Browser.getVersion`
57
+ * describes. The browser brand is Microsoft Edge when the user agent says
58
+ * `Edg/`, Google Chrome otherwise (Chromium and Chrome for Testing look the
59
+ * same over CDP). Edge's Chromium version is only known to the major
60
+ * version. The OS version, architecture and bitness are the host's when it
61
+ * runs the platform the user agent names, empty otherwise (a remote Chrome).
62
+ *
63
+ * @param version - `Browser.getVersion` product and user agent
64
+ * @param host - The daemon's machine
65
+ * @returns Metadata for `Emulation.setUserAgentOverride`
66
+ */
67
+ export declare function regularChromeMetadata(version: BrowserVersion, host: HostPlatform): UserAgentMetadata;
68
+ /**
69
+ * The OS version Chrome reports on Linux or Windows, from `os.release()`:
70
+ * the kernel version's first three numbers on Linux, and on Windows `13.0.0`
71
+ * for Windows 11 and `10.0.0` before it (Chrome reports a Windows API
72
+ * version there, not the OS build).
73
+ *
74
+ * @param platform - `process.platform`
75
+ * @param release - `os.release()`
76
+ * @returns OS version, or empty when unknown
77
+ */
78
+ export declare function releasePlatformVersion(platform: string, release: string): string;
79
+ /**
80
+ * The daemon's machine as client hints describe it.
81
+ *
82
+ * @returns Platform, OS version, architecture and bitness
83
+ */
84
+ export declare function hostPlatform(): HostPlatform;
7
85
  /**
8
86
  * Send the user agent and client hints of regular Chrome from headless
9
87
  * Chrome: sites serve "HeadlessChrome" a different page (or a bot
10
88
  * challenge), so the page would not be the one a user sees. Not for a
11
89
  * session emulating a phone, whose emulation sets a mobile user agent.
12
90
  *
91
+ * The client hints are built from `Browser.getVersion` and the host
92
+ * ({@link regularChromeMetadata}) rather than read from the page: the page is
93
+ * still `about:blank`, which has no `navigator.userAgentData`, and an
94
+ * override without metadata empties the client hints.
95
+ *
13
96
  * @param cdp - CDP connection
14
97
  * @param logger - Logger for failures (the session works without it)
98
+ * @param known - `Browser.getVersion` result, when the caller has it
15
99
  */
16
- export declare function hideHeadlessUserAgent(cdp: CDPConnection, logger: Logger): Promise<void>;
100
+ export declare function hideHeadlessUserAgent(cdp: CDPConnection, logger: Logger, known?: BrowserVersion): Promise<void>;
17
101
  //# sourceMappingURL=userAgent.d.ts.map