css: apply @layer block rules to the cascade#2719
Open
navidemad wants to merge 1 commit into
Open
Conversation
Rules wrapped in @layer blocks (named, dotted sub-layer names, or anonymous) were dropped from the cascade: StyleManager.parseSheet applied top-level style rules and recursed into @media only, so @layer fell into the silent else arm on both the _css_rules path and the <style> text path. @layer blocks now flatten into the cascade like a matching @media body, recursing into nested @media/@layer in both directions under the shared MAX_AT_RULE_NESTING depth cap. The statement form (@layer a, b;) declares ordering only and stays inert. Layer-priority ordering (css-cascade-5 section 6.4) is deliberately out of scope for this change: flattening with the existing specificity + source-order tie-breaking fixes the common failure mode of layered rules vanishing entirely (e.g. Tailwind v4 emits its whole compiled output inside @layer, so .hidden { display: none } never applied and checkVisibility()/getComputedStyle() reported hidden elements as visible). cssRules-inserted layer rules (insertRule/replaceSync) get their own CSSRule.Type variant so the cascade pass can recognize them; their JS-visible type stays 0 per CSSOM section 6.4.1 (no legacy code assigned), matching Chrome's CSSLayerBlockRule. Closes lightpanda-io#2718
7d47a8e to
2ed49ec
Compare
Contributor
Author
|
Bumping this since it's been quiet for a few weeks. The impact keeps getting more common: Tailwind v4 emits most of its utilities inside The branch is still conflict-free against main. Happy to rebase onto the recent StyleManager/computed-style work (#2836, #2864) and re-verify, or rework the approach if you'd prefer a different shape. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Style rules wrapped in CSS Cascade Layers blocks (
@layer name { … }, anonymous@layer { … }, dotted sub-layer names like@layer theme.dark { … }) never participated in the cascade. Adisplay: noneinside@layerleft the element reportingcheckVisibility() === trueand computeddisplay === "block", where Chrome reports it hidden. This PR flattens@layerblock contents into the cascade the same way a matching@mediabody is applied today, on both stylesheet paths (<style>text andcssRulesviainsertRule/replaceSync), recursing into nested@media/@layerin both directions. The statement form (@layer a, b;) declares ordering only and stays inert.Closes #2718.
Root cause
StyleManager.parseSheethandled.stylerules and recursed into@mediaonly.@layerfell into the silentelse => {}arm on the_css_rulespath, and was skipped by the keyword check on the<style>text path. On the CSSOM side,atRuleTypeForhad nolayermapping, socssRules-inserted layer rules degraded to.unknownand were unrecognizable to the cascade pass even if it had wanted to recurse.flowchart LR A[stylesheet parsed] --> B[style rules added] A --> C[media blocks recursed] A --> D[layer blocks hit else arm] D --> E[layered rules vanish from cascade] style D fill:#fdd,color:#000 style E fill:#fdd,color:#000Fix
src/browser/StyleManager.zig: inparseSheet, dispatch@layeron both paths. A newapplyLayerAtRuleflattens the block body (the statement form has no block, so it's a no-op). Two helpers are now shared with the existing@mediapath:applyInnerRules(the nested-rule loop) andatRuleBlock(the comment-aware block-boundary scan), so@media-in-@layerand@layer-in-@mediaboth work under the shared depth cap (MAX_MEDIA_NESTINGgeneralized toMAX_AT_RULE_NESTING). The mutually-recursive group carries an explicitAllocator.Errorerror set, since inferred sets can't resolve the cycle.src/browser/webapi/css/CSSRule.zig: new.layervariant inType;getType()returns0for it (CSSOM §6.4.1:@layerpostdates the legacy numeric constants, matching Chrome'sCSSLayerBlockRule). The variant sits after the last legacy-numbered type, so no existingrule.typevalue shifts.src/browser/webapi/css/CSSStyleSheet.zig:atRuleTypeFormapslayerto.layer.flowchart LR A[stylesheet parsed] --> B[style rules added] A --> C[media blocks recursed] A --> D[layer blocks flattened] D --> E[layered rules join cascade] style D fill:#dfd,color:#000 style E fill:#dfd,color:#000Deliberate scope
Layers are flattened. Full layer-priority ordering (css-cascade-5 §6.4, where earlier-declared layers lose to later ones regardless of specificity) is not implemented: ties keep breaking on specificity + document order, and the layer-name prelude is not retained. The gap this leaves is narrow.
StyleManager's cascade only tracks the visibility filter (display,visibility,opacity,pointer-events), so a missing inter-layer priority can only matter when two of those declarations collide across layers with inverted specificity, and it never touchesgetComputedStylefor any other property. Flattening already fixes the common failure mode, layered rules vanishing entirely (Tailwind v4, for instance, emits its whole compiled output inside@layer). Proper inter-layer ordering can follow separately if a real page needs it.Test
src/browser/webapi/css/CSSStyleSheet.zig→test "WebApi: layer @-rule cascade"runs the new fixturesrc/browser/tests/css/layer_at_rule_cascade.html(named/anonymous/dotted blocks, statement form,@media-in-@layermatching and non-matching,@layer-in-@media,@layer-in-@layer,!important,visibility: hidden,insertRule/replaceSyncpaths,rule.type === 0, 50-deep nesting cap). Every assertion targets a rule that only reaches the cascade once@layeris flattened, so the fixture is meaningless without the fix.zig build teston this branch passes 978/979, including the new test. The single failure (WebApi: Frames, an iframe-over-HTTP test) is pre-existing onmain— cleanmainis 977/978 with the same failure — and isn't touched by this PR, which only changes CSS files.checkVisibility()returnstruefor adisplay:nonerule inside@layer utilities, where the identical unlayered rule returnsfalse.Notes
src/browser/tests/css/media_at_rule_cascade.htmlhas a one-line comment touch-up (MAX_MEDIA_NESTING→MAX_AT_RULE_NESTING) tracking the constant rename.