1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 // Tests the Search Tips feature, which displays a prompt to use the Urlbar on
5 // the newtab page and on the user's default search engine's homepage.
6 // Specifically, it tests that the Tips appear when they should be appearing.
7 // This doesn't test the max-shown-count limit because it requires restarting
12 ChromeUtils.defineESModuleGetters(this, {
13 AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
14 HttpServer: "resource://testing-common/httpd.sys.mjs",
15 UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
16 UrlbarProviderSearchTips:
17 "resource:///modules/UrlbarProviderSearchTips.sys.mjs",
18 UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
21 XPCOMUtils.defineLazyServiceGetter(
24 "@mozilla.org/widget/clipboardhelper;1",
28 // These should match the same consts in UrlbarProviderSearchTips.jsm.
29 const MAX_SHOWN_COUNT = 4;
30 const LAST_UPDATE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
32 // We test some of the bigger Google domains.
33 const GOOGLE_DOMAINS = [
41 // In order for the persist tip to appear, the scheme of the
42 // search engine has to be the same as the scheme of the SERP url.
43 // withDNSRedirect() loads an http: url while the searchform
44 // of the default engine uses https. To enable the search term
45 // to be shown, we use the Example engine because it doesn't require
47 const SEARCH_TERM = "chocolate";
48 const SEARCH_SERP_URL = `https://example.com/?q=${SEARCH_TERM}`;
50 add_setup(async function () {
51 await PlacesUtils.history.clear();
52 await PlacesUtils.bookmarks.eraseEverything();
54 await SpecialPowers.pushPrefEnv({
57 `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`,
61 `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`,
65 `browser.urlbar.tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}`,
68 // Set following prefs so tips are actually shown.
69 ["browser.laterrun.bookkeeping.profileCreationTime", 0],
70 ["browser.laterrun.bookkeeping.updateAppliedTime", 0],
74 // Remove update history and the current active update so tips are shown.
75 let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
76 let updatesFile = updateRootDir.clone();
77 updatesFile.append("updates.xml");
78 let activeUpdateFile = updateRootDir.clone();
79 activeUpdateFile.append("active-update.xml");
81 updatesFile.remove(false);
84 activeUpdateFile.remove(false);
87 let defaultEngine = await Services.search.getDefault();
88 let defaultEngineName = defaultEngine.name;
89 Assert.equal(defaultEngineName, "Google", "Default engine should be Google.");
91 // Add a mock engine so we don't hit the network loading the SERP.
92 await SearchTestUtils.installSearchExtension();
94 registerCleanupFunction(async () => {
95 await setDefaultEngine(defaultEngineName);
96 resetSearchTipsProvider();
100 // Picking the tip's button should cause the Urlbar to blank out and the tip to
101 // be not to be shown again in any session. Telemetry should be updated.
102 add_task(async function pickButton_onboard() {
103 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
104 await SpecialPowers.pushPrefEnv({
105 set: [["browser.urlbar.eventTelemetry.enabled", true]],
108 Services.telemetry.clearEvents();
109 let tab = await BrowserTestUtils.openNewForegroundTab({
114 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false);
116 // Click the tip button.
117 let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
118 let button = result.element.row._buttons.get("0");
119 await UrlbarTestUtils.promisePopupClose(window, () => {
120 EventUtils.synthesizeMouseAtCenter(button, {});
125 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
126 TelemetryTestUtils.assertKeyedScalar(
129 `${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}-picked`,
132 TelemetryTestUtils.assertEvents(
136 method: "engagement",
141 { category: "urlbar" }
146 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`
149 "Onboarding tips are disabled after tip button is picked."
151 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
152 resetSearchTipsProvider();
154 BrowserTestUtils.removeTab(tab);
155 await SpecialPowers.popPrefEnv();
158 // Picking the tip's button should cause the Urlbar to blank out and the tip to
159 // be not to be shown again in any session. Telemetry should be updated.
160 add_task(async function pickButton_redirect() {
161 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
162 await SpecialPowers.pushPrefEnv({
163 set: [["browser.urlbar.eventTelemetry.enabled", true]],
165 Services.telemetry.clearEvents();
167 await setDefaultEngine("Google");
168 await BrowserTestUtils.withNewTab("about:blank", async () => {
169 await withDNSRedirect("www.google.com", "/", async url => {
170 BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
171 await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
172 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT, false);
174 // Click the tip button.
175 let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
176 let button = result.element.row._buttons.get("0");
177 await UrlbarTestUtils.promisePopupClose(window, () => {
178 EventUtils.synthesizeMouseAtCenter(button, {});
185 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
186 TelemetryTestUtils.assertKeyedScalar(
189 `${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}-picked`,
192 TelemetryTestUtils.assertEvents(
196 method: "engagement",
201 { category: "urlbar" }
206 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}`
209 "Redirect tips are disabled after tip button is picked."
211 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
212 resetSearchTipsProvider();
213 await SpecialPowers.popPrefEnv();
216 // Picking the tip's button should cause the Urlbar to keep its current
217 // value and the tip to be not to be shown again in any session.
218 // Telemetry should be updated.
219 add_task(async function pickButton_persist() {
220 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
221 await SpecialPowers.pushPrefEnv({
223 ["browser.urlbar.eventTelemetry.enabled", true],
224 ["browser.urlbar.showSearchTerms.featureGate", true],
227 Services.telemetry.clearEvents();
229 await setDefaultEngine("Example");
231 await BrowserTestUtils.withNewTab("about:blank", async () => {
232 let browserLoadedPromise = BrowserTestUtils.browserLoaded(
233 gBrowser.selectedBrowser,
237 BrowserTestUtils.startLoadingURIString(
238 gBrowser.selectedBrowser,
241 await browserLoadedPromise;
242 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.PERSIST, false);
244 let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
245 let button = result.element.row._buttons.get("0");
247 await UrlbarTestUtils.promisePopupClose(window, () => {
248 EventUtils.synthesizeMouseAtCenter(button, {});
255 "The Urlbar should keep its existing value."
260 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
261 TelemetryTestUtils.assertKeyedScalar(
264 `${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}-picked`,
267 TelemetryTestUtils.assertEvents(
271 method: "engagement",
276 { category: "urlbar" }
281 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`
284 "Persist tips are disabled after tip button is picked."
286 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
287 resetSearchTipsProvider();
288 await SpecialPowers.popPrefEnv();
291 // Clicking in the input while the onboard tip is showing should have the same
292 // effect as picking the tip.
293 add_task(async function clickInInput_onboard() {
294 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
295 await SpecialPowers.pushPrefEnv({
296 set: [["browser.urlbar.eventTelemetry.enabled", true]],
298 Services.telemetry.clearEvents();
300 await setDefaultEngine("Google");
301 let tab = await BrowserTestUtils.openNewForegroundTab({
306 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false);
308 // Click in the input.
309 await UrlbarTestUtils.promisePopupClose(window, () => {
310 EventUtils.synthesizeMouseAtCenter(gURLBar.textbox.parentNode, {});
315 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
316 TelemetryTestUtils.assertKeyedScalar(
319 `${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}-picked`,
322 TelemetryTestUtils.assertEvents(
326 method: "engagement",
331 { category: "urlbar" }
336 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`
339 "Onboarding tips are disabled after tip button is picked."
341 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
342 resetSearchTipsProvider();
344 BrowserTestUtils.removeTab(tab);
345 await SpecialPowers.popPrefEnv();
348 // Pressing Ctrl+L (the open location command) while the onboard tip is showing
349 // should have the same effect as picking the tip.
350 add_task(async function openLocation_onboard() {
351 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
352 await SpecialPowers.pushPrefEnv({
353 set: [["browser.urlbar.eventTelemetry.enabled", true]],
355 Services.telemetry.clearEvents();
357 await setDefaultEngine("Google");
358 let tab = await BrowserTestUtils.openNewForegroundTab({
363 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false);
365 // Trigger the open location command.
366 await UrlbarTestUtils.promisePopupClose(window, () => {
367 document.getElementById("Browser:OpenLocation").doCommand();
372 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
373 TelemetryTestUtils.assertKeyedScalar(
376 `${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}-picked`,
379 TelemetryTestUtils.assertEvents(
383 method: "engagement",
388 { category: "urlbar" }
393 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`
396 "Onboarding tips are disabled after tip button is picked."
398 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
399 resetSearchTipsProvider();
401 BrowserTestUtils.removeTab(tab);
402 await SpecialPowers.popPrefEnv();
405 // Clicking in the input while the redirect tip is showing should have the same
406 // effect as picking the tip.
407 add_task(async function clickInInput_redirect() {
408 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
409 await SpecialPowers.pushPrefEnv({
410 set: [["browser.urlbar.eventTelemetry.enabled", true]],
412 Services.telemetry.clearEvents();
414 await setDefaultEngine("Google");
415 await BrowserTestUtils.withNewTab("about:blank", async () => {
416 await withDNSRedirect("www.google.com", "/", async url => {
417 BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
418 await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
419 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT, false);
421 // Click in the input.
422 await UrlbarTestUtils.promisePopupClose(window, () => {
423 EventUtils.synthesizeMouseAtCenter(gURLBar.textbox.parentNode, {});
430 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
431 TelemetryTestUtils.assertKeyedScalar(
434 `${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}-picked`,
437 TelemetryTestUtils.assertEvents(
441 method: "engagement",
446 { category: "urlbar" }
451 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}`
454 "Redirect tips are disabled after tip button is picked."
456 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
457 resetSearchTipsProvider();
458 await SpecialPowers.popPrefEnv();
461 // Clicking in the input while the persist tip is showing should have the same
462 // effect as picking the tip.
463 add_task(async function clickInInput_persist() {
464 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
465 await SpecialPowers.pushPrefEnv({
467 ["browser.urlbar.eventTelemetry.enabled", true],
468 ["browser.urlbar.showSearchTerms.featureGate", true],
471 Services.telemetry.clearEvents();
473 await setDefaultEngine("Example");
474 await BrowserTestUtils.withNewTab("about:blank", async () => {
475 let browserLoadedPromise = BrowserTestUtils.browserLoaded(
476 gBrowser.selectedBrowser,
480 BrowserTestUtils.startLoadingURIString(
481 gBrowser.selectedBrowser,
484 await browserLoadedPromise;
485 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.PERSIST, false);
487 // Click in the input.
488 await UrlbarTestUtils.promisePopupClose(window, () => {
489 EventUtils.synthesizeMouseAtCenter(gURLBar.textbox.parentNode, {});
495 "The Urlbar should keep its existing value."
500 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
501 TelemetryTestUtils.assertKeyedScalar(
504 `${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}-picked`,
507 TelemetryTestUtils.assertEvents(
511 method: "engagement",
516 { category: "urlbar" }
521 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`
524 "Persist tips are disabled after tip button is picked."
526 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
527 resetSearchTipsProvider();
528 await SpecialPowers.popPrefEnv();
531 // Pressing Ctrl+L (the open location command) while the redirect tip is showing
532 // should have the same effect as picking the tip.
533 add_task(async function openLocation_redirect() {
534 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
535 await SpecialPowers.pushPrefEnv({
536 set: [["browser.urlbar.eventTelemetry.enabled", true]],
538 Services.telemetry.clearEvents();
540 await setDefaultEngine("Google");
541 await BrowserTestUtils.withNewTab("about:blank", async () => {
542 await withDNSRedirect("www.google.com", "/", async url => {
543 BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
544 await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
545 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT, false);
547 // Trigger the open location command.
548 await UrlbarTestUtils.promisePopupClose(window, () => {
549 document.getElementById("Browser:OpenLocation").doCommand();
556 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
557 TelemetryTestUtils.assertKeyedScalar(
560 `${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}-picked`,
563 TelemetryTestUtils.assertEvents(
567 method: "engagement",
572 { category: "urlbar" }
577 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.REDIRECT}`
580 "Redirect tips are disabled after tip button is picked."
582 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
583 resetSearchTipsProvider();
584 await SpecialPowers.popPrefEnv();
587 // Pressing Ctrl+L (the open location command) while the persist tip is showing
588 // should have the same effect as picking the tip.
589 add_task(async function openLocation_persist() {
590 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
591 await SpecialPowers.pushPrefEnv({
593 ["browser.urlbar.eventTelemetry.enabled", true],
594 ["browser.urlbar.showSearchTerms.featureGate", true],
597 Services.telemetry.clearEvents();
599 await setDefaultEngine("Example");
600 await BrowserTestUtils.withNewTab("about:blank", async () => {
601 let browserLoadedPromise = BrowserTestUtils.browserLoaded(
602 gBrowser.selectedBrowser,
606 BrowserTestUtils.startLoadingURIString(
607 gBrowser.selectedBrowser,
610 await browserLoadedPromise;
611 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.PERSIST, false);
613 // Trigger the open location command.
614 await UrlbarTestUtils.promisePopupClose(window, () => {
615 document.getElementById("Browser:OpenLocation").doCommand();
621 "The Urlbar should keep its existing value."
626 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
627 TelemetryTestUtils.assertKeyedScalar(
630 `${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}-picked`,
633 TelemetryTestUtils.assertEvents(
637 method: "engagement",
642 { category: "urlbar" }
647 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.PERSIST}`
650 "Persist tips are disabled after tip button is picked."
652 Assert.equal(gURLBar.value, "", "The Urlbar should be empty.");
653 resetSearchTipsProvider();
654 await SpecialPowers.popPrefEnv();
657 add_task(async function pickingTipDoesNotDisableOtherKinds() {
658 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
659 await setDefaultEngine("Google");
660 let tab = await BrowserTestUtils.openNewForegroundTab({
665 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false);
667 // Click the tip button.
668 let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
669 let button = result.element.row._buttons.get("0");
670 await UrlbarTestUtils.promisePopupClose(window, () => {
671 EventUtils.synthesizeMouseAtCenter(button, {});
677 `tipShownCount.${UrlbarProviderSearchTips.TIP_TYPE.ONBOARD}`
680 "Onboarding tips are disabled after tip button is picked."
683 BrowserTestUtils.removeTab(tab);
685 // Simulate a new session.
686 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
688 // Onboarding tips should no longer be shown.
689 let tab2 = await BrowserTestUtils.openNewForegroundTab({
694 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.NONE);
696 // We should still show redirect tips.
697 await withDNSRedirect("www.google.com", "/", async url => {
698 await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT);
701 BrowserTestUtils.removeTab(tab2);
702 resetSearchTipsProvider();
705 // The tip shouldn't be shown when there's another notification present.
706 add_task(async function notification() {
707 await BrowserTestUtils.withNewTab("about:blank", async () => {
708 let box = gBrowser.getNotificationBox();
709 let note = box.appendNotification("urlbar-test", {
711 priority: box.PRIORITY_INFO_HIGH,
713 // Give it a big persistence so it doesn't go away on page load.
714 note.persistence = 100;
715 await withDNSRedirect("www.google.com", "/", async url => {
716 BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
717 await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
718 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.NONE);
719 box.removeNotification(note, true);
722 resetSearchTipsProvider();
725 // The tip should be shown when switching to a tab where it should be shown.
726 add_task(async function tabSwitch() {
727 let tab = BrowserTestUtils.addTab(gBrowser, "about:newtab");
728 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
729 Services.telemetry.clearScalars();
730 await BrowserTestUtils.switchTab(gBrowser, tab);
731 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD);
732 BrowserTestUtils.removeTab(tab);
733 resetSearchTipsProvider();
736 // The engagement event should be ended if the user ignores a tip.
738 add_task(async function ignoreEndsEngagement() {
739 await setDefaultEngine("Google");
740 await BrowserTestUtils.withNewTab("about:blank", async () => {
741 await withDNSRedirect("www.google.com", "/", async url => {
742 BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url);
743 await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
744 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT, false);
745 // We're just looking for any target outside the Urlbar.
746 let spring = gURLBar.inputField
748 .querySelector("toolbarspring");
749 await UrlbarTestUtils.promisePopupClose(window, async () => {
750 await EventUtils.synthesizeMouseAtCenter(spring, {});
753 UrlbarProviderSearchTips.showedTipTypeInCurrentEngagement ==
754 UrlbarProviderSearchTips.TIP_TYPE.NONE,
755 "The engagement should have ended after the tip was ignored."
759 resetSearchTipsProvider();
762 add_task(async function pasteAndGo_url() {
763 await doPasteAndGoTest("http://example.com/", "http://example.com/");
766 add_task(async function pasteAndGo_nonURL() {
767 await setDefaultEngine("Example");
768 await doPasteAndGoTest(
770 "https://example.com/?q=pasteAndGo_nonURL"
772 await setDefaultEngine("Google");
775 async function doPasteAndGoTest(searchString, expectedURL) {
776 UrlbarProviderSearchTips.disableTipsForCurrentSession = false;
777 let tab = await BrowserTestUtils.openNewForegroundTab({
782 await checkTip(window, UrlbarProviderSearchTips.TIP_TYPE.ONBOARD, false);
784 await SimpleTest.promiseClipboardChange(searchString, () => {
785 clipboardHelper.copyString(searchString);
788 let textBox = gURLBar.querySelector("moz-input-box");
789 let cxmenu = textBox.menupopup;
790 let cxmenuPromise = BrowserTestUtils.waitForEvent(cxmenu, "popupshown");
791 EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {
796 let menuitem = textBox.getMenuItem("paste-and-go");
798 let browserLoadedPromise = BrowserTestUtils.browserLoaded(
799 gBrowser.selectedBrowser,
803 cxmenu.activateItem(menuitem);
804 await browserLoadedPromise;
805 BrowserTestUtils.removeTab(tab);
806 resetSearchTipsProvider();
809 // Since we coupled the logic that decides whether to show the tip with our
810 // gURLBar.search call, we should make sure search isn't called when
811 // the conditions for a tip are met but the provider is disabled.
812 add_task(async function noActionWhenDisabled() {
813 await setDefaultEngine("Bing");
814 await withDNSRedirect("www.bing.com", "/", async url => {
815 await checkTab(window, url, UrlbarProviderSearchTips.TIP_TYPE.REDIRECT);
818 await SpecialPowers.pushPrefEnv({
821 "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
827 await withDNSRedirect("www.bing.com", "/", async url => {
829 !UrlbarTestUtils.isPopupOpen(window),
830 "The UrlbarView should not be open."
834 await SpecialPowers.popPrefEnv();