Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE html>
<html>
<head>
<meta name="author" title="Sean Feng" href="mailto:sefeng@mozilla.com">
<meta name="assert" content="Elements with autofocus should have high precedence over other elements for delegates focus">
<link rel="help" href="https://github.com/whatwg/html/pull/6990">
<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>
<script src="resources/shadow-utils.js"></script>
</head>
<body>
<script>
function createShadowDOMTree() {
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <div #firstOuterDiv>
// <div #innertHost>
// #shadowRoot
// <div #firstInnerDiv>
// <div #secondInnerDiv>
// <div #secondOuterDiv>
const host = document.createElement("div");
host.setAttribute("id", "host");
const outerRoot = host.attachShadow({mode: "open", delegatesFocus: true});
const firstOuterDiv = document.createElement("div");
const innerHost = document.createElement("div");
const innerRoot = innerHost.attachShadow({mode: "open"});
const firstInnerDiv = document.createElement("div");
const secondInnerDiv = document.createElement("div");
innerRoot.appendChild(firstInnerDiv);
innerRoot.appendChild(secondInnerDiv);
const secondOuterDiv = document.createElement("div");
outerRoot.appendChild(firstOuterDiv);
outerRoot.appendChild(innerHost);
outerRoot.appendChild(secondOuterDiv);
document.body.appendChild(host);
return [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
]
}
function resetShadowDOMTree() {
const host = document.getElementById("host");
if (host) {
host.remove();
}
return createShadowDOMTree();
}
function resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
) {
firstOuterDiv.removeAttribute("tabindex");
firstOuterDiv.removeAttribute("autofocus");
secondOuterDiv.removeAttribute("tabindex");
secondOuterDiv.removeAttribute("autofocus");
firstInnerDiv.removeAttribute("tabindex");
firstInnerDiv.removeAttribute("autofocus");
secondInnerDiv.removeAttribute("tabindex");
secondInnerDiv.removeAttribute("autofocus");
resetFocus(document);
resetFocus(outerRoot);
resetFocus(innerRoot);
}
function setAllTabIndexTo(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
tabIndex
) {
firstOuterDiv.tabIndex = tabIndex;
secondOuterDiv.tabIndex = tabIndex;
firstInnerDiv.tabIndex = tabIndex;
secondInnerDiv.tabIndex = tabIndex;
}
test(function() {
const [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
] = resetShadowDOMTree();
resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
);
setAllTabIndexTo(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
0
);
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <div tabIndex=0 #firstOuterDiv>
// <div #innertHost>
// #shadowRoot
// <div tabIndex=0 #firstInnerDiv>
// <div tabIndex=0 #secondInnerDiv>
// <div tabIndex=0 autofocus #secondOuterDiv>
secondOuterDiv.autofocus = true;
secondOuterDiv.setAttribute("autofocus", true);
host.focus();
assert_equals(document.activeElement, host);
assert_equals(outerRoot.activeElement, secondOuterDiv);
}, "The second input should be focused since it has autofocus");
test(function() {
const [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
] = resetShadowDOMTree();
resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
);
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <div #firstOuterDiv>
// <div #innertHost>
// #shadowRoot
// <div tabIndex=0 #firstInnerDiv>
// <div tabIndex=0 autofocus #secondInnerDiv>
// <div #secondOuterDiv>
firstInnerDiv.tabIndex = 0;
secondInnerDiv.tabIndex = 0;
secondInnerDiv.setAttribute("autofocus", true);
host.focus();
assert_equals(document.activeElement, document.body);
assert_equals(outerRoot.activeElement, null);
}, "Focus should not be delegated to the autofocus element because the inner host doesn't have delegates focus");
test(function() {
const [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
] = resetShadowDOMTree();
resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
);
const newInnerHost = document.createElement("div");
const newInnerRoot = newInnerHost.attachShadow({mode: "open", delegatesFocus: true});
const newFirstInnerDiv = document.createElement("div");
const newSecondInnerDiv = document.createElement("div");
newFirstInnerDiv.setAttribute("tabIndex", 0);
newSecondInnerDiv.setAttribute("tabIndex", 0);
newSecondInnerDiv.setAttribute("autofocus", true);
newInnerRoot.appendChild(newFirstInnerDiv);
newInnerRoot.appendChild(newSecondInnerDiv);
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <div #firstOuterDiv>
// <div #innertHost> (delegatesFocus = true)
// #shadowRoot
// <div tabIndex=0 #newFirstInnerDiv>
// <div tabIndex=0 autofocus #newSecondInnerDiv>
// <div #secondOuterDiv>
outerRoot.replaceChild(newInnerHost, innerHost);
host.focus();
assert_equals(document.activeElement, host);
assert_equals(outerRoot.activeElement, newInnerHost);
assert_equals(newInnerRoot.activeElement, newSecondInnerDiv);
}, "Focus should be delegated to the autofocus element when the inner host has delegates focus");
test(function() {
const [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
] = resetShadowDOMTree();
resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
);
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <slot>
// (slotted) <div autofocus tabIndex=0 #slottedAutofocus></div>
// <div tabIndex=0 #firstOuterDiv>
// <div #innertHost>
// #shadowRoot
// <div tabIndex=0 #firstInnerDiv>
// <div tabIndex=0 autofocus #secondInnerDiv>
// <div #secondOuterDiv>
const slottedAutofocus = document.createElement("div");
slottedAutofocus.tabIndex = 0;
slottedAutofocus.setAttribute("autofocus", true);
host.appendChild(slottedAutofocus);
const slot = document.createElement("slot");
outerRoot.insertBefore(slot, firstOuterDiv);
firstOuterDiv.tabIndex = 0;
host.focus();
assert_equals(document.activeElement, host);
assert_equals(outerRoot.activeElement, firstOuterDiv);
}, "Focus should not be delegated to the slotted elements");
test(function() {
const [
host,
outerRoot,
firstOuterDiv,
secondOuterDiv,
innerHost,
innerRoot,
firstInnerDiv,
secondInnerDiv
] = resetShadowDOMTree();
resetTabIndexAndFocus(
firstOuterDiv,
secondOuterDiv,
firstInnerDiv,
secondInnerDiv,
outerRoot,
innerRoot
);
// <div #host> (delegatesFocus = true)
// #shadowRoot
// <div #firstOuterDiv>
// <div tabIndex=0 #firstNestedDiv>
// <div tabIndex=0 #secondNestedDiv>
// <div tabIndex=0 autofocus #thirdNestedDiv>
// <div #innertHost>
// #shadowRoot
// <div #firstInnerDiv>
// <div #secondInnerDiv>
// <div autofocus tabIndex=0 #secondOuterDiv>
secondInnerDiv.tabIndex = 0;
secondInnerDiv.setAttribute("autofocus", true);
const firstNestedDiv = document.createElement("div");
const secondNestedDiv = document.createElement("div");
const thirdNestedDiv = document.createElement("div");
firstNestedDiv.tabIndex = 0;
secondNestedDiv.tabIndex = 0;
thirdNestedDiv.tabIndex = 0;
thirdNestedDiv.setAttribute("autofocus", true);
firstOuterDiv.appendChild(firstNestedDiv);
firstNestedDiv.appendChild(secondNestedDiv);
secondNestedDiv.appendChild(thirdNestedDiv);
host.focus();
assert_equals(document.activeElement, host);
assert_equals(outerRoot.activeElement, thirdNestedDiv);
}, "Focus should be delegated to the nested div which has autofocus based on the tree order");
</script>
</body>
</html>