file_focus_shadow_dom.html (39950B)
1 <html> 2 <head> 3 <title>Test for Bug 1453693</title> 4 <script src="/tests/SimpleTest/SimpleTest.js"></script> 5 <script src="/tests/SimpleTest/EventUtils.js"></script> 6 <script> 7 8 class TestNode extends HTMLElement { 9 constructor() { 10 super(); 11 const styles = "<style>:focus{background-color:yellow;}</style>"; 12 this.attachShadow({ mode: 'open' }); 13 this.shadowRoot.innerHTML = 14 `${styles}<div tabindex='-1'>test node</div> <slot></slot>`; 15 }} 16 17 window.customElements.define('test-node', TestNode); 18 19 var lastFocusTarget; 20 function focusLogger(event) { 21 lastFocusTarget = event.target; 22 console.log(event.target + " under " + event.target.parentNode); 23 event.stopPropagation(); 24 } 25 26 function flushLayout(...iframes) { 27 document.body.offsetHeight; 28 iframes.forEach(ifr => ifr.contentDocument?.body.offsetHeight); 29 } 30 31 async function testTabbingThroughShadowDOMWithTabIndexes() { 32 var anchor = document.createElement("a"); 33 anchor.onfocus = focusLogger; 34 anchor.href = "#"; 35 anchor.textContent = "in light DOM"; 36 document.body.appendChild(anchor); 37 38 var host = document.createElement("div"); 39 document.body.appendChild(host); 40 41 var sr = host.attachShadow({mode: "open"}); 42 var shadowAnchor = anchor.cloneNode(false); 43 shadowAnchor.onfocus = focusLogger; 44 shadowAnchor.textContent = "in shadow DOM"; 45 sr.appendChild(shadowAnchor); 46 var shadowInput = document.createElement("input"); 47 shadowInput.onfocus = focusLogger; 48 shadowInput.tabIndex = 1; 49 sr.appendChild(shadowInput); 50 51 var shadowDate = document.createElement("input"); 52 shadowDate.type = "date"; 53 shadowDate.onfocus = focusLogger; 54 shadowDate.tabIndex = 1; 55 sr.appendChild(shadowDate); 56 57 var shadowIframe = document.createElement("iframe"); 58 shadowIframe.tabIndex = 1; 59 sr.appendChild(shadowIframe); 60 shadowIframe.contentDocument.body.innerHTML = "<input>"; 61 62 var input = document.createElement("input"); 63 input.onfocus = focusLogger; 64 input.tabIndex = 1; 65 document.body.appendChild(input); 66 67 var input2 = document.createElement("input"); 68 input2.onfocus = focusLogger; 69 document.body.appendChild(input2); 70 71 flushLayout(shadowIframe); 72 73 synthesizeKey("KEY_Tab"); 74 opener.is(lastFocusTarget, input, "Should have focused input element. (3)"); 75 synthesizeKey("KEY_Tab"); 76 opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)"); 77 synthesizeKey("KEY_Tab"); 78 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); 79 synthesizeKey("KEY_Tab"); 80 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); 81 synthesizeKey("KEY_Tab"); 82 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); 83 synthesizeKey("KEY_Tab"); 84 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); 85 synthesizeKey("KEY_Tab"); 86 opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (3)"); 87 88 synthesizeKey("KEY_Tab"); 89 opener.is(shadowIframe.contentDocument.activeElement, 90 shadowIframe.contentDocument.body.firstChild, 91 "Should have focused input element in shadow iframe. (3)"); 92 synthesizeKey("KEY_Tab"); 93 opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)"); 94 synthesizeKey("KEY_Tab"); 95 opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)"); 96 97 // Backwards 98 synthesizeKey("KEY_Tab", {shiftKey: true}); 99 opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)"); 100 synthesizeKey("KEY_Tab", {shiftKey: true}); 101 opener.is(shadowIframe.contentDocument.activeElement, 102 shadowIframe.contentDocument.body.firstChild, 103 "Should have focused input element in shadow iframe. (4)"); 104 synthesizeKey("KEY_Tab", {shiftKey: true}); 105 opener.is(lastFocusTarget, shadowDate, "Should have focused date element with a calendar button in shadow DOM. (4)"); 106 synthesizeKey("KEY_Tab", {shiftKey: true}); 107 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); 108 synthesizeKey("KEY_Tab", {shiftKey: true}); 109 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); 110 synthesizeKey("KEY_Tab", {shiftKey: true}); 111 opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); 112 synthesizeKey("KEY_Tab", {shiftKey: true}); 113 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)"); 114 synthesizeKey("KEY_Tab", {shiftKey: true}); 115 opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)"); 116 synthesizeKey("KEY_Tab", {shiftKey: true}); 117 opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); 118 119 document.body.innerHTML = null; 120 } 121 122 async function testTabbingThroughSimpleShadowDOM() { 123 var anchor = document.createElement("a"); 124 anchor.onfocus = focusLogger; 125 anchor.href = "#"; 126 anchor.textContent = "in light DOM"; 127 document.body.appendChild(anchor); 128 anchor.focus(); 129 130 var host = document.createElement("div"); 131 document.body.appendChild(host); 132 133 var sr = host.attachShadow({mode: "open"}); 134 var shadowAnchor = anchor.cloneNode(false); 135 shadowAnchor.onfocus = focusLogger; 136 shadowAnchor.textContent = "in shadow DOM"; 137 sr.appendChild(shadowAnchor); 138 var shadowInput = document.createElement("input"); 139 shadowInput.onfocus = focusLogger; 140 sr.appendChild(shadowInput); 141 142 var hiddenShadowButton = document.createElement("button"); 143 hiddenShadowButton.setAttribute("style", "display: none;"); 144 sr.appendChild(hiddenShadowButton); 145 146 var input = document.createElement("input"); 147 input.onfocus = focusLogger; 148 document.body.appendChild(input); 149 150 var input2 = document.createElement("input"); 151 input2.onfocus = focusLogger; 152 document.body.appendChild(input2); 153 154 flushLayout(); 155 156 synthesizeKey("KEY_Tab"); 157 opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM."); 158 synthesizeKey("KEY_Tab"); 159 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM."); 160 synthesizeKey("KEY_Tab"); 161 opener.is(lastFocusTarget, input, "Should have focused input element."); 162 synthesizeKey("KEY_Tab"); 163 opener.is(lastFocusTarget, input2, "Should have focused input[2] element."); 164 165 // Backwards 166 synthesizeKey("KEY_Tab", {shiftKey: true}); 167 opener.is(lastFocusTarget, input, "Should have focused input element. (2)"); 168 synthesizeKey("KEY_Tab", {shiftKey: true}); 169 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (2)"); 170 synthesizeKey("KEY_Tab", {shiftKey: true}); 171 opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (2)"); 172 synthesizeKey("KEY_Tab", {shiftKey: true}); 173 opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (2)"); 174 175 host.remove(); 176 input.remove(); 177 input2.remove(); 178 } 179 180 async function testTabbingThroughNestedShadowDOM() { 181 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); 182 183 var host = document.createElement("div"); 184 host.id = "host"; 185 document.body.appendChild(host); 186 187 var sr0 = host.attachShadow({mode: "open"}); 188 sr0.innerHTML = "<button id='button'>X</button><br id='br'><div id='h1'></div><div id='h2'></div>"; 189 var button = sr0.getElementById("button"); 190 button.onfocus = focusLogger; 191 192 var h1 = sr0.getElementById("h1"); 193 var sr1 = h1.attachShadow({mode: "open"}); 194 sr1.innerHTML = "h1 <input id='h11' placeholder='click me and press tab'><input id='h12' placeholder='and then tab again'>"; 195 var input11 = sr1.getElementById("h11"); 196 input11.onfocus = focusLogger; 197 var input12 = sr1.getElementById("h12"); 198 input12.onfocus = focusLogger; 199 200 var h2 = sr0.getElementById("h2"); 201 var sr2 = h2.attachShadow({mode: "open"}); 202 sr2.innerHTML = "h2 <input id='h21'><input id='h22'>"; 203 var input21 = sr2.getElementById("h21"); 204 input21.onfocus = focusLogger; 205 var input22 = sr2.getElementById("h22"); 206 input22.onfocus = focusLogger; 207 208 flushLayout(); 209 210 synthesizeKey("KEY_Tab"); 211 opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)"); 212 213 synthesizeKey("KEY_Tab"); 214 opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (1)"); 215 216 synthesizeKey("KEY_Tab"); 217 opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (2)"); 218 219 synthesizeKey("KEY_Tab"); 220 opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (3)"); 221 222 synthesizeKey("KEY_Tab"); 223 opener.is(lastFocusTarget, input22, "[nested shadow] Should have focused input element. (4)"); 224 225 // Backwards 226 synthesizeKey("KEY_Tab", {shiftKey: true}); 227 opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (5)"); 228 229 synthesizeKey("KEY_Tab", {shiftKey: true}); 230 opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (6)"); 231 232 synthesizeKey("KEY_Tab", {shiftKey: true}); 233 opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (7)"); 234 235 synthesizeKey("KEY_Tab", {shiftKey: true}); 236 opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (8)"); 237 238 // Back to beginning, outside of Shadow DOM. 239 synthesizeKey("KEY_Tab", {shiftKey: true}); 240 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); 241 242 host.remove(); 243 } 244 245 async function testTabbingThroughDisplayContentsHost() { 246 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); 247 248 var host = document.createElement("div"); 249 host.id = "host"; 250 host.setAttribute("style", "display: contents; border: 1px solid black;"); 251 document.body.appendChild(host); 252 253 var sr0 = host.attachShadow({mode: "open"}); 254 sr0.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; 255 var shadowInput1 = sr0.getElementById("shadowInput1"); 256 shadowInput1.onfocus = focusLogger; 257 var shadowInput2 = sr0.getElementById("shadowInput2"); 258 shadowInput2.onfocus = focusLogger; 259 260 var host1 = document.createElement("div"); 261 host1.id = "host"; 262 host1.tabIndex = 0; 263 host1.setAttribute("style", "display: contents; border: 1px solid black;"); 264 document.body.appendChild(host1); 265 266 var sr1 = host1.attachShadow({mode: "open"}); 267 sr1.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; 268 var shadowInput3 = sr1.getElementById("shadowInput1"); 269 shadowInput3.onfocus = focusLogger; 270 var shadowInput4 = sr1.getElementById("shadowInput2"); 271 shadowInput4.onfocus = focusLogger; 272 273 flushLayout(); 274 275 synthesizeKey("KEY_Tab"); 276 opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)"); 277 278 synthesizeKey("KEY_Tab"); 279 opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (2)"); 280 281 synthesizeKey("KEY_Tab"); 282 opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (3)"); 283 284 synthesizeKey("KEY_Tab"); 285 opener.is(lastFocusTarget, shadowInput4, "Should have focused input element. (4)"); 286 287 // Backwards 288 synthesizeKey("KEY_Tab", {shiftKey: true}); 289 opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (5)"); 290 291 synthesizeKey("KEY_Tab", {shiftKey: true}); 292 opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (6)"); 293 294 synthesizeKey("KEY_Tab", {shiftKey: true}); 295 opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (7)"); 296 297 // Back to beginning, outside of Shadow DOM. 298 synthesizeKey("KEY_Tab", {shiftKey: true}); 299 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); 300 301 host.remove(); 302 host1.remove(); 303 } 304 305 async function testTabbingThroughLightDOMShadowDOMLightDOM() { 306 opener.is(document.activeElement, document.body.firstChild, 307 "body's first child should have focus."); 308 309 var host = document.createElement("span"); 310 host.innerHTML = "\n"; 311 host.id = "host"; 312 document.body.appendChild(host); 313 314 var sr0 = host.attachShadow({mode: "open"}); 315 sr0.innerHTML = document.getElementById("template").innerHTML; 316 var p1 = sr0.getElementById("p1"); 317 p1.onfocus = focusLogger; 318 var p2 = sr0.getElementById("p2"); 319 p2.onfocus = focusLogger; 320 321 var p = document.createElement("p"); 322 p.innerHTML = " <a href='#p'>link 1</a> "; 323 var a = p.firstElementChild; 324 a.onfocus = focusLogger; 325 document.body.appendChild(p); 326 327 flushLayout(); 328 329 synthesizeKey("KEY_Tab"); 330 opener.is(lastFocusTarget, p1, "Should have focused p1."); 331 332 synthesizeKey("KEY_Tab"); 333 opener.is(lastFocusTarget, p2, "Should have focused p2."); 334 335 synthesizeKey("KEY_Tab"); 336 opener.is(lastFocusTarget, a, "Should have focused a."); 337 338 // Backwards 339 synthesizeKey("KEY_Tab", {shiftKey: true}); 340 opener.is(lastFocusTarget, p2, "Should have focused p2."); 341 342 synthesizeKey("KEY_Tab", {shiftKey: true}); 343 opener.is(lastFocusTarget, p1, "Should have focused p1."); 344 345 synthesizeKey("KEY_Tab", {shiftKey: true}); 346 opener.is(document.activeElement, document.body.firstChild, 347 "body's first child should have focus."); 348 349 host.remove(); 350 p.remove(); 351 } 352 353 async function testFocusableHost() { 354 opener.is(document.activeElement, document.body.firstChild, 355 "body's first child should have focus."); 356 357 var host = document.createElement("div"); 358 host.id = "host"; 359 host.tabIndex = 0; 360 host.onfocus = focusLogger; 361 document.body.appendChild(host); 362 363 var slotted = document.createElement("div"); 364 slotted.tabIndex = 0; 365 slotted.onfocus = focusLogger; 366 host.appendChild(slotted); 367 368 var sr0 = host.attachShadow({mode: "open"}); 369 sr0.appendChild(document.createElement("slot")); 370 371 var p = document.createElement("p"); 372 p.innerHTML = " <a href='#p'>link 1</a> "; 373 var a = p.firstElementChild; 374 a.onfocus = focusLogger; 375 document.body.appendChild(p); 376 377 flushLayout(); 378 379 synthesizeKey("KEY_Tab"); 380 opener.is(lastFocusTarget, host, "Should have focused host."); 381 382 synthesizeKey("KEY_Tab"); 383 opener.is(lastFocusTarget, slotted, "Should have focused slotted."); 384 385 synthesizeKey("KEY_Tab"); 386 opener.is(lastFocusTarget, a, "Should have focused a."); 387 388 // Backwards 389 synthesizeKey("KEY_Tab", {shiftKey: true}); 390 opener.is(lastFocusTarget, slotted, "Should have focused slotted."); 391 392 synthesizeKey("KEY_Tab", {shiftKey: true}); 393 opener.is(lastFocusTarget, host, "Should have focused host."); 394 395 synthesizeKey("KEY_Tab", {shiftKey: true}); 396 opener.is(document.activeElement, document.body.firstChild, 397 "body's first child should have focus."); 398 399 host.remove(); 400 p.remove(); 401 } 402 403 async function testShiftTabbingThroughFocusableHost() { 404 opener.is(document.activeElement, document.body.firstChild, 405 "body's first child should have focus."); 406 407 var host = document.createElement("div"); 408 host.id = "host"; 409 host.tabIndex = 0; 410 host.onfocus = focusLogger; 411 document.body.appendChild(host); 412 413 var sr = host.attachShadow({mode: "open"}); 414 var shadowButton = document.createElement("button"); 415 shadowButton.innerText = "X"; 416 shadowButton.onfocus = focusLogger; 417 sr.appendChild(shadowButton); 418 419 var shadowInput = document.createElement("input"); 420 shadowInput.onfocus = focusLogger; 421 sr.appendChild(shadowInput); 422 sr.appendChild(document.createElement("br")); 423 424 var input = document.createElement("input"); 425 input.onfocus = focusLogger; 426 document.body.appendChild(input); 427 428 flushLayout(); 429 430 synthesizeKey("KEY_Tab"); 431 opener.is(lastFocusTarget, host, "Should have focused host element. (1)"); 432 433 synthesizeKey("KEY_Tab"); 434 opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)"); 435 436 synthesizeKey("KEY_Tab"); 437 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); 438 439 synthesizeKey("KEY_Tab"); 440 opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); 441 442 // Backwards 443 synthesizeKey("KEY_Tab", {shiftKey: true}); 444 opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)"); 445 446 synthesizeKey("KEY_Tab", {shiftKey: true}); 447 opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)"); 448 449 synthesizeKey("KEY_Tab", {shiftKey: true}); 450 // focus is already on host 451 opener.is(sr.activeElement, null, 452 "Focus should have left button element in shadow DOM. (7)"); 453 454 synthesizeKey("KEY_Tab", {shiftKey: true}); 455 opener.is(document.activeElement, document.body.firstChild, 456 "body's first child should have focus."); 457 458 host.remove(); 459 input.remove(); 460 } 461 462 async function testTabbingThroughNestedSlot() { 463 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 464 465 var host0 = document.createElement("div"); 466 var sr0 = host0.attachShadow({mode: "open"}); 467 sr0.innerHTML = "<slot></slot>"; 468 document.body.appendChild(host0); 469 470 // focusable 471 var host00 = document.createElement("div"); 472 var sr00 = host00.attachShadow({mode: "open"}); 473 var div00 = document.createElement("div"); 474 div00.tabIndex = 0; 475 div00.onfocus = focusLogger; 476 sr00.appendChild(div00); 477 host0.appendChild(host00); 478 479 // not focusable 480 var host01 = document.createElement("div"); 481 var sr01 = host01.attachShadow({mode: "open"}); 482 sr01.innerHTML = "<div></div>"; 483 host0.appendChild(host01); 484 485 // focusable 486 var host02 = document.createElement("div"); 487 var sr02 = host02.attachShadow({mode: "open"}); 488 var div02 = document.createElement("div"); 489 div02.tabIndex = 0; 490 div02.onfocus = focusLogger; 491 sr02.appendChild(div02); 492 host0.appendChild(host02); 493 494 var host1 = document.createElement("div"); 495 var sr1 = host1.attachShadow({mode: "open"}); 496 sr1.innerHTML = "<slot></slot>"; 497 document.body.appendChild(host1); 498 499 var host10 = document.createElement("div"); 500 var sr10 = host10.attachShadow({mode: "open"}); 501 sr10.innerHTML = "<slot></slot>"; 502 host1.appendChild(host10); 503 504 var input10 = document.createElement("input"); 505 input10.onfocus = focusLogger; 506 host10.appendChild(input10); 507 508 var host11 = document.createElement("div"); 509 var sr11 = host11.attachShadow({mode: "open"}); 510 sr11.innerHTML = "<slot></slot>"; 511 host1.appendChild(host11); 512 513 var input11 = document.createElement("input"); 514 input11.onfocus = focusLogger; 515 host11.appendChild(input11); 516 517 flushLayout(); 518 519 synthesizeKey("KEY_Tab"); 520 opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)"); 521 522 synthesizeKey("KEY_Tab"); 523 opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)"); 524 525 synthesizeKey("KEY_Tab"); 526 opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)"); 527 528 synthesizeKey("KEY_Tab"); 529 opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)"); 530 531 // Backwards 532 synthesizeKey("KEY_Tab", {shiftKey: true}); 533 opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)"); 534 535 synthesizeKey("KEY_Tab", {shiftKey: true}); 536 opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)"); 537 538 synthesizeKey("KEY_Tab", {shiftKey: true}); 539 opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)"); 540 541 synthesizeKey("KEY_Tab", {shiftKey: true}); 542 opener.is(document.activeElement, document.body.firstChild, 543 "body's first child should have focus."); 544 545 host0.remove(); 546 host1.remove(); 547 } 548 549 async function testTabbingThroughSlotInLightDOM() { 550 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 551 552 var input0 = document.createElement("input"); 553 input0.onfocus = focusLogger; 554 document.body.appendChild(input0); 555 556 var slot1 = document.createElement("slot"); 557 document.body.appendChild(slot1); 558 559 var input10 = document.createElement("input"); 560 input10.onfocus = focusLogger; 561 slot1.appendChild(input10); 562 563 var input11 = document.createElement("input"); 564 input11.onfocus = focusLogger; 565 slot1.appendChild(input11); 566 567 var input2 = document.createElement("input"); 568 input2.onfocus = focusLogger; 569 document.body.appendChild(input2); 570 571 flushLayout(); 572 573 synthesizeKey("KEY_Tab"); 574 opener.is(lastFocusTarget, input0, "Should have focused input element. (1)"); 575 576 synthesizeKey("KEY_Tab"); 577 opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (2)"); 578 579 synthesizeKey("KEY_Tab"); 580 opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (3)"); 581 582 synthesizeKey("KEY_Tab"); 583 opener.is(lastFocusTarget, input2, "Should have focused input element. (4)"); 584 585 // Backwards 586 synthesizeKey("KEY_Tab", {shiftKey: true}); 587 opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (5)"); 588 589 synthesizeKey("KEY_Tab", {shiftKey: true}); 590 opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (6)"); 591 592 synthesizeKey("KEY_Tab", {shiftKey: true}); 593 opener.is(lastFocusTarget, input0, "Should have focused input element. (7)"); 594 595 synthesizeKey("KEY_Tab", {shiftKey: true}); 596 opener.is(document.activeElement, document.body.firstChild, 597 "body's first child should have focus."); 598 599 input0.remove(); 600 slot1.remove(); 601 input2.remove(); 602 } 603 604 async function testTabbingThroughFocusableSlotInLightDOM() { 605 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 606 607 var slot0 = document.createElement("slot"); 608 slot0.tabIndex = 0; 609 slot0.setAttribute("style", "display: inline;"); 610 slot0.onfocus = focusLogger; 611 document.body.appendChild(slot0); 612 613 var slot00 = document.createElement("slot"); 614 slot00.tabIndex = 0; 615 slot00.setAttribute("style", "display: inline;"); 616 slot00.onfocus = focusLogger; 617 slot0.appendChild(slot00); 618 619 var input000 = document.createElement("input"); 620 input000.onfocus = focusLogger; 621 slot00.appendChild(input000); 622 623 var input01 = document.createElement("input"); 624 input01.onfocus = focusLogger; 625 slot0.appendChild(input01); 626 627 var input1 = document.createElement("input"); 628 input1.onfocus = focusLogger; 629 document.body.appendChild(input1); 630 631 flushLayout(); 632 633 synthesizeKey("KEY_Tab"); 634 opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)"); 635 636 synthesizeKey("KEY_Tab"); 637 opener.is(lastFocusTarget, slot00, "Should have focused slot element. (2)"); 638 639 synthesizeKey("KEY_Tab"); 640 opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (3)"); 641 642 synthesizeKey("KEY_Tab"); 643 opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (4)"); 644 645 synthesizeKey("KEY_Tab"); 646 opener.is(lastFocusTarget, input1, "Should have focused input element. (5)"); 647 648 // Backwards 649 synthesizeKey("KEY_Tab", {shiftKey: true}); 650 opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (6)"); 651 652 synthesizeKey("KEY_Tab", {shiftKey: true}); 653 opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (7)"); 654 655 synthesizeKey("KEY_Tab", {shiftKey: true}); 656 opener.is(lastFocusTarget, slot00, "Should have focused slot element. (8)"); 657 658 synthesizeKey("KEY_Tab", {shiftKey: true}); 659 opener.is(lastFocusTarget, slot0, "Should have focused slot element. (9)"); 660 661 synthesizeKey("KEY_Tab", {shiftKey: true}); 662 opener.is(document.activeElement, document.body.firstChild, 663 "body's first child should have focus."); 664 665 slot0.remove(); 666 input1.remove(); 667 } 668 669 async function testTabbingThroughScrollableShadowDOM() { 670 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 671 672 var host0 = document.createElement("div"); 673 host0.setAttribute("style", "height: 50px; overflow: auto;"); 674 host0.onfocus = focusLogger; 675 document.body.appendChild(host0); 676 677 var sr0 = host0.attachShadow({mode: "open"}); 678 sr0.innerHTML = ` 679 <style> 680 div,slot { 681 height: 30px; 682 display: block; 683 overflow: auto; 684 } 685 input { 686 display: block; 687 } 688 </style> 689 `; 690 691 var input00 = document.createElement("input"); 692 input00.setAttribute("style", "background-color: red;"); 693 input00.onfocus = focusLogger; 694 sr0.appendChild(input00); 695 696 var container01 = document.createElement("div"); 697 container01.onfocus = focusLogger; 698 sr0.appendChild(container01); 699 700 var input010 = document.createElement("input"); 701 input010.onfocus = focusLogger; 702 container01.appendChild(input010); 703 704 var input011 = document.createElement("input"); 705 input011.onfocus = focusLogger; 706 container01.appendChild(input011); 707 708 var slot02 = document.createElement("slot"); 709 slot02.onfocus = focusLogger; 710 sr0.appendChild(slot02); 711 712 var input020 = document.createElement("input"); 713 input020.setAttribute("style", "display: block;"); 714 input020.onfocus = focusLogger; 715 host0.appendChild(input020); 716 717 var input021 = document.createElement("input"); 718 input021.setAttribute("style", "display: block;"); 719 input021.onfocus = focusLogger; 720 host0.appendChild(input021); 721 722 var input1 = document.createElement("input"); 723 input1.onfocus = focusLogger; 724 document.body.appendChild(input1); 725 726 flushLayout(); 727 728 synthesizeKey("KEY_Tab"); 729 opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)"); 730 731 synthesizeKey("KEY_Tab"); 732 opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (2)"); 733 734 synthesizeKey("KEY_Tab"); 735 opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (3)"); 736 737 synthesizeKey("KEY_Tab"); 738 opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (4)"); 739 740 synthesizeKey("KEY_Tab"); 741 opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (5)"); 742 743 synthesizeKey("KEY_Tab"); 744 opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (6)"); 745 746 synthesizeKey("KEY_Tab"); 747 opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (7)"); 748 749 synthesizeKey("KEY_Tab"); 750 opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (8)"); 751 752 synthesizeKey("KEY_Tab"); 753 opener.is(lastFocusTarget, input1, "Should have focused input element in light dom. (9)"); 754 755 // Backwards 756 synthesizeKey("KEY_Tab", {shiftKey: true}); 757 opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (10)"); 758 759 synthesizeKey("KEY_Tab", {shiftKey: true}); 760 opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (11)"); 761 762 synthesizeKey("KEY_Tab", {shiftKey: true}); 763 opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (12)"); 764 765 synthesizeKey("KEY_Tab", {shiftKey: true}); 766 opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (13)"); 767 768 synthesizeKey("KEY_Tab", {shiftKey: true}); 769 opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (14)"); 770 771 synthesizeKey("KEY_Tab", {shiftKey: true}); 772 opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (15)"); 773 774 synthesizeKey("KEY_Tab", {shiftKey: true}); 775 opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (16)"); 776 777 synthesizeKey("KEY_Tab", {shiftKey: true}); 778 // focus is already on host 779 opener.is(sr0.activeElement, null, 780 "Focus should have left input element in shadow DOM. (7)"); 781 782 synthesizeKey("KEY_Tab", {shiftKey: true}); 783 opener.is(document.activeElement, document.body.firstChild, 784 "body's first child should have focus."); 785 786 host0.remove(); 787 input1.remove(); 788 } 789 790 // Bug 1604140 791 async function testTabbingThroughScrollableShadowHost() { 792 opener.is(document.activeElement, document.body.firstChild, 793 "body's first child should have focus."); 794 795 let aboveFirstHost = document.createElement("div"); 796 aboveFirstHost.tabIndex = 0; 797 aboveFirstHost.onfocus = focusLogger; 798 document.body.appendChild(aboveFirstHost); 799 800 let firstHost = document.createElement("div"); 801 firstHost.style = "overflow: scroll"; 802 document.body.appendChild(firstHost); 803 804 let firstShadow = firstHost.attachShadow({mode: "open"}); 805 let divInFirstShadow = document.createElement("div"); 806 divInFirstShadow.tabIndex = 0; 807 divInFirstShadow.onfocus = focusLogger; 808 firstShadow.appendChild(divInFirstShadow); 809 810 let aboveSecondHost = document.createElement("div"); 811 aboveSecondHost.tabIndex = 0; 812 aboveSecondHost.onfocus = focusLogger; 813 document.body.appendChild(aboveSecondHost); 814 815 let secondHost = document.createElement("div"); 816 secondHost.style = "overflow: scroll"; 817 secondHost.tabIndex = 0; 818 secondHost.onfocus = focusLogger; 819 document.body.appendChild(secondHost); 820 821 let secondShadow = secondHost.attachShadow({mode: "open"}); 822 let divInSecondShadow = document.createElement("div"); 823 divInSecondShadow.tabIndex = 0; 824 divInSecondShadow.onfocus = focusLogger; 825 secondShadow.appendChild(divInSecondShadow); 826 827 let belowSecondHost = document.createElement("div"); 828 belowSecondHost.tabIndex = 0; 829 belowSecondHost.onfocus = focusLogger; 830 document.body.appendChild(belowSecondHost); 831 832 flushLayout(); 833 834 synthesizeKey("KEY_Tab"); 835 opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (1)"); 836 837 synthesizeKey("KEY_Tab"); 838 opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (2)"); 839 840 synthesizeKey("KEY_Tab"); 841 opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (3)"); 842 843 synthesizeKey("KEY_Tab"); 844 opener.is(lastFocusTarget, secondHost, "Should have focused second host element. (4)"); 845 846 synthesizeKey("KEY_Tab"); 847 opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (5)"); 848 849 synthesizeKey("KEY_Tab"); 850 opener.is(lastFocusTarget, belowSecondHost, "Should have focused div below second host. (6)"); 851 852 // Backwards 853 synthesizeKey("KEY_Tab", {shiftKey: true}); 854 opener.is(lastFocusTarget, divInSecondShadow, "Should have focused div in second shadow dom. (7)"); 855 856 synthesizeKey("KEY_Tab", {shiftKey: true}); 857 // focus is already on second host, so lastFocusTarget won't get updated. 858 opener.is(document.activeElement, secondHost, "Should have focused second host element. (8)"); 859 opener.is(secondShadow.activeElement, null, "Focus should have left div in second shadow dom. (8)"); 860 861 synthesizeKey("KEY_Tab", {shiftKey: true}); 862 opener.is(lastFocusTarget, aboveSecondHost, "Should have focused div above second host element. (9)"); 863 864 synthesizeKey("KEY_Tab", {shiftKey: true}); 865 opener.is(lastFocusTarget, divInFirstShadow, "Should have focused div in first shadow dom. (10)"); 866 867 synthesizeKey("KEY_Tab", {shiftKey: true}); 868 opener.is(lastFocusTarget, aboveFirstHost, "Should have focused div above first host element. (11)"); 869 870 synthesizeKey("KEY_Tab", {shiftKey: true}); 871 opener.is(document.activeElement, document.body.firstChild, 872 "body's first child should have focus."); 873 874 aboveFirstHost.remove(); 875 firstHost.remove(); 876 aboveSecondHost.remove(); 877 secondHost.remove(); 878 belowSecondHost.remove(); 879 } 880 881 async function testDeeplyNestedShadowTree() { 882 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 883 var host1 = document.createElement("test-node"); 884 var lastHost = host1; 885 for (var i = 0; i < 20; ++i) { 886 lastHost.appendChild(document.createElement("test-node")); 887 lastHost = lastHost.firstChild; 888 } 889 890 var input = document.createElement("input"); 891 document.body.appendChild(host1); 892 document.body.appendChild(input); 893 894 flushLayout(); 895 896 // Test shadow tree which doesn't have anything tab-focusable. 897 host1.shadowRoot.querySelector("div").focus(); 898 synthesizeKey("KEY_Tab"); 899 is(document.activeElement, input, "Should have focused input element."); 900 synthesizeKey("KEY_Tab", {shiftKey: true}); 901 opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); 902 903 // Same test but with focusable elements in the tree... 904 var input2 = document.createElement("input"); 905 var host2 = host1.firstChild; 906 var host3 = host2.firstChild; 907 host2.insertBefore(input2, host3); 908 var input3 = document.createElement("input"); 909 lastHost.appendChild(input3); 910 911 flushLayout(); 912 913 host3.shadowRoot.querySelector("div").focus(); 914 synthesizeKey("KEY_Tab"); 915 is(document.activeElement, input3, "Should have focused input3 element."); 916 917 // ...and backwards 918 host3.shadowRoot.querySelector("div").focus(); 919 synthesizeKey("KEY_Tab", {shiftKey: true}); 920 is(document.activeElement, input2, "Should have focused input2 element."); 921 922 // Remove elements added to body element. 923 host1.remove(); 924 input.remove(); 925 926 // Tests expect body.firstChild to have focus. 927 document.body.firstChild.focus(); 928 } 929 930 // Bug 1558393 931 async function testBackwardsTabbingWithSlotsWithoutFocusableContent() { 932 let first = document.createElement("div"); 933 first.tabIndex = 0; 934 let host = document.createElement("div"); 935 host.tabIndex = 0; 936 let second = document.createElement("div"); 937 second.tabIndex = 0; 938 host.appendChild(document.createTextNode("foo")); 939 host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`; 940 941 document.body.appendChild(first); 942 document.body.appendChild(host); 943 document.body.appendChild(second); 944 945 flushLayout(); 946 947 first.focus(); 948 opener.is(document.activeElement, first, "First light div should have focus"); 949 synthesizeKey("KEY_Tab"); 950 opener.is(document.activeElement, host, "Host should be focused"); 951 synthesizeKey("KEY_Tab"); 952 opener.is(document.activeElement, second, "Second light div should be focused"); 953 954 // Now backwards 955 synthesizeKey("KEY_Tab", {shiftKey: true}); 956 opener.is(document.activeElement, host, "Focus should return to host"); 957 synthesizeKey("KEY_Tab", {shiftKey: true}); 958 opener.is(document.activeElement, first, "Focus should return to first light div"); 959 960 second.remove(); 961 host.remove(); 962 first.remove(); 963 } 964 965 async function runTest() { 966 967 await testTabbingThroughShadowDOMWithTabIndexes(); 968 await testTabbingThroughSimpleShadowDOM(); 969 await testTabbingThroughNestedShadowDOM(); 970 await testTabbingThroughDisplayContentsHost(); 971 await testTabbingThroughLightDOMShadowDOMLightDOM(); 972 await testFocusableHost(); 973 await testShiftTabbingThroughFocusableHost(); 974 await testTabbingThroughNestedSlot(); 975 await testTabbingThroughSlotInLightDOM(); 976 await testTabbingThroughFocusableSlotInLightDOM(); 977 await testTabbingThroughScrollableShadowDOM(); 978 await testTabbingThroughScrollableShadowHost(); 979 await testDeeplyNestedShadowTree(); 980 await testBackwardsTabbingWithSlotsWithoutFocusableContent(); 981 982 opener.didRunTests(); 983 window.close(); 984 } 985 986 function init() { 987 SimpleTest.waitForFocus(runTest); 988 } 989 </script> 990 <style> 991 </style> 992 <template id="template"> 993 <div style="overflow: hidden"> 994 <p tabindex="0" id="p1">component</p> 995 <p tabindex="0" id="p2">/component</p> 996 </div> 997 </template> 998 </head> 999 <body onload="init()"> 1000 </body> 1001 </html>