Skip to content

[selectors] Let :is() have better error-recovery behavior than normal Selectors #3264

Open

Description

@tabatkins

This isn't captured in the current spec, but I remember earlier speculation that we could use :matches() as a way to get around the bad Selectors behavior of "a syntax error in one complex selector invalidates the whole sequence" that we're stuck with. Do we still think this is a worthwhile idea to pursue?

Spec-wise, what this would mean is defining the official syntax as :matches( <any-value> ), then split the result on top-level comma tokens, then attempt to parse each item as a <complex-selector>, and just ignore any invalid ones. (If all of them are invalid, the selector matches nothing.)

Then, if you're concerned about using a newer feature, you can just write your selectors like:

:matches( .foo || .bar, td.foo:first-child ) {
  /* styles for the bar cell */

An unfortunately, but relatively minor, tax for getting better error-recovery behavior. (If we do end up renaming it to :is(), it's even more minor.)

Activity

  1. CyberAP commented on Oct 29, 2018

    @CyberAP

    Shouldn't this belong to @supports rule?
    Just to imagine how bloated stylesheet would get if you have to use new selectors extensively. While with @supports you can only test it once and that's it.
    Yes it doesn't cover selector check right now, but with Custom Selectors from CSS Extensions this should be possible.

  2. SelenIT commented on Oct 29, 2018

    @SelenIT
    Collaborator

    @CyberAP, the point is to avoid having to write the same rules twice (for new and all browsers).

    For example, consider a menu that is by default transformed into a hamburger icon (or something), but should "uncollapse" if either hovered or has a link inside it focused via keyboard. For modern browsers, I can write

    .menu:hover, .menu:focus-within { transform: none; }

    ...but in browsers that don't support :focus-within this would not apply at all, and I would have to write this rule twice, for each selector separately, in order to make at least the :hover part work there.

    However, with the new proposal, the following hypothetical example

    :is(.menu:hover, .menu:focus-within, :current(.menu)) { transform: none; }

    would be able to work in browsers that already support :is() and these two pseudo-classes, but don't support :current() yet.

    How can this be solved with @supports (even if extended to test for selectors)? For me, this looks like the opposite of what @supports does (combining the code for old and new browsers into one rule, rather than separating it into different rules). Would be happy to be proven wrong!

  3. CyberAP commented on Oct 31, 2018

    @CyberAP

    @SelenIT, thanks for a great explanation! That does make sense and actually is a really great feature I would like to have.

  4. ExE-Boss commented on Nov 2, 2018

    @ExE-Boss
    Contributor

    You mean like #3082, except without the vendor prefix exemption?

  5. tabatkins commented on Nov 2, 2018

    @tabatkins
    MemberAuthor

    Yes, and safely; I'm pretty sure we can't do #3082.

  6. css-meeting-bot commented on Nov 21, 2018

    @css-meeting-bot
    Member

    The CSS Working Group just discussed Let :matches() have better error-recovery behavior than normal Selectors, and agreed to the following:

    • RESOLVED: Use media query style invalidation inside pseudo classes that accept selector lists
    The full IRC log of that discussion <dael> Topic: Let :matches() have better error-recovery behavior than normal Selectors
    <dael> github: https://github.com//issues/3264
    <dael> Rossen: Is TabAtkins on?
    <dael> fantasai: I can take it
    <dael> fantasai: When we have a list of selectors :foo, :bar
    <dael> fantasai: Browser doesn't rec :foo so the entire style rule is thrown out
    <dael> fantasai: That's selectors invalidation.
    <dael> fantasai: Other style of invalidation is media queries-like where you throw out a section. So if you have foo, screen we recognize screen
    <dael> fantasai: Can't do MQ like because it would break the web for selectors. TabAtkins pointed out we have ability to do it within the new selectors in L4
    <dael> fantasai: Issue proposes we adopt MQ-like invalidation inside the pseudoclasses that take selectors.
    <dael> fantasai: :foo || :bar, [known selector] we'd only do the part we recognize.
    <dael> fantasai: Makes a lot os sense to me together with @supports rule we added. Gives authors much better tools. If they want more granular they can use something else
    <dael> Rossen: Sounds reasonable.
    <dael> Rossen: Opinions?
    <dael> myles: Thoughts from people that teach this? Seems like could be confusing
    <dael> Rossen: leaverou or rachelandrew ?
    <dbaron> Were you suggesting doing different things for :matches() vs. :is() ?
    <tantek> agreed with myles, it could be confusing. would definitely want webdev teacher feedback on this.
    <dael> fantasai: dbaron this was from before we renamed. Title should be is. Applies to is, not, has, nth-child, and maybe current
    <dael> dbaron: One worry is some have been around for a while and might have compat issues
    <dael> fantasai: :not with commas isn't widely supported.
    <dael> fantasai: Not with a single argument that's invalid makes the whole thing invalid
    <dael> florian: I think not with a comma is supported in Safari and Viviostyle
    <dael> fantasai: Not is trickier because it's a negation
    <dael> leaverou: Trying to decide error recovery or syntax?
    <dael> fantasai: Error recovery
    <dael> leaverou: Sounds amazing thing to do. It might be a little confusing, but it's worth it. In talks I only use webkit version and have to mention verbally it's just webkit. As an author I would love it
    <dael> emilio: Doesn't solve unknown pseudo elements issue, right?
    <dael> fantasai: No, sep.
    <dael> emilio: I think that's biggest issue authors want to solve
    <dael> fantasai: There's a rule for the webkit
    <dael> emilio: Want to style a video control for webkit and edge it's not stanard. That's the biggest source of duplication
    <dael> fantasai: This would solve, put it in all one :is
    <dael> emilio: Syntax allow psuedo elements inside?
    <bradk> I’m still concerned about :not. Can we find out how much authors have :-webkit and commas inside not?
    <dael> fantasai: This is work for pseudo classes. Elements is next on agenda
    <florian> I support this
    <dael> Rossen: bradk point about not and his concern, can we address that now?
    <dael> Rossen: Concern is the :not and defining how much authors have commas inside a not?
    <dael> florian: It's safari only
    <dael> bradk: Safari on iphone is used a lot. :not has been without commas for a while, but it's used in mobile web a lot. That's my suggestion, I'd like actual data
    <dael> florian: This is hard data to get. Need to find usage that would break the page if it started working in a different way. That's a judgement call
    <dael> Rossen: Another way to ask is if any Apple folks have an issue with this. If they feel comfortable with compat risk we can resolve
    <dael> bradk: That solution would be okay
    <dael> smfr: I don't think we have enough [missed]
    <dael> smfr: I don't thinkw e have enough information to know. When we impl :not we got compat issues b/c people using it in wrong ways
    <dael> smfr: No feeling for how common comma use is to know if it's risky
    <dael> Rossen: Would you be okay with current proposal in the absense of this information?
    <dael> smfr: I think so
    <dael> Rossen: Anything else before we try and resolve?
    <bradk> No strong objection
    <dael> fantasai: Use media query style invalidation inside psuedo classes that accept selector lists
    <dael> Rossen: Objections to ^?
    <dael> emilio: Would like behavior for :not clarified
    <dael> fantasai: Makes sense
    <dael> RESOLVED: Use media query style invalidation inside pseudo classes that accept selector lists
    <dael> Rossen: fantasai and TabAtkins will have clarification in spec
  7. fantasai commented on Nov 21, 2018

    @fantasai
    Collaborator

    emilio pointed out that we need to be careful about how we handle :not() here. It's not obvious it would work as intended if we just ignore unknown arguments, and also a single argument to :not() likely has Web-compat implications since it's already implemented.

    A related question is :nth-child(); simply ignoring one of its arguments could throw off the count. There likely wouldn't be a Web-compat problem with it, though.

  8. changed the title [-][selectors] Let :matches() have better error-recovery behavior than normal Selectors[/-] [+][selectors] Let :is() have better error-recovery behavior than normal Selectors[/+] on Nov 21, 2018
  9. ExE-Boss commented on Nov 21, 2018

    @ExE-Boss
    Contributor

    The thing about :not() is that only the single‑argument form is supported in major browsers, so we could probably apply it to the multi‑argument form and be fine.

    Also, at least for unknown pseudo‑classes, we could have those match nothing, so :not(:unsupported, :unsupported-2) would match everything because :not(:is(:unsupported, :unsupported-2)) would match everything, as :is(:unsupported, :unsupported-2) would match nothing.

  10. ewilligers commented on Nov 23, 2018

    @ewilligers
    Contributor

    I agree that we could minimize the Web-compat risk by saying that error recovery only applies if two or more selectors are supplied.

    :nth-last-child(:nonsense) is invalid
    :nth-child(:nonsense, :enabled) is :nth-child(:enabled)
    :not(:nonsense, :gibberish) is *
    :is(:nonsense, :gibberish) is :not(*).

    This would have the following benefits:

    • :not(.) and :nth-child(.) and :nth-last-child(.) with a single selector have the same semantics as in Selectors 3.
    • We have error recovery for :is(...) and :where(...) and :has(...)
    • :not(...) and :is(:not(...)) and :not(:is(...)) are always equivalent, same for :nth-child and :nth-last-child

    As a minor variation, preserving the same benefits, we could say that error recovery only applies if at least one of the selectors is valid. Then

    :not(:nonsense, :gibberish) is invalid.

    This might be easier to teach, and avoid developers' confusion when they see an introduced :not(*) in a Developer Tool that hasn't preserved raw text.

  11. ExE-Boss commented on Nov 23, 2018

    @ExE-Boss
    Contributor

    As a minor variation, preserving the same benefits, we could say that error recovery only applies if at least one of the selectors is valid.

    Then :not(:nonsense, :gibberish) is invalid.

    That should probably only apply to :not(…), as :commonly-supported, :is(:rarely-supported) can be used to avoid invalidating the entire selector list if the :rarely-supported pseudo‑class isn’t supported by the current browser but :is(…) and :commonly-supported are supported by the current browser.

    Also, it might be a good idea to still parse :not(:nonsense, :gibberish) as valid, but matching nothing.

  12. 15 remaining items

  13. emilio commented on Aug 24, 2020

    @emilio
    Collaborator

    I'm happy to implement this in Gecko if people define in the spec how to deal with the empty-selector behavior. Like, should :is(:nonsense) serialize just as :is()? Should that be special-cased to never match?

    Also, it may be a bit confusing if people write stuff like :is(::before, ::after), and it gets turned to :is() instead of being rejected as invalid...

  14. emilio commented on Aug 24, 2020

    @emilio
    Collaborator

    Then, of course :is() should be parseable too...

  15. ewilligers commented on Aug 25, 2020

    @ewilligers
    Contributor

    should :is(:nonsense) serialize just as :is()? Should that be special-cased to never match?

    Then, of course :is() should be parseable too...

    Yes, I was going to serialize :is(:nonsense) as :is() in dev tools, and treat :is() as a valid selector that never matches.

    Is serialization of selectors web exposed, i.e. testable in WPTs?

  16. Loirooriol commented on Aug 25, 2020

    @Loirooriol
    Contributor

    @ewilligers Sure, include /css/support/parsing-testcommon.js and then

    test_valid_selector(":is(:nonsense)", ":is()");
  17. css-meeting-bot commented on Sep 2, 2020

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [selectors] Let :is() have better error-recovery behavior than normal Selectors.

    The full IRC log of that discussion <dael> Topic: [selectors] Let :is() have better error-recovery behavior than normal Selectors
    <dael> github: https://github.com//issues/3264
    <dael> ericwilligers: We resolved to have better error recovery but WK shipped without and FF is about to. Is it too late or do we still want error recovery?
    <dael> TabAtkins: If possible I'd like to have it. If we have to oh well, but it's bad behavior without
    <dael> ericwilligers: Who wants to do spec update?
    <dael> ericwilligers: We resolved on issue but spec isn't updated
    <dael> TabAtkins: I'll do it if we agree today to proceed
    <dael> Rossen_: We're wanting to hold previous resolution that requires error recovery and make that edit into spec? Or are we trying to relax the error recovery and not proceed with edits?
    <dael> ericwilligers: [missed]
    <dael> ericwilligers: I'm propsing the first, that we go ahead and make edit.
    <dael> Rossen_: You're proposing add it to spec and pester impl to do that
    <dael> ericwilligers: Yes
    <astearns> notes that Emilio says he's OK implementing it
    <dael> Rossen_: Alright. Any thoughts on that? Are we still committed and have TabAtkins add edits?
    <dael> TabAtkins: We'd need FF or WK dev to be okay doing change
    <dael> Rossen_: emilio is signaling he's fine with change. Only WK is going to be required to update. Anyone from WK on?
    <dael> smfr: If emilio is fine changing we're fine changing
    <dael> Rossen_: We have a resolution to do error recovery. Do we need resolution to have TabAtkins edit it in?
    <dael> Rossen_: We'll close no change but to put in the required edits
    <dael> Rossen_: Thank you
  18. emilio commented on Sep 13, 2020

    @emilio
    Collaborator

    https://bugzilla.mozilla.org/show_bug.cgi?id=1664718 is the Gecko bug for this (sorry for the lag, busy month!). I plan to enable it on Nightly and beta for now to collect potential breakage, but will enable after a couple releases everywhere if there's no issue.

  19. tabatkins commented on Sep 14, 2020

    @tabatkins
    MemberAuthor

    Just verifying as I write the spec - in #3264 (comment) @ewilligers suggested that we handle "single selector, which is invalid" differently from the rest of the cases, since that has legacy implications, but it looks like we're no longer doing that?

    That is, :is(:unknown) is valid but matches nothing, just like :is() and :is(:unknown1, :unknown2)?

  20. emilio commented on Sep 14, 2020

    @emilio
    Collaborator

    Yes, that's what I implemented, fwiw. I don't see why "single invalid selector" should be different.

  21. tabatkins commented on Sep 14, 2020

    @tabatkins
    MemberAuthor

    Cool, then please make sure the spec edits linked above are satisfactory; they apply to :is(), :where(), and :has(), but not :not(), per resolutions earlier in the thread.

  22. emilio commented on Sep 14, 2020

    @emilio
    Collaborator

    They look good. I'd probably note that there's an interesting side-effect of this behavior which I'm not sure it's great, which is that the specificity of :is(:unknown, div), will change when a browser starts supporting :unknown. I don't see a great way to avoid that though.

  23. tabatkins commented on Sep 15, 2020

    @tabatkins
    MemberAuthor

    Yeah, unless I distinguish between "parsable, just using unknown selectors" and "lol wtf is this", I can't handle that.

    And even then, a "parsable but unknown" selector might have complex specificity behavior, so we actually can't even predict that. So it's just something we'll have to accept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions