/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; loadTestSubscript("head_unified_extensions.js"); add_task(async function test_keyboard_navigation_activeScript() { const extension1 = ExtensionTestUtils.loadExtension({ manifest: { manifest_version: 3, name: "1", content_scripts: [ { matches: ["*://*/*"], js: ["script.js"], }, ], }, files: { "script.js": () => { browser.test.fail("this script should NOT have been executed"); }, }, useAddonManager: "temporary", }); const extension2 = ExtensionTestUtils.loadExtension({ manifest: { manifest_version: 3, name: "2", content_scripts: [ { matches: ["*://*/*"], js: ["script.js"], }, ], }, files: { "script.js": () => { browser.test.sendMessage("script executed"); }, }, useAddonManager: "temporary", }); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "https://example.org/" ); await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); await Promise.all([extension1.startup(), extension2.startup()]); // Open the extension panel. await openExtensionsPanel(); let item = getUnifiedExtensionsItem(extension1.id); ok(item, `expected item for ${extension1.id}`); info("moving focus to first item in the unified extensions panel"); let actionButton = item.querySelector( ".unified-extensions-item-action-button" ); let focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("VK_TAB", {}); await focused; is( actionButton, document.activeElement, "expected action button of first extension item to be focused" ); item = getUnifiedExtensionsItem(extension2.id); ok(item, `expected item for ${extension2.id}`); info("moving focus to second item in the unified extensions panel"); actionButton = item.querySelector(".unified-extensions-item-action-button"); focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("KEY_ArrowDown", {}); await focused; is( actionButton, document.activeElement, "expected action button of second extension item to be focused" ); info("granting permission"); const popupHidden = BrowserTestUtils.waitForEvent( document, "popuphidden", true ); EventUtils.synthesizeKey(" ", {}); await Promise.all([popupHidden, extension2.awaitMessage("script executed")]); await Promise.all([extension1.unload(), extension2.unload()]); }); add_task(async function test_keyboard_navigation_opens_menu() { const extension1 = ExtensionTestUtils.loadExtension({ manifest: { name: "1", // activeTab and browser_action needed to enable the action button in mv2. permissions: ["activeTab"], browser_action: {}, }, useAddonManager: "temporary", }); const extension2 = ExtensionTestUtils.loadExtension({ manifest: { name: "2", }, useAddonManager: "temporary", }); const extension3 = ExtensionTestUtils.loadExtension({ manifest: { manifest_version: 3, name: "3", // activeTab enables the action button without a browser action in mv3. permissions: ["activeTab"], }, useAddonManager: "temporary", }); await extension1.startup(); await extension2.startup(); await extension3.startup(); // Open the extension panel. await openExtensionsPanel(); let item = getUnifiedExtensionsItem(extension1.id); ok(item, `expected item for ${extension1.id}`); let messageDeck = item.querySelector(".unified-extensions-item-message-deck"); ok(messageDeck, "expected a message deck element"); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_DEFAULT, "expected selected message in the deck to be the default message" ); info("moving focus to first item in the unified extensions panel"); let actionButton = item.querySelector( ".unified-extensions-item-action-button" ); let focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("VK_TAB", {}); await focused; is( actionButton, document.activeElement, "expected action button of the first extension item to be focused" ); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER, "expected selected message in the deck to be the hover message" ); info( "moving focus to menu button of the first item in the unified extensions panel" ); let menuButton = item.querySelector(".unified-extensions-item-menu-button"); focused = BrowserTestUtils.waitForEvent(menuButton, "focus"); ok(menuButton, "expected menu button"); EventUtils.synthesizeKey("VK_TAB", {}); await focused; is( menuButton, document.activeElement, "expected menu button in first extension item to be focused" ); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_MENU_HOVER, "expected selected message in the deck to be the message when hovering the menu button" ); info("opening menu of the first item"); const contextMenu = document.getElementById( "unified-extensions-context-menu" ); ok(contextMenu, "expected menu"); const shown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); EventUtils.synthesizeKey(" ", {}); await shown; await closeChromeContextMenu(contextMenu.id, null); info("moving focus back to the action button of the first item"); focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); await focused; is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER, "expected selected message in the deck to be the hover message" ); // Moving to the third extension directly because the second extension cannot // do anything on the current page and its action button is disabled. Note // that this third extension does not have a browser action but it has // "activeTab", which makes the extension "clickable". This allows us to // verify the focus/blur behavior of custom elments. info("moving focus to third item in the panel"); item = getUnifiedExtensionsItem(extension3.id); ok(item, `expected item for ${extension3.id}`); actionButton = item.querySelector(".unified-extensions-item-action-button"); ok(actionButton, `expected action button for ${extension3.id}`); messageDeck = item.querySelector(".unified-extensions-item-message-deck"); ok(messageDeck, `expected message deck for ${extension3.id}`); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_DEFAULT, "expected selected message in the deck to be the default message" ); // Now that we checked everything on this third extension, let's actually // focus it with the arrow down key. focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("KEY_ArrowDown", {}); await focused; is( actionButton, document.activeElement, "expected action button of the third extension item to be focused" ); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER, "expected selected message in the deck to be the hover message" ); info( "moving focus to menu button of the third item in the unified extensions panel" ); menuButton = item.querySelector(".unified-extensions-item-menu-button"); focused = BrowserTestUtils.waitForEvent(menuButton, "focus"); ok(menuButton, "expected menu button"); EventUtils.synthesizeKey("VK_TAB", {}); await focused; is( menuButton, document.activeElement, "expected menu button in third extension item to be focused" ); is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_MENU_HOVER, "expected selected message in the deck to be the message when hovering the menu button" ); info("moving focus back to the action button of the third item"); focused = BrowserTestUtils.waitForEvent(actionButton, "focus"); EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }); await focused; is( messageDeck.selectedIndex, gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER, "expected selected message in the deck to be the hover message" ); await closeExtensionsPanel(); await extension1.unload(); await extension2.unload(); await extension3.unload(); }); add_task(async function test_open_panel_with_keyboard_navigation() { const { button, panel } = gUnifiedExtensions; ok(button, "expected button"); ok(panel, "expected panel"); const listView = getListView(); ok(listView, "expected list view"); // Force focus on the unified extensions button. const forceFocusUnifiedExtensionsButton = () => { button.setAttribute("tabindex", "-1"); button.focus(); button.removeAttribute("tabindex"); }; forceFocusUnifiedExtensionsButton(); // Use the "space" key to open the panel. let viewShown = BrowserTestUtils.waitForEvent(listView, "ViewShown"); EventUtils.synthesizeKey(" ", {}); await viewShown; await closeExtensionsPanel(); // Force focus on the unified extensions button again. forceFocusUnifiedExtensionsButton(); // Use the "return" key to open the panel. viewShown = BrowserTestUtils.waitForEvent(listView, "ViewShown"); EventUtils.synthesizeKey("KEY_Enter", {}); await viewShown; await closeExtensionsPanel(); });