diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/css/selectors/invalidation | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/selectors/invalidation')
47 files changed, 5073 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/selectors/invalidation/any-link-pseudo.html b/testing/web-platform/tests/css/selectors/invalidation/any-link-pseudo.html new file mode 100644 index 0000000000..9792fd0ebe --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/any-link-pseudo.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: :any-link</title> + <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com"> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-any-link-pseudo"> + <meta name="assert" content="This tests that the :any-link selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + #link { background-color: red } + #link:any-link { background-color: green } + #link + div { color: pink } + </style> + <a id="link">This link should have a green background.</a> + <div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <script> + test(function() { + var red = "rgb(255, 0, 0)"; + var green = "rgb(0, 128, 0)"; + + assert_equals(getComputedStyle(link).backgroundColor, red); + + link.href = "not-visited.html"; + + assert_equals(getComputedStyle(link).backgroundColor, green); + }, "Style was recalculated for the :any-link pseudo class."); + + </script> + </head> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/attribute-or-elemental-selectors-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/attribute-or-elemental-selectors-in-has.html new file mode 100644 index 0000000000..d0edb1e5e4 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/attribute-or-elemental-selectors-in-has.html @@ -0,0 +1,149 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selectors Invalidation: :has() invalidation basic</title> +<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +.subject:has(> .child) { color: red } +.subject:has(.descendant) { color: green } +.subject:has([attrname=descendant]) { color: blue } +.subject:has(#div_descendant) { color: yellow } +.subject:has(descendant) { color: yellowgreen } +</style> +<main id=main> + <div id=div_subject class="subject"> + <div id=div_child> + <div id=div_grandchild></div> + </div> + </div> +</main> +<script> + let grey = 'rgb(128, 128, 128)'; + let red = 'rgb(255, 0, 0)'; + let green = 'rgb(0, 128, 0)'; + let blue = 'rgb(0, 0, 255)'; + let yellow = 'rgb(255, 255, 0)'; + let yellowgreen = 'rgb(154, 205, 50)'; + + function test_div(test_name, el, color) { + test(function() { + assert_equals(getComputedStyle(el).color, color); + }, test_name + ': div#' + el.id + '.color'); + } + + test_div('initial_color', div_subject, grey); + test_div('initial_color', div_child, grey); + test_div('initial_color', div_grandchild, grey); + + div_child.classList.add('child'); + test_div('add .child to #div_child', div_subject, red); + div_child.classList.remove('child'); + test_div('remove .child from #div_child', div_subject, grey); + + div_grandchild.classList.add('child'); + test_div('add .child to #div_grandchild', div_subject, grey); + div_grandchild.classList.remove('child'); + test_div('remove .child from #div_grandchild', div_subject, grey); + + div_child.classList.add('descendant'); + test_div('add .descendant to #div_child', div_subject, green); + div_child.classList.remove('descendant'); + test_div('remove .descendant from #div_child', div_subject, grey); + + div_grandchild.classList.add('descendant'); + test_div('add .descendant to #div_grandchild', div_subject, green); + div_grandchild.classList.remove('descendant'); + test_div('remove .descendant from #div_grandchild', div_subject, grey); + + div_grandchild.setAttribute('attrname', 'descendant'); + test_div('set descendant to #div_grandchild[attrname]', div_subject, blue); + div_grandchild.setAttribute('attrname', ''); + test_div('clear #div_grandchild[attrname]', div_subject, grey); + + div_grandchild.id = 'div_descendant'; + test_div('change #div_grandchild to #div_descendant', div_subject, yellow); + div_descendant.id = 'div_grandchild'; + test_div('change #div_descendant to #div_grandchild', div_subject, grey); + + { + const descendant = document.createElement('descendant'); + div_subject.appendChild(descendant); + test_div('add descendant to #div_subject', div_subject, yellowgreen); + div_subject.removeChild(descendant); + test_div('remove descendant from #div_subject', div_subject, grey); + } + + { + const div = document.createElement('div'); + const descendant = document.createElement('descendant'); + div.appendChild(descendant); + div_subject.appendChild(div); + test_div('add "div > descendant" to #div_subject', div_subject, yellowgreen); + div_subject.removeChild(div); + test_div('remove "div > descendant" from #div_subject', div_subject, grey); + } + + { + const child = document.createElement('div'); + child.classList.add('child'); + div_subject.appendChild(child); + test_div('add div.child to #div_subject', div_subject, red); + div_subject.removeChild(child); + test_div('remove div.child from #div_subject', div_subject, grey); + } + + { + const descendant = document.createElement('div'); + descendant.classList.add('descendant'); + const div = document.createElement('div'); + div.appendChild(descendant); + div_subject.appendChild(div); + test_div('add "div > div.descendant" to #div_subject', div_subject, green); + div_subject.removeChild(div); + test_div('remove "div > div.descendant" from #div_subject', div_subject, grey); + } + + { + const child = document.createElement('div'); + child.id = 'div_descendant'; + div_subject.appendChild(child); + test_div('add div#div_descendant to #div_subject', div_subject, yellow); + div_subject.removeChild(child); + test_div('remove div#div_descendant from #div_subject', div_subject, grey); + } + + { + const descendant = document.createElement('div'); + descendant.id = 'div_descendant'; + const div = document.createElement('div'); + div.appendChild(descendant); + div_subject.appendChild(div); + test_div('add "div#div_descendant" to #div_subject', div_subject, yellow); + div_subject.removeChild(div); + test_div('remove "div#div_descendant" from #div_subject', div_subject, grey); + } + + { + const child = document.createElement('div'); + child.setAttribute('attrname', 'descendant'); + div_subject.appendChild(child); + test_div('add div[attrname] to #div_subject', div_subject, blue); + div_subject.removeChild(child); + test_div('remove div[attrname] from #div_subject', div_subject, grey); + } + + { + const descendant = document.createElement('div'); + descendant.setAttribute('attrname', 'descendant'); + const div = document.createElement('div'); + div.appendChild(descendant); + div_subject.appendChild(div); + test_div('add "div > div[attrname]" to #div_subject', div_subject, blue); + div_subject.removeChild(div); + test_div('remove "div > div[attrname]" from #div_subject', div_subject, grey); + } + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/attribute.html b/testing/web-platform/tests/css/selectors/invalidation/attribute.html new file mode 100644 index 0000000000..3bda52c91d --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/attribute.html @@ -0,0 +1,234 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: attribute</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#attribute-selectors"> + <meta name="assert" content="This tests that the attribute selectors are effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + div { + color: gray; + } + + #a1[style] { + color: blue; + } + #a1[style] > #b1 { + color: green; + } + #a1[style] #c1 { + color: red; + } + #a1[style] + #d1 { + color: yellow; + } + + [id=a2] { + color: blue; + } + [id=a2] > #b2 { + color: green; + } + [id=a2] #c2 { + color: red; + } + [id=a2] + #d2 { + color: yellow; + } + + #a3[class~=q] { + color: blue; + } + #a3[class~=q] > #b3 { + color: green; + } + #a3[class~=q] #c3 { + color: red; + } + #a3[class~=q] + #d3 { + color: yellow; + } + + #a4[run|=one] { + color: blue; + } + #a4[run|=one] > #b4 { + color: green; + } + #a4[run|=one] #c4 { + color: red; + } + #a4[run|=one] + #d4 { + color: yellow; + } + + #a5 { + color: blue; + } + #a5 > #b5 { + color: green; + } + #a5 #c5 { + color: red; + } + #a5 + #d5 { + color: yellow; + } + + #a6.q { + color: blue; + } + #a6.q > #b6 { + color: green; + } + #a6.q #c6 { + color: red; + } + #a6.q + #d6 { + color: yellow; + } + + </style> + </head> + <body> + + <div id="a1"> + <div id="b1"> + <div id="c1"> + </div> + </div> + </div> + <div id="d1"> + </div> + + <div> + <div id="b2"> + <div id="c2"> + </div> + </div> + </div> + <div id="d2"> + </div> + + <div id="a3"> + <div id="b3"> + <div id="c3"> + </div> + </div> + </div> + <div id="d3"> + </div> + + <div id="a4"> + <div id="b4"> + <div id="c4"> + </div> + </div> + </div> + <div id="d4"> + </div> + + <div> + <div id="b5"> + <div id="c5"> + </div> + </div> + </div> + <div id="d5"> + </div> + + <div id="a6"> + <div id="b6"> + <div id="c6"> + </div> + </div> + </div> + <div id="d6"> + </div> + + <script> + const gray = "rgb(128, 128, 128)"; + const blue = "rgb(0, 0, 255)"; + const green = "rgb(0, 128, 0)"; + const red = "rgb(255, 0, 0)"; + const yellow = "rgb(255, 255, 0)"; + + function assertGray(a, b, c, d) { + assert_equals(getComputedStyle(a).color, gray); + assert_equals(getComputedStyle(b).color, gray); + assert_equals(getComputedStyle(c).color, gray); + assert_equals(getComputedStyle(d).color, gray); + } + + function assertColorful(a, b, c, d) { + assert_equals(getComputedStyle(a).color, blue); + assert_equals(getComputedStyle(b).color, green); + assert_equals(getComputedStyle(c).color, red); + assert_equals(getComputedStyle(d).color, yellow); + } + + test(() => { + assertGray(a1, b1, c1, d1); + a1.style.visibility = "visible"; + assertColorful(a1, b1, c1, d1); + a1.removeAttribute('style'); + assertGray(a1, b1, c1, d1); + }, "[att] selector is effective"); + + test(() => { + const a2 = b2.parentElement; + assertGray(a2, b2, c2, d2); + a2.id = 'x-a2'; + assertGray(a2, b2, c2, d2); + a2.id = 'a2'; + assertColorful(a2, b2, c2, d2); + a2.id = 'a2-y'; + assertGray(a2, b2, c2, d2); + }, "[att=val] selector is effective"); + + test(() => { + assertGray(a3, b3, c3, d3); + a3.setAttribute('class', 'p q r'); + assertColorful(a3, b3, c3, d3); + a3.setAttribute('class', 'q-r'); + assertGray(a3, b3, c3, d3); + }, "[att~=val] selector is effective"); + + test(() => { + assertGray(a4, b4, c4, d4); + a4.setAttribute('run', 'one'); + assertColorful(a4, b4, c4, d4); + a4.setAttribute('run', 'one two three'); + assertGray(a4, b4, c4, d4); + a4.setAttribute('run', 'one-two-three'); + assertColorful(a4, b4, c4, d4); + a4.setAttribute('run', 'zero-one'); + assertGray(a4, b4, c4, d4); + }, "[att|=val] selector is effective"); + + test(() => { + const a5 = b5.parentElement; + assertGray(a5, b5, c5, d5); + a5.setAttribute('id', 'x-a5'); + assertGray(a5, b5, c5, d5); + a5.setAttribute('id', 'a5'); + assertColorful(a5, b5, c5, d5); + a5.setAttribute('id', 'a5-y'); + assertGray(a5, b5, c5, d5); + }, "#id selector is effective"); + + test(() => { + assertGray(a6, b6, c6, d6); + a6.classList.add('p'); + a6.classList.add('q'); + a6.classList.add('r'); + assertColorful(a6, b6, c6, d6); + a6.classList.remove('q'); + a6.classList.add('q-r'); + assertGray(a6, b6, c6, d6); + }, ".class selector is effective"); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/child-indexed-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/child-indexed-pseudo-classes-in-has.html new file mode 100644 index 0000000000..5c689e2333 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/child-indexed-pseudo-classes-in-has.html @@ -0,0 +1,116 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: child-indexed pseudo classes in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #container ~ div { color: grey } + #container:has(:only-child) ~ #only_child { color: red } + #container:has(.orange:first-child) ~ #first_child { color: orange } + #container:has(.yellow:first-child) ~ #first_child { color: yellow } + #container:has(.green:first-child) ~ #first_child { color: green } + #container:has(.orange:last-child) ~ #last_child { color: orange } + #container:has(.yellow:last-child) ~ #last_child { color: yellow } + #container:has(.green:last-child) ~ #last_child { color: green } + #container:has(.orange:nth-child(3n)) ~ #nth_child_3n { color: orange } + #container:has(.yellow:nth-child(3n)) ~ #nth_child_3n { color: yellow } + #container:has(.green:nth-child(3n)) ~ #nth_child_3n { color: green } + #container:has(.orange:nth-child(3n+1)) ~ #nth_child_3n_1 { color: orange } + #container:has(.yellow:nth-child(3n+1)) ~ #nth_child_3n_1 { color: yellow } + #container:has(.green:nth-child(3n+1)) ~ #nth_child_3n_1 { color: green } + #container:has(.orange:nth-child(3n+2)) ~ #nth_child_3n_2 { color: orange } + #container:has(.yellow:nth-child(3n+2)) ~ #nth_child_3n_2 { color: yellow } + #container:has(.green:nth-child(3n+2)) ~ #nth_child_3n_2 { color: green } + #container:has(.orange:nth-child(3n)) ~ #nth_child_3n { color: orange } + #container:has(.yellow:nth-child(3n)) ~ #nth_child_3n { color: yellow } + #container:has(.green:nth-child(3n)) ~ #nth_child_3n { color: green } +</style> +<div id="container"> +</div> +<div id="only_child"></div> +<div id="first_child"></div> +<div id="last_child"></div> +<div id="nth_child_3n_1"></div> +<div id="nth_child_3n_2"></div> +<div id="nth_child_3n"></div> +<script> +const grey = "rgb(128, 128, 128)"; +const red = "rgb(255, 0, 0)"; +const orange = "rgb(255, 165, 0)"; +const yellow = "rgb(255, 255, 0)"; +const green = "rgb(0, 128, 0)"; + +function testColors(test_name, + only_child_color, + first_child_color, + last_child_color, + nth_child_3n_1_color, + nth_child_3n_2_color, + nth_child_3n_color) { + test(function() { + assert_equals(getComputedStyle(only_child).color, only_child_color); + }, test_name + ": #only_child"); + test(function() { + assert_equals(getComputedStyle(first_child).color, first_child_color); + }, test_name + ": #first_child"); + test(function() { + assert_equals(getComputedStyle(last_child).color, last_child_color); + }, test_name + ": #last_child"); + test(function() { + assert_equals(getComputedStyle(nth_child_3n_1).color, nth_child_3n_1_color); + }, test_name + ": #nth_child_3n_1"); + test(function() { + assert_equals(getComputedStyle(nth_child_3n_2).color, nth_child_3n_2_color); + }, test_name + ": #nth_child_3n_2"); + test(function() { + assert_equals(getComputedStyle(nth_child_3n).color, nth_child_3n_color); + }, test_name + ": #nth_child_3n"); +} + +testColors("Initial colors", grey, grey, grey, grey, grey, grey); + +let div1 = document.createElement("div"); +div1.id = "div1"; +div1.classList.add("green"); +container.insertBefore(div1, container.firstChild); +testColors("Prepend #div1.green", red, green, green, green, grey, grey); + +let div2 = document.createElement("div"); +div2.id = "div2"; +div2.classList.add("yellow"); +container.insertBefore(div2, container.firstChild); +testColors("Prepend #div2.yellow", grey, yellow, green, yellow, green, grey); + +let div3 = document.createElement("div"); +div3.id = "div3"; +div3.classList.add("orange"); +container.insertBefore(div3, container.firstChild); +testColors("Prepend #div3.orange", grey, orange, green, orange, yellow, green); + +let div4 = document.createElement("div"); +div4.id = "div4"; +container.insertBefore(div4, container.firstChild); +testColors("Prepend #div4", grey, grey, green, green, orange, yellow); + +let div5 = document.createElement("div"); +div5.id = "div5"; +container.insertBefore(div5, container.firstChild); +testColors("Prepend #div5", grey, grey, green, yellow, green, orange); + +div1.remove(); +testColors("Remove #div1", grey, grey, yellow, yellow, grey, orange); + +div2.remove(); +testColors("Remove #div2", grey, grey, orange, grey, grey, orange); + +div3.remove(); +testColors("Remove #div3", grey, grey, grey, grey, grey, grey); + +div4.remove(); +testColors("Remove #div4", red, grey, grey, grey, grey, grey); + +div5.remove(); +testColors("Remove #div5", grey, grey, grey, grey, grey, grey); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/class-id-attr-ref.html b/testing/web-platform/tests/css/selectors/invalidation/class-id-attr-ref.html new file mode 100644 index 0000000000..a3cc6d3d8f --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/class-id-attr-ref.html @@ -0,0 +1,9 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS test reference</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<style> +div { color: green; } +</style> +<div>This should be green</div> +<div>And this too</div> diff --git a/testing/web-platform/tests/css/selectors/invalidation/class-id-attr.html b/testing/web-platform/tests/css/selectors/invalidation/class-id-attr.html new file mode 100644 index 0000000000..91a6f1a3a5 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/class-id-attr.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: [id] and [class] attribute selectors are invalidated correctly.</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#attribute-selectors"> +<link rel="match" href="class-id-attr-ref.html"> +<style> +[class="foo"] { + color: green; +} +[id="baz"] { + color: green; +} +</style> +<div id="foo">This should be green</div> +<div id="bar">And this too</div> +<script> +onload = function() { + document.documentElement.offsetTop; + foo.classList.add("foo"); + bar.setAttribute("id", "baz"); + document.documentElement.offsetTop; +} +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/defined.html b/testing/web-platform/tests/css/selectors/invalidation/defined.html new file mode 100644 index 0000000000..565ed552e1 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/defined.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: :defined</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-defined-pseudo"> + <meta name="assert" content="This tests that the :defined selector is effective"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/semantics-other.html#selector-defined"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + #container { + color: gray; + } + + #a1:defined { + color: blue; + } + :defined + #b1 { + color: green; + } + :defined > #c1 { + color: red; + } + div + :defined + * #d1 { + color: yellow; + } + </style> + </head> + <body> + <section id="container"> + <elucidate-late id="a1"></elucidate-late> + <div id="b1"></div> + <elucidate-late> + <div id="c1"></div> + </elucidate-late> + <div> + <div id="d1"></div> + </div> + </section> + + <script> + const gray = "rgb(128, 128, 128)"; + const blue = "rgb(0, 0, 255)"; + const green = "rgb(0, 128, 0)"; + const red = "rgb(255, 0, 0)"; + const yellow = "rgb(255, 255, 0)"; + + function assertGray(a, b, c, d) { + assert_equals(getComputedStyle(a).color, gray); + assert_equals(getComputedStyle(b).color, gray); + assert_equals(getComputedStyle(c).color, gray); + assert_equals(getComputedStyle(d).color, gray); + } + + function assertColorful(a, b, c, d) { + assert_equals(getComputedStyle(a).color, blue); + assert_equals(getComputedStyle(b).color, green); + assert_equals(getComputedStyle(c).color, red); + assert_equals(getComputedStyle(d).color, yellow); + } + + class ElucidateLate extends HTMLElement { + constructor() { + super(); + } + } + + test(() => { + assertGray(a1, b1, c1, d1); + customElements.define('elucidate-late', ElucidateLate); + assertColorful(a1, b1, c1, d1); + }, ":defined selector is effective"); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/empty-pseudo-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/empty-pseudo-in-has.html new file mode 100644 index 0000000000..83cf051565 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/empty-pseudo-in-has.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :empty in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #subject { color: red } + #subject:has(:empty) { color: green } + #subject:has(:not(:empty)) { color: blue } +</style> +<div id="subject"></div> +<script> +const red = 'rgb(255, 0, 0)'; +const green = 'rgb(0, 128, 0)'; +const blue = 'rgb(0, 0, 255)'; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +testColor("Empty #subject", red); + +let child = document.createElement("div"); +child.id = "child"; +subject.appendChild(child); + +testColor("Insert div#child to #subject", green); + +child.appendChild(document.createElement("div")); + +testColor("Insert div to div.#child", blue); + +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/enabled-disabled.html b/testing/web-platform/tests/css/selectors/invalidation/enabled-disabled.html new file mode 100644 index 0000000000..bac3067d00 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/enabled-disabled.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>CSS Selectors Invalidation: :enabled and :disabled</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#enableddisabled"> + <link rel="help" href="https://html.spec.whatwg.org/#enabling-and-disabling-form-controls:-the-disabled-attribute"> + <meta name="assert" content="This tests that the :enabled and :disabled selectors are effective, and that the enabled/disabled status of an element is changed when updating its 'disabled' attribute by script."> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + input { + position: absolute; + left: 200px; + top: 300px; + } + + :enabled { + left: 100px; + } + + :disabled { + top: 400px; + } + </style> + </head> + <body> + <input id="first" type="button" value="First"></input> + <input id="second" type="button" value="Second" disabled></input> + + <script> + test(() => { + assert_equals(getComputedStyle(first).left, '100px'); + assert_equals(getComputedStyle(first).top, '300px'); + + first.disabled = true; + assert_equals(getComputedStyle(first).left, '200px'); + assert_equals(getComputedStyle(first).top, '400px'); + }, "Element updates when disabled"); + + test(() => { + assert_equals(getComputedStyle(second).left, '200px'); + assert_equals(getComputedStyle(second).top, '400px'); + + second.disabled = false; + assert_equals(getComputedStyle(second).left, '100px'); + assert_equals(getComputedStyle(second).top, '300px'); + }, "Element updates when enabled"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/first-child-last-child.html b/testing/web-platform/tests/css/selectors/invalidation/first-child-last-child.html new file mode 100644 index 0000000000..4a2ed4570e --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/first-child-last-child.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>CSS Selectors Invalidation: :first-child :last-child</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#the-first-child-pseudo"> + <meta name="assert" content="This tests that the :first-child and :last-child selectors are effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + div { + color: black; + } + span:first-child { + color: blue; + } + span:last-child { + color: red; + } + </style> + </head> + <body> + <div id="target"><span>first-initially</span><span>last-initially</span></div> + <script> + +'use strict'; +const black = 'rgb(0, 0, 0)'; +const blue = 'rgb(0, 0, 255)'; +const red = 'rgb(255, 0, 0)'; + +test(() => { + const target = document.querySelector('#target'); + const first = target.firstChild; + assert_equals(getComputedStyle(first).color, blue); + target.insertAdjacentHTML('afterbegin', '\n<span>foo</span><span>bar</span>'); + assert_equals(getComputedStyle(target.firstElementChild).color, blue); + assert_equals(getComputedStyle(first).color, black); + while (target.firstElementChild !== first) + target.removeChild(target.firstElementChild); + assert_equals(getComputedStyle(first).color, blue); +}, 'Adding multiple nodes at once should invalidate :first-child correctly.'); + +test(() => { + const target = document.querySelector('#target'); + const last = target.lastChild; + assert_equals(getComputedStyle(last).color, red); + target.insertAdjacentHTML('beforeend', '\n<span>foo</span><span>bar</span>'); + assert_equals(getComputedStyle(target.lastChild).color, red); + assert_equals(getComputedStyle(last).color, black); + while (target.lastChild !== last) + target.removeChild(target.lastChild); + assert_equals(getComputedStyle(last).color, red); +}, 'Adding multiple nodes at once should invalidate :last-child correctly.'); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html new file mode 100644 index 0000000000..dbbcea2502 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/fullscreen-pseudo-class-in-has.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :fullscreen pseudo class in :has()</title> +<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<style> + #subject:has(#target:fullscreen) { color: green; } +</style> + +<div id="subject"> + This is some text. + <div id="target">This is going to be fullscreened</div> +</div> + +<script> +let waitForFullscreenChange = () => { + return new Promise((resolve) => { + document.addEventListener("fullscreenchange", resolve, { once: true }); + }); +}; + +promise_test(async function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + test_driver.bless("fullscreen", () => target.requestFullscreen()); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green since target is fullscreen"); + document.exitFullscreen(); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since target is no longer fullscreen"); +}, ":fullscreen pseudo-class invalidation with requestFullscreen + exitFullscreen"); + +promise_test(async function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + test_driver.bless("fullscreen", () => target.requestFullscreen()); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green since target is fullscreen"); + target.remove(); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since target is removed"); +}, ":fullscreen pseudo-class invalidation with requestFullscreen + remove"); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-complexity.html b/testing/web-platform/tests/css/selectors/invalidation/has-complexity.html new file mode 100644 index 0000000000..0bdcdec13b --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-complexity.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() invalidation should not be O(n^2)</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +main:has(span) .subject { color: red } +main:has(span + span) .subject { color: green } +main:has(span + final) .subject { color: blue } +main:has(nonexistent + span) .subject { color: black } +main:has(span) span { color: black } +main:has(nonexistent) span { color: black } +main:has(div div span) .subject { color: purple } +</style> +<main> + <div id=container> + <span></span> + </div> + <div id=subject class=subject></div> +</main> +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const green = 'rgb(0, 128, 0)'; +const blue = 'rgb(0, 0, 255)'; +const purple = 'rgb(128, 0, 128)'; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +const count = 25000; + +testColor(`Before appending ${count} elements`, red); + +for (let i = 0; i < count; ++i) { + const span = document.createElement("span"); + container.appendChild(span); +} + +testColor(`After appending ${count} elements. This should not time out.`, green); + +for (let i = 0; i < count - 1; ++i) { + const span = document.createElement("span"); + container.appendChild(span); +} + +const final = document.createElement("final"); +container.appendChild(final); + +testColor(`After appending another ${count} elements. This should not time out.`, blue); + +const div = document.createElement("div"); +for (let i = 0; i < count; ++i) { + const span = document.createElement("span"); + div.appendChild(span); +} +container.appendChild(div); + +testColor(`After appending div with ${count} elements. This should not time out.`, purple); + +div.remove(); + +testColor(`After removing div with ${count} elements. This should not time out.`, blue); + +for (let i = 0; i < count; ++i) + container.lastChild.remove(); + +testColor(`After removing ${count} elements one-by-one. This should not time out.`, green); + +container.replaceChildren(); + +testColor(`After removing the remaining elements. This should not time out.`, grey); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-in-adjacent-position.html b/testing/web-platform/tests/css/selectors/invalidation/has-in-adjacent-position.html new file mode 100644 index 0000000000..5c1a1ecf78 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-in-adjacent-position.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() in adjacent position</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +div:has(.test) + #subject { color: red } +div:has([test_attr]) + #subject { color: orangered } +div:has(> .test) + #subject { color: green } +div:has(> [test_attr]) + #subject { color: lightgreen } +div:has(~ .test) + #subject { color: yellow } +div:has(~ [test_attr]) + #subject { color: ivory } +div:has(+ .test) + #subject { color: blue } +div:has(+ [test_attr]) + #subject { color: skyblue } +div:has(~ div .test) + #subject { color: purple } +div:has(~ div [test_attr]) + #subject { color: violet } +div:has(+ div .test) + #subject { color: pink } +div:has(+ div [test_attr]) + #subject { color: lightpink } +</style> + +<main id=main> + <div id=previous_sibling> + <div id=previous_sibling_child> + <div id=previous_sibling_descendant></div> + </div> + </div> + <div id=subject></div> + <div id=next_sibling> + <div id=next_sibling_child> + <div id=next_sibling_descendant></div> + </div> + </div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const orangered = 'rgb(255, 69, 0)'; +const green = 'rgb(0, 128, 0)'; +const lightgreen = 'rgb(144, 238, 144)'; +const blue = 'rgb(0, 0, 255)'; +const skyblue = 'rgb(135, 206, 235)'; +const yellow = 'rgb(255, 255, 0)'; +const ivory = 'rgb(255, 255, 240)'; +const purple = 'rgb(128, 0, 128)'; +const violet = 'rgb(238, 130, 238)'; +const pink = 'rgb(255, 192, 203)'; +const lightpink = 'rgb(255, 182, 193)'; +const colors = { + grey: { + classTest: grey, + attributeTest: grey, + }, + red: { + classTest: red, + attributeTest: orangered, + }, + green: { + classTest: green, + attributeTest: lightgreen, + }, + blue: { + classTest: blue, + attributeTest: skyblue, + }, + yellow: { + classTest: yellow, + attributeTest: ivory, + }, + purple: { + classTest: purple, + attributeTest: violet, + }, + pink: { + classTest: pink, + attributeTest: lightpink, + }, +}; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + element.classList.add('test'); + testColor(`add .test to ${element.id}`, expectedColorForClassTest); + element.classList.remove('test'); + testColor(`remove .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + beforeElement.before(newElement); + testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test before ${beforeElement.id}`, grey); + + newElement.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert element div before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div before ${beforeElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + afterElement.after(newElement); + testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test after ${afterElement.id}`, grey); + + newElement.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert element div after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div after ${afterElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test before ${beforeElement.id}`, grey); + + newChild.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert tree div>div before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div before ${beforeElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test after ${afterElement.id}`, grey); + + newChild.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert tree div>div after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div after ${afterElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey); +} + +testColor('Initial color', grey); + +testClassChange(previous_sibling, "grey"); +testClassChange(previous_sibling_child, "green"); +testClassChange(previous_sibling_descendant, "red"); +testClassChange(subject, "blue"); +testClassChange(next_sibling, "yellow"); +testClassChange(next_sibling_child, "purple"); +testClassChange(next_sibling_descendant, "purple"); + +testElementInsertionBefore(previous_sibling, "grey"); +testElementInsertionBefore(previous_sibling_child, "green"); +testElementInsertionBefore(previous_sibling_descendant, "red"); +testElementInsertionBefore(subject, "grey"); +testElementInsertionBefore(next_sibling, "yellow"); +testElementInsertionBefore(next_sibling_child, "purple"); +testElementInsertionBefore(next_sibling_descendant, "purple"); + +testElementInsertionAfter(previous_sibling, "grey"); +testElementInsertionAfter(previous_sibling_child, "green"); +testElementInsertionAfter(previous_sibling_descendant, "red"); +testElementInsertionAfter(subject, "yellow"); +testElementInsertionAfter(next_sibling, "yellow"); +testElementInsertionAfter(next_sibling_child, "purple"); +testElementInsertionAfter(next_sibling_descendant, "purple"); + +testTreeInsertionBefore(previous_sibling, "grey"); +testTreeInsertionBefore(previous_sibling_child, "red"); +testTreeInsertionBefore(previous_sibling_descendant, "red"); +testTreeInsertionBefore(subject, "green"); +testTreeInsertionBefore(next_sibling, "purple"); +testTreeInsertionBefore(next_sibling_child, "purple"); +testTreeInsertionBefore(next_sibling_descendant, "purple"); + +testTreeInsertionAfter(previous_sibling, "green"); +testTreeInsertionAfter(previous_sibling_child, "red"); +testTreeInsertionAfter(previous_sibling_descendant, "red"); +testTreeInsertionAfter(subject, "purple"); +testTreeInsertionAfter(next_sibling, "purple"); +testTreeInsertionAfter(next_sibling_child, "purple"); +testTreeInsertionAfter(next_sibling_descendant, "purple"); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-in-ancestor-position.html b/testing/web-platform/tests/css/selectors/invalidation/has-in-ancestor-position.html new file mode 100644 index 0000000000..7662fbde69 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-in-ancestor-position.html @@ -0,0 +1,320 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() in ancestor position</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +div:has(.test) #subject { color: red } +div:has([test_attr]) #subject { color: orangered } +div:has(> .test) #subject { color: green } +div:has(> [test_attr]) #subject { color: lightgreen } +div:has(~ .test) #subject { color: yellow } +div:has(~ [test_attr]) #subject { color: ivory } +div:has(+ .test) #subject { color: blue } +div:has(+ [test_attr]) #subject { color: skyblue } +div:has(~ div .test) #subject { color: purple } +div:has(~ div [test_attr]) #subject { color: violet } +div:has(+ div .test) #subject { color: pink } +div:has(+ div [test_attr]) #subject { color: lightpink } +</style> + +<main id=main> + <div id=subject_ancestor> + <div id=subject_parent> + <div id=subject> + <div id=subject_child> + <div id=subject_descendant></div> + </div> + </div> + </div> + </div> + <div id=next_sibling> + <div id=next_sibling_child> + <div id=next_sibling_descendant></div> + </div> + </div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const orangered = 'rgb(255, 69, 0)'; +const green = 'rgb(0, 128, 0)'; +const lightgreen = 'rgb(144, 238, 144)'; +const blue = 'rgb(0, 0, 255)'; +const skyblue = 'rgb(135, 206, 235)'; +const yellow = 'rgb(255, 255, 0)'; +const ivory = 'rgb(255, 255, 240)'; +const purple = 'rgb(128, 0, 128)'; +const violet = 'rgb(238, 130, 238)'; +const pink = 'rgb(255, 192, 203)'; +const lightpink = 'rgb(255, 182, 193)'; +const colors = { + grey: { + classTest: grey, + attributeTest: grey, + }, + red: { + classTest: red, + attributeTest: orangered, + }, + green: { + classTest: green, + attributeTest: lightgreen, + }, + blue: { + classTest: blue, + attributeTest: skyblue, + }, + yellow: { + classTest: yellow, + attributeTest: ivory, + }, + purple: { + classTest: purple, + attributeTest: violet, + }, + pink: { + classTest: pink, + attributeTest: lightpink, + }, +}; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + element.classList.add('test'); + testColor(`add .test to ${element.id}`, expectedColorForClassTest); + element.classList.remove('test'); + testColor(`remove .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + beforeElement.before(newElement); + testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test before ${beforeElement.id}`, grey); + + newElement.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert element div before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div before ${beforeElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + afterElement.after(newElement); + testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test after ${afterElement.id}`, grey); + + newElement.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert element div after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div after ${afterElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test before ${beforeElement.id}`, grey); + + newChild.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert tree div>div before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div before ${beforeElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test after ${afterElement.id}`, grey); + + newChild.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert tree div>div after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div after ${afterElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey); +} + +testColor('Initial color', grey); + +testClassChange(subject_ancestor, "grey"); +testClassChange(subject_parent, "green"); +testClassChange(subject, "green"); +testClassChange(subject_child, "red"); +testClassChange(subject_descendant, "red"); +testClassChange(next_sibling, "blue"); +testClassChange(next_sibling_child, "pink"); +testClassChange(next_sibling_descendant, "pink"); + +testElementInsertionBefore(subject_ancestor, "grey"); +testElementInsertionBefore(subject_parent, "green"); +testElementInsertionBefore(subject, "green"); +testElementInsertionBefore(subject_child, "red"); +testElementInsertionBefore(subject_descendant, "red"); +testElementInsertionBefore(next_sibling, "blue"); +testElementInsertionBefore(next_sibling_child, "pink"); +testElementInsertionBefore(next_sibling_descendant, "pink"); + +testElementInsertionAfter(subject_ancestor, "blue"); +testElementInsertionAfter(subject_parent, "blue"); +testElementInsertionAfter(subject, "green"); +testElementInsertionAfter(subject_child, "red"); +testElementInsertionAfter(subject_descendant, "red"); +testElementInsertionAfter(next_sibling, "yellow"); +testElementInsertionAfter(next_sibling_child, "pink"); +testElementInsertionAfter(next_sibling_descendant, "pink"); + +testTreeInsertionBefore(subject_ancestor, "grey"); +testTreeInsertionBefore(subject_parent, "red"); +testTreeInsertionBefore(subject, "red"); +testTreeInsertionBefore(subject_child, "red"); +testTreeInsertionBefore(subject_descendant, "red"); +testTreeInsertionBefore(next_sibling, "pink"); +testTreeInsertionBefore(next_sibling_child, "pink"); +testTreeInsertionBefore(next_sibling_descendant, "pink"); + +testTreeInsertionAfter(subject_ancestor, "pink"); +testTreeInsertionAfter(subject_parent, "pink"); +testTreeInsertionAfter(subject, "red"); +testTreeInsertionAfter(subject_child, "red"); +testTreeInsertionAfter(subject_descendant, "red"); +testTreeInsertionAfter(next_sibling, "purple"); +testTreeInsertionAfter(next_sibling_child, "pink"); +testTreeInsertionAfter(next_sibling_descendant, "pink"); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-in-parent-position.html b/testing/web-platform/tests/css/selectors/invalidation/has-in-parent-position.html new file mode 100644 index 0000000000..4690e6929f --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-in-parent-position.html @@ -0,0 +1,300 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() in parent position</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +div:has(.test) > #subject { color: red } +div:has([test_attr]) > #subject { color: orangered } +div:has(> .test) > #subject { color: green } +div:has(> [test_attr]) > #subject { color: lightgreen } +div:has(~ .test) > #subject { color: yellow } +div:has(~ [test_attr]) > #subject { color: ivory } +div:has(+ .test) > #subject { color: blue } +div:has(+ [test_attr]) > #subject { color: skyblue } +div:has(~ div .test) > #subject { color: purple } +div:has(~ div [test_attr]) > #subject { color: violet } +div:has(+ div .test) > #subject { color: pink } +div:has(+ div [test_attr]) > #subject { color: lightpink } +</style> + +<main id=main> + <div id=subject_ancestor> + <div id=subject_parent> + <div id=subject> + <div id=subject_child> + <div id=subject_descendant></div> + </div> + </div> + </div> + </div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const orangered = 'rgb(255, 69, 0)'; +const green = 'rgb(0, 128, 0)'; +const lightgreen = 'rgb(144, 238, 144)'; +const blue = 'rgb(0, 0, 255)'; +const skyblue = 'rgb(135, 206, 235)'; +const yellow = 'rgb(255, 255, 0)'; +const ivory = 'rgb(255, 255, 240)'; +const purple = 'rgb(128, 0, 128)'; +const violet = 'rgb(238, 130, 238)'; +const pink = 'rgb(255, 192, 203)'; +const lightpink = 'rgb(255, 182, 193)'; +const colors = { + grey: { + classTest: grey, + attributeTest: grey, + }, + red: { + classTest: red, + attributeTest: orangered, + }, + green: { + classTest: green, + attributeTest: lightgreen, + }, + blue: { + classTest: blue, + attributeTest: skyblue, + }, + yellow: { + classTest: yellow, + attributeTest: ivory, + }, + purple: { + classTest: purple, + attributeTest: violet, + }, + pink: { + classTest: pink, + attributeTest: lightpink, + }, +}; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + element.classList.add('test'); + testColor(`add .test to ${element.id}`, expectedColorForClassTest); + element.classList.remove('test'); + testColor(`remove .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test'); + + beforeElement.before(newElement); + testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test before ${beforeElement.id}`, grey); + + newElement.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert element div before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div before ${beforeElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test'); + + afterElement.after(newElement); + testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test after ${afterElement.id}`, grey); + + newElement.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert element div after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div after ${afterElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test before ${beforeElement.id}`, grey); + + newChild.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert tree div>div before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div before ${beforeElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test after ${afterElement.id}`, grey); + + newChild.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert tree div>div after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div after ${afterElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey); +} + +testColor('Initial color', grey); + +testClassChange(subject_ancestor, "grey"); +testClassChange(subject_parent, "grey"); +testClassChange(subject, "green"); +testClassChange(subject_child, "red"); +testClassChange(subject_descendant, "red"); + +testElementInsertionBefore(subject_ancestor, "grey"); +testElementInsertionBefore(subject_parent, "grey"); +testElementInsertionBefore(subject, "green"); +testElementInsertionBefore(subject_child, "red"); +testElementInsertionBefore(subject_descendant, "red"); + +testElementInsertionAfter(subject_ancestor, "grey"); +testElementInsertionAfter(subject_parent, "blue"); +testElementInsertionAfter(subject, "green"); +testElementInsertionAfter(subject_child, "red"); +testElementInsertionAfter(subject_descendant, "red"); + +testTreeInsertionBefore(subject_ancestor, "grey"); +testTreeInsertionBefore(subject_parent, "grey"); +testTreeInsertionBefore(subject, "red"); +testTreeInsertionBefore(subject_child, "red"); +testTreeInsertionBefore(subject_descendant, "red"); + +testTreeInsertionAfter(subject_ancestor, "grey"); +testTreeInsertionAfter(subject_parent, "pink"); +testTreeInsertionAfter(subject, "red"); +testTreeInsertionAfter(subject_child, "red"); +testTreeInsertionAfter(subject_descendant, "red"); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-in-sibling-position.html b/testing/web-platform/tests/css/selectors/invalidation/has-in-sibling-position.html new file mode 100644 index 0000000000..8b35940f87 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-in-sibling-position.html @@ -0,0 +1,312 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() in sibling position</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +div:has(.test) ~ #subject { color: red } +div:has([test_attr]) ~ #subject { color: orangered } +div:has(> .test) ~ #subject { color: green } +div:has(> [test_attr]) ~ #subject { color: lightgreen } +div:has(~ .test) ~ #subject { color: yellow } +div:has(~ [test_attr]) ~ #subject { color: ivory } +div:has(+ .test) ~ #subject { color: blue } +div:has(+ [test_attr]) ~ #subject { color: skyblue } +div:has(~ div .test) ~ #subject { color: purple } +div:has(~ div [test_attr]) ~ #subject { color: violet } +div:has(+ div .test) ~ #subject { color: pink } +div:has(+ div [test_attr]) ~ #subject { color: lightpink } +</style> + +<main id=main> + <div id=previous_sibling> + <div id=previous_sibling_child> + <div id=previous_sibling_descendant></div> + </div> + </div> + <div id=subject></div> + <div id=next_sibling> + <div id=next_sibling_child> + <div id=next_sibling_descendant></div> + </div> + </div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const orangered = 'rgb(255, 69, 0)'; +const green = 'rgb(0, 128, 0)'; +const lightgreen = 'rgb(144, 238, 144)'; +const blue = 'rgb(0, 0, 255)'; +const skyblue = 'rgb(135, 206, 235)'; +const yellow = 'rgb(255, 255, 0)'; +const ivory = 'rgb(255, 255, 240)'; +const purple = 'rgb(128, 0, 128)'; +const violet = 'rgb(238, 130, 238)'; +const pink = 'rgb(255, 192, 203)'; +const lightpink = 'rgb(255, 182, 193)'; +const colors = { + grey: { + classTest: grey, + attributeTest: grey, + }, + red: { + classTest: red, + attributeTest: orangered, + }, + green: { + classTest: green, + attributeTest: lightgreen, + }, + blue: { + classTest: blue, + attributeTest: skyblue, + }, + yellow: { + classTest: yellow, + attributeTest: ivory, + }, + purple: { + classTest: purple, + attributeTest: violet, + }, + pink: { + classTest: pink, + attributeTest: lightpink, + }, +}; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + element.classList.add('test'); + testColor(`add .test to ${element.id}`, expectedColorForClassTest); + element.classList.remove('test'); + testColor(`remove .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + beforeElement.before(newElement); + testColor(`insert element div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test before ${beforeElement.id}`, grey); + + newElement.classList.remove('test') + + beforeElement.before(newElement); + testColor(`insert element div before ${beforeElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div before ${beforeElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + newElement.classList.add('test') + + afterElement.after(newElement); + testColor(`insert element div.test after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' again to the element inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove element div.test after ${afterElement.id}`, grey); + + newElement.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert element div after ${afterElement.id}`, grey); + + newElement.classList.add('test'); + testColor(`add the class 'test' to the element inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newElement.classList.remove('test'); + testColor(`remove the class 'test' from the element inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove element div after ${afterElement.id}`, grey); + + newElement.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div[test_attr] after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted before ${beforeElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test before ${beforeElement.id}`, grey); + + newChild.classList.remove('test'); + + beforeElement.before(newElement); + testColor(`insert tree div>div before ${beforeElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again before ${beforeElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again before ${beforeElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div before ${beforeElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + beforeElement.before(newElement); + testColor(`insert element div>div[test_attr] before ${beforeElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColorName) +{ + const expectedColorForClassTest = colors[expectedColorName].classTest; + const expectedColorForAttributeTest = colors[expectedColorName].attributeTest; + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' again to the element in the tree inserted after ${afterElement.id}`, expectedColorForClassTest); + + newElement.remove(); + testColor(`remove tree div>div.test after ${afterElement.id}`, grey); + + newChild.classList.remove('test'); + + afterElement.after(newElement); + testColor(`insert tree div>div after ${afterElement.id}`, grey); + + newChild.classList.add('test'); + testColor(`add the class 'test' to the element in the tree inserted again after ${afterElement.id}`, expectedColorForClassTest); + + newChild.classList.remove('test'); + testColor(`remove the class 'test' from the element in the tree inserted again after ${afterElement.id}`, grey); + + newElement.remove(); + testColor(`remove tree div>div after ${afterElement.id}`, grey); + + newChild.setAttribute('test_attr', ''); + + afterElement.after(newElement); + testColor(`insert element div>div[test_attr] after ${afterElement.id}`, expectedColorForAttributeTest); + + newElement.remove(); + testColor(`remove element div>div[test_attr] after ${afterElement.id}`, grey); +} + +testColor('Initial color', grey); + +testClassChange(previous_sibling, "grey"); +testClassChange(previous_sibling_child, "green"); +testClassChange(previous_sibling_descendant, "red"); +testClassChange(subject, "blue"); +testClassChange(next_sibling, "yellow"); +testClassChange(next_sibling_child, "purple"); +testClassChange(next_sibling_descendant, "purple"); + +testElementInsertionBefore(previous_sibling, "grey"); +testElementInsertionBefore(previous_sibling_child, "green"); +testElementInsertionBefore(previous_sibling_descendant, "red"); +testElementInsertionBefore(subject, "blue"); +testElementInsertionBefore(next_sibling, "yellow"); +testElementInsertionBefore(next_sibling_child, "purple"); +testElementInsertionBefore(next_sibling_descendant, "purple"); + +testElementInsertionAfter(previous_sibling, "blue"); +testElementInsertionAfter(previous_sibling_child, "green"); +testElementInsertionAfter(previous_sibling_descendant, "red"); +testElementInsertionAfter(subject, "yellow"); +testElementInsertionAfter(next_sibling, "yellow"); +testElementInsertionAfter(next_sibling_child, "purple"); +testElementInsertionAfter(next_sibling_descendant, "purple"); + +testTreeInsertionBefore(previous_sibling, "green"); +testTreeInsertionBefore(previous_sibling_child, "red"); +testTreeInsertionBefore(previous_sibling_descendant, "red"); +testTreeInsertionBefore(subject, "pink"); +testTreeInsertionBefore(next_sibling, "purple"); +testTreeInsertionBefore(next_sibling_child, "purple"); +testTreeInsertionBefore(next_sibling_descendant, "purple"); + +testTreeInsertionAfter(previous_sibling, "pink"); +testTreeInsertionAfter(previous_sibling_child, "red"); +testTreeInsertionAfter(previous_sibling_descendant, "red"); +testTreeInsertionAfter(subject, "purple"); +testTreeInsertionAfter(next_sibling, "purple"); +testTreeInsertionAfter(next_sibling_child, "purple"); +testTreeInsertionAfter(next_sibling_descendant, "purple"); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-after-removing-non-first-element.html b/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-after-removing-non-first-element.html new file mode 100644 index 0000000000..482f07b8af --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-after-removing-non-first-element.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>:has() invalidation after removing non-first element</title> +<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +#subject:has(descendant) { color: red } +</style> +<main id="main"> + <div id="subject"> + <div></div> + <descendant id="descendant"></descendant> + </div> +</main> +<script> + let grey = 'rgb(128, 128, 128)'; + let red = 'rgb(255, 0, 0)'; + + function test_div(test_name, el, color) { + test(function() { + assert_equals(getComputedStyle(el).color, color); + }, test_name + ': div#' + el.id + '.color'); + } + + test_div('initial_color', subject, red); + subject.removeChild(descendant); + test_div('remove descendant', subject, grey); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-for-wiping-an-element.html b/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-for-wiping-an-element.html new file mode 100644 index 0000000000..03fa9a5554 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-invalidation-for-wiping-an-element.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>:has() invalidation for wiping an element by means of innerHTML</title> +<link rel="author" title="Byungwoo Lee" href="mailto:blee@igalia.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +.subject:has(.descendant) { color: green} +</style> +<main id=main> + <div id="subject" class="subject"></div> +</main> +<script> + let grey = 'rgb(128, 128, 128)'; + let green = 'rgb(0, 128, 0)'; + + function test_div(test_name, el, color) { + test(function() { + assert_equals(getComputedStyle(el).color, color); + }, test_name + ': div#' + el.id + '.color'); + } + + test_div('initial color', subject, grey); + + subject.innerHTML = "This is a text <div><div class='descendant'></div></div>"; + + test_div('color after inserting text and div > .descendant', subject, green); + + subject.innerHTML = "This is a text"; + + test_div('color after wiping #child to remove div > .descendant', subject, grey); + + subject.innerHTML = "<div id='child'> This is a text <div class='descendant'></div></div>"; + + test_div('color after inserting text and #child > .descendant', subject, green); + + child.innerHTML = "This is a text"; + + test_div('color after wiping #child to remove .descendant', subject, grey); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-sibling.html b/testing/web-platform/tests/css/selectors/invalidation/has-sibling.html new file mode 100644 index 0000000000..7c56b2e7b3 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-sibling.html @@ -0,0 +1,149 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() with sibling combinator argument</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +#subject:has(~ .test) { color: red } +#subject:has(+ .test) { color: green } +#subject:has(~ div .test) { color: blue } +#subject:has(~ div > .test) { color: purple } +#subject:has(+ div .test) { color: yellow } +#subject:has(+ div > .test) { color: pink } +</style> + +<main id=main> + <div id=subject></div> + <div id=first_sibling> + <div id=first_sibling_child> + <div id=first_sibling_descendant></div> + </div> + </div> + <div id=second_sibling></div> + <div id=third_sibling> + <div id=third_sibling_child> + <div id=third_sibling_descendant></div> + </div> + </div> +</main> +<script> + +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const green = 'rgb(0, 128, 0)'; +const blue = 'rgb(0, 0, 255)'; +const yellow = 'rgb(255, 255, 0)'; +const purple = 'rgb(128, 0, 128)'; +const pink = 'rgb(255, 192, 203)'; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColor) +{ + element.classList.add('test'); + testColor(`add .test to ${element.id}`, expectedColor); + element.classList.remove('test'); + testColor(`remove .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColor) +{ + const newElement = document.createElement('div'); + newElement.classList.add('test') + + beforeElement.before(newElement); + testColor(`insert element div.test before ${beforeElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove element div.test before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColor) +{ + const newElement = document.createElement('div'); + newElement.classList.add('test') + + afterElement.after(newElement); + testColor(`insert element div.test after ${afterElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove element div.test after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColor) +{ + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div.test before ${beforeElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove tree div>div.test before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColor) +{ + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newChild.classList.add('test'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div>div.test after ${afterElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove tree div>div.test after ${afterElement.id}`, grey); +} + +testColor('initial_color', grey); + +testClassChange(first_sibling, green); +testClassChange(second_sibling, red); +testClassChange(third_sibling, red); +testClassChange(first_sibling_child, pink); +testClassChange(first_sibling_descendant, yellow); +testClassChange(third_sibling_child, purple); +testClassChange(third_sibling_descendant, blue); + +testElementInsertionBefore(first_sibling, green); +testElementInsertionBefore(second_sibling, red); +testElementInsertionBefore(third_sibling, red); +testElementInsertionBefore(first_sibling_child, pink); +testElementInsertionBefore(first_sibling_descendant, yellow); +testElementInsertionBefore(third_sibling_child, purple); +testElementInsertionBefore(third_sibling_descendant, blue); + +testElementInsertionAfter(first_sibling, red); +testElementInsertionAfter(second_sibling, red); +testElementInsertionAfter(third_sibling, red); +testElementInsertionAfter(first_sibling_child, pink); +testElementInsertionAfter(first_sibling_descendant, yellow); +testElementInsertionAfter(third_sibling_child, purple); +testElementInsertionAfter(third_sibling_descendant, blue); + +testTreeInsertionBefore(first_sibling, pink); +testTreeInsertionBefore(second_sibling, purple); +testTreeInsertionBefore(third_sibling, purple); +testTreeInsertionBefore(first_sibling_child, yellow); +testTreeInsertionBefore(first_sibling_descendant, yellow); +testTreeInsertionBefore(third_sibling_child, blue); +testTreeInsertionBefore(third_sibling_descendant, blue); + +testTreeInsertionAfter(first_sibling, purple); +testTreeInsertionAfter(second_sibling, purple); +testTreeInsertionAfter(third_sibling, purple); +testTreeInsertionAfter(first_sibling_child, yellow); +testTreeInsertionAfter(first_sibling_descendant, yellow); +testTreeInsertionAfter(third_sibling_child, blue); +testTreeInsertionAfter(third_sibling_descendant, blue); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-with-not.html b/testing/web-platform/tests/css/selectors/invalidation/has-with-not.html new file mode 100644 index 0000000000..b67ec5e3b7 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-with-not.html @@ -0,0 +1,107 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() with :not()</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +div, main { color: grey } +#subject:has(:not(.test)) { color: green } +#subject:has(.test :not(.test)) { color: red } +</style> + +<main id=main> + <div id=subject> + <div id=subject_child class=test> + <div id=subject_descendant class=test></div> + </div> + </div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const green = 'rgb(0, 128, 0)'; +const blue = 'rgb(0, 0, 255)'; + +function testColor(test_name, color) { + test(function() { + assert_equals(getComputedStyle(subject).color, color); + }, test_name); +} + +function testClassChange(element, expectedColor) +{ + element.classList.remove('test'); + testColor(`remove .test to ${element.id}`, expectedColor); + element.classList.add('test'); + testColor(`add .test from ${element.id}`, grey); +} + +function testElementInsertionBefore(beforeElement, expectedColor) +{ + const newElement = document.createElement('div'); + + beforeElement.before(newElement); + testColor(`insert element div before ${beforeElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove element div before ${beforeElement.id}`, grey); +} + +function testElementInsertionAfter(afterElement, expectedColor) +{ + const newElement = document.createElement('div'); + + afterElement.after(newElement); + testColor(`insert element div after ${afterElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove element div after ${afterElement.id}`, grey); +} + +function testTreeInsertionBefore(beforeElement, expectedColor) +{ + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newElement.appendChild(newChild); + + beforeElement.before(newElement); + testColor(`insert tree div>div before ${beforeElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove tree div>div before ${beforeElement.id}`, grey); +} + +function testTreeInsertionAfter(afterElement, expectedColor) +{ + const newElement = document.createElement('div'); + const newChild = document.createElement('div'); + newElement.appendChild(newChild); + + afterElement.after(newElement); + testColor(`insert tree div.test after ${afterElement.id}`, expectedColor); + + newElement.remove(); + testColor(`remove tree div.test after ${afterElement.id}`, grey); +} + +testColor('Initial color', grey); + +testClassChange(subject_child, green); +testClassChange(subject_descendant, red); + +testElementInsertionBefore(subject_child, green); +testElementInsertionBefore(subject_descendant, red); + +testElementInsertionAfter(subject_child, green); +testElementInsertionAfter(subject_descendant, red); + +testTreeInsertionBefore(subject_child, green); +testTreeInsertionBefore(subject_descendant, red); + +testTreeInsertionAfter(subject_child, green); +testTreeInsertionAfter(subject_descendant, red); + +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/has-with-pseudo-class.html b/testing/web-platform/tests/css/selectors/invalidation/has-with-pseudo-class.html new file mode 100644 index 0000000000..4dc4c1a6a2 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/has-with-pseudo-class.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Selector Invalidation: :has() with pseudo-classes</title> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<style> +main:has(input) div { color: grey } +main:has(#checkbox:checked) > #subject { color: red } +main:has(#option:checked) > #subject { color: red } +main:has(#checkbox:disabled) > #subject { color: green } +main:has(#option:disabled) > :is(#subject, #subject2) { color: green } +main:has(#optgroup:disabled) > #subject { color: blue } +main:not(:has(#checkbox:enabled)) > #subject3 { color: green } +main:not(:has(#option:enabled)) :is(#subject3, #subject4) { color: green } +main:not(:has(#optgroup:enabled)) > #subject3 { color: blue } +main:has(#text_input:valid) > #subject { color: yellow } +main:not(:has(#text_input:invalid)) > #subject2 { color: yellow } +main:has(#form:valid) > #subject3 { color: yellow } +main:not(:has(#form:invalid)) > #subject4 { color: yellow } +</style> + +<main id=main> + <form id=form> + <input type=checkbox id=checkbox></input> + <select id=select><optgroup id=optgroup><option>a</option><option id=option>b</option></optgroup></select> + <input id=text_input type=text required></input> + </form> + <div id=subject></div> + <div id=subject2></div> + <div id=subject3></div> + <div id=subject4></div> +</main> + +<script> +const grey = 'rgb(128, 128, 128)'; +const red = 'rgb(255, 0, 0)'; +const green = 'rgb(0, 128, 0)'; +const blue = 'rgb(0, 0, 255)'; +const yellow = 'rgb(255, 255, 0)'; +const purple = 'rgb(128, 0, 128)'; +const pink = 'rgb(255, 192, 203)'; + +function testColor(test_name, subject_element, color) { + test(function() { + assert_equals(getComputedStyle(subject_element).color, color); + }, test_name); +} + +function testPseudoClassChange(element, property, subject_element, expectedColor) +{ + testColor(`Before set ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, grey); + + element[property] = true; + testColor(`Set ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, expectedColor); + + element[property] = false; + testColor(`Unset ${property} on ${element.id}, testing ${subject_element.id}`, subject_element, grey); +} + +function testSelectedChange(option, subject_element, expectedColor) +{ + const oldOption = select.selectedOptions[0]; + option.selected = true; + testColor(`Set select on ${option.id}`, subject_element, expectedColor); + oldOption.selected = true; + testColor(`Reset select`, subject, grey); +} + +function testValueChange(input, subject_element, expectedColor) +{ + testColor(`Before setting value of ${input.id}, testing ${subject_element.id}`, subject_element, grey); + + input.value = "value"; + testColor(`Set value of ${input.id}, testing ${subject_element.id}`, subject_element, expectedColor); + + input.value = ""; + testColor(`Clear value of ${input.id}, testing ${subject_element.id}`, subject_element, grey); +} + +testPseudoClassChange(checkbox, "checked", subject, red); +testSelectedChange(option, subject, red); + +testPseudoClassChange(checkbox, "disabled", subject, green); +testPseudoClassChange(checkbox, "disabled", subject3, green); +testPseudoClassChange(option, "disabled", subject, green); +testPseudoClassChange(option, "disabled", subject3, green); + +testPseudoClassChange(optgroup, "disabled", subject, blue); +testPseudoClassChange(optgroup, "disabled", subject2, green); +testPseudoClassChange(optgroup, "disabled", subject3, blue); +testPseudoClassChange(optgroup, "disabled", subject4, green); + +testValueChange(text_input, subject, yellow); +testValueChange(text_input, subject2, yellow); +testValueChange(text_input, subject3, yellow); +testValueChange(text_input, subject4, yellow); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/host-pseudo-class-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/host-pseudo-class-in-has.html new file mode 100644 index 0000000000..a2c63d5463 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/host-pseudo-class-in-has.html @@ -0,0 +1,65 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: Invalidation for :host() and :host-context() inside :has()</title> +<link rel="author" title="Byungwoo" href="mailto:blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="host_parent"><div id="host"></div></div> +<script> + var shadow = host.attachShadow({ mode: 'open' }); + shadow.innerHTML = ` + <style> + .subject { + color: red; + } + .subject:has(:is(:host-context(.a) > .foo .bar)) { color: green } + .subject:has(:is(:host(.a) > .foo .bar)) { color: yellowgreen } + .subject:has(:is(:host-context(.a) .bar)) { color: blue } + .subject:has(:is(:host(.a) .bar)) { color: skyblue } + </style> + <div class="foo"> + <div id="subject1" class="subject"> + <div class="bar"></div> + </div> + </div> + <div> + <div class="foo"> + <div id="subject2" class="subject"> + <div class="bar"></div> + </div> + </div> + </div> + `; + + const red = "rgb(255, 0, 0)"; + const green = "rgb(0, 128, 0)"; + const yellowgreen = "rgb(154, 205, 50)"; + const blue = "rgb(0, 0, 255)"; + const skyblue = "rgb(135, 206, 235)"; + + function checkColor(test_name, subject_id, subject_color) { + test(function() { + let subject = shadow.querySelector("#" + subject_id); + assert_equals(getComputedStyle(subject).color, subject_color); + }, test_name + ": Check #" + subject_id + " color"); + } + + checkColor("Before adding 'a' to #host_parent", "subject1", red); + checkColor("Before adding 'a' to #host_parent", "subject2", red); + + host_parent.classList.add('a'); + + checkColor("After adding 'a' to #host_parent", "subject1", green); + checkColor("After adding 'a' to #host_parent", "subject2", blue); + + host_parent.classList.remove('a'); + + checkColor("After removing 'a' from #host_parent", "subject1", red); + checkColor("After removing 'a' from #host_parent", "subject2", red); + + host.classList.add('a'); + + checkColor("After adding 'a' to #host", "subject1", yellowgreen); + checkColor("After adding 'a' to #host", "subject2", skyblue); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/input-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/input-pseudo-classes-in-has.html new file mode 100644 index 0000000000..67aeedf315 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/input-pseudo-classes-in-has.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: input pseudo classes in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + .ancestor:has(#checkme:checked) { color: green } + .ancestor:has(#checkme:indeterminate) { color: yellowgreen } + .ancestor:has(#checkme:disabled) { color: blue } + .ancestor:has(#textinput:read-only) { color: skyblue } + .ancestor:has(#textinput:placeholder-shown) { color: navy } + .ancestor:has(#radioinput:default) { color: lightblue } + .ancestor:has(#textinput:valid) { color: lightgreen } + .ancestor:has(#numberinput:out-of-range) { color: darkgreen } + .ancestor:has(#numberinput:required) { color: pink } + .ancestor:has(#progress:indeterminate) { color: orange } +</style> +<div id=subject class=ancestor> + <input type="checkbox" name="my-checkbox" id="checkme"> + <label for="checkme">Check me!</label> + <input type="text" id="textinput" required> + <input id="radioinput" checked> + <input id="numberinput" type="number" min="1" max="10" value="5"> + <progress id="progress" value="50" max="100"></progress> +</div> +<script> + test(function() { + this.add_cleanup(() => { + checkme.checked = false; + }); + checkme.checked = false; + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + checkme.checked = true; + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green"); + checkme.indeterminate = true; + assert_equals(getComputedStyle(subject).color, "rgb(154, 205, 50)", + "ancestor should be yellowgreen"); + const input = checkme; + checkme.remove(); + input.indeterminate = false; + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + + subject.prepend(input); + checkme.checked = true; + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green"); + }, ":checked & :indeterminate invalidation on <input>"); + + test(function() { + this.add_cleanup(() => { + progress.setAttribute("value", "50"); + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + progress.removeAttribute("value"); + assert_equals(getComputedStyle(subject).color, "rgb(255, 165, 0)", + "ancestor should be orange"); + }, ":indeterminate invalidation on <progress>"); + + test(function() { + this.add_cleanup(() => { + checkme.disabled = false; + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + checkme.disabled = true; + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 255)", + "ancestor should be blue"); + }, ":disabled invalidation"); + + test(function() { + this.add_cleanup(() => { + textinput.readOnly = false; + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + textinput.readOnly = true; + assert_equals(getComputedStyle(subject).color, "rgb(135, 206, 235)", + "ancestor should be skyblue"); + }, ":read-only invalidation"); + + test(function() { + this.add_cleanup(() => { + textinput.value = ""; + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + textinput.value = "text input"; + assert_equals(getComputedStyle(subject).color, "rgb(144, 238, 144)", + "ancestor should be lightgreen"); + }, ":valid invalidation"); + + test(function() { + this.add_cleanup(() => { + radioinput.removeAttribute("type"); + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + radioinput.type = 'radio'; + assert_equals(getComputedStyle(subject).color, "rgb(173, 216, 230)", + "ancestor should be lightblue"); + }, ":default invalidation with input[type=radio]"); + + test(function() { + this.add_cleanup(() => { + numberinput.required = false; + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + numberinput.required = true; + assert_equals(getComputedStyle(subject).color, "rgb(255, 192, 203)", + "ancestor should be pink"); + }, ":required invalidation"); + + test(function() { + this.add_cleanup(() => { + numberinput.value = 5; + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + numberinput.value = 12; + assert_equals(getComputedStyle(subject).color, "rgb(0, 100, 0)", + "ancestor should be darkgreen"); + }, ":out-of-range invalidation"); + + test(function() { + this.add_cleanup(() => { + textinput.removeAttribute("placeholder"); + }); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + textinput.placeholder = 'placeholder text'; + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 128)", + "ancestor should be navy"); + }, ":placeholder-shown invalidation"); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-001.html b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-001.html new file mode 100644 index 0000000000..fa966d3f25 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-001.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> + <title>CSS Selectors Invalidation: insert sibling</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#adjacent-sibling-combinators"> + <meta name="assert" content="This tests that the + next-sibling selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .c { background-color: blue; } + .a + * + .c { background-color: green; } + </style> +</head> +<body> + <div> + <div id="first" class="a"></div> + <div></div> + <div id="target" class="c"></div> + </div> + <script> + 'use strict'; + const green = 'rgb(0, 128, 0)'; + const blue = 'rgb(0, 0, 255)'; + + test(function() { + const first = document.getElementById('first'); + const target = document.getElementById('target'); + const parent = first.parentElement; + assert_equals(getComputedStyle(target).backgroundColor, green, "initial color"); + + parent.removeChild(first); + assert_equals(getComputedStyle(target).backgroundColor, blue, "color after removal"); + + parent.insertBefore(first, parent.firstChild); + assert_equals(getComputedStyle(target).backgroundColor, green, "color after insert") + }, "Remove/Insert earlier sibling"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-002.html b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-002.html new file mode 100644 index 0000000000..7e1eac37ea --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-002.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> + <title>CSS Selectors Invalidation: insert adjacent sibling of parent</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#adjacent-sibling-combinators"> + <meta name="assert" content="This tests that the + next-sibling selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .d { background-color: blue; } + + .a + .c > .d { background-color: green; } + </style> +</head> +<body> + <div> + <div id="first" class="a"></div> + <div class="c"> + <div id="target" class="d"></div> + </div> + </div> + <script> + 'use strict'; + const green = 'rgb(0, 128, 0)'; + const blue = 'rgb(0, 0, 255)'; + + test(function() { + const first = document.getElementById('first'); + const target = document.getElementById('target'); + const parent = first.parentElement; + assert_equals(getComputedStyle(target).backgroundColor, green, "initial color"); + + parent.removeChild(first); + assert_equals(getComputedStyle(target).backgroundColor, blue, "color after removal"); + + parent.insertBefore(first, parent.firstChild); + assert_equals(getComputedStyle(target).backgroundColor, green, "color after insert") + }, "Remove/Insert adjacent sibling of parent"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-003.html b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-003.html new file mode 100644 index 0000000000..c7c51eaf6a --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-003.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> + <title>CSS Selectors Invalidation: insert sibling of ancestor</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#adjacent-sibling-combinators"> + <meta name="assert" content="This tests that the + next-sibling selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .c * { background-color: blue; } + + .a + * + .c * { background-color: green; } + </style> +</head> +<body> + <div> + <div id="first" class="a"></div> + <div></div> + <div class="c"> + <div> + <div id="target"></div> + </div> + </div> + </div> + <script> + 'use strict'; + const green = 'rgb(0, 128, 0)'; + const blue = 'rgb(0, 0, 255)'; + + test(function() { + const first = document.getElementById('first'); + const target = document.getElementById('target'); + const parent = first.parentElement; + assert_equals(getComputedStyle(target).backgroundColor, green, "initial color"); + + parent.removeChild(first); + assert_equals(getComputedStyle(target).backgroundColor, blue, "color after removal"); + + parent.insertBefore(first, parent.firstChild); + assert_equals(getComputedStyle(target).backgroundColor, green, "color after insert") + }, "Remove/Insert earlier sibling of ancestor"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-004.html b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-004.html new file mode 100644 index 0000000000..aa3fb1c9fc --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/insert-sibling-004.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<head> + <title>CSS Selectors Invalidation: insert sibling of parent</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#general-sibling-combinators"> + <meta name="assert" content="This tests that the ~ subsequent-sibling selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + span { background-color: blue; } + + .a ~ .c > span { background-color: green; } + </style> +</head> +<body> + <div> + <div id="first" class="a"></div> + <div></div> + <div></div> + <div class="c"> + <span id="target"></span> + </div> + </div> + <script> + 'use strict'; + const green = 'rgb(0, 128, 0)'; + const blue = 'rgb(0, 0, 255)'; + + test(function() { + const first = document.getElementById('first'); + const target = document.getElementById('target'); + const parent = first.parentElement; + assert_equals(getComputedStyle(target).backgroundColor, green, "initial color"); + + parent.removeChild(first); + assert_equals(getComputedStyle(target).backgroundColor, blue, "color after removal"); + + parent.insertBefore(first, parent.firstChild); + assert_equals(getComputedStyle(target).backgroundColor, green, "color after insert") + }, "Remove/Insert earlier sibling of parent"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/is-pseudo-containing-complex-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/is-pseudo-containing-complex-in-has.html new file mode 100644 index 0000000000..4e6d4c8832 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/is-pseudo-containing-complex-in-has.html @@ -0,0 +1,379 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :is() in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +div { color: grey } +.red:has(#descendant:is(.a_has_scope .b)) { color: red } +.orangered:has(#descendant:is(.a_descendant .b)) #descendant { color: orangered } +.darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next { color: darkred } +.pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child { color: pink } +.green:has(#descendant:is(.p + .c_has_scope ~ .d .e)) { color: green } +.lightgreen:has(#descendant:is(.p + .c_descendant ~ .d .e)) #descendant { color: lightgreen } +.darkgreen:has(#descendant:is(.p + .c_indirect_next ~ .d .e)) ~ #indirect_next { color: darkgreen } +.yellowgreen:has(#descendant:is(.p + .c_indirect_next_child ~ .d .e)) ~ #indirect_next #indirect_next_child { color: yellowgreen } +.blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g)) { color: blue } +.skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant { color: skyblue } +.lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next { color: lightblue } +.darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child { color: darkblue } +.yellow:has(~ #indirect_next:is(.h_has_scope .i)) { color: yellow } +.ivory:has(~ #indirect_next:is(.h_descendant .i)) #descendant { color: ivory } +.greenyellow:has(~ #indirect_next:is(.h_indirect_next .i)) ~ #indirect_next { color: greenyellow } +.khaki:has(~ #indirect_next:is(.h_indirect_next_child .i)) ~ #indirect_next #indirect_next_child { color: khaki } +.purple:has(~ #indirect_next:is(.p + .j_has_scope ~ .k .l)) { color: purple } +.violet:has(~ #indirect_next:is(.p + .j_descendant ~ .k .l)) #descendant { color: violet } +.orchid:has(~ #indirect_next:is(.p + .j_indirect_next ~ .k .l)) ~ #indirect_next { color: orchid } +.plum:has(~ #indirect_next:is(.p + .j_indirect_next_child ~ .k .l)) ~ #indirect_next #indirect_next_child { color: plum } +.orange:has(#descendant:is(:is(.m, .n) .o)) { color: orange } +</style> +<div> + <div class="p"></div> + <div id="parent_previous"></div> + <div id="parent" class="d k"> + <div class="p"></div> + <div id="previous"></div> + <div class="p"></div> + <div id="has_scope" class="d"> + <div class="p"></div> + <div id="child_previous"></div> + <div id="child" class="d"> + <div id="descendant" class="b e o"></div> + </div> + </div> + <div class="p"></div> + <div id="direct_next"></div> + <div id="indirect_next" class="g i l"> + <div id="indirect_next_child"></div> + </div> + </div> +</div> +<script> +const grey = "rgb(128, 128, 128)"; +const red = "rgb(255, 0, 0)"; +const orangered = "rgb(255, 69, 0)"; +const darkred = "rgb(139, 0, 0)"; +const pink = "rgb(255, 192, 203)"; +const green = "rgb(0, 128, 0)"; +const lightgreen = "rgb(144, 238, 144)"; +const darkgreen = "rgb(0, 100, 0)"; +const yellowgreen = "rgb(154, 205, 50)"; +const blue = "rgb(0, 0, 255)"; +const skyblue = "rgb(135, 206, 235)"; +const lightblue = "rgb(173, 216, 230)"; +const darkblue = "rgb(0, 0, 139)"; +const yellow = "rgb(255, 255, 0)"; +const ivory = "rgb(255, 255, 240)"; +const greenyellow = "rgb(173, 255, 47)"; +const khaki = "rgb(240, 230, 140)"; +const purple = "rgb(128, 0, 128)"; +const violet = "rgb(238, 130, 238)"; +const orchid = "rgb(218, 112, 214)"; +const plum = "rgb(221, 160, 221)"; +const orange = "rgb(255, 165, 0)"; + +function addClass(element, class_name) { + element.classList.add(class_name); +} + +function removeClass(element, class_name) { + element.classList.remove(class_name); +} + +function testClassChange(operation, class_name, element_id, + selector, matches_result, + subject_id, subject_color) { + let element = document.getElementById(element_id); + assert_equals(element ? element.id : "", element_id); + let subject = document.getElementById(subject_id); + assert_equals(subject ? subject.id : "", subject_id); + let message_prefix = [ + "[", selector, "]", + ["#", element.id, ".classList.", + (operation == addClass ? "add" : "remove"), + "('", class_name, "')"].join(""), + ": "].join(" "); + operation(element, class_name); + test(function() { + assert_equals(subject.matches(selector), matches_result); + }, message_prefix + "check matches (" + matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, subject_color); + }, message_prefix + "check #" + subject_id + " color"); +} + +function testSiblingInsertionRemoval(class_name, insert_before_id, selector, + subject_id, + insertion_matches_result, + insertion_subject_color, + removal_matches_result, + removal_subject_color) { + let insert_before = document.getElementById(insert_before_id); + assert_equals(insert_before ? insert_before.id : "", insert_before_id); + let parent = insert_before.parentElement; + let subject = document.getElementById(subject_id); + assert_equals(subject ? subject.id : "", subject_id); + let message_prefix = [ + "[", selector, "]", + ["insert/remove .", + class_name, " before #", insert_before.id, ")"].join(""), + ": "].join(" "); + + let div = document.createElement("div"); + div.classList.add(class_name); + + parent.insertBefore(div, insert_before); + test(function() { + assert_equals(subject.matches(selector), insertion_matches_result); + }, message_prefix + "(insertion) check matches (" + + insertion_matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, insertion_subject_color); + }, message_prefix + "(insertion) check #" + subject_id + " color"); + + div.remove(); + test(function() { + assert_equals(subject.matches(selector), removal_matches_result); + }, message_prefix + "(removal) check matches (" + + removal_matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, removal_subject_color); + }, message_prefix + "(removal) check #" + subject_id + " color"); +} + +assert_equals(getComputedStyle(has_scope).color, grey); + +let selector = ".red:has(#descendant:is(.a_has_scope .b))"; +testClassChange(addClass, "red", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "a_has_scope", "parent", selector, true, "has_scope", red); +testClassChange(removeClass, "a_has_scope", "parent", selector, false, "has_scope", grey); +testClassChange(addClass, "a_has_scope", "has_scope", selector, true, "has_scope", red); +testClassChange(removeClass, "a_has_scope", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "a_has_scope", "child", selector, true, "has_scope", red); +testClassChange(removeClass, "a_has_scope", "child", selector, false, "has_scope", grey); +testClassChange(removeClass, "red", "has_scope", selector, false, "has_scope", grey); + +selector = ".orangered:has(#descendant:is(.a_descendant .b)) #descendant"; +testClassChange(addClass, "orangered", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "a_descendant", "parent", selector, true, "descendant", orangered); +testClassChange(removeClass, "a_descendant", "parent", selector, false, "descendant", grey); +testClassChange(addClass, "a_descendant", "has_scope", selector, true, "descendant", orangered); +testClassChange(removeClass, "a_descendant", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "a_descendant", "child", selector, true, "descendant", orangered); +testClassChange(removeClass, "a_descendant", "child", selector, false, "descendant", grey); +testClassChange(removeClass, "orangered", "has_scope", selector, false, "descendant", grey); + +selector = ".darkred:has(#descendant:is(.a_indirect_next .b)) ~ #indirect_next"; +testClassChange(addClass, "darkred", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "a_indirect_next", "parent", selector, true, "indirect_next", darkred); +testClassChange(removeClass, "a_indirect_next", "parent", selector, false, "indirect_next", grey); +testClassChange(addClass, "a_indirect_next", "has_scope", selector, true, "indirect_next", darkred); +testClassChange(removeClass, "a_indirect_next", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "a_indirect_next", "child", selector, true, "indirect_next", darkred); +testClassChange(removeClass, "a_indirect_next", "child", selector, false, "indirect_next", grey); +testClassChange(removeClass, "darkred", "has_scope", selector, false, "indirect_next", grey); + +selector = ".pink:has(#descendant:is(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "pink", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "a_indirect_next_child", "parent", selector, true, "indirect_next_child", pink); +testClassChange(removeClass, "a_indirect_next_child", "parent", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "a_indirect_next_child", "has_scope", selector, true, "indirect_next_child", pink); +testClassChange(removeClass, "a_indirect_next_child", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "a_indirect_next_child", "child", selector, true, "indirect_next_child", pink); +testClassChange(removeClass, "a_indirect_next_child", "child", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "pink", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".green:has(#descendant:is(.p + .c_has_scope ~ .d .e))"; +testClassChange(addClass, "green", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "c_has_scope", "parent_previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "has_scope", false, grey, true, green); +testClassChange(removeClass, "c_has_scope", "parent_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("c_has_scope", "parent_previous", selector, "has_scope", true, green, false, grey); +testClassChange(addClass, "c_has_scope", "previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("invalid", "previous", selector, "has_scope", false, grey, true, green); +testClassChange(removeClass, "c_has_scope", "previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("c_has_scope", "previous", selector, "has_scope", true, green, false, grey); +testClassChange(addClass, "c_has_scope", "child_previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "has_scope", false, grey, true, green); +testClassChange(removeClass, "c_has_scope", "child_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("c_has_scope", "child_previous", selector, "has_scope", true, green, false, grey); +testClassChange(removeClass, "green", "has_scope", selector, false, "has_scope", grey); + +selector = ".lightgreen:has(#descendant:is(.p + .c_descendant ~ .d .e)) #descendant"; +testClassChange(addClass, "lightgreen", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "c_descendant", "parent_previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(removeClass, "c_descendant", "parent_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("c_descendant", "parent_previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(addClass, "c_descendant", "previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("invalid", "previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(removeClass, "c_descendant", "previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("c_descendant", "previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(addClass, "c_descendant", "child_previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(removeClass, "c_descendant", "child_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("c_descendant", "child_previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(removeClass, "lightgreen", "has_scope", selector, false, "descendant", grey); + +selector = ".darkgreen:has(#descendant:is(.p + .c_indirect_next ~ .d .e)) ~ #indirect_next"; +testClassChange(addClass, "darkgreen", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "c_indirect_next", "parent_previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(removeClass, "c_indirect_next", "parent_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("c_indirect_next", "parent_previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(addClass, "c_indirect_next", "previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(removeClass, "c_indirect_next", "previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("c_indirect_next", "previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(addClass, "c_indirect_next", "child_previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(removeClass, "c_indirect_next", "child_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("c_indirect_next", "child_previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(removeClass, "darkgreen", "has_scope", selector, false, "indirect_next", grey); + +selector = ".yellowgreen:has(#descendant:is(.p + .c_indirect_next_child ~ .d .e)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "yellowgreen", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "c_indirect_next_child", "parent_previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(removeClass, "c_indirect_next_child", "parent_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("c_indirect_next_child", "parent_previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(addClass, "c_indirect_next_child", "previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(removeClass, "c_indirect_next_child", "previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("c_indirect_next_child", "previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(addClass, "c_indirect_next_child", "child_previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(removeClass, "c_indirect_next_child", "child_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("c_indirect_next_child", "child_previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(removeClass, "yellowgreen", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".blue:has(~ #indirect_next:is(.p + .f_has_scope ~ .g))"; +testClassChange(addClass, "blue", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "f_has_scope", "previous", selector, true, "has_scope", blue); +testSiblingInsertionRemoval("invalid", "previous", selector, "has_scope", false, grey, true, blue); +testClassChange(removeClass, "f_has_scope", "previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("f_has_scope", "previous", selector, "has_scope", true, blue, false, grey); +testClassChange(addClass, "f_has_scope", "has_scope", selector, true, "has_scope", blue); +testClassChange(removeClass, "f_has_scope", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "f_has_scope", "direct_next", selector, true, "has_scope", blue); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "has_scope", false, grey, true, blue); +testClassChange(removeClass, "f_has_scope", "direct_next", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("f_has_scope", "direct_next", selector, "has_scope", true, blue, false, grey); +testClassChange(removeClass, "blue", "has_scope", selector, false, "has_scope", grey); + +selector = ".skyblue:has(~ #indirect_next:is(.p + .f_descendant ~ .g)) #descendant"; +testClassChange(addClass, "skyblue", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "f_descendant", "previous", selector, true, "descendant", skyblue); +testSiblingInsertionRemoval("invalid", "previous", selector, "descendant", false, grey, true, skyblue); +testClassChange(removeClass, "f_descendant", "previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("f_descendant", "previous", selector, "descendant", true, skyblue, false, grey); +testClassChange(addClass, "f_descendant", "has_scope", selector, true, "descendant", skyblue); +testClassChange(removeClass, "f_descendant", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "f_descendant", "direct_next", selector, true, "descendant", skyblue); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "descendant", false, grey, true, skyblue); +testClassChange(removeClass, "f_descendant", "direct_next", selector, false, "descendant", grey); +testSiblingInsertionRemoval("f_descendant", "direct_next", selector, "descendant", true, skyblue, false, grey); +testClassChange(removeClass, "skyblue", "has_scope", selector, false, "descendant", grey); + +selector = ".lightblue:has(~ #indirect_next:is(.p + .f_indirect_next ~ .g)) ~ #indirect_next"; +testClassChange(addClass, "lightblue", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "f_indirect_next", "previous", selector, true, "indirect_next", lightblue); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next", false, grey, true, lightblue); +testClassChange(removeClass, "f_indirect_next", "previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("f_indirect_next", "previous", selector, "indirect_next", true, lightblue, false, grey); +testClassChange(addClass, "f_indirect_next", "has_scope", selector, true, "indirect_next", lightblue); +testClassChange(removeClass, "f_indirect_next", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "f_indirect_next", "direct_next", selector, true, "indirect_next", lightblue); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "indirect_next", false, grey, true, lightblue); +testClassChange(removeClass, "f_indirect_next", "direct_next", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("f_indirect_next", "direct_next", selector, "indirect_next", true, lightblue, false, grey); +testClassChange(removeClass, "lightblue", "has_scope", selector, false, "indirect_next", grey); + +selector = ".darkblue:has(~ #indirect_next:is(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "darkblue", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "f_indirect_next_child", "previous", selector, true, "indirect_next_child", darkblue); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next_child", false, grey, true, darkblue); +testClassChange(removeClass, "f_indirect_next_child", "previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("f_indirect_next_child", "previous", selector, "indirect_next_child", true, darkblue, false, grey); +testClassChange(addClass, "f_indirect_next_child", "has_scope", selector, true, "indirect_next_child", darkblue); +testClassChange(removeClass, "f_indirect_next_child", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "f_indirect_next_child", "direct_next", selector, true, "indirect_next_child", darkblue); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "indirect_next_child", false, grey, true, darkblue); +testClassChange(removeClass, "f_indirect_next_child", "direct_next", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("f_indirect_next_child", "direct_next", selector, "indirect_next_child", true, darkblue, false, grey); +testClassChange(removeClass, "darkblue", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".yellow:has(~ #indirect_next:is(.h_has_scope .i))" +testClassChange(addClass, "yellow", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "h_has_scope", "parent", selector, true, "has_scope", yellow); +testClassChange(removeClass, "h_has_scope", "parent", selector, false, "has_scope", grey); +testClassChange(removeClass, "yellow", "has_scope", selector, false, "has_scope", grey); + +selector = ".ivory:has(~ #indirect_next:is(.h_descendant .i)) #descendant"; +testClassChange(addClass, "ivory", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "h_descendant", "parent", selector, true, "descendant", ivory); +testClassChange(removeClass, "h_descendant", "parent", selector, false, "descendant", grey); +testClassChange(removeClass, "ivory", "has_scope", selector, false, "descendant", grey); + +selector = ".greenyellow:has(~ #indirect_next:is(.h_indirect_next .i)) ~ #indirect_next"; +testClassChange(addClass, "greenyellow", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "h_indirect_next", "parent", selector, true, "indirect_next", greenyellow); +testClassChange(removeClass, "h_indirect_next", "parent", selector, false, "indirect_next", grey); +testClassChange(removeClass, "greenyellow", "has_scope", selector, false, "indirect_next", grey); + +selector = ".khaki:has(~ #indirect_next:is(.h_indirect_next_child .i)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "khaki", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "h_indirect_next_child", "parent", selector, true, "indirect_next_child", khaki); +testClassChange(removeClass, "h_indirect_next_child", "parent", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "khaki", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".purple:has(~ #indirect_next:is(.p + .j_has_scope ~ .k .l))" +testClassChange(addClass, "purple", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "j_has_scope", "parent_previous", selector, true, "has_scope", purple); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "has_scope", false, grey, true, purple); +testClassChange(removeClass, "j_has_scope", "parent_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("j_has_scope", "parent_previous", selector, "has_scope", true, purple, false, grey); +testClassChange(removeClass, "purple", "has_scope", selector, false, "has_scope", grey); + +selector = ".violet:has(~ #indirect_next:is(.p + .j_descendant ~ .k .l)) #descendant"; +testClassChange(addClass, "violet", "has_scope", selector, false, "descendant", grey); +testClassChange(addClass, "j_descendant", "parent_previous", selector, true, "descendant", violet); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "descendant", false, grey, true, violet); +testClassChange(removeClass, "j_descendant", "parent_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("j_descendant", "parent_previous", selector, "descendant", true, violet, false, grey); +testClassChange(removeClass, "violet", "has_scope", selector, false, "descendant", grey); + +selector = ".orchid:has(~ #indirect_next:is(.p + .j_indirect_next ~ .k .l)) ~ #indirect_next"; +testClassChange(addClass, "orchid", "has_scope", selector, false, "indirect_next", grey); +testClassChange(addClass, "j_indirect_next", "parent_previous", selector, true, "indirect_next", orchid); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next", false, grey, true, orchid); +testClassChange(removeClass, "j_indirect_next", "parent_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("j_indirect_next", "parent_previous", selector, "indirect_next", true, orchid, false, grey); +testClassChange(removeClass, "orchid", "has_scope", selector, false, "indirect_next", grey); + +selector = ".plum:has(~ #indirect_next:is(.p + .j_indirect_next_child ~ .k .l)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "plum", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(addClass, "j_indirect_next_child", "parent_previous", selector, true, "indirect_next_child", plum); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next_child", false, grey, true, plum); +testClassChange(removeClass, "j_indirect_next_child", "parent_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("j_indirect_next_child", "parent_previous", selector, "indirect_next_child", true, plum, false, grey); +testClassChange(removeClass, "plum", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".orange:has(#descendant:is(:is(.m, .n) .o))"; +testClassChange(addClass, "orange", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "m", "parent", selector, true, "has_scope", orange); +testClassChange(removeClass, "m", "parent", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "parent", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "parent", selector, false, "has_scope", grey); +testClassChange(addClass, "m", "has_scope", selector, true, "has_scope", orange); +testClassChange(removeClass, "m", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "has_scope", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "m", "child", selector, true, "has_scope", orange); +testClassChange(removeClass, "m", "child", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "child", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "child", selector, false, "has_scope", grey); +testClassChange(removeClass, "orange", "has_scope", selector, false, "has_scope", grey); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/is.html b/testing/web-platform/tests/css/selectors/invalidation/is.html new file mode 100644 index 0000000000..8490f09697 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/is.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: :is()</title> + <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com"> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches"> + <meta name="assert" content="This tests that the :is() selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .b { + color: yellow; + } + /*Simple selector arguments */ + .a :is(.b, .c) { + color: red; + } + /*Compound selector arguments */ + .a :is(.c#d, .e) { + color: green; + } + /* Complex selector arguments */ + .a .g>.b { + color: black; + } + .a :is(.e+.f, .g>.b, .h) { + color: blue; + } + .g>.b { + color: black; + } + .a .h { + color: black; + } + /* Nested */ + .a+.c>.e { + color: black; + } + .c>.a+.e { + color: black; + } + .a+:is(.b+.f, :is(.c>.e, .g)) { + color: red; + } + .c>.e { + color: black; + } + </style> + </head> + <body> + <div id="a1"> + <div class="b" id="b1"> + Red + </div> + <div class="c" id="c1"> + Red + </div> + <div class="c" id="d"> + Green + </div> + <div class="e" id="e1"> + Green + </div> + <div class="f" id="f1"> + Blue + </div> + <div class="g"> + <div class="b" id="b2"> + Blue + <div class="b" id="b3"> + Red + </div> + </div> + </div> + <div class="h" id="h1"> + Blue + </div> + </div> + <div class="c" id="c2"> + <div id="a2"></div> + <div class="e" id="e2"> + Red + </div> + </div> + <script> + document.body.offsetTop; + + var black = "rgb(0, 0, 0)"; + var blue = "rgb(0, 0, 255)"; + var green = "rgb(0, 128, 0)"; + var red = "rgb(255, 0, 0)"; + var yellow = "rgb(255, 255, 0)"; + + test(() => { + assert_equals(getComputedStyle(b1).color, yellow); + assert_equals(getComputedStyle(b2).color, black); + assert_equals(getComputedStyle(b3).color, yellow); + assert_equals(getComputedStyle(c1).color, black); + assert_equals(getComputedStyle(d).color, black); + assert_equals(getComputedStyle(e1).color, black); + assert_equals(getComputedStyle(e2).color, black); + assert_equals(getComputedStyle(f1).color, black); + assert_equals(getComputedStyle(h1).color, black); + }, "Preconditions."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b1).color, red); + assert_equals(getComputedStyle(b3).color, red); + assert_equals(getComputedStyle(c1).color, red); + }, "Invalidate :is() for simple selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(d).color, green); + }, "Invalidate :is() for compound selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b2).color, blue); + assert_equals(getComputedStyle(b3).color, red); + assert_equals(getComputedStyle(f1).color, blue); + }, "Invalidate :is() for complex selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(e2).color, black); + a2.className = "a"; + assert_equals(getComputedStyle(e2).color, red); + }, "Invalidate nested :is()."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b2).color, blue); + assert_equals(getComputedStyle(h1).color, blue); + }, "Test specificity of :is()."); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/link-pseudo-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/link-pseudo-in-has.html new file mode 100644 index 0000000000..0ff879768b --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/link-pseudo-in-has.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :link, :visited :any-link, pseudo-class in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #parent { color: blue; } + #grandparent { color: blue; } + #parent:has(> :not(:link)) { color: grey; } + #parent:has(> :link) { color: green; } + #parent:has(> :visited) { color: red; } + #grandparent:has(:not(:any-link)) { color: grey; } + #grandparent:has(:any-link) { color: green; } +</style> +<div id="grandparent"></div> +<script> + const BLUE = "rgb(0, 0, 255)"; + const GREY = "rgb(128, 128, 128)"; + const GREEN = "rgb(0, 128, 0)"; + const RED = "rgb(255, 0, 0)"; + + function checkColor(id, color, target_matches) { + let element = document.getElementById(id); + let message = ["location.hash ==", location.hash, ": #" + id, "should be", + color, (target_matches ? "with" : "without"), + ":target"].join(" "); + assert_equals(getComputedStyle(element).color, color, message); + } + + promise_test(async () => { + assert_equals(getComputedStyle(grandparent).color, BLUE, + "grandparent should be blue without any element"); + + let parent = document.createElement("div"); + parent.id = "parent"; + grandparent.appendChild(parent); + + assert_equals(getComputedStyle(grandparent).color, GREY, + "grandparent should be grey after parent added"); + assert_equals(getComputedStyle(parent).color, BLUE, + "parent should be blue without any link"); + + let div = document.createElement("div"); + parent.appendChild(div); + + assert_equals(getComputedStyle(grandparent).color, GREY, + "grandparent should be grey after div added"); + assert_equals(getComputedStyle(parent).color, GREY, + "parent should be grey after div added"); + + let visited = document.createElement("a"); + visited.href = ""; + parent.appendChild(visited); + + assert_equals(getComputedStyle(grandparent).color, GREEN, + "grandparent should be green after visited link added"); + assert_equals(getComputedStyle(parent).color, GREEN, + "parent should be green after visited link added"); + + let unvisited = document.createElement("a"); + unvisited.href = "unvisited"; + parent.appendChild(unvisited); + + assert_equals(getComputedStyle(grandparent).color, GREEN, + "grandparent should be green after unvisited link added"); + assert_equals(getComputedStyle(parent).color, GREEN, + "parent should be green after unvisited link added"); + + unvisited.remove(); + + assert_equals(getComputedStyle(grandparent).color, GREEN, + "grandparent should be green after unvisited link removed"); + assert_equals(getComputedStyle(parent).color, GREEN, + "parent should be blue after unvisited link removed"); + + visited.remove(); + + assert_equals(getComputedStyle(grandparent).color, GREY, + "grandparent should be grey after visited link removed"); + assert_equals(getComputedStyle(parent).color, GREY, + "parent should be grey after visited link removed"); + + div.remove(); + + assert_equals(getComputedStyle(grandparent).color, GREY, + "grandparent should be grey after div removed"); + assert_equals(getComputedStyle(parent).color, BLUE, + "parent should be blue after div removed"); + + parent.remove(); + + assert_equals(getComputedStyle(grandparent).color, BLUE, + "grandparent should be blue after parent removed"); + }); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/location-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/location-pseudo-classes-in-has.html new file mode 100644 index 0000000000..697fc70ae6 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/location-pseudo-classes-in-has.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: location pseudo classes in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #parent1:has(:link) { color: green } + #parent1:has(:visited) { color: yellowgreen } +</style> +<div id=parent1> + <div>parent color must be blue with + <a id=link1 href="#unvisited">visited link</a> + </div> +</div> +<script> + test(() => { + history.replaceState({}, "", "#visited"); + + assert_equals(getComputedStyle(parent1).color, "rgb(0, 128, 0)", + "parent should be green"); + + link1.href = "#visited"; + + assert_equals(getComputedStyle(parent1).color, "rgb(0, 128, 0)", + "parent should be still green"); + }); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/media-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/media-pseudo-classes-in-has.html new file mode 100644 index 0000000000..f0df71534a --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/media-pseudo-classes-in-has.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<title>:has() invalidation with :playing, :paused, :seeking and :muted pseudo-classes</title> +<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors/#video-state"> +<style> + #subject { + background-color: black; + accent-color: black; + color: black; + border: 2px solid black; + } + #subject:has(:muted) { + background-color: red; + } + #subject:has(:playing) { + border-color: green; + } + #subject:has(:paused) { + color: orange; + } + #subject:has(:seeking) { + accent-color: blue; + } +</style> +<body> + <div id="subject"> + Test media pseudo-classes invalidation with :has() + <input type="checkbox"> + <video width="300" height="300" loop></video> + </div> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + const GREEN = "rgb(0, 128, 0)"; + const ORANGE = "rgb(255, 165, 0)"; + const BLUE = "rgb(0, 0, 255)"; + const RED = "rgb(255, 0, 0)"; + const BLACK = "rgb(0, 0, 0)"; + + function assert_matches_muted(muted) { + assert_equals(getComputedStyle(subject).backgroundColor, muted ? RED : BLACK); + } + + function assert_matches_playing(playing) { + assert_equals(getComputedStyle(subject).borderColor, playing ? GREEN : BLACK); + assert_equals(getComputedStyle(subject).color, !playing ? ORANGE : BLACK); + } + + function assert_matches_seeking(seeking) { + assert_equals(getComputedStyle(subject).accentColor, seeking ? BLUE : BLACK); + } + + promise_test(async (t) => { + t.add_cleanup(() => { + video.muted = false; + video.pause(); + video.removeAttribute("src"); + }); + const video = document.querySelector("video"); + assert_matches_muted(false); + assert_matches_playing(false); + assert_matches_seeking(false); + await new Promise((r) => { + video.addEventListener("canplay", r, { once: true }); + video.src = "/media/counting.mp4"; + }); + video.muted = true; // allows us to play the video + assert_matches_muted(true); + await new Promise((r) => { + video.addEventListener("playing", r, { once: true }); + video.play(); + }); + assert_matches_playing(true); + }, "Test :playing pseudo-classes"); + + promise_test(async (t) => { + t.add_cleanup(() => { + video.removeAttribute("src"); + }); + const video = document.querySelector("video"); + assert_matches_muted(false); + assert_matches_playing(false); + assert_matches_seeking(false); + await new Promise((r) => { + video.addEventListener("canplay", r, { once: true }); + video.src = "/media/counting.mp4"; + }); + + assert_matches_seeking(false); + await new Promise((r) => { + video.addEventListener("seeking", r, { once: true }); + video.currentTime = 10; + }); + assert_matches_seeking(true); + }, "Test :seeking pseudo-class"); + + promise_test(async (t) => { + t.add_cleanup(() => { + video.removeAttribute("src"); + }); + const video = document.querySelector("video"); + await new Promise((r) => { + video.addEventListener("canplay", r, { once: true }); + video.src = "/media/counting.mp4"; + }); + assert_matches_muted(false); + video.muted = true; + assert_matches_muted(true); + video.muted = false; + assert_matches_muted(false); + }, "Test :muted pseudo-class"); + </script> +</body> diff --git a/testing/web-platform/tests/css/selectors/invalidation/modal-pseudo-class-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/modal-pseudo-class-in-has.html new file mode 100644 index 0000000000..1bff896d49 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/modal-pseudo-class-in-has.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :modal pseudo class in :has()</title> +<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + #subject:has(#dialog:modal) { color: green; } + #subject:has(#fullscreenTarget:modal) { color: blue; } +</style> +<div id="subject"> + This is some text. + <dialog id="dialog">This is a dialog</dialog> + <div id="fullscreenTarget">This is going to be fullscreened</div> +</div> +<script> + // Dialog tests + test(function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since dialog is not modal"); + dialog.show(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since dialog is not modal"); + dialog.close(); + }, ":modal pseudo-class is not active with dialog.show()"); + test(function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + dialog.showModal(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green since dialog is shown modally"); + dialog.close(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since dialog is closed"); + }, ":modal pseudo-class invalidation with showModal + close"); + test(function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + dialog.showModal(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)", + "ancestor should be green since dialog is shown modally"); + dialog.remove(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since dialog is removed"); + }, ":modal pseudo-class invalidation with showModal + remove"); + + // Fullscreen tests + let waitForFullscreenChange = () => { + return new Promise((resolve) => { + document.addEventListener("fullscreenchange", resolve, { once: true }); + }); + }; + promise_test(async function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + test_driver.bless("fullscreen", () => fullscreenTarget.requestFullscreen()); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 255)", + "ancestor should be blue since target is fullscreen"); + document.exitFullscreen(); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since target is no longer fullscreen"); + }, ":modal pseudo-class invalidation with requestFullscreen + exitFullscreen"); + promise_test(async function() { + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black"); + test_driver.bless("fullscreen", () => fullscreenTarget.requestFullscreen()); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 255)", + "ancestor should be blue since target is fullscreen"); + fullscreenTarget.remove(); + await waitForFullscreenChange(); + assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)", + "ancestor should be black since target is removed"); + }, ":modal pseudo-class invalidation with requestFullscreen + remove"); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/not-001.html b/testing/web-platform/tests/css/selectors/invalidation/not-001.html new file mode 100644 index 0000000000..db1cf291c7 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/not-001.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>CSS Selectors Invalidation: complex :not()</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#negation"> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + * { + color: yellow; + } + :not(.b ~ *) { + color: green; + } +</style> +<div id="b"> +</div> +<div class="c"> +</div> +<div class="d"> +</div> +<script> + var black = "rgb(0, 0, 0)"; + var blue = "rgb(0, 0, 255)"; + var green = "rgb(0, 128, 0)"; + var red = "rgb(255, 0, 0)"; + var yellow = "rgb(255, 255, 0)"; + + test(() => { + assert_equals(getComputedStyle(document.querySelector("#b")).color, green); + assert_equals(getComputedStyle(document.querySelector(".c")).color, green); + assert_equals(getComputedStyle(document.querySelector(".d")).color, green); + }, "precondition"); + + test(() => { + document.getElementById("b").className = "b"; + assert_equals(getComputedStyle(document.querySelector("#b")).color, green); + assert_equals(getComputedStyle(document.querySelector(".c")).color, yellow); + assert_equals(getComputedStyle(document.querySelector(".d")).color, yellow); + }, "Invalidation of sibling combinators in :not()"); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/not-002.html b/testing/web-platform/tests/css/selectors/invalidation/not-002.html new file mode 100644 index 0000000000..811b125f8c --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/not-002.html @@ -0,0 +1,133 @@ +<!DOCTYPE html> +<title>CSS Selectors Invalidation: complex :not()</title> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#negation"> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + .b { + color: yellow; + } + /*Simple selector arguments */ + .a :not(:not(.b, .c)) { + color: red; + } + /*Compound selector arguments */ + .a :not(:not(.c#d, .e)) { + color: green; + } + /* Complex selector arguments */ + .a .g>.b { + color: black; + } + .a :not(:not(.e+.f, .g>.b, .h)) { + color: blue; + } + .g>.b { + color: black; + } + .a .h { + color: black; + } + /* Nested */ + .a+.c>.e { + color: black; + } + .c>.a+.e { + color: black; + } + .a+:not(:not(.b+.f, :is(.c>.e, .g))) { + color: red; + } + .c>.e { + color: black; + } +</style> +<div id="a1"> + <div class="b" id="b1"> + Red + </div> + <div class="c" id="c1"> + Red + </div> + <div class="c" id="d"> + Green + </div> + <div class="e" id="e1"> + Green + </div> + <div class="f" id="f1"> + Blue + </div> + <div class="g"> + <div class="b" id="b2"> + Blue + <div class="b" id="b3"> + Red + </div> + </div> + </div> + <div class="h" id="h1"> + Blue + </div> +</div> +<div class="c" id="c2"> + <div id="a2"></div> + <div class="e" id="e2"> + Red + </div> +</div> +<script> + document.body.offsetTop; + + var black = "rgb(0, 0, 0)"; + var blue = "rgb(0, 0, 255)"; + var green = "rgb(0, 128, 0)"; + var red = "rgb(255, 0, 0)"; + var yellow = "rgb(255, 255, 0)"; + + test(() => { + assert_equals(getComputedStyle(b1).color, yellow); + assert_equals(getComputedStyle(b2).color, black); + assert_equals(getComputedStyle(b3).color, yellow); + assert_equals(getComputedStyle(c1).color, black); + assert_equals(getComputedStyle(d).color, black); + assert_equals(getComputedStyle(e1).color, black); + assert_equals(getComputedStyle(e2).color, black); + assert_equals(getComputedStyle(f1).color, black); + assert_equals(getComputedStyle(h1).color, black); + }, "Preconditions."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b1).color, red); + assert_equals(getComputedStyle(b3).color, red); + assert_equals(getComputedStyle(c1).color, red); + }, "Invalidate :not() for simple selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(d).color, green); + }, "Invalidate :not() for compound selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b2).color, blue); + assert_equals(getComputedStyle(b3).color, red); + assert_equals(getComputedStyle(f1).color, blue); + }, "Invalidate :not() for complex selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(e2).color, black); + a2.className = "a"; + assert_equals(getComputedStyle(e2).color, red); + }, "Invalidate nested :is() inside :not()."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b2).color, blue); + assert_equals(getComputedStyle(h1).color, blue); + }, "Test specificity of :not()."); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/not-pseudo-containing-complex-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/not-pseudo-containing-complex-in-has.html new file mode 100644 index 0000000000..d24abf69f7 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/not-pseudo-containing-complex-in-has.html @@ -0,0 +1,375 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :not(<complex-selector>) in :has() argument (complex selector)</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +div { color: grey } +.red:has(#descendant:not(.a_has_scope .b)) { color: red } +.orangered:has(#descendant:not(.a_descendant .b)) #descendant { color: orangered } +.darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next { color: darkred } +.pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child { color: pink } +.green:has(#descendant:not(.p + .c_has_scope ~ .d .e)) { color: green } +.lightgreen:has(#descendant:not(.p + .c_descendant ~ .d .e)) #descendant { color: lightgreen } +.darkgreen:has(#descendant:not(.p + .c_indirect_next ~ .d .e)) ~ #indirect_next { color: darkgreen } +.yellowgreen:has(#descendant:not(.p + .c_indirect_next_child ~ .d .e)) ~ #indirect_next #indirect_next_child { color: yellowgreen } +.blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g)) { color: blue } +.skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant { color: skyblue } +.lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next { color: lightblue } +.darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child { color: darkblue } +.yellow:has(~ #indirect_next:not(.h_has_scope .i)) { color: yellow } +.ivory:has(~ #indirect_next:not(.h_descendant .i)) #descendant { color: ivory } +.greenyellow:has(~ #indirect_next:not(.h_indirect_next .i)) ~ #indirect_next { color: greenyellow } +.khaki:has(~ #indirect_next:not(.h_indirect_next_child .i)) ~ #indirect_next #indirect_next_child { color: khaki } +.purple:has(~ #indirect_next:not(.p + .j_has_scope ~ .k .l)) { color: purple } +.violet:has(~ #indirect_next:not(.p + .j_descendant ~ .k .l)) #descendant { color: violet } +.orchid:has(~ #indirect_next:not(.p + .j_indirect_next ~ .k .l)) ~ #indirect_next { color: orchid } +.plum:has(~ #indirect_next:not(.p + .j_indirect_next_child ~ .k .l)) ~ #indirect_next #indirect_next_child { color: plum } +.orange:has(#descendant:not(.m:not(.n) .o)) { color: orange } +</style> +<div> + <div class="p"></div> + <div id="parent_previous"></div> + <div id="parent" class="d k"> + <div class="p"></div> + <div id="previous"></div> + <div class="p"></div> + <div id="has_scope" class="d"> + <div class="p"></div> + <div id="child_previous"></div> + <div id="child" class="d"> + <div id="descendant" class="b e o"></div> + </div> + </div> + <div class="p"></div> + <div id="direct_next"></div> + <div id="indirect_next" class="g i l"> + <div id="indirect_next_child"></div> + </div> + </div> +</div> +<script> +const grey = "rgb(128, 128, 128)"; +const red = "rgb(255, 0, 0)"; +const orangered = "rgb(255, 69, 0)"; +const darkred = "rgb(139, 0, 0)"; +const pink = "rgb(255, 192, 203)"; +const green = "rgb(0, 128, 0)"; +const lightgreen = "rgb(144, 238, 144)"; +const darkgreen = "rgb(0, 100, 0)"; +const yellowgreen = "rgb(154, 205, 50)"; +const blue = "rgb(0, 0, 255)"; +const skyblue = "rgb(135, 206, 235)"; +const lightblue = "rgb(173, 216, 230)"; +const darkblue = "rgb(0, 0, 139)"; +const yellow = "rgb(255, 255, 0)"; +const ivory = "rgb(255, 255, 240)"; +const greenyellow = "rgb(173, 255, 47)"; +const khaki = "rgb(240, 230, 140)"; +const purple = "rgb(128, 0, 128)"; +const violet = "rgb(238, 130, 238)"; +const orchid = "rgb(218, 112, 214)"; +const plum = "rgb(221, 160, 221)"; +const orange = "rgb(255, 165, 0)"; + +function addClass(element, class_name) { + element.classList.add(class_name); +} + +function removeClass(element, class_name) { + element.classList.remove(class_name); +} + +function testClassChange(operation, class_name, element_id, + selector, matches_result, + subject_id, subject_color) { + let element = document.getElementById(element_id); + assert_equals(element ? element.id : "", element_id); + let subject = document.getElementById(subject_id); + assert_equals(subject ? subject.id : "", subject_id); + let message_prefix = [ + "[", selector, "]", + ["#", element.id, ".classList.", + (operation == addClass ? "add" : "remove"), + "('", class_name, "')"].join(""), + ": "].join(" "); + operation(element, class_name); + test(function() { + assert_equals(subject.matches(selector), matches_result); + }, message_prefix + "check matches (" + matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, subject_color); + }, message_prefix + "check #" + subject_id + " color"); +} + +function testSiblingInsertionRemoval(class_name, insert_before_id, selector, + subject_id, + insertion_matches_result, + insertion_subject_color, + removal_matches_result, + removal_subject_color) { + let insert_before = document.getElementById(insert_before_id); + assert_equals(insert_before ? insert_before.id : "", insert_before_id); + let parent = insert_before.parentElement; + let subject = document.getElementById(subject_id); + assert_equals(subject ? subject.id : "", subject_id); + let message_prefix = [ + "[", selector, "]", + ["insert/remove .", + class_name, " before #", insert_before.id, ")"].join(""), + ": "].join(" "); + + let div = document.createElement("div"); + div.classList.add(class_name); + + parent.insertBefore(div, insert_before); + test(function() { + assert_equals(subject.matches(selector), insertion_matches_result); + }, message_prefix + "(insertion) check matches (" + + insertion_matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, insertion_subject_color); + }, message_prefix + "(insertion) check #" + subject_id + " color"); + + div.remove(); + test(function() { + assert_equals(subject.matches(selector), removal_matches_result); + }, message_prefix + "(removal) check matches (" + + removal_matches_result + ")"); + test(function() { + assert_equals(getComputedStyle(subject).color, removal_subject_color); + }, message_prefix + "(removal) check #" + subject_id + " color"); +} + +assert_equals(getComputedStyle(has_scope).color, grey); + +let selector = ".red:has(#descendant:not(.a_has_scope .b))"; +testClassChange(addClass, "red", "has_scope", selector, true, "has_scope", red); +testClassChange(addClass, "a_has_scope", "parent", selector, false, "has_scope", grey); +testClassChange(removeClass, "a_has_scope", "parent", selector, true, "has_scope", red); +testClassChange(addClass, "a_has_scope", "has_scope", selector, false, "has_scope", grey); +testClassChange(removeClass, "a_has_scope", "has_scope", selector, true, "has_scope", red); +testClassChange(addClass, "a_has_scope", "child", selector, false, "has_scope", grey); +testClassChange(removeClass, "a_has_scope", "child", selector, true, "has_scope", red); +testClassChange(removeClass, "red", "has_scope", selector, false, "has_scope", grey); + +selector = ".orangered:has(#descendant:not(.a_descendant .b)) #descendant"; +testClassChange(addClass, "orangered", "has_scope", selector, true, "descendant", orangered); +testClassChange(addClass, "a_descendant", "parent", selector, false, "descendant", grey); +testClassChange(removeClass, "a_descendant", "parent", selector, true, "descendant", orangered); +testClassChange(addClass, "a_descendant", "has_scope", selector, false, "descendant", grey); +testClassChange(removeClass, "a_descendant", "has_scope", selector, true, "descendant", orangered); +testClassChange(addClass, "a_descendant", "child", selector, false, "descendant", grey); +testClassChange(removeClass, "a_descendant", "child", selector, true, "descendant", orangered); +testClassChange(removeClass, "orangered", "has_scope", selector, false, "descendant", grey); + +selector = ".darkred:has(#descendant:not(.a_indirect_next .b)) ~ #indirect_next"; +testClassChange(addClass, "darkred", "has_scope", selector, true, "indirect_next", darkred); +testClassChange(addClass, "a_indirect_next", "parent", selector, false, "indirect_next", grey); +testClassChange(removeClass, "a_indirect_next", "parent", selector, true, "indirect_next", darkred); +testClassChange(addClass, "a_indirect_next", "has_scope", selector, false, "indirect_next", grey); +testClassChange(removeClass, "a_indirect_next", "has_scope", selector, true, "indirect_next", darkred); +testClassChange(addClass, "a_indirect_next", "child", selector, false, "indirect_next", grey); +testClassChange(removeClass, "a_indirect_next", "child", selector, true, "indirect_next", darkred); +testClassChange(removeClass, "darkred", "has_scope", selector, false, "indirect_next", grey); + +selector = ".pink:has(#descendant:not(.a_indirect_next_child .b)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "pink", "has_scope", selector, true, "indirect_next_child", pink); +testClassChange(addClass, "a_indirect_next_child", "parent", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "a_indirect_next_child", "parent", selector, true, "indirect_next_child", pink); +testClassChange(addClass, "a_indirect_next_child", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "a_indirect_next_child", "has_scope", selector, true, "indirect_next_child", pink); +testClassChange(addClass, "a_indirect_next_child", "child", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "a_indirect_next_child", "child", selector, true, "indirect_next_child", pink); +testClassChange(removeClass, "pink", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".green:has(#descendant:not(.p + .c_has_scope ~ .d .e))"; +testClassChange(addClass, "green", "has_scope", selector, true, "has_scope", green); +testClassChange(addClass, "c_has_scope", "parent_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "has_scope", true, green, false, grey); +testClassChange(removeClass, "c_has_scope", "parent_previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("c_has_scope", "parent_previous", selector, "has_scope", false, grey, true, green); +testClassChange(addClass, "c_has_scope", "previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "has_scope", true, green, false, grey); +testClassChange(removeClass, "c_has_scope", "previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("c_has_scope", "previous", selector, "has_scope", false, grey, true, green); +testClassChange(addClass, "c_has_scope", "child_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "has_scope", true, green, false, grey); +testClassChange(removeClass, "c_has_scope", "child_previous", selector, true, "has_scope", green); +testSiblingInsertionRemoval("c_has_scope", "child_previous", selector, "has_scope", false, grey, true, green); +testClassChange(removeClass, "green", "has_scope", selector, false, "has_scope", grey); + +selector = ".lightgreen:has(#descendant:not(.p + .c_descendant ~ .d .e)) #descendant"; +testClassChange(addClass, "lightgreen", "has_scope", selector, true, "descendant", lightgreen); +testClassChange(addClass, "c_descendant", "parent_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(removeClass, "c_descendant", "parent_previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("c_descendant", "parent_previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(addClass, "c_descendant", "previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(removeClass, "c_descendant", "previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("c_descendant", "previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(addClass, "c_descendant", "child_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "descendant", true, lightgreen, false, grey); +testClassChange(removeClass, "c_descendant", "child_previous", selector, true, "descendant", lightgreen); +testSiblingInsertionRemoval("c_descendant", "child_previous", selector, "descendant", false, grey, true, lightgreen); +testClassChange(removeClass, "lightgreen", "has_scope", selector, false, "descendant", grey); + +selector = ".darkgreen:has(#descendant:not(.p + .c_indirect_next ~ .d .e)) ~ #indirect_next"; +testClassChange(addClass, "darkgreen", "has_scope", selector, true, "indirect_next", darkgreen); +testClassChange(addClass, "c_indirect_next", "parent_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(removeClass, "c_indirect_next", "parent_previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("c_indirect_next", "parent_previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(addClass, "c_indirect_next", "previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(removeClass, "c_indirect_next", "previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("c_indirect_next", "previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(addClass, "c_indirect_next", "child_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "indirect_next", true, darkgreen, false, grey); +testClassChange(removeClass, "c_indirect_next", "child_previous", selector, true, "indirect_next", darkgreen); +testSiblingInsertionRemoval("c_indirect_next", "child_previous", selector, "indirect_next", false, grey, true, darkgreen); +testClassChange(removeClass, "darkgreen", "has_scope", selector, false, "indirect_next", grey); + +selector = ".yellowgreen:has(#descendant:not(.p + .c_indirect_next_child ~ .d .e)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "yellowgreen", "has_scope", selector, true, "indirect_next_child", yellowgreen); +testClassChange(addClass, "c_indirect_next_child", "parent_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(removeClass, "c_indirect_next_child", "parent_previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("c_indirect_next_child", "parent_previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(addClass, "c_indirect_next_child", "previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(removeClass, "c_indirect_next_child", "previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("c_indirect_next_child", "previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(addClass, "c_indirect_next_child", "child_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "child_previous", selector, "indirect_next_child", true, yellowgreen, false, grey); +testClassChange(removeClass, "c_indirect_next_child", "child_previous", selector, true, "indirect_next_child", yellowgreen); +testSiblingInsertionRemoval("c_indirect_next_child", "child_previous", selector, "indirect_next_child", false, grey, true, yellowgreen); +testClassChange(removeClass, "yellowgreen", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".blue:has(~ #indirect_next:not(.p + .f_has_scope ~ .g))"; +testClassChange(addClass, "blue", "has_scope", selector, true, "has_scope", blue); +testClassChange(addClass, "f_has_scope", "previous", selector, false, "has_scope", grey); +testClassChange(removeClass, "f_has_scope", "previous", selector, true, "has_scope", blue); +testSiblingInsertionRemoval("f_has_scope", "previous", selector, "has_scope", false, grey, true, blue); +testClassChange(addClass, "f_has_scope", "has_scope", selector, false, "has_scope", grey); +testClassChange(removeClass, "f_has_scope", "has_scope", selector, true, "has_scope", blue); +testClassChange(addClass, "f_has_scope", "direct_next", selector, false, "has_scope", grey); +testClassChange(removeClass, "f_has_scope", "direct_next", selector, true, "has_scope", blue); +testSiblingInsertionRemoval("f_has_scope", "direct_next", selector, "has_scope", false, grey, true, blue); +testClassChange(removeClass, "blue", "has_scope", selector, false, "has_scope", grey); + +selector = ".skyblue:has(~ #indirect_next:not(.p + .f_descendant ~ .g)) #descendant"; +testClassChange(addClass, "skyblue", "has_scope", selector, true, "descendant", skyblue); +testClassChange(addClass, "f_descendant", "previous", selector, false, "descendant", grey); +testClassChange(removeClass, "f_descendant", "previous", selector, true, "descendant", skyblue); +testSiblingInsertionRemoval("f_descendant", "previous", selector, "descendant", false, grey, true, skyblue); +testClassChange(addClass, "f_descendant", "has_scope", selector, false, "descendant", grey); +testClassChange(removeClass, "f_descendant", "has_scope", selector, true, "descendant", skyblue); +testClassChange(addClass, "f_descendant", "direct_next", selector, false, "descendant", grey); +testClassChange(removeClass, "f_descendant", "direct_next", selector, true, "descendant", skyblue); +testSiblingInsertionRemoval("f_descendant", "direct_next", selector, "descendant", false, grey, true, skyblue); +testClassChange(removeClass, "skyblue", "has_scope", selector, false, "descendant", grey); + +selector = ".lightblue:has(~ #indirect_next:not(.p + .f_indirect_next ~ .g)) ~ #indirect_next"; +testClassChange(addClass, "lightblue", "has_scope", selector, true, "indirect_next", lightblue); +testClassChange(addClass, "f_indirect_next", "previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next", true, lightblue, false, grey); +testClassChange(removeClass, "f_indirect_next", "previous", selector, true, "indirect_next", lightblue); +testSiblingInsertionRemoval("f_indirect_next", "previous", selector, "indirect_next", false, grey, true, lightblue); +testClassChange(addClass, "f_indirect_next", "has_scope", selector, false, "indirect_next", grey); +testClassChange(removeClass, "f_indirect_next", "has_scope", selector, true, "indirect_next", lightblue); +testClassChange(addClass, "f_indirect_next", "direct_next", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "indirect_next", true, lightblue, false, grey); +testClassChange(removeClass, "f_indirect_next", "direct_next", selector, true, "indirect_next", lightblue); +testSiblingInsertionRemoval("f_indirect_next", "direct_next", selector, "indirect_next", false, grey, true, lightblue); +testClassChange(removeClass, "lightblue", "has_scope", selector, false, "indirect_next", grey); + +selector = ".darkblue:has(~ #indirect_next:not(.p + .f_indirect_next_child ~ .g)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "darkblue", "has_scope", selector, true, "indirect_next_child", darkblue); +testClassChange(addClass, "f_indirect_next_child", "previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "previous", selector, "indirect_next_child", true, darkblue, false, grey); +testClassChange(removeClass, "f_indirect_next_child", "previous", selector, true, "indirect_next_child", darkblue); +testSiblingInsertionRemoval("f_indirect_next_child", "previous", selector, "indirect_next_child", false, grey, true, darkblue); +testClassChange(addClass, "f_indirect_next_child", "has_scope", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "f_indirect_next_child", "has_scope", selector, true, "indirect_next_child", darkblue); +testClassChange(addClass, "f_indirect_next_child", "direct_next", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "direct_next", selector, "indirect_next_child", true, darkblue, false, grey); +testClassChange(removeClass, "f_indirect_next_child", "direct_next", selector, true, "indirect_next_child", darkblue); +testSiblingInsertionRemoval("f_indirect_next_child", "direct_next", selector, "indirect_next_child", false, grey, true, darkblue); +testClassChange(removeClass, "darkblue", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".yellow:has(~ #indirect_next:not(.h_has_scope .i))" +testClassChange(addClass, "yellow", "has_scope", selector, true, "has_scope", yellow); +testClassChange(addClass, "h_has_scope", "parent", selector, false, "has_scope", grey); +testClassChange(removeClass, "h_has_scope", "parent", selector, true, "has_scope", yellow); +testClassChange(removeClass, "yellow", "has_scope", selector, false, "has_scope", grey); + +selector = ".ivory:has(~ #indirect_next:not(.h_descendant .i)) #descendant"; +testClassChange(addClass, "ivory", "has_scope", selector, true, "descendant", ivory); +testClassChange(addClass, "h_descendant", "parent", selector, false, "descendant", grey); +testClassChange(removeClass, "h_descendant", "parent", selector, true, "descendant", ivory); +testClassChange(removeClass, "ivory", "has_scope", selector, false, "descendant", grey); + +selector = ".greenyellow:has(~ #indirect_next:not(.h_indirect_next .i)) ~ #indirect_next"; +testClassChange(addClass, "greenyellow", "has_scope", selector, true, "indirect_next", greenyellow); +testClassChange(addClass, "h_indirect_next", "parent", selector, false, "indirect_next", grey); +testClassChange(removeClass, "h_indirect_next", "parent", selector, true, "indirect_next", greenyellow); +testClassChange(removeClass, "greenyellow", "has_scope", selector, false, "indirect_next", grey); + +selector = ".khaki:has(~ #indirect_next:not(.h_indirect_next_child .i)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "khaki", "has_scope", selector, true, "indirect_next_child", khaki); +testClassChange(addClass, "h_indirect_next_child", "parent", selector, false, "indirect_next_child", grey); +testClassChange(removeClass, "h_indirect_next_child", "parent", selector, true, "indirect_next_child", khaki); +testClassChange(removeClass, "khaki", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".purple:has(~ #indirect_next:not(.p + .j_has_scope ~ .k .l))" +testClassChange(addClass, "purple", "has_scope", selector, true, "has_scope", purple); +testClassChange(addClass, "j_has_scope", "parent_previous", selector, false, "has_scope", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "has_scope", true, purple, false, grey); +testClassChange(removeClass, "j_has_scope", "parent_previous", selector, true, "has_scope", purple); +testSiblingInsertionRemoval("j_has_scope", "parent_previous", selector, "has_scope", false, grey, true, purple); +testClassChange(removeClass, "purple", "has_scope", selector, false, "has_scope", grey); + +selector = ".violet:has(~ #indirect_next:not(.p + .j_descendant ~ .k .l)) #descendant"; +testClassChange(addClass, "violet", "has_scope", selector, true, "descendant", violet); +testClassChange(addClass, "j_descendant", "parent_previous", selector, false, "descendant", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "descendant", true, violet, false, grey); +testClassChange(removeClass, "j_descendant", "parent_previous", selector, true, "descendant", violet); +testSiblingInsertionRemoval("j_descendant", "parent_previous", selector, "descendant", false, grey, true, violet); +testClassChange(removeClass, "violet", "has_scope", selector, false, "descendant", grey); + +selector = ".orchid:has(~ #indirect_next:not(.p + .j_indirect_next ~ .k .l)) ~ #indirect_next"; +testClassChange(addClass, "orchid", "has_scope", selector, true, "indirect_next", orchid); +testClassChange(addClass, "j_indirect_next", "parent_previous", selector, false, "indirect_next", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next", true, orchid, false, grey); +testClassChange(removeClass, "j_indirect_next", "parent_previous", selector, true, "indirect_next", orchid); +testSiblingInsertionRemoval("j_indirect_next", "parent_previous", selector, "indirect_next", false, grey, true, orchid); +testClassChange(removeClass, "orchid", "has_scope", selector, false, "indirect_next", grey); + +selector = ".plum:has(~ #indirect_next:not(.p + .j_indirect_next_child ~ .k .l)) ~ #indirect_next #indirect_next_child"; +testClassChange(addClass, "plum", "has_scope", selector, true, "indirect_next_child", plum); +testClassChange(addClass, "j_indirect_next_child", "parent_previous", selector, false, "indirect_next_child", grey); +testSiblingInsertionRemoval("invalid", "parent_previous", selector, "indirect_next_child", true, plum, false, grey); +testClassChange(removeClass, "j_indirect_next_child", "parent_previous", selector, true, "indirect_next_child", plum); +testSiblingInsertionRemoval("j_indirect_next_child", "parent_previous", selector, "indirect_next_child", false, grey, true, plum); +testClassChange(removeClass, "plum", "has_scope", selector, false, "indirect_next_child", grey); + +selector = ".orange:has(#descendant:not(.m:not(.n) .o))"; +testClassChange(addClass, "orange", "has_scope", selector, true, "has_scope", orange); +testClassChange(addClass, "m", "parent", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "parent", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "parent", selector, false, "has_scope", grey); +testClassChange(removeClass, "m", "parent", selector, true, "has_scope", orange); +testClassChange(addClass, "m", "has_scope", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "has_scope", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "has_scope", selector, false, "has_scope", grey); +testClassChange(removeClass, "m", "has_scope", selector, true, "has_scope", orange); +testClassChange(addClass, "m", "child", selector, false, "has_scope", grey); +testClassChange(addClass, "n", "child", selector, true, "has_scope", orange); +testClassChange(removeClass, "n", "child", selector, false, "has_scope", grey); +testClassChange(removeClass, "m", "child", selector, true, "has_scope", orange); +testClassChange(removeClass, "orange", "has_scope", selector, false, "has_scope", grey); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html b/testing/web-platform/tests/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html new file mode 100644 index 0000000000..3d7be98237 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/quirks-mode-stylesheet-dynamic-add-001.html @@ -0,0 +1,27 @@ +<!-- Quirks mode --> +<meta charset="utf-8"> +<title>Invalidation of style due to a dynamic stylesheet change in quirks mode</title> +<link rel="help" href="https://html.spec.whatwg.org/#case-sensitivity-of-selectors"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1433589"> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #foo { + width: 100px; + height: 100px; + background: red; + } +</style> +Should see a green square below. +<div id="foo"></div> +<script> +test(function() { + let foo = document.getElementById('foo'); + assert_equals(getComputedStyle(foo).backgroundColor, "rgb(255, 0, 0)"); + let style = document.createElement('style'); + style.textContent = "#FoO { background: green; }"; + document.body.appendChild(style); + assert_equals(getComputedStyle(foo).backgroundColor, "rgb(0, 128, 0)"); +}, "Style should've changed to a green background"); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/selectorText-dynamic-001.html b/testing/web-platform/tests/css/selectors/invalidation/selectorText-dynamic-001.html new file mode 100644 index 0000000000..ac33eca826 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/selectorText-dynamic-001.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: invalidation of class changes when the selector in a rule has changed</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#invalid"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + body { background: green; } + .red { background: red; } +</style> +<body class="red"> +Should have a green background. +<script> +test(() => { + document.body.offsetTop; + assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)"); + document.body.className = ""; + document.styleSheets[0].cssRules[1].selectorText = ".bar"; + assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)"); +}, "Style should be recomputed correctly when the selector it depends on changes"); +</script> +</body> diff --git a/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-001.html b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-001.html new file mode 100644 index 0000000000..44e38ad81e --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-001.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#invalid"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + body { background: green; } +</style> +<style id="style"> + .red { background: red; } +</style> +<body class="red"> +Should have a green background. +<script> +test(() => { + document.body.offsetTop; + assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)"); + document.body.className = ""; + style.remove(); + assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)"); +}, "Style should be recomputed correctly when the stylesheet it depends on goes away"); +</script> +</body> diff --git a/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002-ref.html b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002-ref.html new file mode 100644 index 0000000000..6784161773 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002-ref.html @@ -0,0 +1,7 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test Reference</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<p style="color: green"> + Should be green. +</p> diff --git a/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002.html b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002.html new file mode 100644 index 0000000000..189abbe080 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002.html @@ -0,0 +1,23 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#invalid"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850"> +<link rel="match" href="sheet-going-away-002-ref.html"> +<style> + p { color: green; } +</style> +<style id="style"> + .red p { color: red; } +</style> +<body class="red"> +<p> + Should be green. +</p> +<script> +document.body.offsetTop; +document.body.className = ""; +style.remove(); +</script> +</body> diff --git a/testing/web-platform/tests/css/selectors/invalidation/sibling.html b/testing/web-platform/tests/css/selectors/invalidation/sibling.html new file mode 100644 index 0000000000..c0e04fb8b3 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/sibling.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: sibling</title> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#adjacent-sibling-combinators"> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#general-sibling-combinators"> + <meta name="assert" content="This tests that the + next-sibling selector is effective"> + <meta name="assert" content="This tests that the ~ subsequent-sibling selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + +* { background-color: inherit; } + +body { background-color: rgba(0, 0, 0, 0); } + +.t1 .sibling + *, +.t2 .sibling ~ *, +.t3 + .sibling + *, +.t4 + .sibling, +.t5 + *, +.t6 ~ .sibling, +.t7 + * + * .child { background-color: rgb(0, 128, 0); } + + </style> + </head> + <body> + +<div> + <div id="t1"> + <div class="sibling"></div> + <div id="r1"></div> + <div id="u1"></div> + </div> +</div> +<div> + <div id="t2"> + <div class="sibling"></div> + <div></div> + <div id="r2"></div> + </div> +</div> +<div> + <div id="t3"></div> + <div class="sibling"></div> + <div id="r3"></div> +</div> +<div> + <div id="t4"></div> + <div id="r4" class="sibling"></div> + <div id="u4" class="sibling"></div> +</div> +<div> + <div id="t5"></div> + <div id="r5"></div> + <div id="u5"></div> +</div> +<div> + <div id="t6"></div> + <div></div> + <div id="r6" class="sibling"> + <div id="r6b"></div> + </div> + <div id="u6"></div> +</div> +<div> + <div id="t7"> + <div class="child"></div> + </div> + <div></div> + <div> + <div id="r7" class="child"></div> + </div> + <div> + <div id="u7" class="child"></div> + </div> +</div> + + <script> + +test(function() { + assert_equals(getComputedStyle(r1).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t1.className = "t1"; + assert_equals(getComputedStyle(r1).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); + assert_equals(getComputedStyle(u1).backgroundColor, "rgba(0, 0, 0, 0)", "Background color remains transparent"); +}, "Adjacent with universal selector"); + +test(function() { + assert_equals(getComputedStyle(r2).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t2.className = "t2"; + assert_equals(getComputedStyle(r2).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); +}, "Indirect adjacent with universal selector"); + +test(function() { + assert_equals(getComputedStyle(r3).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t3.className = "t3"; + assert_equals(getComputedStyle(r3).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); +}, "Indirect adjacent with two adjacent selectors"); + +test(function() { + assert_equals(getComputedStyle(r4).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t4.className = "t4"; + assert_equals(getComputedStyle(r4).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); + assert_equals(getComputedStyle(u4).backgroundColor, "rgba(0, 0, 0, 0)", "Background color remains transparent"); +}, "Adjacent class"); + +test(function() { + assert_equals(getComputedStyle(r5).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t5.className = "t5"; + assert_equals(getComputedStyle(r5).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); + assert_equals(getComputedStyle(u5).backgroundColor, "rgba(0, 0, 0, 0)", "Background color remains transparent"); +}, "Adjacent universal"); + +test(function() { + assert_equals(getComputedStyle(r6).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + assert_equals(getComputedStyle(r6b).backgroundColor, "rgba(0, 0, 0, 0)", "Child's background color should initially be transparent"); + + t6.className = "t6"; + assert_equals(getComputedStyle(r6).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); + assert_equals(getComputedStyle(r6b).backgroundColor, "rgb(0, 128, 0)", "Child's background color is green after class change"); + assert_equals(getComputedStyle(u6).backgroundColor, "rgba(0, 0, 0, 0)", "Background color remains transparent"); +}, "Sibling subtree through an indirect adjacent combinator"); + +test(function() { + assert_equals(getComputedStyle(r7).backgroundColor, "rgba(0, 0, 0, 0)", "Background color should initially be transparent"); + + t7.className = "t7"; + assert_equals(getComputedStyle(r7).backgroundColor, "rgb(0, 128, 0)", "Background color is green after class change"); + assert_equals(getComputedStyle(u7).backgroundColor, "rgba(0, 0, 0, 0)", "Background color remains transparent"); +}, "Sibling descendant through a universal selector"); + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/selectors/invalidation/subject-has-invalidation-with-display-none-anchor-element.html b/testing/web-platform/tests/css/selectors/invalidation/subject-has-invalidation-with-display-none-anchor-element.html new file mode 100644 index 0000000000..6c87560c69 --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/subject-has-invalidation-with-display-none-anchor-element.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: subject :has() invalidation with display: none anchor element</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #target:has(~ input:checked) { display: none; } +</style> +<p>Click checkbox</p> +<div id="target">PASS</div> +<input type="checkbox" id="checkme"> +<label for="checkme">Check me!</label> +<script> + test(function() { + checkme.checked = false; + assert_equals(getComputedStyle(target).display, "block", + "target display should be empty"); + + checkme.checked = true; + assert_equals(getComputedStyle(target).display, "none", + "target display should be none"); + + checkme.checked = false; + assert_equals(getComputedStyle(target).display, "block", + "target display should be empty again"); + }); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/target-pseudo-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/target-pseudo-in-has.html new file mode 100644 index 0000000000..629a6a826b --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/target-pseudo-in-has.html @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: :target pseudo-class in :has() argument</title> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #parent1 { color: grey; } + #parent1:has(:target) { color: green; } + + #parent2 { color: blue; } + #parent2:has(:not(:target)) { color: grey; } + #parent2:has(:target) { color: green; } + + #parent3 { color: green; } + #parent3:not(:has(:target)) { color: grey; } +</style> +<a href="#fragment">link to #fragment</a> +<a href="#fragment2">link to #fragment2</a> +<a href="#fragment3">link to #fragment3</a> +<a href="#">link to #</a> +<div id="parent1"> + 1: + <span id="fragment">Must be green when containing :target</span> +</div> +<div id="parent2"> + 2: + <span id="fragment2">Must be green when containing :target</span> +</div> +<div id="parent3"> + 3: + <span id="fragment3">Must be green when containing :target</span> +</div> +<script> + const GREEN = "rgb(0, 128, 0)"; + const GREY = "rgb(128, 128, 128)"; + const BLUE = "rgb(0, 0, 255)"; + + function fragmentLink(fragment) { + return document.querySelector(`a[href="#${fragment}"]`); + } + + promise_test(async () => { + const fragment = document.querySelector("#fragment"); + const fragment2 = document.querySelector("#fragment2"); + const fragment3 = document.querySelector("#fragment3"); + + location.hash = ""; + + assert_equals(getComputedStyle(parent1).color, GREY, "parent1 should be grey without :target"); + assert_equals(getComputedStyle(parent2).color, GREY, "parent2 should be grey without :target"); + assert_equals(getComputedStyle(parent3).color, GREY, "parent3 should be grey without :target"); + + fragmentLink("fragment").click(); + + assert_true(fragment.matches(":target")); + assert_equals(getComputedStyle(parent1).color, GREEN, "parent1 should be green on fragment click"); + assert_equals(getComputedStyle(parent2).color, GREY, "parent2 should be grey without :target"); + assert_equals(getComputedStyle(parent3).color, GREY, "parent3 should be grey without :target"); + + fragmentLink("fragment2").click(); + + assert_true(fragment2.matches(":target")); + assert_equals(getComputedStyle(parent1).color, GREY, "parent1 should be grey without :target"); + assert_equals(getComputedStyle(parent2).color, GREEN, "parent2 should be green on fragment click"); + assert_equals(getComputedStyle(parent3).color, GREY, "parent3 should be grey without :target"); + + fragment2.remove(); + assert_equals(getComputedStyle(parent2).color, BLUE, "parent2 should be blue after removing only child"); + + parent2.append(fragment2); + + // Skip to check parent2 color since there is nothing in the spec mentioning DOM mutations affecting the target element. + // - https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-fragid + + fragmentLink("fragment3").click(); + + assert_true(fragment3.matches(":target")); + assert_equals(getComputedStyle(parent1).color, GREY, "parent1 should be grey without :target"); + assert_equals(getComputedStyle(parent2).color, GREY, "parent2 should be grey without :target"); + assert_equals(getComputedStyle(parent3).color, GREEN, "parent3 should be green on fragment click"); + + fragmentLink("").click(); + + assert_equals(location.hash, ""); + assert_equals(getComputedStyle(parent1).color, GREY, "parent1 should be grey without :target"); + assert_equals(getComputedStyle(parent2).color, GREY, "parent2 should be grey without :target"); + assert_equals(getComputedStyle(parent3).color, GREY, "parent3 should be grey without :target"); + }); +</script> diff --git a/testing/web-platform/tests/css/selectors/invalidation/typed-child-indexed-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/typed-child-indexed-pseudo-classes-in-has.html new file mode 100644 index 0000000000..b3a91d67cb --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/typed-child-indexed-pseudo-classes-in-has.html @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: typed child-indexed pseudo classes in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #container ~ div { color: grey } + #container:has(div:only-of-type) ~ #only_of_type { color: red } + #container:has(div.orange:first-of-type) ~ #first_of_type { color: orange } + #container:has(div.yellow:first-of-type) ~ #first_of_type { color: yellow } + #container:has(div.green:first-of-type) ~ #first_of_type { color: green } + #container:has(div.orange:last-of-type) ~ #last_of_type { color: orange } + #container:has(div.yellow:last-of-type) ~ #last_of_type { color: yellow } + #container:has(div.green:last-of-type) ~ #last_of_type { color: green } + #container:has(div.orange:nth-of-type(3n+1)) ~ #nth_of_type_3n_1 { color: orange } + #container:has(div.yellow:nth-of-type(3n+1)) ~ #nth_of_type_3n_1 { color: yellow } + #container:has(div.green:nth-of-type(3n+1)) ~ #nth_of_type_3n_1 { color: green } + #container:has(div.orange:nth-of-type(3n+2)) ~ #nth_of_type_3n_2 { color: orange } + #container:has(div.yellow:nth-of-type(3n+2)) ~ #nth_of_type_3n_2 { color: yellow } + #container:has(div.green:nth-of-type(3n+2)) ~ #nth_of_type_3n_2 { color: green } + #container:has(div.orange:nth-of-type(3n)) ~ #nth_of_type_3n { color: orange } + #container:has(div.yellow:nth-of-type(3n)) ~ #nth_of_type_3n { color: yellow } + #container:has(div.green:nth-of-type(3n)) ~ #nth_of_type_3n { color: green } +</style> +<div id="container"> +</div> +<div id="only_of_type"></div> +<div id="first_of_type"></div> +<div id="last_of_type"></div> +<div id="nth_of_type_3n_1"></div> +<div id="nth_of_type_3n_2"></div> +<div id="nth_of_type_3n"></div> +<script> +const grey = "rgb(128, 128, 128)"; +const red = "rgb(255, 0, 0)"; +const orange = "rgb(255, 165, 0)"; +const yellow = "rgb(255, 255, 0)"; +const green = "rgb(0, 128, 0)"; + +function testColors(test_name, + only_of_type_color, + first_of_type_color, + last_of_type_color, + nth_of_type_3n_1_color, + nth_of_type_3n_2_color, + nth_of_type_3n_color) { + test(function() { + assert_equals(getComputedStyle(only_of_type).color, only_of_type_color); + }, test_name + ": #only_of_type"); + test(function() { + assert_equals(getComputedStyle(first_of_type).color, first_of_type_color); + }, test_name + ": #first_of_type"); + test(function() { + assert_equals(getComputedStyle(last_of_type).color, last_of_type_color); + }, test_name + ": #last_of_type"); + test(function() { + assert_equals(getComputedStyle(nth_of_type_3n_1).color, nth_of_type_3n_1_color); + }, test_name + ": #nth_of_type_3n_1"); + test(function() { + assert_equals(getComputedStyle(nth_of_type_3n_2).color, nth_of_type_3n_2_color); + }, test_name + ": #nth_of_type_3n_2"); + test(function() { + assert_equals(getComputedStyle(nth_of_type_3n).color, nth_of_type_3n_color); + }, test_name + ": #nth_of_type_3n"); +} + +testColors("Initial colors", grey, grey, grey, grey, grey, grey); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (1)", grey, grey, grey, grey, grey, grey); + +let div1 = document.createElement("div"); +div1.id = "div1"; +div1.classList.add("green"); +container.insertBefore(div1, container.firstChild); +testColors("Prepend #div1.green", red, green, green, green, grey, grey); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (2)", red, green, green, green, grey, grey); + +let div2 = document.createElement("div"); +div2.id = "div2"; +div2.classList.add("yellow"); +container.insertBefore(div2, container.firstChild); +testColors("Prepend #div2.yellow", grey, yellow, green, yellow, green, grey); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (3)", grey, yellow, green, yellow, green, grey); + +let div3 = document.createElement("div"); +div3.id = "div3"; +div3.classList.add("orange"); +container.insertBefore(div3, container.firstChild); +testColors("Prepend #div3.orange", grey, orange, green, orange, yellow, green); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (4)", grey, orange, green, orange, yellow, green); + +let div4 = document.createElement("div"); +div4.id = "div4"; +container.insertBefore(div4, container.firstChild); +testColors("Prepend #div4", grey, grey, green, green, orange, yellow); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (5)", grey, grey, green, green, orange, yellow); + +let div5 = document.createElement("div"); +div5.id = "div5"; +container.insertBefore(div5, container.firstChild); +testColors("Prepend #div5", grey, grey, green, yellow, green, orange); + +container.insertBefore(document.createElement("span"), container.firstChild); +testColors("Prepend span (6)", grey, grey, green, yellow, green, orange); + +div1.remove(); +testColors("Remove #div1", grey, grey, yellow, yellow, grey, orange); + +div2.remove(); +testColors("Remove #div2", grey, grey, orange, grey, grey, orange); + +div3.remove(); +testColors("Remove #div3", grey, grey, grey, grey, grey, grey); + +div4.remove(); +testColors("Remove #div4", red, grey, grey, grey, grey, grey); + +div5.remove(); +testColors("Remove #div5", grey, grey, grey, grey, grey, grey); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/user-action-pseudo-classes-in-has.html b/testing/web-platform/tests/css/selectors/invalidation/user-action-pseudo-classes-in-has.html new file mode 100644 index 0000000000..466e8610fd --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/user-action-pseudo-classes-in-has.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>CSS Selectors Invalidation: user-action pseudo classes in :has() argument</title> +<link rel="author" title="Byungwoo Lee" href="blee@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/selectors/#relational"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<style> + .ancestor:has(.descendant1:hover) { color: blue } + .ancestor:has(.descendant1:hover) .other-descendant { color: navy } + .ancestor:has(.descendant1:hover:active) { color: skyblue } + .ancestor:has(.descendant1:hover:active) .other-descendant { color: lightblue } + .ancestor:has(:focus) { color: green } + .ancestor:has(:focus) .other-descendant { color: darkgreen } + .ancestor:has(.descendant2:focus-visible) { color: yellowgreen } + .ancestor:has(.descendant2:focus-visible) .other-descendant { color: greenyellow } + .ancestor:has(.descendant3:focus-within) { color: lightgreen } + .ancestor:has(.descendant3:focus-within) .other-descendant { color: violet } +</style> +<div id=subject1 class=ancestor> + <div> + <div id=hoverme class=descendant1>Hover and click me</div> + <div id=focusme1 tabindex=1>Focus me</div> + <div id=focusme2 class=descendant2 tabindex=2>Focus me</div> + <div class=descendant3> + <div><div id=focusme3 tabindex=3>Focus me</div></div> + </div> + </div> + <div><div id=subject3 class=other-descendant>subject</div></div> +</div> +<div id=subject2 class=ancestor> + <div id=focusme4 tabindex=4>Focus me</div> + <div><div id=subject4 class=other-descendant>subject</div></div> +</div> +<script> + const tab_key = '\ue004'; + + async_test(function(t) { + hoverme.addEventListener("mouseover", t.step_func(event => { + assert_equals(getComputedStyle(subject1).color, "rgb(0, 0, 255)", + "subject1 should be blue"); + assert_equals(getComputedStyle(subject3).color, "rgb(0, 0, 128)", + "subject3 should be navy"); + })); + hoverme.addEventListener("mousedown", t.step_func(event => { + assert_equals(getComputedStyle(subject1).color, "rgb(135, 206, 235)", + "subject1 should be skyblue"); + assert_equals(getComputedStyle(subject3).color, "rgb(173, 216, 230)", + "subject3 should be lightblue"); + })); + hoverme.addEventListener("mouseup", t.step_func(event => { + assert_equals(getComputedStyle(subject1).color, "rgb(0, 0, 255)", + "subject1 should be blue again"); + assert_equals(getComputedStyle(subject3).color, "rgb(0, 0, 128)", + "subject3 should be navy again"); + })); + focusme1.addEventListener("focus", t.step_func(function() { + assert_equals(getComputedStyle(subject1).color, "rgb(0, 128, 0)", + "subject1 should be green"); + assert_equals(getComputedStyle(subject3).color, "rgb(0, 100, 0)", + "subject3 should be darkgreen"); + test_driver.send_keys(document.body, tab_key); + })); + focusme2.addEventListener("focus", t.step_func(function() { + assert_equals(getComputedStyle(subject1).color, "rgb(154, 205, 50)", + "subject1 should be yellowgreen"); + assert_equals(getComputedStyle(subject3).color, "rgb(173, 255, 47)", + "subject3 should be greenyellow"); + test_driver.send_keys(document.body, tab_key); + })); + focusme3.addEventListener("focus", t.step_func(function() { + assert_equals(getComputedStyle(subject1).color, "rgb(144, 238, 144)", + "subject1 should be lightgreen"); + assert_equals(getComputedStyle(subject3).color, "rgb(238, 130, 238)", + "subject3 should be violet"); + + focusme3.remove(); + assert_equals(getComputedStyle(subject1).color, "rgb(0, 0, 0)", + "subject1 should be black"); + assert_equals(getComputedStyle(subject3).color, "rgb(0, 0, 0)", + "subject3 should be black"); + + test_driver.send_keys(document.body, tab_key); + })); + focusme4.addEventListener("focus", t.step_func_done(function() { + assert_equals(getComputedStyle(subject2).color, "rgb(0, 128, 0)", + "subject2 should be green"); + assert_equals(getComputedStyle(subject4).color, "rgb(0, 100, 0)", + "subject4 should be darkgreen"); + + focusme4.remove(); + assert_equals(getComputedStyle(subject2).color, "rgb(0, 0, 0)", + "subject2 should be black"); + assert_equals(getComputedStyle(subject4).color, "rgb(0, 0, 0)", + "subject4 should be black"); + })); + }, "Invalidation with :focus, :focus-within, :focus-visible in :has()"); + + test(() => { + assert_equals(getComputedStyle(subject1).color, "rgb(0, 0, 0)"); + assert_equals(getComputedStyle(subject2).color, "rgb(0, 0, 0)"); + }, "ancestor should be black"); + + new test_driver.Actions() + .pointerMove(0, 0, {origin: hoverme}) + .pointerDown() + .pointerUp() + .pointerMove(0, 0, {origin: focusme1}) + .pointerDown() + .pointerUp() + .send(); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/selectors/invalidation/where.html b/testing/web-platform/tests/css/selectors/invalidation/where.html new file mode 100644 index 0000000000..170398300f --- /dev/null +++ b/testing/web-platform/tests/css/selectors/invalidation/where.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<html> + <head> + <title>CSS Selectors Invalidation: :where()</title> + <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com"> + <link rel="help" href="https://drafts.csswg.org/selectors-4/#zero-matches"> + <meta name="assert" content="This tests that the :where() selector is effective"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + * { + color: black; + } + .b { + color: yellow; + } + /*Simple selector arguments */ + :where(.b, .c) { + color: red; + } + /*Compound selector arguments */ + .a~:where(.c#d, .e) { + color: green; + } + /* Complex selector arguments */ + .h { + color: red; + } + :where(.a~.h, .a~.h+.f) { + color: yellow; + } + /* Nested */ + :where(.a>:where(.g+.h, .b)~.i) { + color: blue; + } + :where(:is(.a~.h)) { + color: yellow; + } + </style> + </head> + <body> + <div id="a1"> + <div class="g"> + </div> + <div class="h"> + </div> + <div class="i" id="i1"> + Blue + </div> + </div> + <div class="b" id="b1"> + Yellow + </div> + <div class="c" id="c1"> + Red + </div> + <div class="c" id="d"> + Green + </div> + <div class="h" id="h1"> + Red + </div> + <div class="f" id="f1"> + Yellow + </div> + <script> + document.body.offsetTop; + + var black = "rgb(0, 0, 0)"; + var green = "rgb(0, 128, 0)"; + var red = "rgb(255, 0, 0)"; + var yellow = "rgb(255, 255, 0)"; + var blue = "rgb(0, 0, 255)"; + + test(() => { + assert_equals(getComputedStyle(b1).color, yellow); + assert_equals(getComputedStyle(c1).color, red); + assert_equals(getComputedStyle(d).color, red); + assert_equals(getComputedStyle(h1).color, red); + assert_equals(getComputedStyle(f1).color, black); + assert_equals(getComputedStyle(i1).color, black); + }, "Preconditions."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(b1).color, yellow); + assert_equals(getComputedStyle(c1).color, red); + }, "Invalidate :where() for simple selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(d).color, green); + }, "Invalidate :where() for compound selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(h1).color, red); + assert_equals(getComputedStyle(f1).color, yellow); + }, "Invalidate :where() for complex selector arguments."); + + test(() => { + a1.className = "a"; + assert_equals(getComputedStyle(i1).color, blue); + }, "Invalidate nested :where()."); + + </script> + </body> +</html>
\ No newline at end of file |