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.
Files changed (112) hide show
  1. package/.claude/skills/bdg/SKILL.md +100 -186
  2. package/README.md +4 -4
  3. package/dist/commands/console.js +5 -1
  4. package/dist/commands/dom/a11y.d.ts +1 -1
  5. package/dist/commands/dom/a11y.js +20 -20
  6. package/dist/commands/dom/eval.d.ts +2 -1
  7. package/dist/commands/dom/eval.js +21 -3
  8. package/dist/commands/dom/formInteraction.js +1 -1
  9. package/dist/commands/dom/get.js +25 -7
  10. package/dist/commands/dom/index.js +7 -2
  11. package/dist/commands/dom/query.d.ts +2 -1
  12. package/dist/commands/dom/query.js +5 -3
  13. package/dist/commands/dom/screenshot.js +1 -0
  14. package/dist/commands/helpJson.js +1 -1
  15. package/dist/commands/network/list.js +46 -3
  16. package/dist/commands/optionBehaviors.d.ts +25 -2
  17. package/dist/commands/optionBehaviors.js +55 -42
  18. package/dist/commands/peek.js +3 -0
  19. package/dist/commands/shared/CommandRunner.js +13 -13
  20. package/dist/commands/shared/daemonErrorHandler.js +2 -2
  21. package/dist/commands/shared/dataFetcher.d.ts +4 -2
  22. package/dist/commands/shared/dataFetcher.js +11 -3
  23. package/dist/commands/shared/handleValidationError.js +3 -3
  24. package/dist/commands/shared/optionTypes.d.ts +14 -3
  25. package/dist/commands/shared/startHelpers.js +3 -3
  26. package/dist/connection/chromeIdentity.d.ts +8 -2
  27. package/dist/connection/chromeIdentity.js +85 -13
  28. package/dist/constants.d.ts +29 -1
  29. package/dist/constants.js +35 -1
  30. package/dist/daemon/SessionController.js +2 -0
  31. package/dist/daemon/session/Session.d.ts +2 -1
  32. package/dist/daemon/session/Session.js +10 -2
  33. package/dist/daemon/session/TelemetryStore.d.ts +7 -0
  34. package/dist/daemon/session/TelemetryStore.js +6 -0
  35. package/dist/daemon/session/commandRegistry.js +23 -5
  36. package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
  37. package/dist/daemon/session/matchedStylesReset.js +46 -0
  38. package/dist/daemon/session/plugins.js +1 -0
  39. package/dist/daemon/session/triggeredRequests.d.ts +0 -5
  40. package/dist/daemon/session/triggeredRequests.js +13 -7
  41. package/dist/daemon.js +742 -460
  42. package/dist/errors/messages.d.ts +8 -0
  43. package/dist/errors/messages.js +10 -0
  44. package/dist/index.js +710 -518
  45. package/dist/ipc/protocol/commands.d.ts +4 -0
  46. package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
  47. package/dist/ipc/session/types.d.ts +5 -1
  48. package/dist/program.d.ts +14 -0
  49. package/dist/program.js +53 -0
  50. package/dist/runtime/dom/elementGeometry.d.ts +23 -0
  51. package/dist/runtime/dom/elementGeometry.js +17 -15
  52. package/dist/runtime/dom/elementInfo.d.ts +6 -4
  53. package/dist/runtime/dom/elementInfo.js +7 -4
  54. package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
  55. package/dist/runtime/dom/frameScopedConnection.js +2 -2
  56. package/dist/runtime/dom/inspect.d.ts +17 -3
  57. package/dist/runtime/dom/inspect.js +40 -26
  58. package/dist/runtime/dom/inspectModel.d.ts +3 -3
  59. package/dist/runtime/dom/inspectRules.d.ts +29 -3
  60. package/dist/runtime/dom/inspectRules.js +205 -11
  61. package/dist/runtime/dom/layout.d.ts +0 -2
  62. package/dist/runtime/dom/layout.js +1 -2
  63. package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
  64. package/dist/runtime/dom/reactEventHelpers.js +9 -2
  65. package/dist/runtime/dom/targetNode.d.ts +10 -6
  66. package/dist/runtime/dom/targetNode.js +15 -8
  67. package/dist/telemetry/a11y.d.ts +15 -1
  68. package/dist/telemetry/a11y.js +83 -0
  69. package/dist/telemetry/har/builder.js +1 -1
  70. package/dist/telemetry/network.d.ts +13 -16
  71. package/dist/telemetry/network.js +30 -52
  72. package/dist/telemetry/networkRetention.d.ts +83 -0
  73. package/dist/telemetry/networkRetention.js +117 -0
  74. package/dist/types.d.ts +26 -0
  75. package/dist/ui/OutputBuilder.d.ts +10 -0
  76. package/dist/ui/OutputBuilder.js +12 -0
  77. package/dist/ui/formatters/a11y.d.ts +5 -7
  78. package/dist/ui/formatters/a11y.js +7 -61
  79. package/dist/ui/formatters/console/chronological.js +4 -4
  80. package/dist/ui/formatters/console/follow.d.ts +4 -2
  81. package/dist/ui/formatters/console/follow.js +6 -3
  82. package/dist/ui/formatters/console/json.d.ts +3 -6
  83. package/dist/ui/formatters/console/json.js +9 -13
  84. package/dist/ui/formatters/console/shared.d.ts +17 -2
  85. package/dist/ui/formatters/console/shared.js +17 -0
  86. package/dist/ui/formatters/console/summarize.d.ts +2 -2
  87. package/dist/ui/formatters/console/summarize.js +22 -7
  88. package/dist/ui/formatters/console.d.ts +1 -1
  89. package/dist/ui/formatters/console.js +1 -5
  90. package/dist/ui/formatters/details.js +1 -1
  91. package/dist/ui/formatters/dom.d.ts +13 -4
  92. package/dist/ui/formatters/dom.js +25 -7
  93. package/dist/ui/formatters/layout.js +2 -1
  94. package/dist/ui/formatters/longValues.d.ts +14 -0
  95. package/dist/ui/formatters/longValues.js +23 -0
  96. package/dist/ui/formatters/networkList.d.ts +8 -2
  97. package/dist/ui/formatters/networkList.js +11 -2
  98. package/dist/ui/formatters/preview.d.ts +4 -1
  99. package/dist/ui/formatters/preview.js +55 -13
  100. package/dist/ui/formatters/status.js +7 -0
  101. package/dist/ui/formatters/triggeredRequests.js +2 -1
  102. package/dist/ui/messages/chrome.d.ts +20 -1
  103. package/dist/ui/messages/chrome.js +29 -3
  104. package/dist/ui/messages/commands.d.ts +29 -8
  105. package/dist/ui/messages/commands.js +36 -8
  106. package/dist/ui/messages/networkMessages.d.ts +24 -0
  107. package/dist/ui/messages/networkMessages.js +45 -0
  108. package/dist/utils/http.d.ts +9 -2
  109. package/dist/utils/http.js +4 -3
  110. package/dist/utils/strings.d.ts +19 -0
  111. package/dist/utils/strings.js +16 -0
  112. 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
- /** The cascade was not read: Chrome took longer than the time allowed, or failed */
451
- cascade?: 'timeout' | 'failed';
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
- /** Total network requests captured. */
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
@@ -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, {@link HOLDS_FIXED_JS}).
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 = (node) => node.parentElement || (node.parentNode && node.parentNode.host) || null;
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 (looked up through open shadow
167
- * roots): the padding boxes ({@link CLIP_BOX_JS}) of those that cut off
168
- * overflowing content and hold the node in their containing-block chain. An
169
- * absolutely positioned node skips static ancestors (that are not
170
- * transformed) up to its containing block, a fixed one is not clipped at all
171
- * unless a transformed (or filtered, contained, …) ancestor holds it like an
172
- * absolute one, and inline ancestors and `display: contents` ones have no box
173
- * to clip with. The root element is left out (its overflow belongs to the
174
- * viewport), and so is the body's overflow unless the root element's
175
- * overflow is not `visible` (then the body keeps its own overflow and, e.g.
176
- * as the page's scroller, clips like any container).
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 = (n) => n.parentElement || (n.parentNode && n.parentNode.host) || null;
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; raw text keeps `innerText`'s collapsed
48
- * whitespace. The text is cut at `limit` characters, and a
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|select)$/.test(node.localName) || 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}";
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|select)$/.test(node.localName) || 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}";
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; raw text keeps `innerText`'s collapsed
88
- * whitespace. The text is cut at `limit` characters, and a
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|select)$/.test(node.localName) || node.isContentEditable) return;
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`. DOM and
12
- * CSS are enabled on the first inspect and kept on. Matched rules are not
13
- * read (no cascade).
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`. DOM and
12
- * CSS are enabled on the first inspect and kept on. Matched rules are not
13
- * read (no cascade).
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, params),
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 and its border box size.
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, params) {
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 [matched, style, parentStyle, holderStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all([
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
- ...(matched && { matched }),
491
+ nodeId: nodeIdOf(related.node),
491
492
  };
492
493
  }
493
494
  /**
494
- * The element's matched rules, when hints, `--rules` or `--why` need them:
495
- * within {@link HINTS_BUDGET_MS} for the default hints (skipped on very
496
- * large stylesheets), {@link RULES_BUDGET_MS} when asked for explicitly.
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, `timeout`, or undefined when not needed (or no node id)
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 explicit = params.rules === true || params.why !== undefined;
505
- const skipped = !explicit && (params.hints === false || params.props !== undefined || params.all === true);
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 `failed` when they were not read */
33
- matched?: Protocol.CSS.GetMatchedStylesForNodeResponse | 'timeout' | 'failed';
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
- * @returns Matched styles, `timeout` (longer than the budget) or `failed` (CDP error)
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): Promise<Protocol.CSS.GetMatchedStylesForNodeResponse | 'timeout' | 'failed'>;
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