<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Selector Invalidation: :has() in ancestor position</title>
<link rel="author" title="Antti Koivisto" href="mailto:[email protected]">
<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>