summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm')
-rw-r--r--browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm411
1 files changed, 411 insertions, 0 deletions
diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
new file mode 100644
index 0000000000..0da59a5aa7
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
@@ -0,0 +1,411 @@
+/* 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 EXPORTED_SYMBOLS = ["AboutWelcomeChild"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ DEFAULT_SITES: "resource://activity-stream/lib/DefaultSites.jsm",
+ ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm",
+ shortURL: "resource://activity-stream/lib/ShortURL.jsm",
+ TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
+ AboutWelcomeDefaults:
+ "resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
+ NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "log", () => {
+ const { Logger } = ChromeUtils.import(
+ "resource://messaging-system/lib/Logger.jsm"
+ );
+ return new Logger("AboutWelcomeChild");
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "tippyTopProvider", () =>
+ (async () => {
+ const provider = new lazy.TippyTopProvider();
+ await provider.init();
+ return provider;
+ })()
+);
+
+const SEARCH_REGION_PREF = "browser.search.region";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "searchRegion",
+ SEARCH_REGION_PREF,
+ ""
+);
+
+/**
+ * Lazily get importable sites from parent or reuse cached ones.
+ */
+function getImportableSites(child) {
+ return (
+ getImportableSites.cache ??
+ (getImportableSites.cache = (async () => {
+ // Use tippy top to get packaged rich icons
+ const tippyTop = await lazy.tippyTopProvider;
+ // Remove duplicate entries if they would appear the same
+ return `[${[
+ ...new Set(
+ (await child.sendQuery("AWPage:IMPORTABLE_SITES")).map(url => {
+ // Get both rich icon and short name and save for deduping
+ const site = { url };
+ tippyTop.processSite(site, "*");
+ return JSON.stringify({
+ icon: site.tippyTopIcon,
+ label: lazy.shortURL(site),
+ });
+ })
+ ),
+ ]}]`;
+ })())
+ );
+}
+
+async function getDefaultSites(child) {
+ // Get default TopSites by region
+ let sites = lazy.DEFAULT_SITES.get(
+ lazy.DEFAULT_SITES.has(lazy.searchRegion) ? lazy.searchRegion : ""
+ );
+
+ // Use tippy top to get packaged rich icons
+ const tippyTop = await lazy.tippyTopProvider;
+ let defaultSites = sites.split(",").map(link => {
+ let site = { url: link };
+ tippyTop.processSite(site);
+ return {
+ icon: site.tippyTopIcon,
+ title: lazy.shortURL(site),
+ };
+ });
+ return Cu.cloneInto(defaultSites, child.contentWindow);
+}
+
+async function getSelectedTheme(child) {
+ let activeThemeId = await child.sendQuery("AWPage:GET_SELECTED_THEME");
+ return activeThemeId;
+}
+
+class AboutWelcomeChild extends JSWindowActorChild {
+ actorCreated() {
+ this.exportFunctions();
+ }
+
+ /**
+ * Send event that can be handled by the page
+ * @param {{type: string, data?: any}} action
+ */
+ sendToPage(action) {
+ lazy.log.debug(`Sending to page: ${action.type}`);
+ const win = this.document.defaultView;
+ const event = new win.CustomEvent("AboutWelcomeChromeToContent", {
+ detail: Cu.cloneInto(action, win),
+ });
+ win.dispatchEvent(event);
+ }
+
+ /**
+ * Export functions that can be called by page js
+ */
+ exportFunctions() {
+ let window = this.contentWindow;
+
+ Cu.exportFunction(this.AWGetFeatureConfig.bind(this), window, {
+ defineAs: "AWGetFeatureConfig",
+ });
+
+ Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, {
+ defineAs: "AWGetFxAMetricsFlowURI",
+ });
+
+ Cu.exportFunction(this.AWGetImportableSites.bind(this), window, {
+ defineAs: "AWGetImportableSites",
+ });
+
+ Cu.exportFunction(this.AWGetDefaultSites.bind(this), window, {
+ defineAs: "AWGetDefaultSites",
+ });
+
+ Cu.exportFunction(this.AWGetSelectedTheme.bind(this), window, {
+ defineAs: "AWGetSelectedTheme",
+ });
+
+ Cu.exportFunction(this.AWGetRegion.bind(this), window, {
+ defineAs: "AWGetRegion",
+ });
+
+ Cu.exportFunction(this.AWSelectTheme.bind(this), window, {
+ defineAs: "AWSelectTheme",
+ });
+
+ Cu.exportFunction(this.AWSendEventTelemetry.bind(this), window, {
+ defineAs: "AWSendEventTelemetry",
+ });
+
+ Cu.exportFunction(this.AWSendToParent.bind(this), window, {
+ defineAs: "AWSendToParent",
+ });
+
+ Cu.exportFunction(this.AWWaitForMigrationClose.bind(this), window, {
+ defineAs: "AWWaitForMigrationClose",
+ });
+
+ Cu.exportFunction(this.AWFinish.bind(this), window, {
+ defineAs: "AWFinish",
+ });
+
+ Cu.exportFunction(this.AWEnsureLangPackInstalled.bind(this), window, {
+ defineAs: "AWEnsureLangPackInstalled",
+ });
+
+ Cu.exportFunction(
+ this.AWNegotiateLangPackForLanguageMismatch.bind(this),
+ window,
+ {
+ defineAs: "AWNegotiateLangPackForLanguageMismatch",
+ }
+ );
+
+ Cu.exportFunction(this.AWSetRequestedLocales.bind(this), window, {
+ defineAs: "AWSetRequestedLocales",
+ });
+
+ Cu.exportFunction(this.AWSendToDeviceEmailsSupported.bind(this), window, {
+ defineAs: "AWSendToDeviceEmailsSupported",
+ });
+
+ Cu.exportFunction(this.AWNewScreen.bind(this), window, {
+ defineAs: "AWNewScreen",
+ });
+ }
+
+ /**
+ * Wrap a promise so content can use Promise methods.
+ */
+ wrapPromise(promise) {
+ return new this.contentWindow.Promise((resolve, reject) =>
+ promise.then(resolve, reject)
+ );
+ }
+
+ /**
+ * Clones the result of the query into the content window.
+ */
+ sendQueryAndCloneForContent(...sendQueryArgs) {
+ return this.wrapPromise(
+ (async () => {
+ return Cu.cloneInto(
+ await this.sendQuery(...sendQueryArgs),
+ this.contentWindow
+ );
+ })()
+ );
+ }
+
+ AWSelectTheme(data) {
+ return this.wrapPromise(
+ this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
+ );
+ }
+
+ /**
+ * Send initial data to page including experiment information
+ */
+ async getAWContent() {
+ let attributionData = await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA");
+
+ // Return to AMO gets returned early.
+ if (attributionData?.template) {
+ lazy.log.debug("Loading about:welcome with RTAMO attribution data");
+ return Cu.cloneInto(attributionData, this.contentWindow);
+ } else if (attributionData?.ua) {
+ lazy.log.debug("Loading about:welcome with UA attribution");
+ }
+
+ let experimentMetadata =
+ lazy.ExperimentAPI.getExperimentMetaData({
+ featureId: "aboutwelcome",
+ }) || {};
+
+ lazy.log.debug(
+ `Loading about:welcome with ${experimentMetadata?.slug ??
+ "no"} experiment`
+ );
+
+ let featureConfig = lazy.NimbusFeatures.aboutwelcome.getAllVariables();
+ featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT");
+ featureConfig.needPin = await this.sendQuery("AWPage:DOES_APP_NEED_PIN");
+ if (featureConfig.languageMismatchEnabled) {
+ featureConfig.appAndSystemLocaleInfo = await this.sendQuery(
+ "AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO"
+ );
+ }
+
+ // The MR2022 onboarding variable overrides the about:welcome templateMR
+ // variable if enrolled.
+ const useMROnboarding = lazy.NimbusFeatures.majorRelease2022.getVariable(
+ "onboarding"
+ );
+ const useTemplateMR = useMROnboarding ?? featureConfig.templateMR;
+
+ // FeatureConfig (from experiments) has higher precendence
+ // to defaults. But the `screens` property isn't defined we shouldn't
+ // override the default with `null`
+ let defaults = lazy.AboutWelcomeDefaults.getDefaults(useTemplateMR);
+
+ const content = await lazy.AboutWelcomeDefaults.prepareContentForReact({
+ ...attributionData,
+ ...experimentMetadata,
+ ...defaults,
+ ...featureConfig,
+ templateMR: useTemplateMR,
+ screens: featureConfig.screens ?? defaults.screens,
+ backdrop: featureConfig.backdrop ?? defaults.backdrop,
+ });
+
+ return Cu.cloneInto(content, this.contentWindow);
+ }
+
+ AWGetFeatureConfig() {
+ return this.wrapPromise(this.getAWContent());
+ }
+
+ AWGetFxAMetricsFlowURI() {
+ return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI"));
+ }
+
+ AWGetImportableSites() {
+ return this.wrapPromise(getImportableSites(this));
+ }
+
+ AWGetDefaultSites() {
+ return this.wrapPromise(getDefaultSites(this));
+ }
+
+ AWGetSelectedTheme() {
+ return this.wrapPromise(getSelectedTheme(this));
+ }
+
+ /**
+ * Send Event Telemetry
+ * @param {object} eventData
+ */
+ AWSendEventTelemetry(eventData) {
+ this.AWSendToParent("TELEMETRY_EVENT", {
+ ...eventData,
+ event_context: {
+ ...eventData.event_context,
+ },
+ });
+ }
+
+ /**
+ * Send message that can be handled by AboutWelcomeParent.jsm
+ * @param {string} type
+ * @param {any=} data
+ */
+ AWSendToParent(type, data) {
+ this.sendAsyncMessage(`AWPage:${type}`, data);
+ }
+
+ AWWaitForMigrationClose() {
+ return this.wrapPromise(this.sendQuery("AWPage:WAIT_FOR_MIGRATION_CLOSE"));
+ }
+
+ AWGetRegion() {
+ return this.wrapPromise(this.sendQuery("AWPage:GET_REGION"));
+ }
+
+ AWFinish() {
+ this.contentWindow.location.href = "about:home";
+ }
+
+ AWEnsureLangPackInstalled(negotiated, screenContent) {
+ const content = Cu.cloneInto(screenContent, {});
+ return this.wrapPromise(
+ this.sendQuery(
+ "AWPage:ENSURE_LANG_PACK_INSTALLED",
+ negotiated.langPack
+ ).then(() => {
+ const formatting = [];
+ const l10n = new Localization(
+ ["branding/brand.ftl", "browser/newtab/onboarding.ftl"],
+ false,
+ undefined,
+ // Use the system-ish then app then default locale.
+ [...negotiated.requestSystemLocales, "en-US"]
+ );
+
+ // Add the negotiated language name as args.
+ function addMessageArgsAndUseLangPack(obj) {
+ for (const value of Object.values(obj)) {
+ if (value?.string_id) {
+ value.args = {
+ ...value.args,
+ negotiatedLanguage: negotiated.langPackDisplayName,
+ };
+
+ // Expose fluent strings wanting lang pack as raw.
+ if (value.useLangPack) {
+ formatting.push(
+ l10n.formatValue(value.string_id, value.args).then(raw => {
+ delete value.string_id;
+ value.raw = raw;
+ })
+ );
+ }
+ }
+ }
+ }
+ addMessageArgsAndUseLangPack(content.languageSwitcher);
+ addMessageArgsAndUseLangPack(content);
+ return Promise.all(formatting).then(() =>
+ Cu.cloneInto(content, this.contentWindow)
+ );
+ })
+ );
+ }
+
+ AWSetRequestedLocales(requestSystemLocales) {
+ return this.sendQueryAndCloneForContent(
+ "AWPage:SET_REQUESTED_LOCALES",
+ requestSystemLocales
+ );
+ }
+
+ AWNegotiateLangPackForLanguageMismatch(appAndSystemLocaleInfo) {
+ return this.sendQueryAndCloneForContent(
+ "AWPage:NEGOTIATE_LANGPACK",
+ appAndSystemLocaleInfo
+ );
+ }
+
+ AWSendToDeviceEmailsSupported() {
+ return this.wrapPromise(
+ this.sendQuery("AWPage:SEND_TO_DEVICE_EMAILS_SUPPORTED")
+ );
+ }
+
+ AWNewScreen(screenId) {
+ return this.wrapPromise(this.sendQuery("AWPage:NEW_SCREEN", screenId));
+ }
+
+ /**
+ * @param {{type: string, detail?: any}} event
+ * @override
+ */
+ handleEvent(event) {
+ lazy.log.debug(`Received page event ${event.type}`);
+ }
+}