browser_PanelMultiView_keyboard.js (23748B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Test the keyboard behavior of PanelViews. 8 */ 9 10 const kEmbeddedDocUrl = 11 'data:text/html,<textarea id="docTextarea">value</textarea><button id="docButton"></button>'; 12 13 let gAnchor; 14 let gPanel; 15 let gPanelMultiView; 16 let gMainView; 17 let gMainContext; 18 let gMainButton1; 19 let gMainMenulist; 20 let gMainRadiogroup; 21 let gMainTextbox; 22 let gMainButton2; 23 let gMainButton3; 24 let gCheckbox; 25 let gNamespacedLink; 26 let gLink; 27 let gMainTabOrder; 28 let gMainArrowOrder; 29 let gSubView; 30 let gSubButton; 31 let gSubTextarea; 32 let gBrowserView; 33 let gBrowserBrowser; 34 let gIframeView; 35 let gIframeIframe; 36 let gToggle; 37 let gMozButton; 38 let gMozButtonSubviewNav; 39 let gComponentView; 40 let gShadowRoot; 41 let gShadowRootButtonA; 42 let gShadowRootButtonB; 43 44 async function openPopup() { 45 let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown"); 46 PanelMultiView.openPopup(gPanel, gAnchor, "bottomright topright"); 47 await shown; 48 } 49 50 async function hidePopup() { 51 let hidden = BrowserTestUtils.waitForEvent(gPanel, "popuphidden"); 52 PanelMultiView.hidePopup(gPanel); 53 await hidden; 54 } 55 56 async function showSubView(view = gSubView) { 57 let shown = BrowserTestUtils.waitForEvent(view, "ViewShown"); 58 // We must show with an anchor so the Back button is generated. 59 gPanelMultiView.showSubView(view, gMainButton1); 60 await shown; 61 } 62 63 async function expectFocusAfterKey(aKey, aFocus) { 64 let res = aKey.match(/^(Shift\+)?(.+)$/); 65 let shift = Boolean(res[1]); 66 let key; 67 if (res[2].length == 1) { 68 key = res[2]; // Character. 69 } else { 70 key = "KEY_" + res[2]; // Tab, ArrowRight, etc. 71 } 72 info("Waiting for focus on " + aFocus.id); 73 let focused = BrowserTestUtils.waitForEvent(aFocus, "focus"); 74 EventUtils.synthesizeKey(key, { shiftKey: shift }); 75 await focused; 76 ok(true, aFocus.id + " focused after " + aKey + " pressed"); 77 } 78 79 add_setup(async function () { 80 // This shouldn't be necessary - but it is, because we use same-process frames. 81 // https://bugzilla.mozilla.org/show_bug.cgi?id=1565276 covers improving this. 82 await SpecialPowers.pushPrefEnv({ 83 set: [["security.allow_unsafe_parent_loads", true]], 84 }); 85 let navBar = document.getElementById("nav-bar"); 86 gAnchor = document.createXULElement("toolbarbutton"); 87 navBar.appendChild(gAnchor); 88 gPanel = document.createXULElement("panel"); 89 navBar.appendChild(gPanel); 90 gPanelMultiView = document.createXULElement("panelmultiview"); 91 gPanelMultiView.setAttribute("mainViewId", "testMainView"); 92 gPanel.appendChild(gPanelMultiView); 93 94 gMainView = document.createXULElement("panelview"); 95 gMainView.id = "testMainView"; 96 gPanelMultiView.appendChild(gMainView); 97 gMainContext = document.createXULElement("menupopup"); 98 gMainContext.id = "gMainContext"; 99 gMainView.appendChild(gMainContext); 100 gMainContext.appendChild(document.createXULElement("menuitem")); 101 gMainButton1 = document.createXULElement("button"); 102 gMainButton1.id = "gMainButton1"; 103 gMainView.appendChild(gMainButton1); 104 // We use this for anchoring subviews, so it must have a label. 105 gMainButton1.setAttribute("label", "gMainButton1"); 106 gMainButton1.setAttribute("context", "gMainContext"); 107 gMainMenulist = document.createXULElement("menulist"); 108 gMainMenulist.id = "gMainMenulist"; 109 gMainView.appendChild(gMainMenulist); 110 let menuPopup = document.createXULElement("menupopup"); 111 gMainMenulist.appendChild(menuPopup); 112 let item = document.createXULElement("menuitem"); 113 item.setAttribute("value", "1"); 114 item.setAttribute("selected", "true"); 115 menuPopup.appendChild(item); 116 item = document.createXULElement("menuitem"); 117 item.setAttribute("value", "2"); 118 menuPopup.appendChild(item); 119 gMainRadiogroup = document.createXULElement("radiogroup"); 120 gMainRadiogroup.id = "gMainRadiogroup"; 121 gMainView.appendChild(gMainRadiogroup); 122 let radio = document.createXULElement("radio"); 123 radio.setAttribute("value", "1"); 124 radio.setAttribute("selected", "true"); 125 gMainRadiogroup.appendChild(radio); 126 radio = document.createXULElement("radio"); 127 radio.setAttribute("value", "2"); 128 gMainRadiogroup.appendChild(radio); 129 gMainTextbox = document.createElement("input"); 130 gMainTextbox.id = "gMainTextbox"; 131 gMainView.appendChild(gMainTextbox); 132 gMainTextbox.setAttribute("value", "value"); 133 gMainButton2 = document.createXULElement("button"); 134 gMainButton2.id = "gMainButton2"; 135 gMainView.appendChild(gMainButton2); 136 gMainButton3 = document.createXULElement("button"); 137 gMainButton3.id = "gMainButton3"; 138 gMainView.appendChild(gMainButton3); 139 gCheckbox = document.createXULElement("checkbox"); 140 gCheckbox.id = "gCheckbox"; 141 gMainView.appendChild(gCheckbox); 142 143 // moz-support-links in XUL documents are created with the 144 // <html:a> tag and so we need to test this separately from 145 // <a> tags. 146 gNamespacedLink = document.createElementNS( 147 "http://www.w3.org/1999/xhtml", 148 "html:a" 149 ); 150 gNamespacedLink.href = "www.mozilla.org"; 151 gNamespacedLink.innerText = "gNamespacedLink"; 152 gNamespacedLink.id = "gNamespacedLink"; 153 gMainView.appendChild(gNamespacedLink); 154 gLink = document.createElement("a"); 155 gLink.href = "www.mozilla.org"; 156 gLink.innerText = "gLink"; 157 gLink.id = "gLink"; 158 gMainView.appendChild(gLink); 159 gToggle = document.createElement("moz-toggle"); 160 gToggle.label = "Test label"; 161 gMainView.appendChild(gToggle); 162 gMozButton = document.createElement("moz-button"); 163 gMozButton.label = "gMozButton"; 164 gMozButton.id = "gMozButton"; 165 gMainView.appendChild(gMozButton); 166 gMozButtonSubviewNav = document.createElement("moz-button"); 167 gMozButtonSubviewNav.label = "gMozButtonSubviewNav"; 168 gMozButtonSubviewNav.id = "gMozButtonSubviewNav"; 169 gMozButtonSubviewNav.classList.add("moz-button-subviewbutton-nav"); 170 gMainView.appendChild(gMozButtonSubviewNav); 171 172 gMainTabOrder = [ 173 gMainButton1, 174 gMainMenulist, 175 gMainRadiogroup, 176 gMainTextbox, 177 gMainButton2, 178 gMainButton3, 179 gCheckbox, 180 gNamespacedLink, 181 gLink, 182 gToggle, 183 gMozButton, 184 gMozButtonSubviewNav, 185 ]; 186 gMainArrowOrder = [ 187 gMainButton1, 188 gMainButton2, 189 gMainButton3, 190 gCheckbox, 191 gNamespacedLink, 192 gLink, 193 gToggle, 194 gMozButton, 195 gMozButtonSubviewNav, 196 ]; 197 198 gSubView = document.createXULElement("panelview"); 199 gSubView.id = "testSubView"; 200 gPanelMultiView.appendChild(gSubView); 201 gSubButton = document.createXULElement("button"); 202 gSubView.appendChild(gSubButton); 203 gSubTextarea = document.createElementNS( 204 "http://www.w3.org/1999/xhtml", 205 "textarea" 206 ); 207 gSubTextarea.id = "gSubTextarea"; 208 gSubView.appendChild(gSubTextarea); 209 gSubTextarea.value = "value"; 210 211 gBrowserView = document.createXULElement("panelview"); 212 gBrowserView.id = "testBrowserView"; 213 gPanelMultiView.appendChild(gBrowserView); 214 gBrowserBrowser = document.createXULElement("browser"); 215 gBrowserBrowser.id = "GBrowserBrowser"; 216 gBrowserBrowser.setAttribute("type", "content"); 217 gBrowserBrowser.setAttribute("src", kEmbeddedDocUrl); 218 gBrowserBrowser.style.minWidth = gBrowserBrowser.style.minHeight = "100px"; 219 gBrowserView.appendChild(gBrowserBrowser); 220 221 gIframeView = document.createXULElement("panelview"); 222 gIframeView.id = "testIframeView"; 223 gPanelMultiView.appendChild(gIframeView); 224 gIframeIframe = document.createXULElement("iframe"); 225 gIframeIframe.id = "gIframeIframe"; 226 gIframeIframe.setAttribute("src", kEmbeddedDocUrl); 227 gIframeView.appendChild(gIframeIframe); 228 229 gComponentView = document.createXULElement("panelview"); 230 gComponentView.id = "testComponentView"; 231 gPanelMultiView.appendChild(gComponentView); 232 // Shadow root that delegates focus with multiple buttons 233 gShadowRoot = document.createElement("section"); 234 gShadowRoot.id = "gShadowRoot"; 235 gShadowRoot.dataset.navigableWithTabOnly = "true"; 236 gShadowRoot.attachShadow({ mode: "open", delegatesFocus: true }); 237 gShadowRootButtonA = document.createElement("moz-button"); 238 gShadowRootButtonA.id = "gShadowRootButtonA"; 239 gShadowRootButtonA.label = "Button A"; 240 gShadowRootButtonB = document.createElement("moz-button"); 241 gShadowRootButtonB.id = "gShadowRootButtonB"; 242 gShadowRootButtonB.label = "Button B"; 243 gShadowRoot.shadowRoot.appendChild(gShadowRootButtonA); 244 gShadowRoot.shadowRoot.appendChild(gShadowRootButtonB); 245 gComponentView.appendChild(gShadowRoot); 246 247 registerCleanupFunction(() => { 248 gAnchor.remove(); 249 gPanel.remove(); 250 }); 251 }); 252 253 // Test that the tab key focuses all expected controls. 254 add_task(async function testTab() { 255 await openPopup(); 256 for (let elem of gMainTabOrder) { 257 await expectFocusAfterKey("Tab", elem); 258 } 259 // Wrap around. 260 await expectFocusAfterKey("Tab", gMainTabOrder[0]); 261 await hidePopup(); 262 }); 263 264 // Test that the shift+tab key focuses all expected controls. 265 add_task(async function testShiftTab() { 266 await openPopup(); 267 for (let i = gMainTabOrder.length - 1; i >= 0; --i) { 268 await expectFocusAfterKey("Shift+Tab", gMainTabOrder[i]); 269 } 270 // Wrap around. 271 await expectFocusAfterKey( 272 "Shift+Tab", 273 gMainTabOrder[gMainTabOrder.length - 1] 274 ); 275 await hidePopup(); 276 }); 277 278 // Test that the down arrow key skips menulists and textboxes. 279 add_task(async function testDownArrow() { 280 await openPopup(); 281 for (let elem of gMainArrowOrder) { 282 await expectFocusAfterKey("ArrowDown", elem); 283 } 284 // Wrap around. 285 await expectFocusAfterKey("ArrowDown", gMainArrowOrder[0]); 286 await hidePopup(); 287 }); 288 289 // Test that the up arrow key skips menulists and textboxes. 290 add_task(async function testUpArrow() { 291 await openPopup(); 292 for (let i = gMainArrowOrder.length - 1; i >= 0; --i) { 293 await expectFocusAfterKey("ArrowUp", gMainArrowOrder[i]); 294 } 295 // Wrap around. 296 await expectFocusAfterKey( 297 "ArrowUp", 298 gMainArrowOrder[gMainArrowOrder.length - 1] 299 ); 300 await hidePopup(); 301 }); 302 303 // Test that the home/end keys move to the first/last controls. 304 add_task(async function testHomeEnd() { 305 await openPopup(); 306 await expectFocusAfterKey("Home", gMainArrowOrder[0]); 307 await expectFocusAfterKey("End", gMainArrowOrder[gMainArrowOrder.length - 1]); 308 await hidePopup(); 309 }); 310 311 // Test that the up/down arrow keys work as expected in menulists. 312 add_task(async function testArrowsMenulist() { 313 await openPopup(); 314 gMainMenulist.focus(); 315 is(document.activeElement, gMainMenulist, "menulist focused"); 316 is(gMainMenulist.value, "1", "menulist initial value 1"); 317 if (AppConstants.platform == "macosx") { 318 // On Mac, down/up arrows just open the menulist. 319 let popup = gMainMenulist.menupopup; 320 for (let key of ["ArrowDown", "ArrowUp"]) { 321 let shown = BrowserTestUtils.waitForEvent(popup, "popupshown"); 322 EventUtils.synthesizeKey("KEY_" + key); 323 await shown; 324 ok(gMainMenulist.open, "menulist open after " + key); 325 let hidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); 326 EventUtils.synthesizeKey("KEY_Escape"); 327 await hidden; 328 ok(!gMainMenulist.open, "menulist closed after Escape"); 329 } 330 } else { 331 // On other platforms, down/up arrows change the value without opening the 332 // menulist. 333 EventUtils.synthesizeKey("KEY_ArrowDown"); 334 is( 335 document.activeElement, 336 gMainMenulist, 337 "menulist still focused after ArrowDown" 338 ); 339 is(gMainMenulist.value, "2", "menulist value 2 after ArrowDown"); 340 EventUtils.synthesizeKey("KEY_ArrowUp"); 341 is( 342 document.activeElement, 343 gMainMenulist, 344 "menulist still focused after ArrowUp" 345 ); 346 is(gMainMenulist.value, "1", "menulist value 1 after ArrowUp"); 347 } 348 await hidePopup(); 349 }); 350 351 // Test that the tab key closes an open menu list. 352 add_task(async function testTabOpenMenulist() { 353 await openPopup(); 354 gMainMenulist.focus(); 355 is(document.activeElement, gMainMenulist, "menulist focused"); 356 let popup = gMainMenulist.menupopup; 357 let shown = BrowserTestUtils.waitForEvent(popup, "popupshown"); 358 gMainMenulist.open = true; 359 await shown; 360 ok(gMainMenulist.open, "menulist open"); 361 let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); 362 EventUtils.synthesizeKey("KEY_Tab"); 363 await menuHidden; 364 ok(!gMainMenulist.open, "menulist closed after Tab"); 365 is(gPanel.state, "open", "Panel should be open"); 366 await hidePopup(); 367 }); 368 369 if (AppConstants.platform == "macosx") { 370 // Test that using the mouse to open a menulist still allows keyboard navigation 371 // inside it. 372 add_task(async function testNavigateMouseOpenedMenulist() { 373 await openPopup(); 374 let popup = gMainMenulist.menupopup; 375 let shown = BrowserTestUtils.waitForEvent(popup, "popupshown"); 376 gMainMenulist.open = true; 377 await shown; 378 ok(gMainMenulist.open, "menulist open"); 379 let oldFocus = document.activeElement; 380 let oldSelectedItem = gMainMenulist.selectedItem; 381 ok( 382 oldSelectedItem.hasAttribute("_moz-menuactive"), 383 "Selected item should show up as active" 384 ); 385 EventUtils.synthesizeKey("KEY_ArrowDown"); 386 await TestUtils.waitForCondition( 387 () => !oldSelectedItem.hasAttribute("_moz-menuactive") 388 ); 389 is(oldFocus, document.activeElement, "Focus should not move on mac"); 390 ok( 391 !oldSelectedItem.hasAttribute("_moz-menuactive"), 392 "Selected item should change" 393 ); 394 395 let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); 396 EventUtils.synthesizeKey("KEY_Tab"); 397 await menuHidden; 398 ok(!gMainMenulist.open, "menulist closed after Tab"); 399 is(gPanel.state, "open", "Panel should be open"); 400 await hidePopup(); 401 }); 402 } 403 404 // Test that the up/down arrow keys work as expected in radiogroups. 405 add_task(async function testArrowsRadiogroup() { 406 await openPopup(); 407 gMainRadiogroup.focus(); 408 is(document.activeElement, gMainRadiogroup, "radiogroup focused"); 409 is(gMainRadiogroup.value, "1", "radiogroup initial value 1"); 410 EventUtils.synthesizeKey("KEY_ArrowDown"); 411 is( 412 document.activeElement, 413 gMainRadiogroup, 414 "radiogroup still focused after ArrowDown" 415 ); 416 is(gMainRadiogroup.value, "2", "radiogroup value 2 after ArrowDown"); 417 EventUtils.synthesizeKey("KEY_ArrowUp"); 418 is( 419 document.activeElement, 420 gMainRadiogroup, 421 "radiogroup still focused after ArrowUp" 422 ); 423 is(gMainRadiogroup.value, "1", "radiogroup value 1 after ArrowUp"); 424 await hidePopup(); 425 }); 426 427 // Test that pressing space in a textbox inserts a space (instead of trying to 428 // activate the control). 429 add_task(async function testSpaceTextbox() { 430 await openPopup(); 431 gMainTextbox.focus(); 432 gMainTextbox.selectionStart = gMainTextbox.selectionEnd = 0; 433 EventUtils.synthesizeKey(" "); 434 is(gMainTextbox.value, " value", "Space typed into textbox"); 435 gMainTextbox.value = "value"; 436 await hidePopup(); 437 }); 438 439 // Tests that the left arrow key normally moves back to the previous view. 440 add_task(async function testLeftArrow() { 441 await openPopup(); 442 await showSubView(); 443 let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown"); 444 EventUtils.synthesizeKey("KEY_ArrowLeft"); 445 await shown; 446 ok("Moved to previous view after ArrowLeft"); 447 await hidePopup(); 448 }); 449 450 // Tests that the left arrow key moves the caret in a textarea in a subview 451 // (instead of going back to the previous view). 452 add_task(async function testLeftArrowTextarea() { 453 await openPopup(); 454 await showSubView(); 455 gSubTextarea.focus(); 456 is(document.activeElement, gSubTextarea, "textarea focused"); 457 EventUtils.synthesizeKey("KEY_End"); 458 is(gSubTextarea.selectionStart, 5, "selectionStart 5 after End"); 459 EventUtils.synthesizeKey("KEY_ArrowLeft"); 460 is(gSubTextarea.selectionStart, 4, "selectionStart 4 after ArrowLeft"); 461 is(document.activeElement, gSubTextarea, "textarea still focused"); 462 await hidePopup(); 463 }); 464 465 add_task(async function testRightArrow() { 466 await openPopup(); 467 468 // Ensure non moz-button-subviewbutton-navs respond to the right arrow 469 let clicked = false; 470 let assertNoClick = () => { 471 clicked = true; 472 }; 473 gMozButton.addEventListener("click", assertNoClick); 474 // Focus using the arrow keys so PanelMultiView#selectedElement is set 475 for (let i = 0; i < gMainTabOrder.length; i++) { 476 EventUtils.synthesizeKey("KEY_ArrowUp"); 477 if (document.activeElement == gMozButton) { 478 break; 479 } 480 } 481 is(document.activeElement, gMozButton, "gMozButton focused"); 482 EventUtils.synthesizeKey("KEY_ArrowRight"); 483 ok(!clicked, "click handler should not be triggered for regular button"); 484 EventUtils.synthesizeKey(" "); 485 ok(clicked, "click handler triggered on space"); 486 gMozButton.removeEventListener("click", assertNoClick); 487 488 // Ensure moz-button-subviewbutton-nav gets click on right arrow 489 gMozButtonSubviewNav.addEventListener( 490 "click", 491 () => gPanelMultiView.showSubView(gSubView, gMozButtonSubviewNav), 492 { once: true } 493 ); 494 let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown"); 495 EventUtils.synthesizeKey("KEY_ArrowDown"); 496 is( 497 document.activeElement, 498 gMozButtonSubviewNav, 499 "gMozButtonSubviewNav focused" 500 ); 501 EventUtils.synthesizeKey("KEY_ArrowRight"); 502 await shown; 503 ok(true, "Triggered moz-button-subviewbutton-nav on right arrow"); 504 await hidePopup(); 505 }); 506 507 // Test navigation to a button which is initially disabled and later enabled. 508 add_task(async function testDynamicButton() { 509 gMainButton2.disabled = true; 510 await openPopup(); 511 await expectFocusAfterKey("ArrowDown", gMainButton1); 512 await expectFocusAfterKey("ArrowDown", gMainButton3); 513 gMainButton2.disabled = false; 514 await expectFocusAfterKey("ArrowUp", gMainButton2); 515 await hidePopup(); 516 }); 517 518 add_task(async function testActivation() { 519 function checkActivated(elem, activationFn, reason) { 520 let activated = false; 521 elem.onclick = function () { 522 activated = true; 523 }; 524 activationFn(); 525 ok(activated, "Should have activated button after " + reason); 526 elem.onclick = null; 527 } 528 await openPopup(); 529 await expectFocusAfterKey("ArrowDown", gMainButton1); 530 checkActivated( 531 gMainButton1, 532 () => EventUtils.synthesizeKey("KEY_Enter"), 533 "pressing enter" 534 ); 535 checkActivated( 536 gMainButton1, 537 () => EventUtils.synthesizeKey(" "), 538 "pressing space" 539 ); 540 checkActivated( 541 gMainButton1, 542 () => EventUtils.synthesizeKey("KEY_Enter", { code: "NumpadEnter" }), 543 "pressing numpad enter" 544 ); 545 await hidePopup(); 546 }); 547 548 // Test that keyboard activation works for buttons responding to mousedown 549 // events (instead of command or click). The Library button does this, for 550 // example. 551 add_task(async function testActivationMousedown() { 552 await openPopup(); 553 await expectFocusAfterKey("ArrowDown", gMainButton1); 554 let activated = false; 555 gMainButton1.onmousedown = function () { 556 activated = true; 557 }; 558 EventUtils.synthesizeKey(" "); 559 ok(activated, "mousedown activated after space"); 560 gMainButton1.onmousedown = null; 561 await hidePopup(); 562 }); 563 564 // Test that tab and the arrow keys aren't overridden in embedded documents. 565 async function testTabArrowsEmbeddedDoc(aView, aEmbedder) { 566 await openPopup(); 567 await showSubView(aView); 568 let doc = aEmbedder.contentDocument; 569 if (doc.readyState != "complete" || doc.location.href != kEmbeddedDocUrl) { 570 info(`Embedded doc readyState ${doc.readyState}, location ${doc.location}`); 571 info("Waiting for load on embedder"); 572 // Browsers don't fire load events, and iframes don't fire load events in 573 // typeChrome windows. We can handle both by using a capturing event 574 // listener to capture the load event from the child document. 575 await BrowserTestUtils.waitForEvent(aEmbedder, "load", true); 576 // The original doc might have been a temporary about:blank, so fetch it 577 // again. 578 doc = aEmbedder.contentDocument; 579 } 580 is(doc.location.href, kEmbeddedDocUrl, "Embedded doc has correct URl"); 581 let backButton = aView.querySelector(".subviewbutton-back"); 582 backButton.id = "docBack"; 583 await expectFocusAfterKey("Tab", backButton); 584 // Documents don't have an id property, but expectFocusAfterKey wants one. 585 doc.id = "doc"; 586 await expectFocusAfterKey("Tab", doc); 587 // Make sure tab/arrows aren't overridden within the embedded document. 588 let textarea = doc.getElementById("docTextarea"); 589 // Tab should really focus the textarea, but default tab handling seems to 590 // skip everything inside the embedder element when run in this test. This 591 // behaves as expected in real panels, though. Force focus to the textarea 592 // and then test from there. 593 textarea.focus(); 594 is(doc.activeElement, textarea, "textarea focused"); 595 is(textarea.selectionStart, 0, "selectionStart initially 0"); 596 EventUtils.synthesizeKey("KEY_ArrowRight"); 597 is(textarea.selectionStart, 1, "selectionStart 1 after ArrowRight"); 598 EventUtils.synthesizeKey("KEY_ArrowLeft"); 599 is(textarea.selectionStart, 0, "selectionStart 0 after ArrowLeft"); 600 is(doc.activeElement, textarea, "textarea still focused"); 601 let docButton = doc.getElementById("docButton"); 602 await expectFocusAfterKey("Tab", docButton); 603 await hidePopup(); 604 } 605 606 // Test that tab and the arrow keys aren't overridden in embedded browsers. 607 add_task(async function testTabArrowsBrowser() { 608 await testTabArrowsEmbeddedDoc(gBrowserView, gBrowserBrowser); 609 }); 610 611 // Test that tab and the arrow keys aren't overridden in embedded iframes. 612 add_task(async function testTabArrowsIframe() { 613 await testTabArrowsEmbeddedDoc(gIframeView, gIframeIframe); 614 }); 615 616 // Test that the arrow keys aren't overridden in context menus. 617 add_task(async function testArowsContext() { 618 await openPopup(); 619 await expectFocusAfterKey("ArrowDown", gMainButton1); 620 let shown = BrowserTestUtils.waitForEvent(gMainContext, "popupshown"); 621 // There's no cross-platform way to open a context menu from the keyboard. 622 gMainContext.openPopup(gMainButton1); 623 await shown; 624 let item = gMainContext.children[0]; 625 ok( 626 !item.getAttribute("_moz-menuactive"), 627 "First context menu item initially inactive" 628 ); 629 let active = BrowserTestUtils.waitForEvent(item, "DOMMenuItemActive"); 630 EventUtils.synthesizeKey("KEY_ArrowDown"); 631 await active; 632 ok( 633 item.getAttribute("_moz-menuactive"), 634 "First context menu item active after ArrowDown" 635 ); 636 is( 637 document.activeElement, 638 gMainButton1, 639 "gMainButton1 still focused after ArrowDown" 640 ); 641 let hidden = BrowserTestUtils.waitForEvent(gMainContext, "popuphidden"); 642 gMainContext.hidePopup(); 643 await hidden; 644 await hidePopup(); 645 }); 646 647 add_task(async function testMozToggle() { 648 await openPopup(); 649 is(gToggle.pressed, false, "The toggle is not pressed initially."); 650 // Focus the toggle via keyboard navigation. 651 while (document.activeElement !== gToggle) { 652 EventUtils.synthesizeKey("KEY_Tab"); 653 } 654 EventUtils.synthesizeKey(" "); 655 await gToggle.updateComplete; 656 is(gToggle.pressed, true, "Toggle pressed state changes via spacebar."); 657 EventUtils.synthesizeKey("KEY_Enter"); 658 await gToggle.updateComplete; 659 is(gToggle.pressed, false, "Toggle pressed state changes via enter."); 660 await hidePopup(); 661 }); 662 663 // Test that tab key is not overridden in elements that capture focus. 664 add_task(async function testTabCapturesFocus() { 665 await openPopup(); 666 await showSubView(gComponentView); 667 668 let backButton = gComponentView.querySelector(".subviewbutton-back"); 669 backButton.id = "shadowBack"; 670 await expectFocusAfterKey("Tab", backButton); 671 672 // Only Button A can be navigated to before looping to back button. 673 await expectFocusAfterKey("Tab", gShadowRootButtonA); 674 675 await expectFocusAfterKey("Tab", backButton); 676 677 gShadowRoot.dataset.capturesFocus = "true"; 678 679 // Both buttons can be focused before looping. 680 await expectFocusAfterKey("Tab", gShadowRootButtonA); 681 await expectFocusAfterKey("Tab", gShadowRootButtonB); 682 683 await expectFocusAfterKey("Tab", backButton); 684 685 await hidePopup(); 686 });