Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Gecko Bug 1728864] Focus the first focusable area with autofocus for delegates focus #31376

Merged
merged 1 commit into from
Oct 26, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>