| <!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> |