--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1711,16 +1711,17 @@ pref("urlclassifier.downloadAllowTable",
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-proto");
pref("browser.suppress_first_window_animation", true);
// Preferences for Photon onboarding system extension
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", 2);
+pref("browser.onboarding.state", "default");
// 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.notification.mute-duration-on-first-session-ms", 300000); // 5 mins
pref("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000); // 5 days
pref("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000); // 14 days
pref("browser.onboarding.notification.max-prompt-count-per-tour", 8);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1682,17 +1682,17 @@ BrowserGlue.prototype = {
return;
this._openPreferences("sync", { origin: "doorhanger" });
}
this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
},
// eslint-disable-next-line complexity
_migrateUI: function BG__migrateUI() {
- const UI_VERSION = 53;
+ const UI_VERSION = 54;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion;
if (Services.prefs.prefHasUserValue("browser.migration.version")) {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
} else {
// This is a new profile, nothing to migrate.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -2073,16 +2073,25 @@ BrowserGlue.prototype = {
let malwareList = Services.prefs.getCharPref(MALWARE_PREF);
if (malwareList.indexOf("goog-malware-shavar") != -1) {
malwareList.replace("goog-malware-shavar", "goog-malware-proto");
Services.prefs.setCharPref(MALWARE_PREF, malwareList);
}
}
}
+ if (currentUIVersion < 54) {
+ // Migrate browser.onboarding.hidden to browser.onboarding.state.
+ if (Services.prefs.prefHasUserValue("browser.onboarding.hidden")) {
+ let state = Services.prefs.getBoolPref("browser.onboarding.hidden") ? "watermark" : "default";
+ Services.prefs.setStringPref("browser.onboarding.state", state);
+ Services.prefs.clearUserPref("browser.onboarding.hidden");
+ }
+ }
+
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},
_checkForDefaultBrowser() {
// Perform default browser checking.
if (!ShellService) {
return;
--- a/browser/extensions/onboarding/OnboardingTourType.jsm
+++ b/browser/extensions/onboarding/OnboardingTourType.jsm
@@ -32,12 +32,13 @@ var OnboardingTourType = {
} else if (Services.prefs.getIntPref(PREF_SEEN_TOURSET_VERSION) < TOURSET_VERSION) {
// show the update user tour when tour set version is larger than the seen tourset version
Services.prefs.setStringPref(PREF_TOUR_TYPE, "update");
// Reset all the notification-related prefs because tours update.
Services.prefs.setBoolPref("browser.onboarding.notification.finished", false);
Services.prefs.clearUserPref("browser.onboarding.notification.prompt-count");
Services.prefs.clearUserPref("browser.onboarding.notification.last-time-of-changing-tour-sec");
Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue");
+ Services.prefs.clearUserPref("browser.onboarding.state");
}
Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
},
};
--- a/browser/extensions/onboarding/README.md
+++ b/browser/extensions/onboarding/README.md
@@ -47,8 +47,16 @@ Edit `browser/app/profile/firefox.js` an
The tourset version is used to track the last major tourset change version. The `tourset-version` pref store the major tourset version (ex: `1`) but not the current browser version. When browser update to the next version (ex: 58, 59) the tourset pref is still `1` if we didn't do any major tourset update.
Once the tour set version is updated (ex: `2`), onboarding overlay should show the update tour to the updated user (ex: update from v56 -> v57), even when user has watched the previous tours or preferred to hide the previous tours.
Edit `browser/app/profile/firefox.js` and set `browser.onboarding.tourset-version` as `[tourset version]` (in integer format).
For example, if we update the tourset in v60 and decide to show all update users the tour, we set `browser.onboarding.tourset-version` as `3`.
+
+## Icon states
+
+Onboarding module has two states for its overlay icon: `default` and `watermark`.
+By default, it shows `default` state.
+When either tours or notifications are all completed, the icon changes to the `watermark` state.
+The icon state is stored in `browser.onboarding.state`.
+When `tourset-version` is updated, or when we detect the `tour-type` is changed to `update`, icon state will be changed back to the `default` state.
--- a/browser/extensions/onboarding/bootstrap.js
+++ b/browser/extensions/onboarding/bootstrap.js
@@ -15,16 +15,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/FxAccounts.jsm");
const {PREF_STRING, PREF_BOOL, PREF_INT} = Ci.nsIPrefBranch;
const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
const PREF_WHITELIST = [
["browser.onboarding.enabled", PREF_BOOL],
+ ["browser.onboarding.state", PREF_STRING],
["browser.onboarding.notification.finished", PREF_BOOL],
["browser.onboarding.notification.prompt-count", PREF_INT],
["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT],
["browser.onboarding.notification.tour-ids-queue", PREF_STRING],
];
[
"onboarding-tour-addons",
@@ -42,17 +43,17 @@ let waitingForBrowserReady = true;
/**
* 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
+ * - {String} name the pref name, such as `browser.onboarding.state`
* - {*} value the value to set
**/
function setPrefs(prefs) {
prefs.forEach(pref => {
let prefObj = PREF_WHITELIST.find(([name, ]) => name == pref.name);
if (!prefObj) {
return;
}
new file mode 100644
index 0000000000000000000000000000000000000000..b8c0ffe162691995065ba0ab13189e33138dc7ec
GIT binary patch
literal 2118
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
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -40,17 +40,17 @@
/* Keyboard focus styling */
#onboarding-overlay-button:-moz-focusring {
outline: solid 2px rgba(0, 0, 0, 0.1);
-moz-outline-radius: 5px;
outline-offset: 5px;
transition: outline-offset 150ms;
}
-#onboarding-overlay-button-icon {
+#onboarding-overlay-button > img {
width: 32px;
vertical-align: top;
}
#onboarding-overlay-button::after {
content: " ";
border-radius: 50%;
margin-top: -1px;
@@ -89,16 +89,26 @@
margin-top: -10px;
box-shadow: -2px 0 5px 0 rgba(74, 74, 79, 0.25);
}
#onboarding-overlay-button:dir(rtl)::after {
box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25);
}
+#onboarding-overlay-button-watermark-icon,
+#onboarding-overlay-button.onboarding-watermark:not(:hover)::after,
+#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-icon {
+ display: none;
+}
+
+#onboarding-overlay-button.onboarding-watermark:not(:hover) > #onboarding-overlay-button-watermark-icon {
+ display: block;
+}
+
#onboarding-overlay-dialog,
.onboarding-hidden,
#onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out,
#onboarding-tour-sync-page[data-login-state=logged-out] .show-on-logged-in {
display: none;
}
.onboarding-close-btn {
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -18,16 +18,18 @@ const UITOUR_JS_URI = "resource://onboar
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");
const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
const ONBOARDING_MIN_WIDTH_PX = 960;
const SPEECH_BUBBLE_MIN_WIDTH_PX = 1130;
+const ICON_STATE_WATERMARK = "watermark";
+const ICON_STATE_DEFAULT = "default";
/**
* Add any number of tours, key is the tourId, value should follow the format below
* "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",
@@ -414,16 +416,18 @@ class Onboarding {
this._overlay = this._renderOverlay();
this._overlay.addEventListener("click", this);
this._overlay.addEventListener("keypress", this);
body.appendChild(this._overlay);
this._loadJS(TOUR_AGENT_JS_URI);
this._initPrefObserver();
+ this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
+
// Doing tour notification takes some effort. Let's do it on idle.
this._window.requestIdleCallback(() => this._initNotification());
}
_getTourIDList() {
let tours = Services.prefs.getStringPref(`browser.onboarding.${this._tourType}tour`, "");
return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
}
@@ -447,27 +451,41 @@ class Onboarding {
}
_initPrefObserver() {
if (this._prefsObserved) {
return;
}
this._prefsObserved = new Map();
+ this._prefsObserved.set("browser.onboarding.state", () => {
+ this._onIconStateChange(Services.prefs.getStringPref("browser.onboarding.state", ICON_STATE_DEFAULT));
+ });
this._tours.forEach(tour => {
let tourId = tour.id;
this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
this.markTourCompletionState(tourId);
+ this._checkWatermarkByTours();
});
});
for (let [name, callback] of this._prefsObserved) {
Services.prefs.addObserver(name, callback);
}
}
+ _checkWatermarkByTours() {
+ let tourDone = this._tours.every(tour => this.isTourCompleted(tour.id));
+ if (tourDone) {
+ sendMessageToChrome("set-prefs", [{
+ name: "browser.onboarding.state",
+ value: ICON_STATE_WATERMARK
+ }]);
+ }
+ }
+
_clearPrefObserver() {
if (this._prefsObserved) {
for (let [name, callback] of this._prefsObserved) {
Services.prefs.removeObserver(name, callback);
}
this._prefsObserved = null;
}
}
@@ -650,16 +668,28 @@ class Onboarding {
this._overlay.remove();
if (this._notificationBar) {
this._notificationBar.remove();
}
this._tourItems = this._tourPages =
this._overlayIcon = this._overlay = this._notificationBar = null;
}
+ _onIconStateChange(state) {
+ switch (state) {
+ case ICON_STATE_DEFAULT:
+ this._overlayIcon.classList.remove("onboarding-watermark");
+ break;
+ case ICON_STATE_WATERMARK:
+ this._overlayIcon.classList.add("onboarding-watermark");
+ break;
+ }
+ return true;
+ }
+
showOverlay() {
if (this._tourItems.length == 0) {
// Lazy loading until first toggle.
this._loadTours(this._tours);
}
this.hideNotification();
this.toggleModal(this._overlay.classList.toggle("onboarding-opened"));
@@ -918,16 +948,20 @@ class Onboarding {
sendMessageToChrome("set-prefs", [
{
name: "browser.onboarding.notification.finished",
value: true
},
{
name: "browser.onboarding.notification.tour-ids-queue",
value: ""
+ },
+ {
+ name: "browser.onboarding.state",
+ value: ICON_STATE_WATERMARK
}
]);
return;
}
let targetTourId = queue[0];
let targetTour = this._tours.find(tour => tour.id == targetTourId);
// Show the target tour notification
@@ -1001,16 +1035,20 @@ class Onboarding {
}
skipTour() {
this.setToursCompleted(this._tours.map(tour => tour.id));
sendMessageToChrome("set-prefs", [
{
name: "browser.onboarding.notification.finished",
value: true
+ },
+ {
+ name: "browser.onboarding.state",
+ value: ICON_STATE_WATERMARK
}
]);
}
_renderOverlay() {
let div = this._window.document.createElement("div");
div.id = "onboarding-overlay";
// We use `innerHTML` for more friendly reading.
@@ -1045,21 +1083,26 @@ class Onboarding {
let button = this._window.document.createElement("button");
let tooltipStringId = this._tourType === "new" ?
"onboarding.overlay-icon-tooltip2" : "onboarding.overlay-icon-tooltip-updated2";
let tooltip = this._bundle.formatStringFromName(tooltipStringId, [BRAND_SHORT_NAME], 1);
button.setAttribute("aria-label", tooltip);
button.id = "onboarding-overlay-button";
button.setAttribute("aria-haspopup", true);
button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
- let img = this._window.document.createElement("img");
- img.id = "onboarding-overlay-button-icon";
- img.setAttribute("role", "presentation");
- img.src = "chrome://branding/content/icon64.png";
- button.appendChild(img);
+ let defaultImg = this._window.document.createElement("img");
+ defaultImg.id = "onboarding-overlay-button-icon";
+ defaultImg.setAttribute("role", "presentation");
+ defaultImg.src = "chrome://branding/content/icon64.png";
+ button.appendChild(defaultImg);
+ let watermarkImg = this._window.document.createElement("img");
+ watermarkImg.id = "onboarding-overlay-button-watermark-icon";
+ watermarkImg.setAttribute("role", "presentation");
+ watermarkImg.src = "resource://onboarding/img/watermark64.png";
+ button.appendChild(watermarkImg);
return button;
}
_loadTours(tours) {
let itemsFrag = this._window.document.createDocumentFragment();
let pagesFrag = this._window.document.createDocumentFragment();
for (let tour of tours) {
// Create tour navigation items dynamically
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
@@ -8,24 +8,28 @@ requestLongerTimeout(3);
add_task(async function test_show_tour_notifications_in_order() {
resetOnboardingDefaultState();
Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 1);
skipMuteNotificationOnFirstSession();
let tourIds = TOUR_IDs;
let tab = null;
let targetTourId = null;
- let expectedPrefUpdate = null;
+ let expectedPrefUpdates = null;
await loopTourNotificationQueueOnceInOrder();
await loopTourNotificationQueueOnceInOrder();
- expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+ expectedPrefUpdates = Promise.all([
+ promisePrefUpdated("browser.onboarding.notification.finished", true),
+ promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+ ]);
await reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
- await expectedPrefUpdate;
+ await expectedPrefUpdates;
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt each tour for more than 2 chances.");
await BrowserTestUtils.removeTab(tab);
async function loopTourNotificationQueueOnceInOrder() {
for (let i = 0; i < tourIds.length; ++i) {
if (tab) {
await reloadTab(tab);
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
@@ -9,20 +9,25 @@ add_task(async function test_remove_all_
resetOnboardingDefaultState();
skipMuteNotificationOnFirstSession();
let tourIds = TOUR_IDs;
let tab = null;
let targetTourId = null;
await closeTourNotificationsOneByOne();
- let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+ let expectedPrefUpdates = [
+ promisePrefUpdated("browser.onboarding.notification.finished", true),
+ promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+ ];
await reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
- await expectedPrefUpdate;
+ await Promise.all(expectedPrefUpdates);
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
+
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations.");
await BrowserTestUtils.removeTab(tab);
async function closeTourNotificationsOneByOne() {
for (let i = 0; i < tourIds.length; ++i) {
if (tab) {
await reloadTab(tab);
@@ -43,20 +48,25 @@ add_task(async function test_remove_all_
resetOnboardingDefaultState();
skipMuteNotificationOnFirstSession();
let tourIds = TOUR_IDs;
let tab = null;
let targetTourId = null;
await clickTourNotificationActionButtonsOneByOne();
- let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+ let expectedPrefUpdates = [
+ promisePrefUpdated("browser.onboarding.notification.finished", true),
+ promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+ ];
await reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
- await expectedPrefUpdate;
+ await Promise.all(expectedPrefUpdates);
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
+
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations.");
await BrowserTestUtils.removeTab(tab);
async function clickTourNotificationActionButtonsOneByOne() {
for (let i = 0; i < tourIds.length; ++i) {
if (tab) {
await reloadTab(tab);
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
@@ -8,14 +8,18 @@ add_task(async function test_finish_tour
skipMuteNotificationOnFirstSession();
let tab = await openTab(ABOUT_NEWTAB_URL);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await promiseTourNotificationOpened(tab.linkedBrowser);
let totalMaxTime = Preferences.get("browser.onboarding.notification.max-life-time-all-tours-ms");
Preferences.set("browser.onboarding.notification.last-time-of-changing-tour-sec", Math.floor((Date.now() - totalMaxTime) / 1000));
- let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.finished", true);
+ let expectedPrefUpdates = Promise.all([
+ promisePrefUpdated("browser.onboarding.notification.finished", true),
+ promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
+ ]);
await reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
- await expectedPrefUpdate;
+ await expectedPrefUpdates;
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
await BrowserTestUtils.removeTab(tab);
});
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
@@ -3,24 +3,26 @@
"use strict";
add_task(async function test_skip_onboarding_tours() {
resetOnboardingDefaultState();
let tourIds = TOUR_IDs;
let expectedPrefUpdates = [
- promisePrefUpdated("browser.onboarding.notification.finished", true)
+ promisePrefUpdated("browser.onboarding.notification.finished", true),
+ promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK)
];
tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true)));
let tab = await openTab(ABOUT_NEWTAB_URL);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
let overlayClosedPromise = promiseOnboardingOverlayClosed(tab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser);
await overlayClosedPromise;
await Promise.all(expectedPrefUpdates);
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
await BrowserTestUtils.removeTab(tab);
});
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -83,8 +83,33 @@ add_task(async function test_click_actio
let tab = tabs[i];
await assertOverlaySemantics(tab.linkedBrowser);
for (let id of tourIds) {
await assertTourCompleted(id, id == completedTourId, tab.linkedBrowser);
}
await BrowserTestUtils.removeTab(tab);
}
});
+
+add_task(async function test_set_watermark_after_all_tour_completed() {
+ resetOnboardingDefaultState();
+
+ await SpecialPowers.pushPrefEnv({set: [
+ ["browser.onboarding.tour-type", "new"]
+ ]});
+
+ let tabs = [];
+ for (let url of URLs) {
+ let tab = await openTab(url);
+ await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+ await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+ await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+ tabs.push(tab);
+ }
+ let expectedPrefUpdate = promisePrefUpdated("browser.onboarding.state", ICON_STATE_WATERMARK);
+ TOUR_IDs.forEach(id => Preferences.set(`browser.onboarding.tour.${id}.completed`, true));
+ await expectedPrefUpdate;
+
+ for (let tab of tabs) {
+ await assertWatermarkIconDisplayed(tab.linkedBrowser);
+ await BrowserTestUtils.removeTab(tab);
+ }
+});
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -17,23 +17,26 @@ const TOUR_IDs = [
const UPDATE_TOUR_IDs = [
"onboarding-tour-performance",
"onboarding-tour-library",
"onboarding-tour-screenshots",
"onboarding-tour-singlesearch",
"onboarding-tour-customize",
"onboarding-tour-sync",
];
+const ICON_STATE_WATERMARK = "watermark";
+const ICON_STATE_DEFAULT = "default";
registerCleanupFunction(resetOnboardingDefaultState);
function resetOnboardingDefaultState() {
// All the prefs should be reset to the default states
// and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
Preferences.set("browser.onboarding.enabled", true);
+ Preferences.set("browser.onboarding.state", ICON_STATE_DEFAULT);
Preferences.set("browser.onboarding.notification.finished", false);
Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000);
Preferences.set("browser.onboarding.notification.max-life-time-per-tour-ms", 432000000);
Preferences.set("browser.onboarding.notification.max-life-time-all-tours-ms", 1209600000);
Preferences.set("browser.onboarding.notification.max-prompt-count-per-tour", 8);
Preferences.reset("browser.onboarding.notification.last-time-of-changing-tour-sec");
Preferences.reset("browser.onboarding.notification.prompt-count");
Preferences.reset("browser.onboarding.notification.tour-ids-queue");
@@ -270,8 +273,15 @@ function assertModalDialog(browser, args
is(overlayButton, doc.activeElement,
"Focus should be set on overlay button");
}
ok(!overlayButton.dataset.keyboardFocus,
"Overlay button focus state should be cleared");
}
});
}
+
+function assertWatermarkIconDisplayed(browser) {
+ return ContentTask.spawn(browser, {}, function() {
+ let overlayButton = content.document.getElementById("onboarding-overlay-button");
+ ok(overlayButton.classList.contains("onboarding-watermark"), "Should display the watermark onboarding icon");
+ });
+}