Commit 698073f9 authored by Meredith Lane's avatar Meredith Lane Committed by Commit Bot

[AOM] Element Reflection tests for scoping.

Adds in extra test cases relating to moving elements in and out of scope.
This behaviour is still under discussion, so is likely to change in
future as the API is finalized.

Bug=981423

Change-Id: I5863b1f8d7ab1b6287af1d95e859945af38fea60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1816496
Commit-Queue: Meredith Lane <meredithl@chromium.org>
Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Reviewed-by: default avatarAlice Boxhall <aboxhall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713347}
parent 8e7f4fc5
......@@ -425,6 +425,11 @@ HeapVector<Member<Element>>* GetExplicitlySetElementsForAttr(
bool ElementIsDescendantOfShadowIncludingAncestor(
const Element& attribute_element,
const Element& candidate) {
// TODO(meredithl): Update this to allow setting relationships for elements
// outside of the DOM once the spec is finalized. For consistency and
// simplicity, for now it is disallowed.
if (!attribute_element.IsInTreeScope() || !candidate.IsInTreeScope())
return false;
ShadowRoot* nearest_root = attribute_element.ContainingShadowRoot();
const Element* shadow_host = &attribute_element;
while (nearest_root) {
......@@ -757,20 +762,35 @@ void Element::SetElementArrayAttribute(
SpaceSplitString value;
for (auto element : given_elements) {
// Elements that are not descendants of this element's shadow including
// ancestors are dropped.
if (!ElementIsDescendantOfShadowIncludingAncestor(*this, *element))
continue;
// If |value| is null, this means a previous element must have been invalid,
// and the content attribute should reflect the empty string, so we don't
// continue trying to compute it.
if (value.IsNull() && !elements->IsEmpty()) {
elements->push_back(element);
continue;
}
elements->push_back(element);
const AtomicString given_element_id = element->GetIdAttribute();
// We compute the content attribute string as a space separated string of
// the given |element| ids. Every |element| in |given_elements| must have an
// id, must be in the same tree scope and must be the first id in tree order
// with that id, otherwise the content attribute should reflect the empty
// string.
if (given_element_id.IsNull() ||
GetTreeScope() != element->GetTreeScope() ||
GetTreeScope().getElementById(given_element_id) != element) {
value.Clear();
continue;
}
// Whitespace between elements is added when the string is serialized.
value.Add(given_element_id);
}
......
......@@ -8,118 +8,109 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<div id="activedescendant" aria-activedescendant="x"></div>
<div id="parent-listbox" role="listbox" aria-activedescendant="i1">
<div id="parentListbox" role="listbox" aria-activedescendant="i1">
<div role="option" id="i1">Item 1</div>
<div role="option" id="i2">Item 2</div>
</div>
<script>
test(function(t) {
const ancestor = document.getElementById("parent-listbox");
assert_equals(activedescendant.ariaActiveDescendantElement, null,
"invalid ID for relationship returns null");
const descendant1 = document.getElementById("i1");
const descendant2 = document.getElementById("i2");
// Element reference should be set if the content attribute was included.
assert_equals(ancestor.getAttribute("aria-activedescendant"), "i1", "check content attribute after parsing.");
assert_equals(ancestor.ariaActiveDescendantElement, i1, "check idl attribute after parsing.");
assert_equals(parentListbox.getAttribute("aria-activedescendant"), "i1", "check content attribute after parsing.");
assert_equals(parentListbox.ariaActiveDescendantElement, i1, "check idl attribute after parsing.");
// If we set the content attribute, the element reference should reflect this.
ancestor.setAttribute("aria-activedescendant", "i2");
assert_equals(ancestor.ariaActiveDescendantElement, descendant2, "setting the content attribute updates the element reference.");
parentListbox.setAttribute("aria-activedescendant", "i2");
assert_equals(parentListbox.ariaActiveDescendantElement, i2, "setting the content attribute updates the element reference.");
// Setting the element reference should be reflected in the content attribute.
ancestor.ariaActiveDescendantElement = descendant1;
assert_equals(ancestor.ariaActiveDescendantElement, descendant1, "getter should return the right element reference.");
assert_equals(ancestor.getAttribute("aria-activedescendant"), "i1", "content attribute should reflect the element reference.");
parentListbox.ariaActiveDescendantElement = i1;
assert_equals(parentListbox.ariaActiveDescendantElement, i1, "getter should return the right element reference.");
assert_equals(parentListbox.getAttribute("aria-activedescendant"), "i1", "content attribute should reflect the element reference.");
// Both content and IDL attribute should be nullable.
ancestor.ariaActiveDescendantElement = null;
assert_equals(ancestor.ariaActiveDescendantElement, null);
assert_false(ancestor.hasAttribute("aria-activedescendant"));
assert_equals(ancestor.getAttribute("aria-activedescendant"), null, "nullifying the idl attribute removes the content attribute.");
parentListbox.ariaActiveDescendantElement = null;
assert_equals(parentListbox.ariaActiveDescendantElement, null);
assert_false(parentListbox.hasAttribute("aria-activedescendant"));
assert_equals(parentListbox.getAttribute("aria-activedescendant"), null, "nullifying the idl attribute removes the content attribute.");
// Setting content attribute to non-existent or non compatible element should nullify the IDL attribute.
// Reset the element to an existant one.
ancestor.setAttribute("aria-activedescendant", "i1");
assert_equals(ancestor.ariaActiveDescendantElement, i1, "reset attribute.");
parentListbox.setAttribute("aria-activedescendant", "i1");
assert_equals(parentListbox.ariaActiveDescendantElement, i1, "reset attribute.");
ancestor.setAttribute("aria-activedescendant", "non-existent-element");
assert_equals(ancestor.getAttribute("aria-activedescendant"), "non-existent-element");
assert_equals(ancestor.ariaActiveDescendantElement, null,"non-DOM content attribute should null the element reference");
parentListbox.setAttribute("aria-activedescendant", "non-existent-element");
assert_equals(parentListbox.getAttribute("aria-activedescendant"), "non-existent-element");
assert_equals(parentListbox.ariaActiveDescendantElement, null,"non-DOM content attribute should null the element reference");
}, "aria-activedescendant element reflection");
</script>
<div id="parent-listbox-2" role="listbox" aria-activedescendant="option1">
<div id="parentListbox2" role="listbox" aria-activedescendant="option1">
<div role="option" id="option1">Item 1</div>
<div role="option" id="option2">Item 2</div>
</div>
<script>
test(function(t) {
const ancestor = document.getElementById("parent-listbox-2");
const option1 = document.getElementById("option1");
const option2 = document.getElementById("option2");
assert_equals(ancestor.ariaActiveDescendantElement, option1);
assert_equals(parentListbox2.ariaActiveDescendantElement, option1);
option1.removeAttribute("id");
option2.setAttribute("id", "option1");
const option2Duplicate = document.getElementById("option1");
assert_equals(option2, option2Duplicate);
assert_equals(ancestor.ariaActiveDescendantElement, option2);
assert_equals(parentListbox2.ariaActiveDescendantElement, option2);
}, "If the content attribute is set directly, the IDL attribute getter always returns the first element whose ID matches the content attribute.");
</script>
<div id="blank-id-parent" role="listbox">
<div id="blankIdParent" role="listbox">
<div role="option" id="multiple-id"></div>
<div role="option" id="multiple-id"></div>
</div>
<script>
test(function(t) {
const ancestor = document.getElementById("blank-id-parent");
// Get second child of parent. This violates the setting of a reflected element
// as it will not be the first child of the parent with that ID, which should
// result in an empty string for the content attribute.
ancestor.ariaActiveDescendantElement = ancestor.children[1];
assert_true(ancestor.hasAttribute("aria-activedescendant"));
assert_equals(ancestor.getAttribute("aria-activedescendant"), "");
assert_equals(ancestor.ariaActiveDescendantElement, ancestor.children[1]);
blankIdParent.ariaActiveDescendantElement = blankIdParent.children[1];
assert_true(blankIdParent.hasAttribute("aria-activedescendant"));
assert_equals(blankIdParent.getAttribute("aria-activedescendant"), "");
assert_equals(blankIdParent.ariaActiveDescendantElement, blankIdParent.children[1]);
}, "Setting the IDL attribute to an element which is not the first element in DOM order with its ID causes the content attribute to be an empty string");
</script>
<div id="outer-container">
<p id="light-paragraph">Hello world!</p>
<span class="shadow-host">
<div id="outerContainer">
<p id="lightParagraph">Hello world!</p>
<span id="shadowHost">
</span>
</div>
<script>
test(function(t) {
const shadowElement = document.querySelector(".shadow-host");
const shadow = shadowElement.attachShadow({mode: "open"});
const shadow = shadowHost.attachShadow({mode: "open"});
const link = document.createElement("a");
shadow.appendChild(link);
const lightPara = document.getElementById("light-paragraph");
assert_equals(lightPara.ariaActiveDescendantElement, null);
assert_equals(lightParagraph.ariaActiveDescendantElement, null);
// The given element crosses a shadow dom boundary, so it cannot be
// set as an element reference.
lightPara.ariaActiveDescendantElement = link;
assert_equals(lightPara.ariaActiveDescendantElement, null);
lightParagraph.ariaActiveDescendantElement = link;
assert_equals(lightParagraph.ariaActiveDescendantElement, null);
// The given element crosses a shadow dom boundary (upwards), so
// can be used as an element reference, but the content attribute
// should reflect the empty string.
link.ariaActiveDescendantElement = lightPara;
assert_equals(link.ariaActiveDescendantElement, lightPara);
link.ariaActiveDescendantElement = lightParagraph;
assert_equals(link.ariaActiveDescendantElement, lightParagraph);
assert_equals(link.getAttribute("aria-activedescendant"), "");
}, "Setting an element reference that crosses into a shadow tree is disallowed, but setting one that is in a shadow inclusive ancestor is allowed.");
</script>
......@@ -129,18 +120,15 @@
<script>
test(function(t) {
const inputElement = document.getElementById("startTime");
const errorMessage = document.getElementById("errorMessage");
startTime.ariaErrorMessageElement = errorMessage;
assert_equals(startTime.getAttribute("aria-errormessage"), "errorMessage");
inputElement.ariaErrorMessageElement = errorMessage;
assert_equals(inputElement.getAttribute("aria-errormessage"), "errorMessage");
startTime.ariaErrorMessageElement = null;
assert_equals(startTime.ariaErrorMessageElement, null, "blah");
assert_false(startTime.hasAttribute("aria-errormessage"));
inputElement.ariaErrorMessageElement = null;
assert_equals(inputElement.ariaErrorMessageElement, null, "blah");
assert_false(inputElement.hasAttribute("aria-errormessage"));
inputElement.setAttribute("aria-errormessage", "errorMessage");
assert_equals(inputElement.ariaErrorMessageElement, errorMessage);
startTime.setAttribute("aria-errormessage", "errorMessage");
assert_equals(startTime.ariaErrorMessageElement, errorMessage);
}, "aria-errormessage");
......@@ -148,74 +136,62 @@
<label>
Password:
<input id="password-field" type="password" aria-details="pw">
<input id="passwordField" type="password" aria-details="pw">
</label>
<ul>
<li id="list-item-1">First description.</li>
<li id="list-item-2">Second description.</li>
<li id="listItem1">First description.</li>
<li id="listItem2">Second description.</li>
</ul>
<script>
test(function(t) {
const inputElement = document.getElementById("password-field");
const ul1 = document.getElementById("list-item-1");
const ul2 = document.getElementById("list-item-2");
assert_equals(inputElement.ariaDetailsElement, null);
inputElement.ariaDetailsElement = ul1;
assert_equals(inputElement.getAttribute("aria-details"), "list-item-1");
assert_equals(passwordField.ariaDetailsElement, null);
passwordField.ariaDetailsElement = listItem1;
assert_equals(passwordField.getAttribute("aria-details"), "listItem1");
inputElement.ariaDetailsElement = ul2;
assert_equals(inputElement.getAttribute("aria-details"), "list-item-2");
passwordField.ariaDetailsElement = listItem2;
assert_equals(passwordField.getAttribute("aria-details"), "listItem2");
}, "aria-details");
</script>
<div id="old-parent" role="listbox" aria-activedescendant="content-attr-element">
<div role="option" id="content-attr-element">Item 1</div>
<div role="option" id="idl-attr-element">Item 2</div>
<div id="deletionParent" role="listbox" aria-activedescendant="contentAttrElement">
<div role="option" id="contentAttrElement">Item 1</div>
<div role="option" id="idlAttrElement">Item 2</div>
</div>
<script>
test(function(t) {
const deletionParent = document.getElementById("old-parent");
const descendant1 = document.getElementById("content-attr-element");
const descendant2 = document.getElementById("idl-attr-element");
// Deleting an element set via the content attribute.
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "content-attr-element");
assert_equals(deletionParent.ariaActiveDescendantElement, descendant1);
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "contentAttrElement");
assert_equals(deletionParent.ariaActiveDescendantElement, contentAttrElement);
deletionParent.removeChild(descendant1);
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "content-attr-element");
deletionParent.removeChild(contentAttrElement);
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "contentAttrElement");
assert_equals(deletionParent.ariaActiveDescendantElement, null);
// Deleting an element set via the IDL attribute.
deletionParent.ariaActiveDescendantElement = descendant2;
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "idl-attr-element");
deletionParent.ariaActiveDescendantElement = idlAttrElement;
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "idlAttrElement");
deletionParent.removeChild(descendant2);
deletionParent.removeChild(idlAttrElement);
assert_equals(deletionParent.ariaActiveDescendantElement, null);
// The content attribute will still reflect the id.
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "idl-attr-element");
assert_equals(deletionParent.getAttribute("aria-activedescendant"), "idlAttrElement");
}, "Deleting a reflected element should return null for the IDL attribute and cause the content attribute to become stale.");
</script>
<div id="parent" role="listbox" aria-activedescendant="original">
<div role="option" id="original">Item 1</div>
<div role="option" id="element-with-persistant-id">Item 2</div>
<div id="parentNode" role="listbox" aria-activedescendant="changingIdElement">
<div role="option" id="changingIdElement">Item 1</div>
<div role="option" id="persistantIDElement">Item 2</div>
</div>
<script>
test(function(t) {
const parentNode = document.getElementById("parent");
const changingIdElement = document.getElementById("original");
const persistantIDElement = document.getElementById("element-with-persistant-id");
const changingIdElement = document.getElementById("changingIdElement");
assert_equals(parentNode.ariaActiveDescendantElement, changingIdElement);
// Modify the id attribute.
......@@ -223,7 +199,7 @@
// The content attribute still reflects the old id, and we expect the
// Element reference to be null as there is no DOM node with id "original"
assert_equals(parentNode.getAttribute("aria-activedescendant"), "original");
assert_equals(parentNode.getAttribute("aria-activedescendant"), "changingIdElement");
assert_equals(parentNode.ariaActiveDescendantElement, null, "Element set via content attribute with a changed id will return null on getting");
parentNode.ariaActiveDescendantElement = changingIdElement;
......@@ -238,57 +214,48 @@
}, "Changing the ID of an element causes the content attribute to become out of sync.");
</script>
<div id="light-parent" role="listbox">
<div role="option" id="light-element">Hello world!</div>
<div id="lightParent" role="listbox">
<div role="option" id="lightElement">Hello world!</div>
</div>
<div id="shadowHost"></div>
<div id="shadowHostElement"></div>
<script>
test(function(t) {
const shadowElement = document.getElementById("shadowHost");
const shadowHost = shadowElement.attachShadow({mode: "open"});
const lightParent = document.getElementById("light-parent");
const optionElement = document.getElementById("light-element");
const lightElement = document.getElementById("lightElement");
const shadowRoot = shadowHostElement.attachShadow({mode: "open"});
lightParent.ariaActiveDescendantElement = optionElement;
assert_equals(lightParent.ariaActiveDescendantElement, optionElement);
lightParent.ariaActiveDescendantElement = lightElement;
assert_equals(lightParent.ariaActiveDescendantElement, lightElement);
// Move the referenced element into shadow DOM.
shadowHost.appendChild(optionElement);
shadowRoot.appendChild(lightElement);
assert_equals(lightParent.ariaActiveDescendantElement, null);
assert_equals(lightParent.getAttribute("aria-activedescendant"), "light-element");
assert_equals(lightParent.getAttribute("aria-activedescendant"), "lightElement");
// Move the referenced element back into light DOM.
lightParent.appendChild(optionElement);
assert_equals(lightParent.ariaActiveDescendantElement, optionElement);
lightParent.appendChild(lightElement);
assert_equals(lightParent.ariaActiveDescendantElement, lightElement);
}, "Reparenting an element into a descendant shadow scope nullifies the element reference.");
</script>
<div id="myBillingId">Billing</div>
<div id="billingElement">Billing</div>
<div>
<div id="myNameId">Name</div>
<input type="text" id="input1" aria-labelledby="myBillingId myNameId"/>
<div id="nameElement">Name</div>
<input type="text" id="input1" aria-labelledby="billingElement nameElement"/>
</div>
<div>
<div id="myAddressId">Address</div>
<div id="addressElement">Address</div>
<input type="text" id="input2"/>
</div>
<script>
test(function(t) {
const billingElement = document.getElementById("myBillingId");
const nameElement = document.getElementById("myNameId");
const addressElement = document.getElementById("myAddressId");
const input1 = document.getElementById("input1");
const input2 = document.getElementById("input2");
assert_array_equals(input1.ariaLabelledByElements, [billingElement, nameElement], "parsed content attribute sets element references.");
assert_equals(input2.ariaLabelledByElements, null, "Testing empty content attribute after parsing.");
input2.ariaLabelledByElements = [billingElement, addressElement];
assert_array_equals(input2.ariaLabelledByElements, [billingElement, addressElement], "Testing IDL setter/getter.");
assert_equals(input2.getAttribute("aria-labelledby"), "myBillingId myAddressId");
assert_equals(input2.getAttribute("aria-labelledby"), "billingElement addressElement");
billingElement.remove();
assert_array_equals(input2.ariaLabelledByElements, [addressElement]);
......@@ -312,10 +279,6 @@
<script>
test(function(t) {
const link1 = document.getElementById("link1");
const link2 = document.getElementById("link2");
const panel1 = document.getElementById("panel1");
const panel2 = document.getElementById("panel2");
assert_array_equals(link1.ariaControlsElements, [panel1]);
assert_equals(link2.ariaControlsElements, null);
......@@ -333,60 +296,56 @@
}, "aria-controls.");
</script>
<a id="described-link" aria-describedby="description1 description2">Fruit</a>
<a id="describedLink" aria-describedby="description1 description2">Fruit</a>
<div id="description1">Delicious</div>
<div id="description2">Nutritious</div>
<script>
test(function(t) {
const link = document.getElementById("described-link");
const description1 = document.getElementById("description1");
const description2 = document.getElementById("description2");
assert_array_equals(link.ariaDescribedByElements, [description1, description2]);
assert_array_equals(describedLink.ariaDescribedByElements, [description1, description2]);
link.ariaDescribedByElements = [description1, description2];
assert_equals(link.getAttribute("aria-describedby"), "description1 description2");
describedLink.ariaDescribedByElements = [description1, description2];
assert_equals(describedLink.getAttribute("aria-describedby"), "description1 description2");
link.ariaDescribedByElements = [];
assert_equals(link.getAttribute("aria-describedby"), "");
describedLink.ariaDescribedByElements = [];
assert_equals(describedLink.getAttribute("aria-describedby"), "");
link.setAttribute("aria-describedby", "description1");
assert_array_equals(link.ariaDescribedByElements, [description1]);
describedLink.setAttribute("aria-describedby", "description1");
assert_array_equals(describedLink.ariaDescribedByElements, [description1]);
link.removeAttribute("aria-describedby");
assert_equals(link.ariaDescribedByElements, null);
describedLink.removeAttribute("aria-describedby");
assert_equals(describedLink.ariaDescribedByElements, null);
}, "aria-describedby.");
</script>
<h2 id="title-heading" aria-flowto="article1 article2">Title</h2>
<h2 id="titleHeading" aria-flowto="article1 article2">Title</h2>
<div>Next</div>
<article id="article2">Content2</article>
<article id="article1">Content1</article>
<script>
test(function(t) {
const heading = document.getElementById("title-heading");
const article1 = document.getElementById("article1");
const article2 = document.getElementById("article2");
assert_array_equals(heading.ariaFlowToElements, [article1, article2]);
assert_array_equals(titleHeading.ariaFlowToElements, [article1, article2]);
heading.ariaFlowToElements = [article1, article2];
assert_equals(heading.getAttribute("aria-flowto"), "article1 article2");
titleHeading.ariaFlowToElements = [article1, article2];
assert_equals(titleHeading.getAttribute("aria-flowto"), "article1 article2");
heading.ariaFlowToElements = [];
assert_equals(heading.getAttribute("aria-flowto"), "");
titleHeading.ariaFlowToElements = [];
assert_equals(titleHeading.getAttribute("aria-flowto"), "");
heading.setAttribute("aria-flowto", "article1");
assert_array_equals(heading.ariaFlowToElements, [article1]);
titleHeading.setAttribute("aria-flowto", "article1");
assert_array_equals(titleHeading.ariaFlowToElements, [article1]);
heading.removeAttribute("aria-flowto");
assert_equals(heading.ariaFlowToElements, null);
titleHeading.removeAttribute("aria-flowto");
assert_equals(titleHeading.ariaFlowToElements, null);
}, "aria-flowto.");
</script>
<ul>
<li id="li-owner" aria-owns="child1 child2">Parent</li>
<li id="listItemOwner" aria-owns="child1 child2">Parent</li>
</ul>
<ul>
<li id="child1">Child 1</li>
......@@ -394,51 +353,41 @@
</ul>
<script>
test(function(t) {
const owner = document.getElementById("li-owner");
const child1 = document.getElementById("child1");
const child2 = document.getElementById("child2");
assert_array_equals(owner.ariaOwnsElements, [child1, child2]);
assert_array_equals(listItemOwner.ariaOwnsElements, [child1, child2]);
owner.removeAttribute("aria-owns");
assert_equals(owner.ariaOwnsElements, null);
listItemOwner.removeAttribute("aria-owns");
assert_equals(listItemOwner.ariaOwnsElements, null);
owner.ariaOwnsElements = [child1, child2];
assert_equals(owner.getAttribute("aria-owns"), "child1 child2");
listItemOwner.ariaOwnsElements = [child1, child2];
assert_equals(listItemOwner.getAttribute("aria-owns"), "child1 child2");
owner.ariaOwnsElements = [];
assert_equals(owner.getAttribute("aria-owns"), "");
listItemOwner.ariaOwnsElements = [];
assert_equals(listItemOwner.getAttribute("aria-owns"), "");
owner.setAttribute("aria-owns", "child1");
assert_array_equals(owner.ariaOwnsElements, [child1]);
listItemOwner.setAttribute("aria-owns", "child1");
assert_array_equals(listItemOwner.ariaOwnsElements, [child1]);
}, "aria-owns.");
</script>
<div id="light-dom-container">
<h2 id="light-dom-heading" aria-flowto="shadow-child-1 shadow-child-2">Light DOM Heading</h2>
<div id="shadow-dom-host"></div>
<p id="light-dom-text-1">Light DOM text</p>
<p id="light-dom-text-2">Light DOM text</p>
<div id="lightDomContainer">
<h2 id="lightDomHeading" aria-flowto="shadowChild1 shadowChild2">Light DOM Heading</h2>
<div id="host"></div>
<p id="lightDomText1">Light DOM text</p>
<p id="lightDomText2">Light DOM text</p>
</div>
<script>
test(function(t) {
const shadowHost = document.getElementById("shadow-dom-host");
const lightDomHeading = document.getElementById("light-dom-heading");
const lightDomText1 = document.getElementById("light-dom-text-1");
const lightDomText2 = document.getElementById("light-dom-text-2");
const shadowRoot = shadowHost.attachShadow({mode: "open"});
const shadowRoot = host.attachShadow({mode: "open"});
const shadowChild1 = document.createElement("article");
shadowChild1.setAttribute("id", "shadow-child-1");
shadowChild1.setAttribute("id", "shadowChild1");
shadowRoot.appendChild(shadowChild1);
const shadowChild2 = document.createElement("article");
shadowChild2.setAttribute("id", "shadow-child-1");
shadowChild2.setAttribute("id", "shadowChild1");
shadowRoot.appendChild(shadowChild2);
// The elements in the content attribute are in a "darker" tree - they
// enter a shadow encapsulation boundary, so should reflect null for
// the content attribute.
// enter a shadow encapsulation boundary, so not be associated any more.
assert_equals(lightDomHeading.ariaFlowToElements, null);
// These elements are in a shadow including ancestor, i.e "lighter" tree.
......@@ -448,14 +397,14 @@
// These IDs belong to a different scope, so the attr-associated-element
// cannot be computed.
shadowChild2.setAttribute("aria-flowto", "light-dom-text-1 light-dom-text-2");
shadowChild2.setAttribute("aria-flowto", "lightDomText1 lightDomText2");
assert_equals(shadowChild2.ariaFlowToElements, null);
// Elements that cross into shadow DOM are dropped, only reflect the valid
// elements in IDL and in the content attribute.
lightDomHeading.ariaFlowToElements = [shadowChild1, shadowChild2, lightDomText1, lightDomText2];
assert_array_equals(lightDomHeading.ariaFlowToElements, [lightDomText1, lightDomText2]);
assert_equals(lightDomHeading.getAttribute("aria-flowto"), "light-dom-text-1 light-dom-text-2", "empty content attribute if any given elements cross shadow boundaries");
assert_equals(lightDomHeading.getAttribute("aria-flowto"), "lightDomText1 lightDomText2", "empty content attribute if any given elements cross shadow boundaries");
// Using a mixture of elements in the same scope and in a shadow including
// ancestor should set the IDL attribute, but should reflect the empty
......@@ -466,4 +415,124 @@
}, "shadow DOM behaviour for FrozenArray element reflection.");
</script>
<div id="describedButtonContainer">
<div id="buttonDescription1">Delicious</div>
<div id="buttonDescription2">Nutritious</div>
<div id="outerShadowHost"></div>
</div>
<script>
test(function(t) {
const description1 = document.getElementById("buttonDescription1");
const description2 = document.getElementById("buttonDescription2");
const outerShadowRoot = outerShadowHost.attachShadow({mode: "open"});
const innerShadowHost = document.createElement("div");
outerShadowRoot.appendChild(innerShadowHost);
const innerShadowRoot = innerShadowHost.attachShadow({mode: "open"});
// Create an element, add some attr associated light DOM elements and append it to the outer shadow root.
const describedElement = document.createElement("button");
describedButtonContainer.appendChild(describedElement);
describedElement.ariaDescribedByElements = [description1, description2];
// All elements were in the same scope, so elements are gettable and the content attribute reflects the ids.
assert_array_equals(describedElement.ariaDescribedByElements, [description1, description2]);
assert_equals(describedElement.getAttribute("aria-describedby"), "buttonDescription1 buttonDescription2");
outerShadowRoot.appendChild(describedElement);
// Explicitly set attr-associated-elements should still be gettable because we are referencing elements in a lighter scope.
// The content attr still reflects the ids from the explicit elements because they were in a valid scope at the time of setting.
assert_array_equals(describedElement.ariaDescribedByElements, [description1, description2]);
assert_equals(describedElement.getAttribute("aria-describedby"), "buttonDescription1 buttonDescription2");
// Move the explicitly set elements into a deeper shadow DOM to test the relationship should not be gettable.
innerShadowRoot.appendChild(description1);
innerShadowRoot.appendChild(description2);
// Explicitly set elements are still present, but are not retrieved.
// content attribute is still stale because all elements were in a valid scope when they were set.
assert_array_equals(describedElement.ariaDescribedByElements, []);
assert_equals(describedElement.getAttribute("aria-describedby"), "buttonDescription1 buttonDescription2");
// Move into the same shadow scope as the explicitly set elements to test that the elements are gettable
// and reflect the correct IDs onto the content attribute.
innerShadowRoot.appendChild(describedElement);
assert_array_equals(describedElement.ariaDescribedByElements, [description1, description2]);
assert_equals(describedElement.getAttribute("aria-describedby"), "buttonDescription1 buttonDescription2");
}, "Moving explicitly set elements across shadow DOM boundaries.");
</script>
<div id="sameScopeContainer">
<div id="headingLabel1">Wonderful</div>
<div id="headingLabel2">Fantastic</div>
<div id="headingShadowHost"></div>
</div>
<script>
test(function(t) {
const shadowRoot = headingShadowHost.attachShadow({mode: "open"});
const headingElement = document.createElement("h1");
const headingLabel1 = document.getElementById("headingLabel1")
const headingLabel2 = document.getElementById("headingLabel2")
shadowRoot.appendChild(headingElement);
// Explicitly set elements are in a lighter shadow DOM, so that's ok.
headingElement.ariaLabelledByElements = [headingLabel1, headingLabel2];
assert_array_equals(headingElement.ariaLabelledByElements, [headingLabel1, headingLabel2], "Lighter elements are gettable when explicitly set.");
assert_equals(headingElement.getAttribute("aria-labelledby"), "", "Crosses shadow DOM boundary, so content attribute should be empty string.");
// Move into Light DOM, explicitly set elements should still be gettable.
// Note that the content attribute still reflects the element ids - when scope changes it becomes stale.
sameScopeContainer.appendChild(headingElement);
assert_array_equals(headingElement.ariaLabelledByElements, [headingLabel1, headingLabel2], "Elements are all in same scope, so gettable.");
assert_equals(headingElement.getAttribute("aria-labelledby"), "", "Content attribute is empty, as on setting the explicitly set elements they were in a different scope.");
// Reset the association, to update the content attribute.
headingElement.ariaLabelledByElements = [headingLabel1, headingLabel2];
assert_equals(headingElement.getAttribute("aria-labelledby"), "headingLabel1 headingLabel2", "Elements are set again, so the content attribute is updated.");
// Remove the referring element from the DOM, elements are not gettable.
// This behaviour is still under discussion.
headingElement.remove();
assert_array_equals(headingElement.ariaLabelledByElements, [], "Element is no longer in the document, so references should not be gettable.");
assert_equals(headingElement.getAttribute("aria-labelledby"), "headingLabel1 headingLabel2");
// Insert it back in.
sameScopeContainer.appendChild(headingElement);
assert_array_equals(headingElement.ariaLabelledByElements, [headingLabel1, headingLabel2]);
assert_equals(headingElement.getAttribute("aria-labelledby"), "headingLabel1 headingLabel2");
// Remove everything from the DOM, nothing should be gettable.
headingLabel1.remove();
headingLabel2.remove();
assert_array_equals(headingElement.ariaLabelledByElements, []);
assert_equals(headingElement.getAttribute("aria-labelledby"), "headingLabel1 headingLabel2");
assert_equals(document.getElementById("headingLabel1"), null);
assert_equals(document.getElementById("headingLabel2"), null);
// Reset the association to update the content attribute.
headingElement.ariaLabelledByElements = [headingLabel1, headingLabel2];
assert_array_equals(headingElement.ariaLabelledByElements, []);
assert_equals(headingElement.getAttribute("aria-labelledby"), "");
}, "Moving explicitly set elements around within the same scope, and removing from the DOM.");
</script>
<input id="input">
<optgroup>
<option id="first">First option</option>
<option id="second">Second option</option>
</optgroup>
<script>
test(function(t) {
input.ariaActiveDescendantElement = first;
first.parentElement.appendChild(first);
// This behaviour is currently under discussion by WHATWG.
// See: https://github.com/whatwg/html/pull/3917#issuecomment-527263562
assert_equals(input.ariaActiveDescendantElement, first);
}, "Reparenting.");
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment