Skip to content

Commit

Permalink
Focus the first focusable area with autofocus for delegates focus
Browse files Browse the repository at this point in the history
Spec: whatwg/html#6990

Differential Revision: https://phabricator.services.mozilla.com/D128301

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1728864
gecko-commit: d3034ec3376b62837afd4ee95acb18373d0143df
gecko-reviewers: smaug
  • Loading branch information
sefeng211 authored and moz-wptsync-bot committed Oct 26, 2021
1 parent c1be1a0 commit facfe02
Showing 1 changed file with 338 additions and 0 deletions.
338 changes: 338 additions & 0 deletions shadow-dom/focus/focus-autofocus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
<!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>

0 comments on commit facfe02

Please sign in to comment.