Intent to Ship : :has() pseudo class

1,141 views
Skip to first unread message

Byungwoo Lee

unread,
May 16, 2022, 5:36:15 AM5/16/22
to blin...@chromium.org

Contact emails

bl...@igalia.com

Explainer

https://github.com/Igalia/explainers/tree/main/css/has

Specification

https://www.w3.org/TR/selectors-4/#relational

Summary

The ':has()' pseudo class is a selector that specifies an element which has at least one element that matches the relative selector passed as an argument.


Blink component

Blink>CSS

TAG review

No TAG review for ':has()' pseudo class.

TAG review status

Pending

Risks



Interoperability and Compatibility

Gecko: No signal (https://github.com/mozilla/standards-positions/issues/528)
Gecko haven't explicitly expressed the position yet.
But it seems positive according to the latest communication on a related bug, as it looks that Gecko started tracking ':has()' in the internal roadmap:
https://bugzilla.mozilla.org/show_bug.cgi?id=418039#c37

WebKit: Shipped/Shipping (https://bugs.webkit.org/show_bug.cgi?id=233809)
WebKit supports ':has()' by default since Safari 15.4:
https://developer.apple.com/documentation/safari-release-notes/safari-15_4-release-notes
Chrome and WebKit are mostly interoperable, but there are some differences now. Please refer the web-platform-test section below.

Web developers: Strongly positive (https://bugs.chromium.org/p/chromium/issues/detail?id=669058)
The bug has 89 stars. It's one of the most requested features by web developers:
https://2020.stateofcss.com/en-US/opinions/#currently_missing_from_css  (State of CSS Survey from 2020)

Other signals:

Ergonomics risks

Authors can easily increase complexity of selector query or style invalidation by using ':has()' pseudo classes.
Similar to the other traditional selector usages, these would be helpful.

  • Use direct sibling combinator or child combinator if possible.
    • '.a:has(.b) {}' -> '.a:has(> .b) {}'
    • '.a:has(~ .b .c) {}' -> '.a:has(+ .b .c) {}' -> '.a:has(+ .b > .c) {}'
  • For some pseudo classes that change state frequently (e.g :hover, :focus), add compound condition if possible.
    • '.a:has(:hover) {}' -> '.a:has(.b:hover) {}'

Regardless of the above, we applied following approaches to prevent the risk of having bad performance caused by using ':has()' inefficiently.

  • Apply ':has()' checking result cache for a single operation (a single selector query or a single style recalculation lifecycle)
  • Apply the approach of using affected-by flags and dynamic-restyle flags to prevent unnecessary ':has()' invalidation.
  • Disallow ':is()', ':where()', ':has()' inside ':has()' to prevent increasing ':has()' invalidation complexity.

In addition to the above, we have a plan of applying the bloom filter approach that WebKit applies.

Activation risks

No

Security risks

There are concerns about leaking sensitive information to the parent or out of shadow boundary, and these were already addressed.


WebView application risks

No


Debuggability

':has()' pseudo class is well supported by DevTools. 'Elements>Styles' tab correctly displays and updates the style rules with ':has()'. The rule-matched elements are successfully highlighted when mouse is over on a style rule with ':has()'. In Console drawer, developer can query (matches/querySelector/querySelectorAll/closest) with ':has()' pseudo class. Some cases were explicitly limited to prevent security risks or to avoid increasing complexity. It would be better to provide the author with the `:has()` feature and its coverage or limitations. Some kind of documentation in either MDN or web.dev about those would be welcome, and we're willing to help with that.



Is this feature fully tested by web-platform-tests?

Yes

web-platform-tests covers most cases.
There are some failures in Chrome.
There are some differences between Chrome and WebKit. All results are same except these cases.
  • ':is()' inside ':has()'
    • Chrome : Limited
    • WebKit : Parsing(Works) / Selector query(Works) / Invalidation(Not-tested)
  • Applying forgiving-relative-selector
    • Chrome : Applied
    • WebKit : Not-applied
  • Invalidation with a mutation on an inserted element
    • Chrome : Works
    • WebKit : Fails
  • Invalidation with ':default' or ':out-of-range' inside ':has()'
    • Chrome : Works
    • WebKit : Fails
  • Invalidation with ':focus', ':focus-within', ':focus-visible' inside ':has()'
    • Chrome : Works
    • WebKit : Fails

Flag name

CSSPseudoHas

Requires code in //chrome?

No

Tracking bug

https://crbug.com/669058

Sample links

https://css-has.glitch.me/
https://has-pseudo-class-performance.glitch.me/


Estimated milestones

105


Anticipated spec changes

There are 4 open issues posted on the csswg draft.
These are suggestions of placing limitations on some cases to prevent increasing invalidation complexity. Current implementation doesn’t support invalidation of those cases, and we have a plan to place limitation on those cases to avoid increasing complexity. The reported issues will be addressed by CSSWG. If those suggestions are not accepted, we can handle those cases in follow-up patches improving ':has()' support.

Link to entry on the Chrome Platform Status

https://chromestatus.com/feature/5794378545102848

Links to previous Intent discussions

Intent to prototype: https://groups.google.com/u/1/a/chromium.org/g/blink-dev/c/hqkcKdDrhXE?pli=1


This intent message was generated by Chrome Platform Status.

Emilio Cobos Álvarez

unread,
May 16, 2022, 2:18:05 PM5/16/22
to Byungwoo Lee, blin...@chromium.org
On 5/16/22 11:05, Byungwoo Lee wrote:
> Anticipated spec changes
>
> There are 4 open issues posted on the csswg draft.
>
> * Remove scope dependency from relative selectors definition:
> https://github.com/w3c/csswg-drafts/issues/6399
> * Disallowing logical combination pseudo classes inside ':has()':
> https://github.com/w3c/csswg-drafts/issues/6952
> * Disallowing ':scope' inside ':has()':
> https://github.com/w3c/csswg-drafts/issues/7211
> * Disallowing ':host', ':host()', ':host-context()' inside ':has()':
> https://github.com/w3c/csswg-drafts/issues/7212

It'd be great to get resolution on these issues before shipping, IMO.

In general, given how the usefulness of this feature relies on browser
engines having predictable performance (the feature is useless if WebKit
or Firefox get cases fast that Chrome gets slow or vice-versa), it'd be
great to document in the spec some of these limitations and the
reasoning for them.

To be clear, in general I'd favor shipping with the limitations in place
for now, and if/when there are use cases and so to unrestrict them do
so. But that said, for that we need proper detection for those
limitations. I filed this on the topic:

* https://github.com/w3c/csswg-drafts/issues/7280

-- Emilio
OpenPGP_0xE1152D0994E4BF8A.asc

Byungwoo Lee

unread,
May 17, 2022, 2:19:03 AM5/17/22
to Emilio Cobos Álvarez, blin...@chromium.org


On 5/17/22 03:17, Emilio Cobos Álvarez wrote:
On 5/16/22 11:05, Byungwoo Lee wrote:
        Anticipated spec changes

There are 4 open issues posted on the csswg draft.

  * Remove scope dependency from relative selectors definition:
    https://github.com/w3c/csswg-drafts/issues/6399
  * Disallowing logical combination pseudo classes inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/6952
  * Disallowing ':scope' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7211
  * Disallowing ':host', ':host()', ':host-context()' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7212

It'd be great to get resolution on these issues before shipping, IMO.

In general, given how the usefulness of this feature relies on browser engines having predictable performance (the feature is useless if WebKit or Firefox get cases fast that Chrome gets slow or vice-versa), it'd be great to document in the spec some of these limitations and the reasoning for them.

All the above 4 issues are essentially related to the case of ':is()' inside ':has()'.

The dependency between the 4 issues can be summarized as follows:

  • To avoid increasing invalidation complexity, disallow ':is()' or ':where()' inside ':has()' (#6952)
    • ':scope' inside ':has()' has the same (or worse) problem as ':is()' inside ':has()', so disallow ':scope' inside ':has()' (#7211)
      • After ':scope' is disallowed inside ':has()', we can keep the current definition of absolutizing with ':scope' because ':scope' will not be used explicitly inside the ':has()' (#6399)
      • ':host', ':host()', ':host-context()' is meaningless unless it is used with ':scope' inside ':has()' (#7212)


The ':is()' inside ':has()' case is the start of the 4 issues, and most engines seems to agree to disallow the ':is()' inside ':has()' case now.

If so, I think it would be OK to ship to Chrome with explicit limitations for the above cases even if those issues are not yet addressed in the spec. How do you think about this?



To be clear, in general I'd favor shipping with the limitations in place for now, and if/when there are use cases and so to unrestrict them do so. But that said, for that we need proper detection for those limitations. I filed this on the topic:

 * https://github.com/w3c/csswg-drafts/issues/7280

Your suggestion to `@supports` seems to help detect the limitations, so I left a comment. Thank you for filing the issue!



 -- Emilio


Regards,
Byungwoo

Byungwoo Lee

unread,
May 18, 2022, 9:05:39 AM5/18/22
to Antti Koivisto, Emilio Cobos Alvarez, blink-dev


On 5/18/22 17:33, Antti Koivisto wrote:


On Tuesday, May 17, 2022 at 9:19:03 AM UTC+3 bl...@igalia.com wrote:


On 5/17/22 03:17, Emilio Cobos Álvarez wrote:
On 5/16/22 11:05, Byungwoo Lee wrote:
        Anticipated spec changes

There are 4 open issues posted on the csswg draft.

  * Remove scope dependency from relative selectors definition:
    https://github.com/w3c/csswg-drafts/issues/6399
  * Disallowing logical combination pseudo classes inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/6952
  * Disallowing ':scope' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7211
  * Disallowing ':host', ':host()', ':host-context()' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7212

It'd be great to get resolution on these issues before shipping, IMO.

In general, given how the usefulness of this feature relies on browser engines having predictable performance (the feature is useless if WebKit or Firefox get cases fast that Chrome gets slow or vice-versa), it'd be great to document in the spec some of these limitations and the reasoning for them.

All the above 4 issues are essentially related to the case of ':is()' inside ':has()'.

The dependency between the 4 issues can be summarized as follows:

  • To avoid increasing invalidation complexity, disallow ':is()' or ':where()' inside ':has()' (#6952)
    • ':scope' inside ':has()' has the same (or worse) problem as ':is()' inside ':has()', so disallow ':scope' inside ':has()' (#7211)
      • After ':scope' is disallowed inside ':has()', we can keep the current definition of absolutizing with ':scope' because ':scope' will not be used explicitly inside the ':has()' (#6399)
      • ':host', ':host()', ':host-context()' is meaningless unless it is used with ':scope' inside ':has()' (#7212)


The ':is()' inside ':has()' case is the start of the 4 issues, and most engines seems to agree to disallow the ':is()' inside ':has()' case now.

If so, I think it would be OK to ship to Chrome with explicit limitations for the above cases even if those issues are not yet addressed in the spec. How do you think about this?

WebKit does not disallow :is() inside :has() and I don't see a particular reason to. While not very useful it does not increase complexity over :not() inside :has() (which is supported and people have found useful). The only current limitation with logical combinator pseudo-classes is disallowing :has() nested inside :has() (which increases complexity a lot without enabling anything useful).

  antti

I think I misunderstood that the option of disallowing ':is()' inside ':has()' is still alive. Also I overlooked that ':not()' inside ':has()' has the same problem as ':is()' inside ':has()'.

I communicated with Antti about the above limitations, and we both agreed these:

  • Positive on disallowing explicit ':scope' inside ':has()' since ':has()' has an implicit scope.
  • Positive on disallowing ':has()' inside ':has()' since it can increase complexity a lot.
  • Should allow ':is()'/':where()' inside ':has() since:
    • we should consider ':is()', ':where()', ':not()' as a whole in terms of complexity,
    • those cases (especially ':not()') enables useful cases
    • invalidation performance will not be great but also it will not be different compared to some other worst cases
    • both WebKit and Chrome haven't considered some invalidation cases, (https://codepen.io/byung-woo/pen/vYdxPMa) but fixing the bug will not be very complex or difficult.

Based on this consensus, I'm going to allow ':is()' and ':where()' inside ':has()' before shipping.

The bug pointed at above will not be fixed before shipping.

Since it is positive to disallow explicit ':scope' inside ':has()', I think disallowing ':host()' inside ':has()' is still reasonable.

How about this?


Byungwoo.


Antti Koivisto

unread,
May 18, 2022, 12:32:12 PM5/18/22
to blink-dev, bl...@igalia.com, Emilio Cobos Alvarez
On Tuesday, May 17, 2022 at 9:19:03 AM UTC+3 bl...@igalia.com wrote:


On 5/17/22 03:17, Emilio Cobos Álvarez wrote:
On 5/16/22 11:05, Byungwoo Lee wrote:
        Anticipated spec changes

There are 4 open issues posted on the csswg draft.

  * Remove scope dependency from relative selectors definition:
    https://github.com/w3c/csswg-drafts/issues/6399
  * Disallowing logical combination pseudo classes inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/6952
  * Disallowing ':scope' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7211
  * Disallowing ':host', ':host()', ':host-context()' inside ':has()':
    https://github.com/w3c/csswg-drafts/issues/7212

It'd be great to get resolution on these issues before shipping, IMO.

In general, given how the usefulness of this feature relies on browser engines having predictable performance (the feature is useless if WebKit or Firefox get cases fast that Chrome gets slow or vice-versa), it'd be great to document in the spec some of these limitations and the reasoning for them.

All the above 4 issues are essentially related to the case of ':is()' inside ':has()'.

The dependency between the 4 issues can be summarized as follows:

  • To avoid increasing invalidation complexity, disallow ':is()' or ':where()' inside ':has()' (#6952)
    • ':scope' inside ':has()' has the same (or worse) problem as ':is()' inside ':has()', so disallow ':scope' inside ':has()' (#7211)
      • After ':scope' is disallowed inside ':has()', we can keep the current definition of absolutizing with ':scope' because ':scope' will not be used explicitly inside the ':has()' (#6399)
      • ':host', ':host()', ':host-context()' is meaningless unless it is used with ':scope' inside ':has()' (#7212)


The ':is()' inside ':has()' case is the start of the 4 issues, and most engines seems to agree to disallow the ':is()' inside ':has()' case now.

If so, I think it would be OK to ship to Chrome with explicit limitations for the above cases even if those issues are not yet addressed in the spec. How do you think about this?

WebKit does not disallow :is() inside :has() and I don't see a particular reason to. While not very useful it does not increase complexity over :not() inside :has() (which is supported and people have found useful). The only current limitation with logical combinator pseudo-classes is disallowing :has() nested inside :has() (which increases complexity a lot without enabling anything useful).

  antti
To be clear, in general I'd favor shipping with the limitations in place for now, and if/when there are use cases and so to unrestrict them do so. But that said, for that we need proper detection for those limitations. I filed this on the topic:

 * https://github.com/w3c/csswg-drafts/issues/7280

Chris Harrelson

unread,
May 18, 2022, 6:50:52 PM5/18/22
to Byungwoo Lee, Antti Koivisto, Emilio Cobos Alvarez, blink-dev
Hi Byungwoo,

I think it would be better to resolve the referenced issues at the CSSWG, including aspects Antti mentioned here, before shipping.


--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/b8aba55a-2ea6-4b75-bf13-f04e27661938%40igalia.com.

Byungwoo Lee

unread,
May 20, 2022, 1:49:03 AM5/20/22
to blink-dev, Chris Harrelson, Antti Koivisto, Emilio Cobos Alvarez, blink-dev
Thank you for the reply!

To address the issues, I've added a comment based on the latest communication in this thread.

Hope this helps to solve the issues.

2022년 5월 19일 목요일 오전 7시 50분 52초 UTC+9에 Chris Harrelson님이 작성:

Byungwoo Lee

unread,
Jun 2, 2022, 3:25:09 AM6/2/22
to blink-dev, Chris Harrelson, Antti Koivisto, Emilio Cobos Alvarez

There is an update!

  1. All the :has() related issues have been resolved in CSSWG.
    (Thanks to everyone who arranged and discussed!)

    #6399 Remove the :scope dependency from the relative selectors definition ()
      -> Remove special handling of :scope in relative selectors generally
    #6952 Consider disallowing logical combination pseudo-classes inside :has()
      -> Disallow nesting :has() inside :has()
    #7280 Detecting :has() restrictions
      -> @supports uses non-forgiving parsing for all selectors
    #6845 Consider disallowing :has() outside the rightmost compound
      -> Close no change
    #7211 Consider disallowing :scope inside :has()
      -> Closed as a duplicate of #6399 (continues to be allowed inside :has())
    #7212 Consider disallowing :host, :host(), :host-context() inside :has()
      -> No change; :host etc. continues to be allowed inside :has()


  2.  Chrome implementation has already followed the above resolutions.
    Currently, :has() works as expected based on the spec and the above resolved results.
    The only bug that remains is about some invalidation cases for logical combinations inside :has() (bug 1331207), and I prepared CLs to fix the bug.

Please let us know if there is any other considerations.

Thank you!

Yoav Weiss

unread,
Jun 2, 2022, 4:06:03 AM6/2/22
to Byungwoo Lee, blink-dev, Chris Harrelson, Antti Koivisto, Emilio Cobos Alvarez
Thanks for the update! 

LGTM1 to ship, once we're aligned with the spec and WG decisions.

Daniel Bratell

unread,
Jun 2, 2022, 4:25:24 AM6/2/22
to Yoav Weiss, Byungwoo Lee, blink-dev, Chris Harrelson, Antti Koivisto, Emilio Cobos Alvarez

Byungwoo Lee

unread,
Jun 2, 2022, 11:42:47 AM6/2/22
to blink-dev, bl...@igalia.com, Chris Harrelson, Antti Koivisto, Emilio Cobos Alvarez, blink-dev
There is an update!
  1. All the :has() related issues have been resolved in CSSWG.
    (Thanks to everyone who arranged and discussed!)

    #6399 Remove the :scope dependency from the relative selectors definition ()
      -> Remove special handling of :scope in relative selectors generally
    #6952 Consider disallowing logical combination pseudo-classes inside :has()
      -> Disallow nesting :has() inside :has()
    #7280 Detecting :has() restrictions
      -> @supports uses non-forgiving parsing for all selectors
    #6845 Consider disallowing :has() outside the rightmost compound
      -> Close no change
    #7211 Consider disallowing :scope inside :has()
      -> Closed as a duplicate of #6399 (continues to be allowed inside :has())
    #7212 Consider disallowing :host, :host(), :host-context() inside :has()
      -> No change; :host etc. continues to be allowed inside :has()


  2.  Chrome implementation has already followed the above resolutions.
    Currently, :has() works as expected based on the spec and the above resolved results.
    The only bug that remains is about some invalidation cases for logical combinations inside :has() (bug 1331207), and I prepared CLs to fix the bug.

Please let us know if there is any other considerations.

Thank you!

2022년 5월 20일 금요일 오후 2시 49분 3초 UTC+9에 bl...@igalia.com님이 작성:

Chris Harrelson

unread,
Jun 2, 2022, 11:57:16 AM6/2/22
to Daniel Bratell, Yoav Weiss, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
LGTM3, once the implementation aligns with the WG decisions, there are tests, and the corresponding spec PRs have landed.

Congratulations to all who worked on this feature! I think it's a great addition to the platform that developers will really like.

Rune Lillesveen

unread,
Sep 2, 2022, 5:40:32 AM9/2/22
to Chris Harrelson, Daniel Bratell, Yoav Weiss, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
Hi all,

We have an incoming issue for jQuery that seems pretty serious for them:

https://crbug.com/1358953

The problem is that jQuery uses the native implementation of :has() when present, but the feature detection detects support for other custom jQuery selectors inside :has() because of :has() accepting forgiving selectors.

It should be possible to fix this for jQuery, but the problem is for existing content which relies on this feature detection.

The reason why this was not detected when Safari shipped :has(), is that Safari does not accept <forgiving-relative-selector-list> like the spec says. I have filed https://bugs.webkit.org/show_bug.cgi?id=244708 against WebKit.




--
Rune Lillesveen

Rune Lillesveen

unread,
Sep 2, 2022, 7:09:27 AM9/2/22
to Chris Harrelson, Daniel Bratell, Yoav Weiss, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
On Fri, Sep 2, 2022 at 11:40 AM Rune Lillesveen <fut...@chromium.org> wrote:
Hi all,

We have an incoming issue for jQuery that seems pretty serious for them:



--
Rune Lillesveen

Rune Lillesveen

unread,
Sep 2, 2022, 7:11:53 AM9/2/22
to Chris Harrelson, Daniel Bratell, Yoav Weiss, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
On Fri, Sep 2, 2022 at 1:09 PM Rune Lillesveen <fut...@chromium.org> wrote:
On Fri, Sep 2, 2022 at 11:40 AM Rune Lillesveen <fut...@chromium.org> wrote:
Hi all,

We have an incoming issue for jQuery that seems pretty serious for them:

An update on the impact for jQuery:


There was an issue filed for the CSSWG https://github.com/w3c/csswg-drafts/issues/7676


--
Rune Lillesveen

Yoav Weiss

unread,
Sep 2, 2022, 7:37:10 AM9/2/22
to Rune Lillesveen, Chris Harrelson, Daniel Bratell, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
Thanks for the update! Given that this is something that web developers have been using (as a polyfill, but still) for a loooong while, I'm somewhat skeptical that we can get away with the spec as currently written.
As we can't have use-counters for things passed as jquery selectors, I wonder if an HTTPArchive and a GitHub search can reveal how widespread this is.

But I suspect this is a "revert first and ask questions later" kind of situation. Unfortunately, it seems like this would require a code update, as the flag is not propagated to a Chromium feature flag AFAICT.

Rune Lillesveen

unread,
Sep 2, 2022, 8:19:30 AM9/2/22
to Yoav Weiss, Chris Harrelson, Daniel Bratell, Byungwoo Lee, blink-dev, Antti Koivisto, Emilio Cobos Alvarez
On Fri, Sep 2, 2022 at 1:37 PM Yoav Weiss <yoav...@chromium.org> wrote:
Thanks for the update! Given that this is something that web developers have been using (as a polyfill, but still) for a loooong while, I'm somewhat skeptical that we can get away with the spec as currently written.
As we can't have use-counters for things passed as jquery selectors, I wonder if an HTTPArchive and a GitHub search can reveal how widespread this is.

But I suspect this is a "revert first and ask questions later"