<html> <head> <title>Test for Bug 1453693</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/EventUtils.js"></script> <script> class TestNode extends HTMLElement { constructor() { super(); const styles = "<style>:focus{background-color:yellow;}</style>"; this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = `${styles}<div tabindex='-1'>test node</div> <slot></slot>`; }} window.customElements.define('test-node', TestNode); var lastFocusTarget; function focusLogger(event) { lastFocusTarget = event.target; console.log(event.target + " under " + event.target.parentNode); event.stopPropagation(); } function testTabbingThroughShadowDOMWithTabIndexes() { var anchor = document.createElement("a"); anchor.onfocus = focusLogger; anchor.href = "#"; anchor.textContent = "in light DOM"; document.body.appendChild(anchor); var host = document.createElement("div"); document.body.appendChild(host); var sr = host.attachShadow({mode: "open"}); var shadowAnchor = anchor.cloneNode(false); shadowAnchor.onfocus = focusLogger; shadowAnchor.textContent = "in shadow DOM"; sr.appendChild(shadowAnchor); var shadowInput = document.createElement("input"); shadowInput.onfocus = focusLogger; shadowInput.tabIndex = 1; sr.appendChild(shadowInput); var shadowDate = document.createElement("input"); shadowDate.type = "date"; shadowDate.onfocus = focusLogger; shadowDate.tabIndex = 1; sr.appendChild(shadowDate); var shadowIframe = document.createElement("iframe"); shadowIframe.tabIndex = 1; sr.appendChild(shadowIframe); shadowIframe.contentDocument.body.innerHTML = "<input>"; var input = document.createElement("input"); input.onfocus = focusLogger; input.tabIndex = 1; document.body.appendChild(input); var input2 = document.createElement("input"); input2.onfocus = focusLogger; document.body.appendChild(input2); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input, "Should have focused input element. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.documentElement, "Should have focused document element in shadow iframe. (3)"); synthesizeKey("KEY_Tab"); opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.body.firstChild, "Should have focused input element in shadow iframe. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.body.firstChild, "Should have focused input element in shadow iframe. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(shadowIframe.contentDocument.activeElement, shadowIframe.contentDocument.documentElement, "Should have focused document element in shadow iframe. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); document.body.innerHTML = null; } function testTabbingThroughSimpleShadowDOM() { var anchor = document.createElement("a"); anchor.onfocus = focusLogger; anchor.href = "#"; anchor.textContent = "in light DOM"; document.body.appendChild(anchor); anchor.focus(); var host = document.createElement("div"); document.body.appendChild(host); var sr = host.attachShadow({mode: "open"}); var shadowAnchor = anchor.cloneNode(false); shadowAnchor.onfocus = focusLogger; shadowAnchor.textContent = "in shadow DOM"; sr.appendChild(shadowAnchor); var shadowInput = document.createElement("input"); shadowInput.onfocus = focusLogger; sr.appendChild(shadowInput); var hiddenShadowButton = document.createElement("button"); hiddenShadowButton.setAttribute("style", "display: none;"); sr.appendChild(hiddenShadowButton); var input = document.createElement("input"); input.onfocus = focusLogger; document.body.appendChild(input); var input2 = document.createElement("input"); input2.onfocus = focusLogger; document.body.appendChild(input2); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input, "Should have focused input element."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input2, "Should have focused input[2] element."); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input, "Should have focused input element. (2)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (2)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (2)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (2)"); host.remove(); input.remove(); input2.remove(); } function testTabbingThroughNestedShadowDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); var host = document.createElement("div"); host.id = "host"; document.body.appendChild(host); var sr0 = host.attachShadow({mode: "open"}); sr0.innerHTML = "<button id='button'>X</button><br id='br'><div id='h1'></div><div id='h2'></div>"; var button = sr0.getElementById("button"); button.onfocus = focusLogger; var h1 = sr0.getElementById("h1"); var sr1 = h1.attachShadow({mode: "open"}); sr1.innerHTML = "h1 <input id='h11' placeholder='click me and press tab'><input id='h12' placeholder='and then tab again'>"; var input11 = sr1.getElementById("h11"); input11.onfocus = focusLogger; var input12 = sr1.getElementById("h12"); input12.onfocus = focusLogger; var h2 = sr0.getElementById("h2"); var sr2 = h2.attachShadow({mode: "open"}); sr2.innerHTML = "h2 <input id='h21'><input id='h22'>"; var input21 = sr2.getElementById("h21"); input21.onfocus = focusLogger; var input22 = sr2.getElementById("h22"); input22.onfocus = focusLogger; document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input22, "[nested shadow] Should have focused input element. (4)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (5)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (8)"); // Back to beginning, outside of Shadow DOM. synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); host.remove(); } function testTabbingThroughDisplayContentsHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); var host = document.createElement("div"); host.id = "host"; host.setAttribute("style", "display: contents; border: 1px solid black;"); document.body.appendChild(host); var sr0 = host.attachShadow({mode: "open"}); sr0.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; var shadowInput1 = sr0.getElementById("shadowInput1"); shadowInput1.onfocus = focusLogger; var shadowInput2 = sr0.getElementById("shadowInput2"); shadowInput2.onfocus = focusLogger; var host1 = document.createElement("div"); host1.id = "host"; host1.tabIndex = 0; host1.setAttribute("style", "display: contents; border: 1px solid black;"); document.body.appendChild(host1); var sr1 = host1.attachShadow({mode: "open"}); sr1.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; var shadowInput3 = sr1.getElementById("shadowInput1"); shadowInput3.onfocus = focusLogger; var shadowInput4 = sr1.getElementById("shadowInput2"); shadowInput4.onfocus = focusLogger; document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput4, "Should have focused input element. (4)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (5)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (7)"); // Back to beginning, outside of Shadow DOM. synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); host.remove(); host1.remove(); } function testTabbingThroughLightDOMShadowDOMLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host = document.createElement("span"); host.innerHTML = "\n"; host.id = "host"; document.body.appendChild(host); var sr0 = host.attachShadow({mode: "open"}); sr0.innerHTML = document.getElementById("template").innerHTML; var p1 = sr0.getElementById("p1"); p1.onfocus = focusLogger; var p2 = sr0.getElementById("p2"); p2.onfocus = focusLogger; var p = document.createElement("p"); p.innerHTML = " <a href='#p'>link 1</a> "; var a = p.firstElementChild; a.onfocus = focusLogger; document.body.appendChild(p); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, p1, "Should have focused p1."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, p2, "Should have focused p2."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, a, "Should have focused a."); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, p2, "Should have focused p2."); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, p1, "Should have focused p1."); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); host.remove(); p.remove(); } function testFocusableHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host = document.createElement("div"); host.id = "host"; host.tabIndex = 0; host.onfocus = focusLogger; document.body.appendChild(host); var slotted = document.createElement("div"); slotted.tabIndex = 0; slotted.onfocus = focusLogger; host.appendChild(slotted); var sr0 = host.attachShadow({mode: "open"}); sr0.appendChild(document.createElement("slot")); var p = document.createElement("p"); p.innerHTML = " <a href='#p'>link 1</a> "; var a = p.firstElementChild; a.onfocus = focusLogger; document.body.appendChild(p); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host, "Should have focused host."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, slotted, "Should have focused slotted."); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, a, "Should have focused a."); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, slotted, "Should have focused slotted."); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, host, "Should have focused host."); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); host.remove(); p.remove(); } function testShiftTabbingThroughFocusableHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host = document.createElement("div"); host.id = "host"; host.tabIndex = 0; host.onfocus = focusLogger; document.body.appendChild(host); var sr = host.attachShadow({mode: "open"}); var shadowButton = document.createElement("button"); shadowButton.innerText = "X"; shadowButton.onfocus = focusLogger; sr.appendChild(shadowButton); var shadowInput = document.createElement("input"); shadowInput.onfocus = focusLogger; sr.appendChild(shadowInput); sr.appendChild(document.createElement("br")); var input = document.createElement("input"); input.onfocus = focusLogger; document.body.appendChild(input); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host, "Should have focused host element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); // focus is already on host opener.is(sr.activeElement, null, "Focus should have left button element in shadow DOM. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); host.remove(); input.remove(); } function testTabbingThroughNestedSlot() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host0 = document.createElement("div"); var sr0 = host0.attachShadow({mode: "open"}); sr0.innerHTML = "<slot></slot>"; document.body.appendChild(host0); // focusable var host00 = document.createElement("div"); var sr00 = host00.attachShadow({mode: "open"}); var div00 = document.createElement("div"); div00.tabIndex = 0; div00.onfocus = focusLogger; sr00.appendChild(div00); host0.appendChild(host00); // not focusable var host01 = document.createElement("div"); var sr01 = host01.attachShadow({mode: "open"}); sr01.innerHTML = "<div></div>"; host0.appendChild(host01); // focusable var host02 = document.createElement("div"); var sr02 = host02.attachShadow({mode: "open"}); var div02 = document.createElement("div"); div02.tabIndex = 0; div02.onfocus = focusLogger; sr02.appendChild(div02); host0.appendChild(host02); var host1 = document.createElement("div"); var sr1 = host1.attachShadow({mode: "open"}); sr1.innerHTML = "<slot></slot>"; document.body.appendChild(host1); var host10 = document.createElement("div"); var sr10 = host10.attachShadow({mode: "open"}); sr10.innerHTML = "<slot></slot>"; host1.appendChild(host10); var input10 = document.createElement("input"); input10.onfocus = focusLogger; host10.appendChild(input10); var host11 = document.createElement("div"); var sr11 = host11.attachShadow({mode: "open"}); sr11.innerHTML = "<slot></slot>"; host1.appendChild(host11); var input11 = document.createElement("input"); input11.onfocus = focusLogger; host11.appendChild(input11); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); host0.remove(); host1.remove(); } function testTabbingThroughSlotInLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var input0 = document.createElement("input"); input0.onfocus = focusLogger; document.body.appendChild(input0); var slot1 = document.createElement("slot"); document.body.appendChild(slot1); var input10 = document.createElement("input"); input10.onfocus = focusLogger; slot1.appendChild(input10); var input11 = document.createElement("input"); input11.onfocus = focusLogger; slot1.appendChild(input11); var input2 = document.createElement("input"); input2.onfocus = focusLogger; document.body.appendChild(input2); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input0, "Should have focused input element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input2, "Should have focused input element. (4)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (5)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input0, "Should have focused input element. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); input0.remove(); slot1.remove(); input2.remove(); } function testTabbingThroughFocusableSlotInLightDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var slot0 = document.createElement("slot"); slot0.tabIndex = 0; slot0.setAttribute("style", "display: inline;"); slot0.onfocus = focusLogger; document.body.appendChild(slot0); var slot00 = document.createElement("slot"); slot00.tabIndex = 0; slot00.setAttribute("style", "display: inline;"); slot00.onfocus = focusLogger; slot0.appendChild(slot00); var input000 = document.createElement("input"); input000.onfocus = focusLogger; slot00.appendChild(input000); var input01 = document.createElement("input"); input01.onfocus = focusLogger; slot0.appendChild(input01); var input1 = document.createElement("input"); input1.onfocus = focusLogger; document.body.appendChild(input1); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, slot00, "Should have focused slot element. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (4)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input1, "Should have focused input element. (5)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (6)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, slot00, "Should have focused slot element. (8)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, slot0, "Should have focused slot element. (9)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); slot0.remove(); input1.remove(); } function testTabbingThroughScrollableShadowDOM() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host0 = document.createElement("div"); host0.setAttribute("style", "height: 50px; overflow: auto;"); host0.onfocus = focusLogger; document.body.appendChild(host0); var sr0 = host0.attachShadow({mode: "open"}); sr0.innerHTML = ` <style> div,slot { height: 30px; display: block; overflow: auto; } input { display: block; } </style> `; var input00 = document.createElement("input"); input00.setAttribute("style", "background-color: red;"); input00.onfocus = focusLogger; sr0.appendChild(input00); var container01 = document.createElement("div"); container01.onfocus = focusLogger; sr0.appendChild(container01); var input010 = document.createElement("input"); input010.onfocus = focusLogger; container01.appendChild(input010); var input011 = document.createElement("input"); input011.onfocus = focusLogger; container01.appendChild(input011); var slot02 = document.createElement("slot"); slot02.onfocus = focusLogger; sr0.appendChild(slot02); var input020 = document.createElement("input"); input020.setAttribute("style", "display: block;"); input020.onfocus = focusLogger; host0.appendChild(input020); var input021 = document.createElement("input"); input021.setAttribute("style", "display: block;"); input021.onfocus = focusLogger; host0.appendChild(input021); var input1 = document.createElement("input"); input1.onfocus = focusLogger; document.body.appendChild(input1); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (4)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (5)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (6)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (7)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (8)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, input1, "Should have focused input element in light dom. (9)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (10)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (11)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (12)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (13)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (14)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (15)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (16)"); synthesizeKey("KEY_Tab", {shiftKey: true}); // focus is already on host opener.is(sr0.activeElement, null, "Focus should have left input element in shadow DOM. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); host0.remove(); input1.remove(); } // Bug 1604140 function testTabbingThroughScrollableShadowHost() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); let aboveFirstHost = document.createElement("div"); aboveFirstHost.tabIndex = 0; aboveFirstHost.onfocus = focusLogger; document.body.appendChild(aboveFirstHost); let firstHost = document.createElement("div"); firstHost.style = "overflow: scroll"; document.body.appendChild(firstHost); let firstShadow = firstHost.attachShadow({mode: "open"}); let divInFirstShadow = document.createElement("div"); divInFirstShadow.tabIndex = 0; divInFirstShadow.onfocus = focusLogger; firstShadow.appendChild(divInFirstShadow); let aboveSecondHost = document.createElement("div"); aboveSecondHost.tabIndex = 0; aboveSecondHost.onfocus = focusLogger; document.body.appendChild(aboveSecondHost); let secondHost = document.createElement("div"); secondHost.style = "overflow: scroll"; secondHost.tabIndex = 0; secondHost.onfocus = focusLogger; document.body.appendChild(secondHost); let secondShadow = secondHost.attachShadow({mode: "open"}); let divInSecondShadow = document.createElement("div"); divInSecondShadow.tabIndex = 0; divInSecondShadow.onfocus = focusLogger; secondShadow.appendChild(divInSecondShadow); let belowSecondHost = document.createElement("div"); belowSecondHost.tabIndex = 0; belowSecondHost.onfocus = focusLogger; document.body.appendChild(belowSecondHost); document.body.offsetLeft; synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (1)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (2)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (3)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, secondHost, "Should have focused second host element. (4)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (5)"); synthesizeKey("KEY_Tab"); opener.is(lastFocusTarget, belowSecondHost, "Should have focused div below second host. (6)"); // Backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (7)"); synthesizeKey("KEY_Tab", {shiftKey: true}); // focus is already on second host, so lastFocusTarget won't get updated. opener.is(document.activeElement, secondHost, "Should have focused second host element. (8)"); opener.is(secondShadow.activeElement, null, "Focus should have left div in second shadow dom. (8)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (9)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (10)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (11)"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); aboveFirstHost.remove(); firstHost.remove(); aboveSecondHost.remove(); secondHost.remove(); belowSecondHost.remove(); } function testDeeplyNestedShadowTree() { opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); var host1 = document.createElement("test-node"); var lastHost = host1; for (var i = 0; i < 20; ++i) { lastHost.appendChild(document.createElement("test-node")); lastHost = lastHost.firstChild; } var input = document.createElement("input"); document.body.appendChild(host1); document.body.appendChild(input); document.body.offsetLeft; // Test shadow tree which doesn't have anything tab-focusable. host1.shadowRoot.querySelector("div").focus(); synthesizeKey("KEY_Tab"); is(document.activeElement, input, "Should have focused input element."); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); // Same test but with focusable elements in the tree... var input2 = document.createElement("input"); var host2 = host1.firstChild; var host3 = host2.firstChild; host2.insertBefore(input2, host3); var input3 = document.createElement("input"); lastHost.appendChild(input3); document.body.offsetLeft; host3.shadowRoot.querySelector("div").focus(); synthesizeKey("KEY_Tab"); is(document.activeElement, input3, "Should have focused input3 element."); // ...and backwards host3.shadowRoot.querySelector("div").focus(); synthesizeKey("KEY_Tab", {shiftKey: true}); is(document.activeElement, input2, "Should have focused input2 element."); // Remove elements added to body element. host1.remove(); input.remove(); // Tests expect body.firstChild to have focus. document.body.firstChild.focus(); } // Bug 1558393 function testBackwardsTabbingWithSlotsWithoutFocusableContent() { let first = document.createElement("div"); first.tabIndex = 0; let host = document.createElement("div"); host.tabIndex = 0; let second = document.createElement("div"); second.tabIndex = 0; host.appendChild(document.createTextNode("foo")); host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`; document.body.appendChild(first); document.body.appendChild(host); document.body.appendChild(second); document.body.offsetLeft; first.focus(); opener.is(document.activeElement, first, "First light div should have focus"); synthesizeKey("KEY_Tab"); opener.is(document.activeElement, host, "Host should be focused"); synthesizeKey("KEY_Tab"); opener.is(document.activeElement, second, "Second light div should be focused"); // Now backwards synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, host, "Focus should return to host"); synthesizeKey("KEY_Tab", {shiftKey: true}); opener.is(document.activeElement, first, "Focus should return to first light div"); second.remove(); host.remove(); first.remove(); } function runTest() { testTabbingThroughShadowDOMWithTabIndexes(); testTabbingThroughSimpleShadowDOM(); testTabbingThroughNestedShadowDOM(); testTabbingThroughDisplayContentsHost(); testTabbingThroughLightDOMShadowDOMLightDOM(); testFocusableHost(); testShiftTabbingThroughFocusableHost(); testTabbingThroughNestedSlot(); testTabbingThroughSlotInLightDOM(); testTabbingThroughFocusableSlotInLightDOM(); testTabbingThroughScrollableShadowDOM(); testTabbingThroughScrollableShadowHost(); testDeeplyNestedShadowTree(); testBackwardsTabbingWithSlotsWithoutFocusableContent(); opener.didRunTests(); window.close(); } function init() { SimpleTest.waitForFocus(runTest); } </script> <style> </style> <template id="template"> <div style="overflow: hidden"> <p tabindex="0" id="p1">component</p> <p tabindex="0" id="p2">/component</p> </div> </template> </head> <body onload="init()"> </body> </html>