Implement `:is()` and `:where()` selectors
Categories
(Core :: CSS Parsing and Computation, enhancement, P3)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox77 | --- | fixed |
People
(Reporter: e7358d9c, Assigned: emilio)
References
(Blocks 1 open bug, )
Details
(Keywords: dev-doc-complete, Whiteboard: [layout:backlog:77])
Attachments
(6 files)
|
47 bytes,
text/x-phabricator-request
|
Details | Review | |
|
47 bytes,
text/x-phabricator-request
|
Details | Review | |
|
47 bytes,
text/x-phabricator-request
|
Details | Review | |
|
47 bytes,
text/x-phabricator-request
|
Details | Review | |
|
47 bytes,
text/x-phabricator-request
|
Details | Review | |
|
47 bytes,
text/x-phabricator-request
|
Details | Review |
Comment 1•7 years ago
|
||
Comment 2•7 years ago
|
||
| Assignee | ||
Comment 4•7 years ago
|
||
Updated•7 years ago
|
| Assignee | ||
Comment 5•5 years ago
|
||
The tricky part of :is() and :where() is that they can have combinators inside,
so something like this is valid:
foo:is(#bar > .baz) ~ taz
The current invalidation logic is based on the assumption that you can
represent a combinator as a (selector, offset) tuple, which are stored in the
Dependency struct. This assumption breaks with :is() and :where(), so we need
to make them be able to represent a combinator in an "inner" selector.
For this purpose, we add a parent dependency. With it, when invalidating
inside the :is() we can represent combinators inside as a stack.
The basic idea is that, for the example above, when an id of "bar" is added or
removed, we'd find a dependency like:
Dependency {
selector: #bar > .baz,
offset: 1, // pointing to the `>` combinator
parent: Some(Dependency {
selector: foo:is(#bar > .baz) > taz,
offset: 1, // Pointing to the `~` combinator.
parent: None,
})
}
That way, we'd start matching at the element that changed, towards the right,
and if we find an element that matches .baz, instead of invalidating that
element, we'd look at the parent dependency, then double-check that the whole
left-hand-side of the selector (foo:is(#bar > .baz)) actually changed, and then
keep invalidating to the right using the parent dependency as usual.
This patch only builds the data structure and keeps the code compiling, the
actual invalidation work will come in a following patch.
Updated•5 years ago
|
| Assignee | ||
Comment 6•5 years ago
|
||
That way we can look at the parent dependency as described in the previous
patch. An alternative would be to add a:
parent_dependency: Option<&'a Dependency>
on construction to Invalidation, but this way seems slightly better to avoid
growing the struct. It's not even one more indirection because the selector is
contained directly in the Dependency struct.
Depends on D71421
| Assignee | ||
Comment 7•5 years ago
|
||
See the comment about why this is valuable. For a selector like:
.foo:is(.bar) > .baz
Before this patch we'd generate an Dependency for .bar like this:
Dependency {
selector: .bar,
offset: 0,
parent: Some(Dependency {
selector: .foo:is(.bar) > .baz,
offset: 1, // Pointing to the `>` combinator.
parent: None,
}),
}
After this patch we'd generate just:
Dependency {
selector: .foo:is(.bar) > .baz,
offset: 1, // Pointing to the `>` combinator.
parent: None,
}
This is not only less memory but also less work. The reason for that is that,
before this patch, when .bar changes, we'd look the dependency, and see there's
a parent, and then scan that, so we'd match .bar two times, one for the
initial dependency, and one for .foo:is(.bar).
Instead, with this we'd only check .foo:is(.bar) once.
Depends on D71422
| Assignee | ||
Comment 8•5 years ago
|
||
There are a bunch of missing tests, and there are some tests that don't
match the current spec text, that I need to write or fix before I enable the
feature everywhere.
But there are fairly complex invalidation tests, that we pass flawlessly :)
Depends on D71423
Updated•5 years ago
|
https://developer.mozilla.org/en-US/docs/Web/CSS/:is#Avoiding_selector_list_invalidation
This feature in the documentation does not seem to be implemented
| Assignee | ||
Comment 10•5 years ago
|
||
(In reply to 709922234 from comment #9)
That is not in https://drafts.csswg.org/selectors/, other than in an issue, as far as I can tell. Not clear how should we serialize :is with an empty list. Should writing :is() be valid?
| Assignee | ||
Comment 12•5 years ago
|
||
This way, something like:
*:where(.foo, .bar)
Will end up twice on the selector map, just as if you would've written
.foo, .bar.
But we're a bit careful to not be wasteful, so:
.foo:where(div, span)
Will still end up using the .foo bucket.
It needs a bit of borrow-checker gymnastics to avoid cloning the entry
in the common path. It's a bit gross but not too terrible I think.
Depends on D71424
| Assignee | ||
Comment 13•5 years ago
|
||
We can only collect hashes from single-length selectors, for obvious
reasons.
Depends on D71457
Updated•5 years ago
|
Updated•5 years ago
|
Updated•5 years ago
|
Comment 14•5 years ago
|
||
Comment 15•5 years ago
|
||
Failure logs: https://treeherder.mozilla.org/logviewer.html#?job_id=299168652&repo=autoland
https://treeherder.mozilla.org/logviewer.html#?job_id=299170392&repo=autoland
https://treeherder.mozilla.org/logviewer.html#?job_id=299170400&repo=autoland
Backout link: https://hg.mozilla.org/integration/autoland/rev/9429eb59e262fa6e32c42f9a637474377c56041d
Comment 16•5 years ago
|
||
Comment 18•5 years ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/979dcbf7da31
https://hg.mozilla.org/mozilla-central/rev/95e39fd51ace
https://hg.mozilla.org/mozilla-central/rev/18e9d0eb2beb
https://hg.mozilla.org/mozilla-central/rev/58b044f3fda8
https://hg.mozilla.org/mozilla-central/rev/e6cf4f05fff1
https://hg.mozilla.org/mozilla-central/rev/f3686ddab414
Comment 19•5 years ago
|
||
MDN documentation updated accordingly; see https://github.com/mdn/sprints/issues/3190#issuecomment-630280034 for the specifics.
Let me know if you need anything else here; a review would be nice. Thanks!
Description
•