--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1699,16 +1699,18 @@ pref("browser.suppress_first_window_anim
pref("browser.onboarding.enabled", true);
// Mark this as an upgraded profile so we don't offer the initial new user onboarding tour.
pref("browser.onboarding.tourset-version", 1);
pref("browser.onboarding.hidden", false);
// On the Activity-Stream page, the snippet's position overlaps with our notification.
// So use `browser.onboarding.notification.finished` to let the AS page know
// if our notification is finished and safe to show their snippet.
pref("browser.onboarding.notification.finished", false);
+pref("browser.onboarding.newtour", "private,addons,customize,search,default,sync");
+pref("browser.onboarding.updatetour", "");
// Preferences for the Screenshots feature:
// Temporarily disable Screenshots in Beta & Release, so that we can gradually
// roll out the feature using SHIELD pref flipping.
#ifdef NIGHTLY_BUILD
pref("extensions.screenshots.system-disabled", false);
#else
pref("extensions.screenshots.system-disabled", true);
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -17,17 +17,17 @@ const BUNDLE_URI = "chrome://onboarding/
const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
const BRAND_SHORT_NAME = Services.strings
.createBundle("chrome://branding/locale/brand.properties")
.GetStringFromName("brandShortName");
/**
* Add any number of tours, following the format
- * {
+ * "tourId": { // The short tour id which could be saved in pref
* // The unique tour id
* id: "onboarding-tour-addons",
* // The string id of tour name which would be displayed on the navigation bar
* tourNameId: "onboarding.tour-addon",
* // The method returing strings used on tour notification
* getNotificationStrings(bundle):
* - title: // The string of tour notification title
* - message: // The string of tour notification message
@@ -35,18 +35,18 @@ const BRAND_SHORT_NAME = Services.string
* // 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-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 = [
- {
+var onboardingTourset = {
+ "private": {
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"),
};
@@ -63,17 +63,17 @@ var onboardingTours = [
</section>
<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;
},
},
- {
+ "addons": {
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"),
};
@@ -90,17 +90,17 @@ var onboardingTours = [
</section>
<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;
},
},
- {
+ "customize": {
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"),
};
@@ -117,17 +117,17 @@ var onboardingTours = [
</section>
<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;
},
},
- {
+ "search": {
id: "onboarding-tour-search",
tourNameId: "onboarding.tour-search2",
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"),
};
@@ -144,17 +144,17 @@ var onboardingTours = [
</section>
<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;
},
},
- {
+ "default": {
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"),
};
@@ -174,17 +174,17 @@ var onboardingTours = [
</section>
<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;
},
},
- {
+ "sync": {
id: "onboarding-tour-sync",
tourNameId: "onboarding.tour-sync2",
getNotificationStrings(bundle) {
return {
title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.title"),
message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.message"),
button: bundle.GetStringFromName("onboarding.button.learnMore"),
};
@@ -207,34 +207,41 @@ var onboardingTours = [
<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;
},
},
-];
+};
/**
* 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);
}
async init(contentWindow) {
this._window = contentWindow;
this._tourItems = [];
this._tourPages = [];
+ this._tours = [];
- // we only support the new user tour at this moment
- if (Services.prefs.getStringPref("browser.onboarding.tour-type", "update") !== "new") {
+ let tourIds = this._getTourIDList(Services.prefs.getStringPref("browser.onboarding.tour-type", "update"));
+ tourIds.forEach(tourId => {
+ if (onboardingTourset[tourId]) {
+ this._tours.push(onboardingTourset[tourId]);
+ }
+ });
+
+ if (this._tours.length === 0) {
return;
}
// 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);
@@ -251,16 +258,21 @@ class Onboarding {
// 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();
}
+ _getTourIDList(tourType) {
+ let tours = Services.prefs.getStringPref(`browser.onboarding.${tourType}tour`, "");
+ return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
+ }
+
_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 want to show notification in that hidden state.
let onVisible = () => {
if (!doc.hidden) {
@@ -280,17 +292,17 @@ class Onboarding {
}
this._prefsObserved = new Map();
this._prefsObserved.set("browser.onboarding.hidden", prefValue => {
if (prefValue) {
this.destroy();
}
});
- onboardingTours.forEach(tour => {
+ this._tours.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);
}
@@ -350,17 +362,17 @@ class Onboarding {
if (this._notificationBar) {
this._notificationBar.remove();
}
}
toggleOverlay() {
if (this._tourItems.length == 0) {
// Lazy loading until first toggle.
- this._loadTours(onboardingTours);
+ this._loadTours(this._tours);
}
this.hideNotification();
this._overlay.classList.toggle("onboarding-opened");
let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
if (hiddenCheckbox.checked) {
this.hide();
@@ -413,34 +425,34 @@ class Onboarding {
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;
+ let lastPromptedId = this._tours[this._tours.length - 1].id;
lastPromptedId = Preferences.get("browser.onboarding.notification.lastPrompted", lastPromptedId);
- let lastTourIndex = onboardingTours.findIndex(tour => tour.id == lastPromptedId);
+ let lastTourIndex = this._tours.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;
+ lastTourIndex = this._tours.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) ];
+ let toursToNotify = [ ...this._tours.slice(lastTourIndex + 1), ...this._tours.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
}]);
@@ -502,17 +514,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.setToursCompleted(this._tours.map(tour => tour.id));
this.sendMessageToChrome("set-prefs", [
{
name: "browser.onboarding.hidden",
value: true
},
{
name: "browser.onboarding.notification.finished",
value: true