Fix :has() invalidation bug on the inserted element/subtree
:has() invalidation doesn't work for any mutation on an inserted
element or subtree if the element or subtree didn't trigger the
:has() invalidation when it inserted.
<style> .a:has(.b) { color: green } </style>
<div id="subject" class="a"></div>
<script>
const child = document.createElement('div');
subject.appendChild(child);
child.classList.add('b');
getComputedStyle(subject).color; // Must be green. But it isn't.
</script>
Currently, only the SelectorChecker set the SiblingsAffectedByHas and
AncestorsOrAncestorSiblingsAffectedByHas flags while checking a :has()
argument selector. So if an insertion didn't trigger any :has()
invalidation, the inserted element or subtree doesn't have the flags
set. If an element doesn't have the flag set, any mutations on the
element cannot trigger the :has() invalidation.
To fix this bug, StyleEngine gets the two flags from the parent or
previous sibling of the inserted element (or the root of inserted
subtree), and set the flags on the inserted element or subtree.
- Get AncestorsOrAncestorSiblingsAffectedByHas from parent
- Get SiblingsAffectedByHas from previous sibling
To prevent unnecessary AncestorsOrAncestorSiblingsAffectdByHas flag
set, the SiblingsAffectedByHas flag is divided into these two flags.
- SiblingsAffectedByHasForSiblingRelationship
The :has() argument starts with a sibling combinator, and it
doesn't have any child or descendant combinator.
(.e.g. '.a:has(~ .b) {}')
- SiblingsAffectedByHasForSiblingDescendantRelationship
The :has() argument starts with sibling combinator, and it also
has a child or descendant combinator.
(.e.g. '.a:has(~ .b .c) {}'
Only when one or more of these two conditions are satisfied,
StyleEngine sets the AncestorsOrAncestorSiblingsAffectedByHas flag
on the descendants of the inserted subtree root.
- If the previous sibling has the
SiblingsAffectedByHasForSiblingDescendantRelationship flag set.
- If the parent has the AncestorsOrAncestorSiblingsAffectedByHas flag
set
The :has() scope element need to have the flags set also because
the inserted element can be the only element that possibly affects
the :has() state of the :has() scope element. (Then the StyleEngine
needs to get the flags from the :has() scope element) So, the flag
setting logic in the SelectorChecker::CheckPseudoHas() was changed
to set the flags on the :has() scope element before starting the
:has() argument checking traversal.
The logic of setting SiblingsAffectedByHas flags was relocated to
be invoked before starting :has() argument checking traversal to
simplify the flag setting logic.
To collect flags setting related code in the same place, the
AffectedByHasIterator class was moved to the selector_checker.cc
file. And it was renamed as below to clarify its purpose.
- AncestorsOrAncestorSiblingsAffectedByHasIterator
This CL includes the fix for https://crbug.com/1315107. For every
parsing finished element, if the parent or previous sibling of the
element has the flags set, StyleEngine checks the finished element
and its descendants for :has() invalidation. Due to the logic of
flag checking, the descendant traversal will be done only for the
topmost parsing finished element.
Bug: 669058, 1315107
Change-Id: I20c5b7e2956514d10257e90270982ab3afd93cb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3590254
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Byungwoo Lee <blee@igalia.com>
Cr-Commit-Position: refs/heads/main@{#996014}
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all users to experience the web.
The project's web site is https://www.chromium.org.
To check out the source code locally, don't use git clone! Instead, follow the instructions on how to get the code.
Documentation in the source is rooted in docs/README.md.
Learn how to Get Around the Chromium Source Code Directory Structure .
For historical reasons, there are some small top level directories. Now the guidance is that new top level directories are for product (e.g. Chrome, Android WebView, Ash). Even if these products have multiple executables, the code should be in subdirectories of the product.
If you found a bug, please file it at https://crbug.com/new.