From d2bfedea15eea695371c408a497cac48bf8f2481 Mon Sep 17 00:00:00 2001 From: Punam Dahiya Date: Sat, 16 May 2020 00:02:11 +0000 Subject: [PATCH] Bug 1637079 - Initial multi stage about:welcome layout r=k88hudson Differential Revision: https://phabricator.services.mozilla.com/D74811 --- browser/app/profile/firefox.js | 2 + browser/components/newtab/.eslintrc.js | 1 + .../newtab/aboutwelcome/AboutWelcomeChild.jsm | 35 ++ .../content/aboutwelcome.bundle.js | 368 +++++++++++------- .../aboutwelcome/content/aboutwelcome.css | 3 - .../content-src/aboutwelcome/aboutwelcome.jsx | 18 +- .../aboutwelcome/aboutwelcome.scss | 5 - .../components/MultiStageAboutWelcome.jsx | 49 +++ .../content-src/lib/aboutwelcome-utils.js | 1 + .../newtab/test/browser/browser.ini | 1 + .../browser_aboutwelcome_multistage.js | 116 ++++++ .../lib/SpecialMessageActions.jsm | 5 +- .../schemas/SpecialMessageActionSchemas.js | 6 + 13 files changed, 452 insertions(+), 158 deletions(-) create mode 100644 browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx create mode 100644 browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 265e03608cc9..653b33425c29 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1327,6 +1327,8 @@ pref("trailhead.firstrun.branches", "join-dynamic"); // Separate about welcome pref("browser.aboutwelcome.enabled", true); +// Used for switching simplified 3 cards welcome to multistage welcome +pref("browser.aboutwelcome.overrideContent", ""); // The pref that controls if the What's New panel is enabled. pref("browser.messaging-system.whatsNewPanel.enabled", true); diff --git a/browser/components/newtab/.eslintrc.js b/browser/components/newtab/.eslintrc.js index e166a246bc46..6153232afd1d 100644 --- a/browser/components/newtab/.eslintrc.js +++ b/browser/components/newtab/.eslintrc.js @@ -39,6 +39,7 @@ module.exports = { // These files use fluent-dom to insert content files: [ "content-src/aboutwelcome/components/HeroText.jsx", + "content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx", "content-src/asrouter/templates/OnboardingMessage/**", "content-src/asrouter/templates/FirstRun/**", "content-src/asrouter/templates/Trailhead/**", diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm index 5dc68975658a..01c15fe48167 100644 --- a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm +++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm @@ -21,6 +21,25 @@ XPCOMUtils.defineLazyGetter(this, "log", () => { return new Logger("AboutWelcomeChild"); }); +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 +); + class AboutWelcomeChild extends JSWindowActorChild { actorCreated() { this.exportFunctions(); @@ -73,6 +92,12 @@ class AboutWelcomeChild extends JSWindowActorChild { defineAs: "AWGetStartupData", }); + // For local dev, checks for JSON content inside pref browser.aboutwelcome.overrideContent + // that is used to override default 3 cards welcome UI with multistage welcome + Cu.exportFunction(this.AWGetMultiStageScreens.bind(this), window, { + defineAs: "AWGetMultiStageScreens", + }); + Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, { defineAs: "AWGetFxAMetricsFlowURI", }); @@ -92,6 +117,16 @@ class AboutWelcomeChild extends JSWindowActorChild { ); } + /** + * Send multistage welcome JSON data read from aboutwelcome.overrideConetent pref to page + */ + AWGetMultiStageScreens() { + return Cu.cloneInto( + multiStageAboutWelcomeContent || {}, + this.contentWindow + ); + } + /** * Send initial data to page including experiment information */ diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js index 0e1150cb4278..ae60542de790 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -100,10 +100,11 @@ __webpack_require__.r(__webpack_exports__); /* 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_HeroText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); -/* harmony import */ var _components_FxCards__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); -/* harmony import */ var _components_MSLocalized__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4); -/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8); +/* harmony import */ var _components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); +/* harmony import */ var _components_HeroText__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6); +/* harmony import */ var _components_FxCards__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7); +/* harmony import */ var _components_MSLocalized__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4); +/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5); 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 @@ -116,6 +117,7 @@ function _extends() { _extends = Object.assign || function (target) { for (var i + class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent { constructor(props) { super(props); @@ -136,6 +138,8 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp componentDidMount() { if (this.props.experiment && this.props.branchId) { this.messageId = `ABOUT_WELCOME_${this.props.experiment}_${this.props.branchId}`.toUpperCase(); + } else if (this.props.id && this.props.screens) { + this.messageId = this.props.id; } this.fetchFxAFlowUri(); @@ -149,7 +153,7 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp } handleStartBtnClick() { - _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__["AboutWelcomeUtils"].handleUserAction(this.props.startButton.action); + _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_6__["AboutWelcomeUtils"].handleUserAction(this.props.startButton.action); const ping = { event: "CLICK_BUTTON", event_context: { @@ -165,21 +169,31 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp render() { const { props - } = this; + } = this; // TBD: Refactor to redirect based off template value + // inside props.template + // Create SimpleAboutWelcome that renders default about welcome + // See Bug 1638087 + + if (props.screens) { + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__["MultiStageAboutWelcome"], { + screens: props.screens + }); + } + let UTMTerm = this.props.experiment && this.props.branchId ? `${this.props.experiment}-${this.props.branchId}` : "default"; 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(_components_HeroText__WEBPACK_IMPORTED_MODULE_2__["HeroText"], { + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("main", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_HeroText__WEBPACK_IMPORTED_MODULE_3__["HeroText"], { title: props.title, subtitle: props.subtitle - }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_FxCards__WEBPACK_IMPORTED_MODULE_3__["FxCards"], { + }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_FxCards__WEBPACK_IMPORTED_MODULE_4__["FxCards"], { cards: props.cards, metricsFlowUri: this.state.metricsFlowUri, sendTelemetry: window.AWSendEventTelemetry, utm_term: UTMTerm - }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_MSLocalized__WEBPACK_IMPORTED_MODULE_4__["Localized"], { + }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_MSLocalized__WEBPACK_IMPORTED_MODULE_5__["Localized"], { text: props.startButton.label }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", { className: "start-button", @@ -189,14 +203,20 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp } -AboutWelcome.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_5__["DEFAULT_WELCOME_CONTENT"]; +AboutWelcome.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_6__["DEFAULT_WELCOME_CONTENT"]; async function mount() { const { slug, branch } = await window.AWGetStartupData(); - const settings = branch && branch.value ? branch.value : {}; + let settings = branch && branch.value ? branch.value : {}; + + if (!(branch && branch.value)) { + // Check for override content in pref browser.aboutwelcome.overrideContent + settings = await window.AWGetMultiStageScreens(); + } + react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(AboutWelcome, _extends({ experiment: slug, branchId: branch && branch.slug @@ -223,24 +243,48 @@ module.exports = ReactDOM; "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HeroText", function() { return HeroText; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiStageAboutWelcome", function() { return MultiStageAboutWelcome; }); /* 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 _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5); /* 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" + +const MultiStageAboutWelcome = props => { + const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0); // Transition to next screen, opening about:home on last screen button CTA + + const handleTransition = index < props.screens.length ? Object(react__WEBPACK_IMPORTED_MODULE_0__["useCallback"])(() => setScreenIndex(prevState => prevState + 1), []) : _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__["AboutWelcomeUtils"].handleUserAction({ + type: "OPEN_ABOUT_PAGE", + data: { + args: "home", + where: "current" + } + }); + 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` + }, props.screens.map(screen => { + return index === screen.order ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(WelcomeScreen, { + key: screen.id, + id: screen.id, + content: screen.content, + navigate: handleTransition + }) : null; + }))); +}; + +const WelcomeScreen = props => { + return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", { + className: `${props.id}` + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], { + text: props.content.title + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", null)), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], { + text: props.content.primary_button.label + }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", { + onClick: props.navigate }))); }; @@ -305,14 +349,164 @@ const Localized = ({ /* 5 */ /***/ (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_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) { + switch (action.type) { + case "OPEN_ABOUT_PAGE": + case "OPEN_AWESOME_BAR": + case "OPEN_PRIVATE_BROWSER_WINDOW": + case "SHOW_MIGRATION_WIZARD": + window.AWSendToParent("SPECIAL_ACTION", action); + break; + + case "OPEN_URL": + window.open(action.data.args); + break; + } + }, + + sendEvent(type, detail) { + document.dispatchEvent(new CustomEvent(`AWPage:${type}`, { + bubbles: true, + detail + })); + } + +}; +const DEFAULT_WELCOME_CONTENT = { + title: { + string_id: "onboarding-welcome-header" + }, + startButton: { + label: { + string_id: "onboarding-start-browsing-button-label" + }, + message_id: "START_BROWSING_BUTTON", + action: { + type: "OPEN_AWESOME_BAR" + } + }, + cards: [{ + content: { + title: { + string_id: "onboarding-data-sync-title" + }, + text: { + string_id: "onboarding-data-sync-text2" + }, + icon: "devices", + primary_button: { + label: { + string_id: "onboarding-data-sync-button2" + }, + action: { + type: "OPEN_URL", + addFlowParams: true, + data: { + args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", + where: "tabshifted" + } + } + } + }, + id: "TRAILHEAD_CARD_2", + order: 1, + blockOnClick: false + }, { + content: { + title: { + string_id: "onboarding-firefox-monitor-title" + }, + text: { + string_id: "onboarding-firefox-monitor-text2" + }, + icon: "ffmonitor", + primary_button: { + label: { + string_id: "onboarding-firefox-monitor-button" + }, + action: { + type: "OPEN_URL", + data: { + args: "https://monitor.firefox.com/", + where: "tabshifted" + } + } + } + }, + id: "TRAILHEAD_CARD_3", + order: 2, + blockOnClick: false + }, { + content: { + title: { + string_id: "onboarding-browse-privately-title" + }, + text: { + string_id: "onboarding-browse-privately-text" + }, + icon: "private", + primary_button: { + label: { + string_id: "onboarding-browse-privately-button" + }, + action: { + type: "OPEN_PRIVATE_BROWSER_WINDOW" + } + } + }, + id: "TRAILHEAD_CARD_4", + order: 3, + blockOnClick: true + }] +}; + +/***/ }), +/* 6 */ +/***/ (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" + }))); +}; + +/***/ }), +/* 7 */ +/***/ (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__(6); -/* harmony import */ var _asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7); -/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8); +/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); +/* harmony import */ var _asrouter_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9); +/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); 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 @@ -422,7 +616,7 @@ class FxCards extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent } /***/ }), -/* 6 */ +/* 8 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -463,7 +657,7 @@ function addUtmParams(url, utmTerm) { } /***/ }), -/* 7 */ +/* 9 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -527,127 +721,5 @@ class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCo } -/***/ }), -/* 8 */ -/***/ (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_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) { - switch (action.type) { - case "OPEN_AWESOME_BAR": - case "OPEN_PRIVATE_BROWSER_WINDOW": - case "SHOW_MIGRATION_WIZARD": - window.AWSendToParent("SPECIAL_ACTION", action); - break; - - case "OPEN_URL": - window.open(action.data.args); - break; - } - }, - - sendEvent(type, detail) { - document.dispatchEvent(new CustomEvent(`AWPage:${type}`, { - bubbles: true, - detail - })); - } - -}; -const DEFAULT_WELCOME_CONTENT = { - title: { - string_id: "onboarding-welcome-header" - }, - startButton: { - label: { - string_id: "onboarding-start-browsing-button-label" - }, - message_id: "START_BROWSING_BUTTON", - action: { - type: "OPEN_AWESOME_BAR" - } - }, - cards: [{ - content: { - title: { - string_id: "onboarding-data-sync-title" - }, - text: { - string_id: "onboarding-data-sync-text2" - }, - icon: "devices", - primary_button: { - label: { - string_id: "onboarding-data-sync-button2" - }, - action: { - type: "OPEN_URL", - addFlowParams: true, - data: { - args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&style=trailhead", - where: "tabshifted" - } - } - } - }, - id: "TRAILHEAD_CARD_2", - order: 1, - blockOnClick: false - }, { - content: { - title: { - string_id: "onboarding-firefox-monitor-title" - }, - text: { - string_id: "onboarding-firefox-monitor-text2" - }, - icon: "ffmonitor", - primary_button: { - label: { - string_id: "onboarding-firefox-monitor-button" - }, - action: { - type: "OPEN_URL", - data: { - args: "https://monitor.firefox.com/", - where: "tabshifted" - } - } - } - }, - id: "TRAILHEAD_CARD_3", - order: 2, - blockOnClick: false - }, { - content: { - title: { - string_id: "onboarding-browse-privately-title" - }, - text: { - string_id: "onboarding-browse-privately-text" - }, - icon: "private", - primary_button: { - label: { - string_id: "onboarding-browse-privately-button" - }, - action: { - type: "OPEN_PRIVATE_BROWSER_WINDOW" - } - } - }, - id: "TRAILHEAD_CARD_4", - order: 3, - blockOnClick: true - }] -}; - /***/ }) /******/ ]); \ No newline at end of file diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css index dd8176e33ac0..a2673571ebc4 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css @@ -121,12 +121,9 @@ body { margin-top: 32px; display: grid; grid-gap: 32px; - opacity: 0; transition: opacity 0.4s; transition-delay: 0.1s; grid-auto-rows: 1fr; } - .welcomeCardGrid.show { - opacity: 1; } @media (min-width: 610px) { .welcomeCardGrid { grid-template-columns: repeat(auto-fit, 224px); } } diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx index e85054ca91f9..8ab43ab55458 100644 --- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx @@ -4,6 +4,7 @@ import React from "react"; import ReactDOM from "react-dom"; +import { MultiStageAboutWelcome } from "./components/MultiStageAboutWelcome"; import { HeroText } from "./components/HeroText"; import { FxCards } from "./components/FxCards"; import { Localized } from "./components/MSLocalized"; @@ -29,6 +30,8 @@ class AboutWelcome extends React.PureComponent { componentDidMount() { if (this.props.experiment && this.props.branchId) { this.messageId = `ABOUT_WELCOME_${this.props.experiment}_${this.props.branchId}`.toUpperCase(); + } else if (this.props.id && this.props.screens) { + this.messageId = this.props.id; } this.fetchFxAFlowUri(); window.AWSendEventTelemetry({ @@ -56,6 +59,14 @@ class AboutWelcome extends React.PureComponent { render() { const { props } = this; + // TBD: Refactor to redirect based off template value + // inside props.template + // Create SimpleAboutWelcome that renders default about welcome + // See Bug 1638087 + if (props.screens) { + return ; + } + let UTMTerm = this.props.experiment && this.props.branchId ? `${this.props.experiment}-${this.props.branchId}` @@ -88,7 +99,12 @@ AboutWelcome.defaultProps = DEFAULT_WELCOME_CONTENT; async function mount() { const { slug, branch } = await window.AWGetStartupData(); - const settings = branch && branch.value ? branch.value : {}; + let settings = branch && branch.value ? branch.value : {}; + + if (!(branch && branch.value)) { + // Check for override content in pref browser.aboutwelcome.overrideContent + settings = await window.AWGetMultiStageScreens(); + } ReactDOM.render( { + const [index, setScreenIndex] = useState(0); + // Transition to next screen, opening about:home on last screen button CTA + const handleTransition = + index < props.screens.length + ? useCallback(() => setScreenIndex(prevState => prevState + 1), []) + : AboutWelcomeUtils.handleUserAction({ + type: "OPEN_ABOUT_PAGE", + data: { args: "home", where: "current" }, + }); + + return ( + +
+ {props.screens.map(screen => { + return index === screen.order ? ( + + ) : null; + })} +
+
+ ); +}; + +const WelcomeScreen = props => { + return ( +
+ +

+ + +

+ ); +}; diff --git a/browser/components/newtab/content-src/lib/aboutwelcome-utils.js b/browser/components/newtab/content-src/lib/aboutwelcome-utils.js index e1c0d146a5f0..77d5ff5bc107 100644 --- a/browser/components/newtab/content-src/lib/aboutwelcome-utils.js +++ b/browser/components/newtab/content-src/lib/aboutwelcome-utils.js @@ -5,6 +5,7 @@ export const AboutWelcomeUtils = { handleUserAction(action) { switch (action.type) { + case "OPEN_ABOUT_PAGE": case "OPEN_AWESOME_BAR": case "OPEN_PRIVATE_BROWSER_WINDOW": case "SHOW_MIGRATION_WIZARD": diff --git a/browser/components/newtab/test/browser/browser.ini b/browser/components/newtab/test/browser/browser.ini index c5a90993f302..8ae65d2487b3 100644 --- a/browser/components/newtab/test/browser/browser.ini +++ b/browser/components/newtab/test/browser/browser.ini @@ -16,6 +16,7 @@ prefs = [browser_aboutwelcome.js] [browser_aboutwelcome_actors.js] [browser_aboutwelcome_simplified.js] +[browser_aboutwelcome_multistage.js] [browser_aboutwelcome_observer.js] [browser_as_load_location.js] [browser_as_render.js] diff --git a/browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js b/browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js new file mode 100644 index 000000000000..a458c013ead9 --- /dev/null +++ b/browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js @@ -0,0 +1,116 @@ +"use strict"; + +const SEPARATE_ABOUT_WELCOME_PREF = "browser.aboutwelcome.enabled"; +const ABOUT_WELCOME_OVERRIDE_CONTENT_PREF = + "browser.aboutwelcome.overrideContent"; +const TEST_MULTISTAGE_JSON = + '{"id": "multi-stage-welcome","screens": [{"id": "AW_STEP1","order": 0,"content": {"title": "Step 1","primary_button": {"label": "Next"}}},{"id": "AW_STEP2","order": 1,"content": {"title": "Step 2","primary_button": {"label": "Next"}}},{"id": "AW_STEP3","order": 2,"content": {"title": "Step 3","primary_button": {"label": "Next"}}}]}'; + +/** + * Sets the aboutwelcome pref to enabled simplified welcome UI + */ +async function setAboutWelcomePref(value) { + return pushPrefs([SEPARATE_ABOUT_WELCOME_PREF, value]); +} + +async function setAboutWelcomeMultiStage(value) { + return pushPrefs([ABOUT_WELCOME_OVERRIDE_CONTENT_PREF, value]); +} + +async function openAboutWelcome() { + await setAboutWelcomePref(true); + await setAboutWelcomeMultiStage(TEST_MULTISTAGE_JSON); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:welcome", + true + ); + registerCleanupFunction(() => { + BrowserTestUtils.removeTab(tab); + }); + return tab.linkedBrowser; +} + +/** + * Setup and test simplified welcome UI + */ +async function test_about_welcome( + browser, + experiment, + expectedSelectors = [], + unexpectedSelectors = [] +) { + await ContentTask.spawn( + browser, + { expectedSelectors, experiment, unexpectedSelectors }, + async ({ + expectedSelectors: expected, + experiment: experimentName, + unexpectedSelectors: unexpected, + }) => { + for (let selector of expected) { + await ContentTaskUtils.waitForCondition( + () => content.document.querySelector(selector), + `Should render ${selector} in ${experimentName}` + ); + } + for (let selector of unexpected) { + ok( + !content.document.querySelector(selector), + `Should not render ${selector} in ${experimentName}` + ); + } + } + ); +} + +async function onNavigate(browser, message) { + await ContentTask.spawn( + browser, + { message }, + async ({ message: messageText }) => { + await ContentTaskUtils.waitForCondition( + () => content.document.querySelector("button"), + messageText + ); + let button = content.document.querySelector("button"); + button.click(); + } + ); +} + +/** + * Test the multistage welcome UI rendered using TEST_MULTISTAGE_JSON + */ +add_task(async function test_Separate_About_Welcome_branches() { + let browser = await openAboutWelcome(); + + await test_about_welcome( + browser, + "multistage step 1", + // Expected selectors: + ["div.welcomeCardGrid", "div.AW_STEP1"], + // Unexpected selectors: + ["div.AW_STEP2", "div.AW_STEP3"] + ); + + await onNavigate(browser, "Step 1"); + await test_about_welcome( + browser, + "multistage step 2", + // Expected selectors: + ["div.welcomeCardGrid", "div.AW_STEP2"], + // Unexpected selectors: + ["div.AW_STEP1", "div.AW_STEP3"] + ); + await onNavigate(browser, "Step 2"); + await test_about_welcome( + browser, + "multistage step 3", + // Expected selectors: + ["div.welcomeCardGrid", "div.AW_STEP3"], + // Unexpected selectors: + ["div.AW_STEP1", "div.AW_STEP2"] + ); +}); diff --git a/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm b/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm index 311c789157d8..c7d260dea9e2 100644 --- a/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm +++ b/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm @@ -110,7 +110,10 @@ const SpecialMessageActions = { if (action.data.entrypoint) { aboutPageURL.search = action.data.entrypoint; } - window.openTrustedLinkIn(aboutPageURL.toString(), "tab"); + window.openTrustedLinkIn( + aboutPageURL.toString(), + action.data.where || "tab" + ); break; case "OPEN_PREFERENCES_PAGE": window.openPreferences( diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js index 78e91eaacfa2..b572a558aed6 100644 --- a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js +++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js @@ -66,6 +66,12 @@ const SpecialMessageActionSchemas = { description: 'The about page. E.g. "welcome" for about:welcome', type: "string", }, + where: { + default: "tab", + description: "Where the URL is opened.", + enum: ["current", "save", "tab", "tabshifted", "window"], + type: "string", + }, entrypoint: { description: 'Any optional entrypoint value that will be added to the search. E.g. "foo=bar" would result in about:welcome?foo=bar',