Bug 1357641 - Part 1: Add onboarding tour notification, r=mossop,flod draft
authorFischer.json <fischer.json@gmail.com>
Wed, 21 Jun 2017 13:09:29 +0800
changeset 597968 c0151a9808812bdf3ae72268a9eaaddd804f4923
parent 597883 e1e4a481b7e88dce163b9cccc2fb72032023befa
child 597969 3a5a48fc0f9a2ecbc6b74deefcd588d16ab01e50
child 598740 e4c2f940ab836e5c21f2822b517650651920fd6c
child 598821 140c60b97fd163e10188de5ebfb4f57366cc4d2a
push id65095
push userbmo:fliu@mozilla.com
push dateWed, 21 Jun 2017 07:43:14 +0000
reviewersmossop, flod
bugs1357641
milestone56.0a1
Bug 1357641 - Part 1: Add onboarding tour notification, r=mossop,flod This commit - adds onboarding tour notification - shows still not completed onboarding tour notifications in order - opens target tour from tour notification for the target tour MozReview-Commit-ID: AwLtwjoeARQ
browser/extensions/onboarding/bootstrap.js
browser/extensions/onboarding/content/onboarding.css
browser/extensions/onboarding/content/onboarding.js
browser/extensions/onboarding/locales/en-US/onboarding.properties
browser/extensions/onboarding/test/browser/head.js
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -6,17 +6,18 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 
 const PREF_WHITELIST = [
   "browser.onboarding.enabled",
   "browser.onboarding.hidden",
-  "browser.onboarding.notification.finished"
+  "browser.onboarding.notification.finished",
+  "browser.onboarding.notification.lastPrompted"
 ];
 
 /**
  * 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.
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -32,30 +32,32 @@
   offset-inline-start: 30px;
   background: url("img/overlay-icon.svg") no-repeat;
 }
 
 #onboarding-overlay-dialog {
   display: none;
 }
 
-#onboarding-overlay-close-btn {
+#onboarding-overlay-close-btn,
+#onboarding-notification-close-btn {
   position: absolute;
   top: 15px;
   offset-inline-end: 15px;
   cursor: pointer;
   width: 16px;
   height: 16px;
   background-image: url(chrome://browser/skin/sidebar/close.svg);
   background-position: center center;
   background-repeat: no-repeat;
   padding: 12px;
 }
 
-#onboarding-overlay-close-btn:hover {
+#onboarding-overlay-close-btn:hover,
+#onboarding-notification-close-btn:hover {
   background-color: rgba(204, 204, 204, 0.6);
 }
 
 #onboarding-overlay.onboarding-opened > #onboarding-overlay-dialog {
   width: 960px;
   height: 510px;
   background: #f5f5f7;
   border: 1px solid rgba(9, 6, 13, 0.1); /* #09060D, 0.1 opacity */
@@ -231,47 +233,150 @@
 }
 
 /* Tour Icons */
 #onboarding-tour-search {
   background-image: url("img/icons_search.svg");
 }
 
 #onboarding-tour-search.onboarding-active,
-#onboarding-tour-search:hover {
+#onboarding-tour-search:hover,
+#onboarding-notification-bar[data-target-tour-id=onboarding-tour-search] #onboarding-notification-tour-icon {
   background-image: url("img/icons_search-colored.svg");
 }
 
 #onboarding-tour-private-browsing {
   background-image: url("img/icons_private.svg");
 }
 
 #onboarding-tour-private-browsing.onboarding-active,
-#onboarding-tour-private-browsing:hover {
+#onboarding-tour-private-browsing:hover,
+#onboarding-notification-bar[data-target-tour-id=onboarding-tour-private-browsing] #onboarding-notification-tour-icon {
   background-image: url("img/icons_private-colored.svg");
 }
 
 #onboarding-tour-addons {
   background-image: url("img/icons_addons.svg");
 }
 
 #onboarding-tour-addons.onboarding-active,
-#onboarding-tour-addons:hover {
+#onboarding-tour-addons:hover,
+#onboarding-notification-bar[data-target-tour-id=onboarding-tour-addons] #onboarding-notification-tour-icon {
   background-image: url("img/icons_addons-colored.svg");
 }
 
 #onboarding-tour-customize {
   background-image: url("img/icons_customize.svg");
 }
 
 #onboarding-tour-customize.onboarding-active,
-#onboarding-tour-customize:hover {
+#onboarding-tour-customize:hover,
+#onboarding-notification-bar[data-target-tour-id=onboarding-tour-customize] #onboarding-notification-tour-icon {
   background-image: url("img/icons_customize-colored.svg");
 }
 
 #onboarding-tour-default-browser {
   background-image: url("img/icons_default.svg");
 }
 
 #onboarding-tour-default-browser.onboarding-active,
-#onboarding-tour-default-browser:hover {
+#onboarding-tour-default-browser:hover,
+#onboarding-notification-bar[data-target-tour-id=onboarding-tour-default-browser] #onboarding-notification-tour-icon {
   background-image: url("img/icons_default-colored.svg");
 }
+
+
+/* Tour Notifications */
+#onboarding-notification-bar {
+  position: fixed;
+  z-index: 998; /* We want this always under #onboarding-overlay */
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 122px;
+  min-width: 1060px;
+  background: rgba(255, 255, 255, 0.97);
+  border-top: 2px solid #e9e9e9;
+  transition: transform 0.8s;
+  transform: translateY(122px);
+}
+
+#onboarding-notification-bar.onboarding-opened {
+  transform: translateY(0px);
+}
+
+#onboarding-notification-icon {
+  height: 36px;
+  background: url("img/overlay-icon.svg") no-repeat;
+  background-size: 36px;
+  background-position: 34px;
+  padding-inline-start: 190px;
+  position: absolute;
+  offset-block-start: 50%;
+  transform: translateY(-50%);
+}
+
+#onboarding-notification-icon::after {
+  --height: 22px;
+  content: attr(data-tooltip);
+  background: #5ce6e6;
+  position: absolute;
+  top: 0;
+  offset-inline-start: 68px;
+  color: #10404a;
+  font-size: 12px;
+  min-height: var(--height);
+  line-height: var(--height);
+  border-radius: calc(var(--height) / 2);
+  border: 1px solid #fff;
+  padding: 0 10px;
+  text-align: center;
+}
+
+#onboarding-notification-close-btn {
+  background-color: rgba(255, 255, 255, 0.97);
+  border: none;
+  position: absolute;
+  offset-block-start: 50%;
+  offset-inline-end: 34px;
+  transform: translateY(-50%);
+}
+
+#onboarding-notification-message-section {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  position: absolute;
+  offset-block-start: 50%;
+  offset-inline-start: 50%;
+  transform: translate(-50%, -50%);
+}
+
+#onboarding-notification-body {
+  width: 420px;
+  margin: 0 15px;
+  color: #0c0c0d;;
+  display: inline-block;
+}
+
+#onboarding-notification-body * {
+  font-size: 13px
+}
+
+#onboarding-notification-tour-title {
+  margin: 0;
+}
+
+#onboarding-notification-tour-icon {
+  width: 64px;
+  height: 64px;
+  background-repeat: no-repeat;
+}
+
+#onboarding-notification-action-btn {
+  background: #0d96ff;
+  border: none;
+  border-radius: 3px;
+  padding: 10px 20px;
+  font-size: 14px;
+  color: #fff;
+  box-shadow: 0 1px 0 rgba(0,0,0,0.23);
+}
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -22,28 +22,40 @@ const BRAND_SHORT_NAME = Services.string
 
 /**
  * Add any number of tours, following the format
  * {
  *   // 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",
+ *   // The method returing strings used on tour notification
+ *   getNotificationStrings(bundle):
+ *     - title: // The string of tour notification title
+ *     - message: // The string of tour notification message
+ *     - button: // The string of tour notification action button title
  *   // Return a div appended with elements for this tours.
  *   // Each tour should contain the following 3 sections in the div:
  *   // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button.
  *   // Add no-button css class in the div if this tour does not need a button.
  *   // The overlay layout will responsively position and distribute space for these 3 sections based on viewport size
  *   getPage() {},
  * },
  **/
 var onboardingTours = [
   {
     id: "onboarding-tour-private-browsing",
     tourNameId: "onboarding.tour-private-browsing",
+    getNotificationStrings(bundle) {
+      return {
+        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"),
+        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message"),
+        button: bundle.GetStringFromName("onboarding.button.learnMore"),
+      };
+    },
     getPage(win) {
       let div = win.document.createElement("div");
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-private-browsing.title"></h1>
           <p data-l10n-id="onboarding.tour-private-browsing.description"></p>
         </section>
         <section class="onboarding-tour-content">
@@ -54,16 +66,23 @@ var onboardingTours = [
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-addons",
     tourNameId: "onboarding.tour-addons",
+    getNotificationStrings(bundle) {
+      return {
+        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-addons.title"),
+        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-addons.message", [BRAND_SHORT_NAME], 1),
+        button: bundle.GetStringFromName("onboarding.button.learnMore"),
+      };
+    },
     getPage(win) {
       let div = win.document.createElement("div");
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-addons.title"></h1>
           <p data-l10n-id="onboarding.tour-addons.description"></p>
         </section>
         <section class="onboarding-tour-content">
@@ -74,16 +93,23 @@ var onboardingTours = [
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-customize",
     tourNameId: "onboarding.tour-customize",
+    getNotificationStrings(bundle) {
+      return {
+        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-customize.title"),
+        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-customize.message", [BRAND_SHORT_NAME], 1),
+        button: bundle.GetStringFromName("onboarding.button.learnMore"),
+      };
+    },
     getPage(win) {
       let div = win.document.createElement("div");
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-customize.title"></h1>
           <p data-l10n-id="onboarding.tour-customize.description"></p>
         </section>
         <section class="onboarding-tour-content">
@@ -94,16 +120,23 @@ var onboardingTours = [
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-search",
     tourNameId: "onboarding.tour-search",
+    getNotificationStrings(bundle) {
+      return {
+        title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-search.title"),
+        message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-search.message"),
+        button: bundle.GetStringFromName("onboarding.button.learnMore"),
+      };
+    },
     getPage(win) {
       let div = win.document.createElement("div");
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-search.title"></h1>
           <p data-l10n-id="onboarding.tour-search.description"></p>
         </section>
         <section class="onboarding-tour-content">
@@ -114,16 +147,23 @@ var onboardingTours = [
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-default-browser",
     tourNameId: "onboarding.tour-default-browser",
+    getNotificationStrings(bundle) {
+      return {
+        title: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.title", [BRAND_SHORT_NAME], 1),
+        message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.message", [BRAND_SHORT_NAME], 1),
+        button: bundle.GetStringFromName("onboarding.button.learnMore"),
+      };
+    },
     getPage(win) {
       let div = win.document.createElement("div");
       let defaultBrowserButtonId = win.matchMedia("(-moz-os-version: windows-win7)").matches ?
         "onboarding.tour-default-browser.win7.button" : "onboarding.tour-default-browser.button";
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-default-browser.title"></h1>
           <p data-l10n-id="onboarding.tour-default-browser.description"></p>
@@ -142,41 +182,61 @@ var onboardingTours = [
 
 /**
  * The script won't be initialized if we turned off onboarding by
  * setting "browser.onboarding.enabled" to false.
  */
 class Onboarding {
   constructor(contentWindow) {
     this.init(contentWindow);
-    this._bundle = Services.strings.createBundle(BUNDLE_URI);
   }
 
   async init(contentWindow) {
     this._window = contentWindow;
     this._tourItems = [];
     this._tourPages = [];
     // We want to create and append elements after CSS is loaded so
     // no flash of style changes and no additional reflow.
     await this._loadCSS();
+    this._bundle = Services.strings.createBundle(BUNDLE_URI);
+
     this._overlayIcon = this._renderOverlayIcon();
     this._overlay = this._renderOverlay();
     this._window.document.body.appendChild(this._overlayIcon);
     this._window.document.body.appendChild(this._overlay);
 
     this._loadJS(UITOUR_JS_URI);
     this._loadJS(TOUR_AGENT_JS_URI);
 
     this._overlayIcon.addEventListener("click", this);
     this._overlay.addEventListener("click", this);
     // Destroy on unload. This is to ensure we remove all the stuff we left.
     // No any leak out there.
     this._window.addEventListener("unload", () => this.destroy());
 
     this._initPrefObserver();
+    this._initNotification();
+  }
+
+  _initNotification() {
+    let doc = this._window.document;
+    if (doc.hidden) {
+      // When the preloaded-browser feature is on,
+      // it would preload an hidden about:newtab in the background.
+      // We don't wnat to show notification in that hidden state.
+      let onVisible = () => {
+        if (!doc.hidden) {
+          doc.removeEventListener("visibilitychange", onVisible);
+          this.showNotification();
+        }
+      };
+      doc.addEventListener("visibilitychange", onVisible);
+    } else {
+      this.showNotification();
+    }
   }
 
   _initPrefObserver() {
     if (this._prefsObserved) {
       return;
     }
 
     this._prefsObserved = new Map();
@@ -214,58 +274,172 @@ class Onboarding {
       case "onboarding-overlay-icon":
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         this.toggleOverlay();
         break;
+
+      case "onboarding-notification-close-btn":
+        this.hideNotification();
+        break;
+
+      case "onboarding-notification-action-btn":
+        let tourId = this._notificationBar.dataset.targetTourId;
+        this.toggleOverlay();
+        this.gotoPage(tourId);
+        break;
     }
     if (evt.target.classList.contains("onboarding-tour-item")) {
       this.gotoPage(evt.target.id);
     }
   }
 
   destroy() {
     this._clearPrefObserver();
     this._overlayIcon.remove();
     this._overlay.remove();
+    if (this._notificationBar) {
+      this._notificationBar.remove();
+    }
   }
 
   toggleOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
       this._loadTours(onboardingTours);
     }
 
-    this._overlay.classList.toggle("opened");
+    this.hideNotification();
+    this._overlay.classList.toggle("onboarding-opened");
+
     let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
     if (hiddenCheckbox.checked) {
       this.hide();
-      return;
     }
-
-    this._overlay.classList.toggle("onboarding-opened");
   }
 
   gotoPage(tourId) {
     let targetPageId = `${tourId}-page`;
     for (let page of this._tourPages) {
       page.style.display = page.id != targetPageId ? "none" : "";
     }
     for (let li of this._tourItems) {
       if (li.id == tourId) {
         li.classList.add("onboarding-active");
       } else {
         li.classList.remove("onboarding-active");
       }
     }
   }
 
+  isTourCompleted(tourId) {
+    return Preferences.get(`browser.onboarding.tour.${tourId}.completed`, false);
+  }
+
+  showNotification() {
+    if (Preferences.get("browser.onboarding.notification.finished", false)) {
+      return;
+    }
+
+    // Pick out the next target tour to show
+    let targetTour = null;
+
+    // Take the last tour as the default last prompted
+    // so below would start from the 1st one if found no the last prompted from the pref.
+    let lastPromptedId = onboardingTours[onboardingTours.length - 1].id;
+    lastPromptedId = Preferences.get("browser.onboarding.notification.lastPrompted", lastPromptedId);
+
+    let lastTourIndex = onboardingTours.findIndex(tour => tour.id == lastPromptedId);
+    if (lastTourIndex < 0) {
+      // Couldn't find the tour.
+      // This could be because the pref was manually modified into unknown value
+      // or the tour version has been updated so have an new tours set.
+      // Take the last tour as the last prompted so would start from the 1st one below.
+      lastTourIndex = onboardingTours.length - 1;
+    }
+
+    // Form tours to notify into the order we want.
+    // For example, There are tour #0 ~ #5 and the #3 is the last prompted.
+    // This would form [#4, #5, #0, #1, #2, #3].
+    // So the 1st met incomplete tour in #4 ~ #2 would be the one to show.
+    // Or #3 would be the one to show if #4 ~ #2 are all completed.
+    let toursToNotify = [ ...onboardingTours.slice(lastTourIndex + 1), ...onboardingTours.slice(0, lastTourIndex + 1) ];
+    targetTour = toursToNotify.find(tour => !this.isTourCompleted(tour.id));
+
+
+    if (!targetTour) {
+      this.sendMessageToChrome("set-prefs", [{
+        name: "browser.onboarding.notification.finished",
+        value: true
+      }]);
+      return;
+    }
+
+    // Show the target tour notification
+    this._notificationBar = this._renderNotificationBar();
+    this._notificationBar.addEventListener("click", this);
+    this._window.document.body.appendChild(this._notificationBar);
+
+    this._notificationBar.dataset.targetTourId = targetTour.id;
+    let notificationStrings = targetTour.getNotificationStrings(this._bundle);
+    let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn");
+    actionBtn.textContent = notificationStrings.button;
+    let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title");
+    tourTitle.textContent = notificationStrings.title;
+    let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
+    tourMessage.textContent = notificationStrings.message;
+
+    this._notificationBar.addEventListener("transitionend", () => {
+      this._notificationBar.dataset.cssTransition = "end";
+    }, { once: true });
+    this._window.requestAnimationFrame(() => {
+      // Request the 2nd animation frame.
+      // This is to make sure the appending operation above and the css operation happen
+      // in the different layout tick so as to make sure the transition happens.
+      this._window.requestAnimationFrame(() => this._notificationBar.classList.add("onboarding-opened"));
+    });
+
+    this.sendMessageToChrome("set-prefs", [{
+      name: "browser.onboarding.notification.lastPrompted",
+      value: targetTour.id
+    }]);
+  }
+
+  hideNotification() {
+    if (this._notificationBar) {
+      this._notificationBar.classList.remove("onboarding-opened");
+      delete this._notificationBar.dataset.cssTransition;
+    }
+  }
+
+  _renderNotificationBar() {
+    let div = this._window.document.createElement("div");
+    div.id = "onboarding-notification-bar";
+    // Here we use `innerHTML` is for more friendly reading.
+    // The security should be fine because this is not from an external input.
+    div.innerHTML = `
+      <div id="onboarding-notification-icon"></div>
+      <section id="onboarding-notification-message-section">
+        <div id="onboarding-notification-tour-icon"></div>
+        <div id="onboarding-notification-body">
+          <h6 id="onboarding-notification-tour-title"></h6>
+          <span id="onboarding-notification-tour-message"></span>
+        </div>
+        <button id="onboarding-notification-action-btn"></button>
+      </section>
+      <button id="onboarding-notification-close-btn"></button>
+    `;
+    let toolTip = this._bundle.formatStringFromName("onboarding.notification-icon-tool-tip", [BRAND_SHORT_NAME], 1);
+    div.querySelector("#onboarding-notification-icon").setAttribute("data-tooltip", toolTip);
+    return div;
+  }
+
   hide() {
     this.sendMessageToChrome("set-prefs", [
       {
         name: "browser.onboarding.hidden",
         value: true
       },
       {
         name: "browser.onboarding.notification.finished",
@@ -274,17 +448,16 @@ class Onboarding {
     ]);
   }
 
   _renderOverlay() {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-overlay";
     // Here we use `innerHTML` is for more friendly reading.
     // The security should be fine because this is not from an external input.
-    // We're not shipping yet so l10n strings is going to be closed for now.
     div.innerHTML = `
       <div id="onboarding-overlay-dialog">
         <span id="onboarding-overlay-close-btn"></span>
         <header id="onboarding-header"></header>
         <nav>
           <ul id="onboarding-tour-list"></ul>
         </nav>
         <footer id="onboarding-footer">
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -1,45 +1,64 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-# LOCALIZATION NOTE(onboarding.tour-title): This string will be used in the overlay title. %S is brandShortName
+# LOCALIZATION NOTE(onboarding.overlay-title): This string will be used in the overlay title. %S is brandShortName
 onboarding.overlay-title=Getting started with %S
+
 onboarding.tour-search=One-Click Search
 onboarding.tour-search.title=Find the needle or the haystack.
-
 # LOCALIZATION NOTE (onboarding.tour-search.description): If Amazon is not part
 # of the default searchplugins for your locale, you can replace it with another
 # ecommerce website (if you're shipping one), but not with a general purpose
 # search engine (Google, Bing, Yandex, etc.). Alternatively, only reference
 # Wikipedia and drop Amazon from the text.
 onboarding.tour-search.description=Having a default search engine doesn’t mean it’s the only one you use. Pick a search engine or a site, like Amazon or Wikipedia, to search on the fly.
 onboarding.tour-search.button=Open One-Click Search
+onboarding.notification.onboarding-tour-search.title=Find it faster.
+onboarding.notification.onboarding-tour-search.message=Access all of your favorite search engines with a click. Search the whole Web or just one website right from the search box.
+
 onboarding.tour-private-browsing=Private Browsing
 onboarding.tour-private-browsing.title=A little privacy goes a long way.
-
 # LOCALIZATION NOTE(onboarding.tour-private-browsing.description): %S is brandShortName.
 onboarding.tour-private-browsing.description=Browse the internet without saving your searches or the sites you visited. When your session ends, the cookies disappear from %S like they were never there.
 onboarding.tour-private-browsing.button=Show Private Browsing in Menu
-onboarding.hidden-checkbox-label=Hide the tour
+onboarding.notification.onboarding-tour-private-browsing.title=Browse by yourself.
+onboarding.notification.onboarding-tour-private-browsing.message=There’s no reason to share your online life with trackers every time you browse. Want to keep something to yourself? Use Private Browsing with Tracking Protection.
+
 onboarding.tour-addons=Add-ons
 onboarding.tour-addons.title=Add more functionality.
+onboarding.notification.onboarding-tour-addons.title=Get more done.
+# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-addons.message): %S is brandShortName.
+onboarding.notification.onboarding-tour-addons.message=Add-ons are small apps you can add to %S that do lots of things — from managing to-do lists, to downloading videos, to changing the look of your browser.
 
 # LOCALIZATION NOTE(onboarding.tour-addons.description): This string will be used in the add-on tour description. %1$S is brandShortName
 onboarding.tour-addons.description=Add-ons expand %1$S’s built-in features, so %1$S works the way you do. Compare prices, check the weather or express your personality with a custom theme.
 onboarding.tour-addons.button=Show Add-ons in Menu
 onboarding.tour-customize=Customize
 onboarding.tour-customize.title=Do things your way.
-
 # LOCALIZATION NOTE(onboarding.tour-customize.description): This string will be used in the customize tour description. %S is brandShortName
 onboarding.tour-customize.description=Drag, drop, and reorder %S’s toolbar and menu to fit your needs. You can even select a compact theme to give websites more room.
 onboarding.tour-customize.button=Show Customize in Menu
+onboarding.notification.onboarding-tour-customize.title=Rearrange your toolbar.
+# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-customize.message): %S is brandShortName.
+onboarding.notification.onboarding-tour-customize.message=Put the tools you use most right at your fingertips. Add more options to your toolbar. Or select a theme to make %S reflect your personality.
+
 onboarding.tour-default-browser=Default Browser
 onboarding.tour-default-browser.title=We’re there for you.
-
 # LOCALIZATION NOTE(onboarding.tour-default-browser.description): This string will be used in the default browser tour description. %1$S is brandShortName
 onboarding.tour-default-browser.description=Love %1$S? Set it as your default browser. Then when you open a link from another application, %1$S has you covered.
-
 # LOCALIZATION NOTE(onboarding.tour-default-browser.button): Label for a button to open the OS default browser settings where it's not possible to set the default browser directly. (OSX, Linux, Windows 8 and higher)
 onboarding.tour-default-browser.button=Open Default Browser Settings
-
 # LOCALIZATION NOTE(onboarding.tour-default-browser.win7.button): Label for a button to directly set the default browser (Windows 7). %S is brandShortName
 onboarding.tour-default-browser.win7.button=Make %S Your Default Browser
+# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.title): %S is brandShortName.
+onboarding.notification.onboarding-tour-default-browser.title=Make %S your go-to browser.
+# LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.message): %1$S is brandShortName
+onboarding.notification.onboarding-tour-default-browser.message=It doesn’t take much to get the most from %1$S. Just set %1$S as your default browser and put control, customization, and protection on autopilot.
+
+onboarding.hidden-checkbox-label=Hide the tour
+
+#LOCALIZATION NOTE(onboarding.button.learnMore): this string is used as a button label, displayed near the message, and shared across all the onboarding notifications.
+onboarding.button.learnMore=Learn More
+
+# LOCALIZATION NOTE(onboarding.notification-icon-tool-tip): %S is brandShortName.
+onboarding.notification-icon-tool-tip=New to %S?
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -26,17 +26,17 @@ function promiseOnboardingOverlayLoaded(
   );
 }
 
 function promiseOnboardingOverlayOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let overlay = content.document.querySelector("#onboarding-overlay");
-        if (overlay.classList.contains("opened")) {
+        if (overlay.classList.contains("onboarding-opened")) {
           resolve(true);
           return;
         }
         resolve(false);
       });
     })
   };
   return BrowserTestUtils.waitForCondition(