/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests the Search Tips feature, which displays a prompt to use the Urlbar on // the newtab page and on the user's default search engine's homepage. // Specifically, it tests that the Tips appear when they should be appearing. // This doesn't test the max-shown-count limit or the restriction on tips when // we show the default browser prompt because those require restarting the // browser. "use strict"; ChromeUtils.defineESModuleGetters(this, { AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs", ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs", PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs", UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", UrlbarProviderSearchTips: "resource:///modules/UrlbarProviderSearchTips.sys.mjs", }); XPCOMUtils.defineLazyModuleGetters(this, { HttpServer: "resource://testing-common/httpd.js", }); // These should match the same consts in UrlbarProviderSearchTips.jsm. const MAX_SHOWN_COUNT = 4; const LAST_UPDATE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // We test some of the bigger Google domains. const GOOGLE_DOMAINS = [ "www.google.com", "www.google.ca", "www.google.co.uk", "www.google.com.au", "www.google.co.nz", ]; // In order for the persist tip to appear, the scheme of the // search engine has to be the same as the scheme of the SERP url. // withDNSRedirect() loads an http: url while the searchform // of the default engine uses https. To enable the search term // to be shown, we use the Example engine because it doesn't require // a redirect. const SEARCH_SERP_URL = "https://example.com/?q=chocolate"; add_setup(async function () { await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); await SpecialPowers.pushPrefEnv({ set: [ [ `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`, 0, ], [ `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`, 0, ], [ `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}`, 0, ], ], }); // Write an old profile age so tips are actually shown. let age = await ProfileAge(); let originalTimes = age._times; let date = Date.now() - LAST_UPDATE_THRESHOLD_MS - 30000; age._times = { created: date, firstUse: date }; await age.writeTimes(); // Remove update history and the current active update so tips are shown. let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile); let updatesFile = updateRootDir.clone(); updatesFile.append("updates.xml"); let activeUpdateFile = updateRootDir.clone(); activeUpdateFile.append("active-update.xml"); try { updatesFile.remove(false); } catch (e) {} try { activeUpdateFile.remove(false); } catch (e) {} let defaultEngine = await Services.search.getDefault(); let defaultEngineName = defaultEngine.name; Assert.equal(defaultEngineName, "Google", "Default engine should be Google."); // Add a mock engine so we don't hit the network loading the SERP. await SearchTestUtils.installSearchExtension(); registerCleanupFunction(async () => { let age2 = await ProfileAge(); age2._times = originalTimes; await age2.writeTimes(); await setDefaultEngine(defaultEngineName); resetSearchTipsProvider(); }); }); // The onboarding tip should be shown on about:newtab. add_task(async function newtab() { await checkTab( window, "about:newtab", UrlbarProviderSearchTips.TIP_TYPE.ONBOARD ); }); // The onboarding tip should be shown on about:home. add_task(async function home() { await checkTab( window, "about:home", UrlbarProviderSearchTips.TIP_TYPE.ONBOARD ); }); // The redirect tip should be shown for www.google.com when it's the default // engine. add_task(async function google() { await setDefaultEngine("Google"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT); }); } }); // The redirect tip should be shown for www.google.com/webhp when it's the // default engine. add_task(async function googleWebhp() { await setDefaultEngine("Google"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/webhp", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT); }); } }); // The redirect tip should be shown for the Google homepage when query strings // are appended. add_task(async function googleQueryString() { await setDefaultEngine("Google"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/webhp", async url => { await checkTab( window, `${url}?hl=en`, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT ); }); } }); // The redirect tip should not be shown on Google results pages. add_task(async function googleResults() { await setDefaultEngine("Google"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/search", async url => { await checkTab( window, `${url}?q=firefox`, UrlbarProviderSearchTips.TIP_TYPE.NONE ); }); } }); // The redirect tip should not be shown for www.google.com when it's not the // default engine. add_task(async function googleNotDefault() { await setDefaultEngine("Bing"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); } }); // The redirect tip should not be shown for www.google.com/webhp when it's not // the default engine. add_task(async function googleWebhpNotDefault() { await setDefaultEngine("Bing"); for (let domain of GOOGLE_DOMAINS) { await withDNSRedirect(domain, "/webhp", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); } }); // The redirect tip should be shown for www.bing.com when it's the default // engine. add_task(async function bing() { await setDefaultEngine("Bing"); await withDNSRedirect("www.bing.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT); }); }); // The redirect tip should be shown on the Bing homepage even when Bing appends // query strings. add_task(async function bingQueryString() { await setDefaultEngine("Bing"); await withDNSRedirect("www.bing.com", "/", async url => { await checkTab( window, `${url}?toWww=1`, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT ); }); }); // The redirect tip should not be shown on Bing results pages. add_task(async function bingResults() { await setDefaultEngine("Bing"); await withDNSRedirect("www.bing.com", "/search", async url => { await checkTab( window, `${url}?q=firefox`, UrlbarProviderSearchTips.TIP_TYPE.NONE ); }); }); // The redirect tip should not be shown for www.bing.com when it's not the // default engine. add_task(async function bingNotDefault() { await setDefaultEngine("Google"); await withDNSRedirect("www.bing.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); }); // The redirect tip should be shown for duckduckgo.com when it's the default // engine. add_task(async function ddg() { await setDefaultEngine("DuckDuckGo"); await withDNSRedirect("duckduckgo.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT); }); }); // The redirect tip should be shown for start.duckduckgo.com when it's the // default engine. add_task(async function ddgStart() { await setDefaultEngine("DuckDuckGo"); await withDNSRedirect("start.duckduckgo.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT); }); }); // The redirect tip should not be shown for duckduckgo.com when it's not the // default engine. add_task(async function ddgNotDefault() { await setDefaultEngine("Google"); await withDNSRedirect("duckduckgo.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); }); // The redirect tip should not be shown for start.duckduckgo.com when it's not // the default engine. add_task(async function ddgStartNotDefault() { await setDefaultEngine("Google"); await withDNSRedirect("start.duckduckgo.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); }); // The redirect tip should not be shown for duckduckgo.com/?q=foo, the search // results page, which happens to have the same domain and path as the home // page. add_task(async function ddgSearchResultsPage() { await setDefaultEngine("DuckDuckGo"); await withDNSRedirect("duckduckgo.com", "/", async url => { await checkTab( window, `${url}?q=test`, UrlbarProviderSearchTips.TIP_TYPE.NONE ); }); }); // The redirect tip should not be shown on a non-engine page. add_task(async function nonEnginePage() { await checkTab( window, "http://example.com/", UrlbarProviderSearchTips.TIP_TYPE.NONE ); }); // The persist tip should show on default SERPs. // This test also has an implied check that the SERP // is receiving an originalURI. // This is because the page the test is attempting to load // will differ from the page that's actually loaded due to // the DNS redirect. add_task(async function persistTipOnDefault() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); await checkTab( window, SEARCH_SERP_URL, UrlbarProviderSearchTips.TIP_TYPE.PERSIST ); await SpecialPowers.popPrefEnv(); }); // The persist tip should not show on non-default SERPs. add_task(async function noPersistTipOnNonDefault() { await setDefaultEngine("DuckDuckGo"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); await checkTab( window, SEARCH_SERP_URL, UrlbarProviderSearchTips.TIP_TYPE.NONE ); await SpecialPowers.popPrefEnv(); }); // The persist tip should only show up once a session. add_task(async function persistTipOnceOnDefaultSerp() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); await checkTab( window, SEARCH_SERP_URL, UrlbarProviderSearchTips.TIP_TYPE.PERSIST ); await checkTab( window, SEARCH_SERP_URL, UrlbarProviderSearchTips.TIP_TYPE.NONE ); await SpecialPowers.popPrefEnv(); }); // The persist tip should not show in a window // with a selected tab containing a non-SERP url. add_task(async function noPersistTipInWindowWithNonSerpTab() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); // Create a new window for the SERP to be loaded into. let newWindow = await BrowserTestUtils.openNewBrowserWindow(); // Focus on the original window. window.focus(); await waitForBrowserWindowActive(window); // Load the SERP in the new window to initiate a background load. let browserLoadedPromise = BrowserTestUtils.browserLoaded( newWindow.gBrowser.selectedBrowser, false, SEARCH_SERP_URL ); BrowserTestUtils.loadURIString( newWindow.gBrowser.selectedBrowser, SEARCH_SERP_URL ); await browserLoadedPromise; // Wait longer than the persist tip delay to check that the search tip // doesn't show on the non-SERP tab. await new Promise(resolve => // eslint-disable-next-line mozilla/no-arbitrary-setTimeout setTimeout(resolve, UrlbarProviderSearchTips.SHOW_PERSIST_TIP_DELAY_MS * 2) ); Assert.ok(!window.gURLBar.view.isOpen); // Clean up. await BrowserTestUtils.closeWindow(newWindow); await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); }); // Tips should be shown at most once per session regardless of their type. add_task(async function oncePerSession() { await setDefaultEngine("Google"); await checkTab( window, "about:newtab", UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false ); await checkTab( window, "about:newtab", UrlbarProviderSearchTips.TIP_TYPE.NONE, false ); await withDNSRedirect("www.google.com", "/", async url => { await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.NONE); }); await setDefaultEngine("Example"); await checkTab( window, SEARCH_SERP_URL, UrlbarProviderSearchTips.TIP_TYPE.NONE ); }); // The one-off search buttons should not be shown when // a search tip is shown even though the search string is empty. add_task(async function shortcut_buttons_with_tip() { await checkTab( window, "about:newtab", UrlbarProviderSearchTips.TIP_TYPE.ONBOARD ); }); // Don't show the persist search tip when the browser loads // a different page from the page the tip was supposed to show on. add_task(async function noSearchTipWhileAnotherPageLoads() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); // Create a slow endpoint. const SLOW_PAGE = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", "http://www.example.com" ) + "slow-page.sjs"; let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: SEARCH_SERP_URL, }); // Load a slow URI to cause an onStateChange event but // not an onLocationChange event. BrowserTestUtils.loadURIString(tab.linkedBrowser, SLOW_PAGE); // Wait roughly for the amount of time it would take for the // persist search tip to show. await new Promise(resolve => // eslint-disable-next-line mozilla/no-arbitrary-setTimeout setTimeout(resolve, UrlbarProviderSearchTips.SHOW_PERSIST_TIP_DELAY_MS * 2) ); // Check the search tip didn't show while the page was loading. Assert.equal( UrlbarPrefs.get( `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}` ), 0, "The shownCount pref should be 0." ); Assert.equal(false, window.gURLBar.view.isOpen, "Urlbar should be closed."); // Clean up. await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); BrowserTestUtils.removeTab(tab); }); // Show the persist search tip when the browser is still loading // resources from the page the tip is supposed to show on. add_task(async function searchTipWhilePageLoads() { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); // Create a search engine endpoint that will still // be loading resources on the page load. const SLOW_PAGE = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", "https://www.example.com" ) + "slow-page.html"; await SearchTestUtils.installSearchExtension({ name: "Slow Engine", search_url: SLOW_PAGE, search_url_get_params: "search={searchTerms}", }); await setDefaultEngine("Slow Engine"); let engine = Services.search.getEngineByName("Slow Engine"); let [expectedSearchUrl] = UrlbarUtils.getSearchQueryUrl(engine, "chocolate"); // Load a slow SERP. await checkTab( window, expectedSearchUrl, UrlbarProviderSearchTips.TIP_TYPE.PERSIST ); // Clean up. await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); }); // Search tips modify the userTypedValue of a tab. The next time // the pageproxystate is updated, the existence of the userTypedValue // can change the pageproxystate. In the case of the Persist Search Tip, // we don't want to change the pageproxystate while the Urlbar is non-focused, // so check that when an event causes the pageproxystate to update // (e.g. a SERP pushing state), the pageproxystate remains the same. add_task(async function persistSearchTipAfterPushState() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: SEARCH_SERP_URL, }); // Ensure the search tip is visible. await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.PERSIST, false); Assert.equal( gURLBar.getAttribute("pageproxystate"), "valid", "Urlbar is should be in a valid pageproxystate." ); // Mock the default SERP using the History API on an exising website. // This is to trigger another call to setURI. await SpecialPowers.spawn(tab.linkedBrowser, [SEARCH_SERP_URL], async url => { content.history.pushState({}, "", url); }); Assert.equal( gURLBar.getAttribute("pageproxystate"), "valid", "Urlbar is should be in a valid pageproxystate." ); // Clean up. await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); BrowserTestUtils.removeTab(tab); }); // Ensure a the Persist Search Tip is non-visible when a PopupNotification // is already visible. add_task(async function persistSearchTipBeforePopupShown() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: SEARCH_SERP_URL, }); let promisePopupShown = BrowserTestUtils.waitForEvent( PopupNotifications.panel, "popupshown" ); PopupNotifications.show( gBrowser.selectedBrowser, "test-notification", "This is a sample popup.", "geo-notification-icon" ); await promisePopupShown; // Wait roughly for the amount of time it would take for the // persist search tip to show. await new Promise(resolve => // eslint-disable-next-line mozilla/no-arbitrary-setTimeout setTimeout(resolve, UrlbarProviderSearchTips.SHOW_PERSIST_TIP_DELAY_MS * 2) ); // Check the search tip didn't show while the page was loading. Assert.equal( UrlbarPrefs.get( `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}` ), 0, "The shownCount pref should be 0." ); Assert.equal(false, window.gURLBar.view.isOpen, "Urlbar should be closed."); // Clean up. await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); BrowserTestUtils.removeTab(tab); }); // The Persist Search Tip should be hidden when a PopupNotification appears. add_task(async function persistSearchTipAfterPopupShown() { await setDefaultEngine("Example"); await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.showSearchTerms.featureGate", true]], }); let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: SEARCH_SERP_URL, }); // Ensure the search tip is visible. await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.PERSIST, false); // Show a popup after the search tip is shown. let promisePopupShown = BrowserTestUtils.waitForEvent( PopupNotifications.panel, "popupshown" ); PopupNotifications.show( gBrowser.selectedBrowser, "test-notification", "This is a sample popup.", "geo-notification-icon" ); await promisePopupShown; // The search tip should not be visible. Assert.equal(false, window.gURLBar.view.isOpen, "Urlbar should be closed."); Assert.equal( gURLBar.getAttribute("pageproxystate"), "valid", "Urlbar is should be in a valid pageproxystate." ); // Clean up. await SpecialPowers.popPrefEnv(); resetSearchTipsProvider(); BrowserTestUtils.removeTab(tab); }); function waitForBrowserWindowActive(win) { return new Promise(resolve => { if (Services.focus.activeWindow == win) { resolve(); } else { win.addEventListener( "activate", () => { resolve(); }, { once: true } ); } }); }