/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ ChromeUtils.defineESModuleGetters(this, { setTimeout: "resource://gre/modules/Timer.sys.mjs", }); const TOPSITES = "about:robots"; const CONFIG = [ { identifier: "engine1" }, { identifier: "engine2" }, { identifier: "engine3" }, ]; let urlbarInput = gURLBar.inputField; let searchmodeSwitcher = gURLBar.querySelector(".searchmode-switcher"); add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.scotchBonnet.enableOverride", true], ["browser.urlbar.suggest.topsites", true], ["browser.newtabpage.activity-stream.default.sites", TOPSITES], ["widget.macos.native-anchored-menus", false], ], }); await PlacesUtils.history.clear(); // Use top sites to make sure the results panel opens even on empty queries. await updateTopSites( sites => sites && sites.length == 1 && sites[0].url == TOPSITES ); await SearchTestUtils.updateRemoteSettingsConfig(CONFIG); }); add_task( async function test_focus_by_tab_with_no_selected_element_with_urlbar_focused_by_key() { for (const shiftKey of [false, true]) { info(`Test for shifrKey:${shiftKey}`); info("Focus on urlbar by key"); await focusOnURLbar(() => { EventUtils.synthesizeKey("l", { accelKey: true }); }); Assert.ok(!gURLBar.view.selectedElement); let ok = false; for (let i = 0; i < 10; i++) { EventUtils.synthesizeKey("KEY_Tab", { shiftKey }); ok = document.activeElement != urlbarInput && document.activeElement != searchmodeSwitcher; if (ok) { break; } } Assert.ok(ok, "Focus was moved to a component other than the urlbar"); Assert.ok(!gURLBar.view.isOpen); } } ); add_task( async function test_focus_by_tab_with_no_selected_element_with_urlbar_focused_by_click() { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.suggest.topsites", false]], }); let results = [ new UrlbarResult({ type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.HISTORY, payload: { url: "https://mozilla.org/a", }, }), new UrlbarResult({ type: UrlbarUtils.RESULT_TYPE.URL, source: UrlbarUtils.RESULT_SOURCE.HISTORY, payload: { url: "https://mozilla.org/b", }, }), ]; let provider = new UrlbarTestUtils.TestProvider({ results, priority: 1 }); let providersManager = ProvidersManager.getInstanceForSap("urlbar"); providersManager.registerProvider(provider); const FOCUS_ORDER_ASSERTIONS = [ () => Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getFirstSelectableElement() ), () => Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getLastSelectableElement() ), () => Assert.equal(document.activeElement, searchmodeSwitcher), ]; for (const shiftKey of [true, false]) { info("Focus on urlbar by click"); await focusOnURLbar(() => { EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); }); Assert.ok(!gURLBar.view.selectedElement); await BrowserTestUtils.waitForCondition(async () => { if (UrlbarTestUtils.getResultCount(window) != 2) { return false; } let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); return result.providerName == provider.name; }); Assert.ok(true, "This test needs exact 2 results"); for (const assert of shiftKey ? [...FOCUS_ORDER_ASSERTIONS].reverse() : FOCUS_ORDER_ASSERTIONS) { EventUtils.synthesizeKey("KEY_Tab", { shiftKey }); assert(); } Assert.ok(gURLBar.view.isOpen); gURLBar.view.close(); gURLBar.handleRevert(); } providersManager.unregisterProvider(provider); await SpecialPowers.popPrefEnv(); } ); async function focusOnURLbar(focus) { gURLBar.focus(); gURLBar.blur(); await UrlbarTestUtils.promisePopupOpen(window, () => { focus(); }); } /** * Test we can open the SearchModeSwitcher with various keys * * @param {string} openKey - The keyboard character used to open the popup. */ async function test_open_switcher(openKey) { let popup = UrlbarTestUtils.searchModeSwitcherPopup(window).parentElement; let promiseMenuOpen = BrowserTestUtils.waitForEvent(popup, "popupshown"); info(`Open the urlbar and open the switcher via keyboard (${openKey})`); await focusSearchModeSwitcher(); EventUtils.synthesizeKey(openKey); await promiseMenuOpen; Assert.ok(true, "Search mode switcher was opened"); EventUtils.synthesizeKey("KEY_Escape"); } /** * Test that not all characters will open the SearchModeSwitcher * * @param {string} dontOpenKey - The keyboard character we will ignore. */ async function test_dont_open_switcher(dontOpenKey) { let popup = UrlbarTestUtils.searchModeSwitcherPopup(window).parentElement; let popupOpened = false; let opened = () => { popupOpened = true; }; info(`Pressing key that should not open the switcher (${dontOpenKey})`); popup.addEventListener("popupshown", opened); await focusSearchModeSwitcher(); EventUtils.synthesizeKey(dontOpenKey); /* eslint-disable mozilla/no-arbitrary-setTimeout */ await new Promise(r => setTimeout(r, 50)); Assert.ok(!popupOpened, "The popup was not opened"); popup.removeEventListener("popupshown", opened); } /** * Test we can navigate the SearchModeSwitcher with various keys * * @param {string} navKey - The keyboard character used to navigate. * @param {Int} navTimes - The number of times we press that key. * @param {object} searchMode - The searchMode that we expect to select. */ async function test_navigate_switcher(navKey, navTimes, searchMode) { let popup = UrlbarTestUtils.searchModeSwitcherPopup(window).parentElement; let promiseMenuOpen = BrowserTestUtils.waitForEvent(popup, "popupshown"); info("Open the urlbar and open the switcher via Enter key"); await focusSearchModeSwitcher(); EventUtils.synthesizeKey("KEY_Enter"); await promiseMenuOpen; info("Select first result and enter search mode"); for (let i = 0; i < navTimes; i++) { EventUtils.synthesizeKey(navKey); } EventUtils.synthesizeKey("KEY_Enter"); await UrlbarTestUtils.promiseSearchComplete(window); await UrlbarTestUtils.assertSearchMode(window, searchMode); info("Exit the search mode"); await UrlbarTestUtils.promisePopupClose(window, () => { EventUtils.synthesizeKey("KEY_Escape"); }); EventUtils.synthesizeKey("KEY_Escape"); await UrlbarTestUtils.assertSearchMode(window, null); } add_task(async function test_keyboard_nav() { await test_open_switcher("KEY_Enter"); await test_open_switcher(" "); await test_open_switcher("KEY_ArrowDown"); await test_dont_open_switcher("a"); await test_dont_open_switcher("x"); let searchModeTemplate = { entry: "searchbutton", isGeneralPurposeEngine: true, isPreview: false, source: 3, }; await test_navigate_switcher("KEY_ArrowDown", 0, { engineName: "engine1", ...searchModeTemplate, }); await test_navigate_switcher("KEY_ArrowDown", 1, { engineName: "engine2", ...searchModeTemplate, }); await test_navigate_switcher("KEY_ArrowDown", 2, { engineName: "engine3", ...searchModeTemplate, }); }); add_task(async function test_open_switcher_with_page() { info("Open a page"); await BrowserTestUtils.withNewTab( { gBrowser, url: "https://example.com/", }, async function () { info("Click on the urlbar to select all text"); await focusOnURLbar(() => EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}) ); info("Input a char to show the Unified Search Button"); EventUtils.synthesizeKey("a"); info("Move the focus to the button"); EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); await TestUtils.waitForCondition( () => document.activeElement == searchmodeSwitcher ); info("Do the focus test"); let popup = UrlbarTestUtils.searchModeSwitcherPopup(window).parentElement; let promiseHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); await test_open_switcher(" "); info("Close the Unified Search popup"); EventUtils.synthesizeKey("KEY_Escape"); await promiseHidden; } ); }); add_task(async function test_focus_on_switcher_by_tab() { const input = "abc"; info(`Open urlbar view with query [${input}]`); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: input, }); info("Focus on Unified Search Button by tab"); EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); await TestUtils.waitForCondition( () => document.activeElement == searchmodeSwitcher ); Assert.ok(true, "Unified Search Button gets the focus"); Assert.ok( !UrlbarTestUtils.searchModeSwitcherPopup(window).hasAttribute("open"), "Switcher popup should not be opened" ); Assert.ok(gURLBar.view.isOpen, "Urlbar view panel has been opening"); Assert.equal(gURLBar.value, input, "Inputted value still be on urlbar"); info("Open the switcher popup by key"); await UrlbarTestUtils.openSearchModeSwitcher(window, () => { EventUtils.synthesizeKey("KEY_Enter"); }); Assert.equal(gURLBar.view.isOpen, false, "Urlbar view panel is closed"); Assert.equal(gURLBar.value, input, "Inputted value still be on urlbar"); info("Close the switcher popup by Escape"); let promiseMenuClose = UrlbarTestUtils.searchModeSwitcherPopupClosed(window); EventUtils.synthesizeKey("KEY_Escape"); await promiseMenuClose; Assert.equal(document.activeElement, urlbarInput, "Urlbar gets the focus"); Assert.equal( gURLBar.view.panel.hasAttribute("hide-temporarily"), false, "Urlbar view panel is opened" ); Assert.equal(gURLBar.value, input, "Inputted value still be on urlbar"); }); add_task(async function test_focus_order_by_tab() { await PlacesTestUtils.addBookmarkWithDetails({ uri: "https://example.com/", title: "abc", }); const FOCUS_ORDER_ASSERTIONS = [ () => Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getLastSelectableElement() ), () => Assert.equal(document.activeElement, searchmodeSwitcher), () => Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getFirstSelectableElement() ), () => Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getLastSelectableElement() ), () => Assert.equal(document.activeElement, searchmodeSwitcher), ]; for (const shiftKey of [false, true]) { info("Open urlbar view"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "abc", }); Assert.equal(document.activeElement, urlbarInput); Assert.equal( gURLBar.view.selectedElement, gURLBar.view.getFirstSelectableElement() ); await BrowserTestUtils.waitForCondition( () => UrlbarTestUtils.getResultCount(window) == 2 ); Assert.ok(true, "This test needs exact 2 results"); for (const assert of shiftKey ? [...FOCUS_ORDER_ASSERTIONS].reverse() : FOCUS_ORDER_ASSERTIONS) { EventUtils.synthesizeKey("KEY_Tab", { shiftKey }); assert(); } gURLBar.handleRevert(); } await PlacesUtils.bookmarks.eraseEverything(); }); add_task(async function test_focus_order_by_tab_with_no_results() { for (const scotchBonnetEnabled of [true, false]) { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.scotchBonnet.enableOverride", scotchBonnetEnabled], ], }); await test_focus_order_with_no_results({ input: "", shiftKey: false }); await test_focus_order_with_no_results({ input: "", shiftKey: true }); await test_focus_order_with_no_results({ input: "test", shiftKey: false }); await test_focus_order_with_no_results({ input: "test", shiftKey: true }); await SpecialPowers.popPrefEnv(); } }); async function test_focus_order_with_no_results({ input, shiftKey }) { const scotchBonnetEnabled = UrlbarPrefs.get("scotchBonnet.enableOverride"); info(`Test for ${JSON.stringify({ scotchBonnetEnabled, input, shiftKey })}`); info("Open urlbar results"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "", }); info("Enter Tabs mode"); const keywordToEnterTabsMode = scotchBonnetEnabled ? "@tabs " : "% "; keywordToEnterTabsMode.split("").forEach(c => EventUtils.synthesizeKey(c)); await BrowserTestUtils.waitForCondition( () => UrlbarTestUtils.getResultCount(window) == 0, "Wait until updating the results" ); Assert.equal(document.activeElement, urlbarInput); info("Enter extra value"); input.split("").forEach(c => EventUtils.synthesizeKey(c)); let ok = false; for (let i = 0; i < 10; i++) { EventUtils.synthesizeKey("KEY_Tab", { shiftKey }); ok = document.activeElement != urlbarInput && document.activeElement != searchmodeSwitcher; if (ok) { break; } } Assert.ok(ok, "Focus was moved to a component other than the urlbar"); info("Clean up"); gURLBar.searchMode = null; } add_task(async function test_focus_order_by_tab_with_no_selected_element() { for (const shiftKey of [false, true]) { info(`Test for shiftKey:${shiftKey}`); info("Open urlbar results"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "", }); Assert.equal(document.activeElement, urlbarInput); Assert.ok(gURLBar.view.isOpen); Assert.ok(!gURLBar.view.selectedElement); let ok = false; for (let i = 0; i < 10; i++) { EventUtils.synthesizeKey("KEY_Tab", { shiftKey }); ok = document.activeElement != urlbarInput && document.activeElement != searchmodeSwitcher; if (ok) { break; } } Assert.ok(ok, "Focus was moved to a component other than the urlbar"); } }); add_task(async function test_urlbar_focus_after_switcher_lost() { info("Open the urlbar"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "abc", }); Assert.ok( BrowserTestUtils.isVisible(gURLBar.view.panel), "The UrlbarView is opened" ); Assert.ok( gURLBar.hasAttribute("focused"), "The #urlbar element has 'focused' attribute" ); info("Move the focus to the switcher button"); await focusSearchModeSwitcher(); info("Move the focus to browser element"); // We intentionally turn off this a11y check, because the following click is // purposefully targeting a non-interactive element. AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false }); EventUtils.synthesizeMouseAtCenter(document.getElementById("browser"), {}); AccessibilityUtils.resetEnv(); Assert.ok( !gURLBar.hasAttribute("focused"), "The #urlbar element does not have 'focused' attribute" ); info("Clean up"); gURLBar.handleRevert(); }); add_task(async function test_esc_on_UnifiedSearchButton() { info("Open urlbar results"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "abc", }); Assert.equal(document.activeElement, urlbarInput); info("Focus on Unified Search Button by tab"); EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); await TestUtils.waitForCondition( () => document.activeElement == searchmodeSwitcher ); Assert.ok(true, "Unified Search Button gets the focus"); info("Press ESC key"); EventUtils.synthesizeKey("KEY_Escape"); await TestUtils.waitForCondition(() => document.activeElement == urlbarInput); Assert.equal( gURLBar.view.isOpen, false, "The urlbar result view should be closed" ); gURLBar.handleRevert(); }); add_task(async function test_ctrl_tab() { info("Prepare multiple tabs"); let mainTab = gBrowser.selectedTab; let newTab1 = BrowserTestUtils.addTab(gBrowser); let newTab2 = BrowserTestUtils.addTab(gBrowser); info("Open urlbar results"); const query = "abc"; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: query, }); const testData = [ { shiftKey: false, expectedOrder: [newTab1, newTab2, mainTab], }, { shiftKey: true, expectedOrder: [newTab2, newTab1, mainTab], }, ]; for (let { shiftKey, expectedOrder } of testData) { for (let nextTab of expectedOrder) { EventUtils.synthesizeKey("KEY_Tab", { ctrlKey: true, shiftKey }); await BrowserTestUtils.waitForCondition( () => gBrowser.selectedTab == nextTab ); Assert.ok(true, "Expected tab is selected"); let expectedInput = nextTab == mainTab ? query : ""; Assert.equal(gURLBar.value, expectedInput, "Urlbar value is correct"); Assert.equal( gURLBar.view.isOpen, !!expectedInput, "Urlbar view is opened as expected" ); } } gBrowser.removeTab(newTab1); gBrowser.removeTab(newTab2); gURLBar.view.close(); gURLBar.handleRevert(); });