Repository navigation
Fix parsing of pseudo-elements and pseudo-classes after ::details-content - #1308
rorshopping wants to merge 3 commits into
Conversation
Element-backed pseudo-elements such as ::details-content behave like a
type selector, and can be followed by other pseudo-elements and
pseudo-classes:
::details-content::before { color: red }
details::details-content::after { content: '' }
This matches the css-pseudo-4 spec section on element-backed
pseudo-elements and the behavior of browsers (Firefox, Chrome).
Also allow functional pseudo-classes such as :lang() and :dir() after
::part() and element-backed pseudo-elements, matching upstream selectors
(Firefox bug 1960561).
| // https://github.com/parcel-bundler/lightningcss/issues/1244 | ||
| minify_test("::details-content::before {color:red}", "::details-content:before{color:red}"); | ||
| minify_test("::details-content::after {color:red}", "::details-content:after{color:red}"); | ||
| minify_test("::details-content::marker {color:red}", "::details-content::marker{color:red}"); |
There was a problem hiding this comment.
What about ::details-content::before::marker and ::part(foo)::details-content::before?
See https://chromium-review.googlesource.com/c/chromium/src/+/5823774
There was a problem hiding this comment.
Overall, this requires a comprehensive consideration of the relationship with other pseudo-elements.
There was a problem hiding this comment.
Good call — I've reworked this to comprehensively model the relationships between pseudo-elements, mirroring the current upstream selectors parser in servo/stylo (where Firefox implements ::details-content, and where the :lang()-after-::part() fix landed) together with the rules from the Chromium CL you linked.
Both cases now parse:
::details-content::before::marker—::before/::afternow accept a single following pseudo-element, restricted to a newvalid_after_before_or_after()allowlist that currently only contains::marker(css-pseudo-4, and upstream stylo does the same).::before::marker,::after::markerand::details-content::before::markerparse;::before::before,::marker::marker,::before::details-contentetc. remain errors.::part(foo)::details-content::before— element-backed pseudo-elements are now parsed exactly like::part()(upstream folds both into one state): they may follow::part()itself, and accept any pseudo-element/pseudo-class after them, matching the WPT tests from the CL (::part(mypart)::details-content,::part(mypart)::marker, …).
The resulting matrix:
| after | allowed | rejected |
|---|---|---|
::part(), element-backed (::details-content, ::picker()) |
pseudo-classes incl. functional (:lang(), :dir()), any pseudo-element incl. element-backed ones |
::part(), ::slotted(), combinators, type/class/id/attribute selectors |
::before, ::after |
stateful pseudo-classes (:hover…), ::marker only |
any other pseudo-element, functional pseudo-classes |
other pseudo-elements (e.g. ::marker, ::selection) |
stateful pseudo-classes | any pseudo-element |
Other fixes that fell out of this:
- lightningcss now also marks
::picker()as element-backed, matching Gecko (pseudo_elements.toml). The view-transition pseudo-elements are deliberately excluded: css-view-transitions only allows:only-childafter them (Gecko's toml does parse them as element-backed via a default, but that conflicts with the spec's:only-child-only rule, so I kept them non-element-backed). - A bare element-backed pseudo-element now sets the
HAS_PSEUDOflag, so e.g.::details-contentis no longer eligible for the:is()lowering inrules/mod.rs(:is(::details-content)would be invalid). - Fixed the pre-existing
test_parsingfailures in the selectors crate (DummyParserwas missing::target-text,::search-text,::highlight(),::picker()etc.), so that suite is fully green now.
One deliberate deviation from upstream stylo: after ::marker, the AFTER_BEFORE_OR_AFTER_PSEUDO state is cleared again, so ::before::marker::marker is rejected — upstream keeps the state set and would accept it, which looks like an oversight given their own test_pseudo_duplicate_before_after_or_marker.
Port the pseudo-element relationship model from the upstream servo/stylo selectors parser: - Rename AFTER_PSEUDO_ELEMENT to AFTER_NON_ELEMENT_BACKED_PSEUDO and add AFTER_BEFORE_OR_AFTER_PSEUDO, matching upstream naming. - Element-backed pseudo-elements now parse exactly like ::part(): all pseudo-classes and pseudo-elements may follow them, and they may also follow ::part() itself, e.g. ::part(foo)::details-content::before. - ::before/::after accept a single following pseudo-element restricted to the new valid_after_before_or_after() allowlist, currently only ::marker, so ::before::marker and ::details-content::before::marker parse while duplicates like ::before::before remain errors. - Mark ::picker() as element-backed in lightningcss as well, matching Gecko. View-transition pseudo-elements stay non-element-backed since the spec only allows :only-child after them. - A bare element-backed pseudo-element now sets the HAS_PSEUDO flag so such selectors are excluded from :is() lowering. - Implement the pseudo-classes/pseudo-elements missing from the selectors crate DummyParser to fix the pre-existing test_parsing failures.
rustc 1.96 stopped passing --allow-undefined to wasm-ld (https://blog.rust-lang.org/2026/04/.../wasm-undefined-symbols), so the undefined N-API import symbols from napi-sys are now a link error. Pass the flag explicitly for wasm32-unknown-unknown; the symbols are resolved by the host at runtime, as before.
|
The failing Root cause: rustc 1.96.0 stopped passing Fix (0e2d60d): pass |
Fixes #1244
Problem
Selectors chaining pseudo-elements off
::details-contentfail to parse:Root cause
::details-contentis an element-backed pseudo-element: it behaves like a type selector, so other pseudo-elements and pseudo-classes may follow it. The parser did not model this distinction, and treated every known pseudo-element the same way as::before(which cannot be followed by further pseudo-elements).Changes
The relationship between pseudo-elements is now modeled comprehensively, mirroring the current upstream
selectorsparser in servo/stylo (where Firefox implements::details-content) and the Chromium change that allowed almost all pseudo-elements after::part()(crrev.com/c/5823774):AFTER_NON_ELEMENT_BACKED_PSEUDO, addAFTER_BEFORE_OR_AFTER_PSEUDO, and addPseudoElement::{parses_as_element_backed, is_before_or_after, valid_after_before_or_after}trait methods, matching upstream naming.::part(): all pseudo-classes (including functional ones like:lang()) and all other pseudo-elements may follow them, and they may also follow::part()itself, e.g.::part(foo)::details-content::before.::part(),::slotted(), combinators and type/class/id selectors remain errors after them.::before/::afteraccept a single following pseudo-element, restricted to avalid_after_before_or_after()allowlist that currently only contains::marker(per css-pseudo-4), so::before::markerand::details-content::before::markerparse while e.g.::before::before,::marker::markerand::before::details-contentare still errors.::picker()as element-backed, matching Gecko (stylo'spseudo_elements.toml). The view-transition pseudo-elements are deliberately left out: css-view-transitions only allows:only-childafter them.HAS_PSEUDOselector flag, so such selectors are excluded from the:is()lowering inrules/mod.rs(where pseudo-elements are not allowed).Now all of these parse:
while these remain errors (matching browsers):
Tests
test_pseudo_on_element_backed_pseudo, plus newtest_pseudo_duplicate_before_after_or_marker,test_pseudo_after_before_or_afterandtest_pseudo_after_part(mirroring upstream), andtest_parsingnow covers the chains above.::details-content::before/after/marker,::before::marker,::details-content::before::marker,details::details-content::before,::part(foo)::details-content::beforeand::picker(select)::before, plus error tests for::details-content::part(foo),::details-content::slotted(div),::details-content::before::before,::details-content::before::selection,::before::details-content,::marker::markerand::part(foo)::part(bar).test_parsingfailures in the selectors crate (theDummyParserwas missing::target-text,::search-text,:current,::highlight(),::picker(),::picker-icon,::checkmark,::grammar-errorand::spelling-error), so that suite is fully green.