| <!DOCTYPE HTML> |
| <html> |
| <head> |
| <meta charset="utf-8" /> |
| <title>Element Reflection for aria-activedescendant and aria-errormessage</title> |
| <link rel=help href="https://whatpr.org/html/3917/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:element"> |
| <link rel="author" title="Meredith Lane" href="meredithl@chromium.org"> |
| <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 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."); |
| |
| // 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."); |
| |
| // 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."); |
| |
| // 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."); |
| |
| // 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."); |
| |
| 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"); |
| }, "aria-activedescendant element reflection"); |
| </script> |
| |
| <div id="parent-listbox-2" 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); |
| |
| option1.removeAttribute("id"); |
| option2.setAttribute("id", "option1"); |
| const option2Duplicate = document.getElementById("option1"); |
| assert_equals(option2, option2Duplicate); |
| |
| assert_equals(ancestor.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 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]); |
| }, "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"> |
| </span> |
| </div> |
| |
| <script> |
| test(function(t) { |
| const shadowElement = document.querySelector(".shadow-host"); |
| const shadow = shadowElement.attachShadow({mode: "open"}); |
| const link = document.createElement("a"); |
| shadow.appendChild(link); |
| |
| const lightPara = document.getElementById("light-paragraph"); |
| assert_equals(lightPara.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); |
| |
| // 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); |
| 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> |
| |
| <input id="startTime" ></input> |
| <span id="errorMessage">Invalid Time</span> |
| |
| <script> |
| test(function(t) { |
| const inputElement = document.getElementById("startTime"); |
| const errorMessage = document.getElementById("errorMessage"); |
| |
| inputElement.ariaErrorMessageElement = errorMessage; |
| assert_equals(inputElement.getAttribute("aria-errormessage"), "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); |
| |
| }, "aria-errormessage"); |
| |
| </script> |
| |
| <label> |
| Password: |
| <input id="password-field" type="password" aria-details="pw"> |
| </label> |
| |
| <ul> |
| <li id="list-item-1">First description.</li> |
| <li id="list-item-2">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"); |
| |
| inputElement.ariaDetailsElement = ul2; |
| assert_equals(inputElement.getAttribute("aria-details"), "list-item-2"); |
| }, "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> |
| |
| <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); |
| |
| deletionParent.removeChild(descendant1); |
| assert_equals(deletionParent.getAttribute("aria-activedescendant"), "content-attr-element"); |
| 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.removeChild(descendant2); |
| assert_equals(deletionParent.ariaActiveDescendantElement, null); |
| |
| // The content attribute will still reflect the id. |
| assert_equals(deletionParent.getAttribute("aria-activedescendant"), "idl-attr-element"); |
| }, "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> |
| |
| <script> |
| test(function(t) { |
| const parentNode = document.getElementById("parent"); |
| const changingIdElement = document.getElementById("original"); |
| const persistantIDElement = document.getElementById("element-with-persistant-id"); |
| |
| assert_equals(parentNode.ariaActiveDescendantElement, changingIdElement); |
| |
| // Modify the id attribute. |
| changingIdElement.setAttribute("id", "new-id"); |
| |
| // 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.ariaActiveDescendantElement, null, "Element set via content attribute with a changed id will return null on getting"); |
| |
| parentNode.ariaActiveDescendantElement = changingIdElement; |
| assert_equals(parentNode.getAttribute("aria-activedescendant"), "new-id"); |
| |
| // The explicitly set element takes precendance over the content attribute. |
| // This means that we still return the same element reference, but the |
| // content attribute reflects the old id. |
| changingIdElement.setAttribute("id", "newer-id"); |
| assert_equals(parentNode.ariaActiveDescendantElement, changingIdElement, "explicitly set element is still present even after the id has been changed"); |
| assert_equals(parentNode.getAttribute("aria-activedescendant"), "new-id", "content attribute reflects the id that was present upon explicitly setting the element reference."); |
| }, "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> |
| <div id="shadowHost"></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"); |
| |
| lightParent.ariaActiveDescendantElement = optionElement; |
| assert_equals(lightParent.ariaActiveDescendantElement, optionElement); |
| |
| // Move the referenced element into shadow DOM. |
| shadowHost.appendChild(optionElement); |
| assert_equals(lightParent.ariaActiveDescendantElement, null); |
| assert_equals(lightParent.getAttribute("aria-activedescendant"), "light-element"); |
| |
| // Move the referenced element back into light DOM. |
| lightParent.appendChild(optionElement); |
| assert_equals(lightParent.ariaActiveDescendantElement, optionElement); |
| }, "Reparenting an element into a descendant shadow scope nullifies the element reference."); |
| </script> |
| </html> |