Bug 1357021 - Part 1: Handle tours completed state, r=mossop
authorFischer.json <fischer.json@gmail.com>
Sun, 18 Jun 2017 14:46:09 +0800
changeset 366486 2a78b750352f
parent 366485 22017d54ba07
child 366487 3a2a088b9465
push id45613
push userrchien@mozilla.com
push dateWed, 28 Jun 2017 22:36:31 +0000
treeherderautoland@3a2a088b9465 [default view] [failures only]
reviewersmossop
bugs1357021
milestone56.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 1357021 - Part 1: Handle tours completed state, r=mossop This commit - turns on the `onboarding-complete` css style for completed tours - sets individual tour as completed when action button of that tour is clicked - sets all tours as completed if hide-the-tour checkbox is checked after toggling the overlay MozReview-Commit-ID: mps3BrdhOz
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
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -10,16 +10,25 @@ Cu.import("resource://gre/modules/Prefer
 
 const PREF_WHITELIST = [
   "browser.onboarding.enabled",
   "browser.onboarding.hidden",
   "browser.onboarding.notification.finished",
   "browser.onboarding.notification.lastPrompted"
 ];
 
+[
+  "onboarding-tour-private-browsing",
+  "onboarding-tour-addons",
+  "onboarding-tour-customize",
+  "onboarding-tour-search",
+  "onboarding-tour-default-browser",
+  "onboarding-tour-sync",
+].forEach(tourId => PREF_WHITELIST.push(`browser.onboarding.tour.${tourId}.completed`));
+
 /**
  * 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, such as `browser.onboarding.hidden`
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -172,29 +172,30 @@
 
 #onboarding-tour-sync-page form > input {
   margin-top: 10px;
   height: 40px;
   width: 80%;
   padding: 7px;
 }
 
-#onboarding-tour-sync-page form > button {
+#onboarding-tour-sync-page form > #onboarding-tour-sync-button {
   padding: 10px 20px;
   min-width: 40%;
   font-size: 15px;
   font-weight: normal;
   line-height: 20px;
   background: #0d96ff;
   border: none;
   border-radius: 3px;
   color: #fff;
   box-shadow: 0 1px 0 rgba(0,0,0,0.23);
   cursor: pointer;
   margin: 15px 0;
+  float: none;
 }
 
 /* Onboarding tour pages */
 .onboarding-tour-page {
   grid-row: page-start / footer-end;
   grid-column: page-start;
   display: grid;
   grid-template-rows: [tour-page-start] 393px [tour-button-start] 1fr [tour-page-end];
@@ -237,44 +238,44 @@
   border: none;
 }
 
 .onboarding-tour-page.onboarding-no-button > .onboarding-tour-content {
   grid-row: tour-page-start / tour-page-end;
   grid-column: tour-content-start / tour-page-end;
 }
 
-.onboarding-tour-button {
+.onboarding-tour-button-container {
   grid-row: tour-button-start / tour-page-end;
   grid-column: tour-content-start / tour-page-end;
 }
 
-.onboarding-tour-page.onboarding-no-button > .onboarding-tour-button {
+.onboarding-tour-page.onboarding-no-button > .onboarding-tour-button-container {
   display: none;
   grid-row: tour-page-end;
   grid-column: tour-page-end;
 }
 
-.onboarding-tour-button > button {
+.onboarding-tour-action-button {
   padding: 10px 20px;
   font-size: 15px;
   font-weight: 600;
   line-height: 21px;
   background: #0d96ff;
   border: none;
   border-radius: 3px;
   color: #fff;
   box-shadow: 0 1px 0 rgba(0,0,0,0.23);
   cursor: pointer;
   float: inline-end;
   margin-inline-end: 26px;
   margin-top: -32px;
 }
 
-.onboarding-tour-button > button:active {
+.onboarding-tour-action-button:active {
   background: #0881dd;
 }
 
 /* Tour Icons */
 #onboarding-tour-search {
   background-image: url("img/icons_search.svg");
 }
 
@@ -413,16 +414,17 @@
 
 #onboarding-notification-tour-title {
   margin: 0;
 }
 
 #onboarding-notification-tour-icon {
   width: 64px;
   height: 64px;
+  background-size: 64px;
   background-repeat: no-repeat;
 }
 
 #onboarding-notification-action-btn {
   background: #0d96ff;
   border: none;
   border-radius: 3px;
   padding: 10px 20px;
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -29,19 +29,19 @@ const BRAND_SHORT_NAME = Services.string
  *   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 onboarding-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
+ *   // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button-container.
+ *   // Add onboarding-no-button css class in the div if this tour does not need a button container.
+ *   // If there was a .onboarding-tour-action-button present and was clicked, tour would be marked as completed.
  *   getPage() {},
  * },
  **/
 var onboardingTours = [
   {
     id: "onboarding-tour-private-browsing",
     tourNameId: "onboarding.tour-private-browsing",
     getNotificationStrings(bundle) {
@@ -56,18 +56,18 @@ var onboardingTours = [
       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">
           <img src="resource://onboarding/img/figure_private.svg" />
         </section>
-        <aside class="onboarding-tour-button">
-          <button id="onboarding-tour-private-browsing-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
+        <aside class="onboarding-tour-button-container">
+          <button id="onboarding-tour-private-browsing-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-addons",
     tourNameId: "onboarding.tour-addons",
@@ -83,18 +83,18 @@ var onboardingTours = [
       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">
           <img src="resource://onboarding/img/figure_addons.svg" />
         </section>
-        <aside class="onboarding-tour-button">
-          <button id="onboarding-tour-addons-button" data-l10n-id="onboarding.tour-addons.button"></button>
+        <aside class="onboarding-tour-button-container">
+          <button id="onboarding-tour-addons-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-addons.button"></button>
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-customize",
     tourNameId: "onboarding.tour-customize",
@@ -110,18 +110,18 @@ var onboardingTours = [
       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">
           <img src="resource://onboarding/img/figure_customize.svg" />
         </section>
-        <aside class="onboarding-tour-button">
-          <button id="onboarding-tour-customize-button" data-l10n-id="onboarding.tour-customize.button"></button>
+        <aside class="onboarding-tour-button-container">
+          <button id="onboarding-tour-customize-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-customize.button"></button>
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-search",
     tourNameId: "onboarding.tour-search",
@@ -137,18 +137,18 @@ var onboardingTours = [
       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">
           <img src="resource://onboarding/img/figure_search.svg" />
         </section>
-        <aside class="onboarding-tour-button">
-          <button id="onboarding-tour-search-button" data-l10n-id="onboarding.tour-search.button"></button>
+        <aside class="onboarding-tour-button-container">
+          <button id="onboarding-tour-search-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-search.button"></button>
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-default-browser",
     tourNameId: "onboarding.tour-default-browser",
@@ -166,18 +166,18 @@ var onboardingTours = [
       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>
         </section>
         <section class="onboarding-tour-content">
           <img src="resource://onboarding/img/figure_default.svg" />
         </section>
-        <aside class="onboarding-tour-button">
-          <button id="onboarding-tour-default-browser-button" data-l10n-id="${defaultBrowserButtonId}"></button>
+        <aside class="onboarding-tour-button-container">
+          <button id="onboarding-tour-default-browser-button" class="onboarding-tour-action-button" data-l10n-id="${defaultBrowserButtonId}"></button>
         </aside>
       `;
       return div;
     },
   },
   {
     id: "onboarding-tour-sync",
     tourNameId: "onboarding.tour-sync",
@@ -196,17 +196,17 @@ var onboardingTours = [
           <h1 data-l10n-id="onboarding.tour-sync.title"></h1>
           <p data-l10n-id="onboarding.tour-sync.description"></p>
         </section>
         <section class="onboarding-tour-content">
           <form>
             <h3 data-l10n-id="onboarding.tour-sync.form.title"></h3>
             <p data-l10n-id="onboarding.tour-sync.form.description"></p>
             <input id="onboarding-tour-sync-email-input" type="text"></input><br />
-            <button id="onboarding-tour-sync-button" data-l10n-id="onboarding.tour-sync.button"></button>
+            <button id="onboarding-tour-sync-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-sync.button"></button>
           </form>
           <img src="resource://onboarding/img/figure_sync.svg" />
         </section>
       `;
       div.querySelector("#onboarding-tour-sync-email-input").placeholder =
         bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder");
       return div;
     },
@@ -273,16 +273,22 @@ class Onboarding {
     }
 
     this._prefsObserved = new Map();
     this._prefsObserved.set("browser.onboarding.hidden", prefValue => {
       if (prefValue) {
         this.destroy();
       }
     });
+    onboardingTours.forEach(tour => {
+      let tourId = tour.id;
+      this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
+        this.markTourCompletionState(tourId);
+      });
+    });
     for (let [name, callback] of this._prefsObserved) {
       Preferences.observe(name, callback);
     }
   }
 
   _clearPrefObserver() {
     if (this._prefsObserved) {
       for (let [name, callback] of this._prefsObserved) {
@@ -318,18 +324,22 @@ class Onboarding {
         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")) {
+    let classList = evt.target.classList;
+    if (classList.contains("onboarding-tour-item")) {
       this.gotoPage(evt.target.id);
+    } else if (classList.contains("onboarding-tour-action-button")) {
+      let activeItem = this._tourItems.find(item => item.classList.contains("onboarding-active"));
+      this.setToursCompleted([ activeItem.id ]);
     }
   }
 
   destroy() {
     this._clearPrefObserver();
     this._overlayIcon.remove();
     this._overlay.remove();
     if (this._notificationBar) {
@@ -365,16 +375,39 @@ class Onboarding {
       }
     }
   }
 
   isTourCompleted(tourId) {
     return Preferences.get(`browser.onboarding.tour.${tourId}.completed`, false);
   }
 
+  setToursCompleted(tourIds) {
+    let params = [];
+    tourIds.forEach(id => {
+      if (!this.isTourCompleted(id)) {
+        params.push({
+          name: `browser.onboarding.tour.${id}.completed`,
+          value: true
+        });
+      }
+    });
+    if (params.length > 0) {
+      this.sendMessageToChrome("set-prefs", params);
+    }
+  }
+
+  markTourCompletionState(tourId) {
+    // We are doing lazy load so there might be no items.
+    if (this._tourItems.length > 0 && this.isTourCompleted(tourId)) {
+      let targetItem = this._tourItems.find(item => item.id == tourId);
+      targetItem.classList.add("onboarding-complete");
+    }
+  }
+
   showNotification() {
     if (Preferences.get("browser.onboarding.notification.finished", false)) {
       return;
     }
 
     // Pick out the next target tour to show
     let targetTour = null;
 
@@ -464,16 +497,17 @@ class Onboarding {
       <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.setToursCompleted(onboardingTours.map(tour => tour.id));
     this.sendMessageToChrome("set-prefs", [
       {
         name: "browser.onboarding.hidden",
         value: true
       },
       {
         name: "browser.onboarding.notification.finished",
         value: true
@@ -495,17 +529,17 @@ class Onboarding {
         </nav>
         <footer id="onboarding-footer">
           <input type="checkbox" id="onboarding-tour-hidden-checkbox" /><label for="onboarding-tour-hidden-checkbox"></label>
         </footer>
       </div>
     `;
 
     div.querySelector("label[for='onboarding-tour-hidden-checkbox']").textContent =
-       this._bundle.GetStringFromName("onboarding.hidden-checkbox-label");
+       this._bundle.GetStringFromName("onboarding.hidden-checkbox-label-text");
     div.querySelector("#onboarding-header").textContent =
        this._bundle.formatStringFromName("onboarding.overlay-title", [BRAND_SHORT_NAME], 1);
     return div;
   }
 
   _renderOverlayIcon() {
     let img = this._window.document.createElement("div");
     img.id = "onboarding-overlay-icon";
@@ -539,16 +573,17 @@ class Onboarding {
       div.id = `${tour.id}-page`;
       div.classList.add("onboarding-tour-page");
       div.style.display = "none";
       pagesFrag.appendChild(div);
       // Cache elements in arrays for later use to avoid cost of querying elements
       this._tourItems.push(li);
       this._tourPages.push(div);
     }
+    tours.forEach(tour => this.markTourCompletionState(tour.id));
 
     let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
     let ul = this._window.document.getElementById("onboarding-tour-list");
     ul.appendChild(itemsFrag);
     let footer = this._window.document.getElementById("onboarding-footer");
     dialog.insertBefore(pagesFrag, footer);
     this.gotoPage(tours[0].id);
   }
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -1,13 +1,18 @@
 # 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.overlay-title): This string will be used in the overlay title. %S is brandShortName
 onboarding.overlay-title=Getting started with %S
+onboarding.hidden-checkbox-label-text=Mark all as complete, and 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?
 
 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.
@@ -21,23 +26,23 @@ onboarding.tour-private-browsing.title=A
 # 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.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.
+# 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.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.
@@ -50,24 +55,16 @@ onboarding.tour-default-browser.descript
 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?
-
 onboarding.tour-sync=Firefox Sync
 onboarding.tour-sync.title=Sync brings it all together.
 onboarding.tour-sync.description=Access your bookmarks and passwords on any device. You can even send a tab from your laptop to your phone! Better yet, you can choose what you sync and what you don’t.
 
 # LOCALIZATION NOTE(onboarding.tour-sync.form.title): This string is displayed
 # as a title and followed by onboarding.tour-sync.form.description.
 # Your translation should be consistent with the form displayed in
 # about:accounts when signing up to Firefox Account.