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
|
@@ -74,6 +74,10 @@ export interface SessionPeekData {
|
|
|
74
74
|
totalConsole: number;
|
|
75
75
|
/** Console messages dropped at the limit (the oldest; indices start after them) */
|
|
76
76
|
droppedConsole?: number;
|
|
77
|
+
/** Finished network requests dropped at the request cap (the oldest) */
|
|
78
|
+
droppedNetwork?: number;
|
|
79
|
+
/** Response bodies evicted at the total body budget (the oldest) */
|
|
80
|
+
evictedNetworkBodies?: number;
|
|
77
81
|
/** Whether there are more network items available. */
|
|
78
82
|
hasMoreNetwork?: boolean;
|
|
79
83
|
/** Whether there are more console items available. */
|
|
@@ -447,7 +447,10 @@ export interface InspectResult {
|
|
|
447
447
|
rules?: InspectRule[];
|
|
448
448
|
/** `--why <property>`: one entry, or one per longhand of a shorthand whose sides differ */
|
|
449
449
|
why?: InspectWhy[];
|
|
450
|
-
/**
|
|
451
|
-
|
|
450
|
+
/**
|
|
451
|
+
* The cascade was not read: Chrome took longer than the time allowed, failed,
|
|
452
|
+
* or (hints only) was not asked because it was too slow earlier on this page
|
|
453
|
+
*/
|
|
454
|
+
cascade?: 'timeout' | 'failed' | 'skipped';
|
|
452
455
|
}
|
|
453
456
|
//# sourceMappingURL=inspectTypes.d.ts.map
|
|
@@ -8,8 +8,12 @@ import type { ColorScheme, ViewportSize } from '../../types.js';
|
|
|
8
8
|
* Session activity metrics.
|
|
9
9
|
*/
|
|
10
10
|
export interface SessionActivity {
|
|
11
|
-
/**
|
|
11
|
+
/** Network requests kept (finished ones; the newest at the cap). */
|
|
12
12
|
networkRequestsCaptured: number;
|
|
13
|
+
/** Oldest finished requests dropped at the request cap (left out when none). */
|
|
14
|
+
networkRequestsDropped?: number;
|
|
15
|
+
/** Oldest response bodies evicted at the total body budget (left out when none). */
|
|
16
|
+
networkBodiesEvicted?: number;
|
|
13
17
|
/** Total console messages captured. */
|
|
14
18
|
consoleMessagesCaptured: number;
|
|
15
19
|
/** Timestamp of last network request. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bdg command tree: root command, its options and every registered command.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, type OutputConfiguration } from 'commander';
|
|
5
|
+
/**
|
|
6
|
+
* Build the bdg program with every command registered, without parsing
|
|
7
|
+
* anything. Commander errors throw instead of exiting; the output
|
|
8
|
+
* configuration is set before commands are registered so they inherit it.
|
|
9
|
+
*
|
|
10
|
+
* @param output - Where Commander writes help and errors
|
|
11
|
+
* @returns Root command
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildProgram(output?: OutputConfiguration): Command;
|
|
14
|
+
//# sourceMappingURL=program.d.ts.map
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bdg command tree: root command, its options and every registered command.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Option } from 'commander';
|
|
5
|
+
import { commandRegistry } from './commands.js';
|
|
6
|
+
import { VERSION } from './utils/version.js';
|
|
7
|
+
const CLI_NAME = 'bdg';
|
|
8
|
+
const CLI_DESCRIPTION = 'Browser telemetry via Chrome DevTools Protocol';
|
|
9
|
+
const SESSION_OPTION_FLAGS = '--session <name>';
|
|
10
|
+
const SESSION_OPTION_DESCRIPTION = 'Use a named session (own daemon, Chrome and port) instead of the default one; env: BDG_SESSION';
|
|
11
|
+
/**
|
|
12
|
+
* Make `--debug`, `-q` and `--session` accepted after any subcommand (program
|
|
13
|
+
* options are positional).
|
|
14
|
+
*
|
|
15
|
+
* @param command - Command whose subcommands get the hidden global options
|
|
16
|
+
*/
|
|
17
|
+
function addGlobalOptions(command) {
|
|
18
|
+
for (const sub of command.commands) {
|
|
19
|
+
if (!sub.options.some((option) => option.long === '--debug')) {
|
|
20
|
+
sub.addOption(new Option('--debug', 'Enable debug logging').hideHelp());
|
|
21
|
+
}
|
|
22
|
+
if (!sub.options.some((option) => option.long === '--quiet')) {
|
|
23
|
+
sub.addOption(new Option('-q, --quiet', 'Hide tips and hints').hideHelp());
|
|
24
|
+
}
|
|
25
|
+
if (!sub.options.some((option) => option.long === '--session')) {
|
|
26
|
+
sub.addOption(new Option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION).hideHelp());
|
|
27
|
+
}
|
|
28
|
+
addGlobalOptions(sub);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build the bdg program with every command registered, without parsing
|
|
33
|
+
* anything. Commander errors throw instead of exiting; the output
|
|
34
|
+
* configuration is set before commands are registered so they inherit it.
|
|
35
|
+
*
|
|
36
|
+
* @param output - Where Commander writes help and errors
|
|
37
|
+
* @returns Root command
|
|
38
|
+
*/
|
|
39
|
+
export function buildProgram(output = {}) {
|
|
40
|
+
const program = new Command()
|
|
41
|
+
.name(CLI_NAME)
|
|
42
|
+
.description(CLI_DESCRIPTION)
|
|
43
|
+
.version(VERSION)
|
|
44
|
+
.option('--debug', 'Enable debug logging (verbose output)')
|
|
45
|
+
.option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION)
|
|
46
|
+
.enablePositionalOptions()
|
|
47
|
+
.exitOverride()
|
|
48
|
+
.configureOutput(output);
|
|
49
|
+
commandRegistry.forEach((register) => register(program));
|
|
50
|
+
addGlobalOptions(program);
|
|
51
|
+
return program;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -119,6 +119,29 @@ export declare const FRAME_OFFSET_JS = "(frame) => {\n const rect = frame.getBo
|
|
|
119
119
|
* its bounding box is the box itself, scaled.
|
|
120
120
|
*/
|
|
121
121
|
export declare const SCALES_ONLY_JS = "(style) => {\n if (style.rotate && style.rotate !== 'none') return false;\n if (!style.transform || style.transform === 'none') return true;\n const matrix = /^matrix\\(([^)]*)\\)$/.exec(style.transform);\n if (!matrix) return false;\n const values = matrix[1].split(',').map(parseFloat);\n return Math.abs(values[1]) < 1e-6 && Math.abs(values[2]) < 1e-6;\n}";
|
|
122
|
+
/**
|
|
123
|
+
* Page-side clip of a node by its ancestors in the flat tree (through the
|
|
124
|
+
* slots it is shown in and open shadow roots): the padding boxes
|
|
125
|
+
* ({@link CLIP_BOX_JS}) of those that cut off overflowing content and hold
|
|
126
|
+
* the node in their containing-block chain. An absolutely positioned node
|
|
127
|
+
* skips static ancestors (that are not transformed) up to its containing
|
|
128
|
+
* block, a fixed one is not clipped at all unless a transformed (or
|
|
129
|
+
* filtered, contained, …) ancestor holds it like an absolute one, and
|
|
130
|
+
* inline ancestors and `display: contents` ones have no box to clip with.
|
|
131
|
+
* The root element is left out (its overflow belongs to the viewport), and
|
|
132
|
+
* so is the body's overflow unless the root element's overflow is not
|
|
133
|
+
* `visible` (then the body keeps its own overflow and, e.g. as the page's
|
|
134
|
+
* scroller, clips like any container).
|
|
135
|
+
* Returns the clip, whether overlay scrollbars of the containers show along
|
|
136
|
+
* its right and bottom edges ({@link OVERLAY_SCROLLBARS_JS}), the innermost
|
|
137
|
+
* ancestor cutting off part of `rect` (null when none does), why the
|
|
138
|
+
* innermost clipping ancestor with no area (a collapsed
|
|
139
|
+
* `height: 0; overflow: hidden` accordion) hides it, e.g.
|
|
140
|
+
* `clipped by div#acc: zero height` (null when none has), and whether the
|
|
141
|
+
* node is fixed to the viewport (it or a container in its containing-block
|
|
142
|
+
* chain is `position: fixed`; `fixedBy` is that node).
|
|
143
|
+
*/
|
|
144
|
+
export declare const ANCESTOR_CLIP_JS: string;
|
|
122
145
|
/** Page-side test of a `clip-path` that cuts everything away: `inset()` with percentages leaving no area. */
|
|
123
146
|
export declare const CLIP_PATH_CUTS_ALL_JS = "(clipPath) => {\n const inset = /^inset\\(([^)]*)\\)/.exec(clipPath || '');\n const values = inset ? inset[1].split(' round ')[0].trim().split(/\\s+/) : [];\n if (values.length === 0 || values.some((v) => !/%$/.test(v))) return false;\n const [top, right = top, bottom = top, left = right] = values.map(parseFloat);\n return top + bottom >= 100 || left + right >= 100;\n}";
|
|
124
147
|
/**
|
|
@@ -152,28 +152,30 @@ const HOLDS_FIXED_JS = `(style) =>
|
|
|
152
152
|
/transform|filter|perspective/.test(style.willChange) || /paint|layout|strict|content/.test(style.contain)`;
|
|
153
153
|
/**
|
|
154
154
|
* Page-side test of whether a `position: fixed` node is fixed to its
|
|
155
|
-
* document's viewport (no ancestor holds it,
|
|
155
|
+
* document's viewport (no ancestor in the flat tree holds it,
|
|
156
|
+
* {@link HOLDS_FIXED_JS}).
|
|
156
157
|
*/
|
|
157
158
|
const FIXED_TO_VIEWPORT_JS = `(n) => {
|
|
158
159
|
const holdsFixed = ${HOLDS_FIXED_JS};
|
|
159
|
-
const parentOf =
|
|
160
|
+
const parentOf = ${FLAT_PARENT_JS};
|
|
160
161
|
for (let p = parentOf(n); p && p !== n.ownerDocument.documentElement; p = parentOf(p)) {
|
|
161
162
|
if (holdsFixed(p.ownerDocument.defaultView.getComputedStyle(p))) return false;
|
|
162
163
|
}
|
|
163
164
|
return true;
|
|
164
165
|
}`;
|
|
165
166
|
/**
|
|
166
|
-
* Page-side clip of a node by its ancestors
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* transformed) up to its containing
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
167
|
+
* Page-side clip of a node by its ancestors in the flat tree (through the
|
|
168
|
+
* slots it is shown in and open shadow roots): the padding boxes
|
|
169
|
+
* ({@link CLIP_BOX_JS}) of those that cut off overflowing content and hold
|
|
170
|
+
* the node in their containing-block chain. An absolutely positioned node
|
|
171
|
+
* skips static ancestors (that are not transformed) up to its containing
|
|
172
|
+
* block, a fixed one is not clipped at all unless a transformed (or
|
|
173
|
+
* filtered, contained, …) ancestor holds it like an absolute one, and
|
|
174
|
+
* inline ancestors and `display: contents` ones have no box to clip with.
|
|
175
|
+
* The root element is left out (its overflow belongs to the viewport), and
|
|
176
|
+
* so is the body's overflow unless the root element's overflow is not
|
|
177
|
+
* `visible` (then the body keeps its own overflow and, e.g. as the page's
|
|
178
|
+
* scroller, clips like any container).
|
|
177
179
|
* Returns the clip, whether overlay scrollbars of the containers show along
|
|
178
180
|
* its right and bottom edges ({@link OVERLAY_SCROLLBARS_JS}), the innermost
|
|
179
181
|
* ancestor cutting off part of `rect` (null when none does), why the
|
|
@@ -183,14 +185,14 @@ const FIXED_TO_VIEWPORT_JS = `(n) => {
|
|
|
183
185
|
* node is fixed to the viewport (it or a container in its containing-block
|
|
184
186
|
* chain is `position: fixed`; `fixedBy` is that node).
|
|
185
187
|
*/
|
|
186
|
-
const ANCESTOR_CLIP_JS = `(node, rect, describe) => {
|
|
188
|
+
export const ANCESTOR_CLIP_JS = `(node, rect, describe) => {
|
|
187
189
|
const clipBox = ${CLIP_BOX_JS};
|
|
188
190
|
const overlayScrollbars = ${OVERLAY_SCROLLBARS_JS};
|
|
189
191
|
const holdsFixed = ${HOLDS_FIXED_JS};
|
|
190
192
|
const fixedToViewport = ${FIXED_TO_VIEWPORT_JS};
|
|
191
193
|
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
192
194
|
const styleOf = (n) => n.ownerDocument.defaultView.getComputedStyle(n);
|
|
193
|
-
const parentOf =
|
|
195
|
+
const parentOf = ${FLAT_PARENT_JS};
|
|
194
196
|
const doc = node.ownerDocument;
|
|
195
197
|
const rootStyle = styleOf(doc.documentElement);
|
|
196
198
|
const bodyClips = rootStyle.overflowX !== 'visible' || rootStyle.overflowY !== 'visible';
|
|
@@ -44,12 +44,14 @@ export declare const COMPOSED_JS = "(node) => {\n const composes = (n) => Boole
|
|
|
44
44
|
* `visibility: hidden` are left out, elements that are not inline are set
|
|
45
45
|
* apart by line breaks, as is a `<br>`. Fields and editable regions inside
|
|
46
46
|
* it (inputs, textareas, selects, a contenteditable editor) are skipped, so
|
|
47
|
-
* what a user typed is never read
|
|
48
|
-
*
|
|
47
|
+
* what a user typed is never read, unless `fields` is set: then selects
|
|
48
|
+
* (all their options) and editable regions are read as `innerText` reads
|
|
49
|
+
* them (inputs and textareas it leaves out too). Raw text keeps
|
|
50
|
+
* `innerText`'s collapsed whitespace. The text is cut at `limit` characters, and a
|
|
49
51
|
* part whose text alone passes the limit is read from its text nodes
|
|
50
52
|
* (`textContent`) instead of `innerText`, which would lay out all of it.
|
|
51
53
|
*/
|
|
52
|
-
export declare const FLAT_TEXT_JS = "(el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea
|
|
54
|
+
export declare const FLAT_TEXT_JS = "(el, limit, fields) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea)$/.test(node.localName)) return;\n if (!fields && (node.localName === 'select' || node.isContentEditable)) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n}";
|
|
53
55
|
/**
|
|
54
56
|
* Page-side text of an element as a user sees it: `innerText` for a rendered
|
|
55
57
|
* element (CSS-hidden parts left out, inline elements not split apart), none
|
|
@@ -65,7 +67,7 @@ export declare const FLAT_TEXT_JS = "(el, limit) => {\n const composed = (node)
|
|
|
65
67
|
* a whole page's text, unless `full` is set. Decorations are left out
|
|
66
68
|
* ({@link WITHOUT_DECORATIONS_JS}).
|
|
67
69
|
*/
|
|
68
|
-
export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const flatText = (el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea
|
|
70
|
+
export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const flatText = (el, limit, fields) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea)$/.test(node.localName)) return;\n if (!fields && (node.localName === 'select' || node.isContentEditable)) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const shown = rendered(el);\n const boxless = !shown && el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';\n if (!shown && !boxless) return '';\n if (composed(el)) return withoutDecorations(el, flatText(el, full ? Infinity : 2000));\n if (boxless) return withoutDecorations(el, full ? all : all.slice(0, 2000));\n if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let start = '';\n while (start.length < 1000 && walker.nextNode()) {\n const parent = walker.currentNode.parentElement;\n if (!parent || rendered(parent)) start += walker.currentNode.data;\n }\n return withoutDecorations(el, start);\n}";
|
|
69
71
|
/** Shown instead of a secret field value (the same for every length) */
|
|
70
72
|
export declare const MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
71
73
|
/**
|
|
@@ -84,12 +84,14 @@ export const COMPOSED_JS = `(node) => {
|
|
|
84
84
|
* `visibility: hidden` are left out, elements that are not inline are set
|
|
85
85
|
* apart by line breaks, as is a `<br>`. Fields and editable regions inside
|
|
86
86
|
* it (inputs, textareas, selects, a contenteditable editor) are skipped, so
|
|
87
|
-
* what a user typed is never read
|
|
88
|
-
*
|
|
87
|
+
* what a user typed is never read, unless `fields` is set: then selects
|
|
88
|
+
* (all their options) and editable regions are read as `innerText` reads
|
|
89
|
+
* them (inputs and textareas it leaves out too). Raw text keeps
|
|
90
|
+
* `innerText`'s collapsed whitespace. The text is cut at `limit` characters, and a
|
|
89
91
|
* part whose text alone passes the limit is read from its text nodes
|
|
90
92
|
* (`textContent`) instead of `innerText`, which would lay out all of it.
|
|
91
93
|
*/
|
|
92
|
-
export const FLAT_TEXT_JS = `(el, limit) => {
|
|
94
|
+
export const FLAT_TEXT_JS = `(el, limit, fields) => {
|
|
93
95
|
const composed = ${COMPOSED_JS};
|
|
94
96
|
const view = el.ownerDocument.defaultView;
|
|
95
97
|
let text = '';
|
|
@@ -99,7 +101,8 @@ export const FLAT_TEXT_JS = `(el, limit) => {
|
|
|
99
101
|
if (text.length >= limit) return;
|
|
100
102
|
if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');
|
|
101
103
|
if (node.nodeType !== 1) return;
|
|
102
|
-
if (/^(input|textarea
|
|
104
|
+
if (/^(input|textarea)$/.test(node.localName)) return;
|
|
105
|
+
if (!fields && (node.localName === 'select' || node.isContentEditable)) return;
|
|
103
106
|
const style = view.getComputedStyle(node);
|
|
104
107
|
if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;
|
|
105
108
|
if (node.localName === 'br') text += '\\n';
|
|
@@ -66,6 +66,13 @@ export declare function mapPoint(mapping: FrameMapping, point: LayoutPoint): Lay
|
|
|
66
66
|
* @returns Box in the top-level viewport
|
|
67
67
|
*/
|
|
68
68
|
export declare function mapBox(mapping: FrameMapping, box: LayoutBox): LayoutBox;
|
|
69
|
+
/**
|
|
70
|
+
* Page-side choice of the box to measure: the element's, or its document's
|
|
71
|
+
* root element when the element has no size (hidden, collapsed).
|
|
72
|
+
*/
|
|
73
|
+
export declare const REFERENCE_NODE_FUNCTION = "function () {\n const r = this.getBoundingClientRect();\n return r.width > 0 && r.height > 0 ? this : this.ownerDocument.documentElement;\n}";
|
|
74
|
+
/** Page-side `getBoundingClientRect()` as a plain box */
|
|
75
|
+
export declare const CLIENT_RECT_FUNCTION = "function () { const r = this.getBoundingClientRect(); return { x: r.left, y: r.top, width: r.width, height: r.height }; }";
|
|
69
76
|
/**
|
|
70
77
|
* How the element's frame maps into the top-level viewport.
|
|
71
78
|
*
|
|
@@ -150,12 +150,12 @@ export function mapBox(mapping, box) {
|
|
|
150
150
|
* Page-side choice of the box to measure: the element's, or its document's
|
|
151
151
|
* root element when the element has no size (hidden, collapsed).
|
|
152
152
|
*/
|
|
153
|
-
const REFERENCE_NODE_FUNCTION = `function () {
|
|
153
|
+
export const REFERENCE_NODE_FUNCTION = `function () {
|
|
154
154
|
const r = this.getBoundingClientRect();
|
|
155
155
|
return r.width > 0 && r.height > 0 ? this : this.ownerDocument.documentElement;
|
|
156
156
|
}`;
|
|
157
157
|
/** Page-side `getBoundingClientRect()` as a plain box */
|
|
158
|
-
const CLIENT_RECT_FUNCTION = 'function () { const r = this.getBoundingClientRect(); return { x: r.left, y: r.top, width: r.width, height: r.height }; }';
|
|
158
|
+
export const CLIENT_RECT_FUNCTION = 'function () { const r = this.getBoundingClientRect(); return { x: r.left, y: r.top, width: r.width, height: r.height }; }';
|
|
159
159
|
/**
|
|
160
160
|
* Measure a box of the element's frame both in the frame and through CDP.
|
|
161
161
|
*
|
|
@@ -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
|