Chrome 150 fails to update a generated ::after pseudo-element after an
ancestor class starts matching a selector containing this combination:
:nth-child(3n + 2 of .ad-count):has(+ .LIFY_block)::afterWrapping :nth-child() in :is() makes invalidation work:
:is(:nth-child(3n + 2 of .ad-count)):has(+ .LIFY_block)::afterThe two selectors match the same elements and have the same specificity. This appears to be style under-invalidation, not a parsing or selector matching problem.
- Chrome for Testing 149.0.7827.155 arm64: PASS
- Chrome for Testing 150.0.7871.125 arm64: FAIL
- Google Chrome 150.0.7871.183 arm64: FAIL
Expected result after adding .show-ads:
{"direct":"\"DIRECT\"","wrapped":"\"WRAPPED\""}Broken result:
{"direct":"\"\"","wrapped":"\"WRAPPED\""}Element.matches() reports that both selector forms match after the class
change. The direct form nevertheless retains its old content: "" computed
style.
Open repro.html, or run:
/path/to/chrome \
--headless \
--no-sandbox \
--disable-gpu \
--dump-dom \
file:///absolute/path/to/repro.htmlInspect the JSON in <pre id="result">.
Add a Blink web test that:
- Creates the DOM without
.show-ads. - Forces lifecycle/style update and verifies both pseudo-elements have empty content.
- Adds
.show-adsto each ancestor. - Forces lifecycle/style update.
- Verifies both the direct and
:is()forms now generate their expected content.
The direct form should fail before the fix and pass after it. The :is() form
is a useful control.
Likely locations:
third_party/blink/web_tests/external/wpt/css/selectors/third_party/blink/renderer/core/css/invalidation/third_party/blink/renderer/core/css/invalidation/style_invalidator_test.cc
Regression CL:
- CL: https://chromium-review.googlesource.com/c/chromium/src/+/7849793
- Commit:
b0f674daa93af2b73d331057f3618fccf16ae541 - Position:
refs/heads/main@{#1631300} - Subject:
Move pseudo-element checking into SelectorChecker
Fix CL:
- CL: https://chromium-review.googlesource.com/c/chromium/src/+/8131020
- Commit:
c9014b8f764e8b465211a02d9fcd3273cad35487 - Position:
refs/heads/main@{#1665478} - Bug: https://issues.chromium.org/issues/531138929
- Subject:
Missing reset of pseudo_element for :nth-*(N of S) matching
The regression CL moved pseudo-element validation into SelectorChecker and
made selector sub-contexts responsible for clearing pseudo-element state. The
:nth-child(N of S) filter path in NthIndexCache::MatchesFilter() did not
clear sub_context.pseudo_element.
While resolving a ::before or ::after style, the stale pseudo-element
context leaked into matching the selector list inside :nth-child(... of S).
This made originating-element matching fail even though Element.matches()
on the originating element returned true.
The :is() workaround succeeded because logical selector-list matching
already cleared the pseudo-element context. The fix adds the missing line:
sub_context.pseudo_element = nullptr;The fix includes a WPT at:
third_party/blink/web_tests/external/wpt/css/selectors/nth-of-selector-before.html
Verified release behavior:
- Chrome 149: passes
- Chrome 150: fails
- Chrome 152.0.7977.1 Canary: passes
https://issues.chromium.org/issues/514372707 is related but not identical. The confirmed issue for this regression is https://issues.chromium.org/issues/531138929.