Csswg-drafts: [css-selectors] `:has()` and performance

Created on 26 Nov 2018  Â·  7Comments  Â·  Source: w3c/csswg-drafts

https://drafts.csswg.org/selectors-4/#profiles

The CSS selectors specification claims that :has() can’t be optimised, which isn’t accurate, as when :has() is the last component of a selector (with no nested :has() pseudo-classes), then:

a b c {}

and

a b:has(c) {}

would be essentially identical in terms of performance, and therefore viable for the live profile.

Closed Rejected as Invalid selectors-4

Most helpful comment

I'm going to point to this thread, where Boris Zbarsky has already answered this question in detail:
https://lists.w3.org/Archives/Public/www-style/2010Jan/thread.html#msg473
I'm going to suggest reading carefully through all his replies. And I'm going to close this issue, as the assertion in the OP (“would be essentially identical in terms of performance”) is invalid.

We would all love for this to be performantly solved, and I'm sure it's possible with some kind of sophisticated caching and/or crazy inversion of the existing matching engines, but until someone actually does it and the other implementers agree to also take it on as a priority, it can't go in the spec, which is supposed to reflect an agreement of what should be imminently implemented.

All 7 comments

I'm not an implementer so I don't know how valuable my comments on this should be considered, but I don't think that it is evaluation of the selector that is particularly hard, but rather the fact that it changes the subject and what tests need to be run on which things and when are different and don't fit neatly into existing strategies for avoiding a ton of extra work. That said, I do think there are probably limitations in what you could write and new optimizations that could strike a doable balance.

would be essentially identical in terms of performance, and therefore viable for the live profile.

That is not true. b.matches('a b:has(c)') requires looking at every descendant of b, while c.matches('a b c') does not. Evaluating the selector is much more expensive.

What @bkardell points out is also a problem, too, since that means that changes to an element can now affect the styles of arbitrary ancestors, and computing which ancestors are affected is not quite trivial.

Even with that I don't think it'd be acceptable perf-wise. Parallelism only gives you a linear speedup with the number of cores best-case, while evaluating :has is exponential.

(previous comment which I removed because I didn’t feel like it added anything valuable to the discussion)

Well, at least Stylo is massively parallel, which would certainly help with performance.


would be essentially identical in terms of performance, and therefore viable for the live profile.

That is not true. b.matches('a b:has(c)') requires looking at every descendant of b, while c.matches('a b c') does not. Evaluating the selector is much more expensive.

That’s from JavaScript.

I primarily meant the initial parsing, which ends up being fast‑ish since once you encounter c, you can send an event back to b, which would’ve been marked as potentially matching a :has(…) selector and be updated as such.

That’s from JavaScript. I mean the initial parsing, which ends up being fast‑ish since once you encounter c, you can send an event back to b, which would be marked as potentially matching a :has(…) selector and be updated as such.

That's not true either. The way style engines work is per-element. You build a list of all the rules on the page that match a certain element, for which you effectively need to do the work element.matches does for each selector of the page. There are optimizations of course to avoid matching every selector against every element (so in your example we'd only match it against every b element that has an a ancestor, but those wouldn't handle :has.

I'm going to point to this thread, where Boris Zbarsky has already answered this question in detail:
https://lists.w3.org/Archives/Public/www-style/2010Jan/thread.html#msg473
I'm going to suggest reading carefully through all his replies. And I'm going to close this issue, as the assertion in the OP (“would be essentially identical in terms of performance”) is invalid.

We would all love for this to be performantly solved, and I'm sure it's possible with some kind of sophisticated caching and/or crazy inversion of the existing matching engines, but until someone actually does it and the other implementers agree to also take it on as a priority, it can't go in the spec, which is supposed to reflect an agreement of what should be imminently implemented.

Was this page helpful?
0 / 5 - 0 ratings