Skip to content

Math functions Chrome rejects at parse time are invalid CSS and dropped, as in Chrome 145 - #46

Merged
thejackshelton merged 5 commits into
masterfrom
border-width-percent
Oct 1, 2026
Merged

thejackshelton merged 5 commits into
masterfrom
border-width-percent

Conversation

@thejackshelton

@thejackshelton thejackshelton commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • css/math.ts adds mathInvalidity, a port of Blink's CSSMathExpressionNodeParser type checking at 145.0.7632.6. It covers:
    • CSSMathType add, multiply and Category with percent hints, and kAddSubtractResult;
    • the trig, stepped, exponential and sign function rules, rounding strategies, and sibling-index()/sibling-count();
    • kMaxExpressionDepth 100, unit categories, and the categories each consumer accepts.
      mathGrammarFor picks number, <line-width> length, length-percentage, or number-or-length-percentage per property.
  • stylesheet.ts parseValue returns invalid with the reason. Dragon now reports DRAGON_CSS_INVALID_VALUE and drops the declaration, so an earlier valid declaration wins, as in Chrome. parseMath runs the same predicate first, so the two can't disagree.
  • The V1 percent refusals for line widths and numbers now cover only what Chrome accepts: percentages that cancel by typed arithmetic, such as calc(5% / 5% * 1px). Those stay refused as unsupported, with a message that says so. Before, they all gave a misleading "percentage gaps" reason. The gap reason is unchanged, because the engine lacks percentage gaps and Chrome accepts them.
  • Never reported invalid, because the check doesn't decide them:
    • substitution functions;
    • anchor(), anchor-size(), calc-size() and progress();
    • some sqrt()/exp() folding cases.
  • Not covered: math nested inside grid track values or colour functions.

Evidence

  • 3322 Chrome 145 CSS.supports rows (22 properties × 151 values), in packages/dragon/test/math-validity-oracle/, which .macroscope/ignore.md ignores by the *-oracle/ shape. They match in both directions, except 4 values the check doesn't decide.
  • A cascade capture in ltr and rtl: border-left-width: 3px stays, and flex-grow: 2 wins with a 2:1 width split, over the invalid declarations that follow.
  • Capture tool: packages/parity/src/cli/math-validity-capture.ts. It lives in parity because dragon's tests may not launch a browser.

Passed (macOS, at 1f5a930, which includes master through #42)

  • pnpm typecheck
  • pnpm test: 135 files, 2948/2948
  • parity:capture: 306/306 fixtures, including the 2 new reject fixtures
  • tw:sweep: snapshot unchanged, with no utility lost
  • lanes.json unchanged, so no device run is needed

Test changes, with reasons

  • values-reject-number-length now expects DRAGON_CSS_INVALID_VALUE, because Chrome rejects calc(10px + 2).
  • The units.test.ts case with 100000 nested parentheses now expects the Chrome depth reason (Blink rejects past depth 100) instead of the token cap.
  • New reject fixtures: values-reject-invalid-line-width-percent and values-reject-invalid-flex-grow-percent.
  • No tolerance or check was loosened.

The cascade outcome is proven by a Chrome capture and a unit test, not a layout fixture. An invalid value is an error that blocks every output, so a layout fixture containing one can't compile.

🤖 Generated with Claude Code

Round 1 (fa307d1): the validity check keeps MAX_MATH_TOKENS, so it is bounded and returns undecided past the limit; parseMath refuses with the token reason. The 100000-nested-parens test is back to expecting the token-limit reason (its earlier retarget in this PR only held because of the unbounded tokenizer). pnpm test 2949/2949.

Note

Drop declarations with math functions Chrome rejects at parse time, as in Chrome 145

  • Adds a Chrome-style math validity check to parseMath and stylesheet value parsing. Declarations with math functions Chrome treats as invalid CSS are now dropped with a Chrome-compatible reason and fix, before Dragon's own support checks run.
  • Adds mathInvalidity and a ValidityParser in math.ts that model Blink's math functions, units, argument counts, nesting depth, and typing. Unmodeled or substitution functions are left undecided and fall through to the existing paths.
  • Replaces the boolean percentage flag with a PercentRefusal object, so line-width (border, outline, column-rule), number, and gap properties each report their own percentage refusal reason and suggested fix.
  • Adds parity fixtures for invalid percentage math in flex-grow and border-left-width, a Chrome capture CLI in math-validity-capture.ts, and a test suite in math-validity.test.ts driven by a captured Chrome oracle.
  • Behavioral Change: non-grid declarations containing recognized math Chrome rejects are now dropped as invalid values instead of being handled by Dragon's later unit/function refusal paths; invalid declarations no longer override earlier valid ones in the cascade.

Macroscope summarized fa307d1.

…ne-width> in border, outline and column-rule widths, invalid in number properties (Chrome drops both), unsupported percentage gaps in gaps
…INVALID_VALUE and dropped, as Chrome drops them

css/math.ts mathInvalidity ports Blink's CSSMathExpressionNodeParser type checking at 145.0.7632.6 (CSSMathType typed
arithmetic, kAddSubtractResult, the math function rules, kMaxExpressionDepth, the consumers' category checks) as one
predicate; parseValue returns 'invalid' with its reason and parseMath refuses with the same reason. A 3322-row corpus captured
from Chrome (packages/parity/src/cli/math-validity-capture.ts) pins both directions; reject fixtures for a border width and
flex-grow; values-reject-number-length now expects DRAGON_CSS_INVALID_VALUE.
…agon/test/math-validity-oracle/ (an ignored *-oracle/ shape for review)
Comment thread packages/dragon/src/css/math.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a substantial Chrome-compatible math-validity parser and wires it into production stylesheet parsing, changing which declarations survive and affect the cascade. Although supported by extensive oracle tests, the breadth and complexity of the runtime behavior warrant focused human review.

You can add or adjust custom eligibility rules. Learn more.

…NS; past it, or on a code point it does not tokenize, it decides nothing

tokenize(source, true) returned an unbounded token array, so a calc() of millions of terms was tokenized whole before
parseMath's own bound refused it. The bound now holds on both paths (validity returns null past it, and parseMath refuses
with the token reason, as before T131); the character check moved into the bounded tokenizer, which in validity mode takes
only css-syntax-3 white space.
@thejackshelton
thejackshelton merged commit 69756ef into master Oct 1, 2026
4 checks passed
thejackshelton added a commit that referenced this pull request Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant