Csswg-drafts: [selectors-4]Why "Pseudo-elements cannot be represented by the matches-any pseudo-class"?

Created on 7 Feb 2018  路  10Comments  路  Source: w3c/csswg-drafts

Pseudo-elements cannot be represented by the matches-any pseudo-class; they are not valid within :matches().

https://www.w3.org/TR/2018/WD-selectors-4-20180201/#matches

Safari support :matches(::before)

selectors-5

Most helpful comment

Ah, I dug up the old thread (from 2012!).

The parsing problem with reinterpreting :: as a combinator is indeed lethal. It's a one-two punch of backwards-compatibility.

First, selectors like .foo > ::before exist today. If :: is a combinator, then that's two combinators in a row. We'd have to additionally add a new ability to selectors - if you have multiple combinators in a row, it implies a * selector between them. That way, the previous selector is identical to foo > * :: before, which has the meaning we want.

However, this requires a new constraint - the descendant combinator (at least in its current whitespace form) can't play with this new chained-combinators feature, or else .foo > .bar would suddenly start interpreting as three combinators in a row, meaning .foo * > * .bar. That's not right! So we'd have to add in a new non-whitespace version of the descendant combinator to allow it to work with this feature.

But this causes another problem! Selectors like .foo ::before exist today. Today, that's equivalent to .foo *::before; with the new combinator-chaining feature and the descendant-combinator restriction, it changes meaning to become .foo :: before (the before pseudo-element of the .foo element). This isn't acceptable!

So, we're screwed. Some existing selectors require a new feature to be parsed the same, which requires a funky restriction, which causes other existing selectors to not parse the same.

The only way around this is to add a totally new combinator for pseudo-children, not ::, and that's much harder to sell as being worthwhile. :(

All 10 comments

I guess it can be useful in something#very.long :matches(::before, ::after). But these pseudo-elements don't have internal structure and thus can't be followed by a combinator, so maybe a :matches that contains such pseudo-elements should neither.

The reason is that pseudo-elements, semantically, contain a combinator in them - if you start with .foo, extending to .foo.bar gives you a subset of the same elements, while extending to .foo::bar gives you something completely different, like .foo > .bar would.

:matches(), on the other hand, is definitely a pseudo-class - it should only return a subset of the elements that the selector without :matches() returns. Pseudo-elements thus don't really make sense in there.

Further, say you have something like .foo:matches(::before):hover - what does that mean? Is it the same as .foo:hover::before, or .foo::before:hover (those are different and both valid!)? Normally we can arbitrarily re-order the non-pseudo-element simple selectors in a compound selectors, including pseudo-classes, but if ::before is allowed maybe we wouldn't be able to do that any more?

It seems to me that since pseudo-elements were allowed to be effectively the subject of pseudo-classing, we can't restrict the effect of pseudo-classing to "subsetting the _elements_ only" anymore. Yes, currently only pseudo-classes of user action are allowed for pseudo-elements, but I agree that @Loirooriol's example could be very useful in many cases.

Maybe it would be useful to introduce something like "any pseudo-element" notation that would be combinable with pseudo-classes like :mathches(), :not() and probably :is() and would make the meaning of the selector unambiguous? E.g. something like .somewhere .something::*:matches(::before, ::after):hover (meaning that :hover belongs to the pseudo-element part, not the originating element)? And simply *:matches() would always match only elements, so :matches(::before) would match nothing, just like other valid-but-meaningless selectors like a:matches(b).

It seems to me that since pseudo-elements were allowed to be effectively the subject of pseudo-classing, we can't restrict the effect of pseudo-classing to "subsetting the elements only" anymore. Yes, currently only pseudo-classes of user action are allowed for pseudo-elements, but I agree that @Loirooriol's example could be very useful in many cases.

If I'm reading you correctly, I think you misunderstood what I meant by my previous comment. I'm not drawing any real distinction between elements and pseudo-elements; as far as Selectors is concerned, they're basically the same thing. (This is my entire point above; because a pseudo-element is effectively a new element, the pseudo-element selector is not equivalent in meaning to any other simple selector, and is instead basically a combinator into the "pseudo-children" of an element.)

Maybe it would be useful to introduce something like "any pseudo-element" notation that would be combinable with pseudo-classes

I wouldn't mind recasting things a bit to add a real combinator into the "pseudo-children", where presumably the pseudo-element names are instead matched as tagnames, like .foo :: before:hover, or .foo :: *:matches(before, after), etc. I've looked into this before and there were some issues with parsing if we just reinterpreted :: as a combinator; I'll have to dig up what they were, as I can't recall right now.

@tabatkins I don't say that elements and pseudo-element are _fundamentally_ different, and I like your interpretation of the :: syntax as a "combinator", but in the same time I tend to see them as something conceptually close (though not the same, of course) to the elements from a different namespace. It's based on the fact that the usual universal selector (*) doesn't select pseudo-elements by default, so we have to list them explicitly like *, *::before, *::after (this is quite popular, for example, for changing the default box-sizing to border-box globally). And I assume that :matches() is equivalent to *:matches(), which leads me to conclusion that element:matches(::pseudo-element) should match nothing because a particular element and its particular pseudo-element can't be the same object.

And I like the idea of treating :: as a combinator! It seems to be consistent with the concept of Shadow DOM combinators. Perhaps only "legacy" pseudo-elements with single colon syntax are the parsing issue in this case?

Ah, I dug up the old thread (from 2012!).

The parsing problem with reinterpreting :: as a combinator is indeed lethal. It's a one-two punch of backwards-compatibility.

First, selectors like .foo > ::before exist today. If :: is a combinator, then that's two combinators in a row. We'd have to additionally add a new ability to selectors - if you have multiple combinators in a row, it implies a * selector between them. That way, the previous selector is identical to foo > * :: before, which has the meaning we want.

However, this requires a new constraint - the descendant combinator (at least in its current whitespace form) can't play with this new chained-combinators feature, or else .foo > .bar would suddenly start interpreting as three combinators in a row, meaning .foo * > * .bar. That's not right! So we'd have to add in a new non-whitespace version of the descendant combinator to allow it to work with this feature.

But this causes another problem! Selectors like .foo ::before exist today. Today, that's equivalent to .foo *::before; with the new combinator-chaining feature and the descendant-combinator restriction, it changes meaning to become .foo :: before (the before pseudo-element of the .foo element). This isn't acceptable!

So, we're screwed. Some existing selectors require a new feature to be parsed the same, which requires a funky restriction, which causes other existing selectors to not parse the same.

The only way around this is to add a totally new combinator for pseudo-children, not ::, and that's much harder to sell as being worthwhile. :(

The CSS Working Group just discussed Why \"Pseudo-elements cannot be represented by the matches-any pseudo-class\"?, and agreed to the following:

  • RESOLVED: Defer this issue to L5

The full IRC log of that discussion
<dael> Topic: Why \"Pseudo-elements cannot be represented by the matches-any pseudo-class\"?

<dael> github: https://github.com/w3c/csswg-drafts/issues/2284

<dael> fantasai: I wanted to ask if this is something we should try and define or close as no change

<dael> TabAtkins: Valid.

<dael> TabAtkins: I know that safari allows pseudo elements, but it doesn't make sense and I don't know why

<TabAtkins> .foo:matches(::before):hover

<dael> TabAtkins: This selector ^ bothers me.

<dael> TabAtkins: Unclear if it means before when foo is hovered or when foo.before is hovered.

<TabAtkins> .foo:matches(::before).bar is invalid, then

<dael> fantasai: If we're going to define we have to define if a selecotr ends in a pseudo element and has same meaning as if you placed element outside of matches.

<TabAtkins> .foo:matches(::before, .baz).bar is invalid, too

<dael> fantasai: That's invalid, right.

<dael> TabAtkins: Reasonable sort of thing to define on

<dael> TabAtkins: It's weird and I don't like it, but if it's what we want I'm okay

<dael> ericwilligers: What's the need for this?

<TabAtkins> .foo:matches(::before, ::after)

<dael> TabAtkins: Safari does it for selectors like ^

<dael> TabAtkins: You want to assign a style to multiple psuedo elements. Can't do that with matches right now. That's annoying, I agree

<dael> TabAtkins: Reasonable thing. Even bikeshed stylesheet would like to do a selector like this.

<fantasai> s/matches/is/ btw ;)

<TabAtkins> .foo:matches(::before, .bar)

<TabAtkins> ^ invalid

<dael> TabAtkins: Another option is we're more limited and you can put psuedo elements in if they're the only thing in the selector. All options have to be pseudo elements

<TabAtkins> .foo:matches(::before:hover) is valid

<dael> TabAtkins: That would be okay way to deal

<plinss> .foo:matches(::before):matches(:hover) ?

<TabAtkins> would be valid by my rule I think

<dael> fantasai: Thing that's tricky is different pseudo elements can be followed by different things. No need to make first example invalid b/c each branch is valid. If any branch is invalid whole is invalid.

<dbaron_> Pseudo elements seem like they might differ in what is allowed after them

<dael> fantasai: More restrictive I'm not sure that gains us a whole lot. You'll still need contextual checking.

<fantasai> s/checking/checking, even if they're all pseudo-elements/

<dael> TabAtkins: ericwilligers we don't support pseudo elements in matches right now, correct?

<dael> ericwilligers: Correct

<dbaron_> It seems like you might want to ensure that all branches of the :matches end in the same restriction state

<dael> TabAtkins: FF or Edge, how does this sound. Should I close no change and Safari violates spec? Or do we want?

<TabAtkins> dbaron_, I agree with that as a good restriction if we go this way

<dael> Rossen: Curious to hear from Safari folks.

<dael> Rossen: dbaron_ is on IRC [reads]

<dbaron_> (where we need to be conservative about what is the same, e.g. before and after OK but not selection)

<TabAtkins> :matches(::before, ::spelling-error) would be invalid

<dael> TabAtkins: before and after are fine, but before and spelling wouldn't work because allow different things

<dael> ericwilligers: Safari uses different selector name. So it doesn't matter too much.

<dael> TabAtkins: Not a backwards compat concern. It's what we want to do for the future

<dael> TabAtkins: I'm perfectly fine saying close no change

<dbaron_> Still have mixed feelings about whether to allow pseudos in v1

<dael> TabAtkins: dbaron_ is unsure. We can relax restriction later

<dbaron_> (typing on phone, BTW)

<dael> Rossen: smfr said Safari folks can't follow because they're in a meeting

<dael> Rossen: Objections to resolving no change?

<dael> fantasai: I think deferred

<dael> fantasai: Close no change means we don't want to accept. We might accept in future

<dael> fantasai: I'd prefer...if we're going to consider in the future we leave it open for selectors 5. If it's a baad idea we close no change.

<dael> Rossen: Don't disagree

<dael> Rossen: objections to defer to L5?

<dael> RESOLVED: Defer this issue to L5

(Copied from #4417)

In the past I've resisted things like .foo:is(.bar, ::before), because the ::before part is technically changing the subject of the compound selector, something no other selector can do.

But this just leaves a functionality gap, since pseudo-elements don't have a combinator dedicated to them to allow matching plainly. Selector syntax is awkwardly designed around pseudo-elements, but we're stuck with it, and should accept that pseudo-elements are part of the compound selector grammar and are allowed to change the subject in a real way.

Thus we should make sure that :not()/etc work properly with them too.

(There's no reason to use :not() with any of the current pseudos, but ::part(foo):not(::part(bar)) makes sense and is useful.)

I聽think ::*:is(before, after) might聽be聽a聽solution to聽avoiding changing聽the聽subject of聽the聽compound聽selector, and聽in聽a聽way that聽would be聽compatible with聽::聽being a聽combinator.

Rethinking the "nature" of pseudo-elements in terms of selector syntax (#5676), a new way to resolve the contradiction came to my mind...

the descendant combinator (at least in its current whitespace form) _can't_ play with this new chained-combinators feature, or else .foo > .bar would suddenly start interpreting as three combinators in a row

What if we declare the whitespaces around combinators other than :: the optional part of these combinators themselves (i.e., the > combinator would be defined as /\s*\>\s*/ in RegExp terms, and so on, with only :: remaining simply /::/ for compat reasons)?

This change seems to make all the examples above unambiguous and retaining their current meaning: e.g. > will always remain a single combinator regardless the number of whitespaces around it (unless these whitespaces are separated by an explicit *), while > :: will be parsed a sequence of two combinators, > and ::, leading to insertion of the implicit * (by the new rule).

Was this page helpful?
0 / 5 - 0 ratings