Intent to Prototype: :has() pseudo class

813 views
Skip to first unread message

Byungwoo Lee

unread,
May 13, 2021, 12:01:39 PM5/13/21
to blin...@chromium.org

Contact emails

bl...@igalia.com

Explainer

Document: https://github.com/Igalia/explainers/tree/main/css/has
Test summary: https://css-has.glitch.me/


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

Motivation

Unlike other selectors, :has pseudo class gives a way to apply a style rule to preceding elements (preceding siblings / ancestors / preceding siblings of ancestors) of a certain elements. This difference is attractive to web developers, but also it generates lots of concerns mainly about performance and complexity. So there have been discussion about these over the years, but it was difficult to get those discussion moving forward. It is true that it can generate performance issues and complex cases. But it is also true that there have been clear demands for the usage. We thought that, clarifying those concerns would be helpful for the discussion. So we started to check feasibility on blink, and were able to get some meaningful and reasonable results from this step. Based on the results, we are going to move forward by prototyping. With the prototyping, we want to break down the large and complex problems into smaller ones, create solutions to the smaller problems, then create the feasible ways to extend it into large and complex problems again, and then see the results. And we hope that the results will give us a clear understanding of what affects how much performance, and what makes how complex problems.



Risks


Interoperability and Compatibility

The overall concept and design of the approach are based on the blink style engine and invalidation approach. Gecko and Webkit have it's own style engine and invalidation approach, so there may be Gecko or WebKit specific issues or risks when applying the approach, and it may not be possible to apply it.


Gecko: No signal (https://github.com/mozilla/standards-positions/issues/528)
WebKit: No signal (https://www.mail-archive.com/webki...@lists.webkit.org/msg30129.html)


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

Not yet. Tests will be added as part of this implementation.

Tracking bug

https://crbug.com/669058

Link to entry on the Chrome Platform Status

https://chromestatus.com/feature/5794378545102848

Artur Janc

unread,
May 25, 2021, 8:58:48 AM5/25/21
to blink-dev, bl...@igalia.com
Quick security question: have you considered the impact of :has() on the detection of visitedness of links? Specifically, we should make sure that something like :has(:visited) does not reveal whether a descendant link had been visited or not.

I imagine this may already be solved given that selectors such as :is() or :not() exist, but I wanted to double-check :)

Cheers,
-Artur

Byungwoo Lee

unread,
May 26, 2021, 9:08:47 PM5/26/21
to blin...@chromium.org

Hello Artur,

I haven't considered or checked that deeply yet. I thought that this will not make that kind of issues because it only adds logic to match ':has' pseudo class and it doesn't change other matching operations or style computation sequences.

But it is an important issue and I think I should check it explicitly. I'll check it and try to add some tests about that.

Thanks to point this!

Regards,
Byungwoo.

--
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/cc304139-1a71-4523-8c62-dadb1a4cb68fn%40chromium.org.

Anders Hartvoll Ruud

unread,
May 27, 2021, 3:36:24 AM5/27/21
to Byungwoo Lee, blink-dev
FWIW I don't think this should be an issue. We indeed had to make some adjustments for :is()/:not(), and those same adjustments should make everything work correctly for :has() as well.

Byungwoo Lee

unread,
May 28, 2021, 6:21:18 AM5/28/21
to Anders Hartvoll Ruud, a...@google.com, blink-dev

Hello Anders and Artur,

I tried some :visited cases. Most cases looks as expected, but I got confused by the :has(:is) case.

<style>
a:visited {
  color: red;
}
div:is(a:visited #grandchild) {
  color: blue;
}
div:has(:is(a:visited #grandchild)) {
  color: green;
}
</style>
<main id=main>
  <div id=grandparent> This is black.
    <div id=parent> This is black.
      <a id=link href=""> This is red.
        <div id=child> This is red. Is it correct? or should be green?
          <div id=grandchild> This is blue.
          </div>
        </div>
      </a>
    </div>
  </div>
</main>

The subject element of 'div:has(:is(a:visited #grandchild)) {...}' can be an ancestor of 'a:visited' and also can be descendant of it.

What will be the correct behavior for this? Is it OK to ignore the style for all the subject elements? Or should apply the style for some subject elements those are descendant of 'a:visited' element?

Artur Janc

unread,
May 31, 2021, 5:34:10 AM5/31/21
to Byungwoo Lee, Anders Hartvoll Ruud, blink-dev
I'm hoping someone more familiar with CSS can answer the specific question, but what I was more worried about how this features interacts with the protections described in https://dbaron.org/mozilla/visited-privacy#algorithm (or, more likely, the Chromium-specific implementation of this approach). Specifically, we should make sure that :has() doesn't make it possible to use properties that affect layout (e.g. display, margin, etc.) based on a link's visitidness. I'm hoping this is already safe, it's just something we shouldn't regress on.

Yoav Weiss

unread,
May 31, 2021, 6:50:10 AM5/31/21
to Artur Janc, Byungwoo Lee, Anders Hartvoll Ruud, blink-dev, dba...@chromium.org
Adding dbaron@, who may have opinions :)

Byungwoo Lee

unread,
May 31, 2021, 6:55:49 AM5/31/21
to Artur Janc, Anders Hartvoll Ruud, blink-dev, Yoav Weiss, dba...@chromium.org

> what I was more worried about how this features interacts with the protections described in https://dbaron.org/mozilla/visited-privacy#algorithm (or, more likely, the Chromium-specific implementation of this approach). Specifically, we should make sure that :has() doesn't make it possible to use properties that affect layout (e.g. display, margin, etc.) based on a link's visitedness. I'm hoping this is already safe, it's just something we shouldn't regress on.

Ok, I got your point, Thanks!

It works well with the current prototyping. I also checked about using the properties, and it works well(doesn't leak visitedness).

Currently the prototype is acting like - ':visited' will return always false for the ':has()' argument matching (because it doesn't set 'context.is_inside_visited_link' flag for :has argument matching). So, all the style rule that has ':visited' in :has() argument selector will ignored if the rule needs the ':visited' as true.

This behavior doesn't make regression on the perspective of leaking visitedness, but I think that the behavior can create this question.

  "Why the ':is(:visited .b .c) {...}' style works, but the '.b:has(:is(:visited .c)) {...}' style doesn't work?"

Currently, I think that, if we can answer the quest as below, then the issue will be simply handled without any issue.

"'With :has' , you can apply the style to ancestors of ':visited', and this can leak the visitedness. So, in case of ':visited' in the ':has()' argument, placed a restriction so that it always returns false." 

Is it acceptable to place this kind of limitation?

Byungwoo Lee

unread,
Jun 1, 2021, 5:07:28 AM6/1/21
to Artur Janc, Anders Hartvoll Ruud, blink-dev, Yoav Weiss, dba...@chromium.org

Hello Artur,

I checked again about the behavior and logic, and I think I can say that
the current :has prototyping approach follows the blink approach of :visited
privacy issue and it doesn't have regression

I think I can explain with this sample.

<style>
:is(:visited .b .c) { color: red }
:is(:visited .b ~ .c) { color: red }
.a:has(:visited) { color: red }
.b:has(:is(:visited .c) { color: red }
</style>
<main>
  <div class="a">a
    <div class="b">b
      <a href="">visited
        <div class="b">b
          <div class="c">descendant of .b</div>
        </div>
        <div class="c">sibling of .b</div>
      </a>
    </div>
  </div>
</main>

In this case, the 'descendant of .b' is red but the 'sibling of .b' is not red
even though the sibling of .b is actually a descendant of :visited.

This is because that the SelectorChecker strictly checks the combinator.
It only allows descendant/child combinator for :visited. If the SelectorChecker
reaches sibling/direct sibling combinator, it will clear the flag 'is_inside_visited_link'
as false to make the :visited always return false.

We can think about :has as a 'ancestor or preceding sibling relational selector'.
It always selects ancestors or preceding siblings of an element. So, in the above
example, the :has() is technically an ancestor combinator, and the SelectorChecker
should not allow those for :visited.

Currently, SelectorChecker set the flag 'is_inside_visited_link' as false for the :has
argument matching, so the :visited in the argument will always return false.
And as a result, the 3rd and 4th rule will not be applied, so the 'a' and 'b' will not be red.

This follows current solution of 'only allow the descendant/child combinator' for :visited.
And it doesn't have regression.

How about this approach? Is this acceptable?

Artur Janc

unread,
Jun 2, 2021, 5:52:28 AM6/2/21
to Byungwoo Lee, Anders Hartvoll Ruud, blink-dev, Yoav Weiss, dba...@chromium.org
On Tue, Jun 1, 2021 at 11:07 AM Byungwoo Lee <bl...@igalia.com> wrote:

Hello Artur,

I checked again about the behavior and logic, and I think I can say that


Thank you, Byungwoo -- I'll admit I'm not familiar enough with our CSS implementation to have a particularly well-informed opinion, but what you wrote makes sense to me and I'm fairly comfortable that we don't have a regression here. Thanks for looking into this!

Cheers,
-Artur

David Baron

unread,
Jun 7, 2021, 2:00:42 PM6/7/21
to Byungwoo Lee, Artur Janc, Anders Hartvoll Ruud, blink-dev, Yoav Weiss
On Mon, May 31, 2021 at 3:55 AM Byungwoo Lee <bl...@igalia.com> wrote:

> what I was more worried about how this features interacts with the protections described in https://dbaron.org/mozilla/visited-privacy#algorithm (or, more likely, the Chromium-specific implementation of this approach). Specifically, we should make sure that :has() doesn't make it possible to use properties that affect layout (e.g. display, margin, etc.) based on a link's visitedness. I'm hoping this is already safe, it's just something we shouldn't regress on.

Ok, I got your point, Thanks!

It works well with the current prototyping. I also checked about using the properties, and it works well(doesn't leak visitedness).

Currently the prototype is acting like - ':visited' will return always false for the ':has()' argument matching (because it doesn't set 'context.is_inside_visited_link' flag for :has argument matching). So, all the style rule that has ':visited' in :has() argument selector will ignored if the rule needs the ':visited' as true.


For what it's worth, this sounds good to me.  While there are some edge cases that are a little bit strange (e.g., "div:has(> :link) > :visited" could match a link that is the only child of a div, by the :link and :visited both matching the same element), I agree that it doesn't change the privacy characteristics of selector matching.

It might be worth adding a comment to https://github.com/w3c/csswg-drafts/issues/2037 (or perhaps a better issue if you can find one) to mention the approach that you've taken with the interaction of :has() and :visited, since it's possible the CSS Working Group may wish to specify it at some point.

-David

Byungwoo Lee

unread,
Jun 8, 2021, 9:40:55 PM6/8/21
to David Baron, Artur Janc, Anders Hartvoll Ruud, blink-dev, Yoav Weiss

Thank you David for checking the approach and sharing the link to the discussion on the :visited :)

I'll add a comment to the issue as you suggested, and will keep following the related discussions.

Best regards,
Byungwoo.

Reply all
Reply to author
Forward
0 new messages