browser-debugger-cli 0.10.0 → 0.12.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 (103) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +148 -74
  3. package/dist/commands/css.d.ts +13 -0
  4. package/dist/commands/css.js +53 -0
  5. package/dist/commands/dom/audit.d.ts +14 -0
  6. package/dist/commands/dom/audit.js +87 -0
  7. package/dist/commands/dom/formInteraction.js +36 -6
  8. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  9. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  10. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  11. package/dist/commands/dom/helpers/screenshot.js +158 -38
  12. package/dist/commands/dom/index.js +4 -1
  13. package/dist/commands/dom/screenshot.js +10 -6
  14. package/dist/commands/dom/wait.js +5 -3
  15. package/dist/commands/helpJson.js +1 -1
  16. package/dist/commands/installSkill.d.ts +20 -0
  17. package/dist/commands/installSkill.js +87 -0
  18. package/dist/commands/optionBehaviors.js +21 -6
  19. package/dist/commands/page.js +7 -4
  20. package/dist/commands/peek.d.ts +7 -0
  21. package/dist/commands/peek.js +65 -23
  22. package/dist/commands/shared/optionTypes.d.ts +5 -1
  23. package/dist/commands/start.d.ts +13 -0
  24. package/dist/commands/start.js +19 -2
  25. package/dist/commands/tail.d.ts +7 -1
  26. package/dist/commands/tail.js +13 -62
  27. package/dist/commands.js +5 -0
  28. package/dist/daemon/session/commandRegistry.js +7 -1
  29. package/dist/daemon/session/plugins.js +4 -52
  30. package/dist/daemon.js +7986 -6848
  31. package/dist/errors/messages.d.ts +50 -4
  32. package/dist/errors/messages.js +94 -5
  33. package/dist/index.js +709 -190
  34. package/dist/ipc/client.d.ts +4 -0
  35. package/dist/ipc/client.js +8 -0
  36. package/dist/ipc/protocol/auditTypes.d.ts +129 -0
  37. package/dist/ipc/protocol/auditTypes.js +6 -0
  38. package/dist/ipc/protocol/commands.d.ts +23 -0
  39. package/dist/ipc/protocol/commands.js +2 -0
  40. package/dist/ipc/protocol/domTypes.d.ts +4 -0
  41. package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
  42. package/dist/runtime/css/search.d.ts +39 -0
  43. package/dist/runtime/css/search.js +122 -0
  44. package/dist/runtime/dom/actionEffects.d.ts +4 -1
  45. package/dist/runtime/dom/actionEffects.js +8 -4
  46. package/dist/runtime/dom/audit.d.ts +19 -0
  47. package/dist/runtime/dom/audit.js +36 -0
  48. package/dist/runtime/dom/auditModel.d.ts +45 -0
  49. package/dist/runtime/dom/auditModel.js +215 -0
  50. package/dist/runtime/dom/auditScripts.d.ts +107 -0
  51. package/dist/runtime/dom/auditScripts.js +112 -0
  52. package/dist/runtime/dom/elementGeometry.d.ts +8 -2
  53. package/dist/runtime/dom/elementGeometry.js +24 -8
  54. package/dist/runtime/dom/elementInfo.d.ts +3 -2
  55. package/dist/runtime/dom/elementInfo.js +8 -2
  56. package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
  57. package/dist/runtime/dom/inspect.d.ts +7 -0
  58. package/dist/runtime/dom/inspect.js +88 -23
  59. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  60. package/dist/runtime/dom/inspectAllStyles.js +89 -7
  61. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  62. package/dist/runtime/dom/inspectCascade.js +214 -44
  63. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  64. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  65. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  66. package/dist/runtime/dom/inspectHints.js +125 -9
  67. package/dist/runtime/dom/inspectModel.d.ts +3 -0
  68. package/dist/runtime/dom/inspectModel.js +30 -7
  69. package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
  70. package/dist/runtime/dom/inspectPaintModel.js +180 -68
  71. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  72. package/dist/runtime/dom/inspectRules.js +21 -5
  73. package/dist/runtime/dom/inspectScripts.d.ts +85 -12
  74. package/dist/runtime/dom/inspectScripts.js +314 -28
  75. package/dist/runtime/dom/inspectTree.js +10 -2
  76. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  77. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  78. package/dist/runtime/dom/layout.js +31 -9
  79. package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
  80. package/dist/runtime/dom/reactEventHelpers.js +27 -9
  81. package/dist/runtime/page/emulation.d.ts +13 -4
  82. package/dist/runtime/page/emulation.js +69 -4
  83. package/dist/runtime/page/userAgent.d.ts +17 -0
  84. package/dist/runtime/page/userAgent.js +57 -0
  85. package/dist/types.d.ts +12 -0
  86. package/dist/ui/formatters/audit.d.ts +19 -0
  87. package/dist/ui/formatters/audit.js +106 -0
  88. package/dist/ui/formatters/dom.d.ts +1 -1
  89. package/dist/ui/formatters/dom.js +6 -3
  90. package/dist/ui/formatters/inspect.js +42 -15
  91. package/dist/ui/formatters/installSkill.d.ts +11 -0
  92. package/dist/ui/formatters/installSkill.js +31 -0
  93. package/dist/ui/formatters/status.js +1 -1
  94. package/dist/ui/messages/commands.d.ts +44 -7
  95. package/dist/ui/messages/commands.js +83 -11
  96. package/dist/ui/messages/preview.d.ts +6 -0
  97. package/dist/ui/messages/preview.js +9 -1
  98. package/dist/utils/cssValues.js +36 -4
  99. package/dist/utils/decisionTrees.js +0 -5
  100. package/dist/utils/suggestions.d.ts +4 -2
  101. package/dist/utils/suggestions.js +7 -5
  102. package/dist/utils/taskMappings.js +1 -1
  103. package/package.json +4 -2
@@ -225,7 +225,8 @@ export const ELEMENT_IDENTITY_JS = `(el) => {
225
225
  }`;
226
226
  /**
227
227
  * Page-side location of an element: the iframes it is in (outermost first)
228
- * and the shadow root holding it, e.g. `iframe#pay > shadow root of <x-card>`;
228
+ * and the shadow root holding it, with the host's first class and light-DOM
229
+ * text, e.g. `iframe#pay > shadow root of <sl-button.primary "Save">`;
229
230
  * empty for the main document.
230
231
  */
231
232
  export const ELEMENT_CONTEXT_JS = `(el) => {
@@ -237,7 +238,12 @@ export const ELEMENT_CONTEXT_JS = `(el) => {
237
238
  doc = frame.ownerDocument;
238
239
  }
239
240
  const root = el.getRootNode();
240
- if (root.host) parts.push('shadow root of <' + describe(root.host) + '>');
241
+ if (root.host) {
242
+ const host = root.host;
243
+ const label = describe(host) + (!host.id && host.classList.length ? '.' + host.classList[0] : '');
244
+ const text = (host.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 30);
245
+ parts.push('shadow root of <' + label + (text ? ' "' + text + '"' : '') + '>');
246
+ }
241
247
  return parts.join(' > ');
242
248
  }`;
243
249
  /**
@@ -7,7 +7,7 @@ import * as path from 'node:path';
7
7
  import { CommandError } from '../../../errors/index.js';
8
8
  import { fileNotFoundError, uploadDirectoryError, singleFileInputError, fillableElementNotFoundError, clickableElementNotFoundError, clickTargetDetachedError, unexpectedResponseFormatError, operationFailedError, pressNotReceivedError, unreachableElementError, } from '../../../errors/messages.js';
9
9
  import { escapeValueForJS, formatScriptExecutionError, throwIfInvalidSelector, withMultipleMatchesWarning, withValueMismatchWarning, } from './shared.js';
10
- import { FILL_READ_BACK_SCRIPT, REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, isFillResult, isClickResult, } from '../reactEventHelpers.js';
10
+ import { FILL_READ_BACK_SCRIPT, REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, SHADOW_FIELD_JS, isFillResult, isClickResult, } from '../reactEventHelpers.js';
11
11
  import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS, selectorArgsJS } from '../targetNode.js';
12
12
  import { createLogger } from '../../../ui/logging/index.js';
13
13
  import { CLICK_NOT_RECEIVED_WARNING, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, domClickFallbackWarning, } from '../../../ui/messages/commands.js';
@@ -176,7 +176,7 @@ async function setFileInput(cdp, selector, value, options) {
176
176
  return problem;
177
177
  try {
178
178
  const located = (await cdp.send('Runtime.evaluate', {
179
- expression: `((el) => (${LABEL_CONTROL_JS})(el) || el)((${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}])`,
179
+ expression: `((el) => (${LABEL_CONTROL_JS})(el) || (${SHADOW_FIELD_JS})(el) || el)((${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}])`,
180
180
  objectGroup: UPLOAD_OBJECT_GROUP,
181
181
  }));
182
182
  if (located.exceptionDetails)
@@ -25,4 +25,11 @@ import type { InspectResult } from '../../ipc/protocol/inspectTypes.js';
25
25
  * selector or unknown property, (87) the cached element left the page
26
26
  */
27
27
  export declare function inspectElement(cdp: CDPConnection, params: DomInspectCommand): Promise<InspectResult>;
28
+ /**
29
+ * Enable DOM and CSS once per connection (kept on: CSS.enable replays every
30
+ * stylesheet, which costs up to a few hundred ms on large sites the first time).
31
+ *
32
+ * @param cdp - CDP connection
33
+ */
34
+ export declare function enableStyleDomains(cdp: CDPConnection): Promise<void>;
28
35
  //# sourceMappingURL=inspect.d.ts.map
@@ -13,13 +13,14 @@
13
13
  * read (no cascade).
14
14
  */
15
15
  import { CommandError } from '../../errors/index.js';
16
- import { operationFailedError, unknownCssPropertyError } from '../../errors/messages.js';
16
+ import { operationFailedError, unknownCssPropertyError, whyAllPropertyError, } from '../../errors/messages.js';
17
17
  import { throwIfInvalidSelector } from './formFillHelpers/shared.js';
18
18
  import { selectedProps } from './inspectAllStyles.js';
19
19
  import { buildCascadeFields } from './inspectCascadeModel.js';
20
20
  import { buildInspectResult } from './inspectModel.js';
21
21
  import { matchedStyles, sourceLabel, trackStyleSheets } from './inspectRules.js';
22
- import { INSPECT_PAGE_JS, RELATED_NODE_JS } from './inspectScripts.js';
22
+ import { explainUnsetVariables } from './inspectHints.js';
23
+ import { INSPECT_PAGE_JS, RELATED_NODE_JS, VARIABLE_SETTERS_JS, } from './inspectScripts.js';
23
24
  import { DEFAULT_TREE_DEPTH, DEFAULT_TREE_LIMIT } from './inspectTree.js';
24
25
  import { inspectLayout } from './layout.js';
25
26
  import { DEEP_QUERY_JS, missingElementError, selectorArgsJS } from './targetNode.js';
@@ -36,12 +37,18 @@ const HINTS_BUDGET_MS = 1000;
36
37
  const RULES_BUDGET_MS = 5000;
37
38
  /** Distinguishes the object groups of concurrent calls */
38
39
  let groupCounter = 0;
39
- /** Page-side choice of a match: the index asked for, else the first rendered one (the first when none is) */
40
+ /**
41
+ * Page-side choice of a match: the index asked for, else the first visible
42
+ * one (rendered, not `visibility: hidden`, not `opacity: 0`), else the first
43
+ * rendered one, else the first
44
+ */
40
45
  const PICK_MATCH_JS = `function (i) {
41
46
  if (i !== null) return i;
42
- const shown = (el) => (el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0);
43
- const first = Array.prototype.findIndex.call(this, shown);
44
- return first < 0 ? 0 : first;
47
+ const check = (el, options) => (el.checkVisibility ? el.checkVisibility(options) : el.getClientRects().length > 0);
48
+ const visible = Array.prototype.findIndex.call(this, (el) => check(el, { visibilityProperty: true, opacityProperty: true }));
49
+ if (visible >= 0) return visible;
50
+ const rendered = Array.prototype.findIndex.call(this, (el) => check(el, {}));
51
+ return rendered < 0 ? 0 : rendered;
45
52
  }`;
46
53
  /**
47
54
  * Inspect one element.
@@ -54,10 +61,20 @@ const PICK_MATCH_JS = `function (i) {
54
61
  */
55
62
  export async function inspectElement(cdp, params) {
56
63
  const started = Date.now();
64
+ if (params.why === 'all') {
65
+ const err = whyAllPropertyError();
66
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
67
+ }
68
+ const pseudoOf = /::?(before|after)\s*$/i.exec(params.selector)?.[1]?.toLowerCase();
69
+ const request = pseudoOf
70
+ ? { ...params, selector: params.selector.replace(/::?(before|after)\s*$/i, '') }
71
+ : params;
57
72
  const objectGroup = `bdg-inspect-${++groupCounter}`;
58
73
  try {
59
- const found = await findElement(cdp, params, objectGroup);
60
- const sources = await readSources(cdp, found.objectId, params, objectGroup);
74
+ const found = await findElement(cdp, request, objectGroup);
75
+ const sources = await readSources(cdp, found.objectId, request, objectGroup);
76
+ if (sources.raw.unknownWhy)
77
+ throwUnknownProperties([params.why ?? ''], sources);
61
78
  const propValues = sources.props ? checkedProps(sources.props, sources) : undefined;
62
79
  const built = buildInspectResult(sources, {
63
80
  selector: params.selector,
@@ -67,8 +84,14 @@ export async function inspectElement(cdp, params) {
67
84
  ...(params.all && { all: true }),
68
85
  ...(propValues && { propValues }),
69
86
  });
70
- const withCascade = { ...built, ...cascadeFields(cdp, sources) };
71
- const result = found.picked ? { ...withCascade, picked: found.picked } : withCascade;
87
+ const cascade = cascadeFields(cdp, sources);
88
+ const hints = cascade.hints && (await explainedHints(cdp, found.objectId, cascade.hints));
89
+ const withCascade = { ...built, ...cascade, ...(hints && { hints }) };
90
+ const result = {
91
+ ...withCascade,
92
+ ...(found.picked && { picked: found.picked }),
93
+ ...(pseudoOf && { pseudoOf: `::${pseudoOf}` }),
94
+ };
72
95
  log.debug(`Inspected ${result.element} in ${Date.now() - started} ms`);
73
96
  return result;
74
97
  }
@@ -78,6 +101,25 @@ export async function inspectElement(cdp, params) {
78
101
  .catch((error) => log.debug(`Object group not released: ${getErrorMessage(error)}`));
79
102
  }
80
103
  }
104
+ /**
105
+ * Hints with unset variables told where the page sets them
106
+ * ({@link VARIABLE_SETTERS_JS}); unchanged when there are none or the
107
+ * search fails.
108
+ *
109
+ * @param cdp - CDP connection
110
+ * @param objectId - The element
111
+ * @param hints - Hints
112
+ * @returns Hints
113
+ */
114
+ async function explainedHints(cdp, objectId, hints) {
115
+ const names = [...new Set(hints.flatMap((hint) => hint.variables ?? []))];
116
+ if (names.length === 0)
117
+ return hints;
118
+ const setters = await callOn(cdp, objectId, VARIABLE_SETTERS_JS, [
119
+ names,
120
+ ]);
121
+ return setters ? explainUnsetVariables(hints, setters) : hints;
122
+ }
81
123
  /**
82
124
  * The properties asked for with `--props`.
83
125
  *
@@ -87,9 +129,19 @@ export async function inspectElement(cdp, params) {
87
129
  * @throws CommandError (81) for a name no value was found for
88
130
  */
89
131
  function checkedProps(names, sources) {
90
- const { props, unknown } = selectedProps(names, sources.style, sources.raw.props, sources.raw.unknownProps);
132
+ const { props, unknown } = selectedProps(names, sources.style, sources.raw.props, sources.raw.unknownProps, sources.raw.rootFontSize);
91
133
  if (unknown.length === 0)
92
134
  return props;
135
+ return throwUnknownProperties(unknown, sources);
136
+ }
137
+ /**
138
+ * Reject names that are not CSS properties, with the closest computed ones.
139
+ *
140
+ * @param unknown - Names
141
+ * @param sources - What was read (computed property names)
142
+ * @throws CommandError (81) always
143
+ */
144
+ function throwUnknownProperties(unknown, sources) {
93
145
  const suggestions = unknown.flatMap((name) => findSimilar(name, Object.keys(sources.style), { maxSuggestions: 1 }));
94
146
  const err = unknownCssPropertyError(unknown, suggestions);
95
147
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
@@ -130,11 +182,14 @@ function cascadeFields(cdp, sources) {
130
182
  style: sources.style,
131
183
  parentStyle: sources.parentStyle,
132
184
  replaced: sources.raw.replaced === true,
133
- formControl: sources.raw.formControl,
185
+ formControl: sources.raw.formControl && sources.raw.hasText,
134
186
  ...(sources.hints === false && { hints: false }),
135
187
  label: (declaration) => sourceLabel(declaration, cdp),
136
188
  ...(sources.rules && { rules: true }),
137
189
  ...(sources.why && { why: sources.why }),
190
+ ...(sources.raw.whyLonghands && { whyLonghands: sources.raw.whyLonghands }),
191
+ ...(sources.raw.whyComputed && { whyComputed: sources.raw.whyComputed }),
192
+ ...(sources.raw.whyInactive && { whyInactive: sources.raw.whyInactive }),
138
193
  ...(sources.props && { props: sources.props }),
139
194
  });
140
195
  }
@@ -269,7 +324,7 @@ async function readSources(cdp, objectId, params, objectGroup) {
269
324
  return {
270
325
  raw,
271
326
  ...styles,
272
- fonts: raw.ownText || raw.formControl ? styles.fonts.node : styles.fonts.textHolder,
327
+ fonts: raw.textHolder ? styles.fonts.textHolder : styles.fonts.node,
273
328
  ...measured,
274
329
  ...(params.rules && { rules: true }),
275
330
  ...(params.why && { why: params.why }),
@@ -290,6 +345,7 @@ async function readPage(cdp, objectId, params) {
290
345
  const raw = await callOn(cdp, objectId, INSPECT_PAGE_JS, [
291
346
  params.props || params.all ? 0 : (params.tree ?? DEFAULT_TREE_DEPTH),
292
347
  params.props ?? null,
348
+ params.why ?? null,
293
349
  ]);
294
350
  if (raw)
295
351
  return raw;
@@ -297,11 +353,12 @@ async function readPage(cdp, objectId, params) {
297
353
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
298
354
  }
299
355
  /**
300
- * The backend node id of the element's layout parent or first text holder.
356
+ * The backend node id of the element's layout parent, the element that
357
+ * draws most of its text, or the parent of that text's nodes.
301
358
  *
302
359
  * @param cdp - CDP connection
303
360
  * @param objectId - The element
304
- * @param which - `parent` or `textHolder`
361
+ * @param which - `parent`, `textHolder` or `fontHolder`
305
362
  * @param objectGroup - Object group for handles
306
363
  * @returns Backend node id, or undefined when there is none
307
364
  */
@@ -316,8 +373,8 @@ async function relatedNode(cdp, objectId, which, objectGroup) {
316
373
  return id ? (await describe(cdp, id)).backendNodeId : undefined;
317
374
  }
318
375
  /**
319
- * The backend node ids of the element, its layout parent, its first text
320
- * holder and its generated pseudo-elements.
376
+ * The backend node ids of the element, its layout parent, its text holder
377
+ * (with the parent of its text nodes) and its generated pseudo-elements.
321
378
  *
322
379
  * @param cdp - CDP connection
323
380
  * @param objectId - The element
@@ -325,10 +382,11 @@ async function relatedNode(cdp, objectId, which, objectGroup) {
325
382
  * @returns Backend node ids
326
383
  */
327
384
  async function relatedNodes(cdp, objectId, objectGroup) {
328
- const [node, parent, textHolder] = await Promise.all([
385
+ const [node, parent, textHolder, fontHolder] = await Promise.all([
329
386
  describe(cdp, objectId),
330
387
  relatedNode(cdp, objectId, 'parent', objectGroup),
331
388
  relatedNode(cdp, objectId, 'textHolder', objectGroup),
389
+ relatedNode(cdp, objectId, 'fontHolder', objectGroup),
332
390
  ]);
333
391
  const pseudo = (node.pseudoElements ?? [])
334
392
  .filter((p) => p.pseudoType === 'before' || p.pseudoType === 'after')
@@ -339,7 +397,8 @@ async function relatedNodes(cdp, objectId, objectGroup) {
339
397
  return {
340
398
  node: node.backendNodeId,
341
399
  ...(parent && { parent }),
342
- ...(textHolder && { textHolder }),
400
+ ...(textHolder && textHolder !== node.backendNodeId && { textHolder }),
401
+ ...(fontHolder && { fontHolder }),
343
402
  pseudo,
344
403
  };
345
404
  }
@@ -392,6 +451,7 @@ async function nodeIdLookup(cdp, related) {
392
451
  related.node,
393
452
  related.parent,
394
453
  related.textHolder,
454
+ related.fontHolder,
395
455
  ...related.pseudo.map((p) => p.backendNodeId),
396
456
  ];
397
457
  const ids = await pushNodes(cdp, order.filter((id) => id !== undefined));
@@ -408,18 +468,23 @@ async function nodeIdLookup(cdp, related) {
408
468
  async function readStyles(cdp, related, params) {
409
469
  await enableStyleDomains(cdp);
410
470
  const nodeIdOf = await nodeIdLookup(cdp, related);
411
- const [matched, style, parentStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all([
471
+ const optionalStyle = (backendNodeId) => backendNodeId === undefined
472
+ ? Promise.resolve(undefined)
473
+ : computedStyle(cdp, nodeIdOf(backendNodeId));
474
+ const [matched, style, parentStyle, holderStyle, nodeFonts, holderFonts, size, pseudo] = await Promise.all([
412
475
  readMatched(cdp, nodeIdOf(related.node), params),
413
476
  computedStyle(cdp, nodeIdOf(related.node)),
414
- related.parent === undefined ? undefined : computedStyle(cdp, nodeIdOf(related.parent)),
477
+ optionalStyle(related.parent),
478
+ optionalStyle(related.textHolder),
415
479
  platformFonts(cdp, nodeIdOf(related.node)),
416
- platformFonts(cdp, nodeIdOf(related.textHolder)),
480
+ platformFonts(cdp, nodeIdOf(related.fontHolder)),
417
481
  borderBoxSize(cdp, related.node),
418
482
  Promise.all(related.pseudo.map((p) => pseudoSource(cdp, p.type, p.backendNodeId, nodeIdOf(p.backendNodeId)))),
419
483
  ]);
420
484
  return {
421
485
  style,
422
486
  ...(parentStyle && { parentStyle }),
487
+ ...(holderStyle && { holderStyle }),
423
488
  pseudo,
424
489
  fonts: { node: nodeFonts, textHolder: holderFonts },
425
490
  ...(size && { size }),
@@ -466,7 +531,7 @@ async function pseudoSource(cdp, type, backendNodeId, nodeId) {
466
531
  *
467
532
  * @param cdp - CDP connection
468
533
  */
469
- async function enableStyleDomains(cdp) {
534
+ export async function enableStyleDomains(cdp) {
470
535
  if (stylesEnabled.has(cdp))
471
536
  return;
472
537
  trackStyleSheets(cdp);
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * `--all` lists every computed longhand that is not noise and not its
5
5
  * default, collapsed into shorthands (margin, padding, inset, border,
6
- * radius, overflow, gap, flex, grid area, outline, animation). Defaults are
6
+ * radius, overflow, gap, flex, grid area, outline, animation, transition). Defaults are
7
7
  * the CSS initial values as Chrome computes them; the UA stylesheet's
8
8
  * per-tag values (a `<p>`'s margins, a button's padding) are real values and
9
9
  * are listed. Display, font, size, line height and color are always listed.
10
10
  *
11
11
  * Noise: custom properties, logical duplicates of physical properties,
12
- * width/height (in the header), transitions, origins without a transform,
12
+ * width/height (in the header), origins without a transform,
13
13
  * colors that only repeat `color` (currentColor), properties of borders,
14
14
  * outlines and rules that are not drawn, SVG paint properties on HTML
15
15
  * elements, and vendor-prefixed internals.
@@ -34,15 +34,25 @@ export declare function isDefaultValue(name: string, value: string): boolean;
34
34
  * @returns Normalized value
35
35
  */
36
36
  export declare function normalizeProperty(name: string, value: string): string;
37
+ /**
38
+ * A shorthand's computed value as `--all` writes it (`margin 0 8`,
39
+ * `outline 2 solid #f00`, `transition color 1s ease-in`).
40
+ *
41
+ * @param shorthand - Shorthand name
42
+ * @param style - Computed longhands
43
+ * @returns Value, or undefined for a shorthand `--all` does not collapse
44
+ */
45
+ export declare function collapsedValue(shorthand: string, style: StyleMap): string | undefined;
37
46
  /**
38
47
  * Every computed longhand that is not noise and not a default, normalized
39
48
  * and collapsed into shorthands.
40
49
  *
41
50
  * @param style - Computed styles
42
51
  * @param svg - The element is an SVG element (SVG properties are listed)
52
+ * @param formControl - A form control (`appearance: none` is not its default)
43
53
  * @returns Properties and values
44
54
  */
45
- export declare function allStyles(style: StyleMap, svg: boolean): Record<string, string>;
55
+ export declare function allStyles(style: StyleMap, svg: boolean, formControl?: boolean): Record<string, string>;
46
56
  /**
47
57
  * The properties asked for with `--props`, raw and normalized. CDP's
48
58
  * computed longhands come first; shorthands and custom properties come from
@@ -52,10 +62,12 @@ export declare function allStyles(style: StyleMap, svg: boolean): Record<string,
52
62
  * @param style - Computed longhands (CDP)
53
63
  * @param pageValues - Values the page read
54
64
  * @param pageUnknown - Names the page does not know as CSS properties
65
+ * @param rootFontSize - Root font size (px): a custom property that is one
66
+ * rem length is also given in px
55
67
  * @returns Values by name (an unset custom property is empty), and the
56
68
  * names that are not properties
57
69
  */
58
- export declare function selectedProps(names: readonly string[], style: StyleMap, pageValues: Record<string, string> | undefined, pageUnknown?: readonly string[]): {
70
+ export declare function selectedProps(names: readonly string[], style: StyleMap, pageValues: Record<string, string> | undefined, pageUnknown?: readonly string[], rootFontSize?: number): {
59
71
  props: Record<string, InspectProp>;
60
72
  unknown: string[];
61
73
  };
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * `--all` lists every computed longhand that is not noise and not its
5
5
  * default, collapsed into shorthands (margin, padding, inset, border,
6
- * radius, overflow, gap, flex, grid area, outline, animation). Defaults are
6
+ * radius, overflow, gap, flex, grid area, outline, animation, transition). Defaults are
7
7
  * the CSS initial values as Chrome computes them; the UA stylesheet's
8
8
  * per-tag values (a `<p>`'s margins, a button's padding) are real values and
9
9
  * are listed. Display, font, size, line height and color are always listed.
10
10
  *
11
11
  * Noise: custom properties, logical duplicates of physical properties,
12
- * width/height (in the header), transitions, origins without a transform,
12
+ * width/height (in the header), origins without a transform,
13
13
  * colors that only repeat `color` (currentColor), properties of borders,
14
14
  * outlines and rules that are not drawn, SVG paint properties on HTML
15
15
  * elements, and vendor-prefixed internals.
@@ -98,6 +98,28 @@ const INITIAL_BY_VALUE = [
98
98
  ['collapse', 'white-space-collapse'],
99
99
  ['horizontal-tb', 'writing-mode'],
100
100
  ['isolate', 'unicode-bidi'],
101
+ ['rgb(0, 0, 0)', 'fill flood-color stop-color'],
102
+ ['1', 'fill-opacity flood-opacity stop-opacity stroke-opacity'],
103
+ ['rgb(255, 255, 255)', 'lighting-color'],
104
+ ['1px', 'stroke-width'],
105
+ ['4', 'stroke-miterlimit'],
106
+ ['butt', 'stroke-linecap'],
107
+ ['miter', 'stroke-linejoin'],
108
+ ['nonzero', 'fill-rule clip-rule'],
109
+ ['none', 'stroke stroke-dasharray marker-start marker-mid marker-end vector-effect'],
110
+ ['0px', 'stroke-dashoffset baseline-shift'],
111
+ [
112
+ 'auto',
113
+ 'shape-rendering dominant-baseline alignment-baseline buffered-rendering color-rendering',
114
+ ],
115
+ ['srgb', 'color-interpolation'],
116
+ ['linearrgb', 'color-interpolation-filters'],
117
+ ['all', 'transition-property'],
118
+ ['0s', 'transition-duration transition-delay'],
119
+ ['ease', 'transition-timing-function'],
120
+ ['normal', 'transition-behavior paint-order'],
121
+ ['start', 'text-anchor'],
122
+ ['rgba(0, 0, 0, 0.18)', '-webkit-tap-highlight-color'],
101
123
  ];
102
124
  /** Initial value per property ({@link INITIAL_BY_VALUE}) */
103
125
  const INITIAL_VALUES = new Map(INITIAL_BY_VALUE.flatMap(([value, names]) => names.split(' ').map((name) => [name, value])));
@@ -144,6 +166,7 @@ const NOISE = new Set([
144
166
  const LOGICAL = /((^|-)block(-|$)|(^|-)inline(-|$)|^inset-|-start-start|-start-end|-end-end|-end-start)/;
145
167
  /** Vendor-prefixed properties worth listing */
146
168
  const PREFIXED_KEPT = new Set([
169
+ '-webkit-tap-highlight-color',
147
170
  '-webkit-line-clamp',
148
171
  '-webkit-text-stroke-width',
149
172
  '-webkit-text-security',
@@ -161,7 +184,9 @@ const CURRENT_COLOR_ECHOES = /^(border-(top|right|bottom|left)-color|outline-col
161
184
  * @returns True when it is left out
162
185
  */
163
186
  function isNoise(name, style, svg) {
164
- if (name.startsWith('--') || NOISE.has(name) || name.startsWith('transition'))
187
+ if (name.startsWith('--') || NOISE.has(name))
188
+ return true;
189
+ if (name.startsWith('transition') && style['transition-duration'] === '0s')
165
190
  return true;
166
191
  if (LOGICAL.test(name) && !name.endsWith('-axis'))
167
192
  return true;
@@ -266,7 +291,56 @@ const COLLAPSES = [
266
291
  ],
267
292
  write: (v) => v.join(' '),
268
293
  },
294
+ {
295
+ shorthand: 'transition',
296
+ longhands: [
297
+ 'transition-property',
298
+ 'transition-duration',
299
+ 'transition-timing-function',
300
+ 'transition-delay',
301
+ 'transition-behavior',
302
+ ],
303
+ write: transitionList,
304
+ },
269
305
  ];
306
+ /**
307
+ * A transition list from its longhands' comma lists, one transition per
308
+ * property, defaults left out (`background-color 0.25s ease-in, opacity 0.2s`).
309
+ *
310
+ * @param values - Property, duration, timing function, delay and behavior lists
311
+ * @returns Transition shorthand
312
+ */
313
+ function transitionList(values) {
314
+ const lists = values.map((value) => value.split(/,\s*(?![^(]*\))/));
315
+ const [properties = []] = lists;
316
+ return properties
317
+ .map((property, i) => {
318
+ const at = (list) => list?.[i % list.length] ?? '';
319
+ const [duration, timing, delay, behavior] = lists.slice(1).map(at);
320
+ return [
321
+ property,
322
+ duration,
323
+ timing === 'ease' ? '' : timing,
324
+ delay === '0s' ? '' : delay,
325
+ behavior === 'normal' ? '' : behavior,
326
+ ]
327
+ .filter(Boolean)
328
+ .join(' ');
329
+ })
330
+ .join(', ');
331
+ }
332
+ /**
333
+ * A shorthand's computed value as `--all` writes it (`margin 0 8`,
334
+ * `outline 2 solid #f00`, `transition color 1s ease-in`).
335
+ *
336
+ * @param shorthand - Shorthand name
337
+ * @param style - Computed longhands
338
+ * @returns Value, or undefined for a shorthand `--all` does not collapse
339
+ */
340
+ export function collapsedValue(shorthand, style) {
341
+ const group = COLLAPSES.find((entry) => entry.shorthand === shorthand);
342
+ return group?.write(group.longhands.map((longhand) => normalizeProperty(longhand, style[longhand] ?? '')));
343
+ }
270
344
  /**
271
345
  * Border longhands collapsed: one `border` when the four drawn sides are
272
346
  * equal, else one entry per drawn side.
@@ -337,13 +411,15 @@ function isUnsetOffset(name, value, style) {
337
411
  *
338
412
  * @param style - Computed styles
339
413
  * @param svg - The element is an SVG element (SVG properties are listed)
414
+ * @param formControl - A form control (`appearance: none` is not its default)
340
415
  * @returns Properties and values
341
416
  */
342
- export function allStyles(style, svg) {
417
+ export function allStyles(style, svg, formControl = false) {
343
418
  const kept = new Map();
344
419
  const transformed = (style['transform'] ?? 'none') !== 'none';
345
420
  for (const [name, value] of Object.entries(style)) {
346
- if (name === 'transform-origin' && transformed) {
421
+ const set = (name === 'transform-origin' && transformed) || (name === 'appearance' && formControl);
422
+ if (set) {
347
423
  kept.set(name, normalizeProperty(name, value));
348
424
  continue;
349
425
  }
@@ -364,10 +440,12 @@ export function allStyles(style, svg) {
364
440
  * @param style - Computed longhands (CDP)
365
441
  * @param pageValues - Values the page read
366
442
  * @param pageUnknown - Names the page does not know as CSS properties
443
+ * @param rootFontSize - Root font size (px): a custom property that is one
444
+ * rem length is also given in px
367
445
  * @returns Values by name (an unset custom property is empty), and the
368
446
  * names that are not properties
369
447
  */
370
- export function selectedProps(names, style, pageValues, pageUnknown = []) {
448
+ export function selectedProps(names, style, pageValues, pageUnknown = [], rootFontSize = 16) {
371
449
  const props = {};
372
450
  const unknown = [];
373
451
  for (const name of names) {
@@ -377,7 +455,11 @@ export function selectedProps(names, style, pageValues, pageUnknown = []) {
377
455
  unknown.push(name);
378
456
  }
379
457
  else {
380
- props[name] = { computed, value: normalizeProperty(name, computed) };
458
+ const rem = name.startsWith('--') ? /^(-?\d*\.?\d+)rem$/.exec(computed.trim()) : null;
459
+ const value = rem
460
+ ? String(Math.round(Number(rem[1]) * rootFontSize * 10) / 10)
461
+ : normalizeProperty(name, computed);
462
+ props[name] = { computed, value };
381
463
  }
382
464
  }
383
465
  return { props, unknown };
@@ -32,6 +32,11 @@ export interface DeclarationSource {
32
32
  condition?: string;
33
33
  /** Specificity of the matching selector (ids, classes, types) */
34
34
  specificity?: [number, number, number];
35
+ /** The rule as written, whitespace collapsed (no selector: the style attribute) */
36
+ rule?: {
37
+ selector?: string;
38
+ declarations: string;
39
+ };
35
40
  }
36
41
  /** One declaration of a longhand */
37
42
  export interface Declaration {
@@ -66,9 +71,11 @@ export declare function shorthandLonghands(name: string): readonly string[] | un
66
71
  * Whether a property inherits.
67
72
  *
68
73
  * @param property - Longhand
69
- * @returns True for inherited properties (custom properties inherit)
74
+ * @param notInherited - Custom properties registered with `inherits: false`
75
+ * @returns True for inherited properties (custom properties inherit unless
76
+ * registered otherwise)
70
77
  */
71
- export declare function isInherited(property: string): boolean;
78
+ export declare function isInherited(property: string, notInherited?: ReadonlySet<string>): boolean;
72
79
  /**
73
80
  * The physical longhand a logical property sets.
74
81
  *
@@ -91,4 +98,14 @@ export declare function ownProperties(matched: Protocol.CSS.GetMatchedStylesForN
91
98
  * @returns Resolution per property
92
99
  */
93
100
  export declare function resolveCascade(matched: Protocol.CSS.GetMatchedStylesForNodeResponse, properties: readonly string[]): Map<string, Resolution>;
101
+ /**
102
+ * The rule a declaration is in, as written: whole when short, else its
103
+ * selector and that declaration (`.btn { … background-color:var(--bs-btn-bg); … }`).
104
+ *
105
+ * @param declaration - Declaration
106
+ * @returns `{ rule }`, or nothing for a browser, extension or attribute style
107
+ */
108
+ export declare function ruleField(declaration: Declaration): {
109
+ rule?: string;
110
+ };
94
111
  //# sourceMappingURL=inspectCascade.d.ts.map