browser-debugger-cli 0.13.0 → 0.15.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 +101 -187
- package/README.md +4 -4
- package/dist/commands/cdp.js +1 -0
- package/dist/commands/cleanup.js +3 -0
- 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 +3 -1
- package/dist/commands/dom/eval.js +8 -5
- package/dist/commands/dom/formInteraction.js +1 -1
- package/dist/commands/dom/get.js +25 -7
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- 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.d.ts +1 -1
- package/dist/commands/helpJson.js +4 -4
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/network/list.js +46 -3
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +60 -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 +15 -3
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.js +3 -3
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/connection/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +1 -1
- package/dist/connection/launcher.js +1 -2
- package/dist/constants.d.ts +31 -5
- package/dist/constants.js +37 -5
- package/dist/daemon/SessionController.js +2 -0
- package/dist/daemon/launcher.d.ts +17 -3
- package/dist/daemon/launcher.js +37 -7
- 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 +25 -7
- 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 +8742 -8315
- package/dist/errors/messages.d.ts +31 -0
- package/dist/errors/messages.js +96 -6
- package/dist/index.js +1129 -548
- package/dist/ipc/client.d.ts +6 -1
- package/dist/ipc/client.js +11 -2
- package/dist/ipc/protocol/commands.d.ts +8 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
- package/dist/ipc/session/types.d.ts +5 -1
- package/dist/ipc/transport/index.d.ts +6 -0
- package/dist/ipc/transport/index.js +16 -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 +13 -4
- package/dist/runtime/dom/elementInfo.js +15 -5
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- 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/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +38 -3
- package/dist/session/paths.js +154 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/a11y.d.ts +15 -1
- package/dist/telemetry/a11y.js +83 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +11 -3
- package/dist/telemetry/har/sanitize.d.ts +24 -0
- package/dist/telemetry/har/sanitize.js +138 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
- package/dist/telemetry/har/sanitizeBody.js +168 -0
- 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/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/messages/chrome.d.ts +34 -7
- package/dist/ui/messages/chrome.js +81 -15
- package/dist/ui/messages/commands.d.ts +29 -8
- package/dist/ui/messages/commands.js +36 -8
- package/dist/ui/messages/networkMessages.d.ts +50 -0
- package/dist/ui/messages/networkMessages.js +66 -0
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -0
- package/dist/utils/http.d.ts +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
|
@@ -84,11 +84,12 @@ export declare function frameContextLostError(conn: Pick<CDPConnection, 'send'>,
|
|
|
84
84
|
* @param script - JavaScript expression
|
|
85
85
|
* @param query - Requested frame (index, name/id attribute, or part of the name, id or URL)
|
|
86
86
|
* @param listedIds - Frame id behind each index of the last `dom frames` listing, if any
|
|
87
|
+
* @param full - `--full`: copy the result with every entry
|
|
87
88
|
* @returns Value, type and the frame's URL
|
|
88
89
|
* @throws CommandError (81/83) when the frame is ambiguous or missing, (87)
|
|
89
90
|
* when an index names another frame than when it was listed, (83) when it
|
|
90
91
|
* navigated or was removed while the script ran, else as evaluateScript
|
|
91
92
|
*/
|
|
92
|
-
export declare function evaluateInFrame(page: CDPConnection, wsUrl: string, script: string, query: string, listedIds?: string[]): Promise<DomEvalData>;
|
|
93
|
+
export declare function evaluateInFrame(page: CDPConnection, wsUrl: string, script: string, query: string, listedIds?: string[], full?: boolean): Promise<DomEvalData>;
|
|
93
94
|
export {};
|
|
94
95
|
//# sourceMappingURL=frames.d.ts.map
|
|
@@ -529,12 +529,13 @@ export async function frameContextLostError(conn, page, frame) {
|
|
|
529
529
|
* @param script - JavaScript expression
|
|
530
530
|
* @param query - Requested frame (index, name/id attribute, or part of the name, id or URL)
|
|
531
531
|
* @param listedIds - Frame id behind each index of the last `dom frames` listing, if any
|
|
532
|
+
* @param full - `--full`: copy the result with every entry
|
|
532
533
|
* @returns Value, type and the frame's URL
|
|
533
534
|
* @throws CommandError (81/83) when the frame is ambiguous or missing, (87)
|
|
534
535
|
* when an index names another frame than when it was listed, (83) when it
|
|
535
536
|
* navigated or was removed while the script ran, else as evaluateScript
|
|
536
537
|
*/
|
|
537
|
-
export async function evaluateInFrame(page, wsUrl, script, query, listedIds) {
|
|
538
|
+
export async function evaluateInFrame(page, wsUrl, script, query, listedIds, full = false) {
|
|
538
539
|
return withFrameConnection(page, wsUrl, async (fc) => {
|
|
539
540
|
const { frame, uniqueContextId } = await resolveFrame(fc, query, listedIds);
|
|
540
541
|
try {
|
|
@@ -542,6 +543,7 @@ export async function evaluateInFrame(page, wsUrl, script, query, listedIds) {
|
|
|
542
543
|
...(frame.sessionId && { sessionId: frame.sessionId }),
|
|
543
544
|
uniqueContextId,
|
|
544
545
|
recovery: recoverySender(fc, frame.sessionId),
|
|
546
|
+
full,
|
|
545
547
|
});
|
|
546
548
|
return { ...result, frame: frame.info.url };
|
|
547
549
|
}
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* tree), `dom layout`'s measurement (page position, hidden, covered,
|
|
9
9
|
* offscreen) and, once the nodes are pushed to CDP, `CSS.getComputedStyleForNode`
|
|
10
10
|
* for the element, its layout parent and its `::before`/`::after`,
|
|
11
|
-
* `CSS.getPlatformFontsForNode` for its text and `DOM.getBoxModel`.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `CSS.getPlatformFontsForNode` for its text and `DOM.getBoxModel`. After
|
|
12
|
+
* those, for the hints, `--rules` and `--why`, the matched rules
|
|
13
|
+
* (`CSS.getMatchedStylesForNode`, see {@link matchedStyles}). DOM and CSS are
|
|
14
|
+
* enabled on the first inspect and kept on.
|
|
14
15
|
*/
|
|
15
16
|
import type { CDPConnection } from '../../connection/cdp.js';
|
|
16
17
|
import type { DomInspectCommand } from '../../ipc/protocol/commands.js';
|
|
@@ -25,6 +26,19 @@ import type { InspectResult } from '../../ipc/protocol/inspectTypes.js';
|
|
|
25
26
|
* selector or unknown property, (87) the cached element left the page
|
|
26
27
|
*/
|
|
27
28
|
export declare function inspectElement(cdp: CDPConnection, params: DomInspectCommand): Promise<InspectResult>;
|
|
29
|
+
/**
|
|
30
|
+
* How a request reads the element's matched rules, when hints, `--rules` or
|
|
31
|
+
* `--why` need them: within {@link HINTS_BUDGET_MS} for the default hints,
|
|
32
|
+
* not waited for at all on a document marked slow; within
|
|
33
|
+
* {@link RULES_BUDGET_MS} when asked for explicitly.
|
|
34
|
+
*
|
|
35
|
+
* @param params - Request
|
|
36
|
+
* @returns Budget and whether to skip on a slow document, or undefined when not needed
|
|
37
|
+
*/
|
|
38
|
+
export declare function matchedStylesRead(params: DomInspectCommand): {
|
|
39
|
+
budgetMs: number;
|
|
40
|
+
skipWhenSlow: boolean;
|
|
41
|
+
} | undefined;
|
|
28
42
|
/**
|
|
29
43
|
* Enable DOM and CSS once per connection (kept on: CSS.enable replays every
|
|
30
44
|
* stylesheet, which costs up to a few hundred ms on large sites the first time).
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* tree), `dom layout`'s measurement (page position, hidden, covered,
|
|
9
9
|
* offscreen) and, once the nodes are pushed to CDP, `CSS.getComputedStyleForNode`
|
|
10
10
|
* for the element, its layout parent and its `::before`/`::after`,
|
|
11
|
-
* `CSS.getPlatformFontsForNode` for its text and `DOM.getBoxModel`.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `CSS.getPlatformFontsForNode` for its text and `DOM.getBoxModel`. After
|
|
12
|
+
* those, for the hints, `--rules` and `--why`, the matched rules
|
|
13
|
+
* (`CSS.getMatchedStylesForNode`, see {@link matchedStyles}). DOM and CSS are
|
|
14
|
+
* enabled on the first inspect and kept on.
|
|
14
15
|
*/
|
|
15
16
|
import { CommandError } from '../../errors/index.js';
|
|
16
17
|
import { operationFailedError, unknownCssPropertyError, whyAllPropertyError, } from '../../errors/messages.js';
|
|
@@ -19,7 +20,7 @@ import { selectedProps } from './inspectAllStyles.js';
|
|
|
19
20
|
import { buildCascadeFields } from './inspectCascadeModel.js';
|
|
20
21
|
import { explainUnsetVariables } from './inspectHints.js';
|
|
21
22
|
import { buildInspectResult } from './inspectModel.js';
|
|
22
|
-
import { matchedStyles, sourceLabel, trackStyleSheets } from './inspectRules.js';
|
|
23
|
+
import { HINTS_BUDGET_MS, matchedStyles, RULES_BUDGET_MS, sourceLabel, trackStyleSheets, } from './inspectRules.js';
|
|
23
24
|
import { INSPECT_PAGE_JS, RELATED_NODE_JS, VARIABLE_SETTERS_JS, } from './inspectScripts.js';
|
|
24
25
|
import { DEFAULT_TREE_DEPTH, DEFAULT_TREE_LIMIT } from './inspectTree.js';
|
|
25
26
|
import { inspectLayout } from './layout.js';
|
|
@@ -32,10 +33,6 @@ import { findSimilar } from '../../utils/suggestions.js';
|
|
|
32
33
|
const log = createLogger('dom');
|
|
33
34
|
/** Connections DOM and CSS were enabled on */
|
|
34
35
|
const stylesEnabled = new WeakSet();
|
|
35
|
-
/** Time allowed for the matched rules behind the default hints (large stylesheets take longer) */
|
|
36
|
-
const HINTS_BUDGET_MS = 1000;
|
|
37
|
-
/** Time allowed for them with --rules or --why */
|
|
38
|
-
const RULES_BUDGET_MS = 5000;
|
|
39
36
|
/** Distinguishes the object groups of concurrent calls */
|
|
40
37
|
let groupCounter = 0;
|
|
41
38
|
/**
|
|
@@ -171,7 +168,7 @@ function expandCustomPropertyPatterns(names, style) {
|
|
|
171
168
|
*
|
|
172
169
|
* @param cdp - CDP connection (stylesheet headers for the source labels)
|
|
173
170
|
* @param sources - What was read
|
|
174
|
-
* @returns Cascade fields, or `cascade: 'timeout' | 'failed'` when the rules were not read
|
|
171
|
+
* @returns Cascade fields, or `cascade: 'timeout' | 'failed' | 'skipped'` when the rules were not read
|
|
175
172
|
*/
|
|
176
173
|
function cascadeFields(cdp, sources) {
|
|
177
174
|
if (!sources.matched)
|
|
@@ -305,7 +302,10 @@ async function callOn(cdp, objectId, functionDeclaration, args) {
|
|
|
305
302
|
}
|
|
306
303
|
/**
|
|
307
304
|
* Read everything about the element: the page-side walk, `dom layout`'s
|
|
308
|
-
* measurement and the CDP styles, fonts and box.
|
|
305
|
+
* measurement and the CDP styles, fonts and box, then the matched rules.
|
|
306
|
+
* Chrome answers one request at a time and the rules can take seconds on
|
|
307
|
+
* CSS-heavy pages, so they are asked for last: the other reads do not wait
|
|
308
|
+
* behind them, only their own budget does.
|
|
309
309
|
*
|
|
310
310
|
* @param cdp - CDP connection
|
|
311
311
|
* @param objectId - The element
|
|
@@ -315,14 +315,16 @@ async function callOn(cdp, objectId, functionDeclaration, args) {
|
|
|
315
315
|
*/
|
|
316
316
|
async function readSources(cdp, objectId, params, objectGroup) {
|
|
317
317
|
const related = await relatedNodes(cdp, objectId, objectGroup);
|
|
318
|
-
const [raw, measured, styles] = await Promise.all([
|
|
318
|
+
const [raw, measured, { nodeId, ...styles }] = await Promise.all([
|
|
319
319
|
readPage(cdp, objectId, params),
|
|
320
320
|
measure(cdp, params.selector, related.node),
|
|
321
|
-
readStyles(cdp, related
|
|
321
|
+
readStyles(cdp, related),
|
|
322
322
|
]);
|
|
323
|
+
const matched = await readMatched(cdp, nodeId, params);
|
|
323
324
|
return {
|
|
324
325
|
raw,
|
|
325
326
|
...styles,
|
|
327
|
+
...(matched && { matched }),
|
|
326
328
|
fonts: raw.textHolder ? styles.fonts.textHolder : styles.fonts.node,
|
|
327
329
|
...measured,
|
|
328
330
|
...(params.rules && { rules: true }),
|
|
@@ -458,20 +460,19 @@ async function nodeIdLookup(cdp, related) {
|
|
|
458
460
|
}
|
|
459
461
|
/**
|
|
460
462
|
* Computed styles of the element, its parent and pseudo-elements, the
|
|
461
|
-
* platform fonts of its text
|
|
463
|
+
* platform fonts of its text, its border box size and its node id.
|
|
462
464
|
*
|
|
463
465
|
* @param cdp - CDP connection
|
|
464
466
|
* @param related - Backend node ids
|
|
465
467
|
* @returns CDP styles
|
|
466
468
|
*/
|
|
467
|
-
async function readStyles(cdp, related
|
|
469
|
+
async function readStyles(cdp, related) {
|
|
468
470
|
await enableStyleDomains(cdp);
|
|
469
471
|
const nodeIdOf = await nodeIdLookup(cdp, related);
|
|
470
472
|
const optionalStyle = (backendNodeId) => backendNodeId === undefined
|
|
471
473
|
? Promise.resolve(undefined)
|
|
472
474
|
: computedStyle(cdp, nodeIdOf(backendNodeId));
|
|
473
|
-
const [
|
|
474
|
-
readMatched(cdp, nodeIdOf(related.node), params),
|
|
475
|
+
const [style, parentStyle, holderStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all([
|
|
475
476
|
computedStyle(cdp, nodeIdOf(related.node)),
|
|
476
477
|
optionalStyle(related.parent),
|
|
477
478
|
optionalStyle(related.textHolder),
|
|
@@ -487,26 +488,39 @@ async function readStyles(cdp, related, params) {
|
|
|
487
488
|
pseudo,
|
|
488
489
|
fonts: { node: nodeFonts, textHolder: holderFonts },
|
|
489
490
|
...(size && { size }),
|
|
490
|
-
|
|
491
|
+
nodeId: nodeIdOf(related.node),
|
|
491
492
|
};
|
|
492
493
|
}
|
|
493
494
|
/**
|
|
494
|
-
*
|
|
495
|
-
* within {@link HINTS_BUDGET_MS} for the default hints
|
|
496
|
-
*
|
|
495
|
+
* How a request reads the element's matched rules, when hints, `--rules` or
|
|
496
|
+
* `--why` need them: within {@link HINTS_BUDGET_MS} for the default hints,
|
|
497
|
+
* not waited for at all on a document marked slow; within
|
|
498
|
+
* {@link RULES_BUDGET_MS} when asked for explicitly.
|
|
499
|
+
*
|
|
500
|
+
* @param params - Request
|
|
501
|
+
* @returns Budget and whether to skip on a slow document, or undefined when not needed
|
|
502
|
+
*/
|
|
503
|
+
export function matchedStylesRead(params) {
|
|
504
|
+
if (params.rules === true || params.why !== undefined) {
|
|
505
|
+
return { budgetMs: RULES_BUDGET_MS, skipWhenSlow: false };
|
|
506
|
+
}
|
|
507
|
+
if (params.hints === false || params.props !== undefined || params.all === true)
|
|
508
|
+
return undefined;
|
|
509
|
+
return { budgetMs: HINTS_BUDGET_MS, skipWhenSlow: true };
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* The element's matched rules, read as {@link matchedStylesRead} says.
|
|
497
513
|
*
|
|
498
514
|
* @param cdp - CDP connection
|
|
499
515
|
* @param nodeId - Node id of the element
|
|
500
516
|
* @param params - Request
|
|
501
|
-
* @returns Matched styles,
|
|
517
|
+
* @returns Matched styles or why they are missing, or undefined when not needed (or no node id)
|
|
502
518
|
*/
|
|
503
519
|
async function readMatched(cdp, nodeId, params) {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
if (nodeId === undefined || skipped) {
|
|
520
|
+
const read = matchedStylesRead(params);
|
|
521
|
+
if (nodeId === undefined || !read)
|
|
507
522
|
return undefined;
|
|
508
|
-
}
|
|
509
|
-
return matchedStyles(cdp, nodeId, explicit ? RULES_BUDGET_MS : HINTS_BUDGET_MS);
|
|
523
|
+
return matchedStyles(cdp, nodeId, read.budgetMs, { skipWhenSlow: read.skipWhenSlow });
|
|
510
524
|
}
|
|
511
525
|
/**
|
|
512
526
|
* A pseudo-element's computed styles and size.
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* tree. Pure: every input is plain data, so the rules are tested without a
|
|
6
6
|
* browser.
|
|
7
7
|
*/
|
|
8
|
-
import type { Protocol } from '../../connection/typed-cdp.js';
|
|
9
8
|
import type { ElementLayout } from '../../ipc/protocol/domTypes.js';
|
|
10
9
|
import type { InspectResult, InspectVisibility } from '../../ipc/protocol/inspectTypes.js';
|
|
11
10
|
import { type StyleMap } from './inspectLayoutModel.js';
|
|
12
11
|
import { type PlatformFont, type PseudoSource } from './inspectPaintModel.js';
|
|
12
|
+
import type { MatchedStyles } from './inspectRules.js';
|
|
13
13
|
import type { RawInspect } from './inspectScripts.js';
|
|
14
14
|
/** What one inspect read */
|
|
15
15
|
export interface InspectSources {
|
|
@@ -29,8 +29,8 @@ export interface InspectSources {
|
|
|
29
29
|
/** The element as `dom layout` measures it */
|
|
30
30
|
layout?: ElementLayout;
|
|
31
31
|
colorScheme?: 'light' | 'dark';
|
|
32
|
-
/** Matched rules for the cascade fields; `timeout` or `
|
|
33
|
-
matched?:
|
|
32
|
+
/** Matched rules for the cascade fields; `timeout`, `failed` or `skipped` when they were not read */
|
|
33
|
+
matched?: MatchedStyles;
|
|
34
34
|
/** `--rules` was asked for */
|
|
35
35
|
rules?: boolean;
|
|
36
36
|
/** `--why` property */
|
|
@@ -22,16 +22,42 @@ export declare function trackStyleSheets(cdp: CDPConnection): void;
|
|
|
22
22
|
* @returns Headers
|
|
23
23
|
*/
|
|
24
24
|
export declare function styleSheetHeaders(cdp: CDPConnection): Iterable<Protocol.CSS.CSSStyleSheetHeader>;
|
|
25
|
+
/** Time allowed for the matched rules behind the default hints; a read within it clears the slow mark */
|
|
26
|
+
export declare const HINTS_BUDGET_MS = 1000;
|
|
27
|
+
/** Time allowed for them with --rules or --why */
|
|
28
|
+
export declare const RULES_BUDGET_MS = 5000;
|
|
29
|
+
/** Matched styles of an element, or why they are missing */
|
|
30
|
+
export type MatchedStyles = Protocol.CSS.GetMatchedStylesForNodeResponse | 'timeout' | 'failed' | 'skipped';
|
|
31
|
+
/**
|
|
32
|
+
* Forget the kept answers of a connection (requests still running stay
|
|
33
|
+
* shared). For commands that may change the page in ways CDP reports no
|
|
34
|
+
* event for: clicks, typing, hovering, scripts, emulation.
|
|
35
|
+
*
|
|
36
|
+
* @param cdp - CDP connection
|
|
37
|
+
*/
|
|
38
|
+
export declare function resetMatchedStyles(cdp: CDPConnection): void;
|
|
25
39
|
/**
|
|
26
40
|
* The rules that match an element, with its inline style and what its
|
|
27
|
-
* ancestors pass down, or why they are missing.
|
|
41
|
+
* ancestors pass down, or why they are missing. A request still running for
|
|
42
|
+
* the element is shared; a slow answer (over {@link KEEP_ANSWERS_SLOWER_THAN_MS})
|
|
43
|
+
* is reused for {@link KEPT_ANSWER_TTL_MS} unless the document, its
|
|
44
|
+
* stylesheets or its DOM change or a command may have changed the page.
|
|
45
|
+
* Another element's request is sent only after the one Chrome is working on,
|
|
46
|
+
* within the budget. When the request this call sent or shared outlasts the
|
|
47
|
+
* budget, the document is marked slow: `skipWhenSlow` reads then return
|
|
48
|
+
* `skipped` at once (unless the answer is kept) until a read is fast again,
|
|
49
|
+
* a stylesheet changes or the page navigates.
|
|
28
50
|
*
|
|
29
51
|
* @param cdp - CDP connection
|
|
30
52
|
* @param nodeId - Node id of the element
|
|
31
53
|
* @param budgetMs - Time allowed
|
|
32
|
-
* @
|
|
54
|
+
* @param options - `skipWhenSlow`: do not wait on a document marked slow (the default hints)
|
|
55
|
+
* @returns Matched styles, `timeout` (longer than the budget), `failed` (CDP error)
|
|
56
|
+
* or `skipped` (slow document)
|
|
33
57
|
*/
|
|
34
|
-
export declare function matchedStyles(cdp: CDPConnection, nodeId: number, budgetMs: number
|
|
58
|
+
export declare function matchedStyles(cdp: CDPConnection, nodeId: number, budgetMs: number, options?: {
|
|
59
|
+
skipWhenSlow?: boolean;
|
|
60
|
+
}): Promise<MatchedStyles>;
|
|
35
61
|
/**
|
|
36
62
|
* Where a declaration comes from, for people: the selector and the file
|
|
37
63
|
* with its line (and column, for single-line minified files), or what kind
|
|
@@ -37,24 +37,218 @@ export function trackStyleSheets(cdp) {
|
|
|
37
37
|
export function styleSheetHeaders(cdp) {
|
|
38
38
|
return headersByConnection.get(cdp)?.values() ?? [];
|
|
39
39
|
}
|
|
40
|
+
/** Time allowed for the matched rules behind the default hints; a read within it clears the slow mark */
|
|
41
|
+
export const HINTS_BUDGET_MS = 1000;
|
|
42
|
+
/** Time allowed for them with --rules or --why */
|
|
43
|
+
export const RULES_BUDGET_MS = 5000;
|
|
44
|
+
/** Answers that came faster are not kept: reading again is cheap and always current */
|
|
45
|
+
const KEEP_ANSWERS_SLOWER_THAN_MS = 300;
|
|
46
|
+
/**
|
|
47
|
+
* How long a kept answer is reused. Page state such as `:checked`, `:hover`
|
|
48
|
+
* or `:focus` changes without any CDP event, so answers are kept only briefly
|
|
49
|
+
* (and dropped by every command that may change the page, see {@link resetMatchedStyles}).
|
|
50
|
+
*/
|
|
51
|
+
const KEPT_ANSWER_TTL_MS = 5000;
|
|
52
|
+
/** Answers kept per document (one can be several MB on CSS-heavy pages) */
|
|
53
|
+
const MAX_KEPT_ANSWERS = 4;
|
|
54
|
+
/** Stylesheet events: kept answers are dropped and the slow mark cleared */
|
|
55
|
+
const STYLESHEET_EVENTS = ['CSS.styleSheetAdded', 'CSS.styleSheetChanged', 'CSS.styleSheetRemoved'];
|
|
56
|
+
/** Other events after which kept answers may be stale */
|
|
57
|
+
const STYLE_CHANGE_EVENTS = [
|
|
58
|
+
'CSS.mediaQueryResultChanged',
|
|
59
|
+
'DOM.attributeModified',
|
|
60
|
+
'DOM.attributeRemoved',
|
|
61
|
+
'DOM.inlineStyleInvalidated',
|
|
62
|
+
'DOM.childNodeInserted',
|
|
63
|
+
'DOM.childNodeRemoved',
|
|
64
|
+
'DOM.childNodeCountUpdated',
|
|
65
|
+
'DOM.pseudoElementAdded',
|
|
66
|
+
'DOM.pseudoElementRemoved',
|
|
67
|
+
];
|
|
68
|
+
/** Matched-styles state per connection */
|
|
69
|
+
const documentStylesByConnection = new WeakMap();
|
|
70
|
+
/**
|
|
71
|
+
* The matched-styles state of a connection, tracked from its first use: a
|
|
72
|
+
* new document (`DOM.documentUpdated`) starts afresh (a request still running
|
|
73
|
+
* for the old one no longer holds back new ones), stylesheet changes drop the
|
|
74
|
+
* kept answers and the slow mark, style and DOM changes the kept answers.
|
|
75
|
+
* Events of other sessions (iframes) are ignored.
|
|
76
|
+
*
|
|
77
|
+
* @param cdp - CDP connection
|
|
78
|
+
* @returns State
|
|
79
|
+
*/
|
|
80
|
+
function documentStyles(cdp) {
|
|
81
|
+
const existing = documentStylesByConnection.get(cdp);
|
|
82
|
+
if (existing)
|
|
83
|
+
return existing;
|
|
84
|
+
const state = { requests: new Map(), slow: false, document: 0 };
|
|
85
|
+
documentStylesByConnection.set(cdp, state);
|
|
86
|
+
cdp.on('DOM.documentUpdated', (_params, sessionId) => {
|
|
87
|
+
if (sessionId)
|
|
88
|
+
return;
|
|
89
|
+
state.document++;
|
|
90
|
+
state.requests.clear();
|
|
91
|
+
state.running = undefined;
|
|
92
|
+
state.slow = false;
|
|
93
|
+
});
|
|
94
|
+
for (const event of STYLESHEET_EVENTS) {
|
|
95
|
+
cdp.on(event, (_params, sessionId) => {
|
|
96
|
+
if (sessionId)
|
|
97
|
+
return;
|
|
98
|
+
state.requests.clear();
|
|
99
|
+
state.slow = false;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
for (const event of STYLE_CHANGE_EVENTS) {
|
|
103
|
+
cdp.on(event, (_params, sessionId) => {
|
|
104
|
+
if (!sessionId)
|
|
105
|
+
state.requests.clear();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Forget the kept answers of a connection (requests still running stay
|
|
112
|
+
* shared). For commands that may change the page in ways CDP reports no
|
|
113
|
+
* event for: clicks, typing, hovering, scripts, emulation.
|
|
114
|
+
*
|
|
115
|
+
* @param cdp - CDP connection
|
|
116
|
+
*/
|
|
117
|
+
export function resetMatchedStyles(cdp) {
|
|
118
|
+
documentStylesByConnection.get(cdp)?.requests.clear();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record an answer: a fast one clears the slow mark and is dropped, a slow
|
|
122
|
+
* one is kept for {@link KEPT_ANSWER_TTL_MS}, a failed one is dropped.
|
|
123
|
+
*
|
|
124
|
+
* @param state - Document state
|
|
125
|
+
* @param nodeId - Node id of the element
|
|
126
|
+
* @param request - The request
|
|
127
|
+
* @param tookMs - How long Chrome took
|
|
128
|
+
*/
|
|
129
|
+
function settleRequest(state, nodeId, request, tookMs) {
|
|
130
|
+
if (state.running === request.promise)
|
|
131
|
+
state.running = undefined;
|
|
132
|
+
if (request.document !== state.document)
|
|
133
|
+
return;
|
|
134
|
+
const answered = request.answer !== 'failed';
|
|
135
|
+
if (answered && tookMs <= HINTS_BUDGET_MS)
|
|
136
|
+
state.slow = false;
|
|
137
|
+
if (answered && tookMs > KEEP_ANSWERS_SLOWER_THAN_MS) {
|
|
138
|
+
request.expiresAt = Date.now() + KEPT_ANSWER_TTL_MS;
|
|
139
|
+
}
|
|
140
|
+
else if (state.requests.get(nodeId) === request) {
|
|
141
|
+
state.requests.delete(nodeId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Send a matched-styles request and share it while it runs, dropping the
|
|
146
|
+
* least recently used entries beyond {@link MAX_KEPT_ANSWERS}.
|
|
147
|
+
*
|
|
148
|
+
* @param cdp - CDP connection
|
|
149
|
+
* @param state - Document state
|
|
150
|
+
* @param nodeId - Node id of the element
|
|
151
|
+
* @returns The request
|
|
152
|
+
*/
|
|
153
|
+
function sendMatchedRequest(cdp, state, nodeId) {
|
|
154
|
+
const sentAt = Date.now();
|
|
155
|
+
const request = {
|
|
156
|
+
document: state.document,
|
|
157
|
+
promise: cdp
|
|
158
|
+
.send('CSS.getMatchedStylesForNode', { nodeId })
|
|
159
|
+
.then((response) => response)
|
|
160
|
+
.catch((error) => {
|
|
161
|
+
log.debug(`CSS.getMatchedStylesForNode failed: ${String(error)}`);
|
|
162
|
+
return 'failed';
|
|
163
|
+
}),
|
|
164
|
+
};
|
|
165
|
+
state.running = request.promise;
|
|
166
|
+
void request.promise.then((answer) => {
|
|
167
|
+
request.answer = answer;
|
|
168
|
+
settleRequest(state, nodeId, request, Date.now() - sentAt);
|
|
169
|
+
});
|
|
170
|
+
state.requests.set(nodeId, request);
|
|
171
|
+
for (const oldest of state.requests.keys()) {
|
|
172
|
+
if (state.requests.size <= MAX_KEPT_ANSWERS)
|
|
173
|
+
break;
|
|
174
|
+
state.requests.delete(oldest);
|
|
175
|
+
}
|
|
176
|
+
return request;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The element's request still running or kept answer, unless expired;
|
|
180
|
+
* marked as the most recently used.
|
|
181
|
+
*
|
|
182
|
+
* @param state - Document state
|
|
183
|
+
* @param nodeId - Node id of the element
|
|
184
|
+
* @returns The request, or undefined
|
|
185
|
+
*/
|
|
186
|
+
function keptRequest(state, nodeId) {
|
|
187
|
+
const kept = state.requests.get(nodeId);
|
|
188
|
+
if (!kept)
|
|
189
|
+
return undefined;
|
|
190
|
+
state.requests.delete(nodeId);
|
|
191
|
+
if (kept.expiresAt !== undefined && kept.expiresAt <= Date.now())
|
|
192
|
+
return undefined;
|
|
193
|
+
state.requests.set(nodeId, kept);
|
|
194
|
+
return kept;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The element's request, sent once Chrome has answered the one it is
|
|
198
|
+
* working on (another element's, or this one's sent by a concurrent call).
|
|
199
|
+
*
|
|
200
|
+
* @param cdp - CDP connection
|
|
201
|
+
* @param state - Document state
|
|
202
|
+
* @param nodeId - Node id of the element
|
|
203
|
+
* @param deadline - When to give up waiting
|
|
204
|
+
* @returns The request, or undefined when the running one outlasted the deadline
|
|
205
|
+
*/
|
|
206
|
+
async function requestWhenFree(cdp, state, nodeId, deadline) {
|
|
207
|
+
while (state.running) {
|
|
208
|
+
const kept = state.requests.get(nodeId);
|
|
209
|
+
if (kept)
|
|
210
|
+
return kept;
|
|
211
|
+
if ((await raceTimeout(state.running, deadline - Date.now())) === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
return state.requests.get(nodeId) ?? sendMatchedRequest(cdp, state, nodeId);
|
|
215
|
+
}
|
|
40
216
|
/**
|
|
41
217
|
* The rules that match an element, with its inline style and what its
|
|
42
|
-
* ancestors pass down, or why they are missing.
|
|
218
|
+
* ancestors pass down, or why they are missing. A request still running for
|
|
219
|
+
* the element is shared; a slow answer (over {@link KEEP_ANSWERS_SLOWER_THAN_MS})
|
|
220
|
+
* is reused for {@link KEPT_ANSWER_TTL_MS} unless the document, its
|
|
221
|
+
* stylesheets or its DOM change or a command may have changed the page.
|
|
222
|
+
* Another element's request is sent only after the one Chrome is working on,
|
|
223
|
+
* within the budget. When the request this call sent or shared outlasts the
|
|
224
|
+
* budget, the document is marked slow: `skipWhenSlow` reads then return
|
|
225
|
+
* `skipped` at once (unless the answer is kept) until a read is fast again,
|
|
226
|
+
* a stylesheet changes or the page navigates.
|
|
43
227
|
*
|
|
44
228
|
* @param cdp - CDP connection
|
|
45
229
|
* @param nodeId - Node id of the element
|
|
46
230
|
* @param budgetMs - Time allowed
|
|
47
|
-
* @
|
|
231
|
+
* @param options - `skipWhenSlow`: do not wait on a document marked slow (the default hints)
|
|
232
|
+
* @returns Matched styles, `timeout` (longer than the budget), `failed` (CDP error)
|
|
233
|
+
* or `skipped` (slow document)
|
|
48
234
|
*/
|
|
49
|
-
export async function matchedStyles(cdp, nodeId, budgetMs) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
return '
|
|
56
|
-
|
|
57
|
-
|
|
235
|
+
export async function matchedStyles(cdp, nodeId, budgetMs, options = {}) {
|
|
236
|
+
const state = documentStyles(cdp);
|
|
237
|
+
const kept = keptRequest(state, nodeId);
|
|
238
|
+
if (kept?.answer)
|
|
239
|
+
return kept.answer;
|
|
240
|
+
if (options.skipWhenSlow && state.slow)
|
|
241
|
+
return 'skipped';
|
|
242
|
+
const deadline = Date.now() + budgetMs;
|
|
243
|
+
const request = kept ?? (await requestWhenFree(cdp, state, nodeId, deadline));
|
|
244
|
+
if (!request)
|
|
245
|
+
return 'timeout';
|
|
246
|
+
const answer = await raceTimeout(request.promise, deadline - Date.now());
|
|
247
|
+
if (answer)
|
|
248
|
+
return answer;
|
|
249
|
+
if (request.document === state.document)
|
|
250
|
+
state.slow = true;
|
|
251
|
+
return 'timeout';
|
|
58
252
|
}
|
|
59
253
|
/**
|
|
60
254
|
* File name of a URL, without query or hash; the host for a site's root.
|
|
@@ -11,8 +11,6 @@ import type { CDPConnection } from '../../connection/cdp.js';
|
|
|
11
11
|
import type { DomLayoutCommand } from '../../ipc/protocol/commands.js';
|
|
12
12
|
import type { LayoutComputedStyle, LayoutResult, PageLayout } from '../../ipc/protocol/domTypes.js';
|
|
13
13
|
import { type ElementGeometry } from './elementGeometry.js';
|
|
14
|
-
/** Matches measured per command (the rest are counted as omitted) */
|
|
15
|
-
export declare const LAYOUT_ELEMENT_LIMIT = 100;
|
|
16
14
|
/**
|
|
17
15
|
* Page-side end of a visible span kept clear of an overlay scrollbar along
|
|
18
16
|
* an edge (16 CSS px wide): overlay scrollbars (macOS, mobile) show for about a
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* shows. The measurements are classified outside the page
|
|
8
8
|
* ({@link classifyViewportPosition}).
|
|
9
9
|
*/
|
|
10
|
+
import { LAYOUT_ELEMENT_LIMIT } from '../../constants.js';
|
|
10
11
|
import { CommandError } from '../../errors/index.js';
|
|
11
12
|
import { operationFailedError } from '../../errors/messages.js';
|
|
12
13
|
import { ELEMENT_GEOMETRY_JS, FRAME_OFFSET_JS, VIEWPORT_SIZE_JS, classifyViewportPosition, } from './elementGeometry.js';
|
|
@@ -20,8 +21,6 @@ import { createLogger } from '../../ui/logging/index.js';
|
|
|
20
21
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
21
22
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
22
23
|
const log = createLogger('dom');
|
|
23
|
-
/** Matches measured per command (the rest are counted as omitted) */
|
|
24
|
-
export const LAYOUT_ELEMENT_LIMIT = 100;
|
|
25
24
|
/**
|
|
26
25
|
* Page-side end of a visible span kept clear of an overlay scrollbar along
|
|
27
26
|
* an edge (16 CSS px wide): overlay scrollbars (macOS, mobile) show for about a
|
|
@@ -107,7 +107,10 @@ export declare const FILL_READ_BACK_SCRIPT: string;
|
|
|
107
107
|
* dialog or bubble can swallow input while the page looks normal.
|
|
108
108
|
* Slotted text hit-tests as its shadow host, so an element in a shadow root
|
|
109
109
|
* that shows slotted content (a button labelled through a `<slot>`) is
|
|
110
|
-
* topmost where its host is hit.
|
|
110
|
+
* topmost where its host is hit. An element that is not topmost because an
|
|
111
|
+
* ancestor in the flat tree clips it away (a collapsed
|
|
112
|
+
* `height: 0; overflow: hidden` accordion, {@link ANCESTOR_CLIP_JS}) is
|
|
113
|
+
* reported as hidden by it, not as covered.
|
|
111
114
|
*/
|
|
112
115
|
export declare const CLICK_ELEMENT_SCRIPT: string;
|
|
113
116
|
/**
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { FILL_REFUSALS, LABEL_WITHOUT_CONTROL, NAME_QUERY_PLACEHOLDER, VIA_LABEL_SUFFIX, } from '../../errors/messages.js';
|
|
9
9
|
import { REVEAL_SNAPSHOT_JS } from './actionEffectsScripts.js';
|
|
10
|
+
import { ANCESTOR_CLIP_JS } from './elementGeometry.js';
|
|
10
11
|
import { DISABLED_CAUSE_JS, ELEMENT_DESCRIPTION_JS, ELEMENT_IDENTITY_JS, } from './elementInfo.js';
|
|
11
12
|
import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS } from './targetNode.js';
|
|
12
13
|
/**
|
|
@@ -484,7 +485,10 @@ export const FILL_READ_BACK_SCRIPT = `(() => {
|
|
|
484
485
|
* dialog or bubble can swallow input while the page looks normal.
|
|
485
486
|
* Slotted text hit-tests as its shadow host, so an element in a shadow root
|
|
486
487
|
* that shows slotted content (a button labelled through a `<slot>`) is
|
|
487
|
-
* topmost where its host is hit.
|
|
488
|
+
* topmost where its host is hit. An element that is not topmost because an
|
|
489
|
+
* ancestor in the flat tree clips it away (a collapsed
|
|
490
|
+
* `height: 0; overflow: hidden` accordion, {@link ANCESTOR_CLIP_JS}) is
|
|
491
|
+
* reported as hidden by it, not as covered.
|
|
488
492
|
*/
|
|
489
493
|
export const CLICK_ELEMENT_SCRIPT = `
|
|
490
494
|
(function(selector, parts, index, action) {
|
|
@@ -639,7 +643,10 @@ export const CLICK_ELEMENT_SCRIPT = `
|
|
|
639
643
|
else if (el.closest('[inert]')) obstruction = 'inert (the page made it non-interactive)';
|
|
640
644
|
else if (style.pointerEvents === 'none') obstruction = 'not clickable (pointer-events: none)';
|
|
641
645
|
else if (!hasSize) obstruction = 'zero-size';
|
|
642
|
-
else if (!hittable)
|
|
646
|
+
else if (!hittable) {
|
|
647
|
+
const collapsed = (${ANCESTOR_CLIP_JS})(el, rect, describe).collapsed;
|
|
648
|
+
obstruction = collapsed ? 'hidden (' + collapsed + ')' : 'covered by another element' + coveredBy();
|
|
649
|
+
}
|
|
643
650
|
|
|
644
651
|
if (action === 'hover') (${REVEAL_SNAPSHOT_JS})(el);
|
|
645
652
|
|