diff options
Diffstat (limited to '')
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() + ); + } +} |