browser-debugger-cli 0.12.0 → 0.14.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 +100 -186
- package/README.md +5 -4
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +67 -13
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +23 -22
- package/dist/commands/dom/eval.d.ts +4 -2
- package/dist/commands/dom/eval.js +31 -7
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +9 -8
- package/dist/commands/dom/get.js +32 -14
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.js +13 -13
- package/dist/commands/dom/index.js +10 -3
- package/dist/commands/dom/query.d.ts +20 -2
- package/dist/commands/dom/query.js +39 -6
- package/dist/commands/dom/screenshot.js +3 -1
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +65 -12
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +81 -46
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +21 -10
- package/dist/commands/shared/dataFetcher.d.ts +14 -4
- package/dist/commands/shared/dataFetcher.js +20 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +17 -3
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/start.d.ts +7 -5
- package/dist/commands/start.js +65 -21
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands.js +1 -1
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher.js +3 -2
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +8 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +42 -3
- package/dist/daemon/session/TelemetryStore.d.ts +15 -1
- package/dist/daemon/session/TelemetryStore.js +19 -1
- package/dist/daemon/session/commandRegistry.js +52 -18
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +17 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +2385 -1229
- package/dist/errors/messages.d.ts +62 -11
- package/dist/errors/messages.js +119 -22
- package/dist/index.js +14995 -9866
- package/dist/ipc/client.d.ts +18 -2
- package/dist/ipc/client.js +26 -5
- package/dist/ipc/protocol/auditTypes.d.ts +8 -2
- package/dist/ipc/protocol/commands.d.ts +16 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +7 -2
- package/dist/ipc/session/types.d.ts +7 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/actionEffects.d.ts +5 -1
- package/dist/runtime/dom/actionEffects.js +26 -14
- package/dist/runtime/dom/audit.js +3 -2
- package/dist/runtime/dom/auditModel.js +6 -1
- package/dist/runtime/dom/auditScripts.d.ts +9 -3
- package/dist/runtime/dom/auditScripts.js +41 -5
- package/dist/runtime/dom/elementGeometry.d.ts +33 -3
- package/dist/runtime/dom/elementGeometry.js +44 -19
- package/dist/runtime/dom/elementInfo.d.ts +76 -18
- package/dist/runtime/dom/elementInfo.js +190 -40
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +7 -11
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +45 -32
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +5 -4
- package/dist/runtime/dom/inspectModel.js +7 -3
- package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
- package/dist/runtime/dom/inspectPaintModel.js +3 -1
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +10 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +17 -4
- package/dist/runtime/dom/reactEventHelpers.js +71 -28
- package/dist/runtime/dom/targetNode.d.ts +27 -10
- package/dist/runtime/dom/targetNode.js +283 -16
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +85 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/har/builder.js +1 -1
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +42 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- package/dist/ui/formatters/audit.js +14 -5
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +7 -5
- package/dist/ui/formatters/console/follow.d.ts +5 -2
- package/dist/ui/formatters/console/follow.js +7 -4
- package/dist/ui/formatters/console/json.d.ts +4 -7
- package/dist/ui/formatters/console/json.js +16 -14
- package/dist/ui/formatters/console/shared.d.ts +47 -2
- package/dist/ui/formatters/console/shared.js +33 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +57 -11
- package/dist/ui/formatters/console.d.ts +3 -2
- package/dist/ui/formatters/console.js +8 -10
- package/dist/ui/formatters/details.js +4 -2
- package/dist/ui/formatters/dom.d.ts +14 -5
- package/dist/ui/formatters/dom.js +30 -13
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +9 -3
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +4 -2
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -3
- package/dist/ui/formatters/preview.d.ts +6 -1
- package/dist/ui/formatters/preview.js +67 -15
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +20 -1
- package/dist/ui/messages/chrome.js +29 -3
- package/dist/ui/messages/commands.d.ts +153 -12
- package/dist/ui/messages/commands.js +198 -15
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -0
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { StatusResponseData } from '../ipc/session/queries.js';
|
|
6
6
|
import { type SocketProbeResult } from './daemonSocket.js';
|
|
7
|
+
import { type UnexpectedEndReason } from './lastSession.js';
|
|
7
8
|
import { type SessionDirEntry } from './paths.js';
|
|
8
9
|
/**
|
|
9
10
|
* State of a session: its daemon runs (`active`, `starting`, `ending`,
|
|
10
|
-
* `unresponsive`),
|
|
11
|
-
* files (`stale`)
|
|
11
|
+
* `unresponsive`), died and left its Chrome running (`crashed`) or only
|
|
12
|
+
* files (`stale`), or exited after the session ended without `bdg stop`
|
|
13
|
+
* (`ended`: Chrome crashed or was closed, the page was closed, `--timeout`).
|
|
12
14
|
*/
|
|
13
|
-
export type RunningSessionState = 'active' | 'starting' | 'ending' | 'unresponsive' | 'crashed' | 'stale';
|
|
15
|
+
export type RunningSessionState = 'active' | 'starting' | 'ending' | 'unresponsive' | 'crashed' | 'stale' | 'ended';
|
|
14
16
|
/**
|
|
15
17
|
* One session in the list.
|
|
16
18
|
*/
|
|
@@ -27,6 +29,10 @@ export interface RunningSessionInfo {
|
|
|
27
29
|
chromePid?: number;
|
|
28
30
|
/** Command that cleans up a crashed or stale session */
|
|
29
31
|
cleanup?: string;
|
|
32
|
+
/** Why an `ended` session ended */
|
|
33
|
+
endReason?: UnexpectedEndReason;
|
|
34
|
+
/** When an `ended` session ended (epoch ms) */
|
|
35
|
+
endedAt?: number;
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
38
|
* Summarize a daemon's status response.
|
|
@@ -38,8 +44,10 @@ export interface RunningSessionInfo {
|
|
|
38
44
|
export declare function toRunningSession(name: string | null, data: StatusResponseData): RunningSessionInfo;
|
|
39
45
|
/**
|
|
40
46
|
* A session whose daemon is gone: `crashed` while the Chrome bdg launched for
|
|
41
|
-
* it still runs, `stale` when only its files are left
|
|
42
|
-
*
|
|
47
|
+
* it still runs, `stale` when only its files are left, `ended` when it left
|
|
48
|
+
* only the record of an end without `bdg stop` ({@link describeEnded}).
|
|
49
|
+
* Nothing is changed on disk; `cleanup` names the command that removes
|
|
50
|
+
* leftover files.
|
|
43
51
|
*
|
|
44
52
|
* @param entry - Session directory
|
|
45
53
|
* @param probe - Result of probing its daemon socket
|
|
@@ -6,6 +6,7 @@ import * as fs from 'fs';
|
|
|
6
6
|
import { getStatus } from '../ipc/client.js';
|
|
7
7
|
import { isSessionChrome, readLiveDaemonPid } from './cleanup/staleSession.js';
|
|
8
8
|
import { probeDaemonSocket } from './daemonSocket.js';
|
|
9
|
+
import { readLastSessionEnd } from './lastSession.js';
|
|
9
10
|
import { SESSION_STATE_FILES, getNamedSessionDir, listSessionDirs, sessionFilePathIn, } from './paths.js';
|
|
10
11
|
import { readPidFromFile } from './pid.js';
|
|
11
12
|
import { readPortFile } from './portClaims.js';
|
|
@@ -58,9 +59,10 @@ async function describeSession(entry) {
|
|
|
58
59
|
return { name, state: 'unresponsive' };
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
|
-
* A session whose daemon socket does not answer: `
|
|
62
|
-
* (from daemon.pid, verified by command line) runs
|
|
63
|
-
* socket
|
|
62
|
+
* A session whose daemon socket does not answer: `ending` while its daemon
|
|
63
|
+
* (from daemon.pid, verified by command line) still runs, since a daemon
|
|
64
|
+
* writes daemon.pid only after it opened its socket and removes the socket
|
|
65
|
+
* first when it shuts down; otherwise see {@link describeLeftovers}.
|
|
64
66
|
*
|
|
65
67
|
* @param entry - Session directory
|
|
66
68
|
* @param probe - Result of probing its daemon socket
|
|
@@ -69,13 +71,15 @@ async function describeSession(entry) {
|
|
|
69
71
|
function describeWithoutSocket(entry, probe) {
|
|
70
72
|
const daemonPid = readLiveDaemonPid(entry.dir);
|
|
71
73
|
if (daemonPid !== null)
|
|
72
|
-
return { name: entry.name, state: '
|
|
74
|
+
return { name: entry.name, state: 'ending', daemonPid };
|
|
73
75
|
return describeLeftovers(entry, probe);
|
|
74
76
|
}
|
|
75
77
|
/**
|
|
76
78
|
* A session whose daemon is gone: `crashed` while the Chrome bdg launched for
|
|
77
|
-
* it still runs, `stale` when only its files are left
|
|
78
|
-
*
|
|
79
|
+
* it still runs, `stale` when only its files are left, `ended` when it left
|
|
80
|
+
* only the record of an end without `bdg stop` ({@link describeEnded}).
|
|
81
|
+
* Nothing is changed on disk; `cleanup` names the command that removes
|
|
82
|
+
* leftover files.
|
|
79
83
|
*
|
|
80
84
|
* @param entry - Session directory
|
|
81
85
|
* @param probe - Result of probing its daemon socket
|
|
@@ -87,7 +91,7 @@ export function describeLeftovers({ name, dir }, probe) {
|
|
|
87
91
|
const leftover = probe === 'stale' ||
|
|
88
92
|
SESSION_STATE_FILES.some((type) => fs.existsSync(sessionFilePathIn(dir, type)));
|
|
89
93
|
if (!orphan && !leftover)
|
|
90
|
-
return
|
|
94
|
+
return describeEnded({ name, dir });
|
|
91
95
|
const port = leftoverPort(dir);
|
|
92
96
|
return {
|
|
93
97
|
name,
|
|
@@ -97,6 +101,26 @@ export function describeLeftovers({ name, dir }, probe) {
|
|
|
97
101
|
cleanup: sessionCommand('bdg cleanup', name),
|
|
98
102
|
};
|
|
99
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* A session that ended without `bdg stop` and whose daemon exited cleanly:
|
|
106
|
+
* `ended`, with why and when, until the session starts again or
|
|
107
|
+
* `bdg cleanup` clears the record.
|
|
108
|
+
*
|
|
109
|
+
* @param entry - Session directory
|
|
110
|
+
* @returns Session info, or null when the directory holds no such record
|
|
111
|
+
*/
|
|
112
|
+
function describeEnded({ name, dir }) {
|
|
113
|
+
const end = readLastSessionEnd(dir);
|
|
114
|
+
if (end === null)
|
|
115
|
+
return null;
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
state: 'ended',
|
|
119
|
+
endReason: end.reason,
|
|
120
|
+
endedAt: end.endedAt,
|
|
121
|
+
cleanup: sessionCommand('bdg cleanup', name),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
100
124
|
/**
|
|
101
125
|
* The port a dead session used: from its metadata, else its `port.txt`.
|
|
102
126
|
*
|
package/dist/telemetry/a11y.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Protocol } from '../connection/typed-cdp.js';
|
|
2
|
-
import type { A11yNode, A11yTree, A11yQueryPattern, A11yQueryResult, NodeRef } from '../types.js';
|
|
2
|
+
import type { A11yNode, A11yTree, A11yQueryPattern, A11yQueryResult, ListedA11yTree, NodeRef } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Builds accessibility tree from raw CDP nodes.
|
|
5
5
|
*
|
|
@@ -14,6 +14,20 @@ import type { A11yNode, A11yTree, A11yQueryPattern, A11yQueryResult, NodeRef } f
|
|
|
14
14
|
* @throws Error if no root node found
|
|
15
15
|
*/
|
|
16
16
|
export declare function buildTreeFromRawNodes(rawNodes: Protocol.Accessibility.AXNode[]): A11yTree;
|
|
17
|
+
/**
|
|
18
|
+
* The nodes `dom a11y tree` lists, depth-first from the root (then nodes not
|
|
19
|
+
* under it, such as frame content): the first `limit` (0 = all) no deeper
|
|
20
|
+
* than `maxDepth`. Noise ({@link isTreeNoise}) is counted as skipped and its
|
|
21
|
+
* children move up a level, so the budget goes to meaningful nodes; nodes cut
|
|
22
|
+
* by the limit or depth are counted as omitted. Every node of the tree is
|
|
23
|
+
* listed, omitted or skipped.
|
|
24
|
+
*
|
|
25
|
+
* @param tree - Accessibility tree
|
|
26
|
+
* @param limit - Nodes to list (0 = all listable ones)
|
|
27
|
+
* @param maxDepth - Deepest level to list (0 = root only; undefined = all)
|
|
28
|
+
* @returns Listed nodes with their depth, the tree size, and how many were cut or skipped
|
|
29
|
+
*/
|
|
30
|
+
export declare function listA11yTree(tree: A11yTree, limit: number, maxDepth?: number): ListedA11yTree;
|
|
17
31
|
/**
|
|
18
32
|
* Collects the full accessibility tree from the page via IPC.
|
|
19
33
|
*
|
package/dist/telemetry/a11y.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CommandError } from '../errors/index.js';
|
|
2
2
|
import { unknownQueryFieldError } from '../errors/messages.js';
|
|
3
|
-
import { callCDP } from '../ipc/client.js';
|
|
3
|
+
import { callBdgScript, callCDP } from '../ipc/client.js';
|
|
4
4
|
import { MASKED_VALUE, SENSITIVE_FIELD_JS } from '../runtime/dom/elementInfo.js';
|
|
5
5
|
import { childFrameIds } from '../runtime/dom/frameLayout.js';
|
|
6
6
|
import { ConcurrencyLimiter } from '../utils/concurrency.js';
|
|
@@ -47,6 +47,89 @@ export function buildTreeFromRawNodes(rawNodes) {
|
|
|
47
47
|
const root = nodes.get(rootRaw.nodeId);
|
|
48
48
|
return { root, nodes, count: nodes.size };
|
|
49
49
|
}
|
|
50
|
+
/** Roles that only lay out their children and say nothing themselves */
|
|
51
|
+
const LAYOUT_ROLES = new Set([
|
|
52
|
+
'generic',
|
|
53
|
+
'none',
|
|
54
|
+
'presentation',
|
|
55
|
+
'LayoutTable',
|
|
56
|
+
'LayoutTableRow',
|
|
57
|
+
'LayoutTableCell',
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Whether a node is left out of `dom a11y tree`: text boxes, blank text,
|
|
61
|
+
* text that repeats its parent's name, and nameless layout wrappers.
|
|
62
|
+
*
|
|
63
|
+
* @param node - Accessibility node
|
|
64
|
+
* @param parentName - Name of the nearest named ancestor
|
|
65
|
+
* @returns True when the node says nothing of its own
|
|
66
|
+
*/
|
|
67
|
+
function isTreeNoise(node, parentName) {
|
|
68
|
+
return (node.role === 'InlineTextBox' ||
|
|
69
|
+
(node.role === 'StaticText' && (node.name === parentName || !node.name?.trim())) ||
|
|
70
|
+
(LAYOUT_ROLES.has(node.role) && !node.name));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The nodes `dom a11y tree` lists, depth-first from the root (then nodes not
|
|
74
|
+
* under it, such as frame content): the first `limit` (0 = all) no deeper
|
|
75
|
+
* than `maxDepth`. Noise ({@link isTreeNoise}) is counted as skipped and its
|
|
76
|
+
* children move up a level, so the budget goes to meaningful nodes; nodes cut
|
|
77
|
+
* by the limit or depth are counted as omitted. Every node of the tree is
|
|
78
|
+
* listed, omitted or skipped.
|
|
79
|
+
*
|
|
80
|
+
* @param tree - Accessibility tree
|
|
81
|
+
* @param limit - Nodes to list (0 = all listable ones)
|
|
82
|
+
* @param maxDepth - Deepest level to list (0 = root only; undefined = all)
|
|
83
|
+
* @returns Listed nodes with their depth, the tree size, and how many were cut or skipped
|
|
84
|
+
*/
|
|
85
|
+
export function listA11yTree(tree, limit, maxDepth) {
|
|
86
|
+
const nodes = [];
|
|
87
|
+
const visited = new Set();
|
|
88
|
+
let omitted = 0;
|
|
89
|
+
let skipped = 0;
|
|
90
|
+
const visit = (node, depth, parentName) => {
|
|
91
|
+
if (visited.has(node.nodeId))
|
|
92
|
+
return;
|
|
93
|
+
visited.add(node.nodeId);
|
|
94
|
+
const listable = !isTreeNoise(node, parentName);
|
|
95
|
+
if (listable) {
|
|
96
|
+
const fits = (limit === 0 || nodes.length < limit) && (maxDepth ?? depth) >= depth;
|
|
97
|
+
if (fits)
|
|
98
|
+
nodes.push(listedNode(node, depth));
|
|
99
|
+
else
|
|
100
|
+
omitted++;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
skipped++;
|
|
104
|
+
}
|
|
105
|
+
for (const childId of node.childIds ?? []) {
|
|
106
|
+
const child = tree.nodes.get(childId);
|
|
107
|
+
if (child)
|
|
108
|
+
visit(child, listable ? depth + 1 : depth, node.name ?? parentName);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
visit(tree.root, 0, undefined);
|
|
112
|
+
for (const node of tree.nodes.values())
|
|
113
|
+
visit(node, 0, undefined);
|
|
114
|
+
return {
|
|
115
|
+
nodes,
|
|
116
|
+
count: tree.count,
|
|
117
|
+
...(omitted > 0 && { omitted }),
|
|
118
|
+
...(skipped > 0 && { skipped }),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A node as the tree lists it: its depth instead of child ids (its children
|
|
123
|
+
* follow it, one level deeper).
|
|
124
|
+
*
|
|
125
|
+
* @param node - Accessibility node
|
|
126
|
+
* @param depth - Its level in the listing
|
|
127
|
+
* @returns Listed node
|
|
128
|
+
*/
|
|
129
|
+
function listedNode(node, depth) {
|
|
130
|
+
const { childIds: _childIds, ...rest } = node;
|
|
131
|
+
return { ...rest, depth };
|
|
132
|
+
}
|
|
50
133
|
/**
|
|
51
134
|
* Ids of a node's children as shown: ignored children are replaced by their
|
|
52
135
|
* own non-ignored descendants; ids missing from the tree are dropped.
|
|
@@ -118,7 +201,7 @@ async function sensitiveNodeIds(tree) {
|
|
|
118
201
|
*/
|
|
119
202
|
async function isSensitiveField(backendNodeId) {
|
|
120
203
|
const objectGroup = 'bdg-a11y-secret';
|
|
121
|
-
const resolved = await
|
|
204
|
+
const resolved = await callBdgScript('DOM.resolveNode', { backendNodeId, objectGroup });
|
|
122
205
|
const objectId = resolved.data?.result?.object
|
|
123
206
|
?.objectId;
|
|
124
207
|
if (!objectId)
|
|
@@ -31,7 +31,8 @@ export declare function formatExceptionText(details: Protocol.Runtime.ExceptionD
|
|
|
31
31
|
* @param messages - Array to populate with console messages
|
|
32
32
|
* @param includeAll - If true, disable default pattern filtering
|
|
33
33
|
* @param getCurrentNavigationId - Function to get current navigation ID
|
|
34
|
+
* @param onDropped - Called for each message dropped at the limit (the oldest go first)
|
|
34
35
|
* @returns Cleanup function to remove event handlers
|
|
35
36
|
*/
|
|
36
|
-
export declare function startConsoleCollection(cdp: CDPConnection, messages: ConsoleMessage[], includeAll?: boolean, getCurrentNavigationId?: () => number): Promise<CleanupFunction>;
|
|
37
|
+
export declare function startConsoleCollection(cdp: CDPConnection, messages: ConsoleMessage[], includeAll?: boolean, getCurrentNavigationId?: () => number, onDropped?: () => void): Promise<CleanupFunction>;
|
|
37
38
|
//# sourceMappingURL=console.d.ts.map
|
|
@@ -75,15 +75,23 @@ function createMessage(type, text, timestamp, args, context) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Insert a message in timestamp order
|
|
79
|
-
*
|
|
78
|
+
* Insert a message in timestamp order (async expansion can deliver a message
|
|
79
|
+
* late). At {@link MAX_CONSOLE_MESSAGES} the oldest message is dropped to
|
|
80
|
+
* make room, so the newest are kept; a late message older than all kept ones
|
|
81
|
+
* is dropped itself.
|
|
80
82
|
*
|
|
81
|
-
* @
|
|
83
|
+
* @param messages - Messages, oldest first (updated)
|
|
84
|
+
* @param message - Message to add
|
|
85
|
+
* @param onDropped - Called for each message dropped
|
|
86
|
+
* @returns False when the message itself was dropped
|
|
82
87
|
*/
|
|
83
|
-
function insertMessageByTimestamp(messages, message) {
|
|
88
|
+
function insertMessageByTimestamp(messages, message, onDropped) {
|
|
84
89
|
if (messages.length >= MAX_CONSOLE_MESSAGES) {
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
const oldest = messages[0];
|
|
91
|
+
onDropped();
|
|
92
|
+
if (oldest && message.timestamp < oldest.timestamp)
|
|
93
|
+
return false;
|
|
94
|
+
messages.shift();
|
|
87
95
|
}
|
|
88
96
|
const insertIndex = findInsertIndex(messages, message.timestamp);
|
|
89
97
|
messages.splice(insertIndex, 0, message);
|
|
@@ -111,32 +119,31 @@ function findInsertIndex(messages, timestamp) {
|
|
|
111
119
|
/**
|
|
112
120
|
* Handle a console API call event with object expansion.
|
|
113
121
|
*/
|
|
114
|
-
function handleConsoleAPICall(cdp,
|
|
122
|
+
function handleConsoleAPICall(cdp, insert, params, context, includeAll) {
|
|
115
123
|
const basicText = formatConsoleArgs(params.args);
|
|
116
124
|
if (shouldExcludeConsoleMessage(basicText, params.type, includeAll)) {
|
|
117
125
|
return;
|
|
118
126
|
}
|
|
119
127
|
if (hasArgsNeedingExpansion(params.args) && !usesFormatSpecifiers(params.args)) {
|
|
120
|
-
handleExpandableMessage(cdp,
|
|
128
|
+
handleExpandableMessage(cdp, insert, params, context, basicText);
|
|
121
129
|
}
|
|
122
130
|
else {
|
|
123
|
-
|
|
124
|
-
insertMessageByTimestamp(messages, message);
|
|
131
|
+
insert(createMessage(params.type, basicText, params.timestamp, params.args, context));
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
134
|
/**
|
|
128
135
|
* Handle a message with expandable objects.
|
|
129
136
|
*/
|
|
130
|
-
function handleExpandableMessage(cdp,
|
|
137
|
+
function handleExpandableMessage(cdp, insert, params, context, fallbackText) {
|
|
131
138
|
void expandConsoleArgs(cdp, params.args)
|
|
132
139
|
.then((expandedText) => {
|
|
133
140
|
const message = createMessage(params.type, expandedText, params.timestamp, params.args, context);
|
|
134
|
-
|
|
141
|
+
insert(message);
|
|
135
142
|
})
|
|
136
143
|
.catch((error) => {
|
|
137
144
|
log.debug(`Object expansion failed, using basic text: ${String(error)}`);
|
|
138
145
|
const message = createMessage(params.type, fallbackText, params.timestamp, params.args, context);
|
|
139
|
-
|
|
146
|
+
insert(message);
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
149
|
/**
|
|
@@ -178,14 +185,14 @@ export function formatExceptionText(details) {
|
|
|
178
185
|
*
|
|
179
186
|
* @returns The message added, undefined when filtered out or over the limit
|
|
180
187
|
*/
|
|
181
|
-
function handleExceptionThrown(
|
|
188
|
+
function handleExceptionThrown(insert, params, context, includeAll) {
|
|
182
189
|
const exception = params.exceptionDetails;
|
|
183
190
|
const text = formatExceptionText(exception);
|
|
184
191
|
if (shouldExcludeConsoleMessage(text, 'error', includeAll)) {
|
|
185
192
|
return undefined;
|
|
186
193
|
}
|
|
187
194
|
const message = createMessage('error', text, params.timestamp, undefined, context);
|
|
188
|
-
return
|
|
195
|
+
return insert(message) ? message : undefined;
|
|
189
196
|
}
|
|
190
197
|
/** CDP's text of an unhandled promise rejection (the only kind Chrome revokes) */
|
|
191
198
|
const UNHANDLED_REJECTION_TEXT = 'Uncaught (in promise)';
|
|
@@ -270,7 +277,7 @@ class RevocableRejections {
|
|
|
270
277
|
* Worker entries are skipped: workers are attached directly, so their console
|
|
271
278
|
* calls already arrive (with arguments) from the worker's own session.
|
|
272
279
|
*/
|
|
273
|
-
function handleLogEntry(
|
|
280
|
+
function handleLogEntry(insert, entry, navigationId, includeAll) {
|
|
274
281
|
const type = LOG_LEVEL_TYPES[entry.level];
|
|
275
282
|
if (entry.source === 'worker' || shouldExcludeConsoleMessage(entry.text, type, includeAll)) {
|
|
276
283
|
return;
|
|
@@ -287,7 +294,7 @@ function handleLogEntry(messages, entry, navigationId, includeAll) {
|
|
|
287
294
|
stackTrace: convertStackTrace(entry.stackTrace) ?? location,
|
|
288
295
|
source: entry.source,
|
|
289
296
|
};
|
|
290
|
-
|
|
297
|
+
insert(createMessage(type, entry.text, entry.timestamp, undefined, context));
|
|
291
298
|
}
|
|
292
299
|
/**
|
|
293
300
|
* Enable console and log events on an attached target's session.
|
|
@@ -325,17 +332,19 @@ async function startOptionalSources(cdp, typed) {
|
|
|
325
332
|
* @param messages - Array to populate with console messages
|
|
326
333
|
* @param includeAll - If true, disable default pattern filtering
|
|
327
334
|
* @param getCurrentNavigationId - Function to get current navigation ID
|
|
335
|
+
* @param onDropped - Called for each message dropped at the limit (the oldest go first)
|
|
328
336
|
* @returns Cleanup function to remove event handlers
|
|
329
337
|
*/
|
|
330
|
-
export async function startConsoleCollection(cdp, messages, includeAll = false, getCurrentNavigationId) {
|
|
338
|
+
export async function startConsoleCollection(cdp, messages, includeAll = false, getCurrentNavigationId, onDropped = () => undefined) {
|
|
331
339
|
const registry = new CDPHandlerRegistry();
|
|
332
340
|
const typed = new TypedCDPConnection(cdp);
|
|
341
|
+
const insert = (message) => insertMessageByTimestamp(messages, message, onDropped);
|
|
333
342
|
registry.registerTyped(typed, 'Runtime.consoleAPICalled', (params, sessionId) => {
|
|
334
343
|
const context = {
|
|
335
344
|
navigationId: getCurrentNavigationId?.(),
|
|
336
345
|
stackTrace: convertStackTrace(params.stackTrace),
|
|
337
346
|
};
|
|
338
|
-
handleConsoleAPICall(senderFor(cdp, sessionId),
|
|
347
|
+
handleConsoleAPICall(senderFor(cdp, sessionId), insert, params, context, includeAll);
|
|
339
348
|
});
|
|
340
349
|
const rejections = new RevocableRejections();
|
|
341
350
|
registry.registerTyped(typed, 'Runtime.exceptionThrown', (params, sessionId) => {
|
|
@@ -343,7 +352,7 @@ export async function startConsoleCollection(cdp, messages, includeAll = false,
|
|
|
343
352
|
navigationId: getCurrentNavigationId?.(),
|
|
344
353
|
stackTrace: convertStackTrace(params.exceptionDetails.stackTrace),
|
|
345
354
|
};
|
|
346
|
-
const message = handleExceptionThrown(
|
|
355
|
+
const message = handleExceptionThrown(insert, params, context, includeAll);
|
|
347
356
|
if (message)
|
|
348
357
|
rejections.track(params.exceptionDetails, message, sessionId);
|
|
349
358
|
});
|
|
@@ -357,7 +366,7 @@ export async function startConsoleCollection(cdp, messages, includeAll = false,
|
|
|
357
366
|
rejections.forgetSession(sessionId);
|
|
358
367
|
});
|
|
359
368
|
registry.registerTyped(typed, 'Log.entryAdded', ({ entry }) => {
|
|
360
|
-
handleLogEntry(
|
|
369
|
+
handleLogEntry(insert, entry, getCurrentNavigationId?.(), includeAll);
|
|
361
370
|
});
|
|
362
371
|
try {
|
|
363
372
|
await typed.send('Runtime.enable', {});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* HAR (HTTP Archive) builder for transforming network telemetry to HAR 1.2 format.
|
|
3
3
|
*/
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { skippedBodyReason } from '../
|
|
5
|
+
import { skippedBodyReason } from '../networkRetention.js';
|
|
6
6
|
/**
|
|
7
7
|
* Loads Node builtins on first use: importing `node:http` in an ES module
|
|
8
8
|
* reads all its exports, which loads undici and zlib (about 8 ms of every CLI
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import type { CDPConnection } from '../connection/cdp.js';
|
|
2
2
|
import type { NetworkRequest, WebSocketConnection, CleanupFunction } from '../types.js';
|
|
3
|
-
|
|
4
|
-
* Placeholder stored instead of a response body that was not fetched.
|
|
5
|
-
*
|
|
6
|
-
* @param reason - Why the body was skipped
|
|
7
|
-
* @returns Placeholder text shown by `bdg details`
|
|
8
|
-
*/
|
|
9
|
-
export declare function skippedBodyPlaceholder(reason: string): string;
|
|
10
|
-
/**
|
|
11
|
-
* Extract the reason from a skipped-body placeholder.
|
|
12
|
-
*
|
|
13
|
-
* @param body - Stored response body
|
|
14
|
-
* @returns Reason if `body` is a placeholder, otherwise undefined
|
|
15
|
-
*/
|
|
16
|
-
export declare function skippedBodyReason(body: string | undefined): string | undefined;
|
|
3
|
+
import { type NetworkEvictions } from './networkRetention.js';
|
|
17
4
|
/**
|
|
18
5
|
* A request that has started but not finished or failed yet.
|
|
19
6
|
*/
|
|
@@ -41,6 +28,12 @@ export interface NetworkCollectionOptions {
|
|
|
41
28
|
networkInclude?: string[];
|
|
42
29
|
networkExclude?: string[];
|
|
43
30
|
maxBodySize?: number;
|
|
31
|
+
/** Finished requests kept at most; past it the oldest are dropped (default {@link MAX_NETWORK_REQUESTS}) */
|
|
32
|
+
maxRequests?: number;
|
|
33
|
+
/** Total size of stored response bodies; past it the oldest are evicted (default {@link MAX_TOTAL_BODY_BYTES}) */
|
|
34
|
+
maxTotalBodyBytes?: number;
|
|
35
|
+
/** Counters of dropped requests and evicted bodies; otherwise the collector keeps private ones */
|
|
36
|
+
evictions?: NetworkEvictions | undefined;
|
|
44
37
|
getCurrentNavigationId?: (() => number) | undefined;
|
|
45
38
|
}
|
|
46
39
|
/**
|
|
@@ -56,8 +49,12 @@ export interface NetworkCollectionOptions {
|
|
|
56
49
|
*
|
|
57
50
|
* @remarks
|
|
58
51
|
* - Chrome buffer limits: 50MB total, 10MB per resource, 1MB POST data (with fallback)
|
|
59
|
-
* -
|
|
60
|
-
*
|
|
52
|
+
* - The newest 10,000 finished requests are kept: past that the oldest finished
|
|
53
|
+
* ones are dropped (counted in `evictions.requestsDropped`); requests in flight
|
|
54
|
+
* are tracked separately and never dropped mid-flight
|
|
55
|
+
* - Stored response bodies total at most 100MB: past that the oldest bodies are
|
|
56
|
+
* replaced by a placeholder (counted in `evictions.bodiesEvicted`), their
|
|
57
|
+
* request metadata stays
|
|
61
58
|
* - Response bodies are automatically skipped for images, fonts, CSS, and source maps (see DEFAULT_SKIP_BODY_PATTERNS)
|
|
62
59
|
* - Response bodies larger than 5MB are skipped with a placeholder message
|
|
63
60
|
* - By default, common tracking/analytics domains are filtered out (use includeAll to disable)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { CDPHandlerRegistry } from '../connection/handlers.js';
|
|
2
2
|
import { TypedCDPConnection } from '../connection/typed-cdp.js';
|
|
3
|
-
import { MAX_NETWORK_REQUESTS, MAX_RESPONSE_SIZE, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, } from '../constants.js';
|
|
3
|
+
import { MAX_NETWORK_REQUESTS, MAX_RESPONSE_SIZE, MAX_TOTAL_BODY_BYTES, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, } from '../constants.js';
|
|
4
4
|
import { attachChildTargets } from './attachedTargets.js';
|
|
5
5
|
import { createLogger } from '../ui/logging/index.js';
|
|
6
6
|
import { getErrorMessage } from '../utils/errors.js';
|
|
7
7
|
import { filterDefined } from '../utils/objects.js';
|
|
8
8
|
import { shouldExcludeDomain, shouldExcludeUrl, shouldFetchBodyWithReason } from './filters.js';
|
|
9
9
|
import { ExtraInfoTracker } from './networkExtraInfo.js';
|
|
10
|
+
import { RequestRetention, skippedBodyPlaceholder, } from './networkRetention.js';
|
|
10
11
|
const log = createLogger('network');
|
|
11
12
|
/**
|
|
12
13
|
* Check if a request should be filtered out based on domain and URL patterns.
|
|
@@ -21,27 +22,26 @@ function shouldFilterRequest(url, includeAll, networkInclude, networkExclude) {
|
|
|
21
22
|
return false;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
|
-
* Fetch response body for a request with cancellation support
|
|
25
|
+
* Fetch response body for a request with cancellation support: a body that
|
|
26
|
+
* arrives after its fetch was cancelled (removed from `pendingFetches`: the
|
|
27
|
+
* collector stopped, or the request was dropped) is discarded.
|
|
25
28
|
*
|
|
26
29
|
* @param cdp - CDP connection instance
|
|
27
30
|
* @param requestId - Request ID to fetch body for
|
|
28
|
-
* @param request - Network request
|
|
31
|
+
* @param request - Network request the body belongs to
|
|
29
32
|
* @param pendingFetches - Set to track pending fetch operations for cleanup
|
|
33
|
+
* @param retention - Stores the body within the session's body budget
|
|
34
|
+
* @param sessionId - Session of the iframe or worker that made the request
|
|
30
35
|
*/
|
|
31
|
-
function fetchResponseBody(cdp, requestId, request, pendingFetches, sessionId) {
|
|
36
|
+
function fetchResponseBody(cdp, requestId, request, pendingFetches, retention, sessionId) {
|
|
32
37
|
pendingFetches.add(requestId);
|
|
33
38
|
void cdp
|
|
34
39
|
.send('Network.getResponseBody', { requestId }, sessionId)
|
|
35
40
|
.then((response) => {
|
|
36
41
|
if (!pendingFetches.has(requestId))
|
|
37
42
|
return;
|
|
38
|
-
const
|
|
39
|
-
request
|
|
40
|
-
if (typedResponse.base64Encoded)
|
|
41
|
-
request.responseBodyBase64 = true;
|
|
42
|
-
if (typedResponse.body) {
|
|
43
|
-
request.decodedBodyLength = Buffer.byteLength(typedResponse.body, typedResponse.base64Encoded ? 'base64' : 'utf-8');
|
|
44
|
-
}
|
|
43
|
+
const { body, base64Encoded } = response;
|
|
44
|
+
retention.storeBody(request, body, base64Encoded);
|
|
45
45
|
})
|
|
46
46
|
.catch((error) => {
|
|
47
47
|
log.debug(`Failed to fetch response body for request ${requestId}: ${getErrorMessage(error)}`);
|
|
@@ -83,25 +83,6 @@ function createNetworkRequest(params, getCurrentNavigationId) {
|
|
|
83
83
|
...(params.type !== undefined && { resourceType: params.type }),
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
-
const SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
|
|
87
|
-
/**
|
|
88
|
-
* Placeholder stored instead of a response body that was not fetched.
|
|
89
|
-
*
|
|
90
|
-
* @param reason - Why the body was skipped
|
|
91
|
-
* @returns Placeholder text shown by `bdg details`
|
|
92
|
-
*/
|
|
93
|
-
export function skippedBodyPlaceholder(reason) {
|
|
94
|
-
return `[SKIPPED: ${reason}]`;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Extract the reason from a skipped-body placeholder.
|
|
98
|
-
*
|
|
99
|
-
* @param body - Stored response body
|
|
100
|
-
* @returns Reason if `body` is a placeholder, otherwise undefined
|
|
101
|
-
*/
|
|
102
|
-
export function skippedBodyReason(body) {
|
|
103
|
-
return body === undefined ? undefined : SKIPPED_BODY_PATTERN.exec(body)?.[1];
|
|
104
|
-
}
|
|
105
86
|
/**
|
|
106
87
|
* Copy response fields (status, headers, timing, connection) onto a request.
|
|
107
88
|
*
|
|
@@ -272,21 +253,31 @@ async function collectChildTargetNetwork(cdp) {
|
|
|
272
253
|
*
|
|
273
254
|
* @remarks
|
|
274
255
|
* - Chrome buffer limits: 50MB total, 10MB per resource, 1MB POST data (with fallback)
|
|
275
|
-
* -
|
|
276
|
-
*
|
|
256
|
+
* - The newest 10,000 finished requests are kept: past that the oldest finished
|
|
257
|
+
* ones are dropped (counted in `evictions.requestsDropped`); requests in flight
|
|
258
|
+
* are tracked separately and never dropped mid-flight
|
|
259
|
+
* - Stored response bodies total at most 100MB: past that the oldest bodies are
|
|
260
|
+
* replaced by a placeholder (counted in `evictions.bodiesEvicted`), their
|
|
261
|
+
* request metadata stays
|
|
277
262
|
* - Response bodies are automatically skipped for images, fonts, CSS, and source maps (see DEFAULT_SKIP_BODY_PATTERNS)
|
|
278
263
|
* - Response bodies larger than 5MB are skipped with a placeholder message
|
|
279
264
|
* - By default, common tracking/analytics domains are filtered out (use includeAll to disable)
|
|
280
265
|
* - Pattern precedence: include patterns always trump exclude patterns
|
|
281
266
|
*/
|
|
282
267
|
export async function startNetworkCollection(cdp, requests, options = {}) {
|
|
283
|
-
const { includeAll = false, fetchAllBodies = false, fetchBodiesInclude = [], fetchBodiesExclude = [], networkInclude = [], networkExclude = [], maxBodySize = MAX_RESPONSE_SIZE, getCurrentNavigationId, } = options;
|
|
268
|
+
const { includeAll = false, fetchAllBodies = false, fetchBodiesInclude = [], fetchBodiesExclude = [], networkInclude = [], networkExclude = [], maxBodySize = MAX_RESPONSE_SIZE, maxRequests = MAX_NETWORK_REQUESTS, maxTotalBodyBytes = MAX_TOTAL_BODY_BYTES, evictions = { requestsDropped: 0, bodiesEvicted: 0 }, getCurrentNavigationId, } = options;
|
|
284
269
|
const requestMap = options.pendingRequests ?? new Map();
|
|
285
270
|
const pendingFetches = new Set();
|
|
286
271
|
const redirectHops = new Map();
|
|
287
272
|
const extraInfo = new ExtraInfoTracker((requestId) => requestMap.get(requestId)?.request);
|
|
288
273
|
const registry = new CDPHandlerRegistry();
|
|
289
274
|
const typed = new TypedCDPConnection(cdp);
|
|
275
|
+
const retention = new RequestRetention(requests, { maxRequests, maxTotalBodyBytes }, evictions);
|
|
276
|
+
const record = (request) => {
|
|
277
|
+
const dropped = retention.add(request);
|
|
278
|
+
if (dropped)
|
|
279
|
+
pendingFetches.delete(dropped.requestId);
|
|
280
|
+
};
|
|
290
281
|
let bodiesFetched = 0;
|
|
291
282
|
let bodiesSkipped = 0;
|
|
292
283
|
try {
|
|
@@ -305,11 +296,9 @@ export async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
305
296
|
const entry = requestMap.get(requestId);
|
|
306
297
|
if (!entry)
|
|
307
298
|
return;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
extraInfo.complete(requestId, entry.request);
|
|
312
|
-
}
|
|
299
|
+
applyFailure(entry.request, failure);
|
|
300
|
+
record(entry.request);
|
|
301
|
+
extraInfo.complete(requestId, entry.request);
|
|
313
302
|
requestMap.delete(requestId);
|
|
314
303
|
redirectHops.delete(requestId);
|
|
315
304
|
};
|
|
@@ -349,16 +338,11 @@ export async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
349
338
|
const hop = completeRedirectHop(previous.request, params, redirectHops);
|
|
350
339
|
extraInfo.applyResponse(params.requestId, hop);
|
|
351
340
|
extraInfo.recordRedirectHop(params.requestId, hop);
|
|
352
|
-
|
|
353
|
-
requests.push(hop);
|
|
341
|
+
record(hop);
|
|
354
342
|
}
|
|
355
343
|
if (shouldFilterRequest(params.request.url, includeAll, networkInclude, networkExclude)) {
|
|
356
344
|
return;
|
|
357
345
|
}
|
|
358
|
-
if (requestMap.size >= MAX_NETWORK_REQUESTS) {
|
|
359
|
-
log.debug(`Warning: Network request limit reached (${MAX_NETWORK_REQUESTS}), dropping new requests`);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
346
|
const request = createNetworkRequest(params, getCurrentNavigationId);
|
|
363
347
|
extraInfo.applyRequest(params.requestId, request);
|
|
364
348
|
if (params.type === 'Document' && params.loaderId) {
|
|
@@ -399,12 +383,6 @@ export async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
399
383
|
const entry = requestMap.get(params.requestId);
|
|
400
384
|
if (!entry)
|
|
401
385
|
return;
|
|
402
|
-
if (requests.length >= MAX_NETWORK_REQUESTS) {
|
|
403
|
-
log.debug(`Warning: Network request limit reached (${MAX_NETWORK_REQUESTS})`);
|
|
404
|
-
requestMap.delete(params.requestId);
|
|
405
|
-
redirectHops.delete(params.requestId);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
386
|
const request = entry.request;
|
|
409
387
|
if (params.encodedDataLength !== undefined) {
|
|
410
388
|
request.encodedDataLength = params.encodedDataLength;
|
|
@@ -419,13 +397,13 @@ export async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
419
397
|
});
|
|
420
398
|
if (decision.should) {
|
|
421
399
|
bodiesFetched++;
|
|
422
|
-
fetchResponseBody(cdp, params.requestId, request, pendingFetches, entry.sessionId);
|
|
400
|
+
fetchResponseBody(cdp, params.requestId, request, pendingFetches, retention, entry.sessionId);
|
|
423
401
|
}
|
|
424
402
|
else {
|
|
425
403
|
bodiesSkipped++;
|
|
426
404
|
request.responseBody = skippedBodyPlaceholder(decision.reason ?? 'not captured');
|
|
427
405
|
}
|
|
428
|
-
|
|
406
|
+
record(request);
|
|
429
407
|
extraInfo.complete(params.requestId, request);
|
|
430
408
|
requestMap.delete(params.requestId);
|
|
431
409
|
redirectHops.delete(params.requestId);
|