Bug 1392475 - [Onboarding] Turn fox logo to watermark if all tours or notifications are finished. r=Fischer,gasolin

MozReview-Commit-ID: CLbiHqCmxr0

--HG--
extra : rebase_source : d04e9ec333b02e7c0c3f4b48d1b7712272228a80
This commit is contained in:
Rex Lee 2017-08-30 15:24:47 +08:00
Родитель a1632b557b
Коммит 4a265bd8d1
14 изменённых файлов: 146 добавлений и 18 удалений

Просмотреть файл

@ -1721,6 +1721,7 @@ pref("browser.suppress_first_window_animation", true);
pref("browser.onboarding.enabled", true); pref("browser.onboarding.enabled", true);
// Mark this as an upgraded profile so we don't offer the initial new user onboarding tour. // 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.tourset-version", 2);
pref("browser.onboarding.state", "default");
// On the Activity-Stream page, the snippet's position overlaps with our notification. // 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 // So use `browser.onboarding.notification.finished` to let the AS page know
// if our notification is finished and safe to show their snippet. // if our notification is finished and safe to show their snippet.

Просмотреть файл

@ -1687,7 +1687,7 @@ BrowserGlue.prototype = {
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
_migrateUI: function BG__migrateUI() { _migrateUI: function BG__migrateUI() {
const UI_VERSION = 53; const UI_VERSION = 54;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul"; const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion; let currentUIVersion;
@ -2078,6 +2078,15 @@ BrowserGlue.prototype = {
} }
} }
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. // Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION); Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
}, },

Просмотреть файл

@ -37,6 +37,7 @@ var OnboardingTourType = {
Services.prefs.clearUserPref("browser.onboarding.notification.prompt-count"); 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.last-time-of-changing-tour-sec");
Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue"); Services.prefs.clearUserPref("browser.onboarding.notification.tour-ids-queue");
Services.prefs.clearUserPref("browser.onboarding.state");
} }
Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION); Services.prefs.setIntPref(PREF_SEEN_TOURSET_VERSION, TOURSET_VERSION);
}, },

Просмотреть файл

@ -52,3 +52,11 @@ Once the tour set version is updated (ex: `2`), onboarding overlay should show t
Edit `browser/app/profile/firefox.js` and set `browser.onboarding.tourset-version` as `[tourset version]` (in integer format). 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`. 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.

3
browser/extensions/onboarding/bootstrap.js поставляемый
Просмотреть файл

@ -20,6 +20,7 @@ const BROWSER_READY_NOTIFICATION = "browser-delayed-startup-finished";
const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored"; const BROWSER_SESSION_STORE_NOTIFICATION = "sessionstore-windows-restored";
const PREF_WHITELIST = [ const PREF_WHITELIST = [
["browser.onboarding.enabled", PREF_BOOL], ["browser.onboarding.enabled", PREF_BOOL],
["browser.onboarding.state", PREF_STRING],
["browser.onboarding.notification.finished", PREF_BOOL], ["browser.onboarding.notification.finished", PREF_BOOL],
["browser.onboarding.notification.prompt-count", PREF_INT], ["browser.onboarding.notification.prompt-count", PREF_INT],
["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT], ["browser.onboarding.notification.last-time-of-changing-tour-sec", PREF_INT],
@ -47,7 +48,7 @@ let waitingForBrowserReady = true;
* *
* @param {Array} prefs the array of prefs to set. * @param {Array} prefs the array of prefs to set.
* The array element carrys info to set pref, should contain * 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 * - {*} value the value to set
**/ **/
function setPrefs(prefs) { function setPrefs(prefs) {

Двоичные данные
browser/extensions/onboarding/content/img/watermark64.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Просмотреть файл

@ -45,7 +45,7 @@
transition: outline-offset 150ms; transition: outline-offset 150ms;
} }
#onboarding-overlay-button-icon { #onboarding-overlay-button > img {
width: 32px; width: 32px;
vertical-align: top; vertical-align: top;
} }
@ -94,6 +94,16 @@
box-shadow: 2px 0 5px 0 rgba(74, 74, 79, 0.25); 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-overlay-dialog,
.onboarding-hidden, .onboarding-hidden,
#onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out, #onboarding-tour-sync-page[data-login-state=logged-in] .show-on-logged-out,

Просмотреть файл

@ -23,6 +23,8 @@ const PROMPT_COUNT_PREF = "browser.onboarding.notification.prompt-count";
const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog"; const ONBOARDING_DIALOG_ID = "onboarding-overlay-dialog";
const ONBOARDING_MIN_WIDTH_PX = 960; const ONBOARDING_MIN_WIDTH_PX = 960;
const SPEECH_BUBBLE_MIN_WIDTH_PX = 1130; 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 * Add any number of tours, key is the tourId, value should follow the format below
@ -419,6 +421,8 @@ class Onboarding {
this._loadJS(TOUR_AGENT_JS_URI); this._loadJS(TOUR_AGENT_JS_URI);
this._initPrefObserver(); 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. // Doing tour notification takes some effort. Let's do it on idle.
this._window.requestIdleCallback(() => this._initNotification()); this._window.requestIdleCallback(() => this._initNotification());
} }
@ -452,10 +456,14 @@ class Onboarding {
} }
this._prefsObserved = new Map(); 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 => { this._tours.forEach(tour => {
let tourId = tour.id; let tourId = tour.id;
this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => { this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
this.markTourCompletionState(tourId); this.markTourCompletionState(tourId);
this._checkWatermarkByTours();
}); });
}); });
for (let [name, callback] of this._prefsObserved) { for (let [name, callback] of this._prefsObserved) {
@ -463,6 +471,16 @@ class Onboarding {
} }
} }
_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() { _clearPrefObserver() {
if (this._prefsObserved) { if (this._prefsObserved) {
for (let [name, callback] of this._prefsObserved) { for (let [name, callback] of this._prefsObserved) {
@ -655,6 +673,18 @@ class Onboarding {
this._overlayIcon = this._overlay = this._notificationBar = null; 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() { showOverlay() {
if (this._tourItems.length == 0) { if (this._tourItems.length == 0) {
// Lazy loading until first toggle. // Lazy loading until first toggle.
@ -923,6 +953,10 @@ class Onboarding {
{ {
name: "browser.onboarding.notification.tour-ids-queue", name: "browser.onboarding.notification.tour-ids-queue",
value: "" value: ""
},
{
name: "browser.onboarding.state",
value: ICON_STATE_WATERMARK
} }
]); ]);
return; return;
@ -1006,6 +1040,10 @@ class Onboarding {
{ {
name: "browser.onboarding.notification.finished", name: "browser.onboarding.notification.finished",
value: true value: true
},
{
name: "browser.onboarding.state",
value: ICON_STATE_WATERMARK
} }
]); ]);
} }
@ -1050,11 +1088,16 @@ class Onboarding {
button.id = "onboarding-overlay-button"; button.id = "onboarding-overlay-button";
button.setAttribute("aria-haspopup", true); button.setAttribute("aria-haspopup", true);
button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`); button.setAttribute("aria-controls", `${ONBOARDING_DIALOG_ID}`);
let img = this._window.document.createElement("img"); let defaultImg = this._window.document.createElement("img");
img.id = "onboarding-overlay-button-icon"; defaultImg.id = "onboarding-overlay-button-icon";
img.setAttribute("role", "presentation"); defaultImg.setAttribute("role", "presentation");
img.src = "chrome://branding/content/icon64.png"; defaultImg.src = "chrome://branding/content/icon64.png";
button.appendChild(img); 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; return button;
} }

Просмотреть файл

@ -13,14 +13,18 @@ add_task(async function test_show_tour_notifications_in_order() {
let tourIds = TOUR_IDs; let tourIds = TOUR_IDs;
let tab = null; let tab = null;
let targetTourId = null; let targetTourId = null;
let expectedPrefUpdate = null; let expectedPrefUpdates = null;
await loopTourNotificationQueueOnceInOrder(); await loopTourNotificationQueueOnceInOrder();
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 reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser); await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await expectedPrefUpdate; await expectedPrefUpdates;
await assertWatermarkIconDisplayed(tab.linkedBrowser);
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt each tour for more than 2 chances."); ok(!tourId, "Should not prompt each tour for more than 2 chances.");
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);

Просмотреть файл

@ -14,10 +14,15 @@ add_task(async function test_remove_all_tour_notifications_through_close_button(
let targetTourId = null; let targetTourId = null;
await closeTourNotificationsOneByOne(); 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 reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser); await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await expectedPrefUpdate; await Promise.all(expectedPrefUpdates);
await assertWatermarkIconDisplayed(tab.linkedBrowser);
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations."); ok(!tourId, "Should not prompt tour notifications any more after closing all notifcations.");
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);
@ -48,10 +53,15 @@ add_task(async function test_remove_all_tour_notifications_through_action_button
let targetTourId = null; let targetTourId = null;
await clickTourNotificationActionButtonsOneByOne(); 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 reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser); await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await expectedPrefUpdate; await Promise.all(expectedPrefUpdates);
await assertWatermarkIconDisplayed(tab.linkedBrowser);
let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser); let tourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations."); ok(!tourId, "Should not prompt tour notifcations any more after taking actions on all notifcations.");
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);

Просмотреть файл

@ -13,9 +13,13 @@ add_task(async function test_finish_tour_notifcations_after_total_max_life_time(
let totalMaxTime = Preferences.get("browser.onboarding.notification.max-life-time-all-tours-ms"); 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)); 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 reloadTab(tab);
await promiseOnboardingOverlayLoaded(tab.linkedBrowser); await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
await expectedPrefUpdate; await expectedPrefUpdates;
await assertWatermarkIconDisplayed(tab.linkedBrowser);
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);
}); });

Просмотреть файл

@ -8,7 +8,8 @@ add_task(async function test_skip_onboarding_tours() {
let tourIds = TOUR_IDs; let tourIds = TOUR_IDs;
let expectedPrefUpdates = [ 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))); tourIds.forEach((id, idx) => expectedPrefUpdates.push(promisePrefUpdated(`browser.onboarding.tour.${id}.completed`, true)));
@ -21,6 +22,7 @@ add_task(async function test_skip_onboarding_tours() {
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser); await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-skip-tour-button", {}, tab.linkedBrowser);
await overlayClosedPromise; await overlayClosedPromise;
await Promise.all(expectedPrefUpdates); await Promise.all(expectedPrefUpdates);
await assertWatermarkIconDisplayed(tab.linkedBrowser);
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);
}); });

Просмотреть файл

@ -88,3 +88,28 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
await BrowserTestUtils.removeTab(tab); 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);
}
});

Просмотреть файл

@ -22,6 +22,8 @@ const UPDATE_TOUR_IDs = [
"onboarding-tour-customize", "onboarding-tour-customize",
"onboarding-tour-sync", "onboarding-tour-sync",
]; ];
const ICON_STATE_WATERMARK = "watermark";
const ICON_STATE_DEFAULT = "default";
registerCleanupFunction(resetOnboardingDefaultState); registerCleanupFunction(resetOnboardingDefaultState);
@ -29,6 +31,7 @@ function resetOnboardingDefaultState() {
// All the prefs should be reset to the default states // All the prefs should be reset to the default states
// and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here. // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
Preferences.set("browser.onboarding.enabled", true); 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.finished", false);
Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 300000); 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-per-tour-ms", 432000000);
@ -275,3 +278,10 @@ function assertModalDialog(browser, args) {
} }
}); });
} }
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");
});
}