diff --git a/browser/components/newtab/.eslintrc.js b/browser/components/newtab/.eslintrc.js
index 94fb85ac0983..ec1f2d35667f 100644
--- a/browser/components/newtab/.eslintrc.js
+++ b/browser/components/newtab/.eslintrc.js
@@ -44,7 +44,6 @@ module.exports = {
// These files use fluent-dom to insert content
"files": [
"content-src/asrouter/templates/OnboardingMessage/**",
- "content-src/asrouter/templates/FirstRun/**",
"content-src/asrouter/templates/Trailhead/**",
"content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx",
"content-src/components/TopSites/**",
diff --git a/browser/components/newtab/CODEOWNERS b/browser/components/newtab/CODEOWNERS
index 7601132713fb..eb3ed9f19d25 100644
--- a/browser/components/newtab/CODEOWNERS
+++ b/browser/components/newtab/CODEOWNERS
@@ -1,2 +1,2 @@
# flod as main contact for string changes
-locales-src/ @flodolo
+locales-src/en-US/strings.properties @flodolo
diff --git a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
index a4d76ab8e504..bb74d9bafd8f 100644
--- a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
+++ b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
@@ -8,19 +8,17 @@ import { generateBundles } from "./rich-text-strings";
import { ImpressionsWrapper } from "./components/ImpressionsWrapper/ImpressionsWrapper";
import { LocalizationProvider } from "fluent-react";
import { NEWTAB_DARK_THEME } from "content-src/lib/constants";
+import { OnboardingMessage } from "./templates/OnboardingMessage/OnboardingMessage";
import React from "react";
import ReactDOM from "react-dom";
+import { ReturnToAMO } from "./templates/ReturnToAMO/ReturnToAMO";
import { SnippetsTemplates } from "./templates/template-manifest";
-import { FirstRun } from "./templates/FirstRun/FirstRun";
+import { StartupOverlay } from "./templates/StartupOverlay/StartupOverlay";
+import { Trailhead } from "./templates/Trailhead/Trailhead";
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
-const TEMPLATES_ABOVE_PAGE = [
- "trailhead",
- "fxa_overlay",
- "return_to_amo_overlay",
-];
-const FIRST_RUN_TEMPLATES = TEMPLATES_ABOVE_PAGE;
+const TEMPLATES_ABOVE_PAGE = ["trailhead"];
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
export const ASRouterUtils = {
@@ -48,6 +46,9 @@ export const ASRouterUtils = {
dismissById(id) {
ASRouterUtils.sendMessage({ type: "DISMISS_MESSAGE_BY_ID", data: { id } });
},
+ dismissBundle(bundle) {
+ ASRouterUtils.sendMessage({ type: "DISMISS_BUNDLE", data: { bundle } });
+ },
executeAction(button_action) {
ASRouterUtils.sendMessage({
type: "USER_ACTION",
@@ -108,7 +109,7 @@ export class ASRouterUISurface extends React.PureComponent {
this.sendClick = this.sendClick.bind(this);
this.sendImpression = this.sendImpression.bind(this);
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
- this.state = { message: {} };
+ this.state = { message: {}, bundle: {} };
if (props.document) {
this.headerPortal = props.document.getElementById(
"header-asrouter-container"
@@ -120,10 +121,14 @@ export class ASRouterUISurface extends React.PureComponent {
}
sendUserActionTelemetry(extraProps = {}) {
- const { message } = this.state;
- const eventType = `${message.provider}_user_event`;
+ const { message, bundle } = this.state;
+ if (!message && !extraProps.message_id) {
+ throw new Error(`You must provide a message_id for bundled messages`);
+ }
+ // snippets_user_event, onboarding_user_event
+ const eventType = `${message.provider || bundle.provider}_user_event`;
ASRouterUtils.sendTelemetry({
- message_id: message.id,
+ message_id: message.id || extraProps.message_id,
source: extraProps.id,
action: eventType,
...extraProps,
@@ -175,6 +180,26 @@ export class ASRouterUISurface extends React.PureComponent {
return () => ASRouterUtils.dismissById(id);
}
+ dismissBundle(bundle) {
+ return () => {
+ ASRouterUtils.dismissBundle(bundle);
+ this.sendUserActionTelemetry({
+ event: "DISMISS",
+ id: "onboarding-cards",
+ message_id: bundle.map(m => m.id).join(","),
+ // Passing the action because some bundles (Trailhead) don't have a provider set
+ action: "onboarding_user_event",
+ });
+ };
+ }
+
+ triggerOnboarding() {
+ ASRouterUtils.sendMessage({
+ type: "TRIGGER",
+ data: { trigger: { id: "showOnboarding" } },
+ });
+ }
+
clearMessage(id) {
if (id === this.state.message.id) {
this.setState({ message: {} });
@@ -188,6 +213,9 @@ export class ASRouterUISurface extends React.PureComponent {
case "SET_MESSAGE":
this.setState({ message: action.data });
break;
+ case "SET_BUNDLED_MESSAGES":
+ this.setState({ bundle: action.data });
+ break;
case "CLEAR_MESSAGE":
this.clearMessage(action.data.id);
break;
@@ -196,8 +224,13 @@ export class ASRouterUISurface extends React.PureComponent {
this.setState({ message: {} });
}
break;
+ case "CLEAR_BUNDLE":
+ if (this.state.bundle.bundle) {
+ this.setState({ bundle: {} });
+ }
+ break;
case "CLEAR_ALL":
- this.setState({ message: {} });
+ this.setState({ message: {}, bundle: {} });
break;
case "AS_ROUTER_TARGETING_UPDATE":
action.data.forEach(id => this.clearMessage(id));
@@ -238,11 +271,18 @@ export class ASRouterUISurface extends React.PureComponent {
}
renderSnippets() {
- const { message } = this.state;
- if (!SnippetsTemplates[message.template]) {
+ if (
+ this.state.bundle.template === "onboarding" ||
+ [
+ "fxa_overlay",
+ "return_to_amo_overlay",
+ "trailhead",
+ "whatsnew_panel_message",
+ ].includes(this.state.message.template)
+ ) {
return null;
}
- const SnippetComponent = SnippetsTemplates[message.template];
+ const SnippetComponent = SnippetsTemplates[this.state.message.template];
const { content } = this.state.message;
return (
@@ -269,6 +309,70 @@ export class ASRouterUISurface extends React.PureComponent {
);
}
+ renderOnboarding() {
+ if (this.state.bundle.template === "onboarding") {
+ return (
+
+ );
+ }
+ return null;
+ }
+
+ renderFirstRunOverlay() {
+ const { message } = this.state;
+ if (message.template === "fxa_overlay") {
+ global.document.body.classList.add("fxa");
+ return (
+
+ );
+ } else if (message.template === "return_to_amo_overlay") {
+ global.document.body.classList.add("amo");
+ return (
+
+
+
+ );
+ }
+ return null;
+ }
+
+ renderTrailhead() {
+ const { message } = this.state;
+ if (message.template === "trailhead") {
+ return (
+
+ );
+ }
+ return null;
+ }
+
renderPreviewBanner() {
if (this.state.message.provider !== "preview") {
return null;
@@ -282,27 +386,9 @@ export class ASRouterUISurface extends React.PureComponent {
);
}
- renderFirstRun() {
- const { message } = this.state;
- if (FIRST_RUN_TEMPLATES.includes(message.template)) {
- return (
-
- );
- }
- return null;
- }
-
render() {
- const { message } = this.state;
- if (!message.id) {
+ const { message, bundle } = this.state;
+ if (!message.id && !bundle.template) {
return null;
}
const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(
@@ -321,7 +407,9 @@ export class ASRouterUISurface extends React.PureComponent {
ReactDOM.createPortal(
<>
{this.renderPreviewBanner()}
- {this.renderFirstRun()}
+ {this.renderTrailhead()}
+ {this.renderFirstRunOverlay()}
+ {this.renderOnboarding()}
{this.renderSnippets()}
>,
shouldRenderInHeader ? this.headerPortal : this.footerPortal
diff --git a/browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx b/browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx
deleted file mode 100644
index fff7078a6af9..000000000000
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx
+++ /dev/null
@@ -1,227 +0,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/. */
-
-import React from "react";
-import { Interrupt } from "./Interrupt";
-import { Triplets } from "./Triplets";
-import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
-import { addUtmParams } from "./addUtmParams";
-
-export const FLUENT_FILES = [
- "branding/brand.ftl",
- "browser/branding/brandings.ftl",
- "browser/branding/sync-brand.ftl",
- "browser/newtab/onboarding.ftl",
-];
-
-export const helpers = {
- selectInterruptAndTriplets(message = {}) {
- const hasInterrupt = Boolean(message.content);
- const hasTriplets = Boolean(message.bundle && message.bundle.length);
- const UTMTerm = message.utm_term || "";
- return {
- hasTriplets,
- hasInterrupt,
- interrupt: hasInterrupt ? message : null,
- triplets: hasTriplets ? message.bundle : null,
- UTMTerm,
- };
- },
-
- addFluent(document) {
- FLUENT_FILES.forEach(file => {
- const link = document.head.appendChild(document.createElement("link"));
- link.href = file;
- link.rel = "localization";
- });
- },
-
- async fetchFlowParams({ fxaEndpoint, UTMTerm, dispatch, setFlowParams }) {
- try {
- const url = new URL(
- `${fxaEndpoint}/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`
- );
- addUtmParams(url, UTMTerm);
- const response = await fetch(url, { credentials: "omit" });
- if (response.status === 200) {
- const { deviceId, flowId, flowBeginTime } = await response.json();
- setFlowParams({ deviceId, flowId, flowBeginTime });
- } else {
- dispatch(
- ac.OnlyToMain({
- type: at.TELEMETRY_UNDESIRED_EVENT,
- data: {
- event: "FXA_METRICS_FETCH_ERROR",
- value: response.status,
- },
- })
- );
- }
- } catch (error) {
- dispatch(
- ac.OnlyToMain({
- type: at.TELEMETRY_UNDESIRED_EVENT,
- data: { event: "FXA_METRICS_ERROR" },
- })
- );
- }
- },
-};
-
-export class FirstRun extends React.PureComponent {
- constructor(props) {
- super(props);
-
- this.didLoadFlowParams = false;
-
- this.state = {
- prevMessage: undefined,
-
- hasInterrupt: false,
- hasTriplets: false,
-
- interrupt: undefined,
- triplets: undefined,
-
- isInterruptVisible: false,
- isTripletsContainerVisible: false,
- isTripletsContentVisible: false,
-
- UTMTerm: "",
-
- flowParams: undefined,
- };
-
- this.closeInterrupt = this.closeInterrupt.bind(this);
- this.closeTriplets = this.closeTriplets.bind(this);
-
- helpers.addFluent(this.props.document);
- }
-
- static getDerivedStateFromProps(props, state) {
- const { message } = props;
- if (message && message.id !== state.prevMessageId) {
- const {
- hasTriplets,
- hasInterrupt,
- interrupt,
- triplets,
- UTMTerm,
- } = helpers.selectInterruptAndTriplets(message);
-
- return {
- prevMessageId: message.id,
-
- hasInterrupt,
- hasTriplets,
-
- interrupt,
- triplets,
-
- isInterruptVisible: hasInterrupt,
- isTripletsContainerVisible: hasTriplets,
- isTripletsContentVisible: !(hasInterrupt || !hasTriplets),
-
- UTMTerm,
- };
- }
- return null;
- }
-
- fetchFlowParams() {
- const { fxaEndpoint, dispatch } = this.props;
- const { UTMTerm } = this.state;
- if (fxaEndpoint && UTMTerm && !this.didLoadFlowParams) {
- this.didLoadFlowParams = true;
- helpers.fetchFlowParams({
- fxaEndpoint,
- UTMTerm,
- dispatch,
- setFlowParams: flowParams => this.setState({ flowParams }),
- });
- }
- }
-
- removeHideMain() {
- if (!this.state.hasInterrupt) {
- // We need to remove hide-main since we should show it underneath everything that has rendered
- this.props.document.body.classList.remove("hide-main", "welcome");
- }
- }
-
- componentDidMount() {
- this.fetchFlowParams();
- this.removeHideMain();
- }
-
- componentDidUpdate() {
- // In case we didn't have FXA info immediately, try again when we receive it.
- this.fetchFlowParams();
- this.removeHideMain();
- }
-
- closeInterrupt() {
- this.setState(prevState => ({
- isInterruptVisible: false,
- isTripletsContainerVisible: prevState.hasTriplets,
- isTripletsContentVisible: prevState.hasTriplets,
- }));
- }
-
- closeTriplets() {
- this.setState({ isTripletsContainerVisible: false });
- }
-
- render() {
- const { props } = this;
- const {
- sendUserActionTelemetry,
- fxaEndpoint,
- dispatch,
- executeAction,
- } = props;
-
- const {
- interrupt,
- triplets,
- isInterruptVisible,
- isTripletsContainerVisible,
- isTripletsContentVisible,
- hasTriplets,
- UTMTerm,
- flowParams,
- } = this.state;
-
- return (
- <>
- {isInterruptVisible ? (
-
- ) : null}
- {hasTriplets ? (
-
- ) : null}
- >
- );
- }
-}
diff --git a/browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx b/browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx
deleted file mode 100644
index 562e5e4bfccb..000000000000
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx
+++ /dev/null
@@ -1,67 +0,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/. */
-
-import React from "react";
-import { Trailhead } from "../Trailhead/Trailhead";
-import { ReturnToAMO } from "../ReturnToAMO/ReturnToAMO";
-import { StartupOverlay } from "../StartupOverlay/StartupOverlay";
-import { LocalizationProvider } from "fluent-react";
-import { generateBundles } from "../../rich-text-strings";
-
-export class Interrupt extends React.PureComponent {
- render() {
- const {
- onDismiss,
- onNextScene,
- message,
- sendUserActionTelemetry,
- executeAction,
- dispatch,
- fxaEndpoint,
- UTMTerm,
- flowParams,
- } = this.props;
-
- switch (message.template) {
- case "return_to_amo_overlay":
- return (
-
-
-
- );
- case "fxa_overlay":
- return (
-
- );
- case "trailhead":
- return (
-
- );
- default:
- throw new Error(`${message.template} is not a valid FirstRun message`);
- }
- }
-}
diff --git a/browser/components/newtab/content-src/asrouter/templates/FirstRun/Triplets.jsx b/browser/components/newtab/content-src/asrouter/templates/FirstRun/Triplets.jsx
deleted file mode 100644
index 74043a91e0a4..000000000000
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/Triplets.jsx
+++ /dev/null
@@ -1,91 +0,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/. */
-
-import React from "react";
-import { OnboardingCard } from "../../templates/OnboardingMessage/OnboardingMessage";
-import { addUtmParams } from "./addUtmParams";
-
-export class Triplets extends React.PureComponent {
- constructor(props) {
- super(props);
- this.onCardAction = this.onCardAction.bind(this);
- this.onHideContainer = this.onHideContainer.bind(this);
- }
-
- componentWillMount() {
- global.document.body.classList.add("inline-onboarding");
- }
-
- componentWillUnmount() {
- this.props.document.body.classList.remove("inline-onboarding");
- }
-
- onCardAction(action) {
- let actionUpdates = {};
- const { flowParams, UTMTerm } = this.props;
-
- if (action.type === "OPEN_URL") {
- let url = new URL(action.data.args);
- addUtmParams(url, UTMTerm);
-
- if (action.addFlowParams) {
- url.searchParams.append("device_id", flowParams.deviceId);
- url.searchParams.append("flow_id", flowParams.flowId);
- url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
- }
-
- actionUpdates = { data: { ...action.data, args: url.toString() } };
- }
-
- this.props.onAction({ ...action, ...actionUpdates });
- }
-
- onHideContainer() {
- const { sendUserActionTelemetry, cards, hideContainer } = this.props;
- hideContainer();
- sendUserActionTelemetry({
- event: "DISMISS",
- id: "onboarding-cards",
- message_id: cards.map(m => m.id).join(","),
- action: "onboarding_user_event",
- });
- }
-
- render() {
- const {
- cards,
- showCardPanel,
- showContent,
- sendUserActionTelemetry,
- } = this.props;
- return (
-
-
-
-
- {cards.map(card => (
-
- ))}
-
- {showCardPanel && (
-
- )}
-
-
- );
- }
-}
diff --git a/browser/components/newtab/content-src/asrouter/templates/FirstRun/addUtmParams.js b/browser/components/newtab/content-src/asrouter/templates/FirstRun/addUtmParams.js
deleted file mode 100644
index 2b66346d2827..000000000000
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/addUtmParams.js
+++ /dev/null
@@ -1,27 +0,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 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).
- */
-export 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;
-}
diff --git a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/OnboardingMessage.jsx b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/OnboardingMessage.jsx
index ba4e23344c18..ca6eab6e5273 100644
--- a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/OnboardingMessage.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/OnboardingMessage.jsx
@@ -2,8 +2,15 @@
* 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/. */
+import { ModalOverlay } from "../../components/ModalOverlay/ModalOverlay";
import React from "react";
+const FLUENT_FILES = [
+ "branding/brand.ftl",
+ "browser/branding/sync-brand.ftl",
+ "browser/newtab/onboarding.ftl",
+];
+
export class OnboardingCard extends React.PureComponent {
constructor(props) {
super(props);
@@ -50,3 +57,33 @@ export class OnboardingCard extends React.PureComponent {
);
}
}
+
+export class OnboardingMessage extends React.PureComponent {
+ componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
+ }
+
+ render() {
+ const { props } = this;
+ const { button_label, header } = props.extraTemplateStrings;
+ return (
+
+
+ {props.bundle.map(message => (
+
+ ))}
+
+
+ );
+ }
+}
diff --git a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/_OnboardingMessage.scss b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/_OnboardingMessage.scss
index 5047f2420c77..c0e99124d88f 100644
--- a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/_OnboardingMessage.scss
+++ b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/_OnboardingMessage.scss
@@ -1,3 +1,20 @@
+.onboardingMessageContainer {
+ display: grid;
+ grid-column-gap: 21px;
+ grid-template-columns: auto auto auto;
+ padding-left: 30px;
+ padding-right: 30px;
+ min-height: 500px;
+
+ // at 850px, the cards go from vertical layout to horizontal layout
+ @media(max-width: 850px) {
+ grid-template-columns: none;
+ grid-template-rows: auto auto auto;
+ padding-left: 110px;
+ padding-right: 110px;
+ }
+}
+
.onboardingMessage {
height: 340px;
text-align: center;
diff --git a/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx b/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
index e2c60f484b57..c944eefbb179 100644
--- a/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
@@ -15,11 +15,8 @@ export class ReturnToAMO extends React.PureComponent {
this.onBlockButton = this.onBlockButton.bind(this);
}
- componentWillMount() {
- global.document.body.classList.add("amo");
- }
-
componentDidMount() {
+ this.props.onReady();
this.props.sendUserActionTelemetry({
event: "IMPRESSION",
id: this.props.UISurface,
diff --git a/browser/components/newtab/content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx b/browser/components/newtab/content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx
index 7d16ec50f4eb..13b7da371f9c 100644
--- a/browser/components/newtab/content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx
@@ -2,15 +2,23 @@
* 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/. */
-import { actionCreators as ac } from "common/Actions.jsm";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
+import { connect } from "react-redux";
import React from "react";
-export class StartupOverlay extends React.PureComponent {
+const FLUENT_FILES = [
+ "branding/brand.ftl",
+ "browser/branding/sync-brand.ftl",
+ "browser/newtab/onboarding.ftl",
+];
+
+export class _StartupOverlay extends React.PureComponent {
constructor(props) {
super(props);
this.onInputChange = this.onInputChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.clickSkip = this.clickSkip.bind(this);
+ this.initScene = this.initScene.bind(this);
this.removeOverlay = this.removeOverlay.bind(this);
this.onInputInvalid = this.onInputInvalid.bind(this);
@@ -18,20 +26,71 @@ export class StartupOverlay extends React.PureComponent {
"utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-control";
this.state = {
- show: false,
emailInput: "",
+ overlayRemoved: false,
+ deviceId: "",
+ flowId: "",
+ flowBeginTime: 0,
};
+ this.didFetch = false;
}
- componentWillMount() {
- global.document.body.classList.add("fxa");
+ async componentWillUpdate() {
+ if (this.props.fxa_endpoint && !this.didFetch) {
+ try {
+ this.didFetch = true;
+ const fxaParams = "entrypoint=activity-stream-firstrun&form_type=email";
+ const response = await fetch(
+ `${this.props.fxa_endpoint}/metrics-flow?${fxaParams}&${
+ this.utmParams
+ }`,
+ { credentials: "omit" }
+ );
+ if (response.status === 200) {
+ const { deviceId, flowId, flowBeginTime } = await response.json();
+ this.setState({ deviceId, flowId, flowBeginTime });
+ } else {
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_FETCH_ERROR",
+ value: response.status,
+ },
+ })
+ );
+ }
+ } catch (error) {
+ this.props.dispatch(
+ ac.OnlyToMain({
+ type: at.TELEMETRY_UNDESIRED_EVENT,
+ data: { event: "FXA_METRICS_ERROR" },
+ })
+ );
+ }
+ }
+ }
+
+ async componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
+
+ await this.componentWillUpdate(this.props);
}
componentDidMount() {
+ this.initScene();
+ }
+
+ initScene() {
// Timeout to allow the scene to render once before attaching the attribute
// to trigger the animation.
setTimeout(() => {
this.setState({ show: true });
+ this.props.onReady();
}, 10);
}
@@ -39,11 +98,11 @@ export class StartupOverlay extends React.PureComponent {
window.removeEventListener("visibilitychange", this.removeOverlay);
document.body.classList.remove("hide-main", "fxa");
this.setState({ show: false });
-
+ this.props.onBlock();
setTimeout(() => {
// Allow scrolling and fully remove overlay after animation finishes.
- this.props.onBlock();
document.body.classList.remove("welcome");
+ this.setState({ overlayRemoved: true });
}, 400);
}
@@ -73,9 +132,7 @@ export class StartupOverlay extends React.PureComponent {
* Report to telemetry additional information about the form submission.
*/
_getFormInfo() {
- const value = {
- has_flow_params: this.props.flowParams.flowId.length > 0,
- };
+ const value = { has_flow_params: this.state.flowId.length > 0 };
return { value };
}
@@ -88,6 +145,12 @@ export class StartupOverlay extends React.PureComponent {
}
render() {
+ // When skipping the onboarding tour we show AS but we are still on
+ // about:welcome, prop.isFirstrun is true and StartupOverlay is rendered
+ if (this.state.overlayRemoved) {
+ return null;
+ }
+
return (
@@ -150,17 +213,13 @@ export class StartupOverlay extends React.PureComponent {
-
+
({ fxa_endpoint: state.Prefs.values.fxa_endpoint });
+export const StartupOverlay = connect(getState)(_StartupOverlay);
diff --git a/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx b/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
index 1457e955cba3..f991e8f787fd 100644
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
@@ -2,11 +2,18 @@
* 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/. */
-import { actionCreators as ac } from "common/Actions.jsm";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
import { ModalOverlayWrapper } from "../../components/ModalOverlay/ModalOverlay";
-import { addUtmParams } from "../FirstRun/addUtmParams";
+import { OnboardingCard } from "../OnboardingMessage/OnboardingMessage";
import React from "react";
+const FLUENT_FILES = [
+ "branding/brand.ftl",
+ "browser/branding/brandings.ftl",
+ "browser/branding/sync-brand.ftl",
+ "browser/newtab/onboarding.ftl",
+];
+
// From resource://devtools/client/shared/focus.js
const FOCUSABLE_SELECTOR = [
"a[href]:not([tabindex='-1'])",
@@ -22,35 +29,106 @@ export class Trailhead extends React.PureComponent {
constructor(props) {
super(props);
this.closeModal = this.closeModal.bind(this);
+ this.hideCardPanel = this.hideCardPanel.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.onStartBlur = this.onStartBlur.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onInputInvalid = this.onInputInvalid.bind(this);
+ this.onCardAction = this.onCardAction.bind(this);
this.state = {
emailInput: "",
+ isModalOpen: true,
+ showCardPanel: true,
+ showCards: false,
+ // The params below are for FxA metrics
+ deviceId: "",
+ flowId: "",
+ flowBeginTime: 0,
};
+ this.fxaMetricsInitialized = false;
}
get dialog() {
return this.props.document.getElementById("trailheadDialog");
}
+ async componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
+
+ await this.componentWillUpdate(this.props);
+ }
+
+ // Get the fxa data if we don't have it yet from mount or update
+ async componentWillUpdate(props) {
+ if (props.fxaEndpoint && !this.fxaMetricsInitialized) {
+ try {
+ this.fxaMetricsInitialized = true;
+ const url = new URL(
+ `${
+ props.fxaEndpoint
+ }/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`
+ );
+ this.addUtmParams(url);
+ const response = await fetch(url, { credentials: "omit" });
+ if (response.status === 200) {
+ const { deviceId, flowId, flowBeginTime } = await response.json();
+ this.setState({ deviceId, flowId, flowBeginTime });
+ } else {
+ props.dispatch(
+ ac.OnlyToMain({
+ type: at.TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_FETCH_ERROR",
+ value: response.status,
+ },
+ })
+ );
+ }
+ } catch (error) {
+ props.dispatch(
+ ac.OnlyToMain({
+ type: at.TELEMETRY_UNDESIRED_EVENT,
+ data: { event: "FXA_METRICS_ERROR" },
+ })
+ );
+ }
+ }
+ }
+
componentDidMount() {
// We need to remove hide-main since we should show it underneath everything that has rendered
this.props.document.body.classList.remove("hide-main");
- // The rest of the page is "hidden" to screen readers when the modal is open
- this.props.document
- .getElementById("root")
- .setAttribute("aria-hidden", "true");
- // Start with focus in the email input box
- const input = this.dialog.querySelector("input[name=email]");
- if (input) {
- input.focus();
+ // Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
+ this.props.document.body.classList.add("inline-onboarding");
+
+ // The rest of the page is "hidden" when the modal is open
+ if (this.props.message.content) {
+ this.props.document
+ .getElementById("root")
+ .setAttribute("aria-hidden", "true");
+
+ // Start with focus in the email input box
+ this.dialog.querySelector("input[name=email]").focus();
+ } else {
+ // No modal overlay, let the user scroll and deal them some cards.
+ this.props.document.body.classList.remove("welcome");
+
+ if (this.props.message.includeBundle || this.props.message.cards) {
+ this.revealCards();
+ }
}
}
+ componentWillUnmount() {
+ this.props.document.body.classList.remove("inline-onboarding");
+ }
+
onInputChange(e) {
let error = e.target.previousSibling;
this.setState({ emailInput: e.target.value });
@@ -94,7 +172,8 @@ export class Trailhead extends React.PureComponent {
global.removeEventListener("visibilitychange", this.closeModal);
this.props.document.body.classList.remove("welcome");
this.props.document.getElementById("root").removeAttribute("aria-hidden");
- this.props.onNextScene();
+ this.setState({ isModalOpen: false });
+ this.revealCards();
// If closeModal() was triggered by a visibilitychange event, the user actually
// submitted the email form so we don't send a SKIPPED_SIGNIN ping.
@@ -112,7 +191,7 @@ export class Trailhead extends React.PureComponent {
* Report to telemetry additional information about the form submission.
*/
_getFormInfo() {
- const value = { has_flow_params: this.props.flowParams.flowId.length > 0 };
+ const value = { has_flow_params: this.state.flowId.length > 0 };
return { value };
}
@@ -124,141 +203,234 @@ export class Trailhead extends React.PureComponent {
e.target.focus();
}
+ hideCardPanel() {
+ this.setState({ showCardPanel: false });
+ this.props.onDismissBundle();
+ }
+
+ revealCards() {
+ this.setState({ showCards: true });
+ }
+
+ /**
+ * 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).
+ */
+ addUtmParams(url, isCard = false) {
+ let returnUrl = url;
+ if (typeof returnUrl === "string") {
+ returnUrl = new URL(url);
+ }
+ returnUrl.searchParams.append("utm_source", "activity-stream");
+ returnUrl.searchParams.append("utm_campaign", "firstrun");
+ returnUrl.searchParams.append("utm_medium", "referral");
+ returnUrl.searchParams.append(
+ "utm_term",
+ `${this.props.message.utm_term}${isCard ? "-card" : ""}`
+ );
+ return returnUrl;
+ }
+
+ onCardAction(action) {
+ let actionUpdates = {};
+
+ if (action.type === "OPEN_URL") {
+ let url = new URL(action.data.args);
+ this.addUtmParams(url, true);
+
+ if (action.addFlowParams) {
+ url.searchParams.append("device_id", this.state.deviceId);
+ url.searchParams.append("flow_id", this.state.flowId);
+ url.searchParams.append("flow_begin_time", this.state.flowBeginTime);
+ }
+
+ actionUpdates = { data: { ...action.data, args: url } };
+ }
+
+ this.props.onAction({ ...action, ...actionUpdates });
+ }
+
render() {
const { props } = this;
- const { UTMTerm } = props;
- const { content } = props.message;
+ const { bundle: cards, content, utm_term } = props.message;
const innerClassName = ["trailhead", content && content.className]
.filter(v => v)
.join(" ");
return (
-
-
-
-
- {content.subtitle && (
-
- )}
-
- {content.benefits.map(item => (
-
-
-
-
- ))}
-
-
-
-
+ {this.state.isModalOpen && content ? (
+
-
-
-
+ onSubmit={this.onSubmit}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+ ) : null}
+ {cards && cards.length ? (
+
+
+
+
+ {cards.map(card => (
+
+ ))}
+
+ {this.state.showCardPanel && (
+
+ )}
+
+
+ ) : null}
+ >
);
}
}
-
-Trailhead.defaultProps = {
- flowParams: { deviceId: "", flowId: "", flowBeginTime: "" },
-};
diff --git a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
index 45053f7bcffb..d2b75b70cd7c 100644
--- a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
+++ b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
@@ -119,7 +119,6 @@ export class CollapsibleSection extends React.PureComponent {
onKeyPress(event) {
if (event.key === "Enter" || event.key === " ") {
- event.preventDefault();
this.onHeaderClick();
}
}
@@ -224,13 +223,16 @@ export class CollapsibleSection extends React.PureComponent {
>
{this.renderIcon()}
+
+
{isCollapsible && (
-
+
+
{this.props.options.map((option, i) =>
option.type === "separator" ? (
-
+
) : (
option.type !== "empty" && (
)
)
@@ -76,7 +77,6 @@ export class ContextMenuItem extends React.PureComponent {
super(props);
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
this.focusFirst = this.focusFirst.bind(this);
}
@@ -129,8 +129,6 @@ export class ContextMenuItem extends React.PureComponent {
this.focusSibling(event.target, event.key);
break;
case "Enter":
- case " ":
- event.preventDefault();
this.props.hideContext();
option.onClick();
break;
@@ -140,24 +138,15 @@ export class ContextMenuItem extends React.PureComponent {
}
}
- // Prevents the default behavior of spacebar
- // scrolling the page & auto-triggering buttons.
- onKeyUp(event) {
- if (event.key === " ") {
- event.preventDefault();
- }
- }
-
render() {
const { option } = this.props;
return (
-
+
{option.icon && (
diff --git a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
index 0364f5386af9..56a9e2de9e63 100644
--- a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
+++ b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
@@ -32,7 +32,7 @@ export class ContextMenuButton extends React.PureComponent {
}
onKeyDown(event) {
- if (event.key === "Enter" || event.key === " ") {
+ if (event.key === "Enter") {
event.preventDefault();
this.openContextMenu(true, event);
}
diff --git a/browser/components/newtab/css/activity-stream-linux.css b/browser/components/newtab/css/activity-stream-linux.css
index 9144ebb35350..3c4ac9371132 100644
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -3421,6 +3421,20 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.submissionStatus .submitStatusTitle {
font-size: 20px; }
+.onboardingMessageContainer {
+ display: grid;
+ grid-column-gap: 21px;
+ grid-template-columns: auto auto auto;
+ padding-left: 30px;
+ padding-right: 30px;
+ min-height: 500px; }
+ @media (max-width: 850px) {
+ .onboardingMessageContainer {
+ grid-template-columns: none;
+ grid-template-rows: auto auto auto;
+ padding-left: 110px;
+ padding-right: 110px; } }
+
.onboardingMessage {
height: 340px;
text-align: center;
diff --git a/browser/components/newtab/css/activity-stream-mac.css b/browser/components/newtab/css/activity-stream-mac.css
index f4b25966640d..71f20ae0bc2c 100644
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -3424,6 +3424,20 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.submissionStatus .submitStatusTitle {
font-size: 20px; }
+.onboardingMessageContainer {
+ display: grid;
+ grid-column-gap: 21px;
+ grid-template-columns: auto auto auto;
+ padding-left: 30px;
+ padding-right: 30px;
+ min-height: 500px; }
+ @media (max-width: 850px) {
+ .onboardingMessageContainer {
+ grid-template-columns: none;
+ grid-template-rows: auto auto auto;
+ padding-left: 110px;
+ padding-right: 110px; } }
+
.onboardingMessage {
height: 340px;
text-align: center;
diff --git a/browser/components/newtab/css/activity-stream-windows.css b/browser/components/newtab/css/activity-stream-windows.css
index 7ea2934dfdbd..b95a4bcf7006 100644
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -3421,6 +3421,20 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.submissionStatus .submitStatusTitle {
font-size: 20px; }
+.onboardingMessageContainer {
+ display: grid;
+ grid-column-gap: 21px;
+ grid-template-columns: auto auto auto;
+ padding-left: 30px;
+ padding-right: 30px;
+ min-height: 500px; }
+ @media (max-width: 850px) {
+ .onboardingMessageContainer {
+ grid-template-columns: none;
+ grid-template-rows: auto auto auto;
+ padding-left: 110px;
+ padding-right: 110px; } }
+
.onboardingMessage {
height: 340px;
text-align: center;
diff --git a/browser/components/newtab/data/content/activity-stream.bundle.js b/browser/components/newtab/data/content/activity-stream.bundle.js
index b1e61ad7811a..b4044a4ab753 100644
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -93,15 +93,15 @@
__webpack_require__.r(__webpack_exports__);
/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony import */ var content_src_components_Base_Base__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
-/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(51);
+/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(49);
/* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(12);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(14);
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(56);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(54);
/* 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/. */
@@ -562,15 +562,15 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony import */ var content_src_components_ASRouterAdmin_ASRouterAdmin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
/* harmony import */ var _asrouter_asrouter_content__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
-/* harmony import */ var content_src_components_ConfirmDialog_ConfirmDialog__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(29);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(27);
+/* harmony import */ var content_src_components_ConfirmDialog_ConfirmDialog__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(52);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34);
+/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(50);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(32);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_7__);
-/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(50);
-/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(39);
+/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(48);
+/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(37);
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
@@ -758,12 +758,12 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterAdmin", function() { return ASRouterAdmin; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony import */ var _asrouter_asrouter_content__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(21);
+/* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var _SimpleHashRouter__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(28);
+/* harmony import */ var _SimpleHashRouter__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(26);
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
@@ -1719,16 +1719,19 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterUISurface", function() { return ASRouterUISurface; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6);
-/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55);
+/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(53);
/* harmony import */ var _components_ImpressionsWrapper_ImpressionsWrapper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53);
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(51);
/* harmony import */ var content_src_lib_constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(9);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(12);
-/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_7__);
-/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(54);
-/* harmony import */ var _templates_FirstRun_FirstRun__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(58);
+/* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(12);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_7__);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(14);
+/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_8__);
+/* harmony import */ var _templates_ReturnToAMO_ReturnToAMO__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(15);
+/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(52);
+/* harmony import */ var _templates_StartupOverlay_StartupOverlay__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(23);
+/* harmony import */ var _templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(25);
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
@@ -1744,10 +1747,12 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
+
+
+
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
-const TEMPLATES_ABOVE_PAGE = ["trailhead", "fxa_overlay", "return_to_amo_overlay"];
-const FIRST_RUN_TEMPLATES = TEMPLATES_ABOVE_PAGE;
+const TEMPLATES_ABOVE_PAGE = ["trailhead"];
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
const ASRouterUtils = {
addListener(listener) {
@@ -1787,6 +1792,15 @@ const ASRouterUtils = {
});
},
+ dismissBundle(bundle) {
+ ASRouterUtils.sendMessage({
+ type: "DISMISS_BUNDLE",
+ data: {
+ bundle
+ }
+ });
+ },
+
executeAction(button_action) {
ASRouterUtils.sendMessage({
type: "USER_ACTION",
@@ -1855,7 +1869,7 @@ function shouldSendImpressionOnUpdate(nextProps, prevProps) {
return nextProps.message.id && (!prevProps.message || prevProps.message.id !== nextProps.message.id);
}
-class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.PureComponent {
+class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_7___default.a.PureComponent {
constructor(props) {
super(props);
this.onMessageFromParent = this.onMessageFromParent.bind(this);
@@ -1863,7 +1877,8 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
this.sendImpression = this.sendImpression.bind(this);
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
this.state = {
- message: {}
+ message: {},
+ bundle: {}
};
if (props.document) {
@@ -1874,11 +1889,18 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
sendUserActionTelemetry(extraProps = {}) {
const {
- message
+ message,
+ bundle
} = this.state;
- const eventType = `${message.provider}_user_event`;
+
+ if (!message && !extraProps.message_id) {
+ throw new Error(`You must provide a message_id for bundled messages`);
+ } // snippets_user_event, onboarding_user_event
+
+
+ const eventType = `${message.provider || bundle.provider}_user_event`;
ASRouterUtils.sendTelemetry({
- message_id: message.id,
+ message_id: message.id || extraProps.message_id,
source: extraProps.id,
action: eventType,
...extraProps
@@ -1941,6 +1963,30 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
return () => ASRouterUtils.dismissById(id);
}
+ dismissBundle(bundle) {
+ return () => {
+ ASRouterUtils.dismissBundle(bundle);
+ this.sendUserActionTelemetry({
+ event: "DISMISS",
+ id: "onboarding-cards",
+ message_id: bundle.map(m => m.id).join(","),
+ // Passing the action because some bundles (Trailhead) don't have a provider set
+ action: "onboarding_user_event"
+ });
+ };
+ }
+
+ triggerOnboarding() {
+ ASRouterUtils.sendMessage({
+ type: "TRIGGER",
+ data: {
+ trigger: {
+ id: "showOnboarding"
+ }
+ }
+ });
+ }
+
clearMessage(id) {
if (id === this.state.message.id) {
this.setState({
@@ -1961,6 +2007,12 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
});
break;
+ case "SET_BUNDLED_MESSAGES":
+ this.setState({
+ bundle: action.data
+ });
+ break;
+
case "CLEAR_MESSAGE":
this.clearMessage(action.data.id);
break;
@@ -1974,9 +2026,19 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
break;
+ case "CLEAR_BUNDLE":
+ if (this.state.bundle.bundle) {
+ this.setState({
+ bundle: {}
+ });
+ }
+
+ break;
+
case "CLEAR_ALL":
this.setState({
- message: {}
+ message: {},
+ bundle: {}
});
break;
@@ -2023,28 +2085,24 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
}
renderSnippets() {
- const {
- message
- } = this.state;
-
- if (!_templates_template_manifest__WEBPACK_IMPORTED_MODULE_8__["SnippetsTemplates"][message.template]) {
+ if (this.state.bundle.template === "onboarding" || ["fxa_overlay", "return_to_amo_overlay", "trailhead", "whatsnew_panel_message"].includes(this.state.message.template)) {
return null;
}
- const SnippetComponent = _templates_template_manifest__WEBPACK_IMPORTED_MODULE_8__["SnippetsTemplates"][message.template];
+ const SnippetComponent = _templates_template_manifest__WEBPACK_IMPORTED_MODULE_10__["SnippetsTemplates"][this.state.message.template];
const {
content
} = this.state.message;
- return react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(_components_ImpressionsWrapper_ImpressionsWrapper__WEBPACK_IMPORTED_MODULE_3__["ImpressionsWrapper"], {
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(_components_ImpressionsWrapper_ImpressionsWrapper__WEBPACK_IMPORTED_MODULE_3__["ImpressionsWrapper"], {
id: "NEWTAB_FOOTER_BAR",
message: this.state.message,
sendImpression: this.sendImpression,
shouldSendImpressionOnUpdate: shouldSendImpressionOnUpdate // This helps with testing
,
document: this.props.document
- }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_4__["LocalizationProvider"], {
+ }, react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_4__["LocalizationProvider"], {
bundles: Object(_rich_text_strings__WEBPACK_IMPORTED_MODULE_2__["generateBundles"])(content)
- }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(SnippetComponent, _extends({}, this.state.message, {
+ }, react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(SnippetComponent, _extends({}, this.state.message, {
UISurface: "NEWTAB_FOOTER_BAR",
onBlock: this.onBlockById(this.state.message.id),
onDismiss: this.onDismissById(this.state.message.id),
@@ -2054,31 +2112,62 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
}))));
}
- renderPreviewBanner() {
- if (this.state.message.provider !== "preview") {
- return null;
+ renderOnboarding() {
+ if (this.state.bundle.template === "onboarding") {
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_6__["OnboardingMessage"], _extends({}, this.state.bundle, {
+ UISurface: "NEWTAB_OVERLAY",
+ onAction: ASRouterUtils.executeAction,
+ onDismissBundle: this.dismissBundle(this.state.bundle.bundle),
+ sendUserActionTelemetry: this.sendUserActionTelemetry
+ }));
}
- return react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
- className: "snippets-preview-banner"
- }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("span", {
- className: "icon icon-small-spacer icon-info"
- }), react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("span", null, "Preview Purposes Only"));
+ return null;
}
- renderFirstRun() {
+ renderFirstRunOverlay() {
const {
message
} = this.state;
- if (FIRST_RUN_TEMPLATES.includes(message.template)) {
- return react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(_templates_FirstRun_FirstRun__WEBPACK_IMPORTED_MODULE_9__["FirstRun"], {
+ if (message.template === "fxa_overlay") {
+ global.document.body.classList.add("fxa");
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(_templates_StartupOverlay_StartupOverlay__WEBPACK_IMPORTED_MODULE_11__["StartupOverlay"], {
+ onReady: this.triggerOnboarding,
+ onBlock: this.onDismissById(message.id),
+ dispatch: this.props.dispatch
+ });
+ } else if (message.template === "return_to_amo_overlay") {
+ global.document.body.classList.add("amo");
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_4__["LocalizationProvider"], {
+ bundles: Object(_rich_text_strings__WEBPACK_IMPORTED_MODULE_2__["generateBundles"])({
+ amo_html: message.content.text
+ })
+ }, react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(_templates_ReturnToAMO_ReturnToAMO__WEBPACK_IMPORTED_MODULE_9__["ReturnToAMO"], _extends({}, message, {
+ UISurface: "NEWTAB_OVERLAY",
+ onReady: this.triggerOnboarding,
+ onBlock: this.onDismissById(message.id),
+ onAction: ASRouterUtils.executeAction,
+ sendUserActionTelemetry: this.sendUserActionTelemetry
+ })));
+ }
+
+ return null;
+ }
+
+ renderTrailhead() {
+ const {
+ message
+ } = this.state;
+
+ if (message.template === "trailhead") {
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(_templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_12__["Trailhead"], {
document: this.props.document,
message: message,
+ onAction: ASRouterUtils.executeAction,
+ onDismissBundle: this.dismissBundle(this.state.message.bundle),
sendUserActionTelemetry: this.sendUserActionTelemetry,
- executeAction: ASRouterUtils.executeAction,
dispatch: this.props.dispatch,
- onDismiss: this.onDismissById(this.state.message.id),
fxaEndpoint: this.props.fxaEndpoint
});
}
@@ -2086,23 +2175,36 @@ class ASRouterUISurface extends react__WEBPACK_IMPORTED_MODULE_6___default.a.Pur
return null;
}
+ renderPreviewBanner() {
+ if (this.state.message.provider !== "preview") {
+ return null;
+ }
+
+ return react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement("div", {
+ className: "snippets-preview-banner"
+ }, react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement("span", {
+ className: "icon icon-small-spacer icon-info"
+ }), react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement("span", null, "Preview Purposes Only"));
+ }
+
render() {
const {
- message
+ message,
+ bundle
} = this.state;
- if (!message.id) {
+ if (!message.id && !bundle.template) {
return null;
}
const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(message.template);
const shouldRenderInHeader = TEMPLATES_ABOVE_PAGE.includes(message.template);
return shouldRenderBelowSearch ? // Render special below search snippets in place;
- react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
+ react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement("div", {
className: "below-search-snippet"
}, this.renderSnippets()) : // For onboarding, regular snippets etc. we should render
// everything in our footer container.
- react_dom__WEBPACK_IMPORTED_MODULE_7___default.a.createPortal(react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_6___default.a.Fragment, null, this.renderPreviewBanner(), this.renderFirstRun(), this.renderSnippets()), shouldRenderInHeader ? this.headerPortal : this.footerPortal);
+ react_dom__WEBPACK_IMPORTED_MODULE_8___default.a.createPortal(react__WEBPACK_IMPORTED_MODULE_7___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_7___default.a.Fragment, null, this.renderPreviewBanner(), this.renderTrailhead(), this.renderFirstRunOverlay(), this.renderOnboarding(), this.renderSnippets()), shouldRenderInHeader ? this.headerPortal : this.footerPortal);
}
}
@@ -2415,29 +2517,15 @@ const NEWTAB_DARK_THEME = {
/***/ }),
/* 12 */
-/***/ (function(module, exports) {
-
-module.exports = ReactDOM;
-
-/***/ }),
-/* 13 */
-/***/ (function(module) {
-
-module.exports = {"title":"EOYSnippet","description":"Fundraising Snippet","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"donation_form_url":{"type":"string","description":"Url to the donation form."},"currency_code":{"type":"string","description":"The code for the currency. Examle gbp, cad, usd.","default":"usd"},"locale":{"type":"string","description":"String for the locale code.","default":"en-US"},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"text_color":{"type":"string","description":"Modify the text message color"},"background_color":{"type":"string","description":"Snippet background color."},"highlight_color":{"type":"string","description":"Paragraph em highlight color."},"donation_amount_first":{"type":"number","description":"First button amount."},"donation_amount_second":{"type":"number","description":"Second button amount."},"donation_amount_third":{"type":"number","description":"Third button amount."},"donation_amount_fourth":{"type":"number","description":"Fourth button amount."},"selected_button":{"type":"string","description":"Default donation_amount_second. Donation amount button that's selected by default.","default":"donation_amount_second"},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"icon_alt_text":{"type":"string","description":"Alt text for accessibility","default":""},"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button."},"monthly_checkbox_label_text":{"type":"string","description":"Label text for monthly checkbox.","default":"Make my donation monthly"},"test":{"type":"string","description":"Different styles for the snippet. Options are bold and takeover."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}}},"additionalProperties":false,"required":["text","donation_form_url","donation_amount_first","donation_amount_second","donation_amount_third","donation_amount_fourth","button_label","currency_code"],"dependencies":{"button_color":["button_label"],"button_background_color":["button_label"]}};
-
-/***/ }),
-/* 14 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "convertLinks", function() { return convertLinks; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RichText", function() { return RichText; });
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(53);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingCard", function() { return OnboardingCard; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingMessage", function() { return OnboardingMessage; });
+/* harmony import */ var _components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(13);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55);
-/* harmony import */ var _template_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(15);
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
@@ -2445,243 +2533,60 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-
- // Elements allowed in snippet content
-
-const ALLOWED_TAGS = {
- b: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("b", null),
- i: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("i", null),
- u: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("u", null),
- strong: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("strong", null),
- em: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("em", null),
- br: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("br", null)
-};
-/**
- * Transform an object (tag name: {url}) into (tag name: anchor) where the url
- * is used as href, in order to render links inside a Fluent.Localized component.
- */
-
-function convertLinks(links, sendClick, doNotAutoBlock, openNewWindow = false) {
- if (links) {
- return Object.keys(links).reduce((acc, linkTag) => {
- const {
- action
- } = links[linkTag]; // Setting the value to false will not include the attribute in the anchor
-
- const url = action ? false : Object(_template_utils__WEBPACK_IMPORTED_MODULE_3__["safeURI"])(links[linkTag].url);
- acc[linkTag] = // eslint was getting a false positive caused by the dynamic injection
- // of content.
- // eslint-disable-next-line jsx-a11y/anchor-has-content
- react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
- href: url,
- target: openNewWindow ? "_blank" : "",
- "data-metric": links[linkTag].metric,
- "data-action": action,
- "data-args": links[linkTag].args,
- "data-do_not_autoblock": doNotAutoBlock,
- onClick: sendClick
- });
- return acc;
- }, {});
- }
-
- return null;
-}
-/**
- * Message wrapper used to sanitize markup and render HTML.
- */
-
-function RichText(props) {
- if (!_rich_text_strings__WEBPACK_IMPORTED_MODULE_2__["RICH_TEXT_KEYS"].includes(props.localization_id)) {
- throw new Error(`ASRouter: ${props.localization_id} is not a valid rich text property. If you want it to be processed, you need to add it to asrouter/rich-text-strings.js`);
- }
-
- return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_0__["Localized"], _extends({
- id: props.localization_id
- }, ALLOWED_TAGS, props.customElements, convertLinks(props.links, props.sendClick, props.doNotAutoBlock, props.openNewWindow)), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", null, props.text));
-}
-
-/***/ }),
-/* 15 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "safeURI", function() { return safeURI; });
-/* 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/. */
-function safeURI(url) {
- if (!url) {
- return "";
- }
-
- const {
- protocol
- } = new URL(url);
- const isAllowed = ["http:", "https:", "data:", "resource:", "chrome:"].includes(protocol);
-
- if (!isAllowed) {
- console.warn(`The protocol ${protocol} is not allowed for template URLs.`); // eslint-disable-line no-console
- }
-
- return isAllowed ? url : "";
-}
-
-/***/ }),
-/* 16 */
-/***/ (function(module) {
-
-module.exports = {"title":"SimpleSnippet","description":"A simple template with an icon, text, and optional button.","version":"1.1.1","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred."},"icon_alt_text":{"type":"string","description":"Alt text describing icon for screen readers","default":""},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_alt_text":{"type":"string","description":"Alt text describing title icon for screen readers","default":""},"button_action":{"type":"string","description":"The type of action the button should trigger."},"button_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, button_label links to this"}]},"button_action_args":{"type":"string","description":"Additional parameters for button action, example which specific menu the button should open"},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button.","default":"Remove this"},"tall":{"type":"boolean","description":"To be used by fundraising only, increases height to roughly 120px. Defaults to false."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}},"section_title_icon":{"type":"string","description":"Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_icon_dark_theme":{"type":"string","description":"Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_text":{"type":"string","description":"Section title text. section_title_icon must also be specified to display."},"section_title_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, section_title_text links to this"}]}},"additionalProperties":false,"required":["text"],"dependencies":{"button_action":["button_label"],"button_url":["button_label"],"button_color":["button_label"],"button_background_color":["button_label"],"section_title_url":["section_title_text"]}};
-
-/***/ }),
-/* 17 */
-/***/ (function(module) {
-
-module.exports = {"title":"FXASignupSnippet","description":"A snippet template for FxA sign up/sign in","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"number","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
-
-/***/ }),
-/* 18 */
-/***/ (function(module) {
-
-module.exports = {"title":"NewsletterSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_privacy_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"fmt":{"type":"string","description":"","default":"H"}}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"scene2_newsletter":{"type":"string","description":"Newsletter/basket id user is subscribing to.","default":"mozilla-foundation"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
-
-/***/ }),
-/* 19 */
-/***/ (function(module) {
-
-module.exports = {"title":"SendToDeviceSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"country":{"type":"string","description":"Two character string for the country code (used for SMS)","default":"us"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene2_icon":{"type":"string","description":"(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."},"scene2_icon_dark_theme":{"type":"string","description":"(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Send"},"scene2_input_placeholder":{"type":"string","description":"(send to device) Value to show while input is empty.","default":"Your email here"},"scene2_disclaimer_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"string","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_title":{"type":"string","description":"(send to device) Title shown before text on successful registration."},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"include_sms":{"type":"boolean","description":"(send to device) Allow users to send an SMS message with the form?","default":false},"message_id_sms":{"type":"string","description":"(send to device) Newsletter/basket id representing the SMS message to be sent."},"message_id_email":{"type":"string","description":"(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
-
-/***/ }),
-/* 20 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Trailhead", function() { return Trailhead; });
-/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21);
-/* harmony import */ var _FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(22);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* 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/. */
-
-
-
- // From resource://devtools/client/shared/focus.js
-
-const FOCUSABLE_SELECTOR = ["a[href]:not([tabindex='-1'])", "button:not([disabled]):not([tabindex='-1'])", "iframe:not([tabindex='-1'])", "input:not([disabled]):not([tabindex='-1'])", "select:not([disabled]):not([tabindex='-1'])", "textarea:not([disabled]):not([tabindex='-1'])", "[tabindex]:not([tabindex='-1'])"].join(", ");
-class Trailhead extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent {
+const FLUENT_FILES = ["branding/brand.ftl", "browser/branding/sync-brand.ftl", "browser/newtab/onboarding.ftl"];
+class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureComponent {
constructor(props) {
super(props);
- this.closeModal = this.closeModal.bind(this);
- this.onInputChange = this.onInputChange.bind(this);
- this.onStartBlur = this.onStartBlur.bind(this);
- this.onSubmit = this.onSubmit.bind(this);
- this.onInputInvalid = this.onInputInvalid.bind(this);
- this.state = {
- emailInput: ""
- };
+ this.onClick = this.onClick.bind(this);
}
- get dialog() {
- return this.props.document.getElementById("trailheadDialog");
- }
-
- componentDidMount() {
- // We need to remove hide-main since we should show it underneath everything that has rendered
- this.props.document.body.classList.remove("hide-main"); // The rest of the page is "hidden" to screen readers when the modal is open
-
- this.props.document.getElementById("root").setAttribute("aria-hidden", "true"); // Start with focus in the email input box
-
- const input = this.dialog.querySelector("input[name=email]");
-
- if (input) {
- input.focus();
- }
- }
-
- onInputChange(e) {
- let error = e.target.previousSibling;
- this.setState({
- emailInput: e.target.value
- });
- error.classList.remove("active");
- e.target.classList.remove("invalid");
- }
-
- onStartBlur(event) {
- // Make sure focus stays within the dialog when tabbing from the button
+ onClick() {
const {
- dialog
+ props
} = this;
-
- if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
- dialog.querySelector(FOCUSABLE_SELECTOR).focus();
- }
+ const ping = {
+ event: "CLICK_BUTTON",
+ message_id: props.id,
+ id: props.UISurface
+ };
+ props.sendUserActionTelemetry(ping);
+ props.onAction(props.content.primary_button.action);
}
- onSubmit(event) {
- // Dynamically require the email on submission so screen readers don't read
- // out it's always required because there's also ways to skip the modal
+ render() {
const {
- email
- } = event.target.elements;
-
- if (!email.value.length) {
- email.required = true;
- email.checkValidity();
- event.preventDefault();
- return;
- }
-
- this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
- event: "SUBMIT_EMAIL",
- ...this._getFormInfo()
- }));
- global.addEventListener("visibilitychange", this.closeModal);
+ content
+ } = this.props;
+ const className = this.props.className || "onboardingMessage";
+ return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ className: className
+ }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ className: `onboardingMessageImage ${content.icon}`
+ }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ className: "onboardingContent"
+ }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", null, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("h3", {
+ className: "onboardingTitle",
+ "data-l10n-id": content.title.string_id
+ }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("p", {
+ className: "onboardingText",
+ "data-l10n-id": content.text.string_id
+ })), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", {
+ className: "onboardingButtonContainer"
+ }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("button", {
+ "data-l10n-id": content.primary_button.label.string_id,
+ className: "button onboardingButton",
+ onClick: this.onClick
+ }))));
}
- closeModal(ev) {
- global.removeEventListener("visibilitychange", this.closeModal);
- this.props.document.body.classList.remove("welcome");
- this.props.document.getElementById("root").removeAttribute("aria-hidden");
- this.props.onNextScene(); // If closeModal() was triggered by a visibilitychange event, the user actually
- // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
-
- if (!ev || ev.type !== "visibilitychange") {
- this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
- event: "SKIPPED_SIGNIN",
- ...this._getFormInfo()
- }));
- } // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
-
-
- this.props.document.activeElement.blur();
- }
- /**
- * Report to telemetry additional information about the form submission.
- */
-
-
- _getFormInfo() {
- const value = {
- has_flow_params: this.props.flowParams.flowId.length > 0
- };
- return {
- value
- };
- }
-
- onInputInvalid(e) {
- let error = e.target.previousSibling;
- error.classList.add("active");
- e.target.classList.add("invalid");
- e.preventDefault(); // Override built-in form validation popup
-
- e.target.focus();
+}
+class OnboardingMessage extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureComponent {
+ componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
}
render() {
@@ -2689,147 +2594,26 @@ class Trailhead extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureCompone
props
} = this;
const {
- UTMTerm
- } = props;
- const {
- content
- } = props.message;
- const innerClassName = ["trailhead", content && content.className].filter(v => v).join(" ");
- return react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__["ModalOverlayWrapper"], {
- innerClassName: innerClassName,
- onClose: this.closeModal,
- id: "trailheadDialog",
- headerId: "trailheadHeader"
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
- className: "trailheadInner"
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
- className: "trailheadContent"
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
- "data-l10n-id": content.title.string_id,
- id: "trailheadHeader"
- }), content.subtitle && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
- "data-l10n-id": content.subtitle.string_id
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("ul", {
- className: "trailheadBenefits"
- }, content.benefits.map(item => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("li", {
- key: item.id,
- className: item.id
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h3", {
- "data-l10n-id": item.title.string_id
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
- "data-l10n-id": item.text.string_id
- })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
- className: "trailheadLearn",
- "data-l10n-id": content.learn.text.string_id,
- href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])(content.learn.url, UTMTerm),
- target: "_blank",
- rel: "noopener noreferrer"
- })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
- role: "group",
- "aria-labelledby": "joinFormHeader",
- "aria-describedby": "joinFormBody",
- className: "trailheadForm"
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h3", {
- id: "joinFormHeader",
- "data-l10n-id": content.form.title.string_id
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
- id: "joinFormBody",
- "data-l10n-id": content.form.text.string_id
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("form", {
- method: "get",
- action: this.props.fxaEndpoint,
- target: "_blank",
- rel: "noopener noreferrer",
- onSubmit: this.onSubmit
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "service",
- type: "hidden",
- value: "sync"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "action",
- type: "hidden",
- value: "email"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "context",
- type: "hidden",
- value: "fx_desktop_v3"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "entrypoint",
- type: "hidden",
- value: "activity-stream-firstrun"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "utm_source",
- type: "hidden",
- value: "activity-stream"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "utm_campaign",
- type: "hidden",
- value: "firstrun"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "utm_term",
- type: "hidden",
- value: UTMTerm
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "device_id",
- type: "hidden",
- value: this.props.flowParams.deviceId
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "flow_id",
- type: "hidden",
- value: this.props.flowParams.flowId
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "flow_begin_time",
- type: "hidden",
- value: this.props.flowParams.flowBeginTime
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- name: "style",
- type: "hidden",
- value: "trailhead"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
- "data-l10n-id": "onboarding-join-form-email-error",
- className: "error"
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
- "data-l10n-id": content.form.email.string_id,
- name: "email",
- type: "email",
- onInvalid: this.onInputInvalid,
- onChange: this.onInputChange
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
- className: "trailheadTerms",
- "data-l10n-id": "onboarding-join-form-legal"
- }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
- "data-l10n-name": "terms",
- target: "_blank",
- rel: "noopener noreferrer",
- href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])("https://accounts.firefox.com/legal/terms", UTMTerm)
- }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
- "data-l10n-name": "privacy",
- target: "_blank",
- rel: "noopener noreferrer",
- href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])("https://accounts.firefox.com/legal/privacy", UTMTerm)
- })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
- "data-l10n-id": content.form.button.string_id,
- type: "submit"
- })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
- className: "trailheadStart",
- "data-l10n-id": content.skipButton.string_id,
- onBlur: this.onStartBlur,
- onClick: this.closeModal
- }));
+ button_label,
+ header
+ } = props.extraTemplateStrings;
+ return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_0__["ModalOverlay"], _extends({}, props, {
+ button_label: button_label,
+ title: header
+ }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ className: "onboardingMessageContainer"
+ }, props.bundle.map(message => react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(OnboardingCard, _extends({
+ key: message.id,
+ sendUserActionTelemetry: props.sendUserActionTelemetry,
+ onAction: props.onAction,
+ UISurface: props.UISurface
+ }, message)))));
}
}
-Trailhead.defaultProps = {
- flowParams: {
- deviceId: "",
- flowId: "",
- flowBeginTime: ""
- }
-};
-/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 21 */
+/* 13 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -2922,51 +2706,21 @@ class ModalOverlay extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 22 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
+/* 14 */
+/***/ (function(module, exports) {
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* 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/. */
-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;
-}
+module.exports = ReactDOM;
/***/ }),
-/* 23 */
+/* 15 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReturnToAMO", function() { return ReturnToAMO; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReturnToAMO", function() { return ReturnToAMO; });
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _components_RichText_RichText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14);
+/* harmony import */ var _components_RichText_RichText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16);
/* 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/. */
@@ -2981,11 +2735,8 @@ class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCompo
this.onBlockButton = this.onBlockButton.bind(this);
}
- componentWillMount() {
- global.document.body.classList.add("amo");
- }
-
componentDidMount() {
+ this.props.onReady();
this.props.sendUserActionTelemetry({
event: "IMPRESSION",
id: this.props.UISurface
@@ -3051,49 +2802,241 @@ class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCompo
}
}
-/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 24 */
+/* 16 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StartupOverlay", function() { return StartupOverlay; });
-/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "convertLinks", function() { return convertLinks; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RichText", function() { return RichText; });
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
+/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(53);
+/* harmony import */ var _template_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(17);
+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 StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureComponent {
+
+ // Elements allowed in snippet content
+
+const ALLOWED_TAGS = {
+ b: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("b", null),
+ i: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("i", null),
+ u: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("u", null),
+ strong: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("strong", null),
+ em: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("em", null),
+ br: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("br", null)
+};
+/**
+ * Transform an object (tag name: {url}) into (tag name: anchor) where the url
+ * is used as href, in order to render links inside a Fluent.Localized component.
+ */
+
+function convertLinks(links, sendClick, doNotAutoBlock, openNewWindow = false) {
+ if (links) {
+ return Object.keys(links).reduce((acc, linkTag) => {
+ const {
+ action
+ } = links[linkTag]; // Setting the value to false will not include the attribute in the anchor
+
+ const url = action ? false : Object(_template_utils__WEBPACK_IMPORTED_MODULE_3__["safeURI"])(links[linkTag].url);
+ acc[linkTag] = // eslint was getting a false positive caused by the dynamic injection
+ // of content.
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+ react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
+ href: url,
+ target: openNewWindow ? "_blank" : "",
+ "data-metric": links[linkTag].metric,
+ "data-action": action,
+ "data-args": links[linkTag].args,
+ "data-do_not_autoblock": doNotAutoBlock,
+ onClick: sendClick
+ });
+ return acc;
+ }, {});
+ }
+
+ return null;
+}
+/**
+ * Message wrapper used to sanitize markup and render HTML.
+ */
+
+function RichText(props) {
+ if (!_rich_text_strings__WEBPACK_IMPORTED_MODULE_2__["RICH_TEXT_KEYS"].includes(props.localization_id)) {
+ throw new Error(`ASRouter: ${props.localization_id} is not a valid rich text property. If you want it to be processed, you need to add it to asrouter/rich-text-strings.js`);
+ }
+
+ return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_0__["Localized"], _extends({
+ id: props.localization_id
+ }, ALLOWED_TAGS, props.customElements, convertLinks(props.links, props.sendClick, props.doNotAutoBlock, props.openNewWindow)), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", null, props.text));
+}
+
+/***/ }),
+/* 17 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "safeURI", function() { return safeURI; });
+/* 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/. */
+function safeURI(url) {
+ if (!url) {
+ return "";
+ }
+
+ const {
+ protocol
+ } = new URL(url);
+ const isAllowed = ["http:", "https:", "data:", "resource:", "chrome:"].includes(protocol);
+
+ if (!isAllowed) {
+ console.warn(`The protocol ${protocol} is not allowed for template URLs.`); // eslint-disable-line no-console
+ }
+
+ return isAllowed ? url : "";
+}
+
+/***/ }),
+/* 18 */
+/***/ (function(module) {
+
+module.exports = {"title":"EOYSnippet","description":"Fundraising Snippet","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"donation_form_url":{"type":"string","description":"Url to the donation form."},"currency_code":{"type":"string","description":"The code for the currency. Examle gbp, cad, usd.","default":"usd"},"locale":{"type":"string","description":"String for the locale code.","default":"en-US"},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"text_color":{"type":"string","description":"Modify the text message color"},"background_color":{"type":"string","description":"Snippet background color."},"highlight_color":{"type":"string","description":"Paragraph em highlight color."},"donation_amount_first":{"type":"number","description":"First button amount."},"donation_amount_second":{"type":"number","description":"Second button amount."},"donation_amount_third":{"type":"number","description":"Third button amount."},"donation_amount_fourth":{"type":"number","description":"Fourth button amount."},"selected_button":{"type":"string","description":"Default donation_amount_second. Donation amount button that's selected by default.","default":"donation_amount_second"},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"icon_alt_text":{"type":"string","description":"Alt text for accessibility","default":""},"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button."},"monthly_checkbox_label_text":{"type":"string","description":"Label text for monthly checkbox.","default":"Make my donation monthly"},"test":{"type":"string","description":"Different styles for the snippet. Options are bold and takeover."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}}},"additionalProperties":false,"required":["text","donation_form_url","donation_amount_first","donation_amount_second","donation_amount_third","donation_amount_fourth","button_label","currency_code"],"dependencies":{"button_color":["button_label"],"button_background_color":["button_label"]}};
+
+/***/ }),
+/* 19 */
+/***/ (function(module) {
+
+module.exports = {"title":"SimpleSnippet","description":"A simple template with an icon, text, and optional button.","version":"1.1.1","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred."},"icon_alt_text":{"type":"string","description":"Alt text describing icon for screen readers","default":""},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_alt_text":{"type":"string","description":"Alt text describing title icon for screen readers","default":""},"button_action":{"type":"string","description":"The type of action the button should trigger."},"button_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, button_label links to this"}]},"button_action_args":{"type":"string","description":"Additional parameters for button action, example which specific menu the button should open"},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button.","default":"Remove this"},"tall":{"type":"boolean","description":"To be used by fundraising only, increases height to roughly 120px. Defaults to false."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}},"section_title_icon":{"type":"string","description":"Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_icon_dark_theme":{"type":"string","description":"Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_text":{"type":"string","description":"Section title text. section_title_icon must also be specified to display."},"section_title_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, section_title_text links to this"}]}},"additionalProperties":false,"required":["text"],"dependencies":{"button_action":["button_label"],"button_url":["button_label"],"button_color":["button_label"],"button_background_color":["button_label"],"section_title_url":["section_title_text"]}};
+
+/***/ }),
+/* 20 */
+/***/ (function(module) {
+
+module.exports = {"title":"FXASignupSnippet","description":"A snippet template for FxA sign up/sign in","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"number","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+
+/***/ }),
+/* 21 */
+/***/ (function(module) {
+
+module.exports = {"title":"NewsletterSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_privacy_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"fmt":{"type":"string","description":"","default":"H"}}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"scene2_newsletter":{"type":"string","description":"Newsletter/basket id user is subscribing to.","default":"mozilla-foundation"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+
+/***/ }),
+/* 22 */
+/***/ (function(module) {
+
+module.exports = {"title":"SendToDeviceSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"country":{"type":"string","description":"Two character string for the country code (used for SMS)","default":"us"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene2_icon":{"type":"string","description":"(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."},"scene2_icon_dark_theme":{"type":"string","description":"(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Send"},"scene2_input_placeholder":{"type":"string","description":"(send to device) Value to show while input is empty.","default":"Your email here"},"scene2_disclaimer_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"string","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_title":{"type":"string","description":"(send to device) Title shown before text on successful registration."},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"include_sms":{"type":"boolean","description":"(send to device) Allow users to send an SMS message with the form?","default":false},"message_id_sms":{"type":"string","description":"(send to device) Newsletter/basket id representing the SMS message to be sent."},"message_id_email":{"type":"string","description":"(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+
+/***/ }),
+/* 23 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_StartupOverlay", function() { return _StartupOverlay; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StartupOverlay", function() { return StartupOverlay; });
+/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(24);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
+/* 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 FLUENT_FILES = ["branding/brand.ftl", "browser/branding/sync-brand.ftl", "browser/newtab/onboarding.ftl"];
+class _StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_2___default.a.PureComponent {
constructor(props) {
super(props);
this.onInputChange = this.onInputChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.clickSkip = this.clickSkip.bind(this);
+ this.initScene = this.initScene.bind(this);
this.removeOverlay = this.removeOverlay.bind(this);
this.onInputInvalid = this.onInputInvalid.bind(this);
this.utmParams = "utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-control";
this.state = {
- show: false,
- emailInput: ""
+ emailInput: "",
+ overlayRemoved: false,
+ deviceId: "",
+ flowId: "",
+ flowBeginTime: 0
};
+ this.didFetch = false;
}
- componentWillMount() {
- global.document.body.classList.add("fxa");
+ async componentWillUpdate() {
+ if (this.props.fxa_endpoint && !this.didFetch) {
+ try {
+ this.didFetch = true;
+ const fxaParams = "entrypoint=activity-stream-firstrun&form_type=email";
+ const response = await fetch(`${this.props.fxa_endpoint}/metrics-flow?${fxaParams}&${this.utmParams}`, {
+ credentials: "omit"
+ });
+
+ if (response.status === 200) {
+ const {
+ deviceId,
+ flowId,
+ flowBeginTime
+ } = await response.json();
+ this.setState({
+ deviceId,
+ flowId,
+ flowBeginTime
+ });
+ } else {
+ this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
+ type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_FETCH_ERROR",
+ value: response.status
+ }
+ }));
+ }
+ } catch (error) {
+ this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
+ type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_ERROR"
+ }
+ }));
+ }
+ }
+ }
+
+ async componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
+ await this.componentWillUpdate(this.props);
}
componentDidMount() {
+ this.initScene();
+ }
+
+ initScene() {
// Timeout to allow the scene to render once before attaching the attribute
// to trigger the animation.
setTimeout(() => {
this.setState({
show: true
});
+ this.props.onReady();
}, 10);
}
@@ -3103,10 +3046,13 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
this.setState({
show: false
});
+ this.props.onBlock();
setTimeout(() => {
// Allow scrolling and fully remove overlay after animation finishes.
- this.props.onBlock();
document.body.classList.remove("welcome");
+ this.setState({
+ overlayRemoved: true
+ });
}, 400);
}
@@ -3141,7 +3087,7 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
_getFormInfo() {
const value = {
- has_flow_params: this.props.flowParams.flowId.length > 0
+ has_flow_params: this.state.flowId.length > 0
};
return {
value
@@ -3158,117 +3104,123 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
}
render() {
- return react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ // When skipping the onboarding tour we show AS but we are still on
+ // about:welcome, prop.isFirstrun is true and StartupOverlay is rendered
+ if (this.state.overlayRemoved) {
+ return null;
+ }
+
+ return react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: `overlay-wrapper ${this.state.show ? "show" : ""}`
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "background"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "firstrun-scene"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "fxaccounts-container"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "firstrun-left-divider"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("h1", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("h1", {
className: "firstrun-title",
"data-l10n-id": "onboarding-sync-welcome-header"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("p", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
className: "firstrun-content",
"data-l10n-id": "onboarding-sync-welcome-content"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("a", {
className: "firstrun-link",
href: `https://www.mozilla.org/firefox/features/sync/?${this.utmParams}`,
target: "_blank",
rel: "noopener noreferrer",
"data-l10n-id": "onboarding-sync-welcome-learn-more-link"
- })), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ })), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "firstrun-sign-in"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("p", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
className: "form-header"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("span", {
"data-l10n-id": "onboarding-sync-form-header"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("span", {
className: "sub-header",
"data-l10n-id": "onboarding-sync-form-sub-header"
- })), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("form", {
+ })), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("form", {
method: "get",
action: this.props.fxa_endpoint,
target: "_blank",
rel: "noopener noreferrer",
onSubmit: this.onSubmit
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "service",
type: "hidden",
value: "sync"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "action",
type: "hidden",
value: "email"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "context",
type: "hidden",
value: "fx_desktop_v3"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "entrypoint",
type: "hidden",
value: "activity-stream-firstrun"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "utm_source",
type: "hidden",
value: "activity-stream"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "utm_campaign",
type: "hidden",
value: "firstrun"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "utm_medium",
type: "hidden",
value: "referral"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "utm_term",
type: "hidden",
value: "trailhead-control"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "device_id",
type: "hidden",
- value: this.props.flowParams.deviceId
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ value: this.state.deviceId
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "flow_id",
type: "hidden",
- value: this.props.flowParams.flowId
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ value: this.state.flowId
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
name: "flow_begin_time",
type: "hidden",
- value: this.props.flowParams.flowBeginTime
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("span", {
+ value: this.state.flowBeginTime
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("span", {
className: "error",
"data-l10n-id": "onboarding-sync-form-invalid-input"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("input", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
className: "email-input",
name: "email",
type: "email",
- required: true,
+ required: "true",
onInvalid: this.onInputInvalid,
onChange: this.onInputChange,
"data-l10n-id": "onboarding-sync-form-input"
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("div", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
className: "extra-links"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("p", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
"data-l10n-id": "onboarding-sync-legal-notice"
- }, react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
+ }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("a", {
"data-l10n-name": "terms",
target: "_blank",
rel: "noopener noreferrer",
href: `${this.props.fxa_endpoint}/legal/terms?${this.utmParams}`
- }), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
+ }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("a", {
"data-l10n-name": "privacy",
target: "_blank",
rel: "noopener noreferrer",
href: `${this.props.fxa_endpoint}/legal/privacy?${this.utmParams}`
- }))), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("button", {
+ }))), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("button", {
className: "continue-button",
type: "submit",
"data-l10n-id": "onboarding-sync-form-continue-button"
- })), react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("button", {
+ })), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("button", {
className: "skip-button",
disabled: !!this.state.emailInput,
onClick: this.clickSkip,
@@ -3277,14 +3229,18 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
}
}
-StartupOverlay.defaultProps = {
- flowParams: {
- deviceId: "",
- flowId: "",
- flowBeginTime: ""
- }
-};
-/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
+
+const getState = state => ({
+ fxa_endpoint: state.Prefs.values.fxa_endpoint
+});
+
+const StartupOverlay = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(getState)(_StartupOverlay);
+
+/***/ }),
+/* 24 */
+/***/ (function(module, exports) {
+
+module.exports = ReactRedux;
/***/ }),
/* 25 */
@@ -3292,11 +3248,12 @@ StartupOverlay.defaultProps = {
"use strict";
__webpack_require__.r(__webpack_exports__);
-/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Triplets", function() { return Triplets; });
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
-/* harmony import */ var _addUtmParams__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(22);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Trailhead", function() { return Trailhead; });
+/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
+/* harmony import */ var _components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13);
+/* harmony import */ var _OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(12);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
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
@@ -3305,41 +3262,246 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
-class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+
+const FLUENT_FILES = ["branding/brand.ftl", "browser/branding/brandings.ftl", "browser/branding/sync-brand.ftl", "browser/newtab/onboarding.ftl"]; // From resource://devtools/client/shared/focus.js
+
+const FOCUSABLE_SELECTOR = ["a[href]:not([tabindex='-1'])", "button:not([disabled]):not([tabindex='-1'])", "iframe:not([tabindex='-1'])", "input:not([disabled]):not([tabindex='-1'])", "select:not([disabled]):not([tabindex='-1'])", "textarea:not([disabled]):not([tabindex='-1'])", "[tabindex]:not([tabindex='-1'])"].join(", ");
+class Trailhead extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent {
constructor(props) {
super(props);
+ this.closeModal = this.closeModal.bind(this);
+ this.hideCardPanel = this.hideCardPanel.bind(this);
+ this.onInputChange = this.onInputChange.bind(this);
+ this.onStartBlur = this.onStartBlur.bind(this);
+ this.onSubmit = this.onSubmit.bind(this);
+ this.onInputInvalid = this.onInputInvalid.bind(this);
this.onCardAction = this.onCardAction.bind(this);
- this.onHideContainer = this.onHideContainer.bind(this);
+ this.state = {
+ emailInput: "",
+ isModalOpen: true,
+ showCardPanel: true,
+ showCards: false,
+ // The params below are for FxA metrics
+ deviceId: "",
+ flowId: "",
+ flowBeginTime: 0
+ };
+ this.fxaMetricsInitialized = false;
}
- componentWillMount() {
- global.document.body.classList.add("inline-onboarding");
+ get dialog() {
+ return this.props.document.getElementById("trailheadDialog");
+ }
+
+ async componentWillMount() {
+ FLUENT_FILES.forEach(file => {
+ const link = document.head.appendChild(document.createElement("link"));
+ link.href = file;
+ link.rel = "localization";
+ });
+ await this.componentWillUpdate(this.props);
+ } // Get the fxa data if we don't have it yet from mount or update
+
+
+ async componentWillUpdate(props) {
+ if (props.fxaEndpoint && !this.fxaMetricsInitialized) {
+ try {
+ this.fxaMetricsInitialized = true;
+ const url = new URL(`${props.fxaEndpoint}/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`);
+ this.addUtmParams(url);
+ const response = await fetch(url, {
+ credentials: "omit"
+ });
+
+ if (response.status === 200) {
+ const {
+ deviceId,
+ flowId,
+ flowBeginTime
+ } = await response.json();
+ this.setState({
+ deviceId,
+ flowId,
+ flowBeginTime
+ });
+ } else {
+ props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
+ type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_FETCH_ERROR",
+ value: response.status
+ }
+ }));
+ }
+ } catch (error) {
+ props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
+ type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
+ data: {
+ event: "FXA_METRICS_ERROR"
+ }
+ }));
+ }
+ }
+ }
+
+ componentDidMount() {
+ // We need to remove hide-main since we should show it underneath everything that has rendered
+ this.props.document.body.classList.remove("hide-main"); // Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
+
+ this.props.document.body.classList.add("inline-onboarding"); // The rest of the page is "hidden" when the modal is open
+
+ if (this.props.message.content) {
+ this.props.document.getElementById("root").setAttribute("aria-hidden", "true"); // Start with focus in the email input box
+
+ this.dialog.querySelector("input[name=email]").focus();
+ } else {
+ // No modal overlay, let the user scroll and deal them some cards.
+ this.props.document.body.classList.remove("welcome");
+
+ if (this.props.message.includeBundle || this.props.message.cards) {
+ this.revealCards();
+ }
+ }
}
componentWillUnmount() {
this.props.document.body.classList.remove("inline-onboarding");
}
+ onInputChange(e) {
+ let error = e.target.previousSibling;
+ this.setState({
+ emailInput: e.target.value
+ });
+ error.classList.remove("active");
+ e.target.classList.remove("invalid");
+ }
+
+ onStartBlur(event) {
+ // Make sure focus stays within the dialog when tabbing from the button
+ const {
+ dialog
+ } = this;
+
+ if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
+ dialog.querySelector(FOCUSABLE_SELECTOR).focus();
+ }
+ }
+
+ onSubmit(event) {
+ // Dynamically require the email on submission so screen readers don't read
+ // out it's always required because there's also ways to skip the modal
+ const {
+ email
+ } = event.target.elements;
+
+ if (!email.value.length) {
+ email.required = true;
+ email.checkValidity();
+ event.preventDefault();
+ return;
+ }
+
+ this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
+ event: "SUBMIT_EMAIL",
+ ...this._getFormInfo()
+ }));
+ global.addEventListener("visibilitychange", this.closeModal);
+ }
+
+ closeModal(ev) {
+ global.removeEventListener("visibilitychange", this.closeModal);
+ this.props.document.body.classList.remove("welcome");
+ this.props.document.getElementById("root").removeAttribute("aria-hidden");
+ this.setState({
+ isModalOpen: false
+ });
+ this.revealCards(); // If closeModal() was triggered by a visibilitychange event, the user actually
+ // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
+
+ if (!ev || ev.type !== "visibilitychange") {
+ this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
+ event: "SKIPPED_SIGNIN",
+ ...this._getFormInfo()
+ }));
+ } // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
+
+
+ this.props.document.activeElement.blur();
+ }
+ /**
+ * Report to telemetry additional information about the form submission.
+ */
+
+
+ _getFormInfo() {
+ const value = {
+ has_flow_params: this.state.flowId.length > 0
+ };
+ return {
+ value
+ };
+ }
+
+ onInputInvalid(e) {
+ let error = e.target.previousSibling;
+ error.classList.add("active");
+ e.target.classList.add("invalid");
+ e.preventDefault(); // Override built-in form validation popup
+
+ e.target.focus();
+ }
+
+ hideCardPanel() {
+ this.setState({
+ showCardPanel: false
+ });
+ this.props.onDismissBundle();
+ }
+
+ revealCards() {
+ this.setState({
+ showCards: true
+ });
+ }
+ /**
+ * 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).
+ */
+
+
+ addUtmParams(url, isCard = false) {
+ let returnUrl = url;
+
+ if (typeof returnUrl === "string") {
+ returnUrl = new URL(url);
+ }
+
+ returnUrl.searchParams.append("utm_source", "activity-stream");
+ returnUrl.searchParams.append("utm_campaign", "firstrun");
+ returnUrl.searchParams.append("utm_medium", "referral");
+ returnUrl.searchParams.append("utm_term", `${this.props.message.utm_term}${isCard ? "-card" : ""}`);
+ return returnUrl;
+ }
+
onCardAction(action) {
let actionUpdates = {};
- const {
- flowParams,
- UTMTerm
- } = this.props;
if (action.type === "OPEN_URL") {
let url = new URL(action.data.args);
- Object(_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])(url, UTMTerm);
+ this.addUtmParams(url, true);
if (action.addFlowParams) {
- url.searchParams.append("device_id", flowParams.deviceId);
- url.searchParams.append("flow_id", flowParams.flowId);
- url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
+ url.searchParams.append("device_id", this.state.deviceId);
+ url.searchParams.append("flow_id", this.state.flowId);
+ url.searchParams.append("flow_begin_time", this.state.flowBeginTime);
}
actionUpdates = {
data: { ...action.data,
- args: url.toString()
+ args: url
}
};
}
@@ -3349,48 +3511,156 @@ class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponen
});
}
- onHideContainer() {
- const {
- sendUserActionTelemetry,
- cards,
- hideContainer
- } = this.props;
- hideContainer();
- sendUserActionTelemetry({
- event: "DISMISS",
- id: "onboarding-cards",
- message_id: cards.map(m => m.id).join(","),
- action: "onboarding_user_event"
- });
- }
-
render() {
const {
- cards,
- showCardPanel,
- showContent,
- sendUserActionTelemetry
- } = this.props;
- return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
- className: `trailheadCards ${showCardPanel ? "expanded" : "collapsed"}`
- }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+ props
+ } = this;
+ const {
+ bundle: cards,
+ content,
+ utm_term
+ } = props.message;
+ const innerClassName = ["trailhead", content && content.className].filter(v => v).join(" ");
+ return react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_3___default.a.Fragment, null, this.state.isModalOpen && content ? react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__["ModalOverlayWrapper"], {
+ innerClassName: innerClassName,
+ onClose: this.closeModal,
+ id: "trailheadDialog",
+ headerId: "trailheadHeader"
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+ className: "trailheadInner"
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+ className: "trailheadContent"
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
+ "data-l10n-id": content.title.string_id,
+ id: "trailheadHeader"
+ }), content.subtitle && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+ "data-l10n-id": content.subtitle.string_id
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("ul", {
+ className: "trailheadBenefits"
+ }, content.benefits.map(item => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("li", {
+ key: item.id,
+ className: item.id
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h3", {
+ "data-l10n-id": item.title.string_id
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+ "data-l10n-id": item.text.string_id
+ })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
+ className: "trailheadLearn",
+ "data-l10n-id": content.learn.text.string_id,
+ href: this.addUtmParams(content.learn.url),
+ target: "_blank",
+ rel: "noopener noreferrer"
+ })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+ role: "group",
+ "aria-labelledby": "joinFormHeader",
+ "aria-describedby": "joinFormBody",
+ className: "trailheadForm"
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h3", {
+ id: "joinFormHeader",
+ "data-l10n-id": content.form.title.string_id
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+ id: "joinFormBody",
+ "data-l10n-id": content.form.text.string_id
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("form", {
+ method: "get",
+ action: this.props.fxaEndpoint,
+ target: "_blank",
+ rel: "noopener noreferrer",
+ onSubmit: this.onSubmit
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "service",
+ type: "hidden",
+ value: "sync"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "action",
+ type: "hidden",
+ value: "email"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "context",
+ type: "hidden",
+ value: "fx_desktop_v3"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "entrypoint",
+ type: "hidden",
+ value: "activity-stream-firstrun"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "utm_source",
+ type: "hidden",
+ value: "activity-stream"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "utm_campaign",
+ type: "hidden",
+ value: "firstrun"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "utm_term",
+ type: "hidden",
+ value: utm_term
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "device_id",
+ type: "hidden",
+ value: this.state.deviceId
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "flow_id",
+ type: "hidden",
+ value: this.state.flowId
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "flow_begin_time",
+ type: "hidden",
+ value: this.state.flowBeginTime
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ name: "style",
+ type: "hidden",
+ value: "trailhead"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+ "data-l10n-id": "onboarding-join-form-email-error",
+ className: "error"
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
+ "data-l10n-id": content.form.email.string_id,
+ name: "email",
+ type: "email",
+ onInvalid: this.onInputInvalid,
+ onChange: this.onInputChange
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+ className: "trailheadTerms",
+ "data-l10n-id": "onboarding-join-form-legal"
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
+ "data-l10n-name": "terms",
+ target: "_blank",
+ rel: "noopener noreferrer",
+ href: this.addUtmParams("https://accounts.firefox.com/legal/terms")
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
+ "data-l10n-name": "privacy",
+ target: "_blank",
+ rel: "noopener noreferrer",
+ href: this.addUtmParams("https://accounts.firefox.com/legal/privacy")
+ })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
+ "data-l10n-id": content.form.button.string_id,
+ type: "submit"
+ })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
+ className: "trailheadStart",
+ "data-l10n-id": content.skipButton.string_id,
+ onBlur: this.onStartBlur,
+ onClick: this.closeModal
+ })) : null, cards && cards.length ? react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+ className: `trailheadCards ${this.state.showCardPanel ? "expanded" : "collapsed"}`
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
className: "trailheadCardsInner",
- "aria-hidden": !showContent
- }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", {
+ "aria-hidden": !this.state.showCards
+ }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
"data-l10n-id": "onboarding-welcome-header"
- }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
- className: `trailheadCardGrid${showContent ? " show" : ""}`
- }, cards.map(card => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_1__["OnboardingCard"], _extends({
+ }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+ className: `trailheadCardGrid${this.state.showCards ? " show" : ""}`
+ }, cards.map(card => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__["OnboardingCard"], _extends({
key: card.id,
className: "trailheadCard",
- sendUserActionTelemetry: sendUserActionTelemetry,
+ sendUserActionTelemetry: props.sendUserActionTelemetry,
onAction: this.onCardAction,
UISurface: "TRAILHEAD"
- }, card)))), showCardPanel && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+ }, card)))), this.state.showCardPanel && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
className: "icon icon-dismiss",
- onClick: this.onHideContainer,
+ onClick: this.hideCardPanel,
"data-l10n-id": "onboarding-cards-dismiss"
- })));
+ }))) : null);
}
}
@@ -3400,72 +3670,6 @@ class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponen
/* 26 */
/***/ (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__(9);
-/* 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/. */
-
-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);
- }
-
- 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("h3", {
- className: "onboardingTitle",
- "data-l10n-id": content.title.string_id
- }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
- className: "onboardingText",
- "data-l10n-id": content.text.string_id
- })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
- className: "onboardingButtonContainer"
- }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
- "data-l10n-id": content.primary_button.label.string_id,
- className: "button onboardingButton",
- onClick: this.onClick
- }))));
- }
-
-}
-
-/***/ }),
-/* 27 */
-/***/ (function(module, exports) {
-
-module.exports = ReactRedux;
-
-/***/ }),
-/* 28 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
"use strict";
__webpack_require__.r(__webpack_exports__);
/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleHashRouter", function() { return SimpleHashRouter; });
@@ -3512,7 +3716,7 @@ class SimpleHashRouter extends react__WEBPACK_IMPORTED_MODULE_0___default.a.Pure
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 29 */
+/* 27 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -3520,7 +3724,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_ConfirmDialog", function() { return _ConfirmDialog; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConfirmDialog", function() { return ConfirmDialog; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
@@ -3617,7 +3821,7 @@ class _ConfirmDialog extends react__WEBPACK_IMPORTED_MODULE_2___default.a.PureCo
const ConfirmDialog = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(state => state.Dialog)(_ConfirmDialog);
/***/ }),
-/* 30 */
+/* 28 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -3670,21 +3874,21 @@ class ContextMenu extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCompo
// Disabling focus on the menu span allows the first tab to focus on the first menu item instead of the wrapper.
return (// eslint-disable-next-line jsx-a11y/interactive-supports-focus
react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
- className: "context-menu"
- }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("ul", {
role: "menu",
+ className: "context-menu",
onClick: this.onClick,
- onKeyDown: this.onClick,
+ onKeyDown: this.onClick
+ }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("ul", {
className: "context-menu-list"
}, this.props.options.map((option, i) => option.type === "separator" ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("li", {
key: i,
- className: "separator",
- role: "separator"
+ className: "separator"
}) : option.type !== "empty" && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(ContextMenuItem, {
key: i,
option: option,
hideContext: this.hideContext,
- keyboardAccess: this.props.keyboardAccess
+ keyboardAccess: this.props.keyboardAccess,
+ tabIndex: "0"
}))))
);
}
@@ -3695,7 +3899,6 @@ class ContextMenuItem extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureC
super(props);
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
this.focusFirst = this.focusFirst.bind(this);
}
@@ -3750,8 +3953,6 @@ class ContextMenuItem extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureC
break;
case "Enter":
- case " ":
- event.preventDefault();
this.props.hideContext();
option.onClick();
break;
@@ -3760,14 +3961,6 @@ class ContextMenuItem extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureC
this.props.hideContext();
break;
}
- } // Prevents the default behavior of spacebar
- // scrolling the page & auto-triggering buttons.
-
-
- onKeyUp(event) {
- if (event.key === " ") {
- event.preventDefault();
- }
}
render() {
@@ -3775,14 +3968,13 @@ class ContextMenuItem extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureC
option
} = this.props;
return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("li", {
- role: "presentation",
+ role: "menuitem",
className: "context-menu-item"
}, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
className: option.disabled ? "disabled" : "",
- role: "menuitem",
+ tabIndex: "0",
onClick: this.onClick,
onKeyDown: this.onKeyDown,
- onKeyUp: this.onKeyUp,
ref: option.first ? this.focusFirst : null
}, option.icon && react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
className: `icon icon-spacer icon-${option.icon}`
@@ -3795,7 +3987,7 @@ class ContextMenuItem extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureC
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 31 */
+/* 29 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -3836,7 +4028,7 @@ class ContextMenuButton extends react__WEBPACK_IMPORTED_MODULE_0___default.a.Pur
}
onKeyDown(event) {
- if (event.key === "Enter" || event.key === " ") {
+ if (event.key === "Enter") {
event.preventDefault();
this.openContextMenu(true, event);
}
@@ -3880,7 +4072,7 @@ class ContextMenuButton extends react__WEBPACK_IMPORTED_MODULE_0___default.a.Pur
}
/***/ }),
-/* 32 */
+/* 30 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4107,20 +4299,20 @@ ImpressionStats.defaultProps = {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 33 */
+/* 31 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CollapsibleSection", function() { return CollapsibleSection; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34);
-/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(36);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(32);
+/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(34);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(37);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(38);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(35);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(36);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(29);
/* 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/. */
@@ -4225,7 +4417,6 @@ class CollapsibleSection extends react__WEBPACK_IMPORTED_MODULE_3___default.a.Pu
onKeyPress(event) {
if (event.key === "Enter" || event.key === " ") {
- event.preventDefault();
this.onHeaderClick();
}
}
@@ -4345,8 +4536,13 @@ class CollapsibleSection extends react__WEBPACK_IMPORTED_MODULE_3___default.a.Pu
onClick: this.onHeaderClick
}, this.renderIcon(), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_2__["FluentOrText"], {
message: title
- }), isCollapsible && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("span", {
- "data-l10n-id": collapsed ? "newtab-section-expand-section-label" : "newtab-section-collapse-section-label",
+ })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("span", {
+ className: "click-target",
+ role: "button",
+ tabIndex: "0",
+ onKeyPress: this.onKeyPress,
+ onClick: this.onHeaderClick
+ }, isCollapsible && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("span", {
className: `collapsible-arrow icon ${collapsed ? "icon-arrowhead-forward-small" : "icon-arrowhead-down-small"}`
})), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("span", {
className: "learn-more-link-wrapper"
@@ -4396,14 +4592,14 @@ CollapsibleSection.defaultProps = {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 34 */
+/* 32 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundaryFallback", function() { return ErrorBoundaryFallback; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundary", function() { return ErrorBoundary; });
-/* harmony import */ var content_src_components_A11yLinkButton_A11yLinkButton__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(35);
+/* harmony import */ var content_src_components_A11yLinkButton_A11yLinkButton__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -4483,7 +4679,7 @@ ErrorBoundary.defaultProps = {
};
/***/ }),
-/* 35 */
+/* 33 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4513,7 +4709,7 @@ function A11yLinkButton(props) {
}
/***/ }),
-/* 36 */
+/* 34 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4559,7 +4755,7 @@ class FluentOrText extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
}
/***/ }),
-/* 37 */
+/* 35 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4567,10 +4763,10 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_SectionMenu", function() { return _SectionMenu; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionMenu", function() { return SectionMenu; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(28);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(38);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(36);
/* 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/. */
@@ -4665,7 +4861,7 @@ class _SectionMenu extends react__WEBPACK_IMPORTED_MODULE_2___default.a.PureComp
const SectionMenu = _SectionMenu;
/***/ }),
-/* 38 */
+/* 36 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4795,7 +4991,7 @@ const SectionMenuOptions = {
};
/***/ }),
-/* 39 */
+/* 37 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -4805,18 +5001,18 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Sections", function() { return _Sections; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Sections", function() { return Sections; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(57);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(33);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(41);
-/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(36);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(27);
+/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(39);
+/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(34);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(43);
-/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(44);
+/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(41);
+/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(42);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_8__);
-/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(45);
-/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(46);
+/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(43);
+/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(44);
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
@@ -5166,7 +5362,7 @@ const Sections = Object(react_redux__WEBPACK_IMPORTED_MODULE_5__["connect"])(sta
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 40 */
+/* 38 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5235,14 +5431,14 @@ const ScreenshotUtils = {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 41 */
+/* 39 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ComponentPerfTimer", function() { return ComponentPerfTimer; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(42);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(40);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -5415,7 +5611,7 @@ class ComponentPerfTimer extends react__WEBPACK_IMPORTED_MODULE_2___default.a.Co
}
/***/ }),
-/* 42 */
+/* 40 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5547,7 +5743,7 @@ _PerfService.prototype = {
var perfService = new _PerfService();
/***/ }),
-/* 43 */
+/* 41 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5579,14 +5775,14 @@ class MoreRecommendations extends react__WEBPACK_IMPORTED_MODULE_0___default.a.P
}
/***/ }),
-/* 44 */
+/* 42 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PocketLoggedInCta", function() { return _PocketLoggedInCta; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PocketLoggedInCta", function() { return PocketLoggedInCta; });
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
@@ -5622,7 +5818,7 @@ const PocketLoggedInCta = Object(react_redux__WEBPACK_IMPORTED_MODULE_0__["conne
}))(_PocketLoggedInCta);
/***/ }),
-/* 45 */
+/* 43 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5667,7 +5863,7 @@ class Topics extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent
}
/***/ }),
-/* 46 */
+/* 44 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5675,18 +5871,18 @@ __webpack_require__.r(__webpack_exports__);
/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_TopSites", function() { return _TopSites; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSites", function() { return TopSites; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(47);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(33);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(41);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(27);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(39);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(21);
+/* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(13);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(48);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(56);
-/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(59);
-/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(49);
+/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(46);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(54);
+/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(56);
+/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(47);
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
@@ -5893,7 +6089,7 @@ const TopSites = Object(react_redux__WEBPACK_IMPORTED_MODULE_4__["connect"])(sta
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 47 */
+/* 45 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5916,7 +6112,7 @@ const MIN_RICH_FAVICON_SIZE = 96; // minimum size necessary to show any icon in
const MIN_CORNER_FAVICON_SIZE = 16;
/***/ }),
-/* 48 */
+/* 46 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -5926,7 +6122,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(47);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(45);
/* 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/. */
@@ -6107,7 +6303,7 @@ class SearchShortcutsForm extends react__WEBPACK_IMPORTED_MODULE_1___default.a.P
}
/***/ }),
-/* 49 */
+/* 47 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -6117,13 +6313,13 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSitePlaceholder", function() { return TopSitePlaceholder; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteList", function() { return TopSiteList; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(47);
-/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(60);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45);
+/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(57);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(40);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(56);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(31);
+/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(38);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(54);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(29);
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
@@ -6750,7 +6946,7 @@ class TopSiteList extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureCompo
}
/***/ }),
-/* 50 */
+/* 48 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -6758,7 +6954,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Search", function() { return _Search; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Search", function() { return Search; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(24);
/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var content_src_lib_constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
@@ -6938,14 +7134,14 @@ class _Search extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent
const Search = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])()(_Search);
/***/ }),
-/* 51 */
+/* 49 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DetectUserSessionStart", function() { return DetectUserSessionStart; });
/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(42);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(40);
/* 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/. */
@@ -7020,7 +7216,7 @@ class DetectUserSessionStart {
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
/***/ }),
-/* 52 */
+/* 50 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -7034,7 +7230,7 @@ var external_React_ = __webpack_require__(9);
var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
// EXTERNAL MODULE: external "ReactDOM"
-var external_ReactDOM_ = __webpack_require__(12);
+var external_ReactDOM_ = __webpack_require__(14);
var external_ReactDOM_default = /*#__PURE__*/__webpack_require__.n(external_ReactDOM_);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSImage/DSImage.jsx
@@ -7163,10 +7359,10 @@ DSImage_DSImage.defaultProps = {
};
// EXTERNAL MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx + 1 modules
-var LinkMenu = __webpack_require__(60);
+var LinkMenu = __webpack_require__(57);
// EXTERNAL MODULE: ./content-src/components/ContextMenu/ContextMenuButton.jsx
-var ContextMenuButton = __webpack_require__(31);
+var ContextMenuButton = __webpack_require__(29);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -7245,7 +7441,7 @@ class DSLinkMenu_DSLinkMenu extends external_React_default.a.PureComponent {
}
// EXTERNAL MODULE: ./content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
-var ImpressionStats = __webpack_require__(32);
+var ImpressionStats = __webpack_require__(30);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -7590,10 +7786,10 @@ CardGrid_CardGrid.defaultProps = {
};
// EXTERNAL MODULE: ./content-src/components/CollapsibleSection/CollapsibleSection.jsx
-var CollapsibleSection = __webpack_require__(33);
+var CollapsibleSection = __webpack_require__(31);
// EXTERNAL MODULE: external "ReactRedux"
-var external_ReactRedux_ = __webpack_require__(27);
+var external_ReactRedux_ = __webpack_require__(24);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -7963,7 +8159,7 @@ Hero_Hero.defaultProps = {
};
// EXTERNAL MODULE: ./content-src/components/Sections/Sections.jsx
-var Sections = __webpack_require__(39);
+var Sections = __webpack_require__(37);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/Highlights/Highlights.jsx
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); }
@@ -8274,7 +8470,7 @@ const selectLayoutRender = (state, prefs, rickRollCache) => {
};
};
// EXTERNAL MODULE: ./content-src/components/TopSites/TopSites.jsx
-var TopSites = __webpack_require__(46);
+var TopSites = __webpack_require__(44);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopSites/TopSites.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -8603,7 +8799,7 @@ const DiscoveryStreamBase = Object(external_ReactRedux_["connect"])(state => ({
}))(DiscoveryStreamBase_DiscoveryStreamBase);
/***/ }),
-/* 53 */
+/* 51 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -9400,7 +9596,7 @@ localized_Localized.propTypes = {
/***/ }),
-/* 54 */
+/* 52 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -9411,7 +9607,7 @@ var external_React_ = __webpack_require__(9);
var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
// EXTERNAL MODULE: ./content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json
-var EOYSnippet_schema = __webpack_require__(13);
+var EOYSnippet_schema = __webpack_require__(18);
// CONCATENATED MODULE: ./content-src/asrouter/components/Button/Button.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -9450,13 +9646,13 @@ const ConditionalWrapper = ({
children
}) => condition ? wrap(children) : children;
// EXTERNAL MODULE: ./content-src/asrouter/components/RichText/RichText.jsx
-var RichText = __webpack_require__(14);
+var RichText = __webpack_require__(16);
// EXTERNAL MODULE: ./content-src/asrouter/template-utils.js
-var template_utils = __webpack_require__(15);
+var template_utils = __webpack_require__(17);
// EXTERNAL MODULE: ./content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json
-var SimpleSnippet_schema = __webpack_require__(16);
+var SimpleSnippet_schema = __webpack_require__(19);
// CONCATENATED MODULE: ./content-src/asrouter/components/SnippetBase/SnippetBase.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -9913,7 +10109,7 @@ const EOYSnippet = props => {
}));
};
// EXTERNAL MODULE: ./content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet.schema.json
-var FXASignupSnippet_schema = __webpack_require__(17);
+var FXASignupSnippet_schema = __webpack_require__(20);
// CONCATENATED MODULE: ./content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
function SubmitFormSnippet_extends() { SubmitFormSnippet_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 SubmitFormSnippet_extends.apply(this, arguments); }
@@ -10261,7 +10457,7 @@ const FXASignupSnippet = props => {
}));
};
// EXTERNAL MODULE: ./content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.schema.json
-var NewsletterSnippet_schema = __webpack_require__(18);
+var NewsletterSnippet_schema = __webpack_require__(21);
// CONCATENATED MODULE: ./content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.jsx
function NewsletterSnippet_extends() { NewsletterSnippet_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 NewsletterSnippet_extends.apply(this, arguments); }
@@ -10341,7 +10537,7 @@ function isEmailOrPhoneNumber(val, content) {
return "";
}
// EXTERNAL MODULE: ./content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json
-var SendToDeviceSnippet_schema = __webpack_require__(19);
+var SendToDeviceSnippet_schema = __webpack_require__(22);
// CONCATENATED MODULE: ./content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.jsx
function SendToDeviceSnippet_extends() { SendToDeviceSnippet_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 SendToDeviceSnippet_extends.apply(this, arguments); }
@@ -10488,7 +10684,7 @@ const SnippetsTemplates = {
};
/***/ }),
-/* 55 */
+/* 53 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -11908,7 +12104,7 @@ function generateBundles(content) {
}
/***/ }),
-/* 56 */
+/* 54 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -12767,7 +12963,7 @@ var reducers = {
};
/***/ }),
-/* 57 */
+/* 55 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -12803,20 +12999,20 @@ const cardContextTypes = {
}
};
// EXTERNAL MODULE: external "ReactRedux"
-var external_ReactRedux_ = __webpack_require__(27);
+var external_ReactRedux_ = __webpack_require__(24);
// EXTERNAL MODULE: ./content-src/components/ContextMenu/ContextMenuButton.jsx
-var ContextMenuButton = __webpack_require__(31);
+var ContextMenuButton = __webpack_require__(29);
// EXTERNAL MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx + 1 modules
-var LinkMenu = __webpack_require__(60);
+var LinkMenu = __webpack_require__(57);
// EXTERNAL MODULE: external "React"
var external_React_ = __webpack_require__(9);
var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
// EXTERNAL MODULE: ./content-src/lib/screenshot-utils.js
-var screenshot_utils = __webpack_require__(40);
+var screenshot_utils = __webpack_require__(38);
// CONCATENATED MODULE: ./content-src/components/Card/Card.jsx
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Card", function() { return Card_Card; });
@@ -13150,335 +13346,7 @@ const PlaceholderCard = props => external_React_default.a.createElement(Card, {
});
/***/ }),
-/* 58 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-
-// EXTERNAL MODULE: external "React"
-var external_React_ = __webpack_require__(9);
-var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
-
-// EXTERNAL MODULE: ./content-src/asrouter/templates/Trailhead/Trailhead.jsx
-var Trailhead = __webpack_require__(20);
-
-// EXTERNAL MODULE: ./content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
-var ReturnToAMO = __webpack_require__(23);
-
-// EXTERNAL MODULE: ./content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx
-var StartupOverlay = __webpack_require__(24);
-
-// EXTERNAL MODULE: ./node_modules/fluent-react/src/index.js + 14 modules
-var src = __webpack_require__(53);
-
-// EXTERNAL MODULE: ./content-src/asrouter/rich-text-strings.js + 8 modules
-var rich_text_strings = __webpack_require__(55);
-
-// CONCATENATED MODULE: ./content-src/asrouter/templates/FirstRun/Interrupt.jsx
-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 Interrupt_Interrupt extends external_React_default.a.PureComponent {
- render() {
- const {
- onDismiss,
- onNextScene,
- message,
- sendUserActionTelemetry,
- executeAction,
- dispatch,
- fxaEndpoint,
- UTMTerm,
- flowParams
- } = this.props;
-
- switch (message.template) {
- case "return_to_amo_overlay":
- return external_React_default.a.createElement(src["LocalizationProvider"], {
- bundles: Object(rich_text_strings["generateBundles"])({
- amo_html: message.content.text
- })
- }, external_React_default.a.createElement(ReturnToAMO["ReturnToAMO"], _extends({}, message, {
- UISurface: "NEWTAB_OVERLAY",
- onBlock: onDismiss,
- onAction: executeAction,
- sendUserActionTelemetry: sendUserActionTelemetry
- })));
-
- case "fxa_overlay":
- return external_React_default.a.createElement(StartupOverlay["StartupOverlay"], {
- onBlock: onDismiss,
- dispatch: dispatch,
- fxa_endpoint: fxaEndpoint
- });
-
- case "trailhead":
- return external_React_default.a.createElement(Trailhead["Trailhead"], {
- document: this.props.document,
- message: message,
- onNextScene: onNextScene,
- onAction: executeAction,
- sendUserActionTelemetry: sendUserActionTelemetry,
- dispatch: dispatch,
- fxaEndpoint: fxaEndpoint,
- UTMTerm: UTMTerm,
- flowParams: flowParams
- });
-
- default:
- throw new Error(`${message.template} is not a valid FirstRun message`);
- }
- }
-
-}
-// EXTERNAL MODULE: ./content-src/asrouter/templates/FirstRun/Triplets.jsx
-var Triplets = __webpack_require__(25);
-
-// EXTERNAL MODULE: ./common/Actions.jsm
-var Actions = __webpack_require__(2);
-
-// EXTERNAL MODULE: ./content-src/asrouter/templates/FirstRun/addUtmParams.js
-var addUtmParams = __webpack_require__(22);
-
-// CONCATENATED MODULE: ./content-src/asrouter/templates/FirstRun/FirstRun.jsx
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FLUENT_FILES", function() { return FLUENT_FILES; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "helpers", function() { return helpers; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FirstRun", function() { return FirstRun_FirstRun; });
-/* 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 FLUENT_FILES = ["branding/brand.ftl", "browser/branding/brandings.ftl", "browser/branding/sync-brand.ftl", "browser/newtab/onboarding.ftl"];
-const helpers = {
- selectInterruptAndTriplets(message = {}) {
- const hasInterrupt = Boolean(message.content);
- const hasTriplets = Boolean(message.bundle && message.bundle.length);
- const UTMTerm = message.utm_term || "";
- return {
- hasTriplets,
- hasInterrupt,
- interrupt: hasInterrupt ? message : null,
- triplets: hasTriplets ? message.bundle : null,
- UTMTerm
- };
- },
-
- addFluent(document) {
- FLUENT_FILES.forEach(file => {
- const link = document.head.appendChild(document.createElement("link"));
- link.href = file;
- link.rel = "localization";
- });
- },
-
- async fetchFlowParams({
- fxaEndpoint,
- UTMTerm,
- dispatch,
- setFlowParams
- }) {
- try {
- const url = new URL(`${fxaEndpoint}/metrics-flow?entrypoint=activity-stream-firstrun&form_type=email`);
- Object(addUtmParams["addUtmParams"])(url, UTMTerm);
- const response = await fetch(url, {
- credentials: "omit"
- });
-
- if (response.status === 200) {
- const {
- deviceId,
- flowId,
- flowBeginTime
- } = await response.json();
- setFlowParams({
- deviceId,
- flowId,
- flowBeginTime
- });
- } else {
- dispatch(Actions["actionCreators"].OnlyToMain({
- type: Actions["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
- data: {
- event: "FXA_METRICS_FETCH_ERROR",
- value: response.status
- }
- }));
- }
- } catch (error) {
- dispatch(Actions["actionCreators"].OnlyToMain({
- type: Actions["actionTypes"].TELEMETRY_UNDESIRED_EVENT,
- data: {
- event: "FXA_METRICS_ERROR"
- }
- }));
- }
- }
-
-};
-class FirstRun_FirstRun extends external_React_default.a.PureComponent {
- constructor(props) {
- super(props);
- this.didLoadFlowParams = false;
- this.state = {
- prevMessage: undefined,
- hasInterrupt: false,
- hasTriplets: false,
- interrupt: undefined,
- triplets: undefined,
- isInterruptVisible: false,
- isTripletsContainerVisible: false,
- isTripletsContentVisible: false,
- UTMTerm: "",
- flowParams: undefined
- };
- this.closeInterrupt = this.closeInterrupt.bind(this);
- this.closeTriplets = this.closeTriplets.bind(this);
- helpers.addFluent(this.props.document);
- }
-
- static getDerivedStateFromProps(props, state) {
- const {
- message
- } = props;
-
- if (message && message.id !== state.prevMessageId) {
- const {
- hasTriplets,
- hasInterrupt,
- interrupt,
- triplets,
- UTMTerm
- } = helpers.selectInterruptAndTriplets(message);
- return {
- prevMessageId: message.id,
- hasInterrupt,
- hasTriplets,
- interrupt,
- triplets,
- isInterruptVisible: hasInterrupt,
- isTripletsContainerVisible: hasTriplets,
- isTripletsContentVisible: !(hasInterrupt || !hasTriplets),
- UTMTerm
- };
- }
-
- return null;
- }
-
- fetchFlowParams() {
- const {
- fxaEndpoint,
- dispatch
- } = this.props;
- const {
- UTMTerm
- } = this.state;
-
- if (fxaEndpoint && UTMTerm && !this.didLoadFlowParams) {
- this.didLoadFlowParams = true;
- helpers.fetchFlowParams({
- fxaEndpoint,
- UTMTerm,
- dispatch,
- setFlowParams: flowParams => this.setState({
- flowParams
- })
- });
- }
- }
-
- removeHideMain() {
- if (!this.state.hasInterrupt) {
- // We need to remove hide-main since we should show it underneath everything that has rendered
- this.props.document.body.classList.remove("hide-main", "welcome");
- }
- }
-
- componentDidMount() {
- this.fetchFlowParams();
- this.removeHideMain();
- }
-
- componentDidUpdate() {
- // In case we didn't have FXA info immediately, try again when we receive it.
- this.fetchFlowParams();
- this.removeHideMain();
- }
-
- closeInterrupt() {
- this.setState(prevState => ({
- isInterruptVisible: false,
- isTripletsContainerVisible: prevState.hasTriplets,
- isTripletsContentVisible: prevState.hasTriplets
- }));
- }
-
- closeTriplets() {
- this.setState({
- isTripletsContainerVisible: false
- });
- }
-
- render() {
- const {
- props
- } = this;
- const {
- sendUserActionTelemetry,
- fxaEndpoint,
- dispatch,
- executeAction
- } = props;
- const {
- interrupt,
- triplets,
- isInterruptVisible,
- isTripletsContainerVisible,
- isTripletsContentVisible,
- hasTriplets,
- UTMTerm,
- flowParams
- } = this.state;
- return external_React_default.a.createElement(external_React_default.a.Fragment, null, isInterruptVisible ? external_React_default.a.createElement(Interrupt_Interrupt, {
- document: props.document,
- message: interrupt,
- onNextScene: this.closeInterrupt,
- UTMTerm: UTMTerm,
- sendUserActionTelemetry: sendUserActionTelemetry,
- dispatch: dispatch,
- flowParams: flowParams,
- onDismiss: this.closeInterrupt,
- fxaEndpoint: fxaEndpoint
- }) : null, hasTriplets ? external_React_default.a.createElement(Triplets["Triplets"], {
- document: props.document,
- cards: triplets,
- showCardPanel: isTripletsContainerVisible,
- showContent: isTripletsContentVisible,
- hideContainer: this.closeTriplets,
- sendUserActionTelemetry: sendUserActionTelemetry,
- UTMTerm: `${UTMTerm}-card`,
- flowParams: flowParams,
- onAction: executeAction
- }) : null);
- }
-
-}
-
-/***/ }),
-/* 59 */
+/* 56 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -13488,14 +13356,14 @@ __webpack_require__.r(__webpack_exports__);
var Actions = __webpack_require__(2);
// EXTERNAL MODULE: ./content-src/components/A11yLinkButton/A11yLinkButton.jsx
-var A11yLinkButton = __webpack_require__(35);
+var A11yLinkButton = __webpack_require__(33);
// EXTERNAL MODULE: external "React"
var external_React_ = __webpack_require__(9);
var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
// EXTERNAL MODULE: ./content-src/components/TopSites/TopSitesConstants.js
-var TopSitesConstants = __webpack_require__(47);
+var TopSitesConstants = __webpack_require__(45);
// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteFormInput.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -13610,7 +13478,7 @@ TopSiteFormInput_TopSiteFormInput.defaultProps = {
validationError: false
};
// EXTERNAL MODULE: ./content-src/components/TopSites/TopSite.jsx
-var TopSite = __webpack_require__(49);
+var TopSite = __webpack_require__(47);
// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteForm.jsx
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteForm", function() { return TopSiteForm_TopSiteForm; });
@@ -13907,7 +13775,7 @@ TopSiteForm_TopSiteForm.defaultProps = {
};
/***/ }),
-/* 60 */
+/* 57 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -13917,10 +13785,10 @@ __webpack_require__.r(__webpack_exports__);
var Actions = __webpack_require__(2);
// EXTERNAL MODULE: external "ReactRedux"
-var external_ReactRedux_ = __webpack_require__(27);
+var external_ReactRedux_ = __webpack_require__(24);
// EXTERNAL MODULE: ./content-src/components/ContextMenu/ContextMenu.jsx
-var ContextMenu = __webpack_require__(30);
+var ContextMenu = __webpack_require__(28);
// CONCATENATED MODULE: ./content-src/lib/link-menu-options.js
/* This Source Code Form is subject to the terms of the Mozilla Public
diff --git a/browser/components/newtab/docs/v2-system-addon/data_events.md b/browser/components/newtab/docs/v2-system-addon/data_events.md
index 9aa84af6d0d1..d98e389ead6a 100644
--- a/browser/components/newtab/docs/v2-system-addon/data_events.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_events.md
@@ -1086,23 +1086,3 @@ This reports when a user has seen or clicked a badge/notification in the browser
"event": ["CLICK" | "IMPRESSION"],
}
```
-
-## Panel interaction pings
-
-This reports when a user opens the panel, views messages and clicks on a message.
-For message impressions we concatenate the ids of all messages in the panel.
-
-```
-{
- "locale": "en-US",
- "client_id": "9da773d8-4356-f54f-b7cf-6134726bcf3d",
- "version": "70.0a1",
- "release_channel": "default",
- "addon_version": "20190712095934",
- "action": "cfr_user_event",
- "source": "CFR",
- "message_id": "WHATS_NEW_70",
- "event": ["CLICK" | "IMPRESSION"],
- "value": { "view": ["application_menu" | "toolbar_dropdown"] }
-}
-```
diff --git a/browser/components/newtab/lib/ASRouter.jsm b/browser/components/newtab/lib/ASRouter.jsm
index b1e565a3375c..d0b510d47f84 100644
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -732,7 +732,6 @@ class _ASRouter {
});
ToolbarPanelHub.init(this.waitForInitialized, {
getMessages: this.handleMessageRequest,
- dispatch: this.dispatch,
});
this._loadLocalProviders();
@@ -900,25 +899,18 @@ class _ASRouter {
let interrupt;
let triplet;
+ // Use control Trailhead Branch (for cards) if we are showing RTAMO.
+ if (await this._hasAddonAttributionData()) {
+ return { experiment, interrupt: "control", triplet: "" };
+ }
+
+ // If a value is set in TRAILHEAD_OVERRIDE_PREF, it will be returned and no experiment will be set.
const overrideValue = Services.prefs.getStringPref(
TRAILHEAD_CONFIG.OVERRIDE_PREF,
""
);
if (overrideValue) {
[interrupt, triplet] = overrideValue.split("-");
- }
-
- // Use control Trailhead Branch (for cards) if we are showing RTAMO.
- if (await this._hasAddonAttributionData()) {
- return {
- experiment,
- interrupt: "control",
- triplet: triplet || "privacy",
- };
- }
-
- // If a value is set in TRAILHEAD_OVERRIDE_PREF, it will be returned and no experiment will be set.
- if (overrideValue) {
return { experiment, interrupt, triplet: triplet || "" };
}
@@ -984,7 +976,6 @@ class _ASRouter {
interrupt,
triplet,
} = await this._generateTrailheadBranches();
-
await this.setState({
trailheadInitialized: true,
trailheadInterrupt: interrupt,
@@ -1841,6 +1832,11 @@ class _ASRouter {
data: { id: action.data.id },
});
break;
+ case "DISMISS_BUNDLE":
+ this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
+ type: "CLEAR_BUNDLE",
+ });
+ break;
case "BLOCK_BUNDLE":
await this.blockMessageById(action.data.bundle.map(b => b.id));
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
@@ -1895,7 +1891,6 @@ class _ASRouter {
break;
case "DOORHANGER_TELEMETRY":
case "TOOLBAR_BADGE_TELEMETRY":
- case "TOOLBAR_PANEL_TELEMETRY":
if (this.dispatchToAS) {
this.dispatchToAS(ac.ASRouterUserEvent(action.data));
}
diff --git a/browser/components/newtab/lib/OnboardingMessageProvider.jsm b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
index 9b8d3763f4e0..472e6bef6076 100644
--- a/browser/components/newtab/lib/OnboardingMessageProvider.jsm
+++ b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
@@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* globals Localization */
+const { FxAccountsConfig } = ChromeUtils.import(
+ "resource://gre/modules/FxAccountsConfig.jsm"
+);
const { AttributionCode } = ChromeUtils.import(
"resource:///modules/AttributionCode.jsm"
);
@@ -18,7 +21,114 @@ const L10N = new Localization([
"browser/newtab/onboarding.ftl",
]);
-const ONBOARDING_MESSAGES = () => [
+const ONBOARDING_MESSAGES = async () => [
+ {
+ id: "ONBOARDING_1",
+ template: "onboarding",
+ bundled: 3,
+ order: 2,
+ content: {
+ title: { string_id: "onboarding-private-browsing-title" },
+ text: { string_id: "onboarding-private-browsing-text" },
+ icon: "privatebrowsing",
+ primary_button: {
+ label: { string_id: "onboarding-button-label-try-now" },
+ action: { type: "OPEN_PRIVATE_BROWSER_WINDOW" },
+ },
+ },
+ trigger: { id: "showOnboarding" },
+ },
+ {
+ id: "ONBOARDING_2",
+ template: "onboarding",
+ bundled: 3,
+ order: 3,
+ content: {
+ title: { string_id: "onboarding-screenshots-title" },
+ text: { string_id: "onboarding-screenshots-text" },
+ icon: "screenshots",
+ primary_button: {
+ label: { string_id: "onboarding-button-label-try-now" },
+ action: {
+ type: "OPEN_URL",
+ data: {
+ args: "https://screenshots.firefox.com/#tour",
+ where: "tabshifted",
+ },
+ },
+ },
+ },
+ trigger: { id: "showOnboarding" },
+ },
+ {
+ id: "ONBOARDING_3",
+ template: "onboarding",
+ bundled: 3,
+ order: 1,
+ content: {
+ title: { string_id: "onboarding-addons-title" },
+ text: { string_id: "onboarding-addons-text" },
+ icon: "addons",
+ primary_button: {
+ label: { string_id: "onboarding-button-label-try-now" },
+ action: {
+ type: "OPEN_ABOUT_PAGE",
+ data: { args: "addons" },
+ },
+ },
+ },
+ targeting:
+ "trailheadInterrupt == 'control' && attributionData.campaign != 'non-fx-button' && attributionData.source != 'addons.mozilla.org'",
+ trigger: { id: "showOnboarding" },
+ },
+ {
+ id: "ONBOARDING_4",
+ template: "onboarding",
+ bundled: 3,
+ order: 1,
+ content: {
+ title: { string_id: "onboarding-ghostery-title" },
+ text: { string_id: "onboarding-ghostery-text" },
+ icon: "gift",
+ primary_button: {
+ label: { string_id: "onboarding-button-label-try-now" },
+ action: {
+ type: "OPEN_URL",
+ data: {
+ args: "https://addons.mozilla.org/en-US/firefox/addon/ghostery/",
+ where: "tabshifted",
+ },
+ },
+ },
+ },
+ targeting:
+ "trailheadInterrupt == 'control' && providerCohorts.onboarding == 'ghostery'",
+ trigger: { id: "showOnboarding" },
+ },
+ {
+ id: "ONBOARDING_5",
+ template: "onboarding",
+ bundled: 3,
+ order: 4,
+ content: {
+ title: { string_id: "onboarding-fxa-title" },
+ text: { string_id: "onboarding-fxa-text" },
+ icon: "sync",
+ primary_button: {
+ label: { string_id: "onboarding-button-label-get-started" },
+ action: {
+ type: "OPEN_URL",
+ data: {
+ args: await FxAccountsConfig.promiseEmailFirstURI("onboarding"),
+ where: "tabshifted",
+ },
+ },
+ },
+ },
+ targeting:
+ "trailheadInterrupt == 'control' && attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
+ trigger: { id: "showOnboarding" },
+ },
{
id: "TRAILHEAD_1",
template: "trailhead",
@@ -326,13 +436,7 @@ const ONBOARDING_MESSAGES = () => [
{
id: "FXA_1",
template: "fxa_overlay",
- content: {},
trigger: { id: "firstRun" },
- includeBundle: {
- length: 3,
- template: "onboarding",
- trigger: { id: "showOnboarding" },
- },
},
{
id: "RETURN_TO_AMO_1",
@@ -357,11 +461,6 @@ const ONBOARDING_MESSAGES = () => [
label: { string_id: "return-to-amo-get-started-button" },
},
},
- includeBundle: {
- length: 3,
- template: "onboarding",
- trigger: { id: "showOnboarding" },
- },
targeting:
"attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
trigger: { id: "firstRun" },
diff --git a/browser/components/newtab/lib/PanelTestProvider.jsm b/browser/components/newtab/lib/PanelTestProvider.jsm
index 714f8d527d6f..55876e4b8242 100644
--- a/browser/components/newtab/lib/PanelTestProvider.jsm
+++ b/browser/components/newtab/lib/PanelTestProvider.jsm
@@ -79,9 +79,9 @@ const MESSAGES = () => [
// Never saw this message or saw it in the past 4 days or more recent
targeting: `isWhatsNewPanelEnabled &&
(earliestFirefoxVersion && firefoxVersion > earliestFirefoxVersion) &&
- (!messageImpressions['WHATS_NEW_BADGE_${FIREFOX_VERSION}'] ||
- (messageImpressions['WHATS_NEW_BADGE_${FIREFOX_VERSION}']|length >= 1 &&
- currentDate|date - messageImpressions['WHATS_NEW_BADGE_${FIREFOX_VERSION}'][0] <= 4 * 24 * 3600 * 1000))`,
+ messageImpressions[.id == 'WHATS_NEW_BADGE_${FIREFOX_VERSION}']|length == 0 ||
+ (messageImpressions[.id == 'WHATS_NEW_BADGE_${FIREFOX_VERSION}']|length >= 1 &&
+ currentDate|date - messageImpressions[.id == 'WHATS_NEW_BADGE_${FIREFOX_VERSION}'][0] <= 4 * 24 * 3600 * 1000)`,
},
{
id: "WHATS_NEW_70_1",
diff --git a/browser/components/newtab/lib/ToolbarPanelHub.jsm b/browser/components/newtab/lib/ToolbarPanelHub.jsm
index eff8082c3a5f..3d3d7ae783a1 100644
--- a/browser/components/newtab/lib/ToolbarPanelHub.jsm
+++ b/browser/components/newtab/lib/ToolbarPanelHub.jsm
@@ -13,11 +13,6 @@ ChromeUtils.defineModuleGetter(
"EveryWindow",
"resource:///modules/EveryWindow.jsm"
);
-ChromeUtils.defineModuleGetter(
- this,
- "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm"
-);
const WHATSNEW_ENABLED_PREF = "browser.messaging-system.whatsNewPanel.enabled";
@@ -29,16 +24,14 @@ const BUTTON_STRING_ID = "cfr-whatsnew-button";
class _ToolbarPanelHub {
constructor() {
- this.triggerId = "whatsNewPanelOpened";
this._showAppmenuButton = this._showAppmenuButton.bind(this);
this._hideAppmenuButton = this._hideAppmenuButton.bind(this);
this._showToolbarButton = this._showToolbarButton.bind(this);
this._hideToolbarButton = this._hideToolbarButton.bind(this);
}
- async init(waitForInitialized, { getMessages, dispatch }) {
+ async init(waitForInitialized, { getMessages }) {
this._getMessages = getMessages;
- this._dispatch = dispatch;
// Wait for ASRouter messages to become available in order to know
// if we can show the What's New panel
await waitForInitialized;
@@ -139,36 +132,19 @@ class _ToolbarPanelHub {
if (messages && !container.querySelector(".whatsNew-message")) {
let previousDate = 0;
- for (let message of messages) {
+ for (let { content } of messages) {
container.appendChild(
- this._createMessageElements(win, doc, message, previousDate)
+ this._createMessageElements(win, doc, content, previousDate)
);
- previousDate = message.content.published_date;
+ previousDate = content.published_date;
}
}
+ // TODO: TELEMETRY
this._onPanelHidden(win);
-
- // Panel impressions are not associated with one particular message
- // but with a set of messages. We concatenate message ids and send them
- // back for every impression.
- const eventId = {
- id: messages
- .map(({ id }) => id)
- .sort()
- .join(","),
- };
- // Check `mainview` attribute to determine if the panel is shown as a
- // subview (inside the application menu) or as a toolbar dropdown.
- // https://searchfox.org/mozilla-central/rev/07f7390618692fa4f2a674a96b9b677df3a13450/browser/components/customizableui/PanelMultiView.jsm#1268
- const mainview = win.PanelUI.whatsNewPanel.hasAttribute("mainview");
- this.sendUserEventTelemetry(win, "IMPRESSION", eventId, {
- value: { view: mainview ? "toolbar_dropdown" : "application_menu" },
- });
}
- _createMessageElements(win, doc, message, previousDate) {
- const { content } = message;
+ _createMessageElements(win, doc, content, previousDate) {
const messageEl = this._createElement(doc, "div");
messageEl.classList.add("whatsNew-message");
@@ -191,7 +167,7 @@ class _ToolbarPanelHub {
csp: null,
});
- this.sendUserEventTelemetry(win, "CLICK", message);
+ // TODO: TELEMETRY
});
if (content.icon_url) {
@@ -285,30 +261,6 @@ class _ToolbarPanelHub {
_hideElement(document, id) {
document.getElementById(id).setAttribute("hidden", true);
}
-
- _sendTelemetry(ping) {
- this._dispatch({
- type: "TOOLBAR_PANEL_TELEMETRY",
- data: { action: "cfr_user_event", source: "CFR", ...ping },
- });
- }
-
- sendUserEventTelemetry(win, event, message, options = {}) {
- // Only send pings for non private browsing windows
- if (
- win &&
- !PrivateBrowsingUtils.isBrowserPrivate(
- win.ownerGlobal.gBrowser.selectedBrowser
- )
- ) {
- this._sendTelemetry({
- message_id: message.id,
- bucket_id: message.id,
- event,
- value: options.value,
- });
- }
- }
}
this._ToolbarPanelHub = _ToolbarPanelHub;
diff --git a/browser/components/newtab/locales-src/newtab.ftl b/browser/components/newtab/locales-src/newtab.ftl
index e325254a1ee8..52523ae910c3 100644
--- a/browser/components/newtab/locales-src/newtab.ftl
+++ b/browser/components/newtab/locales-src/newtab.ftl
@@ -47,7 +47,7 @@ newtab-topsites-save-button = Save
newtab-topsites-preview-button = Preview
newtab-topsites-add-button = Add
-## Top Sites - Delete history confirmation dialog.
+## Top Sites - Delete history confirmation dialog.
newtab-confirm-delete-history-p1 = Are you sure you want to delete every instance of this page from your history?
# "This action" refers to deleting a page from history.
@@ -89,7 +89,7 @@ newtab-menu-remove-bookmark = Remove Bookmark
# Bookmark is a verb here.
newtab-menu-bookmark = Bookmark
-## Context Menu - Downloaded Menu. "Download" in these cases is not a verb,
+## Context Menu - Downloaded Menu. "Download" in these cases is not a verb,
## it is a noun. As in, "Copy the link that belongs to this downloaded item".
newtab-menu-copy-download-link = Copy Download Link
@@ -117,7 +117,7 @@ newtab-label-recommended = Trending
newtab-label-saved = Saved to { -pocket-brand-name }
newtab-label-download = Downloaded
-## Section Menu: These strings are displayed in the section context menu and are
+## Section Menu: These strings are displayed in the section context menu and are
## meant as a call to action for the given section.
newtab-section-menu-remove-section = Remove Section
@@ -131,13 +131,6 @@ newtab-section-menu-move-up = Move Up
newtab-section-menu-move-down = Move Down
newtab-section-menu-privacy-notice = Privacy Notice
-## Section aria-labels
-
-newtab-section-collapse-section-label =
- .aria-label = Collapse Section
-newtab-section-expand-section-label =
- .aria-label = Expand Section
-
## Section Headers.
newtab-section-header-topsites = Top Sites
diff --git a/browser/components/newtab/test/browser/browser.ini b/browser/components/newtab/test/browser/browser.ini
index 669d18ba2fa4..efc2e7313dd6 100644
--- a/browser/components/newtab/test/browser/browser.ini
+++ b/browser/components/newtab/test/browser/browser.ini
@@ -27,5 +27,4 @@ skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
[browser_topsites_section.js]
[browser_asrouter_cfr.js]
skip-if = fission
-skip-if = fission
[browser_asrouter_bookmarkpanel.js]
diff --git a/browser/components/newtab/test/browser/browser_onboarding_rtamo.js b/browser/components/newtab/test/browser/browser_onboarding_rtamo.js
index 54d2948f3dc2..997760a771f3 100644
--- a/browser/components/newtab/test/browser/browser_onboarding_rtamo.js
+++ b/browser/components/newtab/test/browser/browser_onboarding_rtamo.js
@@ -83,6 +83,9 @@ add_task(async () => {
".ReturnToAMOContainer",
".ReturnToAMOAddonContents",
".ReturnToAMOIcon",
+ // Regular onboarding cards
+ ".onboardingMessageContainer",
+ ".onboardingMessage",
]) {
ok(content.document.querySelector(selector), `Should render ${selector}`);
}
diff --git a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
index d504305cfe30..08b114a1dd0f 100644
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -215,7 +215,6 @@ describe("ASRouter", () => {
Router.waitForInitialized,
{
getMessages: Router.handleMessageRequest,
- dispatch: Router.dispatch,
}
);
@@ -1302,6 +1301,23 @@ describe("ASRouter", () => {
});
});
+ describe("#onMessage: DISMISS_BUNDLE", () => {
+ it("should add all the ids in the bundle to the messageBlockList and send a CLEAR_BUNDLE message", async () => {
+ await Router.setState({ lastMessageId: "foo" });
+ const msg = fakeAsyncMessage({
+ type: "DISMISS_BUNDLE",
+ data: { bundle: FAKE_BUNDLE },
+ });
+ await Router.onMessage(msg);
+
+ assert.calledWith(
+ channel.sendAsyncMessage,
+ PARENT_TO_CHILD_MESSAGE_NAME,
+ { type: "CLEAR_BUNDLE" }
+ );
+ });
+ });
+
describe("#onMessage: UNBLOCK_MESSAGE_BY_ID", () => {
it("should remove the id from the messageBlockList", async () => {
await Router.onMessage(
diff --git a/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx b/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
index 1c80fac0d4ec..aa0347254725 100644
--- a/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
@@ -20,6 +20,21 @@ const FAKE_BELOW_SEARCH_SNIPPET = FAKE_LOCAL_MESSAGES.find(
);
FAKE_MESSAGE = Object.assign({}, FAKE_MESSAGE, { provider: "fakeprovider" });
+const FAKE_BUNDLED_MESSAGE = {
+ bundle: [
+ {
+ id: "foo",
+ template: "onboarding",
+ content: {
+ title: "Foo",
+ primary_button: { label: "Bar" },
+ text: "Foo123",
+ },
+ },
+ ],
+ extraTemplateStrings: {},
+ template: "onboarding",
+};
describe("ASRouterUtils", () => {
let global;
@@ -63,11 +78,6 @@ describe("ASRouterUISurface", () => {
location: { href: "" },
_listeners: new Set(),
_visibilityState: "hidden",
- head: {
- appendChild(el) {
- return el;
- },
- },
get visibilityState() {
return this._visibilityState;
},
@@ -88,15 +98,7 @@ describe("ASRouterUISurface", () => {
return document.createElement("body");
},
getElementById(id) {
- switch (id) {
- case "header-asrouter-container":
- return headerPortal;
- default:
- return footerPortal;
- }
- },
- createElement(tag) {
- return document.createElement(tag);
+ return id === "header-asrouter-container" ? headerPortal : footerPortal;
},
};
global = new GlobalOverrider();
@@ -143,6 +145,11 @@ describe("ASRouterUISurface", () => {
);
});
+ it("should render the component if a bundle of messages is defined", () => {
+ wrapper.setState({ bundle: FAKE_BUNDLED_MESSAGE });
+ assert.isTrue(wrapper.exists());
+ });
+
it("should render a preview banner if message provider is preview", () => {
wrapper.setState({ message: { ...FAKE_MESSAGE, provider: "preview" } });
assert.isTrue(wrapper.find(".snippets-preview-banner").exists());
@@ -166,13 +173,10 @@ describe("ASRouterUISurface", () => {
});
it("should render a trailhead message in the header portal", async () => {
- // wrapper = shallow( );
const message = (await OnboardingMessageProvider.getUntranslatedMessages()).find(
msg => msg.template === "trailhead"
);
-
wrapper.setState({ message });
-
assert.isTrue(headerPortal.childElementCount > 0);
assert.equal(footerPortal.childElementCount, 0);
});
@@ -366,5 +370,18 @@ describe("ASRouterUISurface", () => {
);
assert.propertyVal(payload, "source", "NEWTAB_FOOTER_BAR");
});
+
+ it("should call .sendTelemetry with the right message data when a bundle is dismissed", () => {
+ wrapper.instance().dismissBundle([{ id: 1 }, { id: 2 }, { id: 3 }])();
+
+ assert.calledOnce(ASRouterUtils.sendTelemetry);
+ assert.calledWith(ASRouterUtils.sendTelemetry, {
+ action: "onboarding_user_event",
+ event: "DISMISS",
+ id: "onboarding-cards",
+ message_id: "1,2,3",
+ source: "onboarding-cards",
+ });
+ });
});
});
diff --git a/browser/components/newtab/test/unit/asrouter/constants.js b/browser/components/newtab/test/unit/asrouter/constants.js
index aad2cfc01a62..ebb70ae94093 100644
--- a/browser/components/newtab/test/unit/asrouter/constants.js
+++ b/browser/components/newtab/test/unit/asrouter/constants.js
@@ -4,14 +4,12 @@ export const PARENT_TO_CHILD_MESSAGE_NAME = "ASRouter:parent-to-child";
export const FAKE_LOCAL_MESSAGES = [
{
id: "foo",
- provider: "snippets",
template: "simple_snippet",
content: { title: "Foo", body: "Foo123" },
},
{
id: "foo1",
template: "simple_snippet",
- provider: "snippets",
bundled: 2,
order: 1,
content: { title: "Foo1", body: "Foo123-1" },
@@ -19,7 +17,6 @@ export const FAKE_LOCAL_MESSAGES = [
{
id: "foo2",
template: "simple_snippet",
- provider: "snippets",
bundled: 2,
order: 2,
content: { title: "Foo2", body: "Foo123-2" },
@@ -32,19 +29,16 @@ export const FAKE_LOCAL_MESSAGES = [
{ id: "baz", content: { title: "Foo", body: "Foo123" } },
{
id: "newsletter",
- provider: "snippets",
template: "newsletter_snippet",
content: { title: "Foo", body: "Foo123" },
},
{
id: "fxa",
- provider: "snippets",
template: "fxa_signup_snippet",
content: { title: "Foo", body: "Foo123" },
},
{
id: "belowsearch",
- provider: "snippets",
template: "simple_below_search_snippet",
content: { text: "Foo" },
},
diff --git a/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx
deleted file mode 100644
index 1f45485d22aa..000000000000
--- a/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import {
- helpers,
- FirstRun,
- FLUENT_FILES,
-} from "content-src/asrouter/templates/FirstRun/FirstRun";
-import { Interrupt } from "content-src/asrouter/templates/FirstRun/Interrupt";
-import { Triplets } from "content-src/asrouter/templates/FirstRun/Triplets";
-import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
-import { mount } from "enzyme";
-import React from "react";
-
-const FAKE_TRIPLETS = [
- {
- id: "CARD_1",
- content: {
- title: { string_id: "onboarding-private-browsing-title" },
- text: { string_id: "onboarding-private-browsing-text" },
- icon: "icon",
- primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
- action: {
- type: "OPEN_URL",
- data: { args: "https://example.com/" },
- },
- },
- },
- },
-];
-
-const FAKE_FLOW_PARAMS = {
- deviceId: "foo",
- flowId: "abc1",
- flowBeginTime: 1234,
-};
-
-async function getTestMessage(id) {
- const message = (await OnboardingMessageProvider.getUntranslatedMessages()).find(
- msg => msg.id === id
- );
- return { ...message, bundle: FAKE_TRIPLETS };
-}
-
-describe("", () => {
- let wrapper;
- let message;
- let fakeDoc;
- let sandbox;
-
- async function setup() {
- sandbox = sinon.createSandbox();
- message = await getTestMessage("TRAILHEAD_1");
- fakeDoc = {
- body: document.createElement("body"),
- head: document.createElement("head"),
- createElement: type => document.createElement(type),
- getElementById: () => document.createElement("div"),
- activeElement: document.createElement("div"),
- };
-
- sandbox
- .stub(global, "fetch")
- .withArgs("http://fake.com/endpoint")
- .resolves({
- ok: true,
- status: 200,
- json: () => Promise.resolve(FAKE_FLOW_PARAMS),
- });
-
- wrapper = mount(
- {}}
- sendUserActionTelemetry={() => {}}
- />
- );
- }
-
- beforeEach(setup);
- afterEach(() => {
- sandbox.restore();
- });
-
- it("should render", () => {
- assert.ok(wrapper);
- });
- describe("with both interrupt and triplets", () => {
- it("should render interrupt and triplets", () => {
- assert.lengthOf(wrapper.find(Interrupt), 1, "");
- assert.lengthOf(wrapper.find(Triplets), 1, "");
- });
- it("should show the card panel and hide the content on the Triplets", () => {
- // This is so the container shows up in the background but we can fade in the content when intterupt is closed.
- const tripletsProps = wrapper.find(Triplets).props();
- assert.propertyVal(tripletsProps, "showCardPanel", true);
- assert.propertyVal(tripletsProps, "showContent", false);
- });
- it("should set the UTM term to trailhead-join (for the traihead-join message)", () => {
- const iProps = wrapper.find(Interrupt).props();
- const tProps = wrapper.find(Triplets).props();
- assert.propertyVal(iProps, "UTMTerm", "trailhead-join");
- assert.propertyVal(tProps, "UTMTerm", "trailhead-join-card");
- });
- });
-
- describe("with an interrupt but no triplets", () => {
- beforeEach(() => {
- message.bundle = []; // Empty triplets
- wrapper = mount( );
- });
- it("should render interrupt but no triplets", () => {
- assert.lengthOf(wrapper.find(Interrupt), 1, "");
- assert.lengthOf(wrapper.find(Triplets), 0, "");
- });
- });
-
- describe("with triplets but no interrupt", () => {
- it("should render interrupt but no triplets", () => {
- delete message.content; // Empty interrupt
- wrapper = mount( );
-
- assert.lengthOf(wrapper.find(Interrupt), 0, "");
- assert.lengthOf(wrapper.find(Triplets), 1, "");
- });
- });
-
- describe("with no triplets or interrupt", () => {
- it("should render empty", () => {
- message = { type: "FOO_123" };
- wrapper = mount( );
-
- assert.isTrue(wrapper.isEmptyRender());
- });
- });
-
- it("should load flow params on mount if fxaEndpoint is defined", () => {
- const spy = sandbox.spy(helpers, "fetchFlowParams");
- wrapper = mount(
- {}}
- fxaEndpoint="https://foo.com"
- />
- );
- assert.calledOnce(spy);
- });
-
- it("should load flow params onUpdate if fxaEndpoint is not defined on mount and then later defined", () => {
- const spy = sandbox.spy(helpers, "fetchFlowParams");
- wrapper = mount(
- {}} />
- );
- assert.notCalled(spy);
- wrapper.setProps({ fxaEndpoint: "https://foo.com" });
- assert.calledOnce(spy);
- });
-
- it("should not load flow params again onUpdate if they were already set", () => {
- const spy = sandbox.spy(helpers, "fetchFlowParams");
- wrapper = mount(
- {}}
- fxaEndpoint="https://foo.com"
- />
- );
- wrapper.setProps({ foo: "bar" });
- wrapper.setProps({ foo: "baz" });
- assert.calledOnce(spy);
- });
-
- it("should load fluent files on mount", () => {
- assert.lengthOf(fakeDoc.head.querySelectorAll("link"), FLUENT_FILES.length);
- });
-
- it("should hide the interrupt and show the triplets when onNextScene is called", () => {
- // Simulate calling next scene
- wrapper
- .find(Interrupt)
- .find(".trailheadStart")
- .simulate("click");
-
- assert.lengthOf(wrapper.find(Interrupt), 0, "Interrupt hidden");
- assert.isTrue(
- wrapper
- .find(Triplets)
- .find(".trailheadCardGrid")
- .hasClass("show"),
- "Show triplet content"
- );
- });
-
- it("should hide triplets when closeTriplets is called", () => {
- // Simulate calling next scene
- wrapper
- .find(Triplets)
- .find(".icon-dismiss")
- .simulate("click");
-
- assert.isFalse(
- wrapper
- .find(Triplets)
- .find(".trailheadCardGrid")
- .hasClass("show"),
- "Show triplet content"
- );
- });
-});
diff --git a/browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx
deleted file mode 100644
index c7a7ce1da7bd..000000000000
--- a/browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Interrupt } from "content-src/asrouter/templates/FirstRun/Interrupt";
-import { ReturnToAMO } from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
-import { StartupOverlay } from "content-src/asrouter/templates/StartupOverlay/StartupOverlay";
-import { Trailhead } from "content-src/asrouter/templates//Trailhead/Trailhead";
-import { shallow } from "enzyme";
-import React from "react";
-
-describe("", () => {
- let wrapper;
- it("should render Return TO AMO when the message has a template of return_to_amo_overlay", () => {
- wrapper = shallow(
-
- );
- assert.lengthOf(wrapper.find(ReturnToAMO), 1);
- });
- it("should render Trailhead when the message has a template of trailhead", () => {
- wrapper = shallow(
-
- );
- assert.lengthOf(wrapper.find(Trailhead), 1);
- });
- it("should render StartupOverlay when the message has a template of fxa_overlay", () => {
- wrapper = shallow(
-
- );
- assert.lengthOf(wrapper.find(StartupOverlay), 1);
- });
- it("should throw an error if another type of message is dispatched", () => {
- assert.throws(() => {
- wrapper = shallow(
-
- );
- });
- });
-});
diff --git a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
index 3817b9005e53..1b10bd85bf23 100644
--- a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
@@ -4,7 +4,7 @@ import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
import React from "react";
import { Trailhead } from "content-src/asrouter/templates/Trailhead/Trailhead";
-export const CARDS = [
+const CARDS = [
{
content: {
title: { string_id: "onboarding-private-browsing-title" },
@@ -27,13 +27,11 @@ describe("", () => {
let dispatch;
let onAction;
let sandbox;
- let onNextScene;
beforeEach(async () => {
sandbox = sinon.sandbox.create();
dispatch = sandbox.stub();
onAction = sandbox.stub();
- onNextScene = sandbox.stub();
sandbox.stub(global, "fetch").resolves({
ok: true,
status: 200,
@@ -61,12 +59,10 @@ describe("", () => {
wrapper = mount(
);
});
@@ -75,13 +71,11 @@ describe("", () => {
sandbox.restore();
});
- it("should emit UserEvent SKIPPED_SIGNIN and call nextScene when you click the start browsing button", () => {
+ it("should emit UserEvent SKIPPED_SIGNIN when you click the start browsing button", () => {
let skipButton = wrapper.find(".trailheadStart");
assert.ok(skipButton.exists());
skipButton.simulate("click");
- assert.calledOnce(onNextScene);
-
assert.calledOnce(dispatch);
assert.isUserEventAction(dispatch.firstCall.args[0]);
assert.calledWith(
@@ -125,6 +119,37 @@ describe("", () => {
);
});
+ it("should add utm_* query params to card actions", () => {
+ let { action } = CARDS[0].content.primary_button;
+ wrapper.instance().onCardAction(action);
+ assert.calledOnce(onAction);
+ const url = onAction.firstCall.args[0].data.args;
+ assert.equal(
+ url,
+ "https://example.com/?utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-join-card"
+ );
+ });
+
+ it("should add flow parameters to card action urls if addFlowParams is true", () => {
+ let action = {
+ type: "OPEN_URL",
+ addFlowParams: true,
+ data: { args: "https://example.com/path?foo=bar" },
+ };
+ wrapper.setState({
+ deviceId: "abc",
+ flowId: "123",
+ flowBeginTime: 456,
+ });
+ wrapper.instance().onCardAction(action);
+ assert.calledOnce(onAction);
+ const url = onAction.firstCall.args[0].data.args;
+ assert.equal(
+ url,
+ "https://example.com/path?foo=bar&utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-join-card&device_id=abc&flow_id=123&flow_begin_time=456"
+ );
+ });
+
it("should keep focus in dialog when blurring start button", () => {
const skipButton = wrapper.find(".trailheadStart");
sandbox.stub(dummyNode, "focus");
diff --git a/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx
deleted file mode 100644
index 7698112e6c23..000000000000
--- a/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { mount } from "enzyme";
-import { Triplets } from "content-src/asrouter/templates/FirstRun/Triplets";
-import { OnboardingCard } from "content-src/asrouter/templates/OnboardingMessage/OnboardingMessage";
-import React from "react";
-
-const CARDS = [
- {
- id: "CARD_1",
- content: {
- title: { string_id: "onboarding-private-browsing-title" },
- text: { string_id: "onboarding-private-browsing-text" },
- icon: "icon",
- primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
- action: {
- type: "OPEN_URL",
- data: { args: "https://example.com/" },
- },
- },
- },
- },
-];
-
-describe("", () => {
- let wrapper;
- let sandbox;
- let sendTelemetryStub;
- let onAction;
- let onHide;
-
- async function setup() {
- sandbox = sinon.createSandbox();
- sendTelemetryStub = sandbox.stub();
- onAction = sandbox.stub();
- onHide = sandbox.stub();
-
- wrapper = mount(
-
- );
- }
-
- beforeEach(setup);
- afterEach(() => {
- sandbox.restore();
- });
-
- it("should add an expanded class to container if props.showCardPanel is true", () => {
- wrapper.setProps({ showCardPanel: true });
- assert.isTrue(
- wrapper.find(".trailheadCards").hasClass("expanded"),
- "has .expanded)"
- );
- });
- it("should add a collapsed class to container if props.showCardPanel is true", () => {
- wrapper.setProps({ showCardPanel: false });
- assert.isFalse(
- wrapper.find(".trailheadCards").hasClass("expanded"),
- "has .expanded)"
- );
- });
- it("should send telemetry and call props.hideContainer when the dismiss button is clicked", () => {
- wrapper.find("button.icon-dismiss").simulate("click");
- assert.calledOnce(onHide);
- assert.calledWith(sendTelemetryStub, {
- event: "DISMISS",
- message_id: CARDS[0].id,
- id: "onboarding-cards",
- action: "onboarding_user_event",
- });
- });
- it("should add utm_* query params to card actions and send the right ping when a card button is clicked", () => {
- wrapper
- .find(OnboardingCard)
- .find("button.onboardingButton")
- .simulate("click");
- assert.calledOnce(onAction);
- const url = onAction.firstCall.args[0].data.args;
- assert.equal(
- url,
- "https://example.com/?utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-join-card"
- );
- assert.calledWith(sendTelemetryStub, {
- event: "CLICK_BUTTON",
- message_id: CARDS[0].id,
- id: "TRAILHEAD",
- });
- });
-});
diff --git a/browser/components/newtab/test/unit/content-src/components/CollapsibleSection.test.jsx b/browser/components/newtab/test/unit/content-src/components/CollapsibleSection.test.jsx
index a07ef559ee8e..db02dc08019c 100644
--- a/browser/components/newtab/test/unit/content-src/components/CollapsibleSection.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/CollapsibleSection.test.jsx
@@ -69,6 +69,21 @@ describe("CollapsibleSection", () => {
.simulate("click");
});
+ it("should fire a pref change event when section title arrow is clicked", done => {
+ function dispatch(a) {
+ if (a.type === at.UPDATE_SECTION_PREFS) {
+ assert.equal(a.data.id, DEFAULT_PROPS.id);
+ assert.equal(a.data.value.collapsed, true);
+ done();
+ }
+ }
+ setup({ dispatch });
+ wrapper
+ .find(".click-target")
+ .at(1)
+ .simulate("click");
+ });
+
it("should not fire a pref change when section title is clicked if sectionBody is falsy", () => {
const dispatch = sinon.spy();
setup({ dispatch });
diff --git a/browser/components/newtab/test/unit/content-src/components/ContextMenu.test.jsx b/browser/components/newtab/test/unit/content-src/components/ContextMenu.test.jsx
index 3266f0f949ff..2288f6c0c82f 100644
--- a/browser/components/newtab/test/unit/content-src/components/ContextMenu.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ContextMenu.test.jsx
@@ -128,10 +128,7 @@ describe("", () => {
it("should be tabbable", () => {
const options = [{ label: "item1", icon: "icon1" }, { type: "separator" }];
const wrapper = mount( );
- assert.equal(
- wrapper.find(".context-menu-item").props().role,
- "presentation"
- );
+ assert.equal(wrapper.find(".context-menu-item").props().role, "menuitem");
});
it("should call onUpdate with false when an option is clicked", () => {
const onUpdate = sinon.spy();
diff --git a/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx b/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
index 93c5580969a9..190a7c97e3b4 100644
--- a/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
@@ -66,6 +66,10 @@ describe("", () => {
sendUserActionTelemetryStub.reset();
});
+ it("should call onReady on componentDidMount", () => {
+ assert.calledOnce(onReady);
+ });
+
it("should send telemetry on block", () => {
wrapper.instance().onBlockButton();
diff --git a/browser/components/newtab/test/unit/content-src/components/StartupOverlay.test.jsx b/browser/components/newtab/test/unit/content-src/components/StartupOverlay.test.jsx
index 68ac4dd38bc0..f37dcb6a7d65 100644
--- a/browser/components/newtab/test/unit/content-src/components/StartupOverlay.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/StartupOverlay.test.jsx
@@ -1,40 +1,44 @@
import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
import { mount } from "enzyme";
import React from "react";
-import { StartupOverlay } from "content-src/asrouter/templates/StartupOverlay/StartupOverlay";
+import { _StartupOverlay as StartupOverlay } from "content-src/asrouter/templates/StartupOverlay/StartupOverlay";
describe("", () => {
let wrapper;
let dispatch;
+ let onReady;
let onBlock;
let sandbox;
beforeEach(() => {
- sandbox = sinon.createSandbox();
+ sandbox = sinon.sandbox.create();
dispatch = sandbox.stub();
+ onReady = sandbox.stub();
onBlock = sandbox.stub();
- wrapper = mount( );
+ wrapper = mount(
+
+ );
});
afterEach(() => {
sandbox.restore();
});
- it("should add show class after mount and timeout", async () => {
+ it("should not render if state.show is false", () => {
+ wrapper.setState({ overlayRemoved: true });
+ assert.isTrue(wrapper.isEmptyRender());
+ });
+
+ it("should call prop.onReady after mount + timeout", async () => {
const clock = sandbox.useFakeTimers();
- wrapper = mount( );
- assert.isFalse(
- wrapper.find(".overlay-wrapper").hasClass("show"),
- ".overlay-wrapper does not have .show class"
+ wrapper = mount(
+
);
+ wrapper.setState({ overlayRemoved: false });
clock.tick(10);
- wrapper.update();
- assert.isTrue(
- wrapper.find(".overlay-wrapper").hasClass("show"),
- ".overlay-wrapper has .show class"
- );
+ assert.calledOnce(onReady);
});
it("should emit UserEvent SKIPPED_SIGNIN when you click the skip button", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/addUtmParams.test.js b/browser/components/newtab/test/unit/content-src/components/addUtmParams.test.js
deleted file mode 100644
index 8c872a575fc7..000000000000
--- a/browser/components/newtab/test/unit/content-src/components/addUtmParams.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { addUtmParams } from "content-src/asrouter/templates/FirstRun/addUtmParams";
-
-describe("addUtmParams", () => {
- it("should convert a string URL", () => {
- const result = addUtmParams("https://foo.com", "foo");
- assert.equal(result.hostname, "foo.com");
- });
- it("should add all base params", () => {
- assert.match(
- addUtmParams(new URL("https://foo.com"), "foo").toString(),
- /utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral/
- );
- });
- it("should add utm_term", () => {
- const params = addUtmParams(new URL("https://foo.com"), "foo").searchParams;
- assert.equal(params.get("utm_term"), "foo", "utm_term");
- });
-});
diff --git a/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
index e8d6c8eb8787..01ab3a8c1af0 100644
--- a/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
+++ b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
@@ -16,8 +16,6 @@ describe("ToolbarPanelHub", () => {
let removeObserverStub;
let getBoolPrefStub;
let waitForInitializedStub;
- let isBrowserPrivateStub;
- let fakeDispatch;
beforeEach(async () => {
sandbox = sinon.createSandbox();
@@ -30,7 +28,6 @@ describe("ToolbarPanelHub", () => {
querySelector: sandbox.stub().returns(null),
appendChild: sandbox.stub(),
addEventListener: sandbox.stub(),
- hasAttribute: sandbox.stub(),
};
fakeDocument = {
l10n: {
@@ -61,10 +58,6 @@ describe("ToolbarPanelHub", () => {
MozXULElement: { insertFTLIfNeeded: sandbox.stub() },
ownerGlobal: {
openLinkIn: sandbox.stub(),
- gBrowser: "gBrowser",
- },
- PanelUI: {
- whatsNewPanel: fakeElementById,
},
};
everyWindowStub = {
@@ -74,8 +67,6 @@ describe("ToolbarPanelHub", () => {
addObserverStub = sandbox.stub();
removeObserverStub = sandbox.stub();
getBoolPrefStub = sandbox.stub();
- fakeDispatch = sandbox.stub();
- isBrowserPrivateStub = sandbox.stub();
globals.set("EveryWindow", everyWindowStub);
globals.set("Services", {
...Services,
@@ -85,9 +76,6 @@ describe("ToolbarPanelHub", () => {
getBoolPref: getBoolPrefStub,
},
});
- globals.set("PrivateBrowsingUtils", {
- isBrowserPrivate: isBrowserPrivateStub,
- });
});
afterEach(() => {
instance.uninit();
@@ -97,10 +85,10 @@ describe("ToolbarPanelHub", () => {
it("should create an instance", () => {
assert.ok(instance);
});
- it("should not enableAppmenuButton() on init() if pref is not enabled", async () => {
+ it("should not enableAppmenuButton() on init() if pref is not enabled", () => {
getBoolPrefStub.returns(false);
instance.enableAppmenuButton = sandbox.stub();
- await instance.init(waitForInitializedStub, { getMessages: () => {} });
+ instance.init(waitForInitializedStub, { getMessages: () => {} });
assert.notCalled(instance.enableAppmenuButton);
});
it("should enableAppmenuButton() on init() if pref is enabled", async () => {
@@ -227,221 +215,85 @@ describe("ToolbarPanelHub", () => {
instance._hideToolbarButton(fakeWindow);
assert.calledWith(fakeElementById.setAttribute, "hidden", true);
});
- describe("#renderMessages", () => {
- let getMessagesStub;
- beforeEach(() => {
- getMessagesStub = sandbox.stub();
- instance.init(waitForInitializedStub, {
- getMessages: getMessagesStub,
- dispatch: fakeDispatch,
- });
+ it("should render messages to the panel on renderMessages()", async () => {
+ const messages = (await PanelTestProvider.getMessages()).filter(
+ m => m.template === "whatsnew_panel_message"
+ );
+ messages[0].content.link_text = { string_id: "link_text_id" };
+ instance.init(waitForInitializedStub, {
+ getMessages: sandbox
+ .stub()
+ .returns([messages[0], messages[2], messages[1]]),
});
- it("should render messages to the panel on renderMessages()", async () => {
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
+ await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+ for (let message of messages) {
+ assert.ok(
+ createdElements.find(
+ el => el.tagName === "h2" && el.textContent === message.content.title
+ )
);
- messages[0].content.link_text = { string_id: "link_text_id" };
+ assert.ok(
+ createdElements.find(
+ el => el.tagName === "p" && el.textContent === message.content.body
+ )
+ );
+ }
+ // Call the click handler to make coverage happy.
+ eventListeners.click();
+ assert.calledOnce(fakeWindow.ownerGlobal.openLinkIn);
+ });
+ it("should only render unique dates (no duplicates)", async () => {
+ instance._createDateElement = sandbox.stub();
+ const messages = (await PanelTestProvider.getMessages()).filter(
+ m => m.template === "whatsnew_panel_message"
+ );
+ const uniqueDates = [
+ ...new Set(messages.map(m => m.content.published_date)),
+ ];
+ instance.init(waitForInitializedStub, {
+ getMessages: sandbox.stub().returns(messages),
+ });
+ await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+ assert.callCount(instance._createDateElement, uniqueDates.length);
+ });
+ it("should listen for panelhidden and remove the toolbar button", async () => {
+ instance.init(waitForInitializedStub, {
+ getMessages: sandbox.stub().returns([]),
+ });
+ fakeDocument.getElementById
+ .withArgs("customizationui-widget-panel")
+ .returns(null);
- getMessagesStub.returns([messages[0], messages[2], messages[1]]);
+ await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+ assert.notCalled(fakeElementById.addEventListener);
+ });
+ it("should listen for panelhidden and remove the toolbar button", async () => {
+ instance.init(waitForInitializedStub, {
+ getMessages: sandbox.stub().returns([]),
+ });
- for (let message of messages) {
- assert.ok(
- createdElements.find(
- el =>
- el.tagName === "h2" && el.textContent === message.content.title
- )
- );
- assert.ok(
- createdElements.find(
- el => el.tagName === "p" && el.textContent === message.content.body
- )
- );
+ await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+
+ assert.calledOnce(fakeElementById.addEventListener);
+ assert.calledWithExactly(
+ fakeElementById.addEventListener,
+ "popuphidden",
+ sinon.match.func,
+ {
+ once: true,
}
- // Call the click handler to make coverage happy.
- eventListeners.click();
- assert.calledOnce(fakeWindow.ownerGlobal.openLinkIn);
- });
- it("should only render unique dates (no duplicates)", async () => {
- instance._createDateElement = sandbox.stub();
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
- );
- const uniqueDates = [
- ...new Set(messages.map(m => m.content.published_date)),
- ];
- getMessagesStub.returns(messages);
+ );
+ const [, cb] = fakeElementById.addEventListener.firstCall.args;
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+ assert.notCalled(everyWindowStub.unregisterCallback);
- assert.callCount(instance._createDateElement, uniqueDates.length);
- });
- it("should listen for panelhidden and remove the toolbar button", async () => {
- getMessagesStub.returns([]);
- fakeDocument.getElementById
- .withArgs("customizationui-widget-panel")
- .returns(null);
+ cb();
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.notCalled(fakeElementById.addEventListener);
- });
- it("should listen for panelhidden and remove the toolbar button", async () => {
- getMessagesStub.returns([]);
-
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.calledOnce(fakeElementById.addEventListener);
- assert.calledWithExactly(
- fakeElementById.addEventListener,
- "popuphidden",
- sinon.match.func,
- {
- once: true,
- }
- );
- const [, cb] = fakeElementById.addEventListener.firstCall.args;
-
- assert.notCalled(everyWindowStub.unregisterCallback);
-
- cb();
-
- assert.calledOnce(everyWindowStub.unregisterCallback);
- assert.calledWithExactly(
- everyWindowStub.unregisterCallback,
- "whats-new-menu-button"
- );
- });
- describe("#IMPRESSION", () => {
- it("should dispatch a IMPRESSION for messages", async () => {
- // means panel is triggered from the toolbar button
- fakeElementById.hasAttribute.returns(true);
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
- );
- getMessagesStub.returns(messages);
- const spy = sandbox.spy(instance, "sendUserEventTelemetry");
-
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.calledOnce(spy);
- assert.calledOnce(fakeDispatch);
- assert.propertyVal(
- spy.firstCall.args[2],
- "id",
- messages
- .map(({ id }) => id)
- .sort()
- .join(",")
- );
- });
- it("should dispatch a CLICK for clicking a message", async () => {
- // means panel is triggered from the toolbar button
- fakeElementById.hasAttribute.returns(true);
- // Force to render the message
- fakeElementById.querySelector.returns(null);
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
- );
- getMessagesStub.returns([messages[0]]);
- const spy = sandbox.spy(instance, "sendUserEventTelemetry");
-
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.calledOnce(spy);
- assert.calledOnce(fakeDispatch);
-
- spy.resetHistory();
-
- // Message click event listener cb
- eventListeners.click();
-
- assert.calledOnce(spy);
- assert.calledWithExactly(spy, fakeWindow, "CLICK", messages[0]);
- });
- it("should dispatch a IMPRESSION with toolbar_dropdown", async () => {
- // means panel is triggered from the toolbar button
- fakeElementById.hasAttribute.returns(true);
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
- );
- getMessagesStub.resolves(messages);
- const spy = sandbox.spy(instance, "sendUserEventTelemetry");
- const panelPingId = messages
- .map(({ id }) => id)
- .sort()
- .join(",");
-
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.calledOnce(spy);
- assert.calledWithExactly(
- spy,
- fakeWindow,
- "IMPRESSION",
- {
- id: panelPingId,
- },
- {
- value: {
- view: "toolbar_dropdown",
- },
- }
- );
- assert.calledOnce(fakeDispatch);
- const {
- args: [dispatchPayload],
- } = fakeDispatch.lastCall;
- assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
- assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
- assert.propertyVal(
- dispatchPayload.data.value,
- "view",
- "toolbar_dropdown"
- );
- });
- it("should dispatch a IMPRESSION with application_menu", async () => {
- // means panel is triggered as a subview in the application menu
- fakeElementById.hasAttribute.returns(false);
- const messages = (await PanelTestProvider.getMessages()).filter(
- m => m.template === "whatsnew_panel_message"
- );
- getMessagesStub.resolves(messages);
- const spy = sandbox.spy(instance, "sendUserEventTelemetry");
- const panelPingId = messages
- .map(({ id }) => id)
- .sort()
- .join(",");
-
- await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
-
- assert.calledOnce(spy);
- assert.calledWithExactly(
- spy,
- fakeWindow,
- "IMPRESSION",
- {
- id: panelPingId,
- },
- {
- value: {
- view: "application_menu",
- },
- }
- );
- assert.calledOnce(fakeDispatch);
- const {
- args: [dispatchPayload],
- } = fakeDispatch.lastCall;
- assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
- assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
- assert.propertyVal(
- dispatchPayload.data.value,
- "view",
- "application_menu"
- );
- });
- });
+ assert.calledOnce(everyWindowStub.unregisterCallback);
+ assert.calledWithExactly(
+ everyWindowStub.unregisterCallback,
+ "whats-new-menu-button"
+ );
});
});
diff --git a/browser/locales/en-US/browser/newtab/newtab.ftl b/browser/locales/en-US/browser/newtab/newtab.ftl
index e325254a1ee8..52523ae910c3 100644
--- a/browser/locales/en-US/browser/newtab/newtab.ftl
+++ b/browser/locales/en-US/browser/newtab/newtab.ftl
@@ -47,7 +47,7 @@ newtab-topsites-save-button = Save
newtab-topsites-preview-button = Preview
newtab-topsites-add-button = Add
-## Top Sites - Delete history confirmation dialog.
+## Top Sites - Delete history confirmation dialog.
newtab-confirm-delete-history-p1 = Are you sure you want to delete every instance of this page from your history?
# "This action" refers to deleting a page from history.
@@ -89,7 +89,7 @@ newtab-menu-remove-bookmark = Remove Bookmark
# Bookmark is a verb here.
newtab-menu-bookmark = Bookmark
-## Context Menu - Downloaded Menu. "Download" in these cases is not a verb,
+## Context Menu - Downloaded Menu. "Download" in these cases is not a verb,
## it is a noun. As in, "Copy the link that belongs to this downloaded item".
newtab-menu-copy-download-link = Copy Download Link
@@ -117,7 +117,7 @@ newtab-label-recommended = Trending
newtab-label-saved = Saved to { -pocket-brand-name }
newtab-label-download = Downloaded
-## Section Menu: These strings are displayed in the section context menu and are
+## Section Menu: These strings are displayed in the section context menu and are
## meant as a call to action for the given section.
newtab-section-menu-remove-section = Remove Section
@@ -131,13 +131,6 @@ newtab-section-menu-move-up = Move Up
newtab-section-menu-move-down = Move Down
newtab-section-menu-privacy-notice = Privacy Notice
-## Section aria-labels
-
-newtab-section-collapse-section-label =
- .aria-label = Collapse Section
-newtab-section-expand-section-label =
- .aria-label = Expand Section
-
## Section Headers.
newtab-section-header-topsites = Top Sites