browser-debugger-cli 0.11.0 → 0.13.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 (222) hide show
  1. package/.claude/skills/bdg/SKILL.md +4 -4
  2. package/README.md +143 -79
  3. package/dist/commands/cdp.d.ts +22 -1
  4. package/dist/commands/cdp.js +100 -43
  5. package/dist/commands/console.d.ts +12 -0
  6. package/dist/commands/console.js +62 -12
  7. package/dist/commands/css.d.ts +13 -0
  8. package/dist/commands/css.js +53 -0
  9. package/dist/commands/dom/DomElementResolver.d.ts +3 -1
  10. package/dist/commands/dom/DomElementResolver.js +10 -3
  11. package/dist/commands/dom/a11y.js +3 -2
  12. package/dist/commands/dom/audit.d.ts +14 -0
  13. package/dist/commands/dom/audit.js +87 -0
  14. package/dist/commands/dom/eval.d.ts +3 -2
  15. package/dist/commands/dom/eval.js +11 -5
  16. package/dist/commands/dom/form.js +10 -9
  17. package/dist/commands/dom/formInteraction.js +42 -11
  18. package/dist/commands/dom/get.js +8 -8
  19. package/dist/commands/dom/helpers/index.d.ts +1 -1
  20. package/dist/commands/dom/helpers/index.js +1 -1
  21. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  22. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  23. package/dist/commands/dom/helpers/query.d.ts +27 -3
  24. package/dist/commands/dom/helpers/query.js +152 -64
  25. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  26. package/dist/commands/dom/helpers/screenshot.js +169 -49
  27. package/dist/commands/dom/index.js +7 -2
  28. package/dist/commands/dom/query.d.ts +19 -2
  29. package/dist/commands/dom/query.js +37 -6
  30. package/dist/commands/dom/screenshot.js +12 -7
  31. package/dist/commands/dom/semanticUtils.d.ts +3 -2
  32. package/dist/commands/dom/semanticUtils.js +40 -9
  33. package/dist/commands/dom/wait.js +5 -3
  34. package/dist/commands/helpJson.d.ts +82 -19
  35. package/dist/commands/helpJson.js +112 -41
  36. package/dist/commands/helpTopic.d.ts +16 -1
  37. package/dist/commands/helpTopic.js +59 -1
  38. package/dist/commands/installSkill.d.ts +15 -5
  39. package/dist/commands/installSkill.js +86 -16
  40. package/dist/commands/network/list.js +22 -12
  41. package/dist/commands/optionBehaviors.js +53 -16
  42. package/dist/commands/page.js +7 -4
  43. package/dist/commands/peek.d.ts +7 -0
  44. package/dist/commands/peek.js +65 -23
  45. package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
  46. package/dist/commands/shared/daemonErrorHandler.js +20 -9
  47. package/dist/commands/shared/dataFetcher.d.ts +12 -4
  48. package/dist/commands/shared/dataFetcher.js +12 -4
  49. package/dist/commands/shared/followMode.d.ts +9 -1
  50. package/dist/commands/shared/followMode.js +22 -4
  51. package/dist/commands/shared/optionTypes.d.ts +9 -2
  52. package/dist/commands/shared/outputFile.js +6 -1
  53. package/dist/commands/start.d.ts +20 -5
  54. package/dist/commands/start.js +84 -23
  55. package/dist/commands/stop.d.ts +11 -0
  56. package/dist/commands/stop.js +24 -1
  57. package/dist/commands/tail.d.ts +7 -1
  58. package/dist/commands/tail.js +13 -62
  59. package/dist/commands.js +2 -0
  60. package/dist/connection/cdp.d.ts +7 -0
  61. package/dist/connection/cdp.js +9 -0
  62. package/dist/connection/launcher.js +3 -2
  63. package/dist/daemon/SessionController.js +6 -1
  64. package/dist/daemon/launcher.d.ts +3 -2
  65. package/dist/daemon/launcher.js +47 -3
  66. package/dist/daemon/session/Session.d.ts +4 -1
  67. package/dist/daemon/session/Session.js +33 -2
  68. package/dist/daemon/session/TelemetryStore.d.ts +8 -1
  69. package/dist/daemon/session/TelemetryStore.js +13 -1
  70. package/dist/daemon/session/commandRegistry.js +36 -14
  71. package/dist/daemon/session/interactions.d.ts +2 -1
  72. package/dist/daemon/session/interactions.js +13 -1
  73. package/dist/daemon/session/plugins.js +19 -53
  74. package/dist/daemon/session/teardown.js +1 -1
  75. package/dist/daemon.js +9234 -7222
  76. package/dist/errors/messages.d.ts +88 -15
  77. package/dist/errors/messages.js +177 -27
  78. package/dist/index.js +19322 -13961
  79. package/dist/ipc/client.d.ts +22 -2
  80. package/dist/ipc/client.js +34 -5
  81. package/dist/ipc/protocol/auditTypes.d.ts +135 -0
  82. package/dist/ipc/protocol/auditTypes.js +6 -0
  83. package/dist/ipc/protocol/commands.d.ts +35 -0
  84. package/dist/ipc/protocol/commands.js +2 -0
  85. package/dist/ipc/protocol/domTypes.d.ts +16 -0
  86. package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
  87. package/dist/ipc/session/types.d.ts +2 -0
  88. package/dist/runtime/css/search.d.ts +39 -0
  89. package/dist/runtime/css/search.js +122 -0
  90. package/dist/runtime/dom/actionEffects.d.ts +9 -2
  91. package/dist/runtime/dom/actionEffects.js +30 -14
  92. package/dist/runtime/dom/audit.d.ts +19 -0
  93. package/dist/runtime/dom/audit.js +37 -0
  94. package/dist/runtime/dom/auditModel.d.ts +45 -0
  95. package/dist/runtime/dom/auditModel.js +220 -0
  96. package/dist/runtime/dom/auditScripts.d.ts +113 -0
  97. package/dist/runtime/dom/auditScripts.js +148 -0
  98. package/dist/runtime/dom/elementGeometry.d.ts +16 -3
  99. package/dist/runtime/dom/elementGeometry.js +49 -10
  100. package/dist/runtime/dom/elementInfo.d.ts +74 -17
  101. package/dist/runtime/dom/elementInfo.js +187 -34
  102. package/dist/runtime/dom/evalHelpers.d.ts +12 -2
  103. package/dist/runtime/dom/evalHelpers.js +67 -7
  104. package/dist/runtime/dom/formDiscovery.d.ts +6 -2
  105. package/dist/runtime/dom/formDiscovery.js +20 -3
  106. package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
  107. package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
  108. package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
  109. package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
  110. package/dist/runtime/dom/formSubmitHelpers.js +4 -3
  111. package/dist/runtime/dom/frameLayout.js +1 -0
  112. package/dist/runtime/dom/inspect.d.ts +7 -0
  113. package/dist/runtime/dom/inspect.js +92 -28
  114. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  115. package/dist/runtime/dom/inspectAllStyles.js +90 -7
  116. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  117. package/dist/runtime/dom/inspectCascade.js +214 -44
  118. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  119. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  120. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  121. package/dist/runtime/dom/inspectHints.js +125 -9
  122. package/dist/runtime/dom/inspectModel.d.ts +5 -1
  123. package/dist/runtime/dom/inspectModel.js +37 -10
  124. package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
  125. package/dist/runtime/dom/inspectPaintModel.js +182 -68
  126. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  127. package/dist/runtime/dom/inspectRules.js +21 -5
  128. package/dist/runtime/dom/inspectScripts.d.ts +112 -12
  129. package/dist/runtime/dom/inspectScripts.js +357 -32
  130. package/dist/runtime/dom/inspectTree.js +10 -2
  131. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  132. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  133. package/dist/runtime/dom/layout.js +40 -16
  134. package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
  135. package/dist/runtime/dom/reactEventHelpers.js +90 -36
  136. package/dist/runtime/dom/targetNode.d.ts +18 -5
  137. package/dist/runtime/dom/targetNode.js +268 -8
  138. package/dist/runtime/dom/wait.js +2 -1
  139. package/dist/runtime/page/bdgWorld.d.ts +57 -0
  140. package/dist/runtime/page/bdgWorld.js +180 -0
  141. package/dist/runtime/page/emulation.d.ts +13 -4
  142. package/dist/runtime/page/emulation.js +69 -4
  143. package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
  144. package/dist/runtime/page/replacedBuiltins.js +136 -0
  145. package/dist/runtime/page/userAgent.d.ts +17 -0
  146. package/dist/runtime/page/userAgent.js +57 -0
  147. package/dist/session/QueryCacheManager.d.ts +4 -1
  148. package/dist/session/QueryCacheManager.js +5 -2
  149. package/dist/session/chrome.d.ts +4 -1
  150. package/dist/session/chrome.js +7 -1
  151. package/dist/session/cleanup/staleSession.d.ts +21 -4
  152. package/dist/session/cleanup/staleSession.js +79 -9
  153. package/dist/session/cleanup/userCommands.d.ts +4 -1
  154. package/dist/session/cleanup/userCommands.js +10 -5
  155. package/dist/session/daemonSocket.d.ts +10 -0
  156. package/dist/session/daemonSocket.js +22 -0
  157. package/dist/session/lastSession.d.ts +6 -3
  158. package/dist/session/lastSession.js +11 -5
  159. package/dist/session/paths.d.ts +3 -1
  160. package/dist/session/paths.js +5 -5
  161. package/dist/session/portClaims.js +4 -3
  162. package/dist/session/sessionList.d.ts +13 -5
  163. package/dist/session/sessionList.js +31 -7
  164. package/dist/telemetry/a11y.js +2 -2
  165. package/dist/telemetry/console.d.ts +2 -1
  166. package/dist/telemetry/console.js +30 -21
  167. package/dist/telemetry/pageCrash.d.ts +26 -0
  168. package/dist/telemetry/pageCrash.js +53 -0
  169. package/dist/types.d.ts +20 -0
  170. package/dist/ui/formatters/audit.d.ts +19 -0
  171. package/dist/ui/formatters/audit.js +115 -0
  172. package/dist/ui/formatters/cdp.d.ts +138 -0
  173. package/dist/ui/formatters/cdp.js +131 -0
  174. package/dist/ui/formatters/console/chronological.js +3 -1
  175. package/dist/ui/formatters/console/follow.d.ts +2 -1
  176. package/dist/ui/formatters/console/follow.js +2 -2
  177. package/dist/ui/formatters/console/json.d.ts +2 -2
  178. package/dist/ui/formatters/console/json.js +11 -5
  179. package/dist/ui/formatters/console/shared.d.ts +30 -0
  180. package/dist/ui/formatters/console/shared.js +16 -0
  181. package/dist/ui/formatters/console/summarize.d.ts +9 -2
  182. package/dist/ui/formatters/console/summarize.js +40 -9
  183. package/dist/ui/formatters/console.d.ts +2 -1
  184. package/dist/ui/formatters/console.js +7 -5
  185. package/dist/ui/formatters/details.js +3 -1
  186. package/dist/ui/formatters/dom.d.ts +2 -2
  187. package/dist/ui/formatters/dom.js +10 -8
  188. package/dist/ui/formatters/helpFormatters.js +1 -1
  189. package/dist/ui/formatters/inspect.js +50 -17
  190. package/dist/ui/formatters/installSkill.d.ts +9 -1
  191. package/dist/ui/formatters/installSkill.js +32 -6
  192. package/dist/ui/formatters/layout.js +2 -1
  193. package/dist/ui/formatters/networkList.d.ts +1 -1
  194. package/dist/ui/formatters/networkList.js +1 -2
  195. package/dist/ui/formatters/preview.d.ts +2 -0
  196. package/dist/ui/formatters/preview.js +17 -7
  197. package/dist/ui/formatters/sessions.d.ts +2 -2
  198. package/dist/ui/formatters/sessions.js +9 -2
  199. package/dist/ui/formatters/status.js +1 -1
  200. package/dist/ui/logging/logger.d.ts +1 -1
  201. package/dist/ui/messages/commands.d.ts +168 -11
  202. package/dist/ui/messages/commands.js +245 -18
  203. package/dist/ui/messages/consoleMessages.d.ts +24 -0
  204. package/dist/ui/messages/consoleMessages.js +32 -0
  205. package/dist/ui/messages/preview.d.ts +12 -0
  206. package/dist/ui/messages/preview.js +18 -2
  207. package/dist/ui/messages/session.d.ts +13 -2
  208. package/dist/ui/messages/session.js +22 -3
  209. package/dist/utils/cssValues.js +36 -4
  210. package/dist/utils/decisionTrees.js +0 -5
  211. package/dist/utils/directories.d.ts +34 -0
  212. package/dist/utils/directories.js +88 -0
  213. package/dist/utils/display.d.ts +16 -0
  214. package/dist/utils/display.js +42 -0
  215. package/dist/utils/exitCodes.d.ts +1 -0
  216. package/dist/utils/exitCodes.js +6 -0
  217. package/dist/utils/process.d.ts +12 -0
  218. package/dist/utils/process.js +25 -0
  219. package/dist/utils/suggestions.d.ts +4 -2
  220. package/dist/utils/suggestions.js +7 -5
  221. package/dist/utils/taskMappings.js +1 -1
  222. package/package.json +3 -2
@@ -12,7 +12,7 @@
12
12
  * instead of the light DOM, slots replaced by what is assigned to them, and
13
13
  * `display: contents` wrappers by their children.
14
14
  */
15
- import { ELEMENT_CONTEXT_JS, ELEMENT_DESCRIPTION_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, } from './elementInfo.js';
15
+ import { COMPOSED_JS, ELEMENT_CONTEXT_JS, ELEMENT_DESCRIPTION_JS, FLAT_TEXT_JS, LABEL_CLASSES_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, } from './elementInfo.js';
16
16
  /** Elements walked for the child tree at most (the rest are counted) */
17
17
  export const TREE_NODE_CAP = 400;
18
18
  /**
@@ -20,7 +20,7 @@ export const TREE_NODE_CAP = 400;
20
20
  * parent, the parent that lays it out (skipping `display: contents`), its
21
21
  * element children in the flat tree and whether an element is rendered.
22
22
  */
23
- const FLAT_TREE_JS = `(view) => {
23
+ export const FLAT_TREE_JS = `(view) => {
24
24
  const style = (n, pseudo) => view.getComputedStyle(n, pseudo);
25
25
  const skipped = /^(script|style|template|link|meta|noscript|title|head|base)$/;
26
26
  const flatParent = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;
@@ -29,37 +29,121 @@ const FLAT_TREE_JS = `(view) => {
29
29
  while (p && style(p).display === 'contents') p = flatParent(p);
30
30
  return p;
31
31
  };
32
+ const via = new WeakMap();
33
+ const firstClass = (n) => (${LABEL_CLASSES_JS})(n)[0];
34
+ const label = (n) => n.localName + (n.id ? '#' + n.id : firstClass(n) ? '.' + firstClass(n) : '');
35
+ const slotText = (slot) => slot.assignedNodes({ flatten: true }).some((t) => t.nodeType === 3 && t.data.trim() !== '');
32
36
  const children = (n) => {
33
37
  const out = [];
34
- const add = (c) => {
38
+ const add = (c, through) => {
35
39
  if (skipped.test(c.localName)) return;
36
40
  if (c.localName === 'slot') {
37
41
  const assigned = c.assignedElements({ flatten: true });
38
- (assigned.length ? assigned : Array.from(c.children)).forEach(add);
42
+ if (assigned.length === 0 && slotText(c)) out.push(c);
43
+ else (assigned.length ? assigned : Array.from(c.children)).forEach((a) => add(a, label(c)));
39
44
  } else if (style(c).display === 'contents') {
40
- Array.from((c.shadowRoot || c).children).forEach(add);
45
+ Array.from((c.shadowRoot || c).children).forEach((a) => add(a, label(c) + ' (contents)'));
41
46
  } else {
47
+ if (through) via.set(c, through);
42
48
  out.push(c);
43
49
  }
44
50
  };
45
- Array.from((n.shadowRoot || n).children).forEach(add);
51
+ Array.from((n.shadowRoot || n).children).forEach((c) => add(c, null));
46
52
  return out;
47
53
  };
48
54
  const rendered = (n) => n.getClientRects().length > 0 &&
49
55
  (!n.checkVisibility || n.checkVisibility({ visibilityProperty: true }));
50
56
  const container = (n) => children(n).some((c) => rendered(c) && !/^inline/.test(style(c).display));
51
- return { style, flatParent, layoutParent, children, rendered, container };
57
+ return { style, skipped, flatParent, layoutParent, children, rendered, container, label, via, slotText };
58
+ }`;
59
+ /**
60
+ * Page-side holder of an element's text: the element whose styles draw most
61
+ * of the visible text in its flat tree (characters, whitespace aside), and
62
+ * the element whose child text nodes those are (for the rendered font).
63
+ * Text slotted into a shadow root is drawn with the slot's styles, and text
64
+ * of an element with a shadow root that is not slotted is not drawn, nor is
65
+ * text inside a fully transparent descendant (a measuring copy under a
66
+ * mask). Null when there is no visible text.
67
+ */
68
+ const TEXT_HOLDER_JS = `(el, tree) => {
69
+ const counts = new Map();
70
+ const parents = new Map();
71
+ let budget = 2000;
72
+ const shown = (n) => Number(tree.style(n).opacity) !== 0 && (tree.style(n).display === 'contents' || tree.rendered(n));
73
+ const nodesOf = (n) => {
74
+ if (n.localName !== 'slot') return Array.from((n.shadowRoot || n).childNodes);
75
+ const assigned = n.assignedNodes({ flatten: true });
76
+ return assigned.length ? assigned : Array.from(n.childNodes);
77
+ };
78
+ const visit = (n) => {
79
+ if (budget-- <= 0) return;
80
+ for (const c of nodesOf(n)) {
81
+ if (budget <= 0) return;
82
+ if (c.nodeType === 3) {
83
+ const length = c.data.replace(/\\s+/g, '').length;
84
+ if (length === 0) continue;
85
+ counts.set(n, (counts.get(n) || 0) + length);
86
+ if (!parents.has(n)) parents.set(n, c.parentElement || n);
87
+ } else if (c.nodeType === 1 && !tree.skipped.test(c.localName) && shown(c)) {
88
+ visit(c);
89
+ }
90
+ }
91
+ };
92
+ visit(el);
93
+ let best = null;
94
+ for (const [holder, length] of counts) if (!best || length > counts.get(best)) best = holder;
95
+ let total = 0;
96
+ for (const length of counts.values()) total += length;
97
+ return best && { style: best, font: parents.get(best), share: counts.get(best) / total };
98
+ }`;
99
+ /**
100
+ * Page-side: whether loaded `@font-face` (or `FontFace`) faces of the first
101
+ * family of an element's `font-family` cover the characters of its text
102
+ * (their `unicode-range`): then that web font draws the text, whatever
103
+ * name its file gives. A subset that lacks the text's characters (latin
104
+ * loaded, Cyrillic text) does not count.
105
+ */
106
+ const FAMILY_LOADED_JS = `(n, tree, text) => {
107
+ const unquote = (name) => name.trim().replace(/^["']|["']$/g, '').toLowerCase();
108
+ const match = /^\\s*("[^"]*"|'[^']*'|[^,]*)/.exec(tree.style(n).fontFamily);
109
+ const first = unquote(match ? match[1] : '');
110
+ if (!first || !n.ownerDocument.fonts) return false;
111
+ const ranges = [];
112
+ for (const face of n.ownerDocument.fonts) {
113
+ if (face.status !== 'loaded' || unquote(face.family) !== first) continue;
114
+ for (const part of face.unicodeRange.split(',')) {
115
+ const [from, to] = part.trim().replace(/^U\\+/i, '').split('-');
116
+ const low = parseInt(from.replace(/\\?/g, '0'), 16);
117
+ const high = parseInt((to || from).replace(/\\?/g, 'F'), 16);
118
+ if (!Number.isNaN(low) && !Number.isNaN(high)) ranges.push([low, high]);
119
+ }
120
+ }
121
+ if (ranges.length === 0) return false;
122
+ const covered = (code) => ranges.some(([low, high]) => code >= low && code <= high);
123
+ return Array.from(text.replace(/\\s+/g, '')).every((char) => covered(char.codePointAt(0)));
124
+ }`;
125
+ /**
126
+ * Page-side: whether a form control draws text of its own (a text field,
127
+ * a select, a button-like input); checkboxes, radios, ranges and color
128
+ * inputs do not. Buttons are not covered: their text is their content.
129
+ */
130
+ const TEXT_CONTROL_JS = `(el) => {
131
+ if (el.localName === 'textarea' || el.localName === 'select') return true;
132
+ return el.localName === 'input' && !/^(checkbox|radio|range|color|hidden|image)$/.test(el.type);
52
133
  }`;
53
134
  /**
54
135
  * Page-side text of an element: a form control's value or a select's chosen
55
136
  * options (masked for secrets, {@link SENSITIVE_FIELD_JS}), else its
56
- * `innerText` (`textContent` for SVG), masked when CSS hides it as a secret
137
+ * `innerText` (`textContent` for SVG; the flat tree's text for web
138
+ * components and slots, {@link FLAT_TEXT_JS}), masked when CSS hides it as a secret
57
139
  * (`-webkit-text-security`), whitespace collapsed. A container holding a
58
140
  * select or textarea gets its visible text without theirs, so a form's text
59
141
  * never carries a chosen option or typed text.
60
142
  */
61
143
  const TEXT_JS = `(el) => {
62
144
  const isSensitive = ${SENSITIVE_FIELD_JS};
145
+ const composed = ${COMPOSED_JS};
146
+ const flatText = ${FLAT_TEXT_JS};
63
147
  const clean = (text) => String(text || '').replace(/\\s+/g, ' ').trim().slice(0, 200);
64
148
  if (el.localName === 'input' && el.type === 'hidden') return '';
65
149
  if (el.localName === 'input' || el.localName === 'textarea') {
@@ -81,7 +165,8 @@ const TEXT_JS = `(el) => {
81
165
  return parts.join(' ');
82
166
  };
83
167
  const holdsControls = Boolean(el.querySelector && el.querySelector('select, textarea'));
84
- const raw = holdsControls ? outsideControls() : typeof el.innerText === 'string' ? el.innerText : el.textContent;
168
+ const ownText = () => (typeof el.innerText === 'string' ? el.innerText : el.textContent);
169
+ const raw = holdsControls ? outsideControls() : composed(el) ? flatText(el, 2000) : ownText();
85
170
  const text = clean(raw);
86
171
  const view = el.ownerDocument.defaultView;
87
172
  const security = view ? view.getComputedStyle(el).getPropertyValue('-webkit-text-security') : '';
@@ -100,14 +185,19 @@ const PLACEMENT_JS = `(el, tree) => {
100
185
  const r = el.getBoundingClientRect();
101
186
  const p = parent.getBoundingClientRect();
102
187
  const ps = tree.style(parent);
103
- const edge = (side) => parseFloat(ps['border-' + side + '-width']) + parseFloat(ps['padding-' + side]);
188
+ const zoom = parent.currentCSSZoom || 1;
189
+ const edge = (side) => (parseFloat(ps['border-' + side + '-width']) + parseFloat(ps['padding-' + side])) * zoom;
104
190
  const inParent = {
105
191
  left: r.left - (p.left + edge('left')),
106
192
  top: r.top - (p.top + edge('top')),
107
193
  right: (p.right - edge('right')) - r.right,
108
194
  bottom: (p.bottom - edge('bottom')) - r.bottom
109
195
  };
110
- const inFlow = (n) => n !== el && tree.rendered(n) && !/^(absolute|fixed)$/.test(tree.style(n).position);
196
+ const inFlow = (n) => {
197
+ if (n === el || !tree.rendered(n) || /^(absolute|fixed)$/.test(tree.style(n).position)) return false;
198
+ const box = n.getBoundingClientRect();
199
+ return box.width > 0 || box.height > 0;
200
+ };
111
201
  const siblings = tree.children(parent);
112
202
  const at = siblings.indexOf(el);
113
203
  const before = siblings.slice(0, Math.max(at, 0)).reverse().find(inFlow);
@@ -135,8 +225,10 @@ const BACKGROUNDS_JS = `(el, tree, view) => {
135
225
  let opacity = 1;
136
226
  for (let n = el; n && backgrounds.length < 60; n = tree.flatParent(n)) {
137
227
  const s = tree.style(n);
138
- backgrounds.push({ color: s.backgroundColor, image: s.backgroundImage !== 'none' });
139
- opacity *= Number(s.opacity) || 0;
228
+ const own = Number(s.opacity) || 0;
229
+ const background = { color: s.backgroundColor, image: s.backgroundImage !== 'none' };
230
+ backgrounds.push(own < 1 ? Object.assign(background, { opacity: own }) : background);
231
+ opacity *= own;
140
232
  }
141
233
  const doc = el.ownerDocument;
142
234
  const meta = doc.querySelector('meta[name="color-scheme"]');
@@ -146,6 +238,94 @@ const BACKGROUNDS_JS = `(el, tree, view) => {
146
238
  const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || prefersDark);
147
239
  return { backgrounds: backgrounds, canvasDark: canvasDark, opacity: opacity };
148
240
  }`;
241
+ /**
242
+ * Page-side hit test that also sees elements with `pointer-events: none`
243
+ * (hero images, decorative layers and blended duplicates often have it, and
244
+ * `elementsFromPoint` skips them): for the duration of `test`, a constructed
245
+ * stylesheet makes every element of the given roots (documents and shadow
246
+ * roots) hittable. It is added and removed within one script turn, so nothing
247
+ * is painted with it and no DOM mutation is recorded. A root that already has
248
+ * it (an outer call covering a whole page walk) is left as it is, so nested
249
+ * calls cost no extra style recalculation.
250
+ */
251
+ export const HIT_EVERYTHING_JS = `(roots, test) => {
252
+ const added = [];
253
+ for (const root of roots) {
254
+ if (!root || !root.adoptedStyleSheets) continue;
255
+ const view = (root.ownerDocument || root).defaultView;
256
+ try {
257
+ if (!view.__bdgHitSheet) {
258
+ view.__bdgHitSheet = new view.CSSStyleSheet();
259
+ view.__bdgHitSheet.replaceSync('* { pointer-events: auto !important; }');
260
+ }
261
+ if (root.adoptedStyleSheets.includes(view.__bdgHitSheet)) continue;
262
+ root.adoptedStyleSheets = [...root.adoptedStyleSheets, view.__bdgHitSheet];
263
+ added.push([root, view.__bdgHitSheet]);
264
+ } catch (e) {
265
+ continue;
266
+ }
267
+ }
268
+ try {
269
+ return test();
270
+ } finally {
271
+ for (const [root, sheet] of added) root.adoptedStyleSheets = root.adoptedStyleSheets.filter((s) => s !== sheet);
272
+ }
273
+ }`;
274
+ /**
275
+ * Page-side reasons the contrast of an element's text is approximate: a
276
+ * blend mode or filter on it or an ancestor, and, hit-testing the middle of
277
+ * its first line of text (else of its first box; only in the viewport), the
278
+ * nearest element below it that is not an ancestor, above the first opaque
279
+ * background of its own chain, and
280
+ * paints (a canvas, video, image, background) and the nearest one on top of
281
+ * it that paints (an overlay).
282
+ */
283
+ export const PAINT_RISKS_JS = `(el, tree, textParent) => {
284
+ const risks = [];
285
+ const chain = [];
286
+ for (let n = el; n && chain.length < 60; n = tree.flatParent(n)) chain.push(n);
287
+ for (const n of chain) {
288
+ const s = tree.style(n);
289
+ if (s.mixBlendMode !== 'normal') risks.push('mix-blend-mode ' + s.mixBlendMode + ' on ' + tree.label(n));
290
+ if (s.filter !== 'none') risks.push('filter on ' + tree.label(n));
291
+ }
292
+ const textNode = Array.from(textParent.childNodes).find((c) => c.nodeType === 3 && c.data.trim() !== '');
293
+ const range = el.ownerDocument.createRange();
294
+ if (textNode) range.selectNodeContents(textNode);
295
+ const box = tree.style(el).display === 'contents' ? tree.layoutParent(el) : el;
296
+ const rect = (textNode && range.getClientRects()[0]) || (box && box.getClientRects()[0]);
297
+ const view = el.ownerDocument.defaultView;
298
+ if (!rect) return risks;
299
+ const x = rect.left + rect.width / 2;
300
+ const y = rect.top + rect.height / 2;
301
+ if (x < 0 || y < 0 || x >= view.innerWidth || y >= view.innerHeight) return risks;
302
+ const root = el.getRootNode();
303
+ const hits = (${HIT_EVERYTHING_JS})([el.ownerDocument, root], () => (root.elementsFromPoint ? root : el.ownerDocument).elementsFromPoint(x, y));
304
+ const at = hits.findIndex((h) => chain.includes(h));
305
+ if (at < 0) return risks;
306
+ const clear = (color) => color === 'transparent' || /^rgba\\(.*,\\s*0\\)$/.test(color) || /\\/\\s*0\\)$/.test(color);
307
+ const opaque = (n) => {
308
+ const s = tree.style(n);
309
+ return /^rgb\\(/.test(s.backgroundColor) && s.backgroundImage === 'none' && Number(s.opacity) === 1;
310
+ };
311
+ const paints = (n) => {
312
+ const s = tree.style(n);
313
+ return /^(canvas|video|img|iframe|embed|object|svg)$/.test(n.localName) || s.backgroundImage !== 'none' || !clear(s.backgroundColor);
314
+ };
315
+ let behind = null;
316
+ for (const h of hits.slice(at)) {
317
+ if (chain.includes(h)) {
318
+ if (opaque(h)) break;
319
+ } else if (paints(h)) {
320
+ behind = h;
321
+ break;
322
+ }
323
+ }
324
+ if (behind) risks.push(tree.label(behind) + ' behind');
325
+ const onTop = hits.slice(0, at).find((h) => !el.contains(h) && paints(h));
326
+ if (onTop) risks.push(tree.label(onTop) + ' on top');
327
+ return risks;
328
+ }`;
149
329
  /**
150
330
  * Page-side child tree to `depth` levels: per element its label, size,
151
331
  * display, position relative to its parent's border box (like Figma's x/y
@@ -157,9 +337,8 @@ const BACKGROUNDS_JS = `(el, tree, view) => {
157
337
  const TREE_JS = `(el, tree, textOf, depth) => {
158
338
  let budget = ${TREE_NODE_CAP};
159
339
  let skipped = 0;
160
- const labelOf = (n) => n.localName + (n.id ? '#' + n.id : n.classList && n.classList.length ? '.' + n.classList[0] : '');
161
340
  const walk = (kids, level, origin) => {
162
- const shown = kids.filter(tree.rendered);
341
+ const shown = kids.filter((k) => tree.rendered(k) || k.localName === 'slot');
163
342
  const nodes = [];
164
343
  for (const k of shown) {
165
344
  if (budget <= 0) { skipped++; continue; }
@@ -170,7 +349,13 @@ const TREE_JS = `(el, tree, textOf, depth) => {
170
349
  };
171
350
  const nodeOf = (n, level, origin) => {
172
351
  const r = n.getBoundingClientRect();
173
- const node = { label: labelOf(n), x: r.left - origin.left, y: r.top - origin.top, w: r.width, h: r.height, display: tree.style(n).display };
352
+ const node = { label: tree.label(n), x: r.left - origin.left, y: r.top - origin.top, w: r.width, h: r.height, display: tree.style(n).display };
353
+ if (tree.via.has(n)) node.via = tree.via.get(n);
354
+ if (n.parentNode && n.parentNode.nodeType === 11 && n.parentNode.host) node.shadow = true;
355
+ if (n.localName === 'slot') {
356
+ node.text = n.assignedNodes({ flatten: true }).map((t) => t.textContent).join(' ').replace(/\\s+/g, ' ').trim().slice(0, 60);
357
+ return node;
358
+ }
174
359
  const kids = tree.children(n);
175
360
  if (!tree.container(n)) node.text = textOf(n).slice(0, 60);
176
361
  if (level >= depth) {
@@ -186,12 +371,49 @@ const TREE_JS = `(el, tree, textOf, depth) => {
186
371
  const top = walk(tree.children(el), 1, el.getBoundingClientRect());
187
372
  return { tree: top.nodes, hiddenChildren: top.hidden, treeSkipped: skipped };
188
373
  }`;
374
+ /**
375
+ * Page-side: rules for the element that set a property but sit under a
376
+ * `@media` or `@supports` condition that does not apply now (at most 5),
377
+ * from same-origin, `@import`ed, constructed and shadow-root stylesheets.
378
+ */
379
+ const INACTIVE_RULES_JS = `(el, names) => {
380
+ const view = el.ownerDocument.defaultView;
381
+ const found = [];
382
+ let budget = 20000;
383
+ const unmet = (rule) => {
384
+ if (rule.media && rule.media.mediaText && !view.matchMedia(rule.media.mediaText).matches) return '@media ' + rule.media.mediaText;
385
+ if (typeof CSSSupportsRule !== 'undefined' && rule instanceof CSSSupportsRule && !CSS.supports(rule.conditionText)) return '@supports ' + rule.conditionText;
386
+ return null;
387
+ };
388
+ const sets = (rule) => names.map((name) => rule.style.getPropertyValue(name).trim()).find((value) => value !== '');
389
+ const matches = (selector) => {
390
+ try { return el.matches(selector); } catch (e) { return false; }
391
+ };
392
+ const visit = (rules, condition) => {
393
+ for (const rule of Array.from(rules)) {
394
+ if (budget-- <= 0 || found.length >= 5) return;
395
+ if (typeof CSSImportRule !== 'undefined' && rule instanceof CSSImportRule) {
396
+ try { if (rule.styleSheet) visit(rule.styleSheet.cssRules, condition); } catch (e) { continue; }
397
+ continue;
398
+ }
399
+ const value = condition && rule.style && rule.selectorText ? sets(rule) : undefined;
400
+ if (value !== undefined && matches(rule.selectorText)) found.push({ selector: rule.selectorText, condition: condition, value: value });
401
+ if (rule.cssRules) visit(rule.cssRules, condition || unmet(rule));
402
+ }
403
+ };
404
+ for (const scope of new Set([el.getRootNode(), el.ownerDocument])) {
405
+ for (const sheet of [...Array.from(scope.styleSheets || []), ...Array.from(scope.adoptedStyleSheets || [])]) {
406
+ try { visit(sheet.cssRules, null); } catch (e) { continue; }
407
+ }
408
+ }
409
+ return found;
410
+ }`;
189
411
  /**
190
412
  * Page-side function run on the element (`this`), see {@link RawInspect}.
191
- * Arguments: tree depth, and the property names asked for with `--props`
192
- * (or null).
413
+ * Arguments: tree depth, the property names asked for with `--props` (or
414
+ * null) and the `--why` property (or null).
193
415
  */
194
- export const INSPECT_PAGE_JS = `function (depth, props) {
416
+ export const INSPECT_PAGE_JS = `function (depth, props, why) {
195
417
  const el = this;
196
418
  const view = el.ownerDocument.defaultView;
197
419
  const tree = (${FLAT_TREE_JS})(view);
@@ -199,6 +421,11 @@ export const INSPECT_PAGE_JS = `function (depth, props) {
199
421
  const describe = ${ELEMENT_DESCRIPTION_JS};
200
422
  const s = tree.style(el);
201
423
  const formControl = /^(input|textarea|select|button)$/.test(el.localName);
424
+ const textControl = (${TEXT_CONTROL_JS})(el);
425
+ const holder = textControl ? null : (${TEXT_HOLDER_JS})(el, tree);
426
+ const textChildren = tree.children(el).filter((c) => tree.rendered(c) && (c.textContent || '').trim() !== '').length;
427
+ const textual = textControl || Boolean(holder && (holder.style === el || (holder.share >= 0.9 && textChildren <= 1) || !tree.container(el)));
428
+ const textFrom = holder && holder.style !== el ? holder.style : el;
202
429
  const content = textOf(el);
203
430
  const parent = tree.layoutParent(el);
204
431
  const typedMap = el.computedStyleMap ? el.computedStyleMap() : null;
@@ -209,15 +436,26 @@ export const INSPECT_PAGE_JS = `function (depth, props) {
209
436
  classes: el.classList ? Array.from(el.classList) : [],
210
437
  context: (${ELEMENT_CONTEXT_JS})(el),
211
438
  content: content,
212
- textual: formControl || (content !== '' && !tree.container(el)),
439
+ textual: textual,
213
440
  formControl: formControl,
214
- replaced: el instanceof SVGElement || /^(img|video|canvas|iframe|embed|object|input|textarea|select|button|meter|progress)$/.test(el.localName),
441
+ rendered: textual && textFrom !== el
442
+ ? tree.style(textFrom).visibility !== 'hidden' && tree.rendered(tree.style(textFrom).display === 'contents' ? tree.layoutParent(textFrom) : textFrom)
443
+ : tree.rendered(el),
444
+ hasText: textControl || holder !== null,
445
+ replaced: el instanceof SVGElement || /^(img|video|audio|canvas|iframe|embed|object|input|textarea|select|button|meter|progress)$/.test(el.localName),
215
446
  svg: el.namespaceURI === 'http://www.w3.org/2000/svg',
216
- ownText: Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.data.trim() !== ''),
217
447
  typed: { width: typed('width'), height: typed('height') },
218
448
  parent: parent ? describe(parent) : undefined,
219
- animating: el.getAnimations ? [...new Set(el.getAnimations().filter((a) => a.playState === 'running').map((a) => a.transitionProperty || a.animationName || 'animation'))] : []
449
+ animating: el.getAnimations ? [...new Set(el.getAnimations().filter((a) => a.playState === 'running' && (!a.timeline || a.timeline === el.ownerDocument.timeline)).map((a) => a.transitionProperty || a.animationName || 'animation'))] : []
450
+ };
451
+ if (textFrom !== el) result.textHolder = tree.label(textFrom);
452
+ const clipped = (n) => {
453
+ const cs = tree.style(n);
454
+ if (cs.overflowX !== 'visible' && n.scrollWidth > n.clientWidth + 1) return true;
455
+ return cs.webkitLineClamp !== 'none' && cs.overflowY !== 'visible' && n.scrollHeight > n.clientHeight + 1;
220
456
  };
457
+ if (textual && !textControl && (clipped(el) || (textFrom !== el && clipped(textFrom)))) result.truncated = true;
458
+ if ((${FAMILY_LOADED_JS})(textFrom, tree, textOf(textFrom))) result.familyLoaded = true;
221
459
  if (/^(input|textarea)$/.test(el.localName) && el.placeholder && !el.value) {
222
460
  result.placeholder = el.placeholder.replace(/\\s+/g, ' ').trim();
223
461
  const placeholderStyle = tree.style(el, '::placeholder');
@@ -236,28 +474,115 @@ export const INSPECT_PAGE_JS = `function (depth, props) {
236
474
  return sides.map((side) => side.slice(name.length + 1) + ' ' + s.getPropertyValue(side).trim()).join(' / ');
237
475
  };
238
476
  result.props = Object.fromEntries(props.filter(known).map((name) => [name, valueOf(name)]));
477
+ result.rootFontSize = parseFloat(tree.style(el.ownerDocument.documentElement).fontSize) || 16;
239
478
  result.unknownProps = props.filter((name) => !known(name));
240
479
  }
480
+ if (why && !why.startsWith('--')) {
481
+ if (!CSS.supports(why, 'inherit')) result.unknownWhy = true;
482
+ else {
483
+ const probe = el.ownerDocument.createElement('div').style;
484
+ probe.setProperty(why, 'inherit');
485
+ if (probe.length > 1) {
486
+ result.whyLonghands = Array.from(probe);
487
+ result.whyComputed = s.getPropertyValue(why).trim();
488
+ }
489
+ const inactive = (${INACTIVE_RULES_JS})(el, [why].concat(result.whyLonghands || []));
490
+ if (inactive.length > 0) result.whyInactive = inactive;
491
+ }
492
+ }
241
493
  return Object.assign(result,
242
494
  (${PLACEMENT_JS})(el, tree),
243
- (${BACKGROUNDS_JS})(el, tree, view),
495
+ (${BACKGROUNDS_JS})(textual ? textFrom : el, tree, view),
496
+ textual ? { paintRisks: (${PAINT_RISKS_JS})(textFrom, tree, holder ? holder.font : el) } : {},
244
497
  (${TREE_JS})(el, tree, textOf, depth));
245
498
  }`;
246
499
  /**
247
500
  * Page-side function run on the element (`this`) that returns another
248
501
  * element whose styles CDP reads: `"parent"`, the parent that lays it out
249
- * (`display: contents` skipped, through shadow roots and slots), or
250
- * `"textHolder"`, for an element whose text is in a descendant
251
- * (`<button><span>Buy</span></button>`), the first element holding text,
252
- * whose rendered font is the one shown. Null when there is none.
502
+ * (`display: contents` skipped, through shadow roots and slots),
503
+ * `"textHolder"`, the element whose styles draw most of its text
504
+ * ({@link TEXT_HOLDER_JS}; the element itself when that is its own text), or
505
+ * `"fontHolder"`, the parent of that text's nodes (for the rendered font: a
506
+ * slot's text nodes belong to the host's light DOM). Null when there is none.
253
507
  */
254
508
  export const RELATED_NODE_JS = `function (which) {
255
509
  const tree = (${FLAT_TREE_JS})(this.ownerDocument.defaultView);
256
510
  if (which === 'parent') return tree.layoutParent(this);
257
- const walker = this.ownerDocument.createTreeWalker(this, NodeFilter.SHOW_TEXT);
258
- while (walker.nextNode()) {
259
- if (walker.currentNode.data.trim() !== '') return walker.currentNode.parentElement;
511
+ const holder = (${TEXT_HOLDER_JS})(this, tree);
512
+ if (!holder) return null;
513
+ return which === 'textHolder' ? holder.style : holder.font;
514
+ }`;
515
+ /**
516
+ * Page-side function run on the element (`this`): for each custom property
517
+ * name, a rule in the page's stylesheets (same-origin, `@import`ed,
518
+ * constructed and the element's shadow root's) that sets it, keyframes
519
+ * included: the first one that matches the element or an ancestor now
520
+ * (across shadow roots, so `:root` matches inside a component), else the
521
+ * first that would match without its state (`.btn:hover` for this `.btn`),
522
+ * else the first one, with the `@media`/`@supports` condition it is under
523
+ * when that does not apply. At most 20000 rules are read; cross-origin sheets are
524
+ * skipped.
525
+ */
526
+ export const VARIABLE_SETTERS_JS = `function (names) {
527
+ const el = this;
528
+ const view = el.ownerDocument.defaultView;
529
+ const matching = {};
530
+ const related = {};
531
+ const other = {};
532
+ let budget = 20000;
533
+ const closestAcross = (selector) => {
534
+ for (let n = el; n; n = n.getRootNode().host || null) {
535
+ if (n.closest(selector)) return true;
536
+ }
537
+ return false;
538
+ };
539
+ const matches = (selector) => {
540
+ try { return closestAcross(selector); } catch (e) { return null; }
541
+ };
542
+ const stateless = (selector) => selector.replace(/:(focus-visible|focus-within|focus|hover|active|visited|checked|target|open)(?![\\w-])/g, '');
543
+ const applies = (rule) => {
544
+ if (rule.media && rule.media.mediaText && !view.matchMedia(rule.media.mediaText).matches) return '@media ' + rule.media.mediaText;
545
+ if (typeof CSSSupportsRule !== 'undefined' && rule instanceof CSSSupportsRule && !CSS.supports(rule.conditionText)) return '@supports ' + rule.conditionText;
546
+ return null;
547
+ };
548
+ const record = (name, rule, keyframes, condition) => {
549
+ const selector = keyframes ? '@keyframes ' + keyframes : rule.selectorText || '';
550
+ const match = keyframes || condition ? false : matches(selector);
551
+ const setter = Object.assign(
552
+ { selector: selector, value: rule.style.getPropertyValue(name).trim(), matches: match },
553
+ keyframes ? { keyframes: keyframes } : {},
554
+ condition ? { condition: condition } : {}
555
+ );
556
+ if (match && !matching[name]) matching[name] = setter;
557
+ else if (!match && !keyframes && !related[name] && matches(stateless(selector))) related[name] = setter;
558
+ else if (!match && !other[name]) other[name] = setter;
559
+ };
560
+ const visit = (rules, keyframes, condition) => {
561
+ for (const rule of Array.from(rules)) {
562
+ if (budget-- <= 0 || names.every((name) => matching[name])) return;
563
+ if (typeof CSSImportRule !== 'undefined' && rule instanceof CSSImportRule) {
564
+ try { if (rule.styleSheet) visit(rule.styleSheet.cssRules, keyframes, condition); } catch (e) { continue; }
565
+ continue;
566
+ }
567
+ if (typeof CSSKeyframesRule !== 'undefined' && rule instanceof CSSKeyframesRule) {
568
+ visit(rule.cssRules, rule.name, condition);
569
+ continue;
570
+ }
571
+ if (rule.style) {
572
+ for (const name of names) {
573
+ if (!matching[name] && Array.from(rule.style).includes(name)) record(name, rule, keyframes, condition);
574
+ }
575
+ }
576
+ if (rule.cssRules) visit(rule.cssRules, keyframes, condition || applies(rule));
577
+ }
578
+ };
579
+ const sheets = [];
580
+ for (const scope of new Set([el.getRootNode(), el.ownerDocument])) {
581
+ sheets.push(...Array.from(scope.styleSheets || []), ...Array.from(scope.adoptedStyleSheets || []));
582
+ }
583
+ for (const sheet of sheets) {
584
+ try { visit(sheet.cssRules, null, null); } catch (e) { continue; }
260
585
  }
261
- return null;
586
+ return Object.assign({}, other, related, matching);
262
587
  }`;
263
588
  //# sourceMappingURL=inspectScripts.js.map
@@ -40,6 +40,9 @@ function toNode(raw) {
40
40
  h: Math.round(raw.h),
41
41
  ...(layout && { layout }),
42
42
  ...(raw.text && { text: rowText(raw.text) }),
43
+ ...(raw.display === 'contents' && { contents: true }),
44
+ ...(raw.via && { via: raw.via }),
45
+ ...(raw.shadow && { shadow: true }),
43
46
  ...(children && children.length > 0 && { children }),
44
47
  ...(raw.childCount && { childCount: raw.childCount }),
45
48
  ...(raw.hidden && { hiddenChildren: raw.hidden }),
@@ -50,10 +53,15 @@ function toNode(raw) {
50
53
  *
51
54
  * @param a - Row
52
55
  * @param b - Row
53
- * @returns True for the same label, size and layout
56
+ * @returns True for the same label, size, layout and path (slot, shadow root)
54
57
  */
55
58
  function identical(a, b) {
56
- return a.element === b.element && a.w === b.w && a.h === b.h && a.layout === b.layout;
59
+ return (a.element === b.element &&
60
+ a.w === b.w &&
61
+ a.h === b.h &&
62
+ a.layout === b.layout &&
63
+ a.via === b.via &&
64
+ a.shadow === b.shadow);
57
65
  }
58
66
  /**
59
67
  * Group runs of identical siblings into one row with a count. A group keeps
@@ -8,6 +8,7 @@
8
8
  import type { InspectWhy } from '../../ipc/protocol/inspectTypes.js';
9
9
  import { type Resolution } from './inspectCascade.js';
10
10
  import type { CascadeInput, PropertyGroup } from './inspectCascadeModel.js';
11
+ import type { CssHint } from './inspectHints.js';
11
12
  /**
12
13
  * `--why` for a property.
13
14
  *
@@ -16,5 +17,5 @@ import type { CascadeInput, PropertyGroup } from './inspectCascadeModel.js';
16
17
  * @param input - Matched rules, computed style and the label function
17
18
  * @returns One entry, or one per longhand when they differ
18
19
  */
19
- export declare function buildWhy(group: PropertyGroup, cascade: Map<string, Resolution>, input: CascadeInput): InspectWhy[];
20
+ export declare function buildWhy(group: PropertyGroup, cascade: Map<string, Resolution>, input: CascadeInput, hints?: readonly CssHint[]): InspectWhy[];
20
21
  //# sourceMappingURL=inspectWhyModel.d.ts.map