Closed Bug 1770136 Opened 3 years ago Closed 3 years ago

[wpt-sync] Sync PR 34123 - Support invalidation of logical combinations inside :has()

Categories

(Core :: CSS Parsing and Computation, task, P4)

task

Tracking

()

RESOLVED FIXED
102 Branch
Tracking Status
firefox102 --- fixed

People

(Reporter: wpt-sync, Unassigned)

References

()

Details

(Whiteboard: [wptsync downstream])

Sync web-platform-tests PR 34123 into mozilla-central (this bug is closed when the sync is complete).

PR: https://github.com/web-platform-tests/wpt/pull/34123
Details from upstream follow.

Byungwoo Lee <blee@igalia.com> wrote:

Support invalidation of logical combinations inside :has()

This CL allows these logical combinations inside ':has()'.

  • :is()
  • :where()
  • :not()

Current logic supports invalidation for the changes on the descendants,
next sibling or next sibling descendant of the :has() scope element.

But current logic doesn't support invalidation for the changes on the
previous sibling, ancestor or ancestor sibling of the :has() scope
element.

The missing cases will be handled later in the separated CLs.

wpt tests were added to show this status.

Bug: 66958
Change-Id: I184f61b96ca7a64d1124fed2298c53132cd92666
Reviewed-on: https://chromium-review.googlesource.com/3653650
WPT-Export-Revision: 0c0993502b589e0e060b6cf9ca2cf32be4b0ac3b

Component: web-platform-tests → CSS Parsing and Computation
Product: Testing → Core

CI Results

Ran 0 Firefox configurations based on mozilla-central, and Firefox, Chrome, and Safari on GitHub CI

Total 2 tests and 92 subtests

Status Summary

Firefox

OK : 2
PASS: 52
FAIL: 132

Chrome

OK : 2
PASS: 133
FAIL: 51

Safari

OK : 2
PASS: 160
FAIL: 24

Links

GitHub PR Head
GitHub PR Base

Details

Firefox-only Failures

  • /css/selectors/invalidation/is-pseudo-containing-complex-in-has.html [wpt.fyi]
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.add('red') : check matches (false): FAIL
    • [ .red:has(#descendant:is(.a .b)) ] #parent.classList.remove('a') : check matches (false): FAIL
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.remove('a') : check matches (false): FAIL
    • [ .red:has(#descendant:is(.a .b)) ] #child.classList.remove('a') : check matches (false): FAIL
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.remove('red') : check matches (false): FAIL
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #has_scope.classList.add('green') : check matches (false): FAIL
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check matches (false): FAIL
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #previous.classList.remove('c') : check matches (false): FAIL
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check matches (false): FAIL
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #has_scope.classList.remove('green') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.add('blue') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #previous.classList.remove('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.remove('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #direct_next.classList.remove('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.remove('blue') : check matches (false): FAIL
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #has_scope.classList.add('yellow') : check matches (false): FAIL
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #parent.classList.remove('h') : check matches (false): FAIL
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #has_scope.classList.remove('yellow') : check matches (false): FAIL
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check matches (false): FAIL
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check matches (false): FAIL
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #has_scope.classList.remove('purple') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('orange') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.remove('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('orange') : check matches (false): FAIL
  • /css/selectors/invalidation/not-pseudo-containing-complex-in-has.html [wpt.fyi]
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('red') : check matches (true): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('red') : check #has_scope color: FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.add('a') : check matches (false): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.remove('a') : check matches (true): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.remove('a') : check #has_scope color: FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('a') : check matches (false): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('a') : check matches (true): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('a') : check #has_scope color: FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.add('a') : check matches (false): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.remove('a') : check matches (true): FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.remove('a') : check #has_scope color: FAIL
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('red') : check matches (false): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.add('green') : check matches (true): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.add('green') : check #has_scope color: FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.add('c') : check matches (false): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check matches (true): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check #has_scope color: FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.add('c') : check matches (false): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.remove('c') : check matches (true): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.remove('c') : check #has_scope color: FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.add('c') : check matches (false): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check matches (true): FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check #has_scope color: FAIL
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.remove('green') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('blue') : check matches (true): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('blue') : check #has_scope color: FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.add('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.remove('f') : check matches (true): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.remove('f') : check #has_scope color: FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('f') : check matches (true): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('f') : check #has_scope color: FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.add('f') : check matches (false): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.remove('f') : check matches (true): FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.remove('f') : check #has_scope color: FAIL
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('blue') : check matches (false): FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.add('yellow') : check matches (true): FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.add('yellow') : check #has_scope color: FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.add('h') : check matches (false): FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.remove('h') : check matches (true): FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.remove('h') : check #has_scope color: FAIL
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.remove('yellow') : check matches (false): FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check matches (true): FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check #has_scope color: FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.add('j') : check matches (false): FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check matches (true): FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check #has_scope color: FAIL
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.remove('purple') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('orange') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('orange') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('n') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('n') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('m') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('n') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('n') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('n') : check matches (false): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('m') : check matches (true): FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('m') : check #has_scope color: FAIL
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('orange') : check matches (false): FAIL

New Tests That Don't Pass

  • /css/selectors/invalidation/is-pseudo-containing-complex-in-has.html [wpt.fyi]
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.add('red') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #parent.classList.add('a') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #parent.classList.add('a') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .red:has(#descendant:is(.a .b)) ] #parent.classList.remove('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.add('a') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.add('a') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.remove('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #child.classList.add('a') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #child.classList.add('a') : check #has_scope color: FAIL (Chrome: FAIL, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #child.classList.remove('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:is(.a .b)) ] #has_scope.classList.remove('red') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #has_scope.classList.add('green') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #parent_previous.classList.add('c') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #parent_previous.classList.add('c') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #previous.classList.add('c') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #previous.classList.add('c') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #previous.classList.remove('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #child_previous.classList.add('c') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #child_previous.classList.add('c') : check #has_scope color: FAIL (Chrome: FAIL, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:is(.c ~ .d .e)) ] #has_scope.classList.remove('green') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.add('blue') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #previous.classList.add('f') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #previous.classList.add('f') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #previous.classList.remove('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.add('f') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.add('f') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.remove('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #direct_next.classList.add('f') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #direct_next.classList.add('f') : check #has_scope color: FAIL (Chrome: FAIL, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #direct_next.classList.remove('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:is(.f ~ .g)) ] #has_scope.classList.remove('blue') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #has_scope.classList.add('yellow') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #parent.classList.add('h') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #parent.classList.add('h') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #parent.classList.remove('h') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:is(.h .i)) ] #has_scope.classList.remove('yellow') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #parent_previous.classList.add('j') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #parent_previous.classList.add('j') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:is(.j ~ .k .l)) ] #has_scope.classList.remove('purple') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('orange') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.add('m') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.add('m') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.add('n') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.add('n') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #parent.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('m') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('m') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('n') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.add('n') : check #has_scope color: FAIL (Chrome: FAIL, Safari: FAIL)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.add('m') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.add('m') : check #has_scope color: FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.remove('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.add('n') : check matches (true): FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.add('n') : check #has_scope color: FAIL (Chrome: FAIL, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #child.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:is(:is(.m, .n) .o)) ] #has_scope.classList.remove('orange') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
  • /css/selectors/invalidation/not-pseudo-containing-complex-in-has.html [wpt.fyi]
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('red') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('red') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.add('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.remove('a') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #parent.classList.remove('a') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.add('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('a') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('a') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.add('a') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.remove('a') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #child.classList.remove('a') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .red:has(#descendant:not(.a .b)) ] #has_scope.classList.remove('red') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.add('green') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.add('green') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.add('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #parent_previous.classList.remove('c') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.add('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.remove('c') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #previous.classList.remove('c') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.add('c') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #child_previous.classList.remove('c') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .green:has(#descendant:not(.c ~ .d .e)) ] #has_scope.classList.remove('green') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('blue') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('blue') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.add('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.remove('f') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #previous.classList.remove('f') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.add('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('f') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('f') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.add('f') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.remove('f') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #direct_next.classList.remove('f') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .blue:has(~ #indirect_next:not(.f ~ .g)) ] #has_scope.classList.remove('blue') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.add('yellow') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.add('yellow') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.add('h') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.remove('h') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #parent.classList.remove('h') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .yellow:has(~ #indirect_next:not(.h .i)) ] #has_scope.classList.remove('yellow') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.add('purple') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.add('j') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #parent_previous.classList.remove('j') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .purple:has(~ #indirect_next:not(.j ~ .k .l)) ] #has_scope.classList.remove('purple') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('orange') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('orange') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('n') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.add('n') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #parent.classList.remove('m') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.add('n') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('m') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('m') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('n') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.add('n') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('n') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('m') : check matches (true): FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #child.classList.remove('m') : check #has_scope color: FAIL (Chrome: PASS, Safari: PASS)
    • [ .orange:has(#descendant:not(.m:not(.n) .o)) ] #has_scope.classList.remove('orange') : check matches (false): FAIL (Chrome: PASS, Safari: PASS)
Pushed by wptsync@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/edd729ec61fb [wpt PR 34123] - Support invalidation of logical combinations inside :has(), a=testonly https://hg.mozilla.org/integration/autoland/rev/8f2958f51685 [wpt PR 34123] - Update wpt metadata, a=testonly
Status: NEW → RESOLVED
Closed: 3 years ago
Resolution: --- → FIXED
Target Milestone: --- → 102 Branch
You need to log in before you can comment on or make changes to this bug.