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.
- package/.claude/skills/bdg/SKILL.md +3 -2
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +201 -133
- package/dist/commands/cleanup.js +21 -4
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +6 -21
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +3 -3
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/optionBehaviors.js +24 -9
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +3 -0
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +35 -2
- package/dist/connection/launcher.js +99 -12
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +3 -5
- package/dist/constants.js +3 -5
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +22 -3
- package/dist/daemon/launcher.js +45 -8
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +48 -13
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +18520 -17014
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +81 -0
- package/dist/errors/messages.js +198 -6
- package/dist/index.js +1446 -1078
- package/dist/ipc/client.d.ts +20 -2
- package/dist/ipc/client.js +32 -6
- package/dist/ipc/protocol/commands.d.ts +36 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +10 -2
- package/dist/ipc/transport/index.js +29 -4
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +12 -3
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +52 -3
- package/dist/session/paths.js +179 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +32 -9
- package/dist/telemetry/har/sanitize.d.ts +28 -0
- package/dist/telemetry/har/sanitize.js +184 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
- package/dist/telemetry/har/sanitizeBody.js +541 -0
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +27 -6
- package/dist/ui/messages/chrome.js +78 -12
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +50 -5
- package/dist/ui/messages/networkMessages.js +50 -6
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -0
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
|
@@ -529,12 +529,13 @@ export async function frameContextLostError(conn, page, frame) {
|
|
|
529
529
|
* @param script - JavaScript expression
|
|
530
530
|
* @param query - Requested frame (index, name/id attribute, or part of the name, id or URL)
|
|
531
531
|
* @param listedIds - Frame id behind each index of the last `dom frames` listing, if any
|
|
532
|
+
* @param full - `--full`: copy the result with every entry
|
|
532
533
|
* @returns Value, type and the frame's URL
|
|
533
534
|
* @throws CommandError (81/83) when the frame is ambiguous or missing, (87)
|
|
534
535
|
* when an index names another frame than when it was listed, (83) when it
|
|
535
536
|
* navigated or was removed while the script ran, else as evaluateScript
|
|
536
537
|
*/
|
|
537
|
-
export async function evaluateInFrame(page, wsUrl, script, query, listedIds) {
|
|
538
|
+
export async function evaluateInFrame(page, wsUrl, script, query, listedIds, full = false) {
|
|
538
539
|
return withFrameConnection(page, wsUrl, async (fc) => {
|
|
539
540
|
const { frame, uniqueContextId } = await resolveFrame(fc, query, listedIds);
|
|
540
541
|
try {
|
|
@@ -542,6 +543,7 @@ export async function evaluateInFrame(page, wsUrl, script, query, listedIds) {
|
|
|
542
543
|
...(frame.sessionId && { sessionId: frame.sessionId }),
|
|
543
544
|
uniqueContextId,
|
|
544
545
|
recovery: recoverySender(fc, frame.sessionId),
|
|
546
|
+
full,
|
|
545
547
|
});
|
|
546
548
|
return { ...result, frame: frame.info.url };
|
|
547
549
|
}
|
|
@@ -23,6 +23,15 @@ import type { Protocol } from '../../connection/typed-cdp.js';
|
|
|
23
23
|
type PageConnection = Pick<CDPConnection, 'send'> & Partial<Pick<CDPConnection, 'on'>>;
|
|
24
24
|
/** Name of bdg's isolated world (shown in DevTools' context selector) */
|
|
25
25
|
export declare const BDG_WORLD_NAME = "bdg";
|
|
26
|
+
/**
|
|
27
|
+
* Whether bdg's world is known for a connection: created or being created,
|
|
28
|
+
* and not forgotten since (the top frame navigated, or its contexts were
|
|
29
|
+
* cleared). Scripts left in a forgotten world went with its document.
|
|
30
|
+
*
|
|
31
|
+
* @param cdp - Connection to the page
|
|
32
|
+
* @returns True while the world is known
|
|
33
|
+
*/
|
|
34
|
+
export declare function hasBdgWorld(cdp: PageConnection): boolean;
|
|
26
35
|
/**
|
|
27
36
|
* `Runtime.evaluate` in bdg's world of the top frame. A call that names its
|
|
28
37
|
* own context is sent as it is.
|
|
@@ -37,6 +37,17 @@ function worldContext(cdp) {
|
|
|
37
37
|
worlds.set(cdp, created);
|
|
38
38
|
return created;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether bdg's world is known for a connection: created or being created,
|
|
42
|
+
* and not forgotten since (the top frame navigated, or its contexts were
|
|
43
|
+
* cleared). Scripts left in a forgotten world went with its document.
|
|
44
|
+
*
|
|
45
|
+
* @param cdp - Connection to the page
|
|
46
|
+
* @returns True while the world is known
|
|
47
|
+
*/
|
|
48
|
+
export function hasBdgWorld(cdp) {
|
|
49
|
+
return worlds.has(cdp);
|
|
50
|
+
}
|
|
40
51
|
/**
|
|
41
52
|
* Create the world in the top frame and forget it when the page changes.
|
|
42
53
|
*
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page emulation a screenshot changes for its capture, and puts back.
|
|
3
|
+
*
|
|
4
|
+
* A capture at a pixel ratio of 1 on a high-DPI page, and one beyond the
|
|
5
|
+
* viewport, override the device metrics (and hide the scrollbars). Each
|
|
6
|
+
* change is recorded before it is sent, so {@link CaptureEmulation.restore}
|
|
7
|
+
* (called once the capture ended, however it ended) undoes exactly what may
|
|
8
|
+
* have been changed: the session's emulation is put back from the daemon's
|
|
9
|
+
* own record of it as it is then (a `page emulate` during the capture
|
|
10
|
+
* counts), not from a file.
|
|
11
|
+
*/
|
|
12
|
+
import type { CDPConnection } from '../../connection/cdp.js';
|
|
13
|
+
import type { ViewportSize } from '../../types.js';
|
|
14
|
+
/** Width and height in CSS px */
|
|
15
|
+
export interface Size {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
/** A page position in CSS px */
|
|
20
|
+
export interface ScrollPosition {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate one of bdg's page scripts and return its value.
|
|
26
|
+
*
|
|
27
|
+
* @param cdp - Session connection
|
|
28
|
+
* @param expression - Script
|
|
29
|
+
* @returns Its value, or undefined when it threw
|
|
30
|
+
*/
|
|
31
|
+
export declare function evaluateValue(cdp: CDPConnection, expression: string): Promise<unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Two numbers a page script returned as an array.
|
|
34
|
+
*
|
|
35
|
+
* @param cdp - Session connection
|
|
36
|
+
* @param expression - Script returning `[a, b]`
|
|
37
|
+
* @returns The numbers, undefined where the page did not answer
|
|
38
|
+
*/
|
|
39
|
+
export declare function evaluatePair(cdp: CDPConnection, expression: string): Promise<[number | undefined, number | undefined]>;
|
|
40
|
+
/**
|
|
41
|
+
* The page's scroll position.
|
|
42
|
+
*
|
|
43
|
+
* @param cdp - Session connection
|
|
44
|
+
* @returns Scroll offsets in CSS px
|
|
45
|
+
*/
|
|
46
|
+
export declare function scrollPosition(cdp: CDPConnection): Promise<ScrollPosition>;
|
|
47
|
+
/**
|
|
48
|
+
* The emulation changes of one capture, undone by {@link restore}.
|
|
49
|
+
*/
|
|
50
|
+
export declare class CaptureEmulation {
|
|
51
|
+
private readonly connection;
|
|
52
|
+
private readonly sessionViewport;
|
|
53
|
+
private readonly cdp;
|
|
54
|
+
private metricsChanged;
|
|
55
|
+
private scrollbarsHidden;
|
|
56
|
+
private scrolledFrom;
|
|
57
|
+
/**
|
|
58
|
+
* @param connection - Session connection
|
|
59
|
+
* @param sessionViewport - Reads the session's emulated viewport
|
|
60
|
+
* (`--viewport`, `--mobile`, `page emulate`) as it is now; put back
|
|
61
|
+
* afterwards, none clears the override
|
|
62
|
+
*/
|
|
63
|
+
constructor(connection: CDPConnection, sessionViewport: () => ViewportSize | undefined);
|
|
64
|
+
/**
|
|
65
|
+
* Capture at a pixel ratio of 1 (CSS px = image px) on a high-DPI page:
|
|
66
|
+
* the viewport is overridden at the session's viewport, else the window's
|
|
67
|
+
* size with its scrollbars (so the layout does not change).
|
|
68
|
+
*
|
|
69
|
+
* @param devicePixelRatio - Page's pixel ratio
|
|
70
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
71
|
+
*/
|
|
72
|
+
useUnitPixelRatio(devicePixelRatio: number, viewport: Size): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Lay the page out at its current width without scrollbars: a capture
|
|
75
|
+
* beyond the viewport hides them, and without this the page would widen by
|
|
76
|
+
* the scrollbar and centered content move after it was measured. The
|
|
77
|
+
* viewport is overridden at the visible size (CSS px, pixel ratio 1; still
|
|
78
|
+
* a phone in a phone session).
|
|
79
|
+
*
|
|
80
|
+
* @param view - Visible viewport size
|
|
81
|
+
*/
|
|
82
|
+
keepLayoutWithoutScrollbars(view: Size): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Record where the page was scrolled before the capture moved it, to
|
|
85
|
+
* scroll it back afterwards (the first position recorded wins).
|
|
86
|
+
*
|
|
87
|
+
* @param position - Scroll position before the capture
|
|
88
|
+
*/
|
|
89
|
+
scrolledAwayFrom(position: ScrollPosition): void;
|
|
90
|
+
/**
|
|
91
|
+
* Put back what the capture changed: scrollbars shown, the session's
|
|
92
|
+
* device metrics (its viewport and a phone's touch input, which a capture
|
|
93
|
+
* beyond the viewport turns off), else none, and the scroll position. Each
|
|
94
|
+
* step runs even when an earlier one failed.
|
|
95
|
+
*
|
|
96
|
+
* @throws The first step's error, once every step ran
|
|
97
|
+
*/
|
|
98
|
+
restore(): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* The steps {@link restore} runs, for what the capture changed.
|
|
101
|
+
*
|
|
102
|
+
* @returns Named steps, in order
|
|
103
|
+
*/
|
|
104
|
+
private restoreSteps;
|
|
105
|
+
/**
|
|
106
|
+
* Put back the session's device metrics, or clear the override.
|
|
107
|
+
*/
|
|
108
|
+
private restoreSessionMetrics;
|
|
109
|
+
/**
|
|
110
|
+
* The window's size with its scrollbars (`innerWidth`/`innerHeight`): an
|
|
111
|
+
* override at this size keeps the page's layout, where the visible size
|
|
112
|
+
* (without scrollbars) would narrow it and move centered content.
|
|
113
|
+
*
|
|
114
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
115
|
+
* @returns Width and height in CSS px
|
|
116
|
+
*/
|
|
117
|
+
private windowSize;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=captureEmulation.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page emulation a screenshot changes for its capture, and puts back.
|
|
3
|
+
*
|
|
4
|
+
* A capture at a pixel ratio of 1 on a high-DPI page, and one beyond the
|
|
5
|
+
* viewport, override the device metrics (and hide the scrollbars). Each
|
|
6
|
+
* change is recorded before it is sent, so {@link CaptureEmulation.restore}
|
|
7
|
+
* (called once the capture ended, however it ended) undoes exactly what may
|
|
8
|
+
* have been changed: the session's emulation is put back from the daemon's
|
|
9
|
+
* own record of it as it is then (a `page emulate` during the capture
|
|
10
|
+
* counts), not from a file.
|
|
11
|
+
*/
|
|
12
|
+
import { TypedCDPConnection } from '../../connection/typed-cdp.js';
|
|
13
|
+
import { evaluateInBdgWorld } from './bdgWorld.js';
|
|
14
|
+
import { viewportOverride } from './emulation.js';
|
|
15
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
16
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
17
|
+
const log = createLogger('dom');
|
|
18
|
+
/**
|
|
19
|
+
* Evaluate one of bdg's page scripts and return its value.
|
|
20
|
+
*
|
|
21
|
+
* @param cdp - Session connection
|
|
22
|
+
* @param expression - Script
|
|
23
|
+
* @returns Its value, or undefined when it threw
|
|
24
|
+
*/
|
|
25
|
+
export async function evaluateValue(cdp, expression) {
|
|
26
|
+
const response = await evaluateInBdgWorld(cdp, { expression, returnByValue: true });
|
|
27
|
+
return response.result.value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Two numbers a page script returned as an array.
|
|
31
|
+
*
|
|
32
|
+
* @param cdp - Session connection
|
|
33
|
+
* @param expression - Script returning `[a, b]`
|
|
34
|
+
* @returns The numbers, undefined where the page did not answer
|
|
35
|
+
*/
|
|
36
|
+
export async function evaluatePair(cdp, expression) {
|
|
37
|
+
const value = await evaluateValue(cdp, expression);
|
|
38
|
+
const [a, b] = Array.isArray(value) ? value : [];
|
|
39
|
+
return [a, b];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The page's scroll position.
|
|
43
|
+
*
|
|
44
|
+
* @param cdp - Session connection
|
|
45
|
+
* @returns Scroll offsets in CSS px
|
|
46
|
+
*/
|
|
47
|
+
export async function scrollPosition(cdp) {
|
|
48
|
+
const [x, y] = await evaluatePair(cdp, '[window.scrollX, window.scrollY]');
|
|
49
|
+
return { x: x ?? 0, y: y ?? 0 };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The emulation changes of one capture, undone by {@link restore}.
|
|
53
|
+
*/
|
|
54
|
+
export class CaptureEmulation {
|
|
55
|
+
connection;
|
|
56
|
+
sessionViewport;
|
|
57
|
+
cdp;
|
|
58
|
+
metricsChanged = false;
|
|
59
|
+
scrollbarsHidden = false;
|
|
60
|
+
scrolledFrom;
|
|
61
|
+
/**
|
|
62
|
+
* @param connection - Session connection
|
|
63
|
+
* @param sessionViewport - Reads the session's emulated viewport
|
|
64
|
+
* (`--viewport`, `--mobile`, `page emulate`) as it is now; put back
|
|
65
|
+
* afterwards, none clears the override
|
|
66
|
+
*/
|
|
67
|
+
constructor(connection, sessionViewport) {
|
|
68
|
+
this.connection = connection;
|
|
69
|
+
this.sessionViewport = sessionViewport;
|
|
70
|
+
this.cdp = new TypedCDPConnection(connection);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Capture at a pixel ratio of 1 (CSS px = image px) on a high-DPI page:
|
|
74
|
+
* the viewport is overridden at the session's viewport, else the window's
|
|
75
|
+
* size with its scrollbars (so the layout does not change).
|
|
76
|
+
*
|
|
77
|
+
* @param devicePixelRatio - Page's pixel ratio
|
|
78
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
79
|
+
*/
|
|
80
|
+
async useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
81
|
+
if (devicePixelRatio === 1)
|
|
82
|
+
return;
|
|
83
|
+
const size = this.sessionViewport() ?? (await this.windowSize(viewport));
|
|
84
|
+
this.metricsChanged = true;
|
|
85
|
+
await this.cdp.send('Emulation.setDeviceMetricsOverride', viewportOverride(size, 1));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Lay the page out at its current width without scrollbars: a capture
|
|
89
|
+
* beyond the viewport hides them, and without this the page would widen by
|
|
90
|
+
* the scrollbar and centered content move after it was measured. The
|
|
91
|
+
* viewport is overridden at the visible size (CSS px, pixel ratio 1; still
|
|
92
|
+
* a phone in a phone session).
|
|
93
|
+
*
|
|
94
|
+
* @param view - Visible viewport size
|
|
95
|
+
*/
|
|
96
|
+
async keepLayoutWithoutScrollbars(view) {
|
|
97
|
+
const phone = this.sessionViewport()?.mobile;
|
|
98
|
+
this.scrollbarsHidden = true;
|
|
99
|
+
await this.cdp.send('Emulation.setScrollbarsHidden', { hidden: true });
|
|
100
|
+
this.metricsChanged = true;
|
|
101
|
+
await this.cdp.send('Emulation.setDeviceMetricsOverride', viewportOverride({ ...view, ...(phone && { mobile: true }) }, 1));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Record where the page was scrolled before the capture moved it, to
|
|
105
|
+
* scroll it back afterwards (the first position recorded wins).
|
|
106
|
+
*
|
|
107
|
+
* @param position - Scroll position before the capture
|
|
108
|
+
*/
|
|
109
|
+
scrolledAwayFrom(position) {
|
|
110
|
+
this.scrolledFrom ??= position;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Put back what the capture changed: scrollbars shown, the session's
|
|
114
|
+
* device metrics (its viewport and a phone's touch input, which a capture
|
|
115
|
+
* beyond the viewport turns off), else none, and the scroll position. Each
|
|
116
|
+
* step runs even when an earlier one failed.
|
|
117
|
+
*
|
|
118
|
+
* @throws The first step's error, once every step ran
|
|
119
|
+
*/
|
|
120
|
+
async restore() {
|
|
121
|
+
const failures = [];
|
|
122
|
+
for (const [name, step] of this.restoreSteps()) {
|
|
123
|
+
try {
|
|
124
|
+
await step();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
log.debug(`Screenshot restore (${name}) failed: ${getErrorMessage(error)}`);
|
|
128
|
+
failures.push(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (failures.length > 0)
|
|
132
|
+
throw failures[0];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The steps {@link restore} runs, for what the capture changed.
|
|
136
|
+
*
|
|
137
|
+
* @returns Named steps, in order
|
|
138
|
+
*/
|
|
139
|
+
restoreSteps() {
|
|
140
|
+
const steps = [];
|
|
141
|
+
if (this.scrollbarsHidden) {
|
|
142
|
+
steps.push([
|
|
143
|
+
'scrollbars',
|
|
144
|
+
() => this.cdp.send('Emulation.setScrollbarsHidden', { hidden: false }),
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
if (this.metricsChanged)
|
|
148
|
+
steps.push(['device metrics', () => this.restoreSessionMetrics()]);
|
|
149
|
+
const scrolledFrom = this.scrolledFrom;
|
|
150
|
+
if (scrolledFrom) {
|
|
151
|
+
const { x, y } = scrolledFrom;
|
|
152
|
+
steps.push(['scroll', () => evaluateValue(this.connection, `window.scrollTo(${x}, ${y})`)]);
|
|
153
|
+
}
|
|
154
|
+
return steps;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Put back the session's device metrics, or clear the override.
|
|
158
|
+
*/
|
|
159
|
+
async restoreSessionMetrics() {
|
|
160
|
+
const viewport = this.sessionViewport();
|
|
161
|
+
if (!viewport) {
|
|
162
|
+
await this.cdp.send('Emulation.clearDeviceMetricsOverride', {});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await this.cdp.send('Emulation.setDeviceMetricsOverride', viewportOverride(viewport));
|
|
166
|
+
if (viewport.mobile) {
|
|
167
|
+
await this.cdp.send('Emulation.setTouchEmulationEnabled', {
|
|
168
|
+
enabled: true,
|
|
169
|
+
maxTouchPoints: 5,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The window's size with its scrollbars (`innerWidth`/`innerHeight`): an
|
|
175
|
+
* override at this size keeps the page's layout, where the visible size
|
|
176
|
+
* (without scrollbars) would narrow it and move centered content.
|
|
177
|
+
*
|
|
178
|
+
* @param viewport - Visible viewport size, used when the page does not answer
|
|
179
|
+
* @returns Width and height in CSS px
|
|
180
|
+
*/
|
|
181
|
+
async windowSize(viewport) {
|
|
182
|
+
const [width, height] = await evaluatePair(this.connection, '[window.innerWidth, window.innerHeight]');
|
|
183
|
+
return {
|
|
184
|
+
width: Math.round(width ?? viewport.width),
|
|
185
|
+
height: Math.round(height ?? viewport.height),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=captureEmulation.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolling an element into view for a page screenshot (`--scroll`), and
|
|
3
|
+
* waiting for the page to settle afterwards (lazy loading, mutations).
|
|
4
|
+
*/
|
|
5
|
+
import type { CDPConnection } from '../../connection/cdp.js';
|
|
6
|
+
import { type ScrollPosition } from './captureEmulation.js';
|
|
7
|
+
/**
|
|
8
|
+
* Scroll an element into view (centered) and wait for the page to settle.
|
|
9
|
+
*
|
|
10
|
+
* @param cdp - Session connection
|
|
11
|
+
* @param selector - The element
|
|
12
|
+
* @returns Scroll position before, to put the page back afterwards
|
|
13
|
+
* @throws CommandError (83) when nothing matches
|
|
14
|
+
*/
|
|
15
|
+
export declare function scrollToElement(cdp: CDPConnection, selector: string): Promise<ScrollPosition>;
|
|
16
|
+
/**
|
|
17
|
+
* Scroll an element into view again (centered), e.g. after an override moved
|
|
18
|
+
* the page.
|
|
19
|
+
*
|
|
20
|
+
* @param cdp - Session connection
|
|
21
|
+
* @param selector - The element
|
|
22
|
+
*/
|
|
23
|
+
export declare function scrollIntoViewAgain(cdp: CDPConnection, selector: string): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=captureScroll.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolling an element into view for a page screenshot (`--scroll`), and
|
|
3
|
+
* waiting for the page to settle afterwards (lazy loading, mutations).
|
|
4
|
+
*/
|
|
5
|
+
import { CommandError } from '../../errors/index.js';
|
|
6
|
+
import { noNodesFoundError } from '../../errors/messages.js';
|
|
7
|
+
import { DEEP_QUERY_JS, selectorArgsJS } from '../dom/targetNode.js';
|
|
8
|
+
import { evaluateValue } from './captureEmulation.js';
|
|
9
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
10
|
+
import { delay } from '../../utils/async.js';
|
|
11
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
12
|
+
const log = createLogger('dom');
|
|
13
|
+
const POST_SCROLL_NETWORK_IDLE_MS = 150;
|
|
14
|
+
const POST_SCROLL_DOM_STABLE_MS = 200;
|
|
15
|
+
const POST_SCROLL_MAX_WAIT_MS = 2000;
|
|
16
|
+
const STABILITY_CHECK_INTERVAL_MS = 50;
|
|
17
|
+
/** Page-side: start recording resource loads and DOM mutations */
|
|
18
|
+
const WATCH_STABILITY_JS = `(() => {
|
|
19
|
+
window.__bdg_scrollStability = {
|
|
20
|
+
lastNetworkActivity: Date.now(),
|
|
21
|
+
lastDomMutation: Date.now(),
|
|
22
|
+
activeRequests: 0
|
|
23
|
+
};
|
|
24
|
+
const state = window.__bdg_scrollStability;
|
|
25
|
+
if (window.PerformanceObserver) {
|
|
26
|
+
const perfObserver = new PerformanceObserver((list) => {
|
|
27
|
+
for (const entry of list.getEntries()) {
|
|
28
|
+
if (entry.entryType === 'resource') state.lastNetworkActivity = Date.now();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
perfObserver.observe({ entryTypes: ['resource'] });
|
|
33
|
+
state.perfObserver = perfObserver;
|
|
34
|
+
} catch (e) {}
|
|
35
|
+
}
|
|
36
|
+
const mutationObserver = new MutationObserver(() => {
|
|
37
|
+
state.lastDomMutation = Date.now();
|
|
38
|
+
});
|
|
39
|
+
mutationObserver.observe(document.body || document.documentElement, {
|
|
40
|
+
childList: true,
|
|
41
|
+
subtree: true,
|
|
42
|
+
attributes: true
|
|
43
|
+
});
|
|
44
|
+
state.mutationObserver = mutationObserver;
|
|
45
|
+
})()`;
|
|
46
|
+
/** Page-side: milliseconds since the last resource load and DOM mutation */
|
|
47
|
+
const STABILITY_JS = `(() => {
|
|
48
|
+
const state = window.__bdg_scrollStability;
|
|
49
|
+
if (!state) return { networkIdle: 999, domIdle: 999 };
|
|
50
|
+
return {
|
|
51
|
+
networkIdle: Date.now() - state.lastNetworkActivity,
|
|
52
|
+
domIdle: Date.now() - state.lastDomMutation
|
|
53
|
+
};
|
|
54
|
+
})()`;
|
|
55
|
+
/** Page-side: stop recording */
|
|
56
|
+
const UNWATCH_STABILITY_JS = `(() => {
|
|
57
|
+
const state = window.__bdg_scrollStability;
|
|
58
|
+
if (state) {
|
|
59
|
+
state.perfObserver?.disconnect();
|
|
60
|
+
state.mutationObserver?.disconnect();
|
|
61
|
+
delete window.__bdg_scrollStability;
|
|
62
|
+
}
|
|
63
|
+
})()`;
|
|
64
|
+
/**
|
|
65
|
+
* Wait for the page to settle after a programmatic scroll (lazy-load idle +
|
|
66
|
+
* DOM mutation idle). Uses shorter thresholds than full page load.
|
|
67
|
+
*
|
|
68
|
+
* @param cdp - Session connection
|
|
69
|
+
*/
|
|
70
|
+
async function waitForPostScrollStability(cdp) {
|
|
71
|
+
const deadline = Date.now() + POST_SCROLL_MAX_WAIT_MS;
|
|
72
|
+
await evaluateValue(cdp, WATCH_STABILITY_JS);
|
|
73
|
+
try {
|
|
74
|
+
while (Date.now() < deadline) {
|
|
75
|
+
const value = (await evaluateValue(cdp, STABILITY_JS));
|
|
76
|
+
const networkIdle = value?.networkIdle ?? 0;
|
|
77
|
+
const domIdle = value?.domIdle ?? 0;
|
|
78
|
+
if (networkIdle >= POST_SCROLL_NETWORK_IDLE_MS && domIdle >= POST_SCROLL_DOM_STABLE_MS) {
|
|
79
|
+
log.debug(`Post-scroll stable: network ${networkIdle}ms, DOM ${domIdle}ms`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await delay(STABILITY_CHECK_INTERVAL_MS);
|
|
83
|
+
}
|
|
84
|
+
log.debug('Post-scroll stability timeout, proceeding anyway');
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await evaluateValue(cdp, UNWATCH_STABILITY_JS);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Scroll an element into view (centered) and wait for the page to settle.
|
|
92
|
+
*
|
|
93
|
+
* @param cdp - Session connection
|
|
94
|
+
* @param selector - The element
|
|
95
|
+
* @returns Scroll position before, to put the page back afterwards
|
|
96
|
+
* @throws CommandError (83) when nothing matches
|
|
97
|
+
*/
|
|
98
|
+
export async function scrollToElement(cdp, selector) {
|
|
99
|
+
const value = (await evaluateValue(cdp, `(() => {
|
|
100
|
+
const el = (${DEEP_QUERY_JS})(${selectorArgsJS(selector)})[0];
|
|
101
|
+
if (!el) return { found: false };
|
|
102
|
+
const originalX = window.scrollX;
|
|
103
|
+
const originalY = window.scrollY;
|
|
104
|
+
el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
105
|
+
return { found: true, originalX, originalY };
|
|
106
|
+
})()`));
|
|
107
|
+
if (!value?.found) {
|
|
108
|
+
const err = noNodesFoundError(selector);
|
|
109
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
|
|
110
|
+
}
|
|
111
|
+
await waitForPostScrollStability(cdp);
|
|
112
|
+
return { x: value.originalX ?? 0, y: value.originalY ?? 0 };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Scroll an element into view again (centered), e.g. after an override moved
|
|
116
|
+
* the page.
|
|
117
|
+
*
|
|
118
|
+
* @param cdp - Session connection
|
|
119
|
+
* @param selector - The element
|
|
120
|
+
*/
|
|
121
|
+
export async function scrollIntoViewAgain(cdp, selector) {
|
|
122
|
+
await evaluateValue(cdp, `(${DEEP_QUERY_JS})(${selectorArgsJS(selector)})[0]?.scrollIntoView({ block: 'center', behavior: 'instant' })`);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=captureScroll.js.map
|
|
@@ -60,12 +60,13 @@ export async function applySessionEmulation(cdp, emulation) {
|
|
|
60
60
|
*/
|
|
61
61
|
async function emulatePhone(cdp, on) {
|
|
62
62
|
await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: on, maxTouchPoints: on ? 5 : 1 });
|
|
63
|
-
const
|
|
63
|
+
const version = (await cdp.send('Browser.getVersion', {}));
|
|
64
|
+
const { userAgent } = version;
|
|
64
65
|
if (!on) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await
|
|
66
|
+
if (userAgent.includes('HeadlessChrome'))
|
|
67
|
+
await hideHeadlessUserAgent(cdp, log, version);
|
|
68
|
+
else
|
|
69
|
+
await cdp.send('Emulation.setUserAgentOverride', { userAgent: '' });
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
const major = /Chrome\/(\d+)/.exec(userAgent)?.[1] ?? '';
|
|
@@ -0,0 +1,41 @@
|
|
|
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 type { CDPConnection } from '../../connection/cdp.js';
|
|
13
|
+
import type { DomScreenshotCommand, DomScreenshotData } from '../../ipc/protocol/commands.js';
|
|
14
|
+
import { type BusyRecoveryOptions } from '../dom/evalHelpers.js';
|
|
15
|
+
import type { ViewportSize } from '../../types.js';
|
|
16
|
+
/** How a screenshot is run */
|
|
17
|
+
export interface ScreenshotOptions {
|
|
18
|
+
/** Aborted when the requesting client disconnects: the capture is skipped, the restore runs */
|
|
19
|
+
abandoned?: AbortSignal | undefined;
|
|
20
|
+
/** When a busy page is checked */
|
|
21
|
+
recovery?: BusyRecoveryOptions;
|
|
22
|
+
}
|
|
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 declare function takeScreenshot(cdp: CDPConnection, params: DomScreenshotCommand, sessionViewport: () => ViewportSize | undefined, options?: ScreenshotOptions): Promise<DomScreenshotData>;
|
|
41
|
+
//# sourceMappingURL=screenshot.d.ts.map
|