Bug 1650852 - Support locale specific default top sites inside multistage about:welcome r=Mardak

Differential Revision: https://phabricator.services.mozilla.com/D84777
This commit is contained in:
Punam Dahiya 2020-07-30 23:41:06 +00:00
Родитель 23a8387aec
Коммит 128c5750ea
6 изменённых файлов: 217 добавлений и 105 удалений

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

@ -11,8 +11,10 @@ const { XPCOMUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
DEFAULT_SITES: "resource://activity-stream/lib/DefaultSites.jsm",
ExperimentAPI: "resource://messaging-system/experiments/ExperimentAPI.jsm",
shortURL: "resource://activity-stream/lib/ShortURL.jsm",
Services: "resource://gre/modules/Services.jsm",
TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
});
@ -23,6 +25,14 @@ XPCOMUtils.defineLazyGetter(this, "log", () => {
return new Logger("AboutWelcomeChild");
});
XPCOMUtils.defineLazyGetter(this, "tippyTopProvider", () =>
(async () => {
const provider = new TippyTopProvider();
await provider.init();
return provider;
})()
);
function _parseOverrideContent(value) {
let result = {};
try {
@ -42,6 +52,15 @@ XPCOMUtils.defineLazyPreferenceGetter(
_parseOverrideContent
);
const SEARCH_REGION_PREF = "browser.search.region";
XPCOMUtils.defineLazyPreferenceGetter(
this,
"searchRegion",
SEARCH_REGION_PREF,
""
);
/**
* Lazily get importable sites from parent or reuse cached ones.
*/
@ -50,9 +69,7 @@ function getImportableSites(child) {
getImportableSites.cache ??
(getImportableSites.cache = (async () => {
// Use tippy top to get packaged rich icons
const tippyTop = new TippyTopProvider();
await tippyTop.init();
const tippyTop = await tippyTopProvider;
// Remove duplicate entries if they would appear the same
return `[${[
...new Set(
@ -71,6 +88,25 @@ function getImportableSites(child) {
);
}
async function getDefaultSites(child) {
// Get default TopSites by region
let sites = DEFAULT_SITES.get(
DEFAULT_SITES.has(searchRegion) ? searchRegion : ""
);
// Use tippy top to get packaged rich icons
const tippyTop = await tippyTopProvider;
let defaultSites = sites.split(",").map(link => {
let site = { url: link };
tippyTop.processSite(site);
return {
icon: site.tippyTopIcon,
title: shortURL(site),
};
});
return Cu.cloneInto(defaultSites, child.contentWindow);
}
class AboutWelcomeChild extends JSWindowActorChild {
actorCreated() {
this.exportFunctions();
@ -137,6 +173,14 @@ class AboutWelcomeChild extends JSWindowActorChild {
defineAs: "AWGetImportableSites",
});
Cu.exportFunction(this.AWGetDefaultSites.bind(this), window, {
defineAs: "AWGetDefaultSites",
});
Cu.exportFunction(this.AWWaitForRegionChange.bind(this), window, {
defineAs: "AWWaitForRegionChange",
});
Cu.exportFunction(this.AWSelectTheme.bind(this), window, {
defineAs: "AWSelectTheme",
});
@ -212,6 +256,10 @@ class AboutWelcomeChild extends JSWindowActorChild {
return this.wrapPromise(getImportableSites(this));
}
AWGetDefaultSites() {
return this.wrapPromise(getDefaultSites(this));
}
/**
* Send Event Telemetry
* @param {object} eventData
@ -239,6 +287,23 @@ class AboutWelcomeChild extends JSWindowActorChild {
return this.wrapPromise(this.sendQuery("AWPage:WAIT_FOR_MIGRATION_CLOSE"));
}
AWWaitForRegionChange() {
return this.wrapPromise(
new Promise(resolve =>
Services.prefs.addObserver(SEARCH_REGION_PREF, function observer(
subject,
topic,
data
) {
if (data === SEARCH_REGION_PREF && topic === "nsPref:changed") {
Services.prefs.removeObserver(SEARCH_REGION_PREF, observer);
resolve(searchRegion);
}
})
)
);
}
/**
* @param {{type: string, detail?: any}} event
* @override

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

@ -278,10 +278,6 @@ __webpack_require__.r(__webpack_exports__);
const DEFAULT_SITES = ["youtube-com", "facebook-com", "amazon", "reddit-com", "wikipedia-org", "twitter-com"].map(site => ({
icon: `resource://activity-stream/data/content/tippytop/images/${site}@2x.png`,
title: site.split("-")[0]
}));
const MultiStageAboutWelcome = props => {
const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0);
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
@ -326,20 +322,41 @@ const MultiStageAboutWelcome = props => {
args: "home",
where: "current"
}
});
const useImportable = props.message_id.includes("IMPORTABLE");
const [topSites, setTopSites] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(DEFAULT_SITES);
}); // Update top sites with default sites by region when region is available
const [region, setRegion] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
(async () => {
setRegion((await window.AWWaitForRegionChange()));
})();
}, []);
const useImportable = props.message_id.includes("IMPORTABLE"); // Track whether we have already sent the importable sites impression telemetry
const [importTelemetrySent, setImportTelemetrySent] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
const [topSites, setTopSites] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])([]);
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
(async () => {
let DEFAULT_SITES = await window.AWGetDefaultSites();
const importable = JSON.parse((await window.AWGetImportableSites()));
const showImportable = useImportable && importable.length >= 5;
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_SITES`, {
display: showImportable ? "importable" : "static",
importable: importable.length
if (!importTelemetrySent) {
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_SITES`, {
display: showImportable ? "importable" : "static",
importable: importable.length
});
setImportTelemetrySent(true);
}
setTopSites(showImportable ? {
data: importable,
showImportable
} : {
data: DEFAULT_SITES,
showImportable
});
setTopSites(showImportable ? importable : DEFAULT_SITES);
})();
}, [useImportable]);
}, [useImportable, region]);
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: `outer-wrapper multistageContainer`
}, props.screens.map(screen => {
@ -451,7 +468,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
renderTiles() {
switch (this.props.content.tiles.type) {
case "topsites":
return this.props.topSites ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
return this.props.topSites && this.props.topSites.data ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: `tiles-container ${this.props.content.tiles.info ? "info" : ""}`
}, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: "tiles-topsites-section",
@ -459,7 +476,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
id: "topsites-section",
"aria-labelledby": "topsites-disclaimer",
role: "region"
}, this.props.topSites.slice(0, 5).map(({
}, this.props.topSites.data.slice(0, 5).map(({
icon,
label,
title
@ -474,7 +491,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
backgroundColor: "transparent",
backgroundImage: `url(${icon})`
} : {}
}, icon ? "" : label[0].toUpperCase()), label && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
}, icon ? "" : label && label[0].toUpperCase()), label && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: "host"
}, label))))) : null;
@ -535,7 +552,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
}
renderDisclaimer() {
if (this.props.content.tiles && this.props.content.tiles.type === "topsites") {
if (this.props.content.tiles && this.props.content.tiles.type === "topsites" && this.props.topSites && this.props.topSites.showImportable) {
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
text: this.props.content.disclaimer
}, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
@ -549,7 +566,8 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
render() {
const {
content
content,
topSites
} = this.props;
const hasSecondaryTopCTA = content.secondary_button && content.secondary_button.position === "top";
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", {
@ -570,7 +588,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
value: "primary_button",
onClick: this.handleAction
}))), content.secondary_button && content.secondary_button.position !== "top" ? this.renderSecondaryCTA() : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("nav", {
className: content.tiles && content.tiles.type === "topsites" ? "steps has-disclaimer" : "steps",
className: content.tiles && content.tiles.type === "topsites" && topSites && topSites.showImportable ? "steps has-disclaimer" : "steps",
"data-l10n-id": "onboarding-welcome-steps-indicator",
"data-l10n-args": `{"current": ${parseInt(this.props.order, 10) + 1}, "total": ${this.props.totalNumberOfScreens}}`
}, this.renderStepsIndicator()), this.renderDisclaimer());

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

@ -8,18 +8,6 @@ import { Zap } from "./Zap";
import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils";
import { addUtmParams } from "../../asrouter/templates/FirstRun/addUtmParams";
const DEFAULT_SITES = [
"youtube-com",
"facebook-com",
"amazon",
"reddit-com",
"wikipedia-org",
"twitter-com",
].map(site => ({
icon: `resource://activity-stream/data/content/tippytop/images/${site}@2x.png`,
title: site.split("-")[0],
}));
export const MultiStageAboutWelcome = props => {
const [index, setScreenIndex] = useState(0);
useEffect(() => {
@ -69,19 +57,37 @@ export const MultiStageAboutWelcome = props => {
data: { args: "home", where: "current" },
});
const useImportable = props.message_id.includes("IMPORTABLE");
const [topSites, setTopSites] = useState(DEFAULT_SITES);
// Update top sites with default sites by region when region is available
const [region, setRegion] = useState(null);
useEffect(() => {
(async () => {
setRegion(await window.AWWaitForRegionChange());
})();
}, []);
const useImportable = props.message_id.includes("IMPORTABLE");
// Track whether we have already sent the importable sites impression telemetry
const [importTelemetrySent, setImportTelemetrySent] = useState(null);
const [topSites, setTopSites] = useState([]);
useEffect(() => {
(async () => {
let DEFAULT_SITES = await window.AWGetDefaultSites();
const importable = JSON.parse(await window.AWGetImportableSites());
const showImportable = useImportable && importable.length >= 5;
AboutWelcomeUtils.sendImpressionTelemetry(`${props.message_id}_SITES`, {
display: showImportable ? "importable" : "static",
importable: importable.length,
});
setTopSites(showImportable ? importable : DEFAULT_SITES);
if (!importTelemetrySent) {
AboutWelcomeUtils.sendImpressionTelemetry(`${props.message_id}_SITES`, {
display: showImportable ? "importable" : "static",
importable: importable.length,
});
setImportTelemetrySent(true);
}
setTopSites(
showImportable
? { data: importable, showImportable }
: { data: DEFAULT_SITES, showImportable }
);
})();
}, [useImportable]);
}, [useImportable, region]);
return (
<React.Fragment>
@ -198,7 +204,7 @@ export class WelcomeScreen extends React.PureComponent {
renderTiles() {
switch (this.props.content.tiles.type) {
case "topsites":
return this.props.topSites ? (
return this.props.topSites && this.props.topSites.data ? (
<div
className={`tiles-container ${
this.props.content.tiles.info ? "info" : ""
@ -211,29 +217,31 @@ export class WelcomeScreen extends React.PureComponent {
aria-labelledby="topsites-disclaimer"
role="region"
>
{this.props.topSites.slice(0, 5).map(({ icon, label, title }) => (
<div
className="site"
key={icon + label}
aria-label={title ? title : label}
role="img"
>
{this.props.topSites.data
.slice(0, 5)
.map(({ icon, label, title }) => (
<div
className="icon"
style={
icon
? {
backgroundColor: "transparent",
backgroundImage: `url(${icon})`,
}
: {}
}
className="site"
key={icon + label}
aria-label={title ? title : label}
role="img"
>
{icon ? "" : label[0].toUpperCase()}
<div
className="icon"
style={
icon
? {
backgroundColor: "transparent",
backgroundImage: `url(${icon})`,
}
: {}
}
>
{icon ? "" : label && label[0].toUpperCase()}
</div>
{label && <div className="host">{label}</div>}
</div>
{label && <div className="host">{label}</div>}
</div>
))}
))}
</div>
</div>
) : null;
@ -294,7 +302,9 @@ export class WelcomeScreen extends React.PureComponent {
renderDisclaimer() {
if (
this.props.content.tiles &&
this.props.content.tiles.type === "topsites"
this.props.content.tiles.type === "topsites" &&
this.props.topSites &&
this.props.topSites.showImportable
) {
return (
<Localized text={this.props.content.disclaimer}>
@ -306,7 +316,7 @@ export class WelcomeScreen extends React.PureComponent {
}
render() {
const { content } = this.props;
const { content, topSites } = this.props;
const hasSecondaryTopCTA =
content.secondary_button && content.secondary_button.position === "top";
return (
@ -336,7 +346,10 @@ export class WelcomeScreen extends React.PureComponent {
: null}
<nav
className={
content.tiles && content.tiles.type === "topsites"
content.tiles &&
content.tiles.type === "topsites" &&
topSites &&
topSites.showImportable
? "steps has-disclaimer"
: "steps"
}

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

@ -11,6 +11,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/AppConstants.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"DEFAULT_SITES",
"resource://activity-stream/lib/DefaultSites.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"Region",
@ -103,45 +109,6 @@ ChromeUtils.defineModuleGetter(
"resource://activity-stream/lib/DiscoveryStreamFeed.jsm"
);
const DEFAULT_SITES = new Map([
// This first item is the global list fallback for any unexpected geos
[
"",
"https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.reddit.com/,https://www.amazon.com/,https://twitter.com/",
],
[
"US",
"https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/",
],
[
"CA",
"https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://www.amazon.ca/,https://twitter.com/",
],
[
"DE",
"https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.de/,https://www.ebay.de/,https://www.wikipedia.org/,https://www.reddit.com/",
],
[
"PL",
"https://www.youtube.com/,https://www.facebook.com/,https://allegro.pl/,https://www.wikipedia.org/,https://www.olx.pl/,https://www.wykop.pl/",
],
[
"RU",
"https://vk.com/,https://www.youtube.com/,https://ok.ru/,https://www.avito.ru/,https://www.aliexpress.com/,https://www.wikipedia.org/",
],
[
"GB",
"https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.amazon.co.uk/,https://www.bbc.co.uk/,https://www.ebay.co.uk/",
],
[
"FR",
"https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.amazon.fr/,https://www.leboncoin.fr/,https://twitter.com/",
],
[
"CN",
"https://www.baidu.com/,https://www.zhihu.com/,https://www.ifeng.com/,https://weibo.com/,https://www.ctrip.com/,https://www.iqiyi.com/",
],
]);
const REGION_STORIES_CONFIG =
"browser.newtabpage.activity-stream.discoverystream.region-stories-config";
const REGION_SPOCS_CONFIG =

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

@ -0,0 +1,50 @@
/* 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/. */
"use strict";
const DEFAULT_SITES_MAP = new Map([
// This first item is the global list fallback for any unexpected geos
[
"",
"https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.reddit.com/,https://www.amazon.com/,https://twitter.com/",
],
[
"US",
"https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/",
],
[
"CA",
"https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://www.amazon.ca/,https://twitter.com/",
],
[
"DE",
"https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.de/,https://www.ebay.de/,https://www.wikipedia.org/,https://www.reddit.com/",
],
[
"PL",
"https://www.youtube.com/,https://www.facebook.com/,https://allegro.pl/,https://www.wikipedia.org/,https://www.olx.pl/,https://www.wykop.pl/",
],
[
"RU",
"https://vk.com/,https://www.youtube.com/,https://ok.ru/,https://www.avito.ru/,https://www.aliexpress.com/,https://www.wikipedia.org/",
],
[
"GB",
"https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.amazon.co.uk/,https://www.bbc.co.uk/,https://www.ebay.co.uk/",
],
[
"FR",
"https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.amazon.fr/,https://www.leboncoin.fr/,https://twitter.com/",
],
[
"CN",
"https://www.baidu.com/,https://www.zhihu.com/,https://www.ifeng.com/,https://weibo.com/,https://www.ctrip.com/,https://www.iqiyi.com/",
],
]);
this.EXPORTED_SYMBOLS = ["DEFAULT_SITES"];
// Immutable for export.
this.DEFAULT_SITES = Object.freeze(DEFAULT_SITES_MAP);

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

@ -211,7 +211,6 @@ add_task(async function test_Multistage_About_Welcome_branches() {
"main.AW_STEP2",
"button.secondary",
"div.tiles-container.info",
"p.tiles-topsites-disclaimer",
],
// Unexpected selectors:
[