/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests QuickActions. */ "use strict"; requestLongerTimeout(3); ChromeUtils.defineESModuleGetters(this, { AppConstants: "resource://gre/modules/AppConstants.sys.mjs", UpdateService: "resource://gre/modules/UpdateService.sys.mjs", UrlbarProviderQuickActions: "resource:///modules/UrlbarProviderQuickActions.sys.mjs", }); XPCOMUtils.defineLazyModuleGetters(this, { BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", }); const DUMMY_PAGE = "http://example.com/browser/browser/base/content/test/general/dummy_page.html"; let testActionCalled = 0; add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", true], ["browser.urlbar.suggest.quickactions", true], ["browser.urlbar.shortcuts.quickactions", true], ], }); UrlbarProviderQuickActions.addAction("testaction", { commands: ["testaction"], label: "quickactions-downloads2", onPick: () => testActionCalled++, }); registerCleanupFunction(() => { UrlbarProviderQuickActions.removeAction("testaction"); }); }); add_task(async function basic() { info("The action isnt shown when not matched"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "nomatch", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 1, "We did no match anything" ); info("A prefix of the command matches"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "testact", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 2, "We matched the action" ); info("The callback of the action is fired when selected"); EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); Assert.equal(testActionCalled, 1, "Test actionwas called"); }); add_task(async function test_label_command() { info("A prefix of the label matches"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "View Dow", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 2, "We matched the action" ); let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); Assert.equal(result.providerName, "quickactions"); await UrlbarTestUtils.promisePopupClose(window, () => { EventUtils.synthesizeKey("KEY_Escape"); }); }); add_task(async function enter_search_mode_button() { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "test", }); let oneOffButton = await TestUtils.waitForCondition(() => window.document.getElementById("urlbar-engine-one-off-item-actions") ); Assert.ok(oneOffButton, "One off button is available when preffed on"); EventUtils.synthesizeMouseAtCenter(oneOffButton, {}, window); await UrlbarTestUtils.assertSearchMode(window, { source: UrlbarUtils.RESULT_SOURCE.ACTIONS, entry: "oneoff", }); await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0); Assert.ok(true, "Actions are shown when we enter actions search mode."); await UrlbarTestUtils.exitSearchMode(window); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); }); add_task(async function enter_search_mode_oneoff_by_key() { // Select actions oneoff button by keyboard. await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "", }); await UrlbarTestUtils.enterSearchMode(window); const oneOffButtons = UrlbarTestUtils.getOneOffSearchButtons(window); for (;;) { EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }); if ( oneOffButtons.selectedButton.source === UrlbarUtils.RESULT_SOURCE.ACTIONS ) { break; } } await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: " ", }); await UrlbarTestUtils.assertSearchMode(window, { source: UrlbarUtils.RESULT_SOURCE.ACTIONS, entry: "oneoff", }); await UrlbarTestUtils.exitSearchMode(window); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); }); add_task(async function enter_search_mode_key() { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "> ", }); await UrlbarTestUtils.assertSearchMode(window, { source: UrlbarUtils.RESULT_SOURCE.ACTIONS, entry: "typed", }); Assert.equal( await hasQuickActions(window), true, "Actions are shown in search mode" ); await UrlbarTestUtils.exitSearchMode(window); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); }); add_task(async function test_disabled() { UrlbarProviderQuickActions.addAction("disabledaction", { commands: ["disabledaction"], isActive: () => false, label: "quickactions-restart", }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "disabled", }); Assert.equal( await hasQuickActions(window), false, "Result for quick actions is hidden" ); await UrlbarTestUtils.promisePopupClose(window); UrlbarProviderQuickActions.removeAction("disabledaction"); }); /** * The first part of this test confirms that when the screenshots component is enabled * the screenshot quick action button will be enabled on about: pages. * The second part confirms that when the screenshots extension is enabled the * screenshot quick action button will be disbaled on about: pages. */ add_task(async function test_screenshot_enabled_or_disabled() { let onLoaded = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "about:blank" ); BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, "about:blank"); await onLoaded; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "screenshot", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 2, "The action is displayed" ); let screenshotButton = window.document.querySelector( ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row" ); Assert.ok( !screenshotButton.hasAttribute("disabled"), "Screenshot button is enabled on about pages" ); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); await SpecialPowers.pushPrefEnv({ set: [["screenshots.browser.component.enabled", false]], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "screenshot", }); Assert.equal( await hasQuickActions(window), false, "Result for quick actions is hidden" ); await UrlbarTestUtils.promisePopupClose(window); }); add_task(async function match_in_phrase() { UrlbarProviderQuickActions.addAction("newtestaction", { commands: ["matchingstring"], label: "quickactions-downloads2", }); info("The action is matched when at end of input"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "Test we match at end of matchingstring", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 2, "We matched the action" ); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); UrlbarProviderQuickActions.removeAction("newtestaction"); }); async function isScreenshotInitialized() { return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { let screenshotsChild = content.windowGlobalChild.getActor( "ScreenshotsComponent" ); return screenshotsChild?._overlay?._initialized; }); } add_task(async function test_screenshot() { await SpecialPowers.pushPrefEnv({ set: [["screenshots.browser.component.enabled", true]], }); BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, DUMMY_PAGE); await BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, DUMMY_PAGE ); info("The action is matched when at end of input"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "screenshot", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 2, "We matched the action" ); let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); Assert.equal(result.providerName, "quickactions"); info("Trigger the screenshot mode"); EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await TestUtils.waitForCondition( isScreenshotInitialized, "Screenshot component is active", 200, 100 ); info("Press Escape to exit screenshot mode"); EventUtils.synthesizeKey("KEY_Escape", {}, window); await TestUtils.waitForCondition( async () => !(await isScreenshotInitialized()), "Screenshot component has been dismissed" ); await UrlbarTestUtils.promisePopupClose(window, () => { EventUtils.synthesizeKey("KEY_Escape"); }); }); add_task(async function test_other_search_mode() { let defaultEngine = await SearchTestUtils.promiseNewSearchEngine({ url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml", }); defaultEngine.alias = "testalias"; let oldDefaultEngine = await Services.search.getDefault(); Services.search.setDefault( defaultEngine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN ); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: defaultEngine.alias + " ", }); Assert.equal( UrlbarTestUtils.getResultCount(window), 0, "The results should be empty as no actions are displayed in other search modes" ); await UrlbarTestUtils.assertSearchMode(window, { engineName: defaultEngine.name, entry: "typed", }); await UrlbarTestUtils.promisePopupClose(window, () => { EventUtils.synthesizeKey("KEY_Escape"); }); Services.search.setDefault( oldDefaultEngine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN ); }); add_task(async function test_no_quickactions_suggestions() { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.suggest.quickactions", false]], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "screenshot", }); Assert.ok( !window.document.querySelector( ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row" ), "Screenshot button is not suggested" ); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "> screenshot", }); Assert.ok( window.document.querySelector( ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row" ), "Screenshot button is suggested" ); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); await SpecialPowers.popPrefEnv(); }); add_task(async function test_quickactions_disabled() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", false], ["browser.urlbar.suggest.quickactions", true], ], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "screenshot", }); Assert.ok( !window.document.querySelector( ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row" ), "Screenshot button is not suggested" ); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "> screenshot", }); Assert.ok( !window.document.querySelector( ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-row" ), "Screenshot button is not suggested" ); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); await SpecialPowers.popPrefEnv(); }); let COMMANDS_TESTS = [ { cmd: "add-ons", uri: "about:addons", testFun: async () => isSelected("button[name=discover]"), }, { cmd: "plugins", uri: "about:addons", testFun: async () => isSelected("button[name=plugin]"), }, { cmd: "extensions", uri: "about:addons", testFun: async () => isSelected("button[name=extension]"), }, { cmd: "themes", uri: "about:addons", testFun: async () => isSelected("button[name=theme]"), }, { cmd: "add-ons", setup: async () => { const onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "http://example.com/" ); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "http://example.com/" ); await onLoad; }, uri: "about:addons", isNewTab: true, testFun: async () => isSelected("button[name=discover]"), }, { cmd: "plugins", setup: async () => { const onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "http://example.com/" ); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "http://example.com/" ); await onLoad; }, uri: "about:addons", isNewTab: true, testFun: async () => isSelected("button[name=plugin]"), }, { cmd: "extensions", setup: async () => { const onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "http://example.com/" ); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "http://example.com/" ); await onLoad; }, uri: "about:addons", isNewTab: true, testFun: async () => isSelected("button[name=extension]"), }, { cmd: "themes", setup: async () => { const onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "http://example.com/" ); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "http://example.com/" ); await onLoad; }, uri: "about:addons", isNewTab: true, testFun: async () => isSelected("button[name=theme]"), }, ]; let isSelected = async selector => SpecialPowers.spawn(gBrowser.selectedBrowser, [selector], arg => { return ContentTaskUtils.waitForCondition(() => content.document.querySelector(arg)?.hasAttribute("selected") ); }); add_task(async function test_pages() { for (const { cmd, uri, setup, isNewTab, testFun } of COMMANDS_TESTS) { info(`Testing ${cmd} command is triggered`); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); if (setup) { info("Setup"); await setup(); } let onLoad = isNewTab ? BrowserTestUtils.waitForNewTab(gBrowser, uri, true) : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: cmd, }); EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); const newTab = await onLoad; Assert.ok( await testFun(), `The command "${cmd}" passed completed its test` ); if (isNewTab) { await BrowserTestUtils.removeTab(newTab); } await BrowserTestUtils.removeTab(tab); } }); const assertActionButtonStatus = async (name, expectedEnabled, description) => { await BrowserTestUtils.waitForCondition(() => window.document.querySelector(`[data-key=${name}]`) ); const target = window.document.querySelector(`[data-key=${name}]`); Assert.equal(!target.hasAttribute("disabled"), expectedEnabled, description); }; add_task(async function test_viewsource() { info("Check the button status of when the page is not web content"); const tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, opening: "about:home", waitForLoad: true, }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "viewsource", }); await assertActionButtonStatus( "viewsource", true, "Should be enabled even if the page is not web content" ); info("Check the button status of when the page is web content"); BrowserTestUtils.loadURIString( gBrowser.selectedBrowser, "http://example.com" ); await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "viewsource", }); await assertActionButtonStatus( "viewsource", true, "Should be enabled on web content as well" ); info("Do view source action"); const onLoad = BrowserTestUtils.waitForNewTab( gBrowser, "view-source:http://example.com/" ); EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); const viewSourceTab = await onLoad; info("Do view source action on the view-source page"); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "viewsource", }); Assert.equal( await hasQuickActions(window), false, "Result for quick actions is hidden" ); // Clean up. BrowserTestUtils.removeTab(viewSourceTab); BrowserTestUtils.removeTab(tab); }); async function doAlertDialogTest({ input, dialogContentURI }) { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: input, }); const onDialog = BrowserTestUtils.promiseAlertDialog(null, null, { isSubDialog: true, callback: win => { Assert.equal(win.location.href, dialogContentURI, "The dialog is opened"); EventUtils.synthesizeKey("KEY_Escape", {}, win); }, }); EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await onDialog; } add_task(async function test_refresh() { await doAlertDialogTest({ input: "refresh", dialogContentURI: "chrome://global/content/resetProfile.xhtml", }); }); add_task(async function test_clear() { await doAlertDialogTest({ input: "clear", dialogContentURI: "chrome://browser/content/sanitize.xhtml", }); }); async function doUpdateActionTest(isActiveExpected, description) { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "update", }); if (isActiveExpected) { await assertActionButtonStatus("update", isActiveExpected, description); } else { Assert.equal(await hasQuickActions(window), false, description); } } add_task(async function test_update() { if (!AppConstants.MOZ_UPDATER) { await doUpdateActionTest( false, "Should be disabled since not AppConstants.MOZ_UPDATER" ); return; } const sandbox = sinon.createSandbox(); try { sandbox .stub(UpdateService.prototype, "currentState") .get(() => Ci.nsIApplicationUpdateService.STATE_IDLE); await doUpdateActionTest( false, "Should be disabled since current update state is not pending" ); sandbox .stub(UpdateService.prototype, "currentState") .get(() => Ci.nsIApplicationUpdateService.STATE_PENDING); await doUpdateActionTest( true, "Should be enabled since current update state is pending" ); } finally { sandbox.restore(); } }); async function hasQuickActions(win) { for (let i = 0, count = UrlbarTestUtils.getResultCount(win); i < count; i++) { const { result } = await UrlbarTestUtils.getDetailsOfResultAt(win, i); if (result.providerName === "quickactions") { return true; } } return false; } add_task(async function test_show_in_zero_prefix() { for (const minimumSearchString of [0, 3]) { info( `Test when quickactions.minimumSearchString pref is ${minimumSearchString}` ); await SpecialPowers.pushPrefEnv({ set: [ [ "browser.urlbar.quickactions.minimumSearchString", minimumSearchString, ], ], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "", }); Assert.equal( await hasQuickActions(window), !minimumSearchString, "Result for quick actions is as expected" ); await SpecialPowers.popPrefEnv(); } }); add_task(async function test_whitespace() { info("Test with quickactions.showInZeroPrefix pref is false"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.quickactions.showInZeroPrefix", false]], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: " ", }); Assert.equal( await hasQuickActions(window), false, "Result for quick actions is not shown" ); await SpecialPowers.popPrefEnv(); info("Test with quickactions.showInZeroPrefix pref is true"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.quickactions.showInZeroPrefix", true]], }); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "", }); const countForEmpty = window.document.querySelectorAll( ".urlbarView-quickaction-row" ).length; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: " ", }); const countForWhitespace = window.document.querySelectorAll( ".urlbarView-quickaction-row" ).length; Assert.equal( countForEmpty, countForWhitespace, "Count of quick actions of empty and whitespace are same" ); await SpecialPowers.popPrefEnv(); });