w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.45k stars 656 forks source link

[selectors] `:nth-child(n of A)` should ignore default namespaces for A, same as `:is()` #9804

Open tabatkins opened 8 months ago

tabatkins commented 8 months ago

The :is(), :not(), and :where() pseudos all have text that states that if the final compound selector in their arguments doesn't have a type selector, we ignore the general rule that requires them to match the default namespace (when one exists).

(That is, given @namespace "http://example.com";, a selector like .foo is implicitly only selecting elements from that default namespace; you have to explicitly write *|*.foo to get back to the normal behavior of allowing any namespace to match. This doesn't apply to :is()/etc arguments.)

This special behavior exists to ensure that the combo pseudoclasses intuitively work correctly when used as syntax sugar; that is, given any selector AB, replacing it with A:is(B, C) is guaranteed to match all the same elements, and possibly more. See #5684 for more details.

This same argument should apply to the n of A arguments to :nth-child()/etc: going from AB to A:nth-child(n of B) should match the exact same elements. Currently (lacking the special rule that :is()/etc have), this isn't necessarily true; changing svg|a.foo to svg.a:nth-child(n of .foo) makes it stop matching anything at all, if the default namespace is not SVG.

tabatkins commented 8 months ago

In the telcon discussion about #5684, dbaron summarized the justification of the :is()/etc behavior really well: we don't want to have the default namespace applied twice to the same element, via different selectors, such that it's easy to not override the default on one of them and get an inconsistent and confusing behavior.

So like in svg|a.foo, you only apply the rule once (nothing happens, since it already has an explicit namespace. In svg|a:is(.foo) you want to apply it only once as well, so you don't get an incompatible selector (effectively svg|a:is(default|*.foo)). But in a case like svg|a:is(div .foo), it's fine for the default namespace to apply to the div selector, since that's a different element, same as if it were written div svg|a.foo.

So the general policy to draw here is, the default namespace rule should not apply to any pseudoclass's selector argument, for the component of that selector that applies to the same element as the pseudoclass itself.

Adopting this as a general policy would obviate this issue; it would automatically fall under the new rule. We could also remove the exception from :is()/etc, since they'd be auto-covered.

Notably, this does not cover :has()'s argument; since that selector is relative, none of its components apply to the element the :has() is on.

(And to be clear, this would only apply to parts that are defined as applying to the subject element. Writing svg|a:has(:is(.foo *)) wouldn't do anything special to the .foo part, even if it ends up matching the svg|a:has(...) element, because that match-up is coincidental rather than by definition.)

Loirooriol commented 8 months ago

And to be clear, this would only apply to parts that are defined as applying to the subject element

I think this can get a bit tedious, like in svg|a > :is(.foo > *) the .foo must be the same as the svg|a because elements have a single parent, but is this considered to be defined as applying to the same element?

tabatkins commented 8 months ago

No, that's exactly the opposite of what I said. ^_^ In :is(), the subject of the selector argument is the thing that's defined to be the same as the pseudo's own element. Any other part of the selector would only match up by other bits of the selector by coincidence.