Bug 1357020 - Should hide the onboarding tour if user explicitly checked the hide-the-tour checkbox, r=gasolin,mossop,rexboy

This patch
- adds one hide-onboarding-tour checkbox
- after toggling the overlay, hides the onboarding tour if user checked hide-the-tour checkbox
- creates the message channel between the chrome process and the content process to set prefs.
- listens to the pref-updated event and then hide the onboarding tour across pages.
- Add one browser_onboarding_hide_tours.js test

MozReview-Commit-ID: 7ZjbrhfO9dB

--HG--
extra : rebase_source : 5c59527ff7cb16996539a4eec49b47a9decafb3a
This commit is contained in:
Fischer.json 2017-06-10 16:14:08 +08:00
Родитель 0d02dac163
Коммит a228efff93
10 изменённых файлов: 248 добавлений и 16 удалений

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

@ -1695,6 +1695,11 @@ pref("browser.suppress_first_window_animation", true);
// Preferences for Photon onboarding system extension
pref("browser.onboarding.enabled", true);
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);
// Preferences for the Screenshots feature:
// Temporarily disable Screenshots in Beta & Release, so that we can gradually

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

@ -6,6 +6,41 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
const PREF_WHITELIST = [
"browser.onboarding.enabled",
"browser.onboarding.hidden",
"browser.onboarding.notification.finished"
];
/**
* 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`
* - {*} value the value to set
**/
function setPrefs(prefs) {
prefs.forEach(pref => {
if (PREF_WHITELIST.includes(pref.name)) {
Preferences.set(pref.name, pref.value);
}
});
}
function initContentMessageListener() {
Services.mm.addMessageListener("Onboarding:OnContentMessage", msg => {
switch (msg.data.action) {
case "set-prefs":
setPrefs(msg.data.params);
break;
}
});
}
function install(aData, aReason) {}
@ -13,6 +48,7 @@ function uninstall(aData, aReason) {}
function startup(aData, reason) {
Services.mm.loadFrameScript("resource://onboarding/onboarding.js", true);
initContentMessageListener();
}
function shutdown(aData, reason) {}

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

@ -96,6 +96,12 @@
#onboarding-overlay-dialog > footer {
grid-row: footer-start;
grid-column: dialog-start / tour-end;
font-size: 13px;
}
#onboarding-tour-hidden-checkbox {
margin-inline-start: 27px;
margin-inline-end: 10px;
}
/* Onboarding tour list */

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

@ -2,12 +2,13 @@
* 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/. */
/* global content */
/* eslint-env mozilla/frame-script */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
const ONBOARDING_CSS_URL = "resource://onboarding/onboarding.css";
const ABOUT_HOME_URL = "about:home";
@ -125,6 +126,43 @@ 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();
}
_initPrefObserver() {
if (this._prefsObserved) {
return;
}
this._prefsObserved = new Map();
this._prefsObserved.set("browser.onboarding.hidden", prefValue => {
if (prefValue) {
this.destroy();
}
});
for (let [name, callback] of this._prefsObserved) {
Preferences.observe(name, callback);
}
}
_clearPrefObserver() {
if (this._prefsObserved) {
for (let [name, callback] of this._prefsObserved) {
Preferences.ignore(name, callback);
}
this._prefsObserved = null;
}
}
/**
* @param {String} action the action to ask the chrome to do
* @param {Array} params the parameters for the action
*/
sendMessageToChrome(action, params) {
sendAsyncMessage("Onboarding:OnContentMessage", {
action, params
});
}
handleEvent(evt) {
@ -144,6 +182,7 @@ class Onboarding {
}
destroy() {
this._clearPrefObserver();
this._overlayIcon.remove();
this._overlay.remove();
}
@ -154,6 +193,13 @@ class Onboarding {
this._loadTours(onboardingTours);
}
this._overlay.classList.toggle("opened");
let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
if (hiddenCheckbox.checked) {
this.hide();
return;
}
this._overlay.classList.toggle("onboarding-opened");
}
@ -171,6 +217,19 @@ class Onboarding {
}
}
hide() {
this.sendMessageToChrome("set-prefs", [
{
name: "browser.onboarding.hidden",
value: true
},
{
name: "browser.onboarding.notification.finished",
value: true
}
]);
}
_renderOverlay() {
let div = this._window.document.createElement("div");
div.id = "onboarding-overlay";
@ -185,10 +244,13 @@ class Onboarding {
<ul id="onboarding-tour-list"></ul>
</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");
div.querySelector("#onboarding-header").textContent =
this._bundle.formatStringFromName("onboarding.overlay-title", [BRAND_SHORT_NAME], 1);
return div;
@ -264,20 +326,22 @@ class Onboarding {
}
}
addEventListener("load", function onLoad(evt) {
if (!content || evt.target != content.document) {
return;
}
removeEventListener("load", onLoad);
// Load onboarding module only when we enable it.
if (Services.prefs.getBoolPref("browser.onboarding.enabled", false) &&
!Services.prefs.getBoolPref("browser.onboarding.hidden", false)) {
let window = evt.target.defaultView;
// Load onboarding module only when we enable it.
if ((window.location.href == ABOUT_NEWTAB_URL ||
window.location.href == ABOUT_HOME_URL) &&
Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
addEventListener("load", function onLoad(evt) {
if (!content || evt.target != content.document) {
return;
}
removeEventListener("load", onLoad);
window.requestIdleCallback(() => {
new Onboarding(window);
});
}
}, true);
let window = evt.target.defaultView;
let location = window.location.href;
if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
window.requestIdleCallback(() => {
new Onboarding(window);
});
}
}, true);
}

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

@ -21,3 +21,4 @@ onboarding.tour-private-browsing.title=A little privacy goes a long way.
# 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.hidden-checkbox-label=Hide the tour

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

@ -17,4 +17,6 @@ FINAL_TARGET_FILES.features['onboarding@mozilla.org'] += [
'bootstrap.js',
]
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
JAR_MANIFESTS += ['jar.mn']

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

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
],
};

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

@ -0,0 +1,5 @@
[DEFAULT]
support-files =
head.js
[browser_onboarding_hide_tours.js]

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

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ABOUT_HOME_URL = "about:home";
const ABOUT_NEWTAB_URL = "about:newtab";
function assertOnboardingDestroyed(browser) {
return ContentTask.spawn(browser, {}, function() {
let expectedRemovals = [
"#onboarding-overlay",
"#onboarding-overlay-icon"
];
for (let selector of expectedRemovals) {
let removal = content.document.querySelector(selector);
ok(!removal, `Should remove ${selector} onboarding element`);
}
});
}
add_task(async function test_hide_onboarding_tours() {
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.hidden", false]]});
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.notification.finished", false]]});
let newtab = await BrowserTestUtils.openNewForegroundTab(gBrowser, ABOUT_NEWTAB_URL);
await promiseOnboardingOverlayLoaded(newtab.linkedBrowser);
let hometab = await BrowserTestUtils.openNewForegroundTab(gBrowser, ABOUT_HOME_URL);
await promiseOnboardingOverlayLoaded(hometab.linkedBrowser);
let expectedPrefUpdates = [
promisePrefUpdated("browser.onboarding.hidden", true),
promisePrefUpdated("browser.onboarding.notification.finished", true)
];
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, hometab.linkedBrowser);
await promiseOnboardingOverlayOpened(hometab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-hidden-checkbox", {}, hometab.linkedBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, hometab.linkedBrowser);
await Promise.all(expectedPrefUpdates);
// Test the hiding operation works arcoss pages
await assertOnboardingDestroyed(hometab.linkedBrowser);
await BrowserTestUtils.removeTab(hometab);
await assertOnboardingDestroyed(newtab.linkedBrowser);
await BrowserTestUtils.removeTab(newtab);
});

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

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/Preferences.jsm");
function promiseOnboardingOverlayLoaded(browser) {
// The onboarding overlay is init inside window.requestIdleCallback, not immediately,
// so we use check conditions here.
let condition = () => {
return ContentTask.spawn(browser, {}, function() {
return new Promise(resolve => {
let doc = content && content.document;
if (doc && doc.querySelector("#onboarding-overlay")) {
resolve(true);
return;
}
resolve(false);
});
})
};
return BrowserTestUtils.waitForCondition(
condition,
"Should load onboarding overlay",
100,
30
);
}
function promiseOnboardingOverlayOpened(browser) {
let condition = () => {
return ContentTask.spawn(browser, {}, function() {
return new Promise(resolve => {
let overlay = content.document.querySelector("#onboarding-overlay");
if (overlay.classList.contains("opened")) {
resolve(true);
return;
}
resolve(false);
});
})
};
return BrowserTestUtils.waitForCondition(
condition,
"Should open onboarding overlay",
100,
30
);
}
function promisePrefUpdated(name, expectedValue) {
return new Promise(resolve => {
let onUpdate = actualValue => {
Preferences.ignore(name, onUpdate);
is(expectedValue, actualValue, `Should update the pref of ${name}`);
resolve();
};
Preferences.observe(name, onUpdate);
});
}