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.
- package/.claude/skills/bdg/SKILL.md +268 -0
- package/README.md +148 -74
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/formInteraction.js +36 -6
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +158 -38
- package/dist/commands/dom/index.js +4 -1
- package/dist/commands/dom/screenshot.js +10 -6
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.js +1 -1
- package/dist/commands/installSkill.d.ts +20 -0
- package/dist/commands/installSkill.js +87 -0
- package/dist/commands/optionBehaviors.js +21 -6
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/optionTypes.d.ts +5 -1
- package/dist/commands/start.d.ts +13 -0
- package/dist/commands/start.js +19 -2
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +5 -0
- package/dist/daemon/session/commandRegistry.js +7 -1
- package/dist/daemon/session/plugins.js +4 -52
- package/dist/daemon.js +7986 -6848
- package/dist/errors/messages.d.ts +50 -4
- package/dist/errors/messages.js +94 -5
- package/dist/index.js +709 -190
- package/dist/ipc/client.d.ts +4 -0
- package/dist/ipc/client.js +8 -0
- package/dist/ipc/protocol/auditTypes.d.ts +129 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +23 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +4 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +4 -1
- package/dist/runtime/dom/actionEffects.js +8 -4
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +36 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +215 -0
- package/dist/runtime/dom/auditScripts.d.ts +107 -0
- package/dist/runtime/dom/auditScripts.js +112 -0
- package/dist/runtime/dom/elementGeometry.d.ts +8 -2
- package/dist/runtime/dom/elementGeometry.js +24 -8
- package/dist/runtime/dom/elementInfo.d.ts +3 -2
- package/dist/runtime/dom/elementInfo.js +8 -2
- package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +88 -23
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +89 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +3 -0
- package/dist/runtime/dom/inspectModel.js +30 -7
- package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
- package/dist/runtime/dom/inspectPaintModel.js +180 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +85 -12
- package/dist/runtime/dom/inspectScripts.js +314 -28
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +31 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
- package/dist/runtime/dom/reactEventHelpers.js +27 -9
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/types.d.ts +12 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +106 -0
- package/dist/ui/formatters/dom.d.ts +1 -1
- package/dist/ui/formatters/dom.js +6 -3
- package/dist/ui/formatters/inspect.js +42 -15
- package/dist/ui/formatters/installSkill.d.ts +11 -0
- package/dist/ui/formatters/installSkill.js +31 -0
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/messages/commands.d.ts +44 -7
- package/dist/ui/messages/commands.js +83 -11
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +4 -2
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* writing). With no declaration of its own, an inherited property comes from
|
|
14
14
|
* the nearest ancestor that declares it.
|
|
15
15
|
*/
|
|
16
|
+
import { truncateByLength } from '../../utils/strings.js';
|
|
16
17
|
/**
|
|
17
18
|
* The four side longhands of a pattern.
|
|
18
19
|
*
|
|
@@ -64,9 +65,37 @@ const SHORTHANDS = {
|
|
|
64
65
|
`border-${side}`,
|
|
65
66
|
['width', 'style', 'color'].map((part) => `border-${side}-${part}`),
|
|
66
67
|
])),
|
|
67
|
-
background: [
|
|
68
|
+
background: [
|
|
69
|
+
'background-color',
|
|
70
|
+
'background-image',
|
|
71
|
+
'background-position-x',
|
|
72
|
+
'background-position-y',
|
|
73
|
+
'background-size',
|
|
74
|
+
'background-repeat',
|
|
75
|
+
'background-origin',
|
|
76
|
+
'background-clip',
|
|
77
|
+
'background-attachment',
|
|
78
|
+
],
|
|
68
79
|
font: ['font-style', 'font-weight', 'font-size', 'line-height', 'font-family'],
|
|
69
80
|
flex: ['flex-grow', 'flex-shrink', 'flex-basis'],
|
|
81
|
+
'flex-flow': ['flex-direction', 'flex-wrap'],
|
|
82
|
+
outline: ['outline-width', 'outline-style', 'outline-color'],
|
|
83
|
+
'text-decoration': [
|
|
84
|
+
'text-decoration-line',
|
|
85
|
+
'text-decoration-style',
|
|
86
|
+
'text-decoration-color',
|
|
87
|
+
'text-decoration-thickness',
|
|
88
|
+
],
|
|
89
|
+
transition: [
|
|
90
|
+
'transition-property',
|
|
91
|
+
'transition-duration',
|
|
92
|
+
'transition-timing-function',
|
|
93
|
+
'transition-delay',
|
|
94
|
+
'transition-behavior',
|
|
95
|
+
],
|
|
96
|
+
'background-position': ['background-position-x', 'background-position-y'],
|
|
97
|
+
'list-style': ['list-style-position', 'list-style-image', 'list-style-type'],
|
|
98
|
+
columns: ['column-width', 'column-count'],
|
|
70
99
|
'place-items': ['align-items', 'justify-items'],
|
|
71
100
|
'place-content': ['align-content', 'justify-content'],
|
|
72
101
|
'place-self': ['align-self', 'justify-self'],
|
|
@@ -91,35 +120,31 @@ const LOGICAL_SIDES = {
|
|
|
91
120
|
'inline-start': 'left',
|
|
92
121
|
'inline-end': 'right',
|
|
93
122
|
};
|
|
94
|
-
/**
|
|
123
|
+
/** Longhands that inherit (CSS, SVG and the prefixed ones Chrome computes) */
|
|
95
124
|
const INHERITED = new Set([
|
|
96
|
-
'color',
|
|
97
|
-
'font-family',
|
|
98
|
-
'font-
|
|
99
|
-
'font-
|
|
100
|
-
'font-
|
|
101
|
-
'font-variant',
|
|
102
|
-
'font-
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
|
|
119
|
-
'pointer-events',
|
|
120
|
-
'user-select',
|
|
121
|
-
'-webkit-text-security',
|
|
122
|
-
]);
|
|
125
|
+
'color caret-color accent-color color-scheme forced-color-adjust print-color-adjust',
|
|
126
|
+
'font-family font-size font-style font-weight font-stretch font-size-adjust font-kerning',
|
|
127
|
+
'font-optical-sizing font-palette font-language-override font-feature-settings',
|
|
128
|
+
'font-variation-settings font-variant font-variant-caps font-variant-ligatures',
|
|
129
|
+
'font-variant-numeric font-variant-east-asian font-variant-alternates',
|
|
130
|
+
'font-variant-position font-variant-emoji font-synthesis-weight font-synthesis-style',
|
|
131
|
+
'font-synthesis-small-caps line-height letter-spacing word-spacing text-align',
|
|
132
|
+
'text-align-last text-indent text-justify text-transform text-shadow text-rendering',
|
|
133
|
+
'text-size-adjust text-underline-position text-underline-offset text-decoration-skip-ink',
|
|
134
|
+
'text-emphasis-style text-emphasis-color text-emphasis-position text-orientation',
|
|
135
|
+
'text-wrap-mode text-wrap-style text-spacing-trim text-autospace white-space-collapse',
|
|
136
|
+
'word-break overflow-wrap line-break hyphens hyphenate-character hyphenate-limit-chars',
|
|
137
|
+
'tab-size direction writing-mode ruby-position ruby-align visibility cursor',
|
|
138
|
+
'pointer-events user-select interpolate-size list-style-type list-style-position',
|
|
139
|
+
'list-style-image quotes border-collapse border-spacing caption-side empty-cells orphans',
|
|
140
|
+
'widows image-rendering fill fill-opacity fill-rule stroke stroke-width stroke-opacity',
|
|
141
|
+
'stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit',
|
|
142
|
+
'paint-order marker-start marker-mid marker-end clip-rule color-interpolation',
|
|
143
|
+
'color-interpolation-filters color-rendering shape-rendering text-anchor dominant-baseline',
|
|
144
|
+
'math-depth math-style math-shift scrollbar-color text-combine-upright',
|
|
145
|
+
'-webkit-text-security -webkit-text-fill-color',
|
|
146
|
+
'-webkit-text-stroke-color -webkit-text-stroke-width',
|
|
147
|
+
].flatMap((names) => names.split(' ')));
|
|
123
148
|
/**
|
|
124
149
|
* The longhands of a shorthand.
|
|
125
150
|
*
|
|
@@ -133,10 +158,30 @@ export function shorthandLonghands(name) {
|
|
|
133
158
|
* Whether a property inherits.
|
|
134
159
|
*
|
|
135
160
|
* @param property - Longhand
|
|
136
|
-
* @
|
|
161
|
+
* @param notInherited - Custom properties registered with `inherits: false`
|
|
162
|
+
* @returns True for inherited properties (custom properties inherit unless
|
|
163
|
+
* registered otherwise)
|
|
164
|
+
*/
|
|
165
|
+
export function isInherited(property, notInherited) {
|
|
166
|
+
if (property.startsWith('--'))
|
|
167
|
+
return !notInherited?.has(property);
|
|
168
|
+
return INHERITED.has(property);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Custom properties registered not to inherit, with `@property` or
|
|
172
|
+
* `CSS.registerProperty`.
|
|
173
|
+
*
|
|
174
|
+
* @param matched - `CSS.getMatchedStylesForNode` response
|
|
175
|
+
* @returns Their names
|
|
137
176
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
177
|
+
function nonInheritingProperties(matched) {
|
|
178
|
+
const fromRules = (matched.cssPropertyRules ?? [])
|
|
179
|
+
.filter((rule) => rule.style.cssProperties.some((property) => property.name === 'inherits' && property.value.trim() === 'false'))
|
|
180
|
+
.map((rule) => rule.propertyName.text);
|
|
181
|
+
const fromScript = (matched.cssPropertyRegistrations ?? [])
|
|
182
|
+
.filter((registration) => !registration.inherits)
|
|
183
|
+
.map((registration) => registration.propertyName);
|
|
184
|
+
return new Set([...fromRules, ...fromScript]);
|
|
140
185
|
}
|
|
141
186
|
/**
|
|
142
187
|
* The physical longhand a logical property sets.
|
|
@@ -211,23 +256,43 @@ function declarationsOf(style, source) {
|
|
|
211
256
|
});
|
|
212
257
|
}
|
|
213
258
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
259
|
+
* The selector of a rule's list that matched with the highest specificity
|
|
260
|
+
* (the one the cascade uses: `.title` of `h1, .title`).
|
|
261
|
+
*
|
|
262
|
+
* @param match - CDP rule match
|
|
263
|
+
* @returns Selector
|
|
264
|
+
*/
|
|
265
|
+
function strongestMatchingSelector(match) {
|
|
266
|
+
const weight = (value) => {
|
|
267
|
+
const s = value?.specificity;
|
|
268
|
+
return s ? s.a * 1e6 + s.b * 1e3 + s.c : -1;
|
|
269
|
+
};
|
|
270
|
+
const selectors = match.rule.selectorList.selectors;
|
|
271
|
+
const matching = match.matchingSelectors.map((index) => selectors[index]);
|
|
272
|
+
return (matching.reduce((best, value) => (weight(value) > weight(best) ? value : best), matching[0]) ??
|
|
273
|
+
selectors[0]);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Source of a matched rule: its matching selector (the strongest of a list),
|
|
277
|
+
* origin, stylesheet, layer (nested ones as `outer.inner`), the media,
|
|
278
|
+
* container, `@supports` and `@scope` conditions it is under (`not all and
|
|
279
|
+
* (…)`, as Chrome writes `not (…)`, shortened) and the selector's specificity.
|
|
218
280
|
*
|
|
219
281
|
* @param match - CDP rule match
|
|
220
282
|
* @returns Declaration source
|
|
221
283
|
*/
|
|
222
284
|
function ruleSource(match) {
|
|
223
285
|
const { rule } = match;
|
|
224
|
-
const matching =
|
|
286
|
+
const matching = strongestMatchingSelector(match);
|
|
225
287
|
const selector = matching?.text ?? rule.selectorList.text;
|
|
226
288
|
const layer = rule.layers?.map((l) => l.text || '(anonymous)').join('.');
|
|
227
|
-
const condition = [
|
|
228
|
-
.map((c) => c.text.replace(/^not all and /, 'not '))
|
|
229
|
-
.
|
|
289
|
+
const condition = [
|
|
290
|
+
...[...(rule.media ?? []), ...(rule.containerQueries ?? [])].map((c) => c.text.replace(/^not all and /, 'not ')),
|
|
291
|
+
...(rule.supports ?? []).map((c) => `supports ${c.text}`),
|
|
292
|
+
...(rule.scopes ?? []).map((c) => `scope ${c.text}`),
|
|
293
|
+
].join(' and ');
|
|
230
294
|
const specificity = matching?.specificity;
|
|
295
|
+
const declarations = rule.style.cssText;
|
|
231
296
|
return {
|
|
232
297
|
kind: 'rule',
|
|
233
298
|
selector,
|
|
@@ -236,6 +301,9 @@ function ruleSource(match) {
|
|
|
236
301
|
...(rule.styleSheetId && { styleSheetId: rule.styleSheetId }),
|
|
237
302
|
...(layer && { layer }),
|
|
238
303
|
...(condition && { condition }),
|
|
304
|
+
...(declarations && {
|
|
305
|
+
rule: { selector: collapse(rule.selectorList.text), declarations: collapse(declarations) },
|
|
306
|
+
}),
|
|
239
307
|
};
|
|
240
308
|
}
|
|
241
309
|
/**
|
|
@@ -248,9 +316,23 @@ function orderedDeclarations(entry) {
|
|
|
248
316
|
return [
|
|
249
317
|
...declarationsOf(entry.attributesStyle, { kind: 'attribute' }),
|
|
250
318
|
...(entry.matchedCSSRules ?? []).flatMap((match) => declarationsOf(match.rule.style, ruleSource(match))),
|
|
251
|
-
...declarationsOf(entry.inlineStyle, {
|
|
319
|
+
...declarationsOf(entry.inlineStyle, {
|
|
320
|
+
kind: 'inline',
|
|
321
|
+
...(entry.inlineStyle?.cssText && {
|
|
322
|
+
rule: { declarations: collapse(entry.inlineStyle.cssText) },
|
|
323
|
+
}),
|
|
324
|
+
}),
|
|
252
325
|
];
|
|
253
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Text with runs of whitespace collapsed to one space.
|
|
329
|
+
*
|
|
330
|
+
* @param text - CSS text
|
|
331
|
+
* @returns Collapsed, trimmed
|
|
332
|
+
*/
|
|
333
|
+
function collapse(text) {
|
|
334
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
335
|
+
}
|
|
254
336
|
/**
|
|
255
337
|
* Whether a declaration comes from the page's authors (not the browser's
|
|
256
338
|
* own stylesheet or an extension's).
|
|
@@ -337,6 +419,7 @@ export function ownProperties(matched) {
|
|
|
337
419
|
export function resolveCascade(matched, properties) {
|
|
338
420
|
const own = orderedDeclarations(matched);
|
|
339
421
|
const ancestors = (matched.inherited ?? []).map(orderedDeclarations);
|
|
422
|
+
const notInherited = nonInheritingProperties(matched);
|
|
340
423
|
const resolved = new Map();
|
|
341
424
|
for (const property of properties) {
|
|
342
425
|
const ranked = byPrecedence(own.filter((d) => d.property === property));
|
|
@@ -345,7 +428,7 @@ export function resolveCascade(matched, properties) {
|
|
|
345
428
|
resolved.set(property, { winner, overridden });
|
|
346
429
|
continue;
|
|
347
430
|
}
|
|
348
|
-
resolved.set(property, inheritedWinner(property, ancestors));
|
|
431
|
+
resolved.set(property, inheritedWinner(property, ancestors, notInherited));
|
|
349
432
|
}
|
|
350
433
|
return resolved;
|
|
351
434
|
}
|
|
@@ -354,11 +437,12 @@ export function resolveCascade(matched, properties) {
|
|
|
354
437
|
*
|
|
355
438
|
* @param property - Longhand
|
|
356
439
|
* @param ancestors - Declarations of each ancestor, nearest first
|
|
440
|
+
* @param notInherited - Custom properties registered not to inherit
|
|
357
441
|
* @returns The winner and the declarations it beat on that ancestor (none for
|
|
358
442
|
* non-inherited properties or when no ancestor sets it)
|
|
359
443
|
*/
|
|
360
|
-
function inheritedWinner(property, ancestors) {
|
|
361
|
-
if (!isInherited(property))
|
|
444
|
+
function inheritedWinner(property, ancestors, notInherited) {
|
|
445
|
+
if (!isInherited(property, notInherited))
|
|
362
446
|
return { overridden: [] };
|
|
363
447
|
for (const [depth, declarations] of ancestors.entries()) {
|
|
364
448
|
const ranked = byPrecedence(declarations.filter((d) => d.property === property)).map((declaration) => ({ ...declaration, ancestor: depth + 1 }));
|
|
@@ -368,4 +452,90 @@ function inheritedWinner(property, ancestors) {
|
|
|
368
452
|
}
|
|
369
453
|
return { overridden: [] };
|
|
370
454
|
}
|
|
455
|
+
/** Longest rule given whole; a longer one (minified CSS) is cut to the declaration */
|
|
456
|
+
const RULE_TEXT_LENGTH = 300;
|
|
457
|
+
/**
|
|
458
|
+
* The rule a declaration is in, as written: whole when short, else its
|
|
459
|
+
* selector and that declaration (`.btn { … background-color:var(--bs-btn-bg); … }`).
|
|
460
|
+
*
|
|
461
|
+
* @param declaration - Declaration
|
|
462
|
+
* @returns `{ rule }`, or nothing for a browser, extension or attribute style
|
|
463
|
+
*/
|
|
464
|
+
export function ruleField(declaration) {
|
|
465
|
+
const { rule } = declaration.source;
|
|
466
|
+
if (!rule || !isAuthor(declaration))
|
|
467
|
+
return {};
|
|
468
|
+
const whole = ruleText(rule.selector, rule.declarations);
|
|
469
|
+
if (whole.length <= RULE_TEXT_LENGTH)
|
|
470
|
+
return { rule: whole };
|
|
471
|
+
const own = lastDeclarationOf(rule.declarations, declaration.via ?? declaration.property);
|
|
472
|
+
if (!own)
|
|
473
|
+
return { rule: truncateByLength(whole, RULE_TEXT_LENGTH) };
|
|
474
|
+
const selector = rule.selector === undefined ? undefined : truncateByLength(rule.selector, 80);
|
|
475
|
+
return { rule: ruleText(selector, `… ${truncateByLength(own, RULE_TEXT_LENGTH - 100)}; …`) };
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* A rule's text: `selector { declarations }`, or `style="declarations"`.
|
|
479
|
+
*
|
|
480
|
+
* @param selector - Selector (absent: the style attribute)
|
|
481
|
+
* @param declarations - Declarations
|
|
482
|
+
* @returns Text
|
|
483
|
+
*/
|
|
484
|
+
function ruleText(selector, declarations) {
|
|
485
|
+
return selector === undefined
|
|
486
|
+
? `style="${declarations.replaceAll('"', '\\"')}"`
|
|
487
|
+
: `${selector} { ${declarations} }`;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The last declaration of a property in a declaration block (the one that
|
|
491
|
+
* counts when it is repeated), split at semicolons outside strings and
|
|
492
|
+
* parentheses (`url(data:image/png;base64,…)` stays whole).
|
|
493
|
+
*
|
|
494
|
+
* @param declarations - Declaration block
|
|
495
|
+
* @param property - Property as written (custom properties are case-sensitive)
|
|
496
|
+
* @returns The declaration, without its semicolon
|
|
497
|
+
*/
|
|
498
|
+
function lastDeclarationOf(declarations, property) {
|
|
499
|
+
const name = (part) => {
|
|
500
|
+
const raw = part.slice(0, part.indexOf(':')).trim();
|
|
501
|
+
return raw.startsWith('--') ? raw : raw.toLowerCase();
|
|
502
|
+
};
|
|
503
|
+
const wanted = property.startsWith('--') ? property : property.toLowerCase();
|
|
504
|
+
return splitDeclarations(declarations)
|
|
505
|
+
.filter((part) => part.includes(':') && name(part) === wanted)
|
|
506
|
+
.at(-1);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Split a declaration block at its top-level semicolons.
|
|
510
|
+
*
|
|
511
|
+
* @param block - Declarations
|
|
512
|
+
* @returns Declarations, trimmed, empty ones dropped
|
|
513
|
+
*/
|
|
514
|
+
function splitDeclarations(block) {
|
|
515
|
+
const parts = [];
|
|
516
|
+
let depth = 0;
|
|
517
|
+
let quote;
|
|
518
|
+
let start = 0;
|
|
519
|
+
for (let i = 0; i < block.length; i++) {
|
|
520
|
+
const char = block[i];
|
|
521
|
+
if (quote) {
|
|
522
|
+
if (char === '\\')
|
|
523
|
+
i++;
|
|
524
|
+
else if (char === quote)
|
|
525
|
+
quote = undefined;
|
|
526
|
+
}
|
|
527
|
+
else if (char === '"' || char === "'")
|
|
528
|
+
quote = char;
|
|
529
|
+
else if (char === '(')
|
|
530
|
+
depth++;
|
|
531
|
+
else if (char === ')')
|
|
532
|
+
depth = Math.max(0, depth - 1);
|
|
533
|
+
else if (char === ';' && depth === 0) {
|
|
534
|
+
parts.push(block.slice(start, i));
|
|
535
|
+
start = i + 1;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
parts.push(block.slice(start));
|
|
539
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
540
|
+
}
|
|
371
541
|
//# sourceMappingURL=inspectCascade.js.map
|
|
@@ -8,6 +8,7 @@ import type { Protocol } from '../../connection/typed-cdp.js';
|
|
|
8
8
|
import type { InspectResult } from '../../ipc/protocol/inspectTypes.js';
|
|
9
9
|
import { type Declaration } from './inspectCascade.js';
|
|
10
10
|
import type { StyleMap } from './inspectLayoutModel.js';
|
|
11
|
+
import type { InactiveRule } from './inspectScripts.js';
|
|
11
12
|
/** Longhands reported together, so a shorthand can stand for its sides */
|
|
12
13
|
export interface PropertyGroup {
|
|
13
14
|
shorthand?: string;
|
|
@@ -25,8 +26,15 @@ export interface CascadeInput {
|
|
|
25
26
|
why?: string;
|
|
26
27
|
/** `--props` names: `--rules` then covers only these */
|
|
27
28
|
props?: string[];
|
|
29
|
+
/** Longhands of the `--why` shorthand as the browser expands it */
|
|
30
|
+
whyLonghands?: string[];
|
|
31
|
+
/** Computed value of the `--why` shorthand, as the page writes it */
|
|
32
|
+
whyComputed?: string;
|
|
33
|
+
/** Rules that set the `--why` property under a condition that does not apply now */
|
|
34
|
+
whyInactive?: InactiveRule[];
|
|
28
35
|
/** Check for declarations that have no effect (default true) */
|
|
29
36
|
hints?: boolean;
|
|
37
|
+
/** A form control that draws text (its font is checked against the parent's) */
|
|
30
38
|
formControl?: boolean;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* property, sides set by one declaration merged into the shorthand) and
|
|
5
5
|
* `--why` (every declaration of one property).
|
|
6
6
|
*/
|
|
7
|
-
import { normalizeProperty } from './inspectAllStyles.js';
|
|
8
|
-
import { physicalName, ownProperties, resolveCascade, shorthandLonghands, } from './inspectCascade.js';
|
|
7
|
+
import { collapsedValue, normalizeProperty } from './inspectAllStyles.js';
|
|
8
|
+
import { physicalName, ownProperties, ruleField, resolveCascade, shorthandLonghands, } from './inspectCascade.js';
|
|
9
9
|
import { formControlFontHints, inactiveHints, undefinedVariableHints, } from './inspectHints.js';
|
|
10
|
+
import { substituteVariables } from './inspectVariables.js';
|
|
10
11
|
import { buildWhy } from './inspectWhyModel.js';
|
|
11
12
|
const SIDES = ['top', 'right', 'bottom', 'left'];
|
|
12
13
|
const sides = (pattern) => SIDES.map((side) => pattern.replace('{side}', side));
|
|
@@ -33,6 +34,11 @@ const RULE_GROUPS = [
|
|
|
33
34
|
},
|
|
34
35
|
{ longhands: ['background-color'] },
|
|
35
36
|
{ longhands: ['background-image'] },
|
|
37
|
+
{ longhands: ['background-size'] },
|
|
38
|
+
{
|
|
39
|
+
shorthand: 'background-position',
|
|
40
|
+
longhands: ['background-position-x', 'background-position-y'],
|
|
41
|
+
},
|
|
36
42
|
{ longhands: ['color'] },
|
|
37
43
|
{ longhands: ['font-family'] },
|
|
38
44
|
{ longhands: ['font-size'] },
|
|
@@ -63,8 +69,10 @@ const RULE_GROUPS = [
|
|
|
63
69
|
* @returns `hints` (empty when checked and nothing found), `rules` and `why`
|
|
64
70
|
*/
|
|
65
71
|
export function buildCascadeFields(input) {
|
|
66
|
-
const ruleGroups = input.rules
|
|
67
|
-
|
|
72
|
+
const ruleGroups = input.rules
|
|
73
|
+
? (input.props?.map((name) => propertyGroup(name)) ?? RULE_GROUPS)
|
|
74
|
+
: [];
|
|
75
|
+
const whyGroup = input.why ? propertyGroup(input.why, input.whyLonghands) : undefined;
|
|
68
76
|
const wanted = [
|
|
69
77
|
...new Set([
|
|
70
78
|
...ownProperties(input.matched),
|
|
@@ -73,40 +81,59 @@ export function buildCascadeFields(input) {
|
|
|
73
81
|
]),
|
|
74
82
|
];
|
|
75
83
|
const cascade = resolveCascade(input.matched, wanted);
|
|
76
|
-
const
|
|
84
|
+
const found = collectHints(cascade, input);
|
|
85
|
+
const hints = input.hints === false ? undefined : found.map((hint) => toInspectHint(hint, input));
|
|
77
86
|
const rules = buildRules(ruleGroups, cascade, input);
|
|
78
87
|
return {
|
|
79
88
|
...(hints && { hints }),
|
|
80
89
|
...(rules.length > 0 && { rules }),
|
|
81
|
-
...(whyGroup && { why: buildWhy(whyGroup, cascade, input) }),
|
|
90
|
+
...(whyGroup && { why: withInactive(buildWhy(whyGroup, cascade, input, found), input) }),
|
|
82
91
|
};
|
|
83
92
|
}
|
|
84
93
|
/**
|
|
85
94
|
* The longhands a property name stands for.
|
|
86
95
|
*
|
|
87
96
|
* @param name - Shorthand, longhand, logical or custom property
|
|
97
|
+
* @param expanded - Its longhands as the browser expands it, for a
|
|
98
|
+
* shorthand bdg does not list (`transition`, `grid-template`)
|
|
88
99
|
* @returns Group (a shorthand with its longhands, or one physical longhand)
|
|
89
100
|
*/
|
|
90
|
-
function propertyGroup(name) {
|
|
91
|
-
const longhands = shorthandLonghands(name);
|
|
101
|
+
function propertyGroup(name, expanded) {
|
|
102
|
+
const longhands = shorthandLonghands(name) ?? (expanded && [...new Set(expanded.map(physicalName))]);
|
|
92
103
|
return longhands
|
|
93
104
|
? { shorthand: name, longhands: [...longhands] }
|
|
94
105
|
: { longhands: [physicalName(name)] };
|
|
95
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The `--why` answer with the rules that would set the property under a
|
|
109
|
+
* condition that does not apply now (on its first entry).
|
|
110
|
+
*
|
|
111
|
+
* @param whys - `--why` entries
|
|
112
|
+
* @param input - Page-side findings
|
|
113
|
+
* @returns Entries
|
|
114
|
+
*/
|
|
115
|
+
function withInactive(whys, input) {
|
|
116
|
+
const [first, ...rest] = whys;
|
|
117
|
+
if (!first || !input.whyInactive?.length)
|
|
118
|
+
return whys;
|
|
119
|
+
return [{ ...first, inactive: input.whyInactive }, ...rest];
|
|
120
|
+
}
|
|
96
121
|
/**
|
|
97
122
|
* Hints: declarations with no effect, and var() of unset custom properties;
|
|
98
123
|
* none without the computed style to check them against.
|
|
99
124
|
*
|
|
100
125
|
* @param cascade - Resolved properties
|
|
101
|
-
* @param input - Styles
|
|
102
|
-
* @returns Hints
|
|
126
|
+
* @param input - Styles
|
|
127
|
+
* @returns Hints with their declarations
|
|
103
128
|
*/
|
|
104
|
-
function
|
|
129
|
+
function collectHints(cascade, input) {
|
|
105
130
|
if (input.style['display'] === undefined)
|
|
106
131
|
return [];
|
|
132
|
+
const declaredDisplay = cascade.get('display')?.winner?.value;
|
|
107
133
|
const ctx = {
|
|
108
134
|
style: input.style,
|
|
109
135
|
parentStyle: input.parentStyle,
|
|
136
|
+
declaredDisplay: declaredDisplay === input.style['display'] ? undefined : declaredDisplay,
|
|
110
137
|
replaced: input.replaced,
|
|
111
138
|
formControl: input.formControl === true,
|
|
112
139
|
};
|
|
@@ -114,14 +141,26 @@ function buildHints(cascade, input) {
|
|
|
114
141
|
...inactiveHints(cascade, ctx),
|
|
115
142
|
...undefinedVariableHints(cascade, input.style),
|
|
116
143
|
...formControlFontHints(cascade, ctx),
|
|
117
|
-
]
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* A hint as the result shows it, with where its declaration is.
|
|
148
|
+
*
|
|
149
|
+
* @param hint - Hint
|
|
150
|
+
* @param input - The label function
|
|
151
|
+
* @returns Result hint
|
|
152
|
+
*/
|
|
153
|
+
function toInspectHint(hint, input) {
|
|
154
|
+
return {
|
|
118
155
|
kind: hint.kind,
|
|
119
156
|
property: hint.property,
|
|
120
157
|
value: hint.value,
|
|
121
158
|
reason: hint.reason,
|
|
122
159
|
fix: hint.fix,
|
|
160
|
+
...(hint.only && { only: hint.only }),
|
|
161
|
+
...(hint.variables && { variables: hint.variables }),
|
|
123
162
|
source: input.label(hint.declaration),
|
|
124
|
-
}
|
|
163
|
+
};
|
|
125
164
|
}
|
|
126
165
|
/**
|
|
127
166
|
* `--rules`: one row per property set by an author declaration; one row for
|
|
@@ -139,16 +178,22 @@ function buildRules(groups, cascade, input) {
|
|
|
139
178
|
const rows = groups.flatMap((group) => {
|
|
140
179
|
const resolutions = group.longhands.map((longhand) => cascade.get(longhand));
|
|
141
180
|
const winners = resolutions.map((resolution) => resolution?.winner);
|
|
142
|
-
const grouped = group.shorthand !== undefined ? groupedRow(
|
|
143
|
-
if (grouped)
|
|
144
|
-
|
|
181
|
+
const grouped = group.shorthand !== undefined ? groupedRow(winners) : undefined;
|
|
182
|
+
if (grouped) {
|
|
183
|
+
const longhands = shorthandLonghands(grouped.property) ?? group.longhands;
|
|
184
|
+
const partly = longhands.some((longhand) => {
|
|
185
|
+
const owner = cascade.get(longhand)?.winner;
|
|
186
|
+
return (owner !== undefined && !(sameSource(owner, grouped) && owner.via === grouped.property));
|
|
187
|
+
});
|
|
188
|
+
return [toRule(grouped.property, grouped, resolutions[0], input, longhands, partly)];
|
|
189
|
+
}
|
|
145
190
|
return group.longhands.flatMap((longhand, i) => {
|
|
146
191
|
const winner = winners[i];
|
|
147
|
-
if (!winner || winner.source.origin === 'user-agent')
|
|
192
|
+
if (!winner || (winner.source.origin === 'user-agent' && !input.props))
|
|
148
193
|
return [];
|
|
149
194
|
const unexpanded = winner.via !== undefined && winner.value === winner.written;
|
|
150
195
|
return unexpanded && winner.via
|
|
151
|
-
? [toRule(winner.via, winner, resolutions[i], input)]
|
|
196
|
+
? [toRule(winner.via, winner, resolutions[i], input, [longhand])]
|
|
152
197
|
: [toRule(longhand, winner, resolutions[i], input)];
|
|
153
198
|
});
|
|
154
199
|
});
|
|
@@ -163,28 +208,20 @@ function buildRules(groups, cascade, input) {
|
|
|
163
208
|
}
|
|
164
209
|
/**
|
|
165
210
|
* One row for a group of longhands that a single author declaration sets:
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* Chrome could not expand (`var()`) as written.
|
|
211
|
+
* that declaration as written (`padding: 4px 8px`; a wider shorthand like
|
|
212
|
+
* `border: none` once, not once per `border-width`/`-style`/`-color` group).
|
|
169
213
|
*
|
|
170
|
-
* @param shorthand - The group's shorthand
|
|
171
214
|
* @param winners - Winning declaration of each longhand
|
|
172
215
|
* @returns The row's declaration, or undefined when the sides differ in source
|
|
173
216
|
*/
|
|
174
|
-
function groupedRow(
|
|
217
|
+
function groupedRow(winners) {
|
|
175
218
|
const [first] = winners;
|
|
176
219
|
if (!first?.via || first.source.origin === 'user-agent')
|
|
177
220
|
return undefined;
|
|
178
221
|
const same = winners.every((w) => w && w.via === first.via && sameSource(w, first));
|
|
179
222
|
if (!same)
|
|
180
223
|
return undefined;
|
|
181
|
-
|
|
182
|
-
if (first.via === shorthand || first.value === written) {
|
|
183
|
-
return { ...first, property: first.via, value: written };
|
|
184
|
-
}
|
|
185
|
-
const values = winners.map((w) => w?.value ?? '');
|
|
186
|
-
const value = values.every((v) => v === first.value) ? first.value : values.join(' ');
|
|
187
|
-
return { ...first, property: shorthand, value };
|
|
224
|
+
return { ...first, property: first.via, value: first.written ?? first.value };
|
|
188
225
|
}
|
|
189
226
|
/**
|
|
190
227
|
* Whether two declarations come from the same place.
|
|
@@ -200,6 +237,26 @@ function sameSource(a, b) {
|
|
|
200
237
|
a.source.line === b.source.line &&
|
|
201
238
|
a.source.column === b.source.column);
|
|
202
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* The computed value of a row shown as a shorthand or logical property,
|
|
242
|
+
* from its longhands: as `--all` writes it (`padding 4 8`), else the
|
|
243
|
+
* longhands' values with runs of equal ones written once (`border` →
|
|
244
|
+
* `2px solid red`).
|
|
245
|
+
*
|
|
246
|
+
* @param property - Shorthand or logical property
|
|
247
|
+
* @param longhands - Its longhands
|
|
248
|
+
* @param style - Computed longhands
|
|
249
|
+
* @returns Value, or undefined when no longhand has one
|
|
250
|
+
*/
|
|
251
|
+
function shorthandValue(property, longhands, style) {
|
|
252
|
+
const collapsed = collapsedValue(property, style);
|
|
253
|
+
if (collapsed !== undefined)
|
|
254
|
+
return collapsed;
|
|
255
|
+
const values = longhands.map((longhand) => style[longhand]);
|
|
256
|
+
if (values.some((value) => value === undefined))
|
|
257
|
+
return undefined;
|
|
258
|
+
return values.filter((value, i) => i === 0 || value !== values[i - 1]).join(' ');
|
|
259
|
+
}
|
|
203
260
|
/**
|
|
204
261
|
* A `--rules` row.
|
|
205
262
|
*
|
|
@@ -207,21 +264,38 @@ function sameSource(a, b) {
|
|
|
207
264
|
* @param winner - Winning declaration
|
|
208
265
|
* @param resolution - Its resolution (for what it overrides)
|
|
209
266
|
* @param input - Computed style and the label function
|
|
267
|
+
* @param longhands - The longhands the row stands for (a shorthand's value comes from them)
|
|
268
|
+
* @param partly - Other declarations win some of its longhands: the value shown is
|
|
269
|
+
* the declaration's own, `var()` substituted, not the computed one
|
|
210
270
|
* @returns Rule
|
|
211
271
|
*/
|
|
212
|
-
function toRule(property, winner, resolution, input) {
|
|
213
|
-
const
|
|
272
|
+
function toRule(property, winner, resolution, input, longhands = [property], partly = false) {
|
|
273
|
+
const substituted = substituteVariables(winner.value, input.style);
|
|
274
|
+
const computed = partly
|
|
275
|
+
? substituted.includes('var(')
|
|
276
|
+
? undefined
|
|
277
|
+
: substituted
|
|
278
|
+
: (input.style[property] ?? shorthandValue(property, longhands, input.style));
|
|
214
279
|
const overrides = [
|
|
215
280
|
...new Set((resolution?.overridden ?? [])
|
|
216
281
|
.filter((d) => d.source.origin !== 'user-agent')
|
|
217
|
-
.map((d) => d.source.selector
|
|
282
|
+
.map((d) => d.source.selector !== undefined && d.source.selector === winner.source.selector
|
|
283
|
+
? input.label(d)
|
|
284
|
+
: (d.source.selector ?? d.source.kind))),
|
|
218
285
|
];
|
|
219
286
|
return {
|
|
220
287
|
property,
|
|
221
288
|
value: winner.value,
|
|
222
289
|
...(winner.value.includes('var(') &&
|
|
223
|
-
computed !== undefined && {
|
|
290
|
+
computed !== undefined && {
|
|
291
|
+
computed: `${normalizeProperty(property, computed)}${partly ? ' (partly overridden)' : ''}`,
|
|
292
|
+
}),
|
|
293
|
+
...(property === 'display' &&
|
|
294
|
+
computed !== undefined &&
|
|
295
|
+
computed !== winner.value &&
|
|
296
|
+
/^(inline|table-|ruby)/.test(winner.value) && { computed: `${computed} (blockified)` }),
|
|
224
297
|
source: input.label(winner),
|
|
298
|
+
...ruleField(winner),
|
|
225
299
|
...(overrides.length > 0 && { overrides }),
|
|
226
300
|
...(winner.ancestor !== undefined && { inherited: winner.ancestor }),
|
|
227
301
|
...(winner.important && { important: true }),
|