Bug 1357021 - Part 1: Handle tours completed state, r=mossop draft
authorFischer.json <fischer.json@gmail.com>
Sun, 18 Jun 2017 14:46:09 +0800
changeset 601430 d0d6820ecb5a4c127a821fdbbb552525d2519b71
parent 601429 30e7f72f59a67c321d2e79372245037e9c3d2e38
child 601431 537417c14198e25a89d744696adadc867504e6b3
push id66050
push userbmo:fliu@mozilla.com
push dateWed, 28 Jun 2017 19:01:25 +0000
reviewersmossop
bugs1357021
milestone56.0a1
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.