From 128c5750ea2eda31a84bc6803898270286c6e94a Mon Sep 17 00:00:00 2001 From: Punam Dahiya Date: Thu, 30 Jul 2020 23:41:06 +0000 Subject: [PATCH] Bug 1650852 - Support locale specific default top sites inside multistage about:welcome r=Mardak Differential Revision: https://phabricator.services.mozilla.com/D84777 --- .../newtab/aboutwelcome/AboutWelcomeChild.jsm | 71 +++++++++++- .../content/aboutwelcome.bundle.js | 54 ++++++---- .../components/MultiStageAboutWelcome.jsx | 101 ++++++++++-------- .../components/newtab/lib/ActivityStream.jsm | 45 ++------ .../components/newtab/lib/DefaultSites.jsm | 50 +++++++++ .../browser_aboutwelcome_multistage.js | 1 - 6 files changed, 217 insertions(+), 105 deletions(-) create mode 100644 browser/components/newtab/lib/DefaultSites.jsm diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm index e79291117358..876d28337756 100644 --- a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm +++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm @@ -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 diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js index 51860b4b713f..3e4634d470e0 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -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()); diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx index af09031de52f..9a70d6efb9c1 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx @@ -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 ( @@ -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 ? (
- {this.props.topSites.slice(0, 5).map(({ icon, label, title }) => ( -
+ {this.props.topSites.data + .slice(0, 5) + .map(({ icon, label, title }) => (
- {icon ? "" : label[0].toUpperCase()} +
+ {icon ? "" : label && label[0].toUpperCase()} +
+ {label &&
{label}
}
- {label &&
{label}
} -
- ))} + ))}
) : 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 ( @@ -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}