Skip to content

Commit e9c08f5

Browse files
pfaffeDevtools-frontend LUCI CQ
authored andcommitted
[css] Move CSS property value parsing to CSSProperty
This allows us to access the information the parsing information across the codebase. That significantly simplifies reasoning about value (computations) for CSS debugging tools to be added. Bug: 373554278 Change-Id: I351b3c42b74c35c089e40a00d8e6096bfad9cf7e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6219313 Reviewed-by: Ergün Erdoğmuş <[email protected]> Commit-Queue: Philip Pfaffe <[email protected]>
1 parent ffe4589 commit e9c08f5

10 files changed

+267
-184
lines changed

front_end/core/sdk/CSSMatchedStyles.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {CSSMetadata, cssMetadata, CSSWideKeyword} from './CSSMetadata.js';
99
import type {CSSModel} from './CSSModel.js';
1010
import {CSSProperty} from './CSSProperty.js';
1111
import * as PropertyParser from './CSSPropertyParser.js';
12+
import {BaseVariableMatcher} from './CSSPropertyParserMatchers.js';
1213
import {
1314
CSSFontPaletteValuesRule,
1415
CSSKeyframesRule,
@@ -1166,7 +1167,7 @@ class DOMInheritanceCascade {
11661167
const record = sccRecord.add(nodeCascade, variableName);
11671168

11681169
const matching = PropertyParser.BottomUpTreeMatching.walk(
1169-
ast, [new PropertyParser.VariableMatcher((match: PropertyParser.VariableMatch) => {
1170+
ast, [new BaseVariableMatcher(match => {
11701171
const parentStyle = definedValue.declaration.style;
11711172
const nodeCascade = this.#styleToNodeCascade.get(parentStyle);
11721173
if (!nodeCascade) {

front_end/core/sdk/CSSProperty.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,35 @@ import * as TextUtils from '../../models/text_utils/text_utils.js';
77
import * as Common from '../common/common.js';
88
import * as HostModule from '../host/host.js';
99
import * as Platform from '../platform/platform.js';
10+
import * as Root from '../root/root.js';
1011

12+
import type {CSSMatchedStyles} from './CSSMatchedStyles.js';
1113
import {cssMetadata, GridAreaRowRegex} from './CSSMetadata.js';
1214
import type {Edit} from './CSSModel.js';
13-
import {stripComments} from './CSSPropertyParser.js';
15+
import {type BottomUpTreeMatching, matchDeclaration, stripComments} from './CSSPropertyParser.js';
16+
import {
17+
AnchorFunctionMatcher,
18+
AngleMatcher,
19+
AutoBaseMatcher,
20+
BezierMatcher,
21+
ColorMatcher,
22+
ColorMixMatcher,
23+
CSSWideKeywordMatcher,
24+
FlexGridMatcher,
25+
FontMatcher,
26+
GridTemplateMatcher,
27+
LengthMatcher,
28+
LightDarkColorMatcher,
29+
LinearGradientMatcher,
30+
LinkableNameMatcher,
31+
PositionAnchorMatcher,
32+
PositionTryMatcher,
33+
SelectFunctionMatcher,
34+
ShadowMatcher,
35+
StringMatcher,
36+
URLMatcher,
37+
VariableMatcher
38+
} from './CSSPropertyParserMatchers.js';
1439
import type {CSSStyleDeclaration} from './CSSStyleDeclaration.js';
1540

1641
export const enum Events {
@@ -88,6 +113,39 @@ export class CSSProperty extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
88113
return result;
89114
}
90115

116+
parseValue(matchedStyles: CSSMatchedStyles, computedStyles: Map<string, string>|null): BottomUpTreeMatching|null {
117+
if (!this.parsedOk) {
118+
return null;
119+
}
120+
const matchers = [
121+
new VariableMatcher(matchedStyles, this.ownerStyle),
122+
new ColorMatcher(() => computedStyles?.get('color') ?? null),
123+
new ColorMixMatcher(),
124+
new URLMatcher(),
125+
new AngleMatcher(),
126+
new LinkableNameMatcher(),
127+
new BezierMatcher(),
128+
new StringMatcher(),
129+
new ShadowMatcher(),
130+
new CSSWideKeywordMatcher(this, matchedStyles),
131+
new LightDarkColorMatcher(),
132+
new GridTemplateMatcher(),
133+
new LinearGradientMatcher(),
134+
new AnchorFunctionMatcher(),
135+
new PositionAnchorMatcher(),
136+
new FlexGridMatcher(),
137+
new PositionTryMatcher(),
138+
new LengthMatcher(),
139+
new SelectFunctionMatcher(),
140+
new AutoBaseMatcher(),
141+
];
142+
143+
if (Root.Runtime.experiments.isEnabled('font-editor')) {
144+
matchers.push(new FontMatcher());
145+
}
146+
return matchDeclaration(this.name, this.value, matchers);
147+
}
148+
91149
private ensureRanges(): void {
92150
if (this.#nameRangeInternal && this.#valueRangeInternal) {
93151
return;

front_end/core/sdk/CSSPropertyParser.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ describe('CSSPropertyParser', () => {
117117

118118
const matchedResult = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [matcher]);
119119
const matchedNode = TreeSearch.find(ast, n => matchedResult.getMatch(n) instanceof matcher.matchType);
120-
const match = matchedNode && matchedResult.getMatch(matchedNode);
120+
const match = (matchedNode && matchedResult.getMatch(matchedNode) as T | undefined) ?? null;
121121

122122
return {
123123
ast,
124-
match: match instanceof matcher.matchType ? match : null,
124+
match,
125125
text: Printer.walk(ast).get(),
126126
};
127127
}
@@ -491,7 +491,7 @@ describe('CSSPropertyParser', () => {
491491
of ['var(--a)', 'var(--a, 123)', 'var(--a, calc(1+1))', 'var(--a, var(--b))', 'var(--a, var(--b, 123))',
492492
'var(--a, a b c)']) {
493493
const {ast, match, text} =
494-
matchSingleValue('width', succeed, new SDK.CSSPropertyParser.VariableMatcher(() => ''));
494+
matchSingleValue('width', succeed, new SDK.CSSPropertyParserMatchers.BaseVariableMatcher(() => ''));
495495

496496
assert.exists(ast, succeed);
497497
assert.exists(match, text);
@@ -502,7 +502,8 @@ describe('CSSPropertyParser', () => {
502502
assert.strictEqual(match.fallback.map(n => ast.text(n)).join(' '), fallback.join(', '));
503503
}
504504
for (const fail of ['var', 'var(--a, 123, 123)', 'var(a)', 'var(--a']) {
505-
const {match, text} = matchSingleValue('width', fail, new SDK.CSSPropertyParser.VariableMatcher(() => ''));
505+
const {match, text} =
506+
matchSingleValue('width', fail, new SDK.CSSPropertyParserMatchers.BaseVariableMatcher(() => ''));
506507

507508
assert.isNull(match, text);
508509
}

front_end/core/sdk/CSSPropertyParser.ts

Lines changed: 9 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import type * as Platform from '../../core/platform/platform.js';
66
import * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js';
77

8+
import {TextMatcher} from './CSSPropertyParserMatchers.js';
9+
810
const globalValues = new Set<string>(['inherit', 'initial', 'unset']);
911

1012
const tagRegexp = /[\x20-\x7E]{4}/;
@@ -495,104 +497,6 @@ export namespace ASTUtils {
495497
}
496498
}
497499

498-
export class VariableMatch implements Match {
499-
constructor(
500-
readonly text: string,
501-
readonly node: CodeMirror.SyntaxNode,
502-
readonly name: string,
503-
readonly fallback: CodeMirror.SyntaxNode[],
504-
readonly matching: BottomUpTreeMatching,
505-
readonly computedTextCallback: (match: VariableMatch, matching: BottomUpTreeMatching) => string | null,
506-
) {
507-
}
508-
509-
computedText(): string|null {
510-
return this.computedTextCallback(this, this.matching);
511-
}
512-
}
513-
514-
// clang-format off
515-
export class VariableMatcher extends matcherBase(VariableMatch) {
516-
// clang-format on
517-
readonly #computedTextCallback: (match: VariableMatch, matching: BottomUpTreeMatching) => string | null;
518-
constructor(computedTextCallback: (match: VariableMatch, matching: BottomUpTreeMatching) => string | null) {
519-
super();
520-
this.#computedTextCallback = computedTextCallback;
521-
}
522-
523-
override matches(node: CodeMirror.SyntaxNode, matching: BottomUpTreeMatching): VariableMatch|null {
524-
const callee = node.getChild('Callee');
525-
const args = node.getChild('ArgList');
526-
if (node.name !== 'CallExpression' || !callee || (matching.ast.text(callee) !== 'var') || !args) {
527-
return null;
528-
}
529-
530-
const [lparenNode, nameNode, ...fallbackOrRParenNodes] = ASTUtils.children(args);
531-
532-
if (lparenNode?.name !== '(' || nameNode?.name !== 'VariableName') {
533-
return null;
534-
}
535-
536-
if (fallbackOrRParenNodes.length <= 1 && fallbackOrRParenNodes[0]?.name !== ')') {
537-
return null;
538-
}
539-
540-
let fallback: CodeMirror.SyntaxNode[] = [];
541-
if (fallbackOrRParenNodes.length > 1) {
542-
if (fallbackOrRParenNodes.shift()?.name !== ',') {
543-
return null;
544-
}
545-
if (fallbackOrRParenNodes.pop()?.name !== ')') {
546-
return null;
547-
}
548-
fallback = fallbackOrRParenNodes;
549-
if (fallback.length === 0) {
550-
return null;
551-
}
552-
if (fallback.some(n => n.name === ',')) {
553-
return null;
554-
}
555-
}
556-
557-
const varName = matching.ast.text(nameNode);
558-
if (!varName.startsWith('--')) {
559-
return null;
560-
}
561-
562-
return new VariableMatch(matching.ast.text(node), node, varName, fallback, matching, this.#computedTextCallback);
563-
}
564-
}
565-
566-
export class TextMatch implements Match {
567-
computedText?: () => string;
568-
constructor(readonly text: string, readonly node: CodeMirror.SyntaxNode) {
569-
if (node.name === 'Comment') {
570-
this.computedText = () => '';
571-
}
572-
}
573-
render(): Node[] {
574-
return [document.createTextNode(this.text)];
575-
}
576-
}
577-
578-
// clang-format off
579-
class TextMatcher extends matcherBase(TextMatch) {
580-
// clang-format on
581-
override accepts(): boolean {
582-
return true;
583-
}
584-
override matches(node: CodeMirror.SyntaxNode, matching: BottomUpTreeMatching): TextMatch|null {
585-
if (!node.firstChild || node.name === 'NumberLiteral' /* may have a Unit child */) {
586-
// Leaf node, just emit text
587-
const text = matching.ast.text(node);
588-
if (text.length) {
589-
return new TextMatch(text, node);
590-
}
591-
}
592-
return null;
593-
}
594-
}
595-
596500
function declaration(rule: string): CodeMirror.SyntaxNode|null {
597501
return cssParser.parse(rule).topNode.getChild('RuleSet')?.getChild('Block')?.getChild('Declaration') ?? null;
598502
}
@@ -648,6 +552,13 @@ export function tokenizePropertyName(name: string): string|null {
648552
return nodeText(propertyName, rule);
649553
}
650554

555+
export function matchDeclaration(name: string, value: string, matchers: Matcher<Match>[]): BottomUpTreeMatching|null {
556+
const ast = tokenizeDeclaration(name, value);
557+
const matchedResult = ast && BottomUpTreeMatching.walk(ast, matchers);
558+
ast?.trailingNodes.forEach(n => matchedResult?.matchText(n));
559+
return matchedResult;
560+
}
561+
651562
export class TreeSearch extends TreeWalker {
652563
#found: CodeMirror.SyntaxNode|null = null;
653564
#predicate: (node: CodeMirror.SyntaxNode) => boolean;

front_end/core/sdk/CSSPropertyParserMatchers.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function injectVariableSubstitutions(variables: Record<string, string>) {
6565
if (!resolvedValue) {
6666
return getMatch.call(this, node);
6767
}
68-
return new SDK.CSSPropertyParser.VariableMatch(
68+
return new SDK.CSSPropertyParserMatchers.BaseVariableMatch(
6969
this.ast.text(node), node, resolvedValue.varName, [], this, () => resolvedValue.value);
7070
});
7171
}

0 commit comments

Comments
 (0)