summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/aboutwelcome
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/aboutwelcome')
-rw-r--r--browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm386
-rw-r--r--browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm310
-rw-r--r--browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js1501
-rw-r--r--browser/components/newtab/aboutwelcome/content/aboutwelcome.css538
-rw-r--r--browser/components/newtab/aboutwelcome/content/aboutwelcome.html26
-rw-r--r--browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm115
6 files changed, 2876 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..459603c379
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
@@ -0,0 +1,386 @@
+/* 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.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+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",
+ TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ const { Logger } = ChromeUtils.import(
+ "resource://messaging-system/lib/Logger.jsm"
+ );
+ return new Logger("AboutWelcomeChild");
+});
+
+XPCOMUtils.defineLazyGetter(this, "tippyTopProvider", () =>
+ (async () => {
+ const provider = new TippyTopProvider();
+ await provider.init();
+ return provider;
+ })()
+);
+
+function _parseOverrideContent(value) {
+ let result = {};
+ try {
+ result = value ? JSON.parse(value) : {};
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ return result;
+}
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "multiStageAboutWelcomeContent",
+ "browser.aboutwelcome.overrideContent",
+ "",
+ null,
+ _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.
+ */
+function getImportableSites(child) {
+ return (
+ getImportableSites.cache ??
+ (getImportableSites.cache = (async () => {
+ // Use tippy top to get packaged rich icons
+ const tippyTop = await 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: shortURL(site),
+ });
+ })
+ ),
+ ]}]`;
+ })())
+ );
+}
+
+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);
+}
+
+async function getSelectedTheme(child) {
+ let activeThemeId = await child.sendQuery("AWPage:GET_SELECTED_THEME");
+ return activeThemeId;
+}
+
+class AboutWelcomeChild extends JSWindowActorChild {
+ actorCreated() {
+ this.exportFunctions();
+ this.initWebProgressListener();
+ }
+
+ initWebProgressListener() {
+ const webProgress = this.manager.browsingContext.top.docShell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+
+ const listener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ listener.onLocationChange = (aWebProgress, aRequest, aLocation, aFlags) => {
+ // Exit if actor 'AboutWelcome' has already been destroyed or
+ // content window doesn't exist
+ if (!this.manager || !this.contentWindow) {
+ return;
+ }
+ log.debug(`onLocationChange handled: ${aWebProgress.DOMWindow}`);
+ this.AWSendToParent("LOCATION_CHANGED");
+ };
+
+ webProgress.addProgressListener(
+ listener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+ }
+
+ /**
+ * Send event that can be handled by the page
+ * @param {{type: string, data?: any}} action
+ */
+ sendToPage(action) {
+ 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.AWGetExperimentData.bind(this), window, {
+ defineAs: "AWGetExperimentData",
+ });
+
+ Cu.exportFunction(this.AWGetAttributionData.bind(this), window, {
+ defineAs: "AWGetAttributionData",
+ });
+
+ // For local dev, checks for JSON content inside pref browser.aboutwelcome.overrideContent
+ // that is used to override default welcome UI
+ Cu.exportFunction(this.AWGetWelcomeOverrideContent.bind(this), window, {
+ defineAs: "AWGetWelcomeOverrideContent",
+ });
+
+ 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",
+ });
+ }
+
+ /**
+ * Wrap a promise so content can use Promise methods.
+ */
+ wrapPromise(promise) {
+ return new this.contentWindow.Promise((resolve, reject) =>
+ promise.then(resolve, reject)
+ );
+ }
+
+ /**
+ * Send multistage welcome JSON data read from aboutwelcome.overrideConetent pref to page
+ */
+ AWGetWelcomeOverrideContent() {
+ return Cu.cloneInto(
+ multiStageAboutWelcomeContent || {},
+ this.contentWindow
+ );
+ }
+
+ AWSelectTheme(data) {
+ return this.wrapPromise(
+ this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
+ );
+ }
+
+ async getAddonInfo(attrbObj) {
+ let { content, source } = attrbObj;
+ try {
+ if (!content || source !== "addons.mozilla.org") {
+ return null;
+ }
+ // Attribution data can be double encoded
+ while (content.includes("%")) {
+ try {
+ const result = decodeURIComponent(content);
+ if (result === content) {
+ break;
+ }
+ content = result;
+ } catch (e) {
+ break;
+ }
+ }
+ return await this.sendQuery("AWPage:GET_ADDON_FROM_REPOSITORY", content);
+ } catch (e) {
+ Cu.reportError(
+ "Failed to get the latest add-on version for Return to AMO"
+ );
+ return null;
+ }
+ }
+
+ hasAMOAttribution(attributionData) {
+ return (
+ attributionData &&
+ attributionData.campaign === "non-fx-button" &&
+ attributionData.source === "addons.mozilla.org"
+ );
+ }
+
+ async formatAttributionData(attribution) {
+ let result = {};
+ if (this.hasAMOAttribution(attribution)) {
+ let extraProps = await this.getAddonInfo(attribution);
+ if (extraProps) {
+ result = {
+ template: "return_to_amo",
+ extraProps,
+ };
+ }
+ }
+ return result;
+ }
+
+ async getAttributionData() {
+ return Cu.cloneInto(
+ await this.formatAttributionData(
+ await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA")
+ ),
+ this.contentWindow
+ );
+ }
+
+ AWGetAttributionData() {
+ return this.wrapPromise(this.getAttributionData());
+ }
+
+ /**
+ * Send initial data to page including experiment information
+ */
+ AWGetExperimentData() {
+ let experimentData;
+ try {
+ // Note that we specifically don't wait for experiments to be loaded from disk so if
+ // about:welcome loads outside of the "FirstStartup" scenario this will likely not be ready
+ experimentData = ExperimentAPI.getExperiment({
+ featureId: "aboutwelcome",
+ // Telemetry handled in AboutNewTabService.jsm
+ sendExposurePing: false,
+ });
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ if (experimentData?.slug) {
+ log.debug(
+ `Loading about:welcome with experiment: ${experimentData.slug}`
+ );
+ } else {
+ log.debug("Loading about:welcome without experiment");
+ }
+ return Cu.cloneInto(experimentData || {}, this.contentWindow);
+ }
+
+ 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,
+ page: "about:welcome",
+ },
+ });
+ }
+
+ /**
+ * 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"));
+ }
+
+ /**
+ * @param {{type: string, detail?: any}} event
+ * @override
+ */
+ handleEvent(event) {
+ log.debug(`Received page event ${event.type}`);
+ }
+}
diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm
new file mode 100644
index 0000000000..37e32ac1d7
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm
@@ -0,0 +1,310 @@
+/* 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 = ["AboutWelcomeParent"];
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
+ AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
+ FxAccounts: "resource://gre/modules/FxAccounts.jsm",
+ MigrationUtils: "resource:///modules/MigrationUtils.jsm",
+ OS: "resource://gre/modules/osfile.jsm",
+ SpecialMessageActions:
+ "resource://messaging-system/lib/SpecialMessageActions.jsm",
+ AboutWelcomeTelemetry:
+ "resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm",
+ AttributionCode: "resource:///modules/AttributionCode.jsm",
+ PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
+ Region: "resource://gre/modules/Region.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ const { Logger } = ChromeUtils.import(
+ "resource://messaging-system/lib/Logger.jsm"
+ );
+ return new Logger("AboutWelcomeParent");
+});
+
+XPCOMUtils.defineLazyGetter(
+ this,
+ "Telemetry",
+ () => new AboutWelcomeTelemetry()
+);
+
+const DID_SEE_ABOUT_WELCOME_PREF = "trailhead.firstrun.didSeeAboutWelcome";
+const AWTerminate = {
+ UNKNOWN: "unknown",
+ WINDOW_CLOSED: "welcome-window-closed",
+ TAB_CLOSED: "welcome-tab-closed",
+ APP_SHUT_DOWN: "app-shut-down",
+ ADDRESS_BAR_NAVIGATED: "address-bar-navigated",
+};
+const LIGHT_WEIGHT_THEMES = {
+ DARK: "firefox-compact-dark@mozilla.org",
+ LIGHT: "firefox-compact-light@mozilla.org",
+ AUTOMATIC: "default-theme@mozilla.org",
+ ALPENGLOW: "firefox-alpenglow@mozilla.org",
+};
+
+async function getImportableSites() {
+ const sites = [];
+
+ // Just handle these chromium-based browsers for now
+ for (const browserId of ["chrome", "chromium-edge", "chromium"]) {
+ // Skip if there's no profile data.
+ const migrator = await MigrationUtils.getMigrator(browserId);
+ if (!migrator) {
+ continue;
+ }
+
+ // Check each profile for top sites
+ const dataPath = await migrator.wrappedJSObject._getChromeUserDataPathIfExists();
+ for (const profile of await migrator.getSourceProfiles()) {
+ let path = OS.Path.join(dataPath, profile.id, "Top Sites");
+ // Skip if top sites data is missing
+ if (!(await OS.File.exists(path))) {
+ Cu.reportError(`Missing file at ${path}`);
+ continue;
+ }
+
+ try {
+ for (const row of await MigrationUtils.getRowsFromDBWithoutLocks(
+ path,
+ `Importable ${browserId} top sites`,
+ `SELECT url
+ FROM top_sites
+ ORDER BY url_rank`
+ )) {
+ sites.push(row.getString(0));
+ }
+ } catch (ex) {
+ Cu.reportError(
+ `Failed to get importable top sites from ${browserId} ${ex}`
+ );
+ }
+ }
+ }
+ return sites;
+}
+
+class AboutWelcomeObserver {
+ constructor() {
+ Services.obs.addObserver(this, "quit-application");
+
+ this.win = Services.focus.activeWindow;
+ if (!this.win) {
+ return;
+ }
+
+ this.terminateReason = AWTerminate.UNKNOWN;
+
+ this.onWindowClose = () => {
+ this.terminateReason = AWTerminate.WINDOW_CLOSED;
+ };
+
+ this.onTabClose = () => {
+ this.terminateReason = AWTerminate.TAB_CLOSED;
+ };
+
+ this.win.addEventListener("TabClose", this.onTabClose, { once: true });
+ this.win.addEventListener("unload", this.onWindowClose, { once: true });
+ }
+
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "quit-application":
+ this.terminateReason = AWTerminate.APP_SHUT_DOWN;
+ break;
+ }
+ }
+
+ // Added for testing
+ get AWTerminate() {
+ return AWTerminate;
+ }
+
+ stop() {
+ log.debug(`Terminate reason is ${this.terminateReason}`);
+ Services.obs.removeObserver(this, "quit-application");
+ if (!this.win) {
+ return;
+ }
+ this.win.removeEventListener("TabClose", this.onTabClose);
+ this.win.removeEventListener("unload", this.onWindowClose);
+ this.win = null;
+ }
+}
+
+class RegionHomeObserver {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case Region.REGION_TOPIC:
+ if (aData === Region.REGION_UPDATED) {
+ Services.obs.removeObserver(this, Region.REGION_TOPIC);
+ this.regionHomeDeferred.resolve(Region.home);
+ this.regionHomeDeferred = null;
+ }
+ break;
+ }
+ }
+
+ promiseRegionHome() {
+ // Add observer and create promise that should be resolved
+ // with region or rejected inside didDestroy if user exits
+ // before region is available
+ if (!this.regionHomeDeferred) {
+ Services.obs.addObserver(this, Region.REGION_TOPIC);
+ this.regionHomeDeferred = PromiseUtils.defer();
+ }
+ return this.regionHomeDeferred.promise;
+ }
+
+ stop() {
+ if (this.regionHomeDeferred) {
+ Services.obs.removeObserver(this, Region.REGION_TOPIC);
+ // Reject unresolved deferred promise on exit
+ this.regionHomeDeferred.reject(
+ new Error("Unresolved region home promise")
+ );
+ this.regionHomeDeferred = null;
+ }
+ }
+}
+
+class AboutWelcomeParent extends JSWindowActorParent {
+ constructor() {
+ super();
+ this.AboutWelcomeObserver = new AboutWelcomeObserver(this);
+ }
+
+ didDestroy() {
+ if (this.AboutWelcomeObserver) {
+ this.AboutWelcomeObserver.stop();
+ }
+ this.RegionHomeObserver?.stop();
+
+ Telemetry.sendTelemetry({
+ event: "SESSION_END",
+ event_context: {
+ reason: this.AboutWelcomeObserver.terminateReason,
+ page: "about:welcome",
+ },
+ message_id: this.AWMessageId,
+ id: "ABOUT_WELCOME",
+ });
+ }
+
+ /**
+ * Handle messages from AboutWelcomeChild.jsm
+ *
+ * @param {string} type
+ * @param {any=} data
+ * @param {Browser} browser
+ * @param {Window} window
+ */
+ async onContentMessage(type, data, browser, window) {
+ log.debug(`Received content event: ${type}`);
+ switch (type) {
+ case "AWPage:SET_WELCOME_MESSAGE_SEEN":
+ this.AWMessageId = data;
+ try {
+ Services.prefs.setBoolPref(DID_SEE_ABOUT_WELCOME_PREF, true);
+ } catch (e) {
+ log.debug(`Fails to set ${DID_SEE_ABOUT_WELCOME_PREF}.`);
+ }
+ break;
+ case "AWPage:SPECIAL_ACTION":
+ SpecialMessageActions.handleAction(data, browser);
+ break;
+ case "AWPage:FXA_METRICS_FLOW_URI":
+ return FxAccounts.config.promiseMetricsFlowURI("aboutwelcome");
+ case "AWPage:GET_ATTRIBUTION_DATA":
+ return AttributionCode.getAttrDataAsync();
+ case "AWPage:IMPORTABLE_SITES":
+ return getImportableSites();
+ case "AWPage:TELEMETRY_EVENT":
+ Telemetry.sendTelemetry(data);
+ break;
+ case "AWPage:LOCATION_CHANGED":
+ this.AboutWelcomeObserver.terminateReason =
+ AWTerminate.ADDRESS_BAR_NAVIGATED;
+ break;
+ case "AWPage:GET_ADDON_FROM_REPOSITORY":
+ const [addonInfo] = await AddonRepository.getAddonsByIDs([data]);
+ if (addonInfo.sourceURI.scheme !== "https") {
+ return null;
+ }
+ return {
+ name: addonInfo.name,
+ url: addonInfo.sourceURI.spec,
+ iconURL: addonInfo.icons["64"] || addonInfo.icons["32"],
+ };
+ case "AWPage:SELECT_THEME":
+ return AddonManager.getAddonByID(
+ LIGHT_WEIGHT_THEMES[data]
+ ).then(addon => addon.enable());
+ case "AWPage:GET_SELECTED_THEME":
+ let themes = await AddonManager.getAddonsByTypes(["theme"]);
+ let activeTheme = themes.find(addon => addon.isActive);
+
+ // convert this to the short form name that the front end code
+ // expects
+ let themeShortName = Object.keys(LIGHT_WEIGHT_THEMES).find(
+ key => LIGHT_WEIGHT_THEMES[key] === activeTheme?.id
+ );
+ return themeShortName?.toLowerCase();
+ case "AWPage:GET_REGION":
+ if (Region.home !== null) {
+ return Region.home;
+ }
+ if (!this.RegionHomeObserver) {
+ this.RegionHomeObserver = new RegionHomeObserver(this);
+ }
+ return this.RegionHomeObserver.promiseRegionHome();
+ case "AWPage:WAIT_FOR_MIGRATION_CLOSE":
+ return new Promise(resolve =>
+ Services.ww.registerNotification(function observer(subject, topic) {
+ if (
+ topic === "domwindowclosed" &&
+ subject.document.documentURI ===
+ "chrome://browser/content/migration/migration.xhtml"
+ ) {
+ Services.ww.unregisterNotification(observer);
+ resolve();
+ }
+ })
+ );
+ default:
+ log.debug(`Unexpected event ${type} was not handled.`);
+ }
+
+ return undefined;
+ }
+
+ /**
+ * @param {{name: string, data?: any}} message
+ * @override
+ */
+ receiveMessage(message) {
+ const { name, data } = message;
+ let browser;
+ let window;
+
+ if (this.manager.rootFrameLoader) {
+ browser = this.manager.rootFrameLoader.ownerElement;
+ window = browser.ownerGlobal;
+ return this.onContentMessage(name, data, browser, window);
+ }
+
+ log.warn(`Not handling ${name} because the browser doesn't exist.`);
+ return null;
+ }
+}
diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
new file mode 100644
index 0000000000..deed7d749f
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
@@ -0,0 +1,1501 @@
+/*!
+ *
+ * NOTE: This file is generated by webpack from aboutwelcome.jsx
+ * using the npm bundle task.
+ *
+ */
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
+/* harmony import */ var _components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
+/* harmony import */ var _components_SimpleAboutWelcome__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
+/* harmony import */ var _components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(12);
+/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(6);
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+/* 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/. */
+
+
+
+
+
+
+
+class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ metricsFlowUri: null
+ };
+ this.fetchFxAFlowUri = this.fetchFxAFlowUri.bind(this);
+ this.handleStartBtnClick = this.handleStartBtnClick.bind(this);
+ }
+
+ async fetchFxAFlowUri() {
+ this.setState({
+ metricsFlowUri: await window.AWGetFxAMetricsFlowURI()
+ });
+ }
+
+ componentDidMount() {
+ this.fetchFxAFlowUri(); // Record impression with performance data after allowing the page to load
+
+ const recordImpression = domState => {
+ const {
+ domComplete,
+ domInteractive
+ } = performance.getEntriesByType("navigation").pop();
+ window.AWSendEventTelemetry({
+ event: "IMPRESSION",
+ event_context: {
+ domComplete,
+ domInteractive,
+ mountStart: performance.getEntriesByName("mount").pop().startTime,
+ domState,
+ source: this.props.UTMTerm,
+ page: "about:welcome"
+ },
+ message_id: this.props.messageId
+ });
+ };
+
+ if (document.readyState === "complete") {
+ // Page might have already triggered a load event because it waited for async data,
+ // e.g., attribution, so the dom load timing could be of a empty content
+ // with domState in telemetry captured as 'complete'
+ recordImpression(document.readyState);
+ } else {
+ window.addEventListener("load", () => recordImpression("load"), {
+ once: true
+ });
+ } // Captures user has seen about:welcome by setting
+ // firstrun.didSeeAboutWelcome pref to true and capturing welcome UI unique messageId
+
+
+ window.AWSendToParent("SET_WELCOME_MESSAGE_SEEN", this.props.messageId);
+ }
+
+ handleStartBtnClick() {
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__["AboutWelcomeUtils"].handleUserAction(this.props.startButton.action);
+ const ping = {
+ event: "CLICK_BUTTON",
+ event_context: {
+ source: this.props.startButton.message_id,
+ page: "about:welcome"
+ },
+ message_id: this.props.messageId,
+ id: "ABOUT_WELCOME"
+ };
+ window.AWSendEventTelemetry(ping);
+ }
+
+ render() {
+ const {
+ props
+ } = this;
+
+ if (props.template === "simplified") {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_SimpleAboutWelcome__WEBPACK_IMPORTED_MODULE_3__["SimpleAboutWelcome"], {
+ metricsFlowUri: this.state.metricsFlowUri,
+ message_id: props.messageId,
+ utm_term: props.UTMTerm,
+ title: props.title,
+ subtitle: props.subtitle,
+ cards: props.cards,
+ startButton: props.startButton,
+ handleStartBtnClick: this.handleStartBtnClick
+ });
+ } else if (props.template === "return_to_amo") {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_4__["ReturnToAMO"], {
+ message_id: props.messageId,
+ name: props.name,
+ url: props.url,
+ iconURL: props.iconURL
+ });
+ }
+
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__["MultiStageAboutWelcome"], {
+ screens: props.screens,
+ metricsFlowUri: this.state.metricsFlowUri,
+ message_id: props.messageId,
+ utm_term: props.UTMTerm
+ });
+ }
+
+}
+
+AboutWelcome.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__["DEFAULT_WELCOME_CONTENT"]; // Computes messageId and UTMTerm info used in telemetry
+
+function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
+ let messageId = welcomeContent.template === "return_to_amo" ? "RTAMO_DEFAULT_WELCOME" : "DEFAULT_ABOUTWELCOME";
+ let UTMTerm = "default";
+
+ if (welcomeContent.id) {
+ messageId = welcomeContent.id.toUpperCase();
+ }
+
+ if (experimentId && branchId) {
+ UTMTerm = `${experimentId}-${branchId}`.toLowerCase();
+ }
+
+ return {
+ messageId,
+ UTMTerm
+ };
+}
+
+async function retrieveRenderContent() {
+ var _aboutWelcomeProps;
+
+ // Check for override content in pref browser.aboutwelcome.overrideContent
+ let aboutWelcomeProps = await window.AWGetWelcomeOverrideContent();
+
+ if ((_aboutWelcomeProps = aboutWelcomeProps) === null || _aboutWelcomeProps === void 0 ? void 0 : _aboutWelcomeProps.template) {
+ let {
+ messageId,
+ UTMTerm
+ } = ComputeTelemetryInfo(aboutWelcomeProps);
+ return {
+ aboutWelcomeProps,
+ messageId,
+ UTMTerm
+ };
+ } // Check for experiment and retrieve content
+
+
+ const {
+ slug,
+ branch
+ } = await window.AWGetExperimentData();
+ aboutWelcomeProps = (branch === null || branch === void 0 ? void 0 : branch.feature) ? branch.feature.value : {}; // Check if there is any attribution data, this could take a while to await in series
+ // especially when there is an add-on that requires remote lookup
+ // Moving RTAMO as part of another screen of multistage is one option to fix the delay
+ // as it will allow the initial page to be fast while we fetch attribution data in parallel for a later screen.
+
+ const attribution = await window.AWGetAttributionData();
+
+ if (attribution === null || attribution === void 0 ? void 0 : attribution.template) {
+ var _aboutWelcomeProps2;
+
+ aboutWelcomeProps = { ...aboutWelcomeProps,
+ // If part of an experiment, render experiment template
+ template: ((_aboutWelcomeProps2 = aboutWelcomeProps) === null || _aboutWelcomeProps2 === void 0 ? void 0 : _aboutWelcomeProps2.template) ? aboutWelcomeProps.template : attribution.template,
+ ...attribution.extraProps
+ };
+ }
+
+ let {
+ messageId,
+ UTMTerm
+ } = ComputeTelemetryInfo(aboutWelcomeProps, slug, branch && branch.slug);
+ return {
+ aboutWelcomeProps,
+ messageId,
+ UTMTerm
+ };
+}
+
+async function mount() {
+ let {
+ aboutWelcomeProps,
+ messageId,
+ UTMTerm
+ } = await retrieveRenderContent();
+ react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(AboutWelcome, _extends({
+ messageId: messageId,
+ UTMTerm: UTMTerm
+ }, aboutWelcomeProps)), document.getElementById("root"));
+}
+
+performance.mark("mount");
+mount();
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports) {
+
+module.exports = React;
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports) {
+
+module.exports = ReactDOM;
+
+/***/ }),
+/* 3 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiStageAboutWelcome", function() { return MultiStageAboutWelcome; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WelcomeScreen", function() { return WelcomeScreen; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+/* harmony import */ var _Zap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
+/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
+/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7);
+/* 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/. */
+
+
+
+
+
+const MultiStageAboutWelcome = props => {
+ const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0);
+ Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
+ // Send impression ping when respective screen first renders
+ props.screens.forEach(screen => {
+ if (index === screen.order) {
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_${screen.id}`);
+ }
+ }); // Remember that a new screen has loaded for browser navigation
+
+ if (index > window.history.state) {
+ window.history.pushState(index, "");
+ }
+ }, [index]);
+ Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
+ // Switch to the screen tracked in state (null for initial state)
+ const handler = ({
+ state
+ }) => setScreenIndex(Number(state)); // Handle page load, e.g., going back to about:welcome from about:home
+
+
+ handler(window.history); // Watch for browser back/forward button navigation events
+
+ window.addEventListener("popstate", handler);
+ return () => window.removeEventListener("popstate", handler);
+ }, []);
+ const [flowParams, setFlowParams] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
+ const {
+ metricsFlowUri
+ } = props;
+ Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
+ (async () => {
+ if (metricsFlowUri) {
+ setFlowParams((await _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].fetchFlowParams(metricsFlowUri)));
+ }
+ })();
+ }, [metricsFlowUri]); // Transition to next screen, opening about:home on last screen button CTA
+
+ const handleTransition = index < props.screens.length - 1 ? () => setScreenIndex(prevState => prevState + 1) : () => _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].handleUserAction({
+ type: "OPEN_ABOUT_PAGE",
+ data: {
+ args: "home",
+ where: "current"
+ }
+ }); // 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.AWGetRegion()));
+ })();
+ }, []); // Get the active theme so the rendering code can make it selected
+ // by default.
+
+ const [activeTheme, setActiveTheme] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
+ const [initialTheme, setInitialTheme] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
+ Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
+ (async () => {
+ let theme = await window.AWGetSelectedTheme();
+ setInitialTheme(theme);
+ setActiveTheme(theme);
+ })();
+ }, []);
+ const useImportable = props.message_id.includes("IMPORTABLE"); // Track whether we have already sent the importable sites impression telemetry
+
+ const importTelemetrySent = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(false);
+ 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;
+
+ if (!importTelemetrySent.current) {
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_SITES`, {
+ display: showImportable ? "importable" : "static",
+ importable: importable.length
+ });
+ importTelemetrySent.current = true;
+ }
+
+ setTopSites(showImportable ? {
+ data: importable,
+ showImportable
+ } : {
+ data: DEFAULT_SITES,
+ showImportable
+ });
+ })();
+ }, [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 onboardingContainer`
+ }, props.screens.map(screen => {
+ return index === screen.order ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(WelcomeScreen, {
+ key: screen.id,
+ id: screen.id,
+ totalNumberOfScreens: props.screens.length,
+ order: screen.order,
+ content: screen.content,
+ navigate: handleTransition,
+ topSites: topSites,
+ messageId: `${props.message_id}_${screen.id}`,
+ UTMTerm: props.utm_term,
+ flowParams: flowParams,
+ activeTheme: activeTheme,
+ initialTheme: initialTheme,
+ setActiveTheme: setActiveTheme
+ }) : null;
+ })));
+};
+class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.handleAction = this.handleAction.bind(this);
+ }
+
+ handleOpenURL(action, flowParams, UTMTerm) {
+ let {
+ type,
+ data
+ } = action;
+
+ if (type === "SHOW_FIREFOX_ACCOUNTS") {
+ let params = { ..._asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__["BASE_PARAMS"],
+ utm_term: `aboutwelcome-${UTMTerm}-screen`
+ };
+
+ if (action.addFlowParams && flowParams) {
+ params = { ...params,
+ ...flowParams
+ };
+ }
+
+ data = { ...data,
+ extraParams: params
+ };
+ } else if (type === "OPEN_URL") {
+ let url = new URL(data.args);
+ Object(_asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__["addUtmParams"])(url, `aboutwelcome-${UTMTerm}-screen`);
+
+ if (action.addFlowParams && flowParams) {
+ url.searchParams.append("device_id", flowParams.deviceId);
+ url.searchParams.append("flow_id", flowParams.flowId);
+ url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
+ }
+
+ data = { ...data,
+ args: url.toString()
+ };
+ }
+
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].handleUserAction({
+ type,
+ data
+ });
+ }
+
+ async handleAction(event) {
+ let {
+ props
+ } = this;
+ let targetContent = props.content[event.currentTarget.value] || props.content.tiles;
+
+ if (!(targetContent && targetContent.action)) {
+ return;
+ } // Send telemetry before waiting on actions
+
+
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendActionTelemetry(props.messageId, event.currentTarget.value);
+ let {
+ action
+ } = targetContent;
+
+ if (["OPEN_URL", "SHOW_FIREFOX_ACCOUNTS"].includes(action.type)) {
+ this.handleOpenURL(action, props.flowParams, props.UTMTerm);
+ } else if (action.type) {
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].handleUserAction(action); // Wait until migration closes to complete the action
+
+ if (action.type === "SHOW_MIGRATION_WIZARD") {
+ await window.AWWaitForMigrationClose();
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].sendActionTelemetry(props.messageId, "migrate_close");
+ }
+ } // A special tiles.action.theme value indicates we should use the event's value vs provided value.
+
+
+ if (action.theme) {
+ let themeToUse = action.theme === "<event>" ? event.currentTarget.value : this.props.initialTheme || action.theme;
+ this.props.setActiveTheme(themeToUse);
+ window.AWSelectTheme(themeToUse);
+ }
+
+ if (action.navigate) {
+ props.navigate();
+ }
+ }
+
+ renderSecondaryCTA(className) {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: className ? `secondary-cta ${className}` : `secondary-cta`
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: this.props.content.secondary_button.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", null)), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: this.props.content.secondary_button.label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ className: "secondary",
+ value: "secondary_button",
+ onClick: this.handleAction
+ })));
+ }
+
+ renderTiles() {
+ switch (this.props.content.tiles.type) {
+ case "topsites":
+ 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",
+ name: "topsites-section",
+ id: "topsites-section",
+ "aria-labelledby": "helptext",
+ role: "region"
+ }, this.props.topSites.data.slice(0, 5).map(({
+ icon,
+ label,
+ title
+ }) => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "site",
+ key: icon + label,
+ "aria-label": title ? title : label,
+ role: "img"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "icon",
+ style: icon ? {
+ backgroundColor: "transparent",
+ backgroundImage: `url(${icon})`
+ } : {}
+ }, icon ? "" : label && label[0].toUpperCase()), this.props.content.tiles.showTitles && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "host"
+ }, title || label))))) : null;
+
+ case "theme":
+ return this.props.content.tiles.data ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "tiles-theme-container"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("fieldset", {
+ className: "tiles-theme-section"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: this.props.content.subtitle
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("legend", {
+ className: "sr-only"
+ })), this.props.content.tiles.data.map(({
+ theme,
+ label,
+ tooltip,
+ description
+ }) => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ key: theme + label,
+ text: typeof tooltip === "object" ? tooltip : {}
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("label", {
+ className: `theme${theme === this.props.activeTheme ? " selected" : ""}`,
+ title: theme + label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: typeof description === "object" ? description : {}
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("input", {
+ type: "radio",
+ value: theme,
+ name: "theme",
+ checked: theme === this.props.activeTheme,
+ className: "sr-only input",
+ onClick: this.handleAction,
+ "data-l10n-attrs": "aria-description"
+ })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: `icon ${theme}`
+ }), label && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "text"
+ })))))))) : null;
+
+ case "video":
+ return this.props.content.tiles.source ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: `tiles-media-section ${this.props.content.tiles.media_type}`
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "fade"
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("video", {
+ className: "media",
+ autoPlay: "true",
+ loop: "true",
+ muted: "true",
+ src: _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].hasDarkMode() ? this.props.content.tiles.source.dark : this.props.content.tiles.source.default
+ })) : null;
+
+ case "image":
+ return this.props.content.tiles.source ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: `${this.props.content.tiles.media_type}`
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
+ src: _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].hasDarkMode() && this.props.content.tiles.source.dark ? this.props.content.tiles.source.dark : this.props.content.tiles.source.default,
+ role: "presentation",
+ alt: ""
+ })) : null;
+ }
+
+ return null;
+ }
+
+ renderStepsIndicator() {
+ let steps = [];
+
+ for (let i = 0; i < this.props.totalNumberOfScreens; i++) {
+ let className = i === this.props.order ? "current" : "";
+ steps.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ key: i,
+ className: `indicator ${className}`
+ }));
+ }
+
+ return steps;
+ }
+
+ renderHelpText() {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: this.props.content.help_text.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
+ id: "helptext",
+ className: `helptext ${this.props.content.help_text.position}`
+ }));
+ }
+
+ render() {
+ const {
+ content,
+ topSites
+ } = this.props;
+ const hasSecondaryTopCTA = content.secondary_button && content.secondary_button.position === "top";
+ const showImportableSitesDisclaimer = content.tiles && content.tiles.type === "topsites" && topSites && topSites.showImportable;
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", {
+ className: `screen ${this.props.id}`
+ }, hasSecondaryTopCTA ? this.renderSecondaryCTA("top") : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: `brand-logo ${hasSecondaryTopCTA ? "cta-top" : ""}`
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "welcome-text"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Zap__WEBPACK_IMPORTED_MODULE_2__["Zap"], {
+ hasZap: content.zap,
+ text: content.title
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: content.subtitle
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", null))), content.tiles ? this.renderTiles() : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: content.primary_button ? content.primary_button.label : null
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ className: "primary",
+ value: "primary_button",
+ onClick: this.handleAction
+ }))), content.secondary_button && content.secondary_button.position !== "top" ? this.renderSecondaryCTA() : null, content.help_text && content.help_text.position === "default" ? this.renderHelpText() : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("nav", {
+ className: content.help_text && content.help_text.position === "footer" || showImportableSitesDisclaimer ? "steps has-helptext" : "steps",
+ "data-l10n-id": "onboarding-welcome-steps-indicator",
+ "data-l10n-args": `{"current": ${parseInt(this.props.order, 10) + 1}, "total": ${this.props.totalNumberOfScreens}}`
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("br", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", null), this.renderStepsIndicator()), content.help_text && content.help_text.position === "footer" || showImportableSitesDisclaimer ? this.renderHelpText() : null);
+ }
+
+}
+
+/***/ }),
+/* 4 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Localized", function() { return Localized; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* 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/. */
+
+const MS_STRING_PROP = "string_id";
+/**
+ * Based on the .text prop, localizes an inner element if a string_id
+ * is provided, OR renders plain text, OR hides it if nothing is provided.
+ *
+ * Examples:
+ *
+ * Localized text
+ * ftl:
+ * title = Welcome
+ * jsx:
+ * <Localized text={{string_id: "title"}}><h1 /></Localized>
+ * output:
+ * <h1 data-l10n-id="title">Welcome</h1>
+ *
+ * Unlocalized text
+ * jsx:
+ * <Localized text="Welcome"><h1 /></Localized>
+ * output:
+ * <h1>Welcome</h1>
+ */
+
+const Localized = ({
+ text,
+ children
+}) => {
+ if (!text) {
+ return null;
+ }
+
+ let props = children ? children.props : {};
+ let textNode;
+
+ if (typeof text === "object" && text[MS_STRING_PROP]) {
+ props = { ...props
+ };
+ props["data-l10n-id"] = text[MS_STRING_PROP];
+ } else if (typeof text === "string") {
+ textNode = text;
+ }
+
+ if (!children) {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", props, textNode);
+ } else if (textNode) {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.cloneElement(children, props, textNode);
+ }
+
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.cloneElement(children, props);
+};
+
+/***/ }),
+/* 5 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Zap", function() { return Zap; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+/* 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/. */
+
+
+const MS_STRING_PROP = "string_id";
+const ZAP_SIZE_THRESHOLD = 160;
+
+function calculateZapLength() {
+ let span = document.querySelector(".zap");
+
+ if (!span) {
+ return;
+ }
+
+ let rect = span.getBoundingClientRect();
+
+ if (rect && rect.width > ZAP_SIZE_THRESHOLD) {
+ span.classList.add("long");
+ } else {
+ span.classList.add("short");
+ }
+}
+
+const Zap = props => {
+ Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
+ requestAnimationFrame(() => calculateZapLength());
+ });
+
+ if (!props.text) {
+ return null;
+ }
+
+ if (props.hasZap) {
+ if (typeof props.text === "object" && props.text[MS_STRING_PROP]) {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: props.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", {
+ className: "welcomeZap"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
+ "data-l10n-name": "zap",
+ className: "zap"
+ })));
+ } else if (typeof props.text === "string") {
+ // Parse string to zap style last word of the props.text
+ let titleArray = props.text.split(" ");
+ let lastWord = `${titleArray.pop()}`;
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", {
+ className: "welcomeZap"
+ }, titleArray.join(" ").concat(" "), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
+ className: "zap"
+ }, lastWord));
+ }
+ } else {
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: props.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", null));
+ }
+
+ return null;
+};
+
+/***/ }),
+/* 6 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AboutWelcomeUtils", function() { return AboutWelcomeUtils; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_RTAMO_CONTENT", function() { return DEFAULT_RTAMO_CONTENT; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_WELCOME_CONTENT", function() { return DEFAULT_WELCOME_CONTENT; });
+/* 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/. */
+const AboutWelcomeUtils = {
+ handleUserAction(action) {
+ window.AWSendToParent("SPECIAL_ACTION", action);
+ },
+
+ sendImpressionTelemetry(messageId, context) {
+ window.AWSendEventTelemetry({
+ event: "IMPRESSION",
+ event_context: context,
+ message_id: messageId
+ });
+ },
+
+ sendActionTelemetry(messageId, elementId) {
+ const ping = {
+ event: "CLICK_BUTTON",
+ event_context: {
+ source: elementId,
+ page: "about:welcome"
+ },
+ message_id: messageId
+ };
+ window.AWSendEventTelemetry(ping);
+ },
+
+ async fetchFlowParams(metricsFlowUri) {
+ let flowParams;
+
+ try {
+ const response = await fetch(metricsFlowUri, {
+ credentials: "omit"
+ });
+
+ if (response.status === 200) {
+ const {
+ deviceId,
+ flowId,
+ flowBeginTime
+ } = await response.json();
+ flowParams = {
+ deviceId,
+ flowId,
+ flowBeginTime
+ };
+ } else {
+ console.error("Non-200 response", response); // eslint-disable-line no-console
+ }
+ } catch (e) {
+ flowParams = null;
+ }
+
+ return flowParams;
+ },
+
+ sendEvent(type, detail) {
+ document.dispatchEvent(new CustomEvent(`AWPage:${type}`, {
+ bubbles: true,
+ detail
+ }));
+ },
+
+ hasDarkMode() {
+ return document.body.hasAttribute("lwt-newtab-brighttext");
+ }
+
+};
+const DEFAULT_RTAMO_CONTENT = {
+ template: "return_to_amo",
+ content: {
+ header: {
+ string_id: "onboarding-welcome-header"
+ },
+ subtitle: {
+ string_id: "return-to-amo-subtitle"
+ },
+ text: {
+ string_id: "return-to-amo-addon-title"
+ },
+ primary_button: {
+ label: {
+ string_id: "return-to-amo-add-extension-label"
+ },
+ action: {
+ type: "INSTALL_ADDON_FROM_URL",
+ data: {
+ url: null,
+ telemetrySource: "rtamo"
+ }
+ }
+ },
+ startButton: {
+ label: {
+ string_id: "onboarding-not-now-button-label"
+ },
+ message_id: "RTAMO_START_BROWSING_BUTTON",
+ action: {
+ type: "OPEN_AWESOME_BAR"
+ }
+ }
+ }
+};
+const DEFAULT_WELCOME_CONTENT = {
+ template: "multistage",
+ screens: [{
+ id: "AW_GET_STARTED",
+ order: 0,
+ content: {
+ zap: true,
+ title: {
+ string_id: "onboarding-multistage-welcome-header"
+ },
+ subtitle: {
+ string_id: "onboarding-multistage-welcome-subtitle"
+ },
+ primary_button: {
+ label: {
+ string_id: "onboarding-multistage-welcome-primary-button-label"
+ },
+ action: {
+ navigate: true
+ }
+ },
+ secondary_button: {
+ text: {
+ string_id: "onboarding-multistage-welcome-secondary-button-text"
+ },
+ label: {
+ string_id: "onboarding-multistage-welcome-secondary-button-label"
+ },
+ position: "top",
+ action: {
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ addFlowParams: true,
+ data: {
+ entrypoint: "activity-stream-firstrun"
+ }
+ }
+ }
+ }
+ }, {
+ id: "AW_IMPORT_SETTINGS",
+ order: 1,
+ content: {
+ zap: true,
+ help_text: {
+ text: {
+ string_id: "onboarding-import-sites-disclaimer"
+ }
+ },
+ title: {
+ string_id: "onboarding-multistage-import-header"
+ },
+ subtitle: {
+ string_id: "onboarding-multistage-import-subtitle"
+ },
+ tiles: {
+ type: "topsites",
+ showTitles: true
+ },
+ primary_button: {
+ label: {
+ string_id: "onboarding-multistage-import-primary-button-label"
+ },
+ action: {
+ type: "SHOW_MIGRATION_WIZARD",
+ navigate: true
+ }
+ },
+ secondary_button: {
+ label: {
+ string_id: "onboarding-multistage-import-secondary-button-label"
+ },
+ action: {
+ navigate: true
+ }
+ }
+ }
+ }, {
+ id: "AW_CHOOSE_THEME",
+ order: 2,
+ content: {
+ zap: true,
+ title: {
+ string_id: "onboarding-multistage-theme-header"
+ },
+ subtitle: {
+ string_id: "onboarding-multistage-theme-subtitle"
+ },
+ tiles: {
+ type: "theme",
+ action: {
+ theme: "<event>"
+ },
+ data: [{
+ theme: "automatic",
+ label: {
+ string_id: "onboarding-multistage-theme-label-automatic"
+ },
+ tooltip: {
+ string_id: "onboarding-multistage-theme-tooltip-automatic-2"
+ },
+ description: {
+ string_id: "onboarding-multistage-theme-description-automatic-2"
+ }
+ }, {
+ theme: "light",
+ label: {
+ string_id: "onboarding-multistage-theme-label-light"
+ },
+ tooltip: {
+ string_id: "onboarding-multistage-theme-tooltip-light-2"
+ },
+ description: {
+ string_id: "onboarding-multistage-theme-description-light"
+ }
+ }, {
+ theme: "dark",
+ label: {
+ string_id: "onboarding-multistage-theme-label-dark"
+ },
+ tooltip: {
+ string_id: "onboarding-multistage-theme-tooltip-dark-2"
+ },
+ description: {
+ string_id: "onboarding-multistage-theme-description-dark"
+ }
+ }, {
+ theme: "alpenglow",
+ label: {
+ string_id: "onboarding-multistage-theme-label-alpenglow"
+ },
+ tooltip: {
+ string_id: "onboarding-multistage-theme-tooltip-alpenglow-2"
+ },
+ description: {
+ string_id: "onboarding-multistage-theme-description-alpenglow"
+ }
+ }]
+ },
+ primary_button: {
+ label: {
+ string_id: "onboarding-multistage-theme-primary-button-label"
+ },
+ action: {
+ navigate: true
+ }
+ },
+ secondary_button: {
+ label: {
+ string_id: "onboarding-multistage-theme-secondary-button-label"
+ },
+ action: {
+ theme: "automatic",
+ navigate: true
+ }
+ }
+ }
+ }]
+};
+
+/***/ }),
+/* 7 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addUtmParams", function() { return addUtmParams; });
+/* 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/. */
+
+/**
+ * BASE_PARAMS keys/values can be modified from outside this file
+ */
+const BASE_PARAMS = {
+ utm_source: "activity-stream",
+ utm_campaign: "firstrun",
+ utm_medium: "referral"
+};
+/**
+ * Takes in a url as a string or URL object and returns a URL object with the
+ * utm_* parameters added to it. If a URL object is passed in, the paraemeters
+ * are added to it (the return value can be ignored in that case as it's the
+ * same object).
+ */
+
+function addUtmParams(url, utmTerm) {
+ let returnUrl = url;
+
+ if (typeof returnUrl === "string") {
+ returnUrl = new URL(url);
+ }
+
+ Object.keys(BASE_PARAMS).forEach(key => {
+ returnUrl.searchParams.append(key, BASE_PARAMS[key]);
+ });
+ returnUrl.searchParams.append("utm_term", utmTerm);
+ return returnUrl;
+}
+
+/***/ }),
+/* 8 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleAboutWelcome", function() { return SimpleAboutWelcome; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _HeroText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
+/* harmony import */ var _FxCards__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
+/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
+/* 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/. */
+
+
+
+
+class SimpleAboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ render() {
+ const {
+ props
+ } = this;
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "outer-wrapper welcomeContainer"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "welcomeContainerInner"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_HeroText__WEBPACK_IMPORTED_MODULE_1__["HeroText"], {
+ title: props.title,
+ subtitle: props.subtitle
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_FxCards__WEBPACK_IMPORTED_MODULE_2__["FxCards"], {
+ cards: props.cards,
+ metricsFlowUri: this.props.metricsFlowUri,
+ sendTelemetry: window.AWSendEventTelemetry,
+ utm_term: this.props.UTMTerm
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_3__["Localized"], {
+ text: props.startButton.label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ className: "start-button",
+ onClick: this.props.handleStartBtnClick
+ })))));
+ }
+
+}
+
+/***/ }),
+/* 9 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HeroText", function() { return HeroText; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+/* 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/. */
+
+
+const HeroText = props => {
+ 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(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: props.title
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", {
+ className: "welcome-title"
+ })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: props.subtitle
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
+ className: "welcome-subtitle"
+ })));
+};
+
+/***/ }),
+/* 10 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FxCards", function() { return FxCards; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
+/* harmony import */ var _asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11);
+/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+/* 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/. */
+
+
+
+
+class FxCards extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ flowParams: null
+ };
+ this.fetchFxAFlowParams = this.fetchFxAFlowParams.bind(this);
+ this.onCardAction = this.onCardAction.bind(this);
+ }
+
+ componentDidUpdate() {
+ this.fetchFxAFlowParams();
+ }
+
+ componentDidMount() {
+ this.fetchFxAFlowParams();
+ }
+
+ async fetchFxAFlowParams() {
+ if (this.state.flowParams || !this.props.metricsFlowUri) {
+ return;
+ }
+
+ const flowParams = await _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].fetchFlowParams(this.props.metricsFlowUri);
+ this.setState({
+ flowParams
+ });
+ }
+
+ onCardAction(action) {
+ let {
+ type,
+ data
+ } = action;
+ let UTMTerm = `aboutwelcome-${this.props.utm_term}-card`;
+
+ if (action.type === "OPEN_URL") {
+ let url = new URL(action.data.args);
+ Object(_asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__["addUtmParams"])(url, UTMTerm);
+
+ if (action.addFlowParams && this.state.flowParams) {
+ url.searchParams.append("device_id", this.state.flowParams.deviceId);
+ url.searchParams.append("flow_id", this.state.flowParams.flowId);
+ url.searchParams.append("flow_begin_time", this.state.flowParams.flowBeginTime);
+ }
+
+ data = { ...data,
+ args: url.toString()
+ };
+ }
+
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__["AboutWelcomeUtils"].handleUserAction({
+ type,
+ data
+ });
+ }
+
+ render() {
+ const {
+ props
+ } = this;
+ 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: `welcomeCardGrid show`
+ }, props.cards.map(card => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__["OnboardingCard"], _extends({
+ key: card.id,
+ message: card,
+ className: "welcomeCard",
+ sendUserActionTelemetry: props.sendTelemetry,
+ onAction: this.onCardAction,
+ UISurface: "ABOUT_WELCOME"
+ }, card)))));
+ }
+
+}
+
+/***/ }),
+/* 11 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingCard", function() { return OnboardingCard; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _aboutwelcome_components_MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
+/* 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/. */
+
+
+class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onClick = this.onClick.bind(this);
+ }
+
+ onClick() {
+ const {
+ props
+ } = this;
+ const ping = {
+ event: "CLICK_BUTTON",
+ message_id: props.id,
+ id: props.UISurface
+ };
+ props.sendUserActionTelemetry(ping);
+ props.onAction(props.content.primary_button.action, props.message);
+ }
+
+ render() {
+ const {
+ content
+ } = this.props;
+ const className = this.props.className || "onboardingMessage";
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: className
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: `onboardingMessageImage ${content.icon}`
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "onboardingContent"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_aboutwelcome_components_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: content.title
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
+ className: "onboardingTitle"
+ })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_aboutwelcome_components_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: content.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
+ className: "onboardingText"
+ }))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
+ className: "onboardingButtonContainer"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_aboutwelcome_components_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
+ text: content.primary_button.label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ className: "button onboardingButton",
+ onClick: this.onClick
+ })))));
+ }
+
+}
+
+/***/ }),
+/* 12 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReturnToAMO", function() { return ReturnToAMO; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6);
+/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
+/* 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/. */
+
+
+
+class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onClickAddExtension = this.onClickAddExtension.bind(this);
+ this.handleStartBtnClick = this.handleStartBtnClick.bind(this);
+ }
+
+ onClickAddExtension() {
+ var _content$primary_butt, _content$primary_butt2;
+
+ const {
+ content,
+ message_id,
+ url
+ } = this.props;
+
+ if (!(content === null || content === void 0 ? void 0 : (_content$primary_butt = content.primary_button) === null || _content$primary_butt === void 0 ? void 0 : (_content$primary_butt2 = _content$primary_butt.action) === null || _content$primary_butt2 === void 0 ? void 0 : _content$primary_butt2.data)) {
+ return;
+ } // Set add-on url in action.data.url property from JSON
+
+
+ content.primary_button.action.data.url = url;
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__["AboutWelcomeUtils"].handleUserAction(content.primary_button.action);
+ const ping = {
+ event: "INSTALL",
+ event_context: {
+ source: "ADD_EXTENSION_BUTTON",
+ page: "about:welcome"
+ },
+ message_id
+ };
+ window.AWSendEventTelemetry(ping);
+ }
+
+ handleStartBtnClick() {
+ const {
+ content,
+ message_id
+ } = this.props;
+ _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__["AboutWelcomeUtils"].handleUserAction(content.startButton.action);
+ const ping = {
+ event: "CLICK_BUTTON",
+ event_context: {
+ source: content.startButton.message_id,
+ page: "about:welcome"
+ },
+ message_id
+ };
+ window.AWSendEventTelemetry(ping);
+ }
+
+ render() {
+ const {
+ content
+ } = this.props;
+
+ if (!content) {
+ return null;
+ } // For experiments, when needed below rendered UI allows settings hard coded strings
+ // directly inside JSON except for ReturnToAMOText which picks add-on name and icon from fluent string
+
+
+ return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "outer-wrapper onboardingContainer"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", {
+ className: "screen"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "brand-logo"
+ }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ className: "welcome-text"
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_2__["Localized"], {
+ text: content.subtitle
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", null)), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_2__["Localized"], {
+ text: content.text
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
+ "data-l10n-args": this.props.name ? JSON.stringify({
+ "addon-name": this.props.name
+ }) : null
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
+ "data-l10n-name": "icon",
+ src: this.props.iconURL,
+ role: "presentation",
+ alt: ""
+ }))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_2__["Localized"], {
+ text: content.primary_button.label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ onClick: this.onClickAddExtension,
+ className: "primary"
+ })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_2__["Localized"], {
+ text: content.startButton.label
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ onClick: this.handleStartBtnClick,
+ className: "secondary"
+ })))));
+ }
+
+}
+ReturnToAMO.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__["DEFAULT_RTAMO_CONTENT"];
+
+/***/ })
+/******/ ]); \ No newline at end of file
diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
new file mode 100644
index 0000000000..5cb0e4a79f
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css
@@ -0,0 +1,538 @@
+/* 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/. */
+html {
+ box-sizing: border-box; }
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit; }
+
+*::-moz-focus-inner {
+ border: 0; }
+
+body {
+ margin: 0; }
+
+button,
+input {
+ background-color: inherit;
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit; }
+
+[hidden] {
+ display: none !important; }
+
+.onboardingMessageImage.addons {
+ background-image: url("chrome://activity-stream/content/data/content/assets/illustration-addons@2x.png"); }
+
+.onboardingMessageImage.privatebrowsing {
+ background-image: url("chrome://activity-stream/content/data/content/assets/illustration-privatebrowsing@2x.png"); }
+
+.onboardingMessageImage.screenshots {
+ background-image: url("chrome://activity-stream/content/data/content/assets/illustration-screenshots@2x.png"); }
+
+.onboardingMessageImage.gift {
+ background-image: url("chrome://activity-stream/content/data/content/assets/illustration-gift@2x.png"); }
+
+.onboardingMessageImage.sync {
+ background-image: url("chrome://activity-stream/content/data/content/assets/illustration-sync@2x.png"); }
+
+.onboardingMessageImage.devices {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-devices.svg"); }
+
+.onboardingMessageImage.fbcont {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-fbcont.svg"); }
+
+.onboardingMessageImage.import {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-import.svg"); }
+
+.onboardingMessageImage.ffmonitor {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-ffmonitor.svg"); }
+
+.onboardingMessageImage.ffsend {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-ffsend.svg"); }
+
+.onboardingMessageImage.lockwise {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-lockwise.svg"); }
+
+.onboardingMessageImage.mobile {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-mobile.svg"); }
+
+.onboardingMessageImage.pledge {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-pledge.svg"); }
+
+.onboardingMessageImage.pocket {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-pocket.svg"); }
+
+.onboardingMessageImage.private {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-private.svg"); }
+
+.onboardingMessageImage.sendtab {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-sendtab.svg"); }
+
+.onboardingMessageImage.tracking {
+ background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/card-illo-tracking.svg"); }
+
+html {
+ height: 100%; }
+
+body {
+ --grey-subtitle: #4A4A4F;
+ --grey-subtitle-1: #696977;
+ --newtab-background-color: #EDEDF0;
+ --newtab-background-color-1: #F9F9FA;
+ --newtab-text-primary-color: #0C0C0D;
+ --newtab-text-conditional-color: #4A4A4F;
+ --newtab-button-primary-color: #0060DF;
+ --newtab-button-secondary-color: #0060DF;
+ --newtab-card-background-color: #FFF;
+ --newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.4);
+ --tiles-theme-section-border-width: 1px;
+ --welcome-header-text-color: #2B2156;
+ --welcome-header-text-color-1: #20133A;
+ --welcome-card-button-background-color: rgba(12, 12, 13, 0.1);
+ --welcome-card-button-background-hover-color: rgba(12, 12, 13, 0.2);
+ --welcome-card-button-background-active-color: rgba(12, 12, 13, 0.3);
+ --welcome-button-box-shadow-color: #0A84FF;
+ --welcome-button-box-shadow-inset-color: rgba(10, 132, 255, 0.3);
+ --welcome-button-text-color: #FFF;
+ --welcome-button-background-hover-color: #003EAA;
+ --welcome-button-background-active-color: #002275;
+ --about-welcome-media-fade: linear-gradient(transparent, transparent 35%, #F9F9FA, #F9F9FA);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ubuntu', 'Helvetica Neue', sans-serif;
+ font-size: 16px;
+ position: relative;
+ /* these two rules fix test failures in
+ "browser_ext_themes_ntp_colors" & "browser_ext_themes_ntp_colors_perwindow".*/
+ color: var(--newtab-text-primary-color);
+ background-color: var(--newtab-background-color); }
+ body[lwt-newtab-brighttext] {
+ --newtab-background-color: #2A2A2E;
+ --newtab-background-color-1: #1D1133;
+ --newtab-text-primary-color: #F9F9FA;
+ --newtab-text-conditional-color: #F9F9FA;
+ --grey-subtitle-1: #FFF;
+ --newtab-button-primary-color: #0060DF;
+ --newtab-button-secondary-color: #FFF;
+ --newtab-card-background-color: #38383D;
+ --newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.4);
+ --welcome-header-text-color: rgba(255, 255, 255, 0.6);
+ --welcome-header-text-color-1: #7542E5;
+ --welcome-card-button-background-color: rgba(12, 12, 13, 0.3);
+ --welcome-card-button-background-hover-color: rgba(12, 12, 13, 0.5);
+ --welcome-card-button-background-active-color: rgba(12, 12, 13, 0.7);
+ --welcome-button-box-shadow-color: #0A84FF;
+ --about-welcome-media-fade: linear-gradient(transparent, transparent 35%, #1D1133, #1D1133); }
+
+.welcomeCardGrid {
+ margin: 0;
+ margin-top: 32px;
+ display: grid;
+ grid-gap: 32px;
+ transition: opacity 0.4s;
+ transition-delay: 0.1s;
+ grid-auto-rows: 1fr; }
+ @media (min-width: 610px) {
+ .welcomeCardGrid {
+ grid-template-columns: repeat(auto-fit, 224px); } }
+ @media (min-width: 1122px) {
+ .welcomeCardGrid {
+ grid-template-columns: repeat(auto-fit, 309px); } }
+
+.welcomeContainer {
+ text-align: center; }
+ @media (min-width: 610px) {
+ .welcomeContainer {
+ max-height: 1000px; } }
+ .welcomeContainer h1 {
+ font-size: 36px;
+ font-weight: 200;
+ margin: 0 0 40px;
+ color: var(--welcome-header-text-color); }
+ .welcomeContainer .welcome-title {
+ margin-bottom: 5px;
+ line-height: 52px; }
+ .welcomeContainer .welcome-subtitle {
+ font-size: 28px;
+ font-weight: 200;
+ margin: 6px 0 0;
+ color: var(--grey-subtitle);
+ line-height: 42px; }
+
+.welcomeContainerInner {
+ margin: auto;
+ padding: 40px 25px; }
+ @media (min-width: 610px) {
+ .welcomeContainerInner {
+ width: 530px; } }
+ @media (min-width: 866px) {
+ .welcomeContainerInner {
+ width: 786px; } }
+ @media (min-width: 1122px) {
+ .welcomeContainerInner {
+ width: 1042px; } }
+
+.welcomeCard {
+ position: relative;
+ background: var(--newtab-card-background-color);
+ border-radius: 4px;
+ box-shadow: var(--newtab-card-shadow);
+ font-size: 13px;
+ padding: 20px 20px 60px; }
+ @media (max-width: 866px) {
+ .welcomeCard {
+ padding: 20px; } }
+ @media (min-width: 1122px) {
+ .welcomeCard {
+ font-size: 15px; } }
+
+.welcomeCard .onboardingTitle {
+ font-weight: normal;
+ color: var(--newtab-text-primary-color);
+ margin: 10px 0 4px;
+ font-size: 15px; }
+ @media (min-width: 1122px) {
+ .welcomeCard .onboardingTitle {
+ font-size: 18px; } }
+
+.welcomeCard .onboardingText {
+ margin: 0 0 60px;
+ color: var(--newtab-text-conditional-color);
+ line-height: 1.5;
+ font-weight: 200; }
+
+.welcomeCard .onboardingButton {
+ color: var(--newtab-text-conditional-color);
+ background: var(--welcome-card-button-background-color);
+ border: 0;
+ border-radius: 4px;
+ margin: 14px;
+ min-width: 70%;
+ padding: 6px 14px;
+ white-space: pre-wrap;
+ cursor: pointer; }
+ .welcomeCard .onboardingButton:focus, .welcomeCard .onboardingButton:hover {
+ box-shadow: none;
+ background: var(--welcome-card-button-background-hover-color); }
+ .welcomeCard .onboardingButton:focus {
+ outline: dotted 1px; }
+ .welcomeCard .onboardingButton:active {
+ background: var(--welcome-card-button-background-active-color); }
+
+.welcomeCard .onboardingButtonContainer {
+ position: absolute;
+ bottom: 16px;
+ left: 0;
+ width: 100%;
+ text-align: center; }
+
+.onboardingMessageImage {
+ height: 112px;
+ width: 180px;
+ background-size: auto 140px;
+ background-position: center center;
+ background-repeat: no-repeat;
+ display: inline-block; }
+ @media (max-width: 866px) {
+ .onboardingMessageImage {
+ height: 75px;
+ min-width: 80px;
+ background-size: 140px; } }
+
+.start-button {
+ border: 0;
+ font-size: 15px;
+ font-family: inherit;
+ font-weight: 200;
+ margin-inline-start: 12px;
+ margin: 30px 0 25px;
+ padding: 8px 16px;
+ white-space: nowrap;
+ background-color: var(--newtab-button-primary-color);
+ color: var(--welcome-button-text-color);
+ cursor: pointer;
+ border-radius: 2px; }
+ .start-button:focus {
+ background: var(--welcome-button-background-hover-color);
+ box-shadow: 0 0 0 1px var(--welcome-button-box-shadow-inset-color) inset, 0 0 0 1px var(--welcome-button-box-shadow-inset-color), 0 0 0 4px var(--welcome-button-box-shadow-color); }
+ .start-button:hover {
+ background: var(--welcome-button-background-hover-color); }
+ .start-button:active {
+ background: var(--welcome-button-background-active-color); }
+
+.onboardingContainer {
+ text-align: center;
+ overflow-x: auto;
+ height: 100vh;
+ background-color: var(--newtab-background-color-1); }
+ .onboardingContainer .screen {
+ display: flex;
+ flex-flow: column nowrap;
+ height: 100%; }
+ .onboardingContainer .brand-logo {
+ background: url("chrome://branding/content/about-logo.svg") top center/112px no-repeat;
+ padding: 112px 0 20px;
+ margin-top: 60px; }
+ .onboardingContainer .brand-logo.cta-top {
+ margin-top: 25px; }
+ .onboardingContainer .welcomeZap span {
+ position: relative;
+ z-index: 1;
+ white-space: nowrap; }
+ .onboardingContainer .welcomeZap .zap::after {
+ display: block;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ content: '';
+ position: absolute;
+ top: calc(100% - 0.15em);
+ width: 100%;
+ height: 0.3em;
+ left: 0;
+ z-index: -1; }
+ .onboardingContainer .welcomeZap .zap.short::after {
+ background-image: url("chrome://activity-stream/content/data/content/assets/short-zap.svg"); }
+ .onboardingContainer .welcomeZap .zap.long::after {
+ background-image: url("chrome://activity-stream/content/data/content/assets/long-zap.svg"); }
+ .onboardingContainer .welcome-text {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 20px; }
+ .onboardingContainer .welcome-text h1,
+ .onboardingContainer .welcome-text h2 {
+ width: 860px; }
+ @media (max-width: 866px) {
+ .onboardingContainer .welcome-text h1,
+ .onboardingContainer .welcome-text h2 {
+ width: 530px; } }
+ @media (max-width: 610px) {
+ .onboardingContainer .welcome-text h1,
+ .onboardingContainer .welcome-text h2 {
+ width: 430px; } }
+ .onboardingContainer .welcome-text h1 {
+ font-size: 48px;
+ line-height: 56px;
+ font-weight: bold;
+ margin: 0 6px;
+ color: var(--welcome-header-text-color-1); }
+ .onboardingContainer .welcome-text h2 {
+ font-size: 18px;
+ font-weight: normal;
+ margin: 10px 6px 0;
+ color: var(--grey-subtitle-1);
+ line-height: 28px;
+ max-width: 750px;
+ letter-spacing: -0.01em; }
+ .onboardingContainer .welcome-text img {
+ margin-inline: 2px;
+ width: 20px;
+ height: 20px; }
+ .onboardingContainer .tiles-theme-container {
+ margin: 10px auto;
+ border: 0; }
+ .onboardingContainer .sr-only {
+ opacity: 0;
+ overflow: hidden;
+ position: absolute; }
+ .onboardingContainer .sr-only.input {
+ height: 1px;
+ width: 1px; }
+ .onboardingContainer .tiles-theme-section {
+ display: grid;
+ grid-gap: 21px;
+ grid-template-columns: repeat(4, auto);
+ /* --newtab-background-color-1 will be invisible, but it's necessary to
+ * keep the content from jumping around when it gets focus-within and
+ * does sprout a dotted border. This way it keeps a 1 pixel wide border
+ * either way so things don't change position.
+ */
+ border: var(--tiles-theme-section-border-width) solid var(--newtab-background-color-1); }
+ @media (max-width: 610px) {
+ .onboardingContainer .tiles-theme-section {
+ grid-template-columns: repeat(2, auto); } }
+ .onboardingContainer .tiles-theme-section:focus-within {
+ border: var(--tiles-theme-section-border-width) dotted; }
+ .onboardingContainer .tiles-theme-section .theme {
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ width: 180px;
+ height: 145px;
+ color: #000;
+ background-color: #FFF;
+ box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.4);
+ border-radius: 4px;
+ cursor: pointer; }
+ .onboardingContainer .tiles-theme-section .theme .icon {
+ background-size: cover;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ height: 91px; }
+ .onboardingContainer .tiles-theme-section .theme .icon:dir(rtl) {
+ transform: scaleX(-1); }
+ .onboardingContainer .tiles-theme-section .theme .icon.light {
+ background-image: url("chrome://mozapps/content/extensions/firefox-compact-light.svg"); }
+ .onboardingContainer .tiles-theme-section .theme .icon.dark {
+ background-image: url("chrome://mozapps/content/extensions/firefox-compact-dark.svg"); }
+ .onboardingContainer .tiles-theme-section .theme .icon.automatic {
+ background-image: url("chrome://mozapps/content/extensions/default-theme.svg"); }
+ .onboardingContainer .tiles-theme-section .theme .icon.alpenglow {
+ background-image: url("chrome://mozapps/content/extensions/firefox-alpenglow.svg"); }
+ .onboardingContainer .tiles-theme-section .theme .text {
+ display: flex;
+ font-size: 14px;
+ font-weight: bold;
+ line-height: 22px;
+ margin-inline-start: 12px;
+ margin-top: 9px; }
+ .onboardingContainer .tiles-theme-section .theme.selected {
+ outline: 4px solid #0090ED;
+ outline-offset: -4px; }
+ .onboardingContainer .tiles-theme-section .theme:focus, .onboardingContainer .tiles-theme-section .theme:active {
+ outline: 4px solid #0090ED;
+ outline-offset: -4px; }
+ .onboardingContainer .tiles-container {
+ margin: 10px auto; }
+ .onboardingContainer .tiles-container.info {
+ padding: 6px 12px 12px; }
+ .onboardingContainer .tiles-container.info:hover, .onboardingContainer .tiles-container.info:focus {
+ background-color: rgba(217, 217, 227, 0.3);
+ border-radius: 4px; }
+ .onboardingContainer .tiles-topsites-section {
+ display: grid;
+ grid-gap: 24px;
+ grid-template-columns: repeat(5, auto); }
+ @media (max-width: 610px) {
+ .onboardingContainer .tiles-topsites-section {
+ grid-template-columns: repeat(3, auto); } }
+ .onboardingContainer .tiles-topsites-section .site {
+ width: 96px; }
+ .onboardingContainer .tiles-topsites-section .icon {
+ background-size: cover;
+ border-radius: 4px;
+ box-shadow: var(--newtab-card-shadow);
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 24px;
+ font-weight: bold;
+ height: 96px;
+ line-height: 96px; }
+ .onboardingContainer .tiles-topsites-section .host {
+ font-size: 12px;
+ line-height: 36px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap; }
+ .onboardingContainer .tiles-topsites-section .site:nth-child(1) .icon {
+ background-color: #7542E5; }
+ .onboardingContainer .tiles-topsites-section .site:nth-child(2) .icon {
+ background-color: #952BB9; }
+ .onboardingContainer .tiles-topsites-section .site:nth-child(3) .icon {
+ background-color: #E31587; }
+ .onboardingContainer .tiles-topsites-section .site:nth-child(4) .icon {
+ background-color: #E25920; }
+ .onboardingContainer .tiles-topsites-section .site:nth-child(5) .icon {
+ background-color: #0250BB; }
+ .onboardingContainer .tiles-media-section {
+ align-self: center;
+ position: relative;
+ margin-top: -12px;
+ margin-bottom: -155px; }
+ .onboardingContainer .tiles-media-section .fade {
+ height: 390px;
+ width: 800px;
+ position: absolute;
+ background-image: var(--about-welcome-media-fade); }
+ .onboardingContainer .tiles-media-section .media {
+ height: 390px;
+ width: 800px; }
+ .onboardingContainer .tiles-media-section.privacy {
+ background: top no-repeat url("chrome://activity-stream/content/data/content/assets/firefox-protections.svg");
+ height: 200px;
+ width: 800px;
+ margin: 0; }
+ .onboardingContainer .tiles-media-section.privacy.media {
+ opacity: 0; }
+ .onboardingContainer button {
+ font-family: inherit;
+ cursor: pointer;
+ border: 0;
+ border-radius: 4px; }
+ .onboardingContainer button.primary {
+ font-size: 16px;
+ margin-inline-start: 12px;
+ margin: 20px 0 0;
+ padding: 12px 20px;
+ white-space: nowrap;
+ background-color: var(--newtab-button-primary-color);
+ color: var(--welcome-button-text-color);
+ fill: currentColor;
+ position: relative;
+ z-index: 1;
+ border: 1px solid transparent; }
+ .onboardingContainer button.primary:focus {
+ background: var(--welcome-button-background-hover-color);
+ box-shadow: 0 0 0 4px var(--welcome-button-box-shadow-color); }
+ .onboardingContainer button.primary:hover {
+ background: var(--welcome-button-background-hover-color); }
+ .onboardingContainer button.primary:active {
+ background: var(--welcome-button-background-active-color); }
+ .onboardingContainer button.secondary {
+ background-color: initial;
+ text-decoration: underline;
+ display: block;
+ padding: 0;
+ width: auto;
+ color: var(--newtab-button-secondary-color);
+ margin-top: 14px; }
+ .onboardingContainer button.secondary:hover, .onboardingContainer button.secondary:active {
+ background-color: initial; }
+ .onboardingContainer .secondary-cta {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ font-size: 14px; }
+ .onboardingContainer .secondary-cta.top {
+ justify-content: end;
+ align-items: end;
+ padding-inline-end: 30px;
+ padding-top: 4px; }
+ @media (max-width: 610px) {
+ .onboardingContainer .secondary-cta.top {
+ justify-content: center; } }
+ .onboardingContainer .secondary-cta span {
+ color: var(--grey-subtitle-1);
+ margin: 0 4px; }
+ .onboardingContainer .helptext {
+ padding: 1em;
+ text-align: center;
+ color: var(--grey-subtitle-1);
+ font-size: 12px;
+ line-height: 18px; }
+ .onboardingContainer .helptext.default {
+ align-self: center;
+ max-width: 40%; }
+ .onboardingContainer .steps {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ margin-top: auto;
+ padding: 32px 0 66px;
+ z-index: 1; }
+ .onboardingContainer .steps.has-helptext {
+ padding-bottom: 0; }
+ .onboardingContainer .steps .indicator {
+ width: 60px;
+ height: 4px;
+ margin-inline-end: 4px;
+ margin-inline-start: 4px;
+ background: var(--grey-subtitle-1);
+ border-radius: 5px;
+ border: 1px solid transparent;
+ opacity: 0.25; }
+ .onboardingContainer .steps .indicator.current {
+ opacity: 1; }
diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.html b/browser/components/newtab/aboutwelcome/content/aboutwelcome.html
new file mode 100644
index 0000000000..39d2b88f97
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.html
@@ -0,0 +1,26 @@
+<!-- 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/. -->
+
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; media-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src resource: chrome:;">
+ <title data-l10n-id="onboarding-welcome-header"></title>
+ <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png">
+ <link rel="stylesheet" href="chrome://activity-stream/content/aboutwelcome/aboutwelcome.css">
+ <link rel="localization" href="branding/brand.ftl"/>
+ <link rel="localization" href="browser/branding/sync-brand.ftl"/>
+ <link rel="localization" href="browser/branding/brandings.ftl"/>
+ <link rel="localization" href="browser/newtab/onboarding.ftl"/>
+ </head>
+ <body>
+ <div id="root" class="welcome-container" role="presentation">
+ </div>
+ <script src="resource://activity-stream/vendor/react.js"></script>
+ <script src="resource://activity-stream/vendor/react-dom.js"></script>
+ <script src="chrome://browser/content/contentTheme.js"></script>
+ <script src="resource://activity-stream/aboutwelcome/aboutwelcome.bundle.js"></script>
+ </body>
+</html>
diff --git a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm
new file mode 100644
index 0000000000..8c103dd280
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeTelemetry.jsm
@@ -0,0 +1,115 @@
+/* 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 = ["AboutWelcomeTelemetry"];
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ PingCentre: "resource:///modules/PingCentre.jsm",
+ ClientID: "resource://gre/modules/ClientID.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+ TelemetrySession: "resource://gre/modules/TelemetrySession.jsm",
+ AttributionCode: "resource:///modules/AttributionCode.jsm",
+});
+XPCOMUtils.defineLazyServiceGetters(this, {
+ gUUIDGenerator: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
+});
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "structuredIngestionEndpointBase",
+ "browser.newtabpage.activity-stream.telemetry.structuredIngestion.endpoint",
+ ""
+);
+XPCOMUtils.defineLazyGetter(this, "telemetryClientId", () =>
+ ClientID.getClientID()
+);
+XPCOMUtils.defineLazyGetter(
+ this,
+ "browserSessionId",
+ () => TelemetrySession.getMetadata("").sessionId
+);
+const TELEMETRY_TOPIC = "about:welcome";
+const PING_TYPE = "onboarding";
+const PING_VERSION = "1";
+const STRUCTURED_INGESTION_NAMESPACE_MS = "messaging-system";
+
+class AboutWelcomeTelemetry {
+ constructor() {
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "telemetryEnabled",
+ "browser.newtabpage.activity-stream.telemetry",
+ false
+ );
+ }
+
+ /**
+ * Lazily initialize PingCentre for Activity Stream to send pings
+ */
+ get pingCentre() {
+ Object.defineProperty(this, "pingCentre", {
+ value: new PingCentre({ topic: TELEMETRY_TOPIC }),
+ });
+ return this.pingCentre;
+ }
+
+ _generateStructuredIngestionEndpoint() {
+ const uuid = gUUIDGenerator.generateUUID().toString();
+ // Structured Ingestion does not support the UUID generated by gUUIDGenerator,
+ // because it contains leading and trailing braces. Need to trim them first.
+ const docID = uuid.slice(1, -1);
+ const extension = `${STRUCTURED_INGESTION_NAMESPACE_MS}/${PING_TYPE}/${PING_VERSION}/${docID}`;
+ return `${structuredIngestionEndpointBase}/${extension}`;
+ }
+
+ /**
+ * Attach browser attribution data to a ping payload.
+ *
+ * It intentionally queries the *cached* attribution data other than calling
+ * `getAttrDataAsync()` in order to minimize the overhead here.
+ * For the same reason, we are not querying the attribution data from
+ * `TelemetryEnvironment.currentEnvironment.settings`.
+ *
+ * In practice, it's very likely that the attribution data is already read
+ * and cached at some point by `AboutWelcomeParent`, so it should be able to
+ * read the cached results for the most if not all of the pings.
+ */
+ _maybeAttachAttribution(ping) {
+ const attribution = AttributionCode.getCachedAttributionData();
+ if (attribution && Object.keys(attribution).length) {
+ ping.attribution = attribution;
+ }
+ return ping;
+ }
+
+ async _createPing(event) {
+ if (event.event_context && typeof event.event_context === "object") {
+ event.event_context = JSON.stringify(event.event_context);
+ }
+ let ping = {
+ ...event,
+ addon_version: Services.appinfo.appBuildID,
+ locale: Services.locale.appLocaleAsBCP47,
+ client_id: await telemetryClientId,
+ browser_session_id: browserSessionId,
+ };
+
+ return this._maybeAttachAttribution(ping);
+ }
+
+ async sendTelemetry(event) {
+ if (!this.telemetryEnabled) {
+ return;
+ }
+
+ const ping = await this._createPing(event);
+ this.pingCentre.sendStructuredIngestionPing(
+ ping,
+ this._generateStructuredIngestionEndpoint()
+ );
+ }
+}