browser-debugger-cli 0.13.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 +4 -4
- package/dist/commands/console.js +5 -1
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +20 -20
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +21 -3
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +2 -1
- package/dist/commands/dom/query.js +5 -3
- package/dist/commands/dom/screenshot.js +1 -0
- package/dist/commands/helpJson.js +1 -1
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +55 -42
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.js +2 -2
- package/dist/commands/shared/dataFetcher.d.ts +4 -2
- package/dist/commands/shared/dataFetcher.js +11 -3
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +14 -3
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/session/Session.d.ts +2 -1
- package/dist/daemon/session/Session.js +10 -2
- package/dist/daemon/session/TelemetryStore.d.ts +7 -0
- package/dist/daemon/session/TelemetryStore.js +6 -0
- package/dist/daemon/session/commandRegistry.js +23 -5
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +1 -0
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +742 -460
- package/dist/errors/messages.d.ts +8 -0
- package/dist/errors/messages.js +10 -0
- package/dist/index.js +710 -518
- package/dist/ipc/protocol/commands.d.ts +4 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -0
- package/dist/runtime/dom/elementGeometry.d.ts +23 -0
- package/dist/runtime/dom/elementGeometry.js +17 -15
- package/dist/runtime/dom/elementInfo.d.ts +6 -4
- package/dist/runtime/dom/elementInfo.js +7 -4
- 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 +40 -26
- package/dist/runtime/dom/inspectModel.d.ts +3 -3
- package/dist/runtime/dom/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +1 -2
- package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
- package/dist/runtime/dom/reactEventHelpers.js +9 -2
- package/dist/runtime/dom/targetNode.d.ts +10 -6
- package/dist/runtime/dom/targetNode.js +15 -8
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- 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/types.d.ts +26 -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/console/chronological.js +4 -4
- package/dist/ui/formatters/console/follow.d.ts +4 -2
- package/dist/ui/formatters/console/follow.js +6 -3
- package/dist/ui/formatters/console/json.d.ts +3 -6
- package/dist/ui/formatters/console/json.js +9 -13
- package/dist/ui/formatters/console/shared.d.ts +17 -2
- package/dist/ui/formatters/console/shared.js +17 -0
- package/dist/ui/formatters/console/summarize.d.ts +2 -2
- package/dist/ui/formatters/console/summarize.js +22 -7
- package/dist/ui/formatters/console.d.ts +1 -1
- package/dist/ui/formatters/console.js +1 -5
- package/dist/ui/formatters/details.js +1 -1
- package/dist/ui/formatters/dom.d.ts +13 -4
- package/dist/ui/formatters/dom.js +25 -7
- package/dist/ui/formatters/layout.js +2 -1
- 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 -2
- package/dist/ui/formatters/preview.d.ts +4 -1
- package/dist/ui/formatters/preview.js +55 -13
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -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 +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -0
- package/dist/utils/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
package/dist/daemon.js
CHANGED
|
@@ -5013,9 +5013,12 @@ var OBJECT_EXPANSION_MAX_DEPTH = 3;
|
|
|
5013
5013
|
var OBJECT_EXPANSION_MAX_PROPERTIES = 10;
|
|
5014
5014
|
var OBJECT_EXPANSION_FAILURE_THRESHOLD = 5;
|
|
5015
5015
|
var MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
|
|
5016
|
+
var MAX_TOTAL_BODY_BYTES = 100 * 1024 * 1024;
|
|
5016
5017
|
var CHROME_NETWORK_BUFFER_TOTAL = 50 * 1024 * 1024;
|
|
5017
5018
|
var CHROME_NETWORK_BUFFER_PER_RESOURCE = 10 * 1024 * 1024;
|
|
5018
5019
|
var CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024;
|
|
5020
|
+
var LAYOUT_ELEMENT_LIMIT = 100;
|
|
5021
|
+
var MAX_TRIGGERED_REQUESTS = 50;
|
|
5019
5022
|
var DEFAULT_PAGE_READINESS_TIMEOUT_MS = 2e3;
|
|
5020
5023
|
var MAX_JSONL_BUFFER_SIZE = 256 * 1024 * 1024;
|
|
5021
5024
|
|
|
@@ -5533,7 +5536,13 @@ function evalPreviewWarning(replaced) {
|
|
|
5533
5536
|
|
|
5534
5537
|
// src/daemon/session/TelemetryStore.ts
|
|
5535
5538
|
var TelemetryStore = class {
|
|
5539
|
+
/**
|
|
5540
|
+
* Finished requests, oldest first: the newest are kept, so the session's
|
|
5541
|
+
* `networkEvictions.requestsDropped` oldest ones are gone
|
|
5542
|
+
*/
|
|
5536
5543
|
networkRequests = [];
|
|
5544
|
+
/** Requests dropped and response bodies evicted at the capture limits */
|
|
5545
|
+
networkEvictions = { requestsDropped: 0, bodiesEvicted: 0 };
|
|
5537
5546
|
/** Requests still in flight, keyed by CDP requestId */
|
|
5538
5547
|
pendingNetworkRequests = /* @__PURE__ */ new Map();
|
|
5539
5548
|
consoleMessages = [];
|
|
@@ -6747,6 +6756,9 @@ function sessionEndedDuringCommandError() {
|
|
|
6747
6756
|
suggestion: `Start a new session with: ${sessionCommand("bdg <url>")}`
|
|
6748
6757
|
};
|
|
6749
6758
|
}
|
|
6759
|
+
function unknownSessionCommandMessage(name) {
|
|
6760
|
+
return `Unknown session command: "${name}"`;
|
|
6761
|
+
}
|
|
6750
6762
|
function navigationFailedError(url, errorText) {
|
|
6751
6763
|
return {
|
|
6752
6764
|
message: `Could not load ${url}: ${errorText}`,
|
|
@@ -7260,9 +7272,10 @@ async function fetchCDPTargets(port = DEFAULT_CDP_PORT, logger, options) {
|
|
|
7260
7272
|
}
|
|
7261
7273
|
async function probeDevToolsEndpoint(port, logger, options) {
|
|
7262
7274
|
const url = `${options?.secure ? "https" : "http"}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
|
|
7275
|
+
const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
|
|
7263
7276
|
let response;
|
|
7264
7277
|
try {
|
|
7265
|
-
response = await fetch(url, { signal: AbortSignal.timeout(
|
|
7278
|
+
response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
7266
7279
|
} catch (error) {
|
|
7267
7280
|
logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
|
|
7268
7281
|
return { kind: "unreachable" };
|
|
@@ -8342,6 +8355,234 @@ function listProcesses() {
|
|
|
8342
8355
|
return result.stdout.split("\n").map((line) => /^\s*(\d+)\s+(.*)$/.exec(line)).filter((match) => match !== null).map(([, pid, command]) => ({ pid: Number(pid), command: command ?? "" }));
|
|
8343
8356
|
}
|
|
8344
8357
|
|
|
8358
|
+
// src/connection/diagnostics.ts
|
|
8359
|
+
var log4 = createLogger("diagnostics");
|
|
8360
|
+
var cachedDiagnostics = null;
|
|
8361
|
+
function getChromeDiagnostics() {
|
|
8362
|
+
if (cachedDiagnostics) {
|
|
8363
|
+
return cachedDiagnostics;
|
|
8364
|
+
}
|
|
8365
|
+
let defaultPath = null;
|
|
8366
|
+
try {
|
|
8367
|
+
defaultPath = getChromePath();
|
|
8368
|
+
} catch (error) {
|
|
8369
|
+
log4.debug(`Failed to get default Chrome path: ${getErrorMessage(error)}`);
|
|
8370
|
+
}
|
|
8371
|
+
let installations = [];
|
|
8372
|
+
try {
|
|
8373
|
+
installations = Launcher.getInstallations();
|
|
8374
|
+
} catch (error) {
|
|
8375
|
+
log4.debug(`Failed to get Chrome installations: ${getErrorMessage(error)}`);
|
|
8376
|
+
}
|
|
8377
|
+
cachedDiagnostics = {
|
|
8378
|
+
defaultPath,
|
|
8379
|
+
installations,
|
|
8380
|
+
installationCount: installations.length
|
|
8381
|
+
};
|
|
8382
|
+
return cachedDiagnostics;
|
|
8383
|
+
}
|
|
8384
|
+
|
|
8385
|
+
// src/ui/messages/chrome.ts
|
|
8386
|
+
function formatChromeNotice(notice) {
|
|
8387
|
+
const ctx = notice.context ?? {};
|
|
8388
|
+
switch (notice.code) {
|
|
8389
|
+
case "EXTERNAL_CHROME_CONNECTING":
|
|
8390
|
+
return chromeExternalConnectionMessage();
|
|
8391
|
+
case "EXTERNAL_CHROME_WS_URL":
|
|
8392
|
+
return chromeExternalWebSocketMessage(ctx["wsUrl"]);
|
|
8393
|
+
case "EXTERNAL_CHROME_NO_PID":
|
|
8394
|
+
return chromeExternalNoPidMessage();
|
|
8395
|
+
case "EXTERNAL_CHROME_SKIP_TERMINATION":
|
|
8396
|
+
return chromeExternalSkipTerminationMessage();
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
function formatChromeIssue(issue) {
|
|
8400
|
+
const ctx = issue.context ?? {};
|
|
8401
|
+
switch (issue.code) {
|
|
8402
|
+
case "PORT_IN_USE":
|
|
8403
|
+
return portInUseError(ctx["port"], ctx["reason"]);
|
|
8404
|
+
case "INVALID_PORT":
|
|
8405
|
+
return invalidPortError(ctx["port"]);
|
|
8406
|
+
case "USER_DATA_DIR_CREATE_FAILED":
|
|
8407
|
+
return userDataDirError(ctx["userDataDir"], ctx["reason"] ?? "");
|
|
8408
|
+
case "CHROME_LAUNCH_FAILED":
|
|
8409
|
+
case "CHROME_DIED_AFTER_LAUNCH": {
|
|
8410
|
+
const port = ctx["port"];
|
|
8411
|
+
const reason = ctx["reason"];
|
|
8412
|
+
const pid = typeof ctx["pid"] === "number" ? ctx["pid"] : "unknown";
|
|
8413
|
+
const header2 = issue.code === "CHROME_DIED_AFTER_LAUNCH" ? `Chrome died immediately after launch (PID: ${pid})` : reason ? chromeLaunchFailedError(reason) : `Chrome failed to launch`;
|
|
8414
|
+
const diagnostics = getFormattedDiagnostics();
|
|
8415
|
+
return joinLines(
|
|
8416
|
+
header2,
|
|
8417
|
+
"",
|
|
8418
|
+
"Possible causes:",
|
|
8419
|
+
` - Port ${port} conflict (check: lsof -ti:${port})`,
|
|
8420
|
+
` - Chrome binary not found`,
|
|
8421
|
+
` - Insufficient permissions`,
|
|
8422
|
+
` - Chrome crashed on startup`,
|
|
8423
|
+
"",
|
|
8424
|
+
...diagnostics,
|
|
8425
|
+
"",
|
|
8426
|
+
"Try:",
|
|
8427
|
+
` - ${sessionCommand("bdg cleanup")}`,
|
|
8428
|
+
` - See what uses the port: lsof -i :${port}`,
|
|
8429
|
+
` - Use different port: ${sessionCommand(`bdg <url> --port ${port + 1}`)}`,
|
|
8430
|
+
` - In a container where Chrome's sandbox fails: BDG_NO_SANDBOX=1 ${sessionCommand("bdg <url>")}`
|
|
8431
|
+
);
|
|
8432
|
+
}
|
|
8433
|
+
case "CHROME_EXITED_DURING_STARTUP":
|
|
8434
|
+
return chromeExitedDuringStartupError(
|
|
8435
|
+
ctx["exitCode"],
|
|
8436
|
+
ctx["output"] ?? [],
|
|
8437
|
+
ctx["userDataDir"],
|
|
8438
|
+
ctx["profileInUse"] === true
|
|
8439
|
+
);
|
|
8440
|
+
case "NO_PAGE_TARGET_FOUND":
|
|
8441
|
+
return noPageTargetFoundError(
|
|
8442
|
+
ctx["port"],
|
|
8443
|
+
ctx["availableTargets"]
|
|
8444
|
+
);
|
|
8445
|
+
case "CHROME_BINARY_NOT_FOUND": {
|
|
8446
|
+
const diagnostics = getFormattedDiagnostics();
|
|
8447
|
+
return joinLines(
|
|
8448
|
+
chromeBinaryOverrideNotFound(ctx["chromePath"], ctx["source"]),
|
|
8449
|
+
"",
|
|
8450
|
+
...diagnostics
|
|
8451
|
+
);
|
|
8452
|
+
}
|
|
8453
|
+
case "CHROME_BINARY_IS_DIRECTORY":
|
|
8454
|
+
return chromeBinaryOverrideIsDirectory(ctx["chromePath"], ctx["source"]);
|
|
8455
|
+
case "CHROME_BINARY_NOT_EXECUTABLE": {
|
|
8456
|
+
const reason = ctx["reason"];
|
|
8457
|
+
const base = chromeBinaryOverrideNotExecutable(
|
|
8458
|
+
ctx["chromePath"],
|
|
8459
|
+
ctx["source"]
|
|
8460
|
+
);
|
|
8461
|
+
return reason ? `${base}
|
|
8462
|
+
|
|
8463
|
+
${reason}` : base;
|
|
8464
|
+
}
|
|
8465
|
+
case "PREFS_FILE_NOT_FOUND":
|
|
8466
|
+
return prefsFileNotFoundError(ctx["file"]);
|
|
8467
|
+
case "PREFS_INVALID_FORMAT":
|
|
8468
|
+
return invalidPrefsFormatError(ctx["file"], ctx["actualType"]);
|
|
8469
|
+
case "PREFS_LOAD_FAILED":
|
|
8470
|
+
return prefsLoadError(ctx["file"], ctx["reason"] ?? "");
|
|
8471
|
+
case "PREFS_NOT_JSON_SERIALIZABLE":
|
|
8472
|
+
return `Chrome preferences must be JSON-serializable: ${ctx["reason"] ?? "unknown error"}`;
|
|
8473
|
+
}
|
|
8474
|
+
}
|
|
8475
|
+
function chromeExitedDuringStartupError(exitCode, output, userDataDir, profileInUse2) {
|
|
8476
|
+
if (profileInUse2) {
|
|
8477
|
+
return joinLines(
|
|
8478
|
+
`The Chrome profile ${userDataDir} is in use by another Chrome`,
|
|
8479
|
+
`Close that Chrome, or use another profile directory: ${sessionCommand("bdg <url> -u ./other-profile")}`
|
|
8480
|
+
);
|
|
8481
|
+
}
|
|
8482
|
+
return joinLines(
|
|
8483
|
+
`Chrome exited during startup (exit code ${exitCode ?? "none"})`,
|
|
8484
|
+
...output.length > 0 ? ["Chrome said:", ...output.map((line) => ` ${line}`)] : [],
|
|
8485
|
+
"Check --chrome-flags and BDG_CHROME_FLAGS (an unknown flag or value can stop Chrome)"
|
|
8486
|
+
);
|
|
8487
|
+
}
|
|
8488
|
+
function getFormattedDiagnostics() {
|
|
8489
|
+
return formatDiagnosticsForError(getChromeDiagnostics());
|
|
8490
|
+
}
|
|
8491
|
+
function formatDiagnosticsForError(diagnostics) {
|
|
8492
|
+
const lines = [];
|
|
8493
|
+
if (diagnostics.installationCount === 0) {
|
|
8494
|
+
lines.push("Error: No Chrome installations detected\n");
|
|
8495
|
+
lines.push("Install Chrome from:");
|
|
8496
|
+
lines.push(" https://www.google.com/chrome/\n");
|
|
8497
|
+
} else {
|
|
8498
|
+
lines.push(`Found ${pluralize(diagnostics.installationCount, "Chrome installation")}:
|
|
8499
|
+
`);
|
|
8500
|
+
diagnostics.installations.forEach((path9, index) => {
|
|
8501
|
+
lines.push(` ${index + 1}. ${path9}`);
|
|
8502
|
+
});
|
|
8503
|
+
lines.push("");
|
|
8504
|
+
if (diagnostics.defaultPath) {
|
|
8505
|
+
lines.push(`Default binary: ${diagnostics.defaultPath}
|
|
8506
|
+
`);
|
|
8507
|
+
} else {
|
|
8508
|
+
lines.push("Default binary: Could not determine\n");
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
return lines;
|
|
8512
|
+
}
|
|
8513
|
+
function invalidPortError(port) {
|
|
8514
|
+
return `Invalid port number: ${port}. Port must be between 1 and 65535.`;
|
|
8515
|
+
}
|
|
8516
|
+
function userDataDirError(dir, error) {
|
|
8517
|
+
return `Failed to create user data directory at ${dir}: ${error}`;
|
|
8518
|
+
}
|
|
8519
|
+
function chromeExternalConnectionMessage() {
|
|
8520
|
+
return "Connecting to existing Chrome instance...";
|
|
8521
|
+
}
|
|
8522
|
+
function chromeExternalWebSocketMessage(wsUrl) {
|
|
8523
|
+
return `WebSocket URL: ${wsUrl}`;
|
|
8524
|
+
}
|
|
8525
|
+
function chromeExternalNoPidMessage() {
|
|
8526
|
+
return "Using external Chrome (no PID - not managed by bdg)";
|
|
8527
|
+
}
|
|
8528
|
+
function chromeExternalSkipTerminationMessage() {
|
|
8529
|
+
return "Using external Chrome - skipping termination (not managed by bdg)";
|
|
8530
|
+
}
|
|
8531
|
+
function noPageTargetFoundError(port, availableTargets) {
|
|
8532
|
+
return joinLines(
|
|
8533
|
+
`Chrome started but opened no page to attach to (port ${port})`,
|
|
8534
|
+
`Retry: ${sessionCommand("bdg <url>")}. If it keeps failing, another program may use the port (lsof -i :${port}) or a crashed session may be left over: ${sessionCommand("bdg cleanup")} && ${sessionCommand("bdg <url>")}`,
|
|
8535
|
+
...availableTargets ? ["", `Chrome targets:
|
|
8536
|
+
${availableTargets}`] : []
|
|
8537
|
+
);
|
|
8538
|
+
}
|
|
8539
|
+
function prefsFileNotFoundError(file) {
|
|
8540
|
+
return `Chrome preferences file not found: ${file}`;
|
|
8541
|
+
}
|
|
8542
|
+
function invalidPrefsFormatError(file, type) {
|
|
8543
|
+
return `Invalid Chrome preferences format in ${file}: expected object, got ${type}`;
|
|
8544
|
+
}
|
|
8545
|
+
function prefsLoadError(file, error) {
|
|
8546
|
+
return `Failed to load Chrome preferences from ${file}: ${error}`;
|
|
8547
|
+
}
|
|
8548
|
+
function chromeLaunchFailedError(error) {
|
|
8549
|
+
return `Failed to launch Chrome: ${error}`;
|
|
8550
|
+
}
|
|
8551
|
+
function chromeBinaryOverrideNotFound(path9, source) {
|
|
8552
|
+
return `Chrome binary override (${source}) points to "${path9}", but that file does not exist.`;
|
|
8553
|
+
}
|
|
8554
|
+
function chromeBinaryOverrideNotExecutable(path9, source) {
|
|
8555
|
+
return `Chrome binary override (${source}) points to "${path9}", but it is not an executable file.
|
|
8556
|
+
Update the path to the Chrome binary (e.g. /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) or unset the override to let bdg auto-detect Chrome.`;
|
|
8557
|
+
}
|
|
8558
|
+
function chromeBinaryOverrideIsDirectory(path9, source) {
|
|
8559
|
+
return `Chrome binary override (${source}) points to "${path9}", which is a directory, not an executable file.`;
|
|
8560
|
+
}
|
|
8561
|
+
function portInUseError(port, reason) {
|
|
8562
|
+
return joinLines(
|
|
8563
|
+
reason ? `Port ${port} is already in use: ${reason}.
|
|
8564
|
+
` : `Port ${port} is already in use.
|
|
8565
|
+
`,
|
|
8566
|
+
"Another program (or a Chrome left from a previous session) is listening on it.\n",
|
|
8567
|
+
"Try:",
|
|
8568
|
+
` - Use a different port: ${sessionCommand(`bdg <url> --port ${port + 1}`)}`,
|
|
8569
|
+
` - If a bdg session holds it: find it with bdg sessions, then end that one: bdg stop --session <name> (bdg cleanup --force --session <name> if it is stuck)`,
|
|
8570
|
+
` - See what uses the port: lsof -i :${port}`
|
|
8571
|
+
);
|
|
8572
|
+
}
|
|
8573
|
+
function portTakenByReason(answeredBy, chromeHost) {
|
|
8574
|
+
if (answeredBy === "nothing")
|
|
8575
|
+
return `something holds 127.0.0.1 (Chrome fell back to ${chromeHost})`;
|
|
8576
|
+
const other = answeredBy === "browser" ? "another browser" : "another process";
|
|
8577
|
+
return `${other} answers on 127.0.0.1 (Chrome listens on ${chromeHost})`;
|
|
8578
|
+
}
|
|
8579
|
+
function chromeNotAnsweringReason(port, waitedMs) {
|
|
8580
|
+
return `Chrome announced port ${port} but did not answer on 127.0.0.1 within ${(waitedMs / 1e3).toFixed(1)}s (slow start)`;
|
|
8581
|
+
}
|
|
8582
|
+
function chromePrefsNotAppliedMessage(file, reason) {
|
|
8583
|
+
return `Warning: Chrome preferences not applied to ${file} (${reason}); password manager bubbles may capture clicks`;
|
|
8584
|
+
}
|
|
8585
|
+
|
|
8345
8586
|
// src/connection/portReservation.ts
|
|
8346
8587
|
import * as net2 from "net";
|
|
8347
8588
|
var CONNECT_CHECK_MS = 500;
|
|
@@ -8362,18 +8603,18 @@ async function isPortAnswering(port) {
|
|
|
8362
8603
|
const answers = await Promise.all(LOOPBACK_HOSTS.map((host) => acceptsConnections(port, host)));
|
|
8363
8604
|
return answers.some(Boolean);
|
|
8364
8605
|
}
|
|
8365
|
-
function
|
|
8606
|
+
function portInUseError2(port) {
|
|
8366
8607
|
return new ChromeLaunchError(`Port ${port} is already in use`, {
|
|
8367
8608
|
issue: { code: "PORT_IN_USE", context: { port } }
|
|
8368
8609
|
});
|
|
8369
8610
|
}
|
|
8370
8611
|
async function reservePort(port) {
|
|
8371
|
-
if (await isPortAnswering(port)) throw
|
|
8612
|
+
if (await isPortAnswering(port)) throw portInUseError2(port);
|
|
8372
8613
|
return new Promise((resolve6, reject2) => {
|
|
8373
8614
|
const server2 = net2.createServer();
|
|
8374
8615
|
server2.once("error", (err) => {
|
|
8375
8616
|
if (err.code === "EADDRINUSE") {
|
|
8376
|
-
reject2(
|
|
8617
|
+
reject2(portInUseError2(port));
|
|
8377
8618
|
} else {
|
|
8378
8619
|
reject2(err);
|
|
8379
8620
|
}
|
|
@@ -8453,7 +8694,8 @@ function startupExitError(exitCode, output, userDataDir) {
|
|
|
8453
8694
|
var LISTENING_PATTERN = /^DevTools listening on (ws:\/\/\S+)/;
|
|
8454
8695
|
var ENDPOINT_WAIT_MS = 1e4;
|
|
8455
8696
|
var ENDPOINT_POLL_MS = 50;
|
|
8456
|
-
var
|
|
8697
|
+
var MIN_ANSWER_WAIT_MS = 1e3;
|
|
8698
|
+
var log5 = createLogger("chrome");
|
|
8457
8699
|
function parseDevToolsListening(lines) {
|
|
8458
8700
|
for (const line of [...lines].reverse()) {
|
|
8459
8701
|
const match = LISTENING_PATTERN.exec(line.trim());
|
|
@@ -8464,7 +8706,7 @@ function parseDevToolsListening(lines) {
|
|
|
8464
8706
|
if (!port || !url.pathname.startsWith("/devtools/browser/")) continue;
|
|
8465
8707
|
return { host: url.hostname, port, browserPath: url.pathname };
|
|
8466
8708
|
} catch (error) {
|
|
8467
|
-
|
|
8709
|
+
log5.debug(`Ignoring malformed DevTools announcement: ${String(error)}`);
|
|
8468
8710
|
}
|
|
8469
8711
|
}
|
|
8470
8712
|
return null;
|
|
@@ -8478,31 +8720,45 @@ async function waitForDevToolsEndpoint(logs, isRunning, timeoutMs = ENDPOINT_WAI
|
|
|
8478
8720
|
}
|
|
8479
8721
|
}
|
|
8480
8722
|
async function verifyLaunchedChrome(options) {
|
|
8481
|
-
const { logs, port, pid, timeoutMs } = options;
|
|
8723
|
+
const { logs, port, pid, timeoutMs = ENDPOINT_WAIT_MS } = options;
|
|
8724
|
+
const started = Date.now();
|
|
8482
8725
|
const isRunning = () => isProcessAlive(pid);
|
|
8483
8726
|
const endpoint = await waitForDevToolsEndpoint(logs, isRunning, timeoutMs);
|
|
8484
|
-
if (!endpoint && !isRunning())
|
|
8485
|
-
throw new ChromeLaunchError(`Chrome died immediately after launch (PID: ${pid})`, {
|
|
8486
|
-
issue: { code: "CHROME_DIED_AFTER_LAUNCH", context: { port, pid } }
|
|
8487
|
-
});
|
|
8488
|
-
}
|
|
8727
|
+
if (!endpoint && !isRunning()) throw diedError(port, pid);
|
|
8489
8728
|
if (!endpoint) return acceptUnannouncedChrome(logs, port);
|
|
8490
8729
|
if (endpoint.port !== port) {
|
|
8491
8730
|
throw portTakenError(port, `Chrome listens on port ${endpoint.port} instead`);
|
|
8492
8731
|
}
|
|
8493
|
-
const
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8732
|
+
const onLoopback = endpoint.host === "127.0.0.1";
|
|
8733
|
+
const deadline = onLoopback ? started + timeoutMs : Date.now();
|
|
8734
|
+
const answer = await waitForAnswer(port, deadline, isRunning);
|
|
8735
|
+
if (answer.kind === "devtools" && new URL(answer.wsUrl).pathname === endpoint.browserPath) {
|
|
8736
|
+
log5.debug(`Chrome on port ${port} is the launched one (${endpoint.browserPath})`);
|
|
8737
|
+
return;
|
|
8738
|
+
}
|
|
8739
|
+
if (answer.kind === "unreachable" && !isRunning()) throw diedError(port, pid);
|
|
8740
|
+
if (answer.kind === "unreachable" && onLoopback) {
|
|
8741
|
+
throw slowStartError(port, Date.now() - started);
|
|
8742
|
+
}
|
|
8743
|
+
throw portTakenError(port, portTakenByReason(answerSource(answer), endpoint.host));
|
|
8744
|
+
}
|
|
8745
|
+
async function waitForAnswer(port, deadline, isRunning) {
|
|
8746
|
+
for (; ; ) {
|
|
8747
|
+
const remaining = Math.max(deadline - Date.now(), MIN_ANSWER_WAIT_MS);
|
|
8748
|
+
const timeoutMs = Math.min(CDP_HTTP_TIMEOUT_MS, remaining);
|
|
8749
|
+
const answer = await probeDevToolsEndpoint(port, void 0, { timeoutMs });
|
|
8750
|
+
if (answer.kind !== "unreachable" || !isRunning() || Date.now() >= deadline) return answer;
|
|
8751
|
+
await delay(ENDPOINT_POLL_MS);
|
|
8499
8752
|
}
|
|
8500
|
-
|
|
8753
|
+
}
|
|
8754
|
+
function answerSource(answer) {
|
|
8755
|
+
if (answer.kind === "devtools") return "browser";
|
|
8756
|
+
return answer.kind === "not-devtools" ? "process" : "nothing";
|
|
8501
8757
|
}
|
|
8502
8758
|
async function acceptUnannouncedChrome(logs, port) {
|
|
8503
8759
|
const errLog = logs.files[0]?.file ?? "chrome-err.log";
|
|
8504
8760
|
const missing = `Chrome did not print "DevTools listening on" to ${errLog}`;
|
|
8505
|
-
if (!await fetchBrowserWsUrl(port,
|
|
8761
|
+
if (!await fetchBrowserWsUrl(port, log5)) {
|
|
8506
8762
|
throw new ChromeLaunchError(`${missing}, and no browser answers on port ${port}`, {
|
|
8507
8763
|
issue: {
|
|
8508
8764
|
code: "CHROME_LAUNCH_FAILED",
|
|
@@ -8513,10 +8769,21 @@ async function acceptUnannouncedChrome(logs, port) {
|
|
|
8513
8769
|
if (await acceptsConnections(port, "::1")) {
|
|
8514
8770
|
throw portTakenError(port, `${missing}, and both 127.0.0.1 and [::1] answer`);
|
|
8515
8771
|
}
|
|
8516
|
-
|
|
8772
|
+
log5.info(
|
|
8517
8773
|
`Warning: ${missing}; using the browser on 127.0.0.1:${port}, which was free before the launch`
|
|
8518
8774
|
);
|
|
8519
8775
|
}
|
|
8776
|
+
function diedError(port, pid) {
|
|
8777
|
+
return new ChromeLaunchError(`Chrome died immediately after launch (PID: ${pid})`, {
|
|
8778
|
+
issue: { code: "CHROME_DIED_AFTER_LAUNCH", context: { port, pid } }
|
|
8779
|
+
});
|
|
8780
|
+
}
|
|
8781
|
+
function slowStartError(port, waitedMs) {
|
|
8782
|
+
const reason = chromeNotAnsweringReason(port, waitedMs);
|
|
8783
|
+
return new ChromeLaunchError(reason, {
|
|
8784
|
+
issue: { code: "CHROME_LAUNCH_FAILED", context: { port, reason } }
|
|
8785
|
+
});
|
|
8786
|
+
}
|
|
8520
8787
|
function portTakenError(port, reason) {
|
|
8521
8788
|
return new ChromeLaunchError(`Port ${port} is already in use: ${reason}`, {
|
|
8522
8789
|
issue: { code: "PORT_IN_USE", context: { port, reason } }
|
|
@@ -8550,367 +8817,147 @@ function resolveChromeBinary(options) {
|
|
|
8550
8817
|
const stats = fs7.statSync(chromePath);
|
|
8551
8818
|
if (stats.isDirectory()) {
|
|
8552
8819
|
throw new ChromeLaunchError(
|
|
8553
|
-
`Chrome binary override (${sourceLabel2}) points to "${chromePath}", which is a directory.`,
|
|
8554
|
-
{
|
|
8555
|
-
issue: {
|
|
8556
|
-
code: "CHROME_BINARY_IS_DIRECTORY",
|
|
8557
|
-
context: { chromePath, source: sourceLabel2 }
|
|
8558
|
-
}
|
|
8559
|
-
}
|
|
8560
|
-
);
|
|
8561
|
-
}
|
|
8562
|
-
fs7.accessSync(chromePath, fs7.constants.X_OK);
|
|
8563
|
-
} catch (error) {
|
|
8564
|
-
if (error instanceof ChromeLaunchError) {
|
|
8565
|
-
throw error;
|
|
8566
|
-
}
|
|
8567
|
-
throw new ChromeLaunchError(
|
|
8568
|
-
`Chrome binary override (${sourceLabel2}) points to "${chromePath}", but it is not executable.`,
|
|
8569
|
-
{
|
|
8570
|
-
...error instanceof Error && { cause: error },
|
|
8571
|
-
issue: {
|
|
8572
|
-
code: "CHROME_BINARY_NOT_EXECUTABLE",
|
|
8573
|
-
context: { chromePath, source: sourceLabel2, reason: getErrorMessage(error) }
|
|
8574
|
-
}
|
|
8575
|
-
}
|
|
8576
|
-
);
|
|
8577
|
-
}
|
|
8578
|
-
return chromePath;
|
|
8579
|
-
}
|
|
8580
|
-
|
|
8581
|
-
// src/connection/launcher/flagsBuilder.ts
|
|
8582
|
-
import * as fs8 from "fs";
|
|
8583
|
-
function chromeSessionMarkerFlag(sessionDir) {
|
|
8584
|
-
return `--bdg-session-dir=${sessionDir}`;
|
|
8585
|
-
}
|
|
8586
|
-
var NO_SANDBOX_ENV = "BDG_NO_SANDBOX";
|
|
8587
|
-
function needsNoSandbox() {
|
|
8588
|
-
if (process.env[NO_SANDBOX_ENV] === "1") return true;
|
|
8589
|
-
if (process.platform === "linux" && process.getuid?.() === 0) return true;
|
|
8590
|
-
return fs8.existsSync("/.dockerenv");
|
|
8591
|
-
}
|
|
8592
|
-
function isDocker() {
|
|
8593
|
-
try {
|
|
8594
|
-
if (fs8.existsSync("/.dockerenv") || fs8.existsSync("/run/.containerenv")) {
|
|
8595
|
-
return true;
|
|
8596
|
-
}
|
|
8597
|
-
if (fs8.existsSync("/proc/self/cgroup")) {
|
|
8598
|
-
const cgroup = fs8.readFileSync("/proc/self/cgroup", "utf8");
|
|
8599
|
-
return cgroup.includes("docker") || cgroup.includes("containerd");
|
|
8600
|
-
}
|
|
8601
|
-
return false;
|
|
8602
|
-
} catch {
|
|
8603
|
-
return false;
|
|
8604
|
-
}
|
|
8605
|
-
}
|
|
8606
|
-
function defaultFlags() {
|
|
8607
|
-
const flags = Launcher.defaultFlags();
|
|
8608
|
-
return process.platform === "linux" ? [...flags, "--disable-setuid-sandbox"] : flags;
|
|
8609
|
-
}
|
|
8610
|
-
function buildChromeFlags(options) {
|
|
8611
|
-
const baseFlags = options.ignoreDefaultFlags ? [] : defaultFlags();
|
|
8612
|
-
const bdgFlags = [
|
|
8613
|
-
...options.sessionDir ? [chromeSessionMarkerFlag(options.sessionDir)] : [],
|
|
8614
|
-
...BDG_CHROME_FLAGS
|
|
8615
|
-
];
|
|
8616
|
-
const dockerFlags = isDocker() ? DOCKER_CHROME_FLAGS : [];
|
|
8617
|
-
const sandboxFlags = needsNoSandbox() ? ["--no-sandbox"] : [];
|
|
8618
|
-
const customFlags = options.chromeFlags ?? [];
|
|
8619
|
-
if (options.headless) {
|
|
8620
|
-
return [
|
|
8621
|
-
HEADLESS_FLAG,
|
|
8622
|
-
...baseFlags,
|
|
8623
|
-
...bdgFlags,
|
|
8624
|
-
...dockerFlags,
|
|
8625
|
-
...sandboxFlags,
|
|
8626
|
-
...customFlags
|
|
8627
|
-
];
|
|
8628
|
-
}
|
|
8629
|
-
return [...baseFlags, ...bdgFlags, ...dockerFlags, ...sandboxFlags, ...customFlags];
|
|
8630
|
-
}
|
|
8631
|
-
|
|
8632
|
-
// src/connection/launcher/preferencesLoader.ts
|
|
8633
|
-
import * as fs9 from "fs";
|
|
8634
|
-
function loadChromePrefs(options) {
|
|
8635
|
-
if (options.prefsFile) {
|
|
8636
|
-
const file = options.prefsFile;
|
|
8637
|
-
if (!fs9.existsSync(file)) {
|
|
8638
|
-
throw new ChromeLaunchError(`Chrome preferences file not found: ${file}`, {
|
|
8639
|
-
issue: { code: "PREFS_FILE_NOT_FOUND", context: { file } }
|
|
8640
|
-
});
|
|
8641
|
-
}
|
|
8642
|
-
try {
|
|
8643
|
-
const prefsContent = fs9.readFileSync(file, "utf8");
|
|
8644
|
-
const parsed = JSON.parse(prefsContent);
|
|
8645
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8646
|
-
throw new ChromeLaunchError(
|
|
8647
|
-
`Invalid Chrome preferences format in ${file}: expected object, got ${typeof parsed}`,
|
|
8648
|
-
{
|
|
8649
|
-
issue: {
|
|
8650
|
-
code: "PREFS_INVALID_FORMAT",
|
|
8651
|
-
context: { file, actualType: Array.isArray(parsed) ? "array" : typeof parsed }
|
|
8652
|
-
}
|
|
8653
|
-
}
|
|
8654
|
-
);
|
|
8655
|
-
}
|
|
8656
|
-
return parsed;
|
|
8657
|
-
} catch (error) {
|
|
8658
|
-
if (error instanceof ChromeLaunchError) {
|
|
8659
|
-
throw error;
|
|
8660
|
-
}
|
|
8661
|
-
throw new ChromeLaunchError(`Failed to load Chrome preferences from ${file}`, {
|
|
8662
|
-
...error instanceof Error && { cause: error },
|
|
8663
|
-
issue: {
|
|
8664
|
-
code: "PREFS_LOAD_FAILED",
|
|
8665
|
-
context: { file, reason: getErrorMessage(error) }
|
|
8666
|
-
}
|
|
8667
|
-
});
|
|
8668
|
-
}
|
|
8669
|
-
}
|
|
8670
|
-
return options.prefs;
|
|
8671
|
-
}
|
|
8672
|
-
function ensureJSONCompatiblePrefs(prefs) {
|
|
8673
|
-
if (prefs === void 0) {
|
|
8674
|
-
return void 0;
|
|
8675
|
-
}
|
|
8676
|
-
try {
|
|
8677
|
-
JSON.stringify(prefs);
|
|
8678
|
-
} catch (error) {
|
|
8679
|
-
throw new ChromeLaunchError("Chrome preferences must be JSON-serializable", {
|
|
8680
|
-
...error instanceof Error && { cause: error },
|
|
8681
|
-
issue: {
|
|
8682
|
-
code: "PREFS_NOT_JSON_SERIALIZABLE",
|
|
8683
|
-
context: { reason: getErrorMessage(error) }
|
|
8684
|
-
}
|
|
8685
|
-
});
|
|
8686
|
-
}
|
|
8687
|
-
return prefs;
|
|
8688
|
-
}
|
|
8689
|
-
|
|
8690
|
-
// src/connection/launcher/profilePreferences.ts
|
|
8691
|
-
import * as fs10 from "fs";
|
|
8692
|
-
import * as os3 from "os";
|
|
8693
|
-
import * as path5 from "path";
|
|
8694
|
-
|
|
8695
|
-
// src/connection/diagnostics.ts
|
|
8696
|
-
var log5 = createLogger("diagnostics");
|
|
8697
|
-
var cachedDiagnostics = null;
|
|
8698
|
-
function getChromeDiagnostics() {
|
|
8699
|
-
if (cachedDiagnostics) {
|
|
8700
|
-
return cachedDiagnostics;
|
|
8701
|
-
}
|
|
8702
|
-
let defaultPath = null;
|
|
8703
|
-
try {
|
|
8704
|
-
defaultPath = getChromePath();
|
|
8705
|
-
} catch (error) {
|
|
8706
|
-
log5.debug(`Failed to get default Chrome path: ${getErrorMessage(error)}`);
|
|
8707
|
-
}
|
|
8708
|
-
let installations = [];
|
|
8709
|
-
try {
|
|
8710
|
-
installations = Launcher.getInstallations();
|
|
8711
|
-
} catch (error) {
|
|
8712
|
-
log5.debug(`Failed to get Chrome installations: ${getErrorMessage(error)}`);
|
|
8713
|
-
}
|
|
8714
|
-
cachedDiagnostics = {
|
|
8715
|
-
defaultPath,
|
|
8716
|
-
installations,
|
|
8717
|
-
installationCount: installations.length
|
|
8718
|
-
};
|
|
8719
|
-
return cachedDiagnostics;
|
|
8720
|
-
}
|
|
8721
|
-
|
|
8722
|
-
// src/ui/messages/chrome.ts
|
|
8723
|
-
function formatChromeNotice(notice) {
|
|
8724
|
-
const ctx = notice.context ?? {};
|
|
8725
|
-
switch (notice.code) {
|
|
8726
|
-
case "EXTERNAL_CHROME_CONNECTING":
|
|
8727
|
-
return chromeExternalConnectionMessage();
|
|
8728
|
-
case "EXTERNAL_CHROME_WS_URL":
|
|
8729
|
-
return chromeExternalWebSocketMessage(ctx["wsUrl"]);
|
|
8730
|
-
case "EXTERNAL_CHROME_NO_PID":
|
|
8731
|
-
return chromeExternalNoPidMessage();
|
|
8732
|
-
case "EXTERNAL_CHROME_SKIP_TERMINATION":
|
|
8733
|
-
return chromeExternalSkipTerminationMessage();
|
|
8734
|
-
}
|
|
8735
|
-
}
|
|
8736
|
-
function formatChromeIssue(issue) {
|
|
8737
|
-
const ctx = issue.context ?? {};
|
|
8738
|
-
switch (issue.code) {
|
|
8739
|
-
case "PORT_IN_USE":
|
|
8740
|
-
return portInUseError2(ctx["port"]);
|
|
8741
|
-
case "INVALID_PORT":
|
|
8742
|
-
return invalidPortError(ctx["port"]);
|
|
8743
|
-
case "USER_DATA_DIR_CREATE_FAILED":
|
|
8744
|
-
return userDataDirError(ctx["userDataDir"], ctx["reason"] ?? "");
|
|
8745
|
-
case "CHROME_LAUNCH_FAILED":
|
|
8746
|
-
case "CHROME_DIED_AFTER_LAUNCH": {
|
|
8747
|
-
const port = ctx["port"];
|
|
8748
|
-
const reason = ctx["reason"];
|
|
8749
|
-
const pid = typeof ctx["pid"] === "number" ? ctx["pid"] : "unknown";
|
|
8750
|
-
const header2 = issue.code === "CHROME_DIED_AFTER_LAUNCH" ? `Chrome died immediately after launch (PID: ${pid})` : reason ? chromeLaunchFailedError(reason) : `Chrome failed to launch`;
|
|
8751
|
-
const diagnostics = getFormattedDiagnostics();
|
|
8752
|
-
return joinLines(
|
|
8753
|
-
header2,
|
|
8754
|
-
"",
|
|
8755
|
-
"Possible causes:",
|
|
8756
|
-
` - Port ${port} conflict (check: lsof -ti:${port})`,
|
|
8757
|
-
` - Chrome binary not found`,
|
|
8758
|
-
` - Insufficient permissions`,
|
|
8759
|
-
` - Chrome crashed on startup`,
|
|
8760
|
-
"",
|
|
8761
|
-
...diagnostics,
|
|
8762
|
-
"",
|
|
8763
|
-
"Try:",
|
|
8764
|
-
` - ${sessionCommand("bdg cleanup")}`,
|
|
8765
|
-
` - See what uses the port: lsof -i :${port}`,
|
|
8766
|
-
` - Use different port: ${sessionCommand(`bdg <url> --port ${port + 1}`)}`,
|
|
8767
|
-
` - In a container where Chrome's sandbox fails: BDG_NO_SANDBOX=1 ${sessionCommand("bdg <url>")}`
|
|
8768
|
-
);
|
|
8769
|
-
}
|
|
8770
|
-
case "CHROME_EXITED_DURING_STARTUP":
|
|
8771
|
-
return chromeExitedDuringStartupError(
|
|
8772
|
-
ctx["exitCode"],
|
|
8773
|
-
ctx["output"] ?? [],
|
|
8774
|
-
ctx["userDataDir"],
|
|
8775
|
-
ctx["profileInUse"] === true
|
|
8776
|
-
);
|
|
8777
|
-
case "NO_PAGE_TARGET_FOUND":
|
|
8778
|
-
return noPageTargetFoundError(
|
|
8779
|
-
ctx["port"],
|
|
8780
|
-
ctx["availableTargets"]
|
|
8781
|
-
);
|
|
8782
|
-
case "CHROME_BINARY_NOT_FOUND": {
|
|
8783
|
-
const diagnostics = getFormattedDiagnostics();
|
|
8784
|
-
return joinLines(
|
|
8785
|
-
chromeBinaryOverrideNotFound(ctx["chromePath"], ctx["source"]),
|
|
8786
|
-
"",
|
|
8787
|
-
...diagnostics
|
|
8788
|
-
);
|
|
8789
|
-
}
|
|
8790
|
-
case "CHROME_BINARY_IS_DIRECTORY":
|
|
8791
|
-
return chromeBinaryOverrideIsDirectory(ctx["chromePath"], ctx["source"]);
|
|
8792
|
-
case "CHROME_BINARY_NOT_EXECUTABLE": {
|
|
8793
|
-
const reason = ctx["reason"];
|
|
8794
|
-
const base = chromeBinaryOverrideNotExecutable(
|
|
8795
|
-
ctx["chromePath"],
|
|
8796
|
-
ctx["source"]
|
|
8797
|
-
);
|
|
8798
|
-
return reason ? `${base}
|
|
8799
|
-
|
|
8800
|
-
${reason}` : base;
|
|
8801
|
-
}
|
|
8802
|
-
case "PREFS_FILE_NOT_FOUND":
|
|
8803
|
-
return prefsFileNotFoundError(ctx["file"]);
|
|
8804
|
-
case "PREFS_INVALID_FORMAT":
|
|
8805
|
-
return invalidPrefsFormatError(ctx["file"], ctx["actualType"]);
|
|
8806
|
-
case "PREFS_LOAD_FAILED":
|
|
8807
|
-
return prefsLoadError(ctx["file"], ctx["reason"] ?? "");
|
|
8808
|
-
case "PREFS_NOT_JSON_SERIALIZABLE":
|
|
8809
|
-
return `Chrome preferences must be JSON-serializable: ${ctx["reason"] ?? "unknown error"}`;
|
|
8810
|
-
}
|
|
8811
|
-
}
|
|
8812
|
-
function chromeExitedDuringStartupError(exitCode, output, userDataDir, profileInUse2) {
|
|
8813
|
-
if (profileInUse2) {
|
|
8814
|
-
return joinLines(
|
|
8815
|
-
`The Chrome profile ${userDataDir} is in use by another Chrome`,
|
|
8816
|
-
`Close that Chrome, or use another profile directory: ${sessionCommand("bdg <url> -u ./other-profile")}`
|
|
8817
|
-
);
|
|
8818
|
-
}
|
|
8819
|
-
return joinLines(
|
|
8820
|
-
`Chrome exited during startup (exit code ${exitCode ?? "none"})`,
|
|
8821
|
-
...output.length > 0 ? ["Chrome said:", ...output.map((line) => ` ${line}`)] : [],
|
|
8822
|
-
"Check --chrome-flags and BDG_CHROME_FLAGS (an unknown flag or value can stop Chrome)"
|
|
8823
|
-
);
|
|
8824
|
-
}
|
|
8825
|
-
function getFormattedDiagnostics() {
|
|
8826
|
-
return formatDiagnosticsForError(getChromeDiagnostics());
|
|
8827
|
-
}
|
|
8828
|
-
function formatDiagnosticsForError(diagnostics) {
|
|
8829
|
-
const lines = [];
|
|
8830
|
-
if (diagnostics.installationCount === 0) {
|
|
8831
|
-
lines.push("Error: No Chrome installations detected\n");
|
|
8832
|
-
lines.push("Install Chrome from:");
|
|
8833
|
-
lines.push(" https://www.google.com/chrome/\n");
|
|
8834
|
-
} else {
|
|
8835
|
-
lines.push(`Found ${pluralize(diagnostics.installationCount, "Chrome installation")}:
|
|
8836
|
-
`);
|
|
8837
|
-
diagnostics.installations.forEach((path9, index) => {
|
|
8838
|
-
lines.push(` ${index + 1}. ${path9}`);
|
|
8839
|
-
});
|
|
8840
|
-
lines.push("");
|
|
8841
|
-
if (diagnostics.defaultPath) {
|
|
8842
|
-
lines.push(`Default binary: ${diagnostics.defaultPath}
|
|
8843
|
-
`);
|
|
8844
|
-
} else {
|
|
8845
|
-
lines.push("Default binary: Could not determine\n");
|
|
8846
|
-
}
|
|
8847
|
-
}
|
|
8848
|
-
return lines;
|
|
8849
|
-
}
|
|
8850
|
-
function invalidPortError(port) {
|
|
8851
|
-
return `Invalid port number: ${port}. Port must be between 1 and 65535.`;
|
|
8852
|
-
}
|
|
8853
|
-
function userDataDirError(dir, error) {
|
|
8854
|
-
return `Failed to create user data directory at ${dir}: ${error}`;
|
|
8855
|
-
}
|
|
8856
|
-
function chromeExternalConnectionMessage() {
|
|
8857
|
-
return "Connecting to existing Chrome instance...";
|
|
8858
|
-
}
|
|
8859
|
-
function chromeExternalWebSocketMessage(wsUrl) {
|
|
8860
|
-
return `WebSocket URL: ${wsUrl}`;
|
|
8861
|
-
}
|
|
8862
|
-
function chromeExternalNoPidMessage() {
|
|
8863
|
-
return "Using external Chrome (no PID - not managed by bdg)";
|
|
8864
|
-
}
|
|
8865
|
-
function chromeExternalSkipTerminationMessage() {
|
|
8866
|
-
return "Using external Chrome - skipping termination (not managed by bdg)";
|
|
8867
|
-
}
|
|
8868
|
-
function noPageTargetFoundError(port, availableTargets) {
|
|
8869
|
-
return joinLines(
|
|
8870
|
-
`Chrome started but opened no page to attach to (port ${port})`,
|
|
8871
|
-
`Retry: ${sessionCommand("bdg <url>")}. If it keeps failing, another program may use the port (lsof -i :${port}) or a crashed session may be left over: ${sessionCommand("bdg cleanup")} && ${sessionCommand("bdg <url>")}`,
|
|
8872
|
-
...availableTargets ? ["", `Chrome targets:
|
|
8873
|
-
${availableTargets}`] : []
|
|
8874
|
-
);
|
|
8875
|
-
}
|
|
8876
|
-
function prefsFileNotFoundError(file) {
|
|
8877
|
-
return `Chrome preferences file not found: ${file}`;
|
|
8878
|
-
}
|
|
8879
|
-
function invalidPrefsFormatError(file, type) {
|
|
8880
|
-
return `Invalid Chrome preferences format in ${file}: expected object, got ${type}`;
|
|
8820
|
+
`Chrome binary override (${sourceLabel2}) points to "${chromePath}", which is a directory.`,
|
|
8821
|
+
{
|
|
8822
|
+
issue: {
|
|
8823
|
+
code: "CHROME_BINARY_IS_DIRECTORY",
|
|
8824
|
+
context: { chromePath, source: sourceLabel2 }
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
);
|
|
8828
|
+
}
|
|
8829
|
+
fs7.accessSync(chromePath, fs7.constants.X_OK);
|
|
8830
|
+
} catch (error) {
|
|
8831
|
+
if (error instanceof ChromeLaunchError) {
|
|
8832
|
+
throw error;
|
|
8833
|
+
}
|
|
8834
|
+
throw new ChromeLaunchError(
|
|
8835
|
+
`Chrome binary override (${sourceLabel2}) points to "${chromePath}", but it is not executable.`,
|
|
8836
|
+
{
|
|
8837
|
+
...error instanceof Error && { cause: error },
|
|
8838
|
+
issue: {
|
|
8839
|
+
code: "CHROME_BINARY_NOT_EXECUTABLE",
|
|
8840
|
+
context: { chromePath, source: sourceLabel2, reason: getErrorMessage(error) }
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
);
|
|
8844
|
+
}
|
|
8845
|
+
return chromePath;
|
|
8881
8846
|
}
|
|
8882
|
-
|
|
8883
|
-
|
|
8847
|
+
|
|
8848
|
+
// src/connection/launcher/flagsBuilder.ts
|
|
8849
|
+
import * as fs8 from "fs";
|
|
8850
|
+
function chromeSessionMarkerFlag(sessionDir) {
|
|
8851
|
+
return `--bdg-session-dir=${sessionDir}`;
|
|
8884
8852
|
}
|
|
8885
|
-
|
|
8886
|
-
|
|
8853
|
+
var NO_SANDBOX_ENV = "BDG_NO_SANDBOX";
|
|
8854
|
+
function needsNoSandbox() {
|
|
8855
|
+
if (process.env[NO_SANDBOX_ENV] === "1") return true;
|
|
8856
|
+
if (process.platform === "linux" && process.getuid?.() === 0) return true;
|
|
8857
|
+
return fs8.existsSync("/.dockerenv");
|
|
8887
8858
|
}
|
|
8888
|
-
function
|
|
8889
|
-
|
|
8859
|
+
function isDocker() {
|
|
8860
|
+
try {
|
|
8861
|
+
if (fs8.existsSync("/.dockerenv") || fs8.existsSync("/run/.containerenv")) {
|
|
8862
|
+
return true;
|
|
8863
|
+
}
|
|
8864
|
+
if (fs8.existsSync("/proc/self/cgroup")) {
|
|
8865
|
+
const cgroup = fs8.readFileSync("/proc/self/cgroup", "utf8");
|
|
8866
|
+
return cgroup.includes("docker") || cgroup.includes("containerd");
|
|
8867
|
+
}
|
|
8868
|
+
return false;
|
|
8869
|
+
} catch {
|
|
8870
|
+
return false;
|
|
8871
|
+
}
|
|
8890
8872
|
}
|
|
8891
|
-
function
|
|
8892
|
-
|
|
8893
|
-
|
|
8873
|
+
function defaultFlags() {
|
|
8874
|
+
const flags = Launcher.defaultFlags();
|
|
8875
|
+
return process.platform === "linux" ? [...flags, "--disable-setuid-sandbox"] : flags;
|
|
8894
8876
|
}
|
|
8895
|
-
function
|
|
8896
|
-
|
|
8877
|
+
function buildChromeFlags(options) {
|
|
8878
|
+
const baseFlags = options.ignoreDefaultFlags ? [] : defaultFlags();
|
|
8879
|
+
const bdgFlags = [
|
|
8880
|
+
...options.sessionDir ? [chromeSessionMarkerFlag(options.sessionDir)] : [],
|
|
8881
|
+
...BDG_CHROME_FLAGS
|
|
8882
|
+
];
|
|
8883
|
+
const dockerFlags = isDocker() ? DOCKER_CHROME_FLAGS : [];
|
|
8884
|
+
const sandboxFlags = needsNoSandbox() ? ["--no-sandbox"] : [];
|
|
8885
|
+
const customFlags = options.chromeFlags ?? [];
|
|
8886
|
+
if (options.headless) {
|
|
8887
|
+
return [
|
|
8888
|
+
HEADLESS_FLAG,
|
|
8889
|
+
...baseFlags,
|
|
8890
|
+
...bdgFlags,
|
|
8891
|
+
...dockerFlags,
|
|
8892
|
+
...sandboxFlags,
|
|
8893
|
+
...customFlags
|
|
8894
|
+
];
|
|
8895
|
+
}
|
|
8896
|
+
return [...baseFlags, ...bdgFlags, ...dockerFlags, ...sandboxFlags, ...customFlags];
|
|
8897
8897
|
}
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8898
|
+
|
|
8899
|
+
// src/connection/launcher/preferencesLoader.ts
|
|
8900
|
+
import * as fs9 from "fs";
|
|
8901
|
+
function loadChromePrefs(options) {
|
|
8902
|
+
if (options.prefsFile) {
|
|
8903
|
+
const file = options.prefsFile;
|
|
8904
|
+
if (!fs9.existsSync(file)) {
|
|
8905
|
+
throw new ChromeLaunchError(`Chrome preferences file not found: ${file}`, {
|
|
8906
|
+
issue: { code: "PREFS_FILE_NOT_FOUND", context: { file } }
|
|
8907
|
+
});
|
|
8908
|
+
}
|
|
8909
|
+
try {
|
|
8910
|
+
const prefsContent = fs9.readFileSync(file, "utf8");
|
|
8911
|
+
const parsed = JSON.parse(prefsContent);
|
|
8912
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8913
|
+
throw new ChromeLaunchError(
|
|
8914
|
+
`Invalid Chrome preferences format in ${file}: expected object, got ${typeof parsed}`,
|
|
8915
|
+
{
|
|
8916
|
+
issue: {
|
|
8917
|
+
code: "PREFS_INVALID_FORMAT",
|
|
8918
|
+
context: { file, actualType: Array.isArray(parsed) ? "array" : typeof parsed }
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
);
|
|
8922
|
+
}
|
|
8923
|
+
return parsed;
|
|
8924
|
+
} catch (error) {
|
|
8925
|
+
if (error instanceof ChromeLaunchError) {
|
|
8926
|
+
throw error;
|
|
8927
|
+
}
|
|
8928
|
+
throw new ChromeLaunchError(`Failed to load Chrome preferences from ${file}`, {
|
|
8929
|
+
...error instanceof Error && { cause: error },
|
|
8930
|
+
issue: {
|
|
8931
|
+
code: "PREFS_LOAD_FAILED",
|
|
8932
|
+
context: { file, reason: getErrorMessage(error) }
|
|
8933
|
+
}
|
|
8934
|
+
});
|
|
8935
|
+
}
|
|
8936
|
+
}
|
|
8937
|
+
return options.prefs;
|
|
8908
8938
|
}
|
|
8909
|
-
function
|
|
8910
|
-
|
|
8939
|
+
function ensureJSONCompatiblePrefs(prefs) {
|
|
8940
|
+
if (prefs === void 0) {
|
|
8941
|
+
return void 0;
|
|
8942
|
+
}
|
|
8943
|
+
try {
|
|
8944
|
+
JSON.stringify(prefs);
|
|
8945
|
+
} catch (error) {
|
|
8946
|
+
throw new ChromeLaunchError("Chrome preferences must be JSON-serializable", {
|
|
8947
|
+
...error instanceof Error && { cause: error },
|
|
8948
|
+
issue: {
|
|
8949
|
+
code: "PREFS_NOT_JSON_SERIALIZABLE",
|
|
8950
|
+
context: { reason: getErrorMessage(error) }
|
|
8951
|
+
}
|
|
8952
|
+
});
|
|
8953
|
+
}
|
|
8954
|
+
return prefs;
|
|
8911
8955
|
}
|
|
8912
8956
|
|
|
8913
8957
|
// src/connection/launcher/profilePreferences.ts
|
|
8958
|
+
import * as fs10 from "fs";
|
|
8959
|
+
import * as os3 from "os";
|
|
8960
|
+
import * as path5 from "path";
|
|
8914
8961
|
function isPrefsObject(value) {
|
|
8915
8962
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8916
8963
|
}
|
|
@@ -10941,6 +10988,101 @@ var ExtraInfoTracker = class {
|
|
|
10941
10988
|
}
|
|
10942
10989
|
};
|
|
10943
10990
|
|
|
10991
|
+
// src/ui/messages/networkMessages.ts
|
|
10992
|
+
function megabytes(bytes) {
|
|
10993
|
+
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
10994
|
+
}
|
|
10995
|
+
function bodyEvictedReason(budgetBytes) {
|
|
10996
|
+
return `evicted: total body budget (bdg keeps the newest ${megabytes(budgetBytes)} of response bodies)`;
|
|
10997
|
+
}
|
|
10998
|
+
|
|
10999
|
+
// src/telemetry/networkRetention.ts
|
|
11000
|
+
var RequestRetention = class {
|
|
11001
|
+
/**
|
|
11002
|
+
* @param requests - Finished requests, oldest first (updated in place)
|
|
11003
|
+
* @param limits - Request cap and body budget
|
|
11004
|
+
* @param evictions - Counters updated on each drop and eviction
|
|
11005
|
+
*/
|
|
11006
|
+
constructor(requests, limits, evictions) {
|
|
11007
|
+
this.requests = requests;
|
|
11008
|
+
this.limits = limits;
|
|
11009
|
+
this.evictions = evictions;
|
|
11010
|
+
}
|
|
11011
|
+
requests;
|
|
11012
|
+
limits;
|
|
11013
|
+
evictions;
|
|
11014
|
+
bodySizes = /* @__PURE__ */ new Map();
|
|
11015
|
+
storedBodyBytes = 0;
|
|
11016
|
+
/**
|
|
11017
|
+
* Add a finished request, dropping the oldest one past the cap.
|
|
11018
|
+
*
|
|
11019
|
+
* @param request - Finished request
|
|
11020
|
+
* @returns The dropped request, if one was
|
|
11021
|
+
*/
|
|
11022
|
+
add(request) {
|
|
11023
|
+
this.requests.push(request);
|
|
11024
|
+
if (this.requests.length <= this.limits.maxRequests) return void 0;
|
|
11025
|
+
const dropped = this.requests.shift();
|
|
11026
|
+
if (!dropped) return void 0;
|
|
11027
|
+
this.evictions.requestsDropped++;
|
|
11028
|
+
this.forgetBody(dropped);
|
|
11029
|
+
return dropped;
|
|
11030
|
+
}
|
|
11031
|
+
/**
|
|
11032
|
+
* Store a fetched response body on a kept request, then evict the oldest
|
|
11033
|
+
* bodies until the total fits the budget (a body larger than the whole
|
|
11034
|
+
* budget is evicted itself). Storing a body again replaces the earlier one.
|
|
11035
|
+
*
|
|
11036
|
+
* @param request - Request the body belongs to
|
|
11037
|
+
* @param body - Body as Chrome returned it
|
|
11038
|
+
* @param base64Encoded - Whether `body` is base64
|
|
11039
|
+
*/
|
|
11040
|
+
storeBody(request, body, base64Encoded) {
|
|
11041
|
+
this.forgetBody(request);
|
|
11042
|
+
request.responseBody = body;
|
|
11043
|
+
if (base64Encoded) request.responseBodyBase64 = true;
|
|
11044
|
+
if (body) {
|
|
11045
|
+
request.decodedBodyLength = Buffer.byteLength(body, base64Encoded ? "base64" : "utf-8");
|
|
11046
|
+
}
|
|
11047
|
+
const size = Buffer.byteLength(body);
|
|
11048
|
+
this.bodySizes.set(request, size);
|
|
11049
|
+
this.storedBodyBytes += size;
|
|
11050
|
+
this.enforceBodyBudget();
|
|
11051
|
+
}
|
|
11052
|
+
/** Evict the oldest stored bodies while their total is over the budget. */
|
|
11053
|
+
enforceBodyBudget() {
|
|
11054
|
+
while (this.storedBodyBytes > this.limits.maxTotalBodyBytes) {
|
|
11055
|
+
const oldest = this.bodySizes.keys().next();
|
|
11056
|
+
if (oldest.done) return;
|
|
11057
|
+
const request = oldest.value;
|
|
11058
|
+
this.forgetBody(request);
|
|
11059
|
+
request.responseBody = skippedBodyPlaceholder(
|
|
11060
|
+
bodyEvictedReason(this.limits.maxTotalBodyBytes)
|
|
11061
|
+
);
|
|
11062
|
+
delete request.responseBodyBase64;
|
|
11063
|
+
this.evictions.bodiesEvicted++;
|
|
11064
|
+
}
|
|
11065
|
+
}
|
|
11066
|
+
/**
|
|
11067
|
+
* Stop counting a request's stored body.
|
|
11068
|
+
*
|
|
11069
|
+
* @param request - Request whose body is let go
|
|
11070
|
+
*/
|
|
11071
|
+
forgetBody(request) {
|
|
11072
|
+
const size = this.bodySizes.get(request);
|
|
11073
|
+
if (size === void 0) return;
|
|
11074
|
+
this.bodySizes.delete(request);
|
|
11075
|
+
this.storedBodyBytes -= size;
|
|
11076
|
+
}
|
|
11077
|
+
};
|
|
11078
|
+
var SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
|
|
11079
|
+
function skippedBodyPlaceholder(reason) {
|
|
11080
|
+
return `[SKIPPED: ${reason}]`;
|
|
11081
|
+
}
|
|
11082
|
+
function skippedBodyReason(body) {
|
|
11083
|
+
return body === void 0 ? void 0 : SKIPPED_BODY_PATTERN.exec(body)?.[1];
|
|
11084
|
+
}
|
|
11085
|
+
|
|
10944
11086
|
// src/telemetry/network.ts
|
|
10945
11087
|
var log16 = createLogger("network");
|
|
10946
11088
|
function shouldFilterRequest(url, includeAll, networkInclude, networkExclude) {
|
|
@@ -10952,19 +11094,12 @@ function shouldFilterRequest(url, includeAll, networkInclude, networkExclude) {
|
|
|
10952
11094
|
}
|
|
10953
11095
|
return false;
|
|
10954
11096
|
}
|
|
10955
|
-
function fetchResponseBody(cdp, requestId, request, pendingFetches, sessionId) {
|
|
11097
|
+
function fetchResponseBody(cdp, requestId, request, pendingFetches, retention, sessionId) {
|
|
10956
11098
|
pendingFetches.add(requestId);
|
|
10957
11099
|
void cdp.send("Network.getResponseBody", { requestId }, sessionId).then((response) => {
|
|
10958
11100
|
if (!pendingFetches.has(requestId)) return;
|
|
10959
|
-
const
|
|
10960
|
-
request
|
|
10961
|
-
if (typedResponse.base64Encoded) request.responseBodyBase64 = true;
|
|
10962
|
-
if (typedResponse.body) {
|
|
10963
|
-
request.decodedBodyLength = Buffer.byteLength(
|
|
10964
|
-
typedResponse.body,
|
|
10965
|
-
typedResponse.base64Encoded ? "base64" : "utf-8"
|
|
10966
|
-
);
|
|
10967
|
-
}
|
|
11101
|
+
const { body, base64Encoded } = response;
|
|
11102
|
+
retention.storeBody(request, body, base64Encoded);
|
|
10968
11103
|
}).catch((error) => {
|
|
10969
11104
|
log16.debug(
|
|
10970
11105
|
`Failed to fetch response body for request ${requestId}: ${getErrorMessage(error)}`
|
|
@@ -10993,13 +11128,6 @@ function createNetworkRequest(params, getCurrentNavigationId) {
|
|
|
10993
11128
|
...params.type !== void 0 && { resourceType: params.type }
|
|
10994
11129
|
};
|
|
10995
11130
|
}
|
|
10996
|
-
var SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
|
|
10997
|
-
function skippedBodyPlaceholder(reason) {
|
|
10998
|
-
return `[SKIPPED: ${reason}]`;
|
|
10999
|
-
}
|
|
11000
|
-
function skippedBodyReason(body) {
|
|
11001
|
-
return body === void 0 ? void 0 : SKIPPED_BODY_PATTERN.exec(body)?.[1];
|
|
11002
|
-
}
|
|
11003
11131
|
function applyResponse(request, response, resourceType) {
|
|
11004
11132
|
const { status, mimeType, headers, timing, remoteIPAddress, remotePort, connectionId } = response;
|
|
11005
11133
|
request.status = status;
|
|
@@ -11090,6 +11218,9 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11090
11218
|
networkInclude = [],
|
|
11091
11219
|
networkExclude = [],
|
|
11092
11220
|
maxBodySize = MAX_RESPONSE_SIZE,
|
|
11221
|
+
maxRequests = MAX_NETWORK_REQUESTS,
|
|
11222
|
+
maxTotalBodyBytes = MAX_TOTAL_BODY_BYTES,
|
|
11223
|
+
evictions = { requestsDropped: 0, bodiesEvicted: 0 },
|
|
11093
11224
|
getCurrentNavigationId
|
|
11094
11225
|
} = options;
|
|
11095
11226
|
const requestMap = options.pendingRequests ?? /* @__PURE__ */ new Map();
|
|
@@ -11098,6 +11229,11 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11098
11229
|
const extraInfo = new ExtraInfoTracker((requestId) => requestMap.get(requestId)?.request);
|
|
11099
11230
|
const registry = new CDPHandlerRegistry();
|
|
11100
11231
|
const typed = new TypedCDPConnection(cdp);
|
|
11232
|
+
const retention = new RequestRetention(requests, { maxRequests, maxTotalBodyBytes }, evictions);
|
|
11233
|
+
const record = (request) => {
|
|
11234
|
+
const dropped = retention.add(request);
|
|
11235
|
+
if (dropped) pendingFetches.delete(dropped.requestId);
|
|
11236
|
+
};
|
|
11101
11237
|
let bodiesFetched = 0;
|
|
11102
11238
|
let bodiesSkipped = 0;
|
|
11103
11239
|
try {
|
|
@@ -11114,11 +11250,9 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11114
11250
|
const failRequest = (requestId, failure) => {
|
|
11115
11251
|
const entry = requestMap.get(requestId);
|
|
11116
11252
|
if (!entry) return;
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
extraInfo.complete(requestId, entry.request);
|
|
11121
|
-
}
|
|
11253
|
+
applyFailure(entry.request, failure);
|
|
11254
|
+
record(entry.request);
|
|
11255
|
+
extraInfo.complete(requestId, entry.request);
|
|
11122
11256
|
requestMap.delete(requestId);
|
|
11123
11257
|
redirectHops.delete(requestId);
|
|
11124
11258
|
};
|
|
@@ -11153,17 +11287,11 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11153
11287
|
const hop = completeRedirectHop(previous.request, params, redirectHops);
|
|
11154
11288
|
extraInfo.applyResponse(params.requestId, hop);
|
|
11155
11289
|
extraInfo.recordRedirectHop(params.requestId, hop);
|
|
11156
|
-
|
|
11290
|
+
record(hop);
|
|
11157
11291
|
}
|
|
11158
11292
|
if (shouldFilterRequest(params.request.url, includeAll, networkInclude, networkExclude)) {
|
|
11159
11293
|
return;
|
|
11160
11294
|
}
|
|
11161
|
-
if (requestMap.size >= MAX_NETWORK_REQUESTS) {
|
|
11162
|
-
log16.debug(
|
|
11163
|
-
`Warning: Network request limit reached (${MAX_NETWORK_REQUESTS}), dropping new requests`
|
|
11164
|
-
);
|
|
11165
|
-
return;
|
|
11166
|
-
}
|
|
11167
11295
|
const request = createNetworkRequest(params, getCurrentNavigationId);
|
|
11168
11296
|
extraInfo.applyRequest(params.requestId, request);
|
|
11169
11297
|
if (params.type === "Document" && params.loaderId) {
|
|
@@ -11200,12 +11328,6 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11200
11328
|
registry.registerTyped(typed, "Network.loadingFinished", (params) => {
|
|
11201
11329
|
const entry = requestMap.get(params.requestId);
|
|
11202
11330
|
if (!entry) return;
|
|
11203
|
-
if (requests.length >= MAX_NETWORK_REQUESTS) {
|
|
11204
|
-
log16.debug(`Warning: Network request limit reached (${MAX_NETWORK_REQUESTS})`);
|
|
11205
|
-
requestMap.delete(params.requestId);
|
|
11206
|
-
redirectHops.delete(params.requestId);
|
|
11207
|
-
return;
|
|
11208
|
-
}
|
|
11209
11331
|
const request = entry.request;
|
|
11210
11332
|
if (params.encodedDataLength !== void 0) {
|
|
11211
11333
|
request.encodedDataLength = params.encodedDataLength;
|
|
@@ -11225,12 +11347,12 @@ async function startNetworkCollection(cdp, requests, options = {}) {
|
|
|
11225
11347
|
);
|
|
11226
11348
|
if (decision.should) {
|
|
11227
11349
|
bodiesFetched++;
|
|
11228
|
-
fetchResponseBody(cdp, params.requestId, request, pendingFetches, entry.sessionId);
|
|
11350
|
+
fetchResponseBody(cdp, params.requestId, request, pendingFetches, retention, entry.sessionId);
|
|
11229
11351
|
} else {
|
|
11230
11352
|
bodiesSkipped++;
|
|
11231
11353
|
request.responseBody = skippedBodyPlaceholder(decision.reason ?? "not captured");
|
|
11232
11354
|
}
|
|
11233
|
-
|
|
11355
|
+
record(request);
|
|
11234
11356
|
extraInfo.complete(params.requestId, request);
|
|
11235
11357
|
requestMap.delete(params.requestId);
|
|
11236
11358
|
redirectHops.delete(params.requestId);
|
|
@@ -11482,6 +11604,7 @@ function createDefaultTelemetryPlugins() {
|
|
|
11482
11604
|
fetchAllBodies: config.includeAll ?? false,
|
|
11483
11605
|
getCurrentNavigationId: store.getCurrentNavigationId ?? void 0,
|
|
11484
11606
|
pendingRequests: store.pendingNetworkRequests,
|
|
11607
|
+
evictions: store.networkEvictions,
|
|
11485
11608
|
...filterDefined({
|
|
11486
11609
|
maxBodySize: config.maxBodySize
|
|
11487
11610
|
})
|
|
@@ -11668,7 +11791,7 @@ var COMPOSED_JS = `(node) => {
|
|
|
11668
11791
|
while (walker.nextNode()) if (composes(walker.currentNode)) return true;
|
|
11669
11792
|
return false;
|
|
11670
11793
|
}`;
|
|
11671
|
-
var FLAT_TEXT_JS = `(el, limit) => {
|
|
11794
|
+
var FLAT_TEXT_JS = `(el, limit, fields) => {
|
|
11672
11795
|
const composed = ${COMPOSED_JS};
|
|
11673
11796
|
const view = el.ownerDocument.defaultView;
|
|
11674
11797
|
let text = '';
|
|
@@ -11678,7 +11801,8 @@ var FLAT_TEXT_JS = `(el, limit) => {
|
|
|
11678
11801
|
if (text.length >= limit) return;
|
|
11679
11802
|
if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');
|
|
11680
11803
|
if (node.nodeType !== 1) return;
|
|
11681
|
-
if (/^(input|textarea
|
|
11804
|
+
if (/^(input|textarea)$/.test(node.localName)) return;
|
|
11805
|
+
if (!fields && (node.localName === 'select' || node.isContentEditable)) return;
|
|
11682
11806
|
const style = view.getComputedStyle(node);
|
|
11683
11807
|
if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;
|
|
11684
11808
|
if (node.localName === 'br') text += '\\n';
|
|
@@ -13090,14 +13214,15 @@ var BIND_MATCH_FUNCTION = `function (selector, i) {
|
|
|
13090
13214
|
}`;
|
|
13091
13215
|
var log22 = createLogger("dom");
|
|
13092
13216
|
var FILTER_MATCHING_JS = `(shadowRoots) => {
|
|
13217
|
+
const composed = ${COMPOSED_JS};
|
|
13218
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
13093
13219
|
const skipped = /^(script|style|noscript|template)$/;
|
|
13094
13220
|
const hiddenText = (el) => {
|
|
13095
|
-
const
|
|
13096
|
-
acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)
|
|
13097
|
-
});
|
|
13221
|
+
const nodes = el.localName === 'slot' ? el.assignedNodes({ flatten: true }) : (el.shadowRoot || el).childNodes;
|
|
13098
13222
|
let text = '';
|
|
13099
|
-
for (
|
|
13223
|
+
for (const node of nodes) {
|
|
13100
13224
|
if (node.nodeType === 3) text += node.data;
|
|
13225
|
+
else if (node.nodeType === 1 && !skipped.test(node.localName)) text += hiddenText(node);
|
|
13101
13226
|
}
|
|
13102
13227
|
return text;
|
|
13103
13228
|
};
|
|
@@ -13105,7 +13230,8 @@ var FILTER_MATCHING_JS = `(shadowRoots) => {
|
|
|
13105
13230
|
if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;
|
|
13106
13231
|
const rendered = typeof el.innerText === 'string' &&
|
|
13107
13232
|
(typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));
|
|
13108
|
-
|
|
13233
|
+
const text = !rendered ? hiddenText(el) : composed(el) ? flatText(el, Infinity, true) : el.innerText;
|
|
13234
|
+
return text.replace(/\\s+/g, ' ').trim();
|
|
13109
13235
|
};
|
|
13110
13236
|
const passes = (el, filter) => {
|
|
13111
13237
|
if (filter.kind === 'visible') {
|
|
@@ -13455,15 +13581,15 @@ var BODY_ABORTED = "net::ERR_ABORTED";
|
|
|
13455
13581
|
|
|
13456
13582
|
// src/daemon/session/triggeredRequests.ts
|
|
13457
13583
|
var LOCAL_URL_PATTERN = /^(data|blob):/i;
|
|
13458
|
-
var MAX_TRIGGERED_REQUESTS = 50;
|
|
13459
13584
|
function watchTriggeredRequests(store) {
|
|
13460
13585
|
if (!store.activeTelemetry.includes("network")) return () => void 0;
|
|
13461
13586
|
const startedAt = Date.now();
|
|
13462
|
-
const firstFinished = store
|
|
13587
|
+
const firstFinished = requestsFinished(store);
|
|
13463
13588
|
const firstWebSocket = store.websocketConnections.length;
|
|
13464
13589
|
return () => {
|
|
13590
|
+
const firstKept = Math.max(0, firstFinished - store.networkEvictions.requestsDropped);
|
|
13465
13591
|
const started = [
|
|
13466
|
-
...store.networkRequests.slice(
|
|
13592
|
+
...store.networkRequests.slice(firstKept).map((request) => ({ request })),
|
|
13467
13593
|
...[...store.pendingNetworkRequests.values()].map((pending) => ({
|
|
13468
13594
|
request: pending.request,
|
|
13469
13595
|
inFlight: true
|
|
@@ -13479,6 +13605,9 @@ function watchTriggeredRequests(store) {
|
|
|
13479
13605
|
};
|
|
13480
13606
|
};
|
|
13481
13607
|
}
|
|
13608
|
+
function requestsFinished(store) {
|
|
13609
|
+
return store.networkEvictions.requestsDropped + store.networkRequests.length;
|
|
13610
|
+
}
|
|
13482
13611
|
function keepNotableFirst(requests, limit) {
|
|
13483
13612
|
if (requests.length <= limit) return requests;
|
|
13484
13613
|
const notable = requests.filter(isNotableRequest).slice(0, limit);
|
|
@@ -16041,12 +16170,113 @@ function trackStyleSheets(cdp) {
|
|
|
16041
16170
|
function styleSheetHeaders(cdp) {
|
|
16042
16171
|
return headersByConnection.get(cdp)?.values() ?? [];
|
|
16043
16172
|
}
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16173
|
+
var HINTS_BUDGET_MS = 1e3;
|
|
16174
|
+
var RULES_BUDGET_MS = 5e3;
|
|
16175
|
+
var KEEP_ANSWERS_SLOWER_THAN_MS = 300;
|
|
16176
|
+
var KEPT_ANSWER_TTL_MS = 5e3;
|
|
16177
|
+
var MAX_KEPT_ANSWERS = 4;
|
|
16178
|
+
var STYLESHEET_EVENTS = ["CSS.styleSheetAdded", "CSS.styleSheetChanged", "CSS.styleSheetRemoved"];
|
|
16179
|
+
var STYLE_CHANGE_EVENTS = [
|
|
16180
|
+
"CSS.mediaQueryResultChanged",
|
|
16181
|
+
"DOM.attributeModified",
|
|
16182
|
+
"DOM.attributeRemoved",
|
|
16183
|
+
"DOM.inlineStyleInvalidated",
|
|
16184
|
+
"DOM.childNodeInserted",
|
|
16185
|
+
"DOM.childNodeRemoved",
|
|
16186
|
+
"DOM.childNodeCountUpdated",
|
|
16187
|
+
"DOM.pseudoElementAdded",
|
|
16188
|
+
"DOM.pseudoElementRemoved"
|
|
16189
|
+
];
|
|
16190
|
+
var documentStylesByConnection = /* @__PURE__ */ new WeakMap();
|
|
16191
|
+
function documentStyles(cdp) {
|
|
16192
|
+
const existing = documentStylesByConnection.get(cdp);
|
|
16193
|
+
if (existing) return existing;
|
|
16194
|
+
const state = { requests: /* @__PURE__ */ new Map(), slow: false, document: 0 };
|
|
16195
|
+
documentStylesByConnection.set(cdp, state);
|
|
16196
|
+
cdp.on("DOM.documentUpdated", (_params, sessionId) => {
|
|
16197
|
+
if (sessionId) return;
|
|
16198
|
+
state.document++;
|
|
16199
|
+
state.requests.clear();
|
|
16200
|
+
state.running = void 0;
|
|
16201
|
+
state.slow = false;
|
|
16202
|
+
});
|
|
16203
|
+
for (const event of STYLESHEET_EVENTS) {
|
|
16204
|
+
cdp.on(event, (_params, sessionId) => {
|
|
16205
|
+
if (sessionId) return;
|
|
16206
|
+
state.requests.clear();
|
|
16207
|
+
state.slow = false;
|
|
16208
|
+
});
|
|
16209
|
+
}
|
|
16210
|
+
for (const event of STYLE_CHANGE_EVENTS) {
|
|
16211
|
+
cdp.on(event, (_params, sessionId) => {
|
|
16212
|
+
if (!sessionId) state.requests.clear();
|
|
16213
|
+
});
|
|
16214
|
+
}
|
|
16215
|
+
return state;
|
|
16216
|
+
}
|
|
16217
|
+
function resetMatchedStyles(cdp) {
|
|
16218
|
+
documentStylesByConnection.get(cdp)?.requests.clear();
|
|
16219
|
+
}
|
|
16220
|
+
function settleRequest(state, nodeId, request, tookMs) {
|
|
16221
|
+
if (state.running === request.promise) state.running = void 0;
|
|
16222
|
+
if (request.document !== state.document) return;
|
|
16223
|
+
const answered = request.answer !== "failed";
|
|
16224
|
+
if (answered && tookMs <= HINTS_BUDGET_MS) state.slow = false;
|
|
16225
|
+
if (answered && tookMs > KEEP_ANSWERS_SLOWER_THAN_MS) {
|
|
16226
|
+
request.expiresAt = Date.now() + KEPT_ANSWER_TTL_MS;
|
|
16227
|
+
} else if (state.requests.get(nodeId) === request) {
|
|
16228
|
+
state.requests.delete(nodeId);
|
|
16229
|
+
}
|
|
16230
|
+
}
|
|
16231
|
+
function sendMatchedRequest(cdp, state, nodeId) {
|
|
16232
|
+
const sentAt = Date.now();
|
|
16233
|
+
const request = {
|
|
16234
|
+
document: state.document,
|
|
16235
|
+
promise: cdp.send("CSS.getMatchedStylesForNode", { nodeId }).then((response) => response).catch((error) => {
|
|
16236
|
+
log24.debug(`CSS.getMatchedStylesForNode failed: ${String(error)}`);
|
|
16237
|
+
return "failed";
|
|
16238
|
+
})
|
|
16239
|
+
};
|
|
16240
|
+
state.running = request.promise;
|
|
16241
|
+
void request.promise.then((answer) => {
|
|
16242
|
+
request.answer = answer;
|
|
16243
|
+
settleRequest(state, nodeId, request, Date.now() - sentAt);
|
|
16048
16244
|
});
|
|
16049
|
-
|
|
16245
|
+
state.requests.set(nodeId, request);
|
|
16246
|
+
for (const oldest of state.requests.keys()) {
|
|
16247
|
+
if (state.requests.size <= MAX_KEPT_ANSWERS) break;
|
|
16248
|
+
state.requests.delete(oldest);
|
|
16249
|
+
}
|
|
16250
|
+
return request;
|
|
16251
|
+
}
|
|
16252
|
+
function keptRequest(state, nodeId) {
|
|
16253
|
+
const kept = state.requests.get(nodeId);
|
|
16254
|
+
if (!kept) return void 0;
|
|
16255
|
+
state.requests.delete(nodeId);
|
|
16256
|
+
if (kept.expiresAt !== void 0 && kept.expiresAt <= Date.now()) return void 0;
|
|
16257
|
+
state.requests.set(nodeId, kept);
|
|
16258
|
+
return kept;
|
|
16259
|
+
}
|
|
16260
|
+
async function requestWhenFree(cdp, state, nodeId, deadline) {
|
|
16261
|
+
while (state.running) {
|
|
16262
|
+
const kept = state.requests.get(nodeId);
|
|
16263
|
+
if (kept) return kept;
|
|
16264
|
+
if (await raceTimeout(state.running, deadline - Date.now()) === void 0) return void 0;
|
|
16265
|
+
}
|
|
16266
|
+
return state.requests.get(nodeId) ?? sendMatchedRequest(cdp, state, nodeId);
|
|
16267
|
+
}
|
|
16268
|
+
async function matchedStyles(cdp, nodeId, budgetMs, options = {}) {
|
|
16269
|
+
const state = documentStyles(cdp);
|
|
16270
|
+
const kept = keptRequest(state, nodeId);
|
|
16271
|
+
if (kept?.answer) return kept.answer;
|
|
16272
|
+
if (options.skipWhenSlow && state.slow) return "skipped";
|
|
16273
|
+
const deadline = Date.now() + budgetMs;
|
|
16274
|
+
const request = kept ?? await requestWhenFree(cdp, state, nodeId, deadline);
|
|
16275
|
+
if (!request) return "timeout";
|
|
16276
|
+
const answer = await raceTimeout(request.promise, deadline - Date.now());
|
|
16277
|
+
if (answer) return answer;
|
|
16278
|
+
if (request.document === state.document) state.slow = true;
|
|
16279
|
+
return "timeout";
|
|
16050
16280
|
}
|
|
16051
16281
|
function fileName(url) {
|
|
16052
16282
|
const path9 = url.split(/[?#]/)[0] ?? url;
|
|
@@ -16635,7 +16865,7 @@ var HOLDS_FIXED_JS = `(style) =>
|
|
|
16635
16865
|
/transform|filter|perspective/.test(style.willChange) || /paint|layout|strict|content/.test(style.contain)`;
|
|
16636
16866
|
var FIXED_TO_VIEWPORT_JS = `(n) => {
|
|
16637
16867
|
const holdsFixed = ${HOLDS_FIXED_JS};
|
|
16638
|
-
const parentOf =
|
|
16868
|
+
const parentOf = ${FLAT_PARENT_JS};
|
|
16639
16869
|
for (let p = parentOf(n); p && p !== n.ownerDocument.documentElement; p = parentOf(p)) {
|
|
16640
16870
|
if (holdsFixed(p.ownerDocument.defaultView.getComputedStyle(p))) return false;
|
|
16641
16871
|
}
|
|
@@ -16648,7 +16878,7 @@ var ANCESTOR_CLIP_JS = `(node, rect, describe) => {
|
|
|
16648
16878
|
const fixedToViewport = ${FIXED_TO_VIEWPORT_JS};
|
|
16649
16879
|
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
16650
16880
|
const styleOf = (n) => n.ownerDocument.defaultView.getComputedStyle(n);
|
|
16651
|
-
const parentOf =
|
|
16881
|
+
const parentOf = ${FLAT_PARENT_JS};
|
|
16652
16882
|
const doc = node.ownerDocument;
|
|
16653
16883
|
const rootStyle = styleOf(doc.documentElement);
|
|
16654
16884
|
const bodyClips = rootStyle.overflowX !== 'visible' || rootStyle.overflowY !== 'visible';
|
|
@@ -16995,7 +17225,6 @@ function placeInOwnerFrame(raw, owner, mapping) {
|
|
|
16995
17225
|
|
|
16996
17226
|
// src/runtime/dom/layout.ts
|
|
16997
17227
|
var log26 = createLogger("dom");
|
|
16998
|
-
var LAYOUT_ELEMENT_LIMIT = 100;
|
|
16999
17228
|
var CLEAR_OF_SCROLLBAR_JS = `(start, end, edge, overlay) =>
|
|
17000
17229
|
overlay && end > edge - 16 && edge - 16 > start ? edge - 16 : end`;
|
|
17001
17230
|
var VIEWPORT_OVERLAY_JS = `(view, viewport) => {
|
|
@@ -17235,8 +17464,6 @@ function elementLayout(raw, page) {
|
|
|
17235
17464
|
// src/runtime/dom/inspect.ts
|
|
17236
17465
|
var log27 = createLogger("dom");
|
|
17237
17466
|
var stylesEnabled = /* @__PURE__ */ new WeakSet();
|
|
17238
|
-
var HINTS_BUDGET_MS = 1e3;
|
|
17239
|
-
var RULES_BUDGET_MS = 5e3;
|
|
17240
17467
|
var groupCounter2 = 0;
|
|
17241
17468
|
var PICK_MATCH_JS = `function (i) {
|
|
17242
17469
|
if (i !== null) return i;
|
|
@@ -17412,14 +17639,16 @@ async function callOn(cdp, objectId, functionDeclaration, args) {
|
|
|
17412
17639
|
}
|
|
17413
17640
|
async function readSources(cdp, objectId, params, objectGroup) {
|
|
17414
17641
|
const related = await relatedNodes(cdp, objectId, objectGroup);
|
|
17415
|
-
const [raw, measured, styles] = await Promise.all([
|
|
17642
|
+
const [raw, measured, { nodeId, ...styles }] = await Promise.all([
|
|
17416
17643
|
readPage2(cdp, objectId, params),
|
|
17417
17644
|
measure(cdp, params.selector, related.node),
|
|
17418
|
-
readStyles(cdp, related
|
|
17645
|
+
readStyles(cdp, related)
|
|
17419
17646
|
]);
|
|
17647
|
+
const matched = await readMatched(cdp, nodeId, params);
|
|
17420
17648
|
return {
|
|
17421
17649
|
raw,
|
|
17422
17650
|
...styles,
|
|
17651
|
+
...matched && { matched },
|
|
17423
17652
|
fonts: raw.textHolder ? styles.fonts.textHolder : styles.fonts.node,
|
|
17424
17653
|
...measured,
|
|
17425
17654
|
...params.rules && { rules: true },
|
|
@@ -17500,24 +17729,25 @@ async function nodeIdLookup(cdp, related) {
|
|
|
17500
17729
|
);
|
|
17501
17730
|
return (backendNodeId) => backendNodeId === void 0 ? void 0 : ids.get(backendNodeId);
|
|
17502
17731
|
}
|
|
17503
|
-
async function readStyles(cdp, related
|
|
17732
|
+
async function readStyles(cdp, related) {
|
|
17504
17733
|
await enableStyleDomains(cdp);
|
|
17505
17734
|
const nodeIdOf = await nodeIdLookup(cdp, related);
|
|
17506
17735
|
const optionalStyle = (backendNodeId) => backendNodeId === void 0 ? Promise.resolve(void 0) : computedStyle(cdp, nodeIdOf(backendNodeId));
|
|
17507
|
-
const [
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17736
|
+
const [style, parentStyle, holderStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all(
|
|
17737
|
+
[
|
|
17738
|
+
computedStyle(cdp, nodeIdOf(related.node)),
|
|
17739
|
+
optionalStyle(related.parent),
|
|
17740
|
+
optionalStyle(related.textHolder),
|
|
17741
|
+
platformFonts(cdp, nodeIdOf(related.node)),
|
|
17742
|
+
platformFonts(cdp, nodeIdOf(related.fontHolder)),
|
|
17743
|
+
borderBoxSize(cdp, related.node),
|
|
17744
|
+
Promise.all(
|
|
17745
|
+
related.pseudo.map(
|
|
17746
|
+
(p) => pseudoSource(cdp, p.type, p.backendNodeId, nodeIdOf(p.backendNodeId))
|
|
17747
|
+
)
|
|
17518
17748
|
)
|
|
17519
|
-
|
|
17520
|
-
|
|
17749
|
+
]
|
|
17750
|
+
);
|
|
17521
17751
|
return {
|
|
17522
17752
|
style,
|
|
17523
17753
|
...parentStyle && { parentStyle },
|
|
@@ -17525,16 +17755,20 @@ async function readStyles(cdp, related, params) {
|
|
|
17525
17755
|
pseudo,
|
|
17526
17756
|
fonts: { node: nodeFonts, textHolder: holderFonts },
|
|
17527
17757
|
...size && { size },
|
|
17528
|
-
|
|
17758
|
+
nodeId: nodeIdOf(related.node)
|
|
17529
17759
|
};
|
|
17530
17760
|
}
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
if (nodeId === void 0 || skipped) {
|
|
17535
|
-
return void 0;
|
|
17761
|
+
function matchedStylesRead(params) {
|
|
17762
|
+
if (params.rules === true || params.why !== void 0) {
|
|
17763
|
+
return { budgetMs: RULES_BUDGET_MS, skipWhenSlow: false };
|
|
17536
17764
|
}
|
|
17537
|
-
|
|
17765
|
+
if (params.hints === false || params.props !== void 0 || params.all === true) return void 0;
|
|
17766
|
+
return { budgetMs: HINTS_BUDGET_MS, skipWhenSlow: true };
|
|
17767
|
+
}
|
|
17768
|
+
async function readMatched(cdp, nodeId, params) {
|
|
17769
|
+
const read = matchedStylesRead(params);
|
|
17770
|
+
if (nodeId === void 0 || !read) return void 0;
|
|
17771
|
+
return matchedStyles(cdp, nodeId, read.budgetMs, { skipWhenSlow: read.skipWhenSlow });
|
|
17538
17772
|
}
|
|
17539
17773
|
async function pseudoSource(cdp, type, backendNodeId, nodeId) {
|
|
17540
17774
|
const [style, size] = await Promise.all([
|
|
@@ -20381,7 +20615,10 @@ var CLICK_ELEMENT_SCRIPT = `
|
|
|
20381
20615
|
else if (el.closest('[inert]')) obstruction = 'inert (the page made it non-interactive)';
|
|
20382
20616
|
else if (style.pointerEvents === 'none') obstruction = 'not clickable (pointer-events: none)';
|
|
20383
20617
|
else if (!hasSize) obstruction = 'zero-size';
|
|
20384
|
-
else if (!hittable)
|
|
20618
|
+
else if (!hittable) {
|
|
20619
|
+
const collapsed = (${ANCESTOR_CLIP_JS})(el, rect, describe).collapsed;
|
|
20620
|
+
obstruction = collapsed ? 'hidden (' + collapsed + ')' : 'covered by another element' + coveredBy();
|
|
20621
|
+
}
|
|
20385
20622
|
|
|
20386
20623
|
if (action === 'hover') (${REVEAL_SNAPSHOT_JS})(el);
|
|
20387
20624
|
|
|
@@ -22277,6 +22514,13 @@ function webSocketAsRequest2(connection) {
|
|
|
22277
22514
|
webSocket: { frames: connection.frames, ...closedTime !== void 0 && { closedTime } }
|
|
22278
22515
|
};
|
|
22279
22516
|
}
|
|
22517
|
+
function networkEvictionActivity(store) {
|
|
22518
|
+
const { requestsDropped, bodiesEvicted } = store.networkEvictions;
|
|
22519
|
+
return {
|
|
22520
|
+
...requestsDropped > 0 && { networkRequestsDropped: requestsDropped },
|
|
22521
|
+
...bodiesEvicted > 0 && { networkBodiesEvicted: bodiesEvicted }
|
|
22522
|
+
};
|
|
22523
|
+
}
|
|
22280
22524
|
function allNetworkRequests(store) {
|
|
22281
22525
|
return [
|
|
22282
22526
|
...store.networkRequests,
|
|
@@ -22403,6 +22647,7 @@ function createCommandRegistry(store, emulation) {
|
|
|
22403
22647
|
const totalNetwork = allNetwork.length;
|
|
22404
22648
|
const totalConsole = store.consoleMessages.length;
|
|
22405
22649
|
const dropped = store.consoleDropped;
|
|
22650
|
+
const { requestsDropped, bodiesEvicted } = store.networkEvictions;
|
|
22406
22651
|
const networkBounds = calculateSliceBounds(totalNetwork, lastN, offset);
|
|
22407
22652
|
const consoleBounds = calculateSliceBounds(totalConsole, lastN, offset);
|
|
22408
22653
|
const recentNetwork = params.only === "console" ? [] : allNetwork.slice(networkBounds.start, networkBounds.end).map((req) => mapNetworkRequestToPreview(req, params.withHeaders));
|
|
@@ -22423,6 +22668,8 @@ function createCommandRegistry(store, emulation) {
|
|
|
22423
22668
|
totalNetwork,
|
|
22424
22669
|
totalConsole,
|
|
22425
22670
|
...dropped > 0 && { droppedConsole: dropped },
|
|
22671
|
+
...requestsDropped > 0 && { droppedNetwork: requestsDropped },
|
|
22672
|
+
...bodiesEvicted > 0 && { evictedNetworkBodies: bodiesEvicted },
|
|
22426
22673
|
hasMoreNetwork: networkBounds.start > 0,
|
|
22427
22674
|
hasMoreConsole: consoleBounds.start > 0
|
|
22428
22675
|
});
|
|
@@ -22457,12 +22704,13 @@ function createCommandRegistry(store, emulation) {
|
|
|
22457
22704
|
...store.pageCrashedAt === void 0 ? await pageAppearance(cdp) : { crashedAt: store.pageCrashedAt }
|
|
22458
22705
|
},
|
|
22459
22706
|
activeTelemetry: store.activeTelemetry,
|
|
22460
|
-
activity:
|
|
22707
|
+
activity: {
|
|
22461
22708
|
networkRequestsCaptured: store.networkRequests.length,
|
|
22462
22709
|
consoleMessagesCaptured: store.consoleMessages.length,
|
|
22463
|
-
lastNetworkRequestAt: lastNetworkRequest
|
|
22464
|
-
lastConsoleMessageAt: lastConsoleMessage
|
|
22465
|
-
|
|
22710
|
+
...lastNetworkRequest && { lastNetworkRequestAt: lastNetworkRequest.timestamp },
|
|
22711
|
+
...lastConsoleMessage && { lastConsoleMessageAt: lastConsoleMessage.timestamp },
|
|
22712
|
+
...networkEvictionActivity(store)
|
|
22713
|
+
},
|
|
22466
22714
|
navigationId: store.getCurrentNavigationId?.() ?? 0
|
|
22467
22715
|
};
|
|
22468
22716
|
return result;
|
|
@@ -22685,6 +22933,30 @@ function createCommandRegistry(store, emulation) {
|
|
|
22685
22933
|
};
|
|
22686
22934
|
}
|
|
22687
22935
|
|
|
22936
|
+
// src/daemon/session/matchedStylesReset.ts
|
|
22937
|
+
var KEEPS_MATCHED_STYLES = /* @__PURE__ */ new Set([
|
|
22938
|
+
"session_peek",
|
|
22939
|
+
"session_details",
|
|
22940
|
+
"session_status",
|
|
22941
|
+
"session_har_data",
|
|
22942
|
+
"session_network_headers",
|
|
22943
|
+
"dom_frames",
|
|
22944
|
+
"dom_form_discover",
|
|
22945
|
+
"dom_listeners",
|
|
22946
|
+
"dom_layout",
|
|
22947
|
+
"dom_audit",
|
|
22948
|
+
"css_search",
|
|
22949
|
+
"dom_inspect"
|
|
22950
|
+
]);
|
|
22951
|
+
function withMatchedStylesReset(cdp, name, run) {
|
|
22952
|
+
if (KEEPS_MATCHED_STYLES.has(name)) return run();
|
|
22953
|
+
const reset = () => resetMatchedStyles(cdp);
|
|
22954
|
+
reset();
|
|
22955
|
+
const result = run();
|
|
22956
|
+
void result.then(reset, reset);
|
|
22957
|
+
return result;
|
|
22958
|
+
}
|
|
22959
|
+
|
|
22688
22960
|
// src/daemon/session/teardown.ts
|
|
22689
22961
|
var CHROME_EXIT_POLL_MS = 50;
|
|
22690
22962
|
var CHROME_EXIT_POLL_ATTEMPTS = 100;
|
|
@@ -23163,7 +23435,8 @@ var Session = class _Session {
|
|
|
23163
23435
|
* Execute a registered command against this session. After a renderer
|
|
23164
23436
|
* crash only {@link RUN_ON_CRASHED_PAGE} commands run (`bdg cdp` only for
|
|
23165
23437
|
* {@link CRASH_SAFE_CDP} methods); others fail with exit 107 instead of
|
|
23166
|
-
* waiting for a page that cannot answer.
|
|
23438
|
+
* waiting for a page that cannot answer. Commands that may change the page
|
|
23439
|
+
* drop `dom inspect`'s kept matched rules ({@link withMatchedStylesReset}).
|
|
23167
23440
|
*
|
|
23168
23441
|
* @param name - Command name
|
|
23169
23442
|
* @param params - Command parameters
|
|
@@ -23179,7 +23452,14 @@ var Session = class _Session {
|
|
|
23179
23452
|
if (crashedAt !== void 0 && needsPage) {
|
|
23180
23453
|
return Promise.reject(pageCrashedCommandError(crashedAt));
|
|
23181
23454
|
}
|
|
23182
|
-
|
|
23455
|
+
if (!Object.hasOwn(this.registry, name)) {
|
|
23456
|
+
return Promise.reject(
|
|
23457
|
+
new CommandError(unknownSessionCommandMessage(name), {}, EXIT_CODES.INVALID_ARGUMENTS)
|
|
23458
|
+
);
|
|
23459
|
+
}
|
|
23460
|
+
const handler = this.registry[name];
|
|
23461
|
+
const cdp = this.cdp;
|
|
23462
|
+
return withMatchedStylesReset(cdp, name, () => handler(cdp, params));
|
|
23183
23463
|
}
|
|
23184
23464
|
/**
|
|
23185
23465
|
* Summary of this session for start responses.
|
|
@@ -23759,7 +24039,9 @@ var SessionController = class {
|
|
|
23759
24039
|
totals: {
|
|
23760
24040
|
network: data.totalNetwork,
|
|
23761
24041
|
console: data.totalConsole,
|
|
23762
|
-
...data.droppedConsole && { consoleDropped: data.droppedConsole }
|
|
24042
|
+
...data.droppedConsole && { consoleDropped: data.droppedConsole },
|
|
24043
|
+
...data.droppedNetwork && { networkDropped: data.droppedNetwork },
|
|
24044
|
+
...data.evictedNetworkBodies && { networkBodiesEvicted: data.evictedNetworkBodies }
|
|
23763
24045
|
},
|
|
23764
24046
|
currentNavigationId: data.currentNavigationId,
|
|
23765
24047
|
...data.pageCrashedAt !== void 0 && { pageCrashedAt: data.pageCrashedAt },
|