-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Focus the first focusable area with autofocus for delegates focus
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
1 parent
c1be1a0
commit facfe02
Showing
1 changed file
with
338 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |