Bug 1392475 - [Onboarding] Turn fox logo to watermark if all tours or notifications are finished. r=Fischer,gasolin
authorRex Lee <rexboy@mozilla.com>
Wed, 30 Aug 2017 15:24:47 +0800
changeset 429992 7d0949c18bf5dac37e9df722a3b6e29d4d0b8ffe
parent 429991 8a9ed6ede914385e577b6bc546830065417816e8
child 429993 6f22cc93383c49876030d81d383b8a4d783b11d5
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFischer, gasolin
bugs1392475
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1392475 - [Onboarding] Turn fox logo to watermark if all tours or notifications are finished. r=Fischer,gasolin MozReview-Commit-ID: CLbiHqCmxr0
browser/app/profile/firefox.js
browser/components/nsBrowserGlue.js
browser/extensions/onboarding/OnboardingTourType.jsm
browser/extensions/onboarding/README.md
browser/extensions/onboarding/bootstrap.js
browser/extensions/onboarding/content/img/watermark64.png
browser/extensions/onboarding/content/onboarding.css
browser/extensions/onboarding/content/onboarding.js
browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
browser/extensions/onboarding/test/browser/head.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1716,16 +1716,17 @@ pref("urlclassifier.downloadAllowTable",
 pref("urlclassifier.downloadBlockTable", "goog-badbinurl-proto");
 
 pref("browser.suppress_first_window_animation", true);
 
 // Preferences for Photon onboarding system extension
 pref("browser.onboarding.enabled", true);
 // Mark this as an upgraded profile so we don't offer the initial new user onboarding tour.
 pref("browser.onboarding.tourset-version", 2);
+pref("browser.onboarding.state", "default");
 // On the Activity-Stream page, the snippet's position overlaps with our notification.
 // So use `browser.onboarding.notification.finished` to let the AS page know
 // if our notification is finished and safe to show their snippet.
 pref("browser.onboarding.notification.finished", false);
 pref("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000); // 5 mins
 pref("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000); // 5 days
 pref("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000); // 14 days
 pref("browser.onboarding.notification.max-prompt-count-per-tour", 8);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1682,17 +1682,17 @@ BrowserGlue.prototype = {
         return;
       this._openPreferences("sync", { origin: "doorhanger" });
     }
     this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 53;
+    const UI_VERSION = 54;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -2073,16 +2073,25 @@ BrowserGlue.prototype = {
         let malwareList = Services.prefs.getCharPref(MALWARE_PREF);
         if (malwareList.indexOf("goog-malware-shavar") != -1) {
           malwareList.replace("goog-malware-shavar", "goog-malware-proto");
           Services.prefs.setCharPref(MALWARE_PREF, malwareList);
         }
       }
     }
 
+    if (currentUIVersion < 54) {
+      // Migrate browser.onboarding.hidden to browser.onboarding.state.
+      if (Services.prefs.prefHasUserValue("browser.onboarding.hidden")) {
+        let state = Services.prefs.getBoolPref("browser.onboarding.hidden") ? "watermark" : "default";
+        Services.prefs.setStringPref("browser.onboarding.state", state);
+        Services.prefs.clearUserPref("browser.onboarding.hidden");
+      }
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   _checkForDefaultBrowser() {
     // Perform default browser checking.
     if (!ShellService) {
       return;
--- a/browser/extensions/onboarding/OnboardingTourType.jsm
+++ b/browser/extensions/onboarding/OnboardingTourType.jsm
@@ -32,12 +32,13 @@ var OnboardingTourType = {
     } else if (Services.prefs.getIntPref(PREF_SEEN_TOURSET_VERSION) < TOURSET_VERSION) {
       // show the update user tour when tour set version is larger than the seen tourset version
       Services.prefs.setStringPref(PREF_TOUR_TYPE, "update");
       // Reset all the notification-related prefs because tours update.
       Services.prefs.setBoolPref("browser.onboarding.notification.finished", false);
       Services.prefs.clearUserPref("browser.onboarding.notification.prompt-count");
       Services.prefs.clearUserPref("browser.onboarding.notification.last-time-of-changing-tour-sec");
       Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue");
+      Services.prefs.clearUserPref("browser.onboarding.state");
     }
     Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
   },
 };
--- a/browser/extensions/onboarding/README.md
+++ b/browser/extensions/onboarding/README.md
@@ -47,8 +47,16 @@ Edit `browser/app/profile/firefox.js` an
 
 The tourset version is used to track the last major tourset change version. The `tourset-version` pref store the major tourset version (ex: `1`) but not the current browser version. When browser update to the next version (ex: 58, 59) the tourset pref is still `1` if we didn't do any major tourset update.
 
 Once the tour set version is updated (ex: `2`), onboarding overlay should show the update tour to the updated user (ex: update from v56 -> v57), even when user has watched the previous tours or preferred to hide the previous tours.
 
 Edit `browser/app/profile/firefox.js` and set `browser.onboarding.tourset-version` as `[tourset version]` (in integer format).
 
 For example, if we update the tourset in v60 and decide to show all update users the tour, we set `browser.onboarding.tourset-version`  as `3`.
+
+## Icon states
+
+Onboarding module has two states for its overlay icon: `default` and `watermark`.
+By default, it shows `default` state.
+When either tours or notifications are all completed, the icon changes to the `watermark` state.
+The icon state is stored in `browser.onboarding.state`.
+When `tourset-version` is updated, or when we detect the `tour-type` is changed to `update`, icon state will be changed back to the `default` state.
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -15,16 +15,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/FxAccounts.jsm");
 
 const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch;
 
 const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
 const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
 const PREF_WHITELIST = [
   ["browser.onboarding.enabled", PREF_BOOL],
+  ["browser.onboarding.state", PREF_STRING],
   ["browser.onboarding.notification.finished", PREF_BOOL],
   ["browser.onboarding.notification.prompt-count", PREF_INT],
   ["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT],
   ["browser.onboarding.notification.tour-ids-queue", PREF_STRING],
 ];
 
 [
   "onboarding-tour-addons",
@@ -42,17 +43,17 @@ let waitingForBrowserReady = true;
 
 /**
  * Set pref. Why no `getPrefs` function is due to the priviledge level.
  * We cannot set prefs inside a framescript but can read.
  * For simplicity and effeciency, we still read prefs inside the framescript.
  *
  * @param {Array} prefs the array of prefs to set.
  *   The array element carrys info to set pref, should contain
- *   - {String} name the pref name
+ *   - {String} name the pref name, such as `browser.onboarding.state`
  *   - {*} value the value to set
  **/
 function setPrefs(prefs) {
   prefs.forEach(pref => {
     let prefObj = PREF_WHITELIST.find(([name, ]) => name == pref.name);
     if (!prefObj) {
       return;
     }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b8c0ffe162691995065ba0ab13189e33138dc7ec
GIT binary patch
literal 2118
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
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -40,17 +40,17 @@
 /* Keyboard focus styling */
 #onboarding-overlay-button:-moz-focusring {
   outline: solid 2px rgba(0, 0, 0, 0.1);
   -moz-outline-radius: 5px;
   outline-offset: 5px;
   transition: outline-offset 150ms;
 }
 
-#onboarding-overlay-button-icon {
+#onboarding-overlay-button > img {
   width: 32px;
   vertical-align: top;
 }
 
 #onboarding-overlay-button::after {
   content: " ";
   border-radius: 50%;
   margin-top: -1px;
@@ -89,16 +89,26 @@
   margin-top: -10px;
   box-shadow: -2px 0 5px 0 rgba(74, 74, 79, 0.25);
 }
 
 #onboarding-overlay-button:dir(rtl)::after {
   box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25);
 }
 
+#onboarding-overlay-button-watermark-icon,
+#onboarding-overlay-button.onboarding-watermark:not(:hover)::after,
+#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-icon {
+  display: none;
+}
+
+#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-watermark-icon {
+  display: block;
+}
+
 #onboarding-overlay-dialog,
 .onboarding-hidden,
 #onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out,
 #onboarding-tour-sync-page[data-login-state=logged-out] .show-on-logged-in {
   display: none;
 }
 
 .onboarding-close-btn {
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -18,16 +18,18 @@ const UITOUR_JS_URI = "resource://onboar
 const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
 const BRAND_SHORT_NAME = Services.strings
                      .createBundle("chrome://branding/locale/brand.properties")
                      .GetStringFromName("brandShortName");
 const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
 const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
 const ONBOARDING_MIN_WIDTH_PX = 960;
 const SPEECH_BUBBLE_MIN_WIDTH_PX = 1130;
+const ICON_STATE_WATERMARK = "watermark";
+const ICON_STATE_DEFAULT = "default";
 
 /**
  * Add any number of tours, key is the tourId, value should follow the format below
  * "tourId": { // The short tour id which could be saved in pref
  *   // The unique tour id
  *   id: "onboarding-tour-addons",
  *   // The string id of tour name which would be displayed on the navigation bar
  *   tourNameId: "onboarding.tour-addon",
@@ -414,16 +416,18 @@ class Onboarding {
     this._overlay = this._renderOverlay();
     this._overlay.addEventListener("click", this);
     this._overlay.addEventListener("keypress", this);
     body.appendChild(this._overlay);
 
     this._loadJS(TOUR_AGENT_JS_URI);
 
     this._initPrefObserver();
+    this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
+
     // Doing tour notification takes some effort. Let's do it on idle.
     this._window.requestIdleCallback(() => this._initNotification());
   }
 
   _getTourIDList() {
     let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
     return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
   }
@@ -447,27 +451,41 @@ class Onboarding {
   }
 
   _initPrefObserver() {
     if (this._prefsObserved) {
       return;
     }
 
     this._prefsObserved = new Map();
+    this._prefsObserved.set("browser.onboarding.state", () => {
+      this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
+    });
     this._tours.forEach(tour => {
       let tourId = tour.id;
       this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
         this.markTourCompletionState(tourId);
+        this._checkWatermarkByTours();
       });
     });
     for (let [name, callback] of this._prefsObserved) {
       Services.prefs.addObserver(name, callback);
     }
   }
 
+  _checkWatermarkByTours() {
+    let tourDone = this._tours.every(tour => this.isTourCompleted(tour.id));
+    if (tourDone) {
+      sendMessageToChrome("set-prefs", [{
+        name: "browser.onboarding.state",
+        value: ICON_STATE_WATERMARK
+      }]);
+    }
+  }
+
   _clearPrefObserver() {
     if (this._prefsObserved) {
       for (let [name, callback] of this._prefsObserved) {
         Services.prefs.removeObserver(name, callback);
       }
       this._prefsObserved = null;
     }
   }
@@ -650,16 +668,28 @@ class Onboarding {
     this._overlay.remove();
     if (this._notificationBar) {
       this._notificationBar.remove();
     }
     this._tourItems = this._tourPages =
     this._overlayIcon = this._overlay = this._notificationBar = null;
   }
 
+  _onIconStateChange(state) {
+    switch (state) {
+      case ICON_STATE_DEFAULT:
+        this._overlayIcon.classList.remove("onboarding-watermark");
+        break;
+      case ICON_STATE_WATERMARK:
+        this._overlayIcon.classList.add("onboarding-watermark");
+        break;
+    }
+    return true;
+  }
+
   showOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
       this._loadTours(this._tours);
     }
 
     this.hideNotification();
     this.toggleModal(this._overlay.classList.toggle("onboarding-opened"));
@@ -918,16 +948,20 @@ class Onboarding {
       sendMessageToChrome("set-prefs", [
         {
           name: "browser.onboarding.notification.finished",
           value: true
         },
         {
           name: "browser.onboarding.notification.tour-ids-queue",
           value: ""
+        },
+        {
+          name: "browser.onboarding.state",
+          value: ICON_STATE_WATERMARK
         }
       ]);
       return;
     }
     let targetTourId = queue[0];
     let targetTour = this._tours.find(tour => tour.id == targetTourId);
 
     // Show the target tour notification
@@ -1001,16 +1035,20 @@ class Onboarding {
   }
 
   skipTour() {
     this.setToursCompleted(this._tours.map(tour => tour.id));
     sendMessageToChrome("set-prefs", [
       {
         name: "browser.onboarding.notification.finished",
         value: true
+      },
+      {
+        name: "browser.onboarding.state",
+        value: ICON_STATE_WATERMARK
       }
     ]);
   }
 
   _renderOverlay() {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-overlay";
     // We use `innerHTML` for more friendly reading.
@@ -1045,21 +1083,26 @@ class Onboarding {
     let button = this._window.document.createElement("button");
     let tooltipStringId = this._tourType === "new" ?
       "onboarding.overlay-icon-tooltip2" : "onboarding.overlay-icon-tooltip-updated2";
     let tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
     button.setAttribute("aria-label", tooltip);
     button.id = "onboarding-overlay-button";
     button.setAttribute("aria-haspopup", true);
     button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
-    let img = this._window.document.createElement("img");
-    img.id = "onboarding-overlay-button-icon";
-    img.setAttribute("role", "presentation");
-    img.src = "chrome://branding/content/icon64.png";
-    button.appendChild(img);
+    let defaultImg = this._window.document.createElement("img");
+    defaultImg.id = "onboarding-overlay-button-icon";
+    defaultImg.setAttribute("role", "presentation");
+    defaultImg.src = "chrome://branding/content/icon64.png";
+    button.appendChild(defaultImg);
+    let watermarkImg = this._window.document.createElement("img");
+    watermarkImg.id = "onboarding-overlay-button-watermark-icon";
+    watermarkImg.setAttribute("role", "presentation");
+    watermarkImg.src = "resource://onboarding/img/watermark64.png";
+    button.appendChild(watermarkImg);
     return button;
   }
 
   _loadTours(tours) {
     let itemsFrag = this._window.document.createDocumentFragment();
     let pagesFrag = this._window.document.createDocumentFragment();
     for (let tour of tours) {
       // Create tour navigation items dynamically
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
@@ -8,24 +8,28 @@ requestLongerTimeout(3);
 add_task(async function test_show_tour_notifications_in_order() {
   resetOnboardingDefaultState();
   Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
-  let expectedPrefUpdate = null;
+  let expectedPrefUpdates = null;
   await loopTourNotificationQueueOnceInOrder();
   await loopTourNotificationQueueOnceInOrder();
 
-  expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  expectedPrefUpdates = Promise.all([
+    promisePrefUpdated("browser.onboarding.notification.finished", true),
+    promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+  ]);
   await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-  await expectedPrefUpdate;
+  await expectedPrefUpdates;
+  await assertWatermarkIconDisplayed(tab.linkedBrowser);
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt each tour for more than 2 chances.");
   await BrowserTestUtils.removeTab(tab);
 
   async function loopTourNotificationQueueOnceInOrder() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
         await reloadTab(tab);
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
@@ -9,20 +9,25 @@ add_task(async function test_remove_all_
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
   await closeTourNotificationsOneByOne();
 
-  let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  let expectedPrefUpdates = [
+    promisePrefUpdated("browser.onboarding.notification.finished", true),
+    promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+  ];
   await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-  await expectedPrefUpdate;
+  await Promise.all(expectedPrefUpdates);
+  await assertWatermarkIconDisplayed(tab.linkedBrowser);
+
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations.");
   await BrowserTestUtils.removeTab(tab);
 
   async function closeTourNotificationsOneByOne() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
         await reloadTab(tab);
@@ -43,20 +48,25 @@ add_task(async function test_remove_all_
   resetOnboardingDefaultState();
   skipMuteNotificationOnFirstSession();
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
   await clickTourNotificationActionButtonsOneByOne();
 
-  let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  let expectedPrefUpdates = [
+    promisePrefUpdated("browser.onboarding.notification.finished", true),
+    promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+  ];
   await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-  await expectedPrefUpdate;
+  await Promise.all(expectedPrefUpdates);
+  await assertWatermarkIconDisplayed(tab.linkedBrowser);
+
   let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations.");
   await BrowserTestUtils.removeTab(tab);
 
   async function clickTourNotificationActionButtonsOneByOne() {
     for (let i = 0; i < tourIds.length; ++i) {
       if (tab) {
         await reloadTab(tab);
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
@@ -8,14 +8,18 @@ add_task(async function test_finish_tour
   skipMuteNotificationOnFirstSession();
 
   let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
 
   let totalMaxTime = Preferences.get("browser.onboarding.notification.max-life-time-all-tours-ms");
   Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", Math.floor((Date.now() - totalMaxTime) / 1000));
-  let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+  let expectedPrefUpdates = Promise.all([
+    promisePrefUpdated("browser.onboarding.notification.finished", true),
+    promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+  ]);
   await reloadTab(tab);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
-  await expectedPrefUpdate;
+  await expectedPrefUpdates;
+  await assertWatermarkIconDisplayed(tab.linkedBrowser);
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
@@ -3,24 +3,26 @@
 
  "use strict";
 
 add_task(async function test_skip_onboarding_tours() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   let expectedPrefUpdates = [
-    promisePrefUpdated("browser.onboarding.notification.finished", true)
+    promisePrefUpdated("browser.onboarding.notification.finished", true),
+    promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
   ];
   tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true)));
 
   let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
   let overlayClosedPromise = promiseOnboardingOverlayClosed(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser);
   await overlayClosedPromise;
   await Promise.all(expectedPrefUpdates);
+  await assertWatermarkIconDisplayed(tab.linkedBrowser);
 
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -83,8 +83,33 @@ add_task(async function test_click_actio
     let tab = tabs[i];
     await assertOverlaySemantics(tab.linkedBrowser);
     for (let id of tourIds) {
       await assertTourCompleted(id, id == completedTourId, tab.linkedBrowser);
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
+
+add_task(async function test_set_watermark_after_all_tour_completed() {
+  resetOnboardingDefaultState();
+
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.tour-type", "new"]
+  ]});
+
+  let tabs = [];
+  for (let url of URLs) {
+    let tab = await openTab(url);
+    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+    tabs.push(tab);
+  }
+  let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK);
+  TOUR_IDs.forEach(id => Preferences.set(`browser.onboarding.tour.${id}.completed`, true));
+  await expectedPrefUpdate;
+
+  for (let tab of tabs) {
+    await assertWatermarkIconDisplayed(tab.linkedBrowser);
+    await BrowserTestUtils.removeTab(tab);
+  }
+});
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -17,23 +17,26 @@ const TOUR_IDs = [
 const UPDATE_TOUR_IDs = [
   "onboarding-tour-performance",
   "onboarding-tour-library",
   "onboarding-tour-screenshots",
   "onboarding-tour-singlesearch",
   "onboarding-tour-customize",
   "onboarding-tour-sync",
 ];
+const ICON_STATE_WATERMARK = "watermark";
+const ICON_STATE_DEFAULT = "default";
 
 registerCleanupFunction(resetOnboardingDefaultState);
 
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to the default states
   // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
   Preferences.set("browser.onboarding.enabled", true);
+  Preferences.set("browser.onboarding.state", ICON_STATE_DEFAULT);
   Preferences.set("browser.onboarding.notification.finished", false);
   Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000);
   Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000);
   Preferences.set("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000);
   Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 8);
   Preferences.reset("browser.onboarding.notification.last-time-of-changing-tour-sec");
   Preferences.reset("browser.onboarding.notification.prompt-count");
   Preferences.reset("browser.onboarding.notification.tour-ids-queue");
@@ -270,8 +273,15 @@ function assertModalDialog(browser, args
         is(overlayButton, doc.activeElement,
           "Focus should be set on overlay button");
       }
       ok(!overlayButton.dataset.keyboardFocus,
         "Overlay button focus state should be cleared");
     }
   });
 }
+
+function assertWatermarkIconDisplayed(browser) {
+  return ContentTask.spawn(browser, {}, function() {
+    let overlayButton = content.document.getElementById("onboarding-overlay-button");
+    ok(overlayButton.classList.contains("onboarding-watermark"), "Should display the watermark onboarding icon");
+  });
+}