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
--- 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.