Backed out changeset 08f94ba4c50c (bug 1659150) for bc failures on browser_all_files_referenced.js. CLOSED TREE
|
@ -1386,8 +1386,8 @@ pref("browser.newtabpage.activity-stream.feeds.section.topstories", true);
|
|||
pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", false);
|
||||
#endif
|
||||
|
||||
// Used to display triplet cards on newtab
|
||||
pref("trailhead.firstrun.newtab.triplets", "");
|
||||
pref("trailhead.firstrun.branches", "join-dynamic");
|
||||
|
||||
// Separate about welcome
|
||||
pref("browser.aboutwelcome.enabled", true);
|
||||
// Used to set multistage welcome UX
|
||||
|
|
|
@ -567,6 +567,7 @@ var Policies = {
|
|||
onBeforeAddons(manager, param) {
|
||||
if (param) {
|
||||
setAndLockPref("identity.fxaccounts.enabled", false);
|
||||
setAndLockPref("trailhead.firstrun.branches", "nofirstrun-empty");
|
||||
setAndLockPref("browser.aboutwelcome.enabled", false);
|
||||
}
|
||||
},
|
||||
|
@ -1316,6 +1317,7 @@ var Policies = {
|
|||
onProfileAfterChange(manager, param) {
|
||||
let url = param ? param.href : "";
|
||||
setAndLockPref("startup.homepage_welcome_url", url);
|
||||
setAndLockPref("trailhead.firstrun.branches", "nofirstrun-empty");
|
||||
setAndLockPref("browser.aboutwelcome.enabled", false);
|
||||
},
|
||||
},
|
||||
|
@ -1983,6 +1985,7 @@ var Policies = {
|
|||
manager.disallowFeature("urlbarinterventions");
|
||||
}
|
||||
if ("SkipOnboarding") {
|
||||
setAndLockPref("trailhead.firstrun.branches", "nofirstrun-empty");
|
||||
setAndLockPref("browser.aboutwelcome.enabled", false);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -43,6 +43,9 @@ module.exports = {
|
|||
"content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx",
|
||||
"content-src/asrouter/templates/OnboardingMessage/**",
|
||||
"content-src/asrouter/templates/FirstRun/**",
|
||||
"content-src/asrouter/templates/Trailhead/**",
|
||||
"content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt.jsx",
|
||||
"content-src/asrouter/components/FxASignupForm/FxASignupForm.jsx",
|
||||
"content-src/components/TopSites/**",
|
||||
"content-src/components/MoreRecommendations/MoreRecommendations.jsx",
|
||||
"content-src/components/CollapsibleSection/CollapsibleSection.jsx",
|
||||
|
|
|
@ -151,6 +151,7 @@ for (const type of [
|
|||
"TOP_SITES_UPDATED",
|
||||
"TOTAL_BOOKMARKS_REQUEST",
|
||||
"TOTAL_BOOKMARKS_RESPONSE",
|
||||
"TRAILHEAD_ENROLL_EVENT",
|
||||
"UNINIT",
|
||||
"UPDATE_PINNED_SEARCH_SHORTCUTS",
|
||||
"UPDATE_SEARCH_SHORTCUTS",
|
||||
|
|
|
@ -11,9 +11,9 @@ import React from "react";
|
|||
import ReactDOM from "react-dom";
|
||||
import { reducers } from "common/Reducers.jsm";
|
||||
|
||||
export const NewTab = ({ store }) => (
|
||||
export const NewTab = ({ store, isFirstrun }) => (
|
||||
<Provider store={store}>
|
||||
<Base />
|
||||
<Base isFirstrun={isFirstrun} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
|
@ -43,12 +43,24 @@ export function renderWithoutState() {
|
|||
doRequest();
|
||||
}
|
||||
|
||||
ReactDOM.hydrate(<NewTab store={store} />, document.getElementById("root"));
|
||||
ReactDOM.hydrate(
|
||||
<NewTab
|
||||
store={store}
|
||||
isFirstrun={global.document.location.href === "about:welcome"}
|
||||
/>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
}
|
||||
|
||||
export function renderCache(initialState) {
|
||||
const store = initStore(reducers, initialState);
|
||||
new DetectUserSessionStart(store).sendEventOrAddListener();
|
||||
|
||||
ReactDOM.hydrate(<NewTab store={store} />, document.getElementById("root"));
|
||||
ReactDOM.hydrate(
|
||||
<NewTab
|
||||
store={store}
|
||||
isFirstrun={global.document.location.href === "about:welcome"}
|
||||
/>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,13 @@ import { FirstRun } from "./templates/FirstRun/FirstRun";
|
|||
|
||||
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
|
||||
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
|
||||
const TEMPLATES_ABOVE_PAGE = ["extended_triplets"];
|
||||
const TEMPLATES_ABOVE_PAGE = [
|
||||
"trailhead",
|
||||
"full_page_interrupt",
|
||||
"return_to_amo_overlay",
|
||||
"extended_triplets",
|
||||
];
|
||||
const FIRST_RUN_TEMPLATES = TEMPLATES_ABOVE_PAGE;
|
||||
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
|
||||
|
||||
export const ASRouterUtils = {
|
||||
|
@ -121,7 +127,7 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
this.onUserAction = this.onUserAction.bind(this);
|
||||
this.fetchFlowParams = this.fetchFlowParams.bind(this);
|
||||
|
||||
this.state = { message: {} };
|
||||
this.state = { message: {}, interruptCleared: false };
|
||||
if (props.document) {
|
||||
this.headerPortal = props.document.getElementById(
|
||||
"header-asrouter-container"
|
||||
|
@ -253,6 +259,8 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
|
||||
if (id === this.state.message.id) {
|
||||
this.setState({ message: {} });
|
||||
// Remove any styles related to the RTAMO message
|
||||
document.body.classList.remove("welcome", "hide-main", "amo");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +269,9 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
case "SET_MESSAGE":
|
||||
this.setState({ message: action.data });
|
||||
break;
|
||||
case "CLEAR_INTERRUPT":
|
||||
this.setState({ interruptCleared: true });
|
||||
break;
|
||||
case "CLEAR_MESSAGE":
|
||||
this.clearMessage(action.data.id);
|
||||
break;
|
||||
|
@ -279,10 +290,21 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
}
|
||||
|
||||
requestMessage(endpoint) {
|
||||
ASRouterUtils.sendMessage({
|
||||
type: "NEWTAB_MESSAGE_REQUEST",
|
||||
data: { endpoint },
|
||||
});
|
||||
// If we are loading about:welcome we want to trigger the onboarding messages
|
||||
if (
|
||||
this.props.document &&
|
||||
this.props.document.location.href === "about:welcome"
|
||||
) {
|
||||
ASRouterUtils.sendMessage({
|
||||
type: "TRIGGER",
|
||||
data: { trigger: { id: "firstRun" } },
|
||||
});
|
||||
} else {
|
||||
ASRouterUtils.sendMessage({
|
||||
type: "NEWTAB_MESSAGE_REQUEST",
|
||||
data: { endpoint },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -379,7 +401,7 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
|
||||
renderFirstRun() {
|
||||
const { message } = this.state;
|
||||
if (TEMPLATES_ABOVE_PAGE.includes(message.template)) {
|
||||
if (FIRST_RUN_TEMPLATES.includes(message.template)) {
|
||||
return (
|
||||
<ImpressionsWrapper
|
||||
id="FIRST_RUN"
|
||||
|
@ -391,9 +413,11 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
>
|
||||
<FirstRun
|
||||
document={this.props.document}
|
||||
interruptCleared={this.state.interruptCleared}
|
||||
message={message}
|
||||
sendUserActionTelemetry={this.sendUserActionTelemetry}
|
||||
executeAction={ASRouterUtils.executeAction}
|
||||
dispatch={this.props.dispatch}
|
||||
onBlockById={ASRouterUtils.blockById}
|
||||
onDismiss={this.onDismissById(this.state.message.id)}
|
||||
fxaEndpoint={this.props.fxaEndpoint}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* 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 { actionCreators as ac } from "common/Actions.jsm";
|
||||
import {
|
||||
addUtmParams,
|
||||
BASE_PARAMS,
|
||||
} from "../../templates/FirstRun/addUtmParams";
|
||||
import React from "react";
|
||||
|
||||
export class FxASignupForm extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
this.handleSignIn = this.handleSignIn.bind(this);
|
||||
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
};
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.props.document
|
||||
.getElementById("fxaSignupForm")
|
||||
.querySelector("input[name=email]");
|
||||
}
|
||||
|
||||
onSubmit(event) {
|
||||
let userEvent = "SUBMIT_EMAIL";
|
||||
const { email } = event.target.elements;
|
||||
if (email.disabled) {
|
||||
userEvent = "SUBMIT_SIGNIN";
|
||||
} else if (!email.value.length) {
|
||||
email.required = true;
|
||||
email.checkValidity();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Report to telemetry additional information about the form submission.
|
||||
const value = { has_flow_params: !!this.props.flowParams.flowId.length };
|
||||
this.props.dispatch(ac.UserEvent({ event: userEvent, value }));
|
||||
|
||||
global.addEventListener("visibilitychange", this.props.onClose);
|
||||
}
|
||||
|
||||
handleSignIn(event) {
|
||||
// Set disabled to prevent email from appearing in url resulting in the wrong page
|
||||
this.email.disabled = true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Start with focus in the email input box
|
||||
if (this.email) {
|
||||
this.email.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onInputChange(e) {
|
||||
let error = e.target.previousSibling;
|
||||
this.setState({ emailInput: e.target.value });
|
||||
error.classList.remove("active");
|
||||
e.target.classList.remove("invalid");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { content, UTMTerm } = this.props;
|
||||
return (
|
||||
<div
|
||||
id="fxaSignupForm"
|
||||
role="group"
|
||||
aria-labelledby="joinFormHeader"
|
||||
aria-describedby="joinFormBody"
|
||||
className="fxaSignupForm"
|
||||
>
|
||||
<h3 id="joinFormHeader" data-l10n-id={content.form.title.string_id} />
|
||||
<p id="joinFormBody" data-l10n-id={content.form.text.string_id} />
|
||||
<form
|
||||
method="get"
|
||||
action={this.props.fxaEndpoint}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onSubmit={this.onSubmit}
|
||||
>
|
||||
<input name="action" type="hidden" value="email" />
|
||||
<input name="context" type="hidden" value="fx_desktop_v3" />
|
||||
<input
|
||||
name="entrypoint"
|
||||
type="hidden"
|
||||
value="activity-stream-firstrun"
|
||||
/>
|
||||
<input name="utm_source" type="hidden" value="activity-stream" />
|
||||
<input
|
||||
name="utm_campaign"
|
||||
type="hidden"
|
||||
value={BASE_PARAMS.utm_campaign}
|
||||
/>
|
||||
<input name="utm_term" type="hidden" value={UTMTerm} />
|
||||
<input
|
||||
name="device_id"
|
||||
type="hidden"
|
||||
value={this.props.flowParams.deviceId}
|
||||
/>
|
||||
<input
|
||||
name="flow_id"
|
||||
type="hidden"
|
||||
value={this.props.flowParams.flowId}
|
||||
/>
|
||||
<input
|
||||
name="flow_begin_time"
|
||||
type="hidden"
|
||||
value={this.props.flowParams.flowBeginTime}
|
||||
/>
|
||||
<input name="style" type="hidden" value="trailhead" />
|
||||
<p
|
||||
data-l10n-id="onboarding-join-form-email-error"
|
||||
className="error"
|
||||
/>
|
||||
<input
|
||||
data-l10n-id={content.form.email.string_id}
|
||||
name="email"
|
||||
type="email"
|
||||
onInvalid={this.onInputInvalid}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
<p className="fxa-terms" data-l10n-id="onboarding-join-form-legal">
|
||||
<a
|
||||
data-l10n-name="terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={addUtmParams(
|
||||
"https://accounts.firefox.com/legal/terms",
|
||||
UTMTerm
|
||||
)}
|
||||
/>
|
||||
<a
|
||||
data-l10n-name="privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={addUtmParams(
|
||||
"https://accounts.firefox.com/legal/privacy",
|
||||
UTMTerm
|
||||
)}
|
||||
/>
|
||||
</p>
|
||||
<button data-l10n-id={content.form.button.string_id} type="submit" />
|
||||
{this.props.showSignInLink && (
|
||||
<div className="fxa-signin">
|
||||
<span data-l10n-id="onboarding-join-form-signin-label" />
|
||||
<button
|
||||
data-l10n-id="onboarding-join-form-signin"
|
||||
onClick={this.handleSignIn}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FxASignupForm.defaultProps = { document: global.document };
|
|
@ -0,0 +1,126 @@
|
|||
.fxaSignupForm {
|
||||
min-width: 260px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.fxa-terms {
|
||||
margin: 4px 30px 20px;
|
||||
|
||||
a,
|
||||
& {
|
||||
color: $white-70;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.fxa-signin {
|
||||
font-size: 16px;
|
||||
margin-top: 19px;
|
||||
|
||||
span {
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: initial;
|
||||
text-decoration: underline;
|
||||
color: $white;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
|
||||
.error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: $white;
|
||||
border: 1px solid $grey-50;
|
||||
box-shadow: none;
|
||||
color: $grey-70;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms;
|
||||
|
||||
&:hover {
|
||||
border-color: $grey-90;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $blue-50;
|
||||
box-shadow: 0 0 0 3px $email-input-focus;
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
border-color: $red-60;
|
||||
}
|
||||
|
||||
&.invalid:focus {
|
||||
box-shadow: 0 0 0 3px $email-input-invalid;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $blue-60;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $trailhead-blue-60;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: dotted 1px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $trailhead-blue-70;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,40 @@
|
|||
# First run on-boarding flow
|
||||
|
||||
First Run flow describes the entire experience users have after Firefox has successfully been installed up until the first instance of new tab is shown.
|
||||
First run help onboard new users by showing relevant messaging on about:welcome and about:newtab using triplets.
|
||||
First run help onboard new users by showing relevant messaging on about:welcome and about:newtab using interrupts and triplets.
|
||||
|
||||
### First Run Multistage
|
||||
A full-page multistep experience that shows up on first run since Fx80 with browser.aboutwelcome.enabled pref as true.
|
||||
## Interrupts
|
||||
A first run experience shown on about:welcome page and decide UI based on messaging template provided. In Firefox 72, interrupt can be one of below three types
|
||||
|
||||
Setting browser.aboutwelcome.enabled to false make first run looks like about:newtab and hides about:welcome
|
||||
### First Run Modal
|
||||
A modal that shows up on first run, usually the first stage.
|
||||
|
||||
In 71+, below modal interrupts are supported:
|
||||
* join - purple first run modal with "Meet Firefox + Products / Knowledge Privacy + Join Firefox" messaging
|
||||
* sync - purple first run modal but with 70 fxa messaging
|
||||
* modal_control - First Run Modal control with same messaging as “join” modal
|
||||
* modal_variant_a - First Run Modal with "Get the most + Sync/Monitor/Lockwise + Start Here" messaging.
|
||||
* modal_variant_b - First Run Modal with "Supercharge privacy + Sync/Monitor/Lockwise + Start Here" messaging.
|
||||
* modal_variant_c - First Run Modal with "Add Privacy + Sync/Monitor/Lockwise + Start Here" messaging.
|
||||
* modal_variant_f - First Run Modal with "Meet Firefox + Products / Knowledge Privacy + Start Here" messaging.
|
||||
|
||||
|
||||
### First Run Takeover
|
||||
A full-page experience that shows up on first run, usually the first stage (a previous variant of this was the blue FxA Sync sign-in page).
|
||||
|
||||
A modal less page showing signup form and triplet messaging together on the same page.
|
||||
* full_page_d - FxA signup form on top with triplet messaging on bottom
|
||||
* full_page_e - FxA signup form on bottom with triplet messaging on top
|
||||
|
||||
### First Run Return to AMO
|
||||
Part of a custom First Run Flow for users that installed Firefox after attempting to add an add-on from another browser. This is a full-page experience on first run.
|
||||
|
||||
Please Note: This is unique interrupt experience shown on about:welcome and not controlled by interrupt value of pref 'trailhead.firstrun.branches' and instead uses attribution targeting condition below
|
||||
|
||||
``` "attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'"```
|
||||
|
||||
## Triplets
|
||||
The cards that show up above the new tab content on the first instance of new tab. Setting browser.aboutwelcome.enabled to false and trailhead.firstrun.newtab.triplets to one of values below hides multistage welcome and takes user straight to triplets on opening about:welcome page
|
||||
The cards that show up above the new tab content on the first instance of new tab, usually the second stage.
|
||||
|
||||
* supercharge - Shows Sync, Monitor and Mobile onboarding cards. Supported in 71+.
|
||||
* payoff - Shows Monitor, Facbook Container and Firefox Send onboarding cards. Supported in 71 only.
|
||||
|
@ -20,3 +45,18 @@ In 72+
|
|||
* static - same experience as ‘supercharge’ triplet - with Sync, Monitor and Mobile onboarding cards
|
||||
* dynamic - Dynamic triplets showing three onboarding cards (Sync, Monitor and Private Browsing) that gets swapped with preselected list of cards that satisfies targeting rules. Preselected cards supported are Send Tab, Mobile and Lockwise.
|
||||
* dynamic_chrome - Dynamic triplets showing three onboarding cards (Chrome switchers, Sync and Monitor) that gets swapped with preselected list of cards that satisfies targeting rules. Preselected cards supported are Private Browsing, Send Tab, Mobile and Lockwise.
|
||||
|
||||
## Misc
|
||||
Below experiences are controlled by using following interrupt values inside 'trailhead.firstrun.branches'
|
||||
* nofirstrun - nothing - looks like about:newtab and hides both first and second stage of about:welcome
|
||||
* cards - no modal straight to triplet. This hides only first stage and takes user straight to triplets on opening about:welcome page.
|
||||
|
||||
|
||||
## How to switch between first run experiences
|
||||
|
||||
First run experiences are controlled by pref 'trailhead.firstrun.branches'. This pref value follow format ```'<interrupt>-<triplet>'``` where ```<interrupt>``` is the interrupt message name from interrupt section above and ```<triplet>``` is triplet message name. If no value is set for 'trailhead.firstrun.branches', by default 'join-supercharge' interrupt and triplet experience is used. 'join-supercharge' is default first run experience in 71+.
|
||||
|
||||
For Example:
|
||||
* Open about:config and set preference 'trailhead.firstrun.branches' to string value 'modal_variant_a-supercharge'
|
||||
* Open about:welcome shows 'modal_variant_a' first run modal stage 1 on welcome screen.
|
||||
* Dismissing welcome screen by clicking on “Start browsing” shows stage 2 'supercharge' triplets experience on new tab.
|
||||
|
|
|
@ -28,6 +28,7 @@ Please note that some targeting attributes require stricter controls on the tele
|
|||
* [sync](#sync)
|
||||
* [topFrecentSites](#topfrecentsites)
|
||||
* [totalBookmarksCount](#totalbookmarkscount)
|
||||
* [trailheadInterrupt](#trailheadinterrupt)
|
||||
* [trailheadTriplet](#trailheadtriplet)
|
||||
* [usesFirefoxSync](#usesfirefoxsync)
|
||||
* [isFxAEnabled](#isFxAEnabled)
|
||||
|
@ -442,6 +443,10 @@ Total number of bookmarks.
|
|||
declare const totalBookmarksCount: number;
|
||||
```
|
||||
|
||||
### `trailheadInterrupt`
|
||||
|
||||
(67.05+ only) Experiment branch for "interrupt" study
|
||||
|
||||
### `trailheadTriplet`
|
||||
|
||||
(67.05+ only) Experiment branch for "triplet" study
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { Interrupt } from "./Interrupt";
|
||||
import { Triplets } from "./Triplets";
|
||||
import { BASE_PARAMS } from "./addUtmParams";
|
||||
|
||||
|
@ -33,10 +34,12 @@ export class FirstRun extends React.PureComponent {
|
|||
this.didLoadFlowParams = false;
|
||||
|
||||
this.state = {
|
||||
didUserClearInterrupt: false,
|
||||
didUserClearTriplets: false,
|
||||
flowParams: undefined,
|
||||
};
|
||||
|
||||
this.closeInterrupt = this.closeInterrupt.bind(this);
|
||||
this.closeTriplets = this.closeTriplets.bind(this);
|
||||
|
||||
helpers.addFluent(this.props.document);
|
||||
|
@ -73,13 +76,46 @@ export class FirstRun extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
removeHideMain() {
|
||||
if (!this.isInterruptVisible) {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// Is there any interrupt content? This is false for new tab triplets.
|
||||
get hasInterrupt() {
|
||||
const { message } = this.props;
|
||||
return Boolean(message && message.content);
|
||||
}
|
||||
|
||||
// Are all conditions met for the interrupt to actually be visible?
|
||||
// 1. hasInterrupt - Is there interrupt content?
|
||||
// 2. state.didUserClearInterrupt - Was it cleared by the user?
|
||||
// 3. props.interruptCleared - Was it cleared externally?
|
||||
get isInterruptVisible() {
|
||||
return (
|
||||
this.hasInterrupt &&
|
||||
!this.state.didUserClearInterrupt &&
|
||||
!this.props.interruptCleared
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
||||
didUserClearInterrupt: true,
|
||||
});
|
||||
}
|
||||
|
||||
closeTriplets() {
|
||||
|
@ -93,15 +129,22 @@ export class FirstRun extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const { props, state, UTMTerm } = this;
|
||||
const { sendUserActionTelemetry, executeAction, message } = props;
|
||||
const {
|
||||
sendUserActionTelemetry,
|
||||
fxaEndpoint,
|
||||
dispatch,
|
||||
executeAction,
|
||||
message,
|
||||
} = props;
|
||||
|
||||
const { didUserClearTriplets, flowParams } = state;
|
||||
|
||||
const hasTriplets = Boolean(message.bundle && message.bundle.length);
|
||||
const interrupt = this.hasInterrupt ? message : null;
|
||||
const triplets = hasTriplets ? message.bundle : null;
|
||||
const isTripletsContainerVisible = hasTriplets && !didUserClearTriplets;
|
||||
|
||||
// Allow 1) falsy to not render a header 2) default welcome header 3) custom header
|
||||
// Allow 1) falsy to not render a header 2) default welcome 3) custom header
|
||||
const tripletsHeaderId =
|
||||
message.tripletsHeaderId === undefined
|
||||
? "onboarding-welcome-header"
|
||||
|
@ -109,12 +152,29 @@ export class FirstRun extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<>
|
||||
{this.isInterruptVisible ? (
|
||||
<Interrupt
|
||||
document={props.document}
|
||||
cards={triplets}
|
||||
message={interrupt}
|
||||
onNextScene={this.closeInterrupt}
|
||||
UTMTerm={UTMTerm}
|
||||
sendUserActionTelemetry={sendUserActionTelemetry}
|
||||
executeAction={executeAction}
|
||||
dispatch={dispatch}
|
||||
flowParams={flowParams}
|
||||
onDismiss={this.closeInterrupt}
|
||||
fxaEndpoint={fxaEndpoint}
|
||||
onBlockById={props.onBlockById}
|
||||
/>
|
||||
) : null}
|
||||
{hasTriplets ? (
|
||||
<Triplets
|
||||
document={props.document}
|
||||
cards={triplets}
|
||||
headerId={tripletsHeaderId}
|
||||
showCardPanel={isTripletsContainerVisible}
|
||||
showContent={!this.isInterruptVisible}
|
||||
hideContainer={this.closeTriplets}
|
||||
sendUserActionTelemetry={sendUserActionTelemetry}
|
||||
UTMTerm={`${UTMTerm}-card`}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/* 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 { FullPageInterrupt } from "../FullPageInterrupt/FullPageInterrupt";
|
||||
import { LocalizationProvider } from "fluent-react";
|
||||
import { generateBundles } from "../../rich-text-strings";
|
||||
|
||||
export class Interrupt extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
cards,
|
||||
onDismiss,
|
||||
onNextScene,
|
||||
message,
|
||||
sendUserActionTelemetry,
|
||||
executeAction,
|
||||
dispatch,
|
||||
fxaEndpoint,
|
||||
UTMTerm,
|
||||
flowParams,
|
||||
} = this.props;
|
||||
|
||||
switch (message.template) {
|
||||
case "return_to_amo_overlay":
|
||||
return (
|
||||
<LocalizationProvider
|
||||
bundles={generateBundles({ amo_html: message.content.text })}
|
||||
>
|
||||
<ReturnToAMO
|
||||
{...message}
|
||||
document={this.props.document}
|
||||
UISurface="NEWTAB_OVERLAY"
|
||||
onBlock={onDismiss}
|
||||
onAction={executeAction}
|
||||
sendUserActionTelemetry={sendUserActionTelemetry}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
case "full_page_interrupt":
|
||||
return (
|
||||
<FullPageInterrupt
|
||||
document={this.props.document}
|
||||
cards={cards}
|
||||
message={message}
|
||||
onBlock={onDismiss}
|
||||
onAction={executeAction}
|
||||
dispatch={dispatch}
|
||||
fxaEndpoint={fxaEndpoint}
|
||||
sendUserActionTelemetry={sendUserActionTelemetry}
|
||||
UTMTerm={UTMTerm}
|
||||
flowParams={flowParams}
|
||||
onBlockById={this.props.onBlockById}
|
||||
/>
|
||||
);
|
||||
case "trailhead":
|
||||
return (
|
||||
<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`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,15 +61,16 @@ export class Triplets extends React.PureComponent {
|
|||
cards,
|
||||
headerId,
|
||||
showCardPanel,
|
||||
showContent,
|
||||
sendUserActionTelemetry,
|
||||
} = this.props;
|
||||
return (
|
||||
<div
|
||||
className={`trailheadCards ${showCardPanel ? "expanded" : "collapsed"}`}
|
||||
>
|
||||
<div className="trailheadCardsInner" aria-hidden={!showCardPanel}>
|
||||
<div className="trailheadCardsInner" aria-hidden={!showContent}>
|
||||
{headerId && <h1 data-l10n-id={headerId} />}
|
||||
<div className={`trailheadCardGrid${showCardPanel ? " show" : ""}`}>
|
||||
<div className={`trailheadCardGrid${showContent ? " show" : ""}`}>
|
||||
{cards.map(card => (
|
||||
<OnboardingCard
|
||||
key={card.id}
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
/* 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 { addUtmParams } from "../FirstRun/addUtmParams";
|
||||
import { FxASignupForm } from "../../components/FxASignupForm/FxASignupForm";
|
||||
import { OnboardingCard } from "../../templates/OnboardingMessage/OnboardingMessage";
|
||||
import React from "react";
|
||||
|
||||
export const FxAccounts = ({
|
||||
document,
|
||||
content,
|
||||
dispatch,
|
||||
fxaEndpoint,
|
||||
flowParams,
|
||||
removeOverlay,
|
||||
url,
|
||||
UTMTerm,
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="fullpage-left-section"
|
||||
aria-labelledby="fullpage-left-title"
|
||||
aria-describedby="fullpage-left-content"
|
||||
>
|
||||
<h1
|
||||
id="fullpage-left-title"
|
||||
className="fullpage-left-title"
|
||||
data-l10n-id="onboarding-welcome-body"
|
||||
/>
|
||||
<p
|
||||
id="fullpage-left-content"
|
||||
className="fullpage-left-content"
|
||||
data-l10n-id="onboarding-benefit-products-text"
|
||||
/>
|
||||
<p
|
||||
className="fullpage-left-content"
|
||||
data-l10n-id="onboarding-benefit-privacy-text"
|
||||
/>
|
||||
<a
|
||||
className="fullpage-left-link"
|
||||
href={addUtmParams(url, UTMTerm)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-l10n-id="onboarding-welcome-learn-more"
|
||||
/>
|
||||
<div className="fullpage-icon fx-systems-icons" />
|
||||
</div>
|
||||
<div className="fullpage-form">
|
||||
<FxASignupForm
|
||||
document={document}
|
||||
content={content}
|
||||
dispatch={dispatch}
|
||||
fxaEndpoint={fxaEndpoint}
|
||||
UTMTerm={UTMTerm}
|
||||
flowParams={flowParams}
|
||||
onClose={removeOverlay}
|
||||
showSignInLink={true}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
export const FxCards = ({ cards, onCardAction, sendUserActionTelemetry }) => (
|
||||
<React.Fragment>
|
||||
{cards.map(card => (
|
||||
<OnboardingCard
|
||||
key={card.id}
|
||||
message={card}
|
||||
className="trailheadCard"
|
||||
sendUserActionTelemetry={sendUserActionTelemetry}
|
||||
onAction={onCardAction}
|
||||
UISurface="TRAILHEAD"
|
||||
{...card}
|
||||
/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
export class FullPageInterrupt extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.removeOverlay = this.removeOverlay.bind(this);
|
||||
this.onCardAction = this.onCardAction.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
global.document.body.classList.add("trailhead-fullpage");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Hide the page content from screen readers while the full page interrupt is open
|
||||
this.props.document
|
||||
.getElementById("root")
|
||||
.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
removeOverlay() {
|
||||
window.removeEventListener("visibilitychange", this.removeOverlay);
|
||||
document.body.classList.remove("hide-main", "trailhead-fullpage");
|
||||
// Re-enable the document for screen readers
|
||||
this.props.document
|
||||
.getElementById("root")
|
||||
.setAttribute("aria-hidden", "false");
|
||||
|
||||
this.props.onBlock();
|
||||
document.body.classList.remove("welcome");
|
||||
}
|
||||
|
||||
onCardAction(action, message) {
|
||||
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 });
|
||||
// Only block if message is in dynamic triplets experiment
|
||||
if (message.blockOnClick) {
|
||||
this.props.onBlockById(message.id, { preloadedOnly: true });
|
||||
}
|
||||
this.removeOverlay();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { content } = props.message;
|
||||
const cards = (
|
||||
<FxCards
|
||||
cards={props.cards}
|
||||
onCardAction={this.onCardAction}
|
||||
sendUserActionTelemetry={props.sendUserActionTelemetry}
|
||||
/>
|
||||
);
|
||||
|
||||
const accounts = (
|
||||
<FxAccounts
|
||||
document={props.document}
|
||||
content={content}
|
||||
dispatch={props.dispatch}
|
||||
fxaEndpoint={props.fxaEndpoint}
|
||||
flowParams={props.flowParams}
|
||||
removeOverlay={this.removeOverlay}
|
||||
url={content.learn.url}
|
||||
UTMTerm={props.UTMTerm}
|
||||
/>
|
||||
);
|
||||
|
||||
// By default we show accounts section on top and
|
||||
// cards section in bottom half of the full page interrupt
|
||||
const cardsFirst = content && content.className === "fullPageCardsAtTop";
|
||||
const firstContainerClassName = [
|
||||
"container",
|
||||
content && content.className,
|
||||
].join(" ");
|
||||
return (
|
||||
<div className="fullpage-wrapper">
|
||||
<div className="fullpage-icon brand-logo" />
|
||||
<h1
|
||||
className="welcome-title"
|
||||
data-l10n-id="onboarding-welcome-header"
|
||||
/>
|
||||
<h2
|
||||
className="welcome-subtitle"
|
||||
data-l10n-id="onboarding-fullpage-welcome-subheader"
|
||||
/>
|
||||
<div className={firstContainerClassName}>
|
||||
{cardsFirst ? cards : accounts}
|
||||
</div>
|
||||
<div className="section-divider" />
|
||||
<div className="container">{cardsFirst ? accounts : cards}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FullPageInterrupt.defaultProps = {
|
||||
flowParams: { deviceId: "", flowId: "", flowBeginTime: "" },
|
||||
};
|
|
@ -0,0 +1,258 @@
|
|||
.activity-stream {
|
||||
&.welcome {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:not(.welcome) {
|
||||
.fullpage-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fullpage-wrapper {
|
||||
$responsive-breakpoint: 975px;
|
||||
$responsive-width: 300px;
|
||||
$header-size: 36px;
|
||||
$form-text-size: 16px;
|
||||
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 21000;
|
||||
background-color: $ghost-white;
|
||||
|
||||
+ div {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fullpage-icon {
|
||||
background-position-x: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position-x: right;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
background-image: url('chrome://branding/content/about-logo.png');
|
||||
margin: 20px 10px 10px 20px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.welcome-title,
|
||||
.welcome-subtitle {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
color: $trailhead-purple-80;
|
||||
font-size: 46px;
|
||||
font-weight: 600;
|
||||
line-height: 62px;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
color: $trailhead-violet;
|
||||
font-size: 20px;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
padding: 50px 0;
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
flex-direction: column;
|
||||
width: $responsive-width;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
width: $responsive-width;
|
||||
}
|
||||
|
||||
.fullpage-left-content {
|
||||
color: $grey-60;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
margin-inline-end: 2px;
|
||||
}
|
||||
|
||||
.fullpage-left-link {
|
||||
color: $blue-60;
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $blue-60;
|
||||
}
|
||||
}
|
||||
|
||||
.fullpage-left-title {
|
||||
margin: 0;
|
||||
color: $trailhead-purple-80;
|
||||
font-size: $header-size;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.fx-systems-icons {
|
||||
height: 33px;
|
||||
display: block;
|
||||
background-image: url('#{$image-path}trailhead/firefox-systems.png');
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.fullpage-form {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-inline-start: $header-size;
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.fxaSignupForm {
|
||||
width: 356px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 0 16px 0 $black-15;
|
||||
border-radius: 6px;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.fxa-terms {
|
||||
margin: 4px 0 20px;
|
||||
|
||||
a,
|
||||
& {
|
||||
color: $grey-60;
|
||||
font-size: 12px;
|
||||
line-height: $form-text-size;
|
||||
}
|
||||
}
|
||||
|
||||
.fxa-signin {
|
||||
color: $grey-60;
|
||||
line-height: 30px;
|
||||
opacity: 0.77;
|
||||
|
||||
button {
|
||||
color: $blue-60;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: $trailhead-purple-80;
|
||||
font-weight: 400;
|
||||
font-size: $header-size;
|
||||
line-height: $header-size;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
h3 + p {
|
||||
color: $grey-60;
|
||||
font-size: $form-text-size;
|
||||
line-height: 20px;
|
||||
opacity: 0.77;
|
||||
}
|
||||
|
||||
input {
|
||||
background: $white;
|
||||
border: 1px solid $grey-30;
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
border-color: $grey-50;
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
border-color: $red-60;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
color: $white;
|
||||
font-size: $form-text-size;
|
||||
|
||||
&:focus {
|
||||
outline: dotted 1px $grey-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-divider::after {
|
||||
content: '';
|
||||
display: block;
|
||||
border-bottom: 0.5px solid $grey-30;
|
||||
}
|
||||
|
||||
.trailheadCard {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-align: center;
|
||||
width: 320px;
|
||||
padding: 18px;
|
||||
|
||||
.onboardingTitle {
|
||||
color: $grey-90;
|
||||
}
|
||||
|
||||
.onboardingText {
|
||||
font-weight: normal;
|
||||
color: $grey-60;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.onboardingButton {
|
||||
color: $grey-60;
|
||||
background: $grey-90-10;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: $grey-90-20;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $grey-90-30;
|
||||
}
|
||||
}
|
||||
|
||||
.onboardingMessageImage {
|
||||
height: 112px;
|
||||
width: 154px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
width: $responsive-width;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -114,6 +114,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Also used for Trailhead
|
||||
.onboardingMessageImage {
|
||||
height: 112px;
|
||||
width: 180px;
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/* 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 { RichText } from "../../components/RichText/RichText";
|
||||
|
||||
// Alt text if available; in the future this should come from the server. See bug 1551711
|
||||
const ICON_ALT_TEXT = "";
|
||||
|
||||
export class ReturnToAMO extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClickAddExtension = this.onClickAddExtension.bind(this);
|
||||
this.onBlockButton = this.onBlockButton.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
global.document.body.classList.add("amo");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "IMPRESSION",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
// Hide the page content from screen readers while the modal is open
|
||||
this.props.document
|
||||
.getElementById("root")
|
||||
.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
onClickAddExtension() {
|
||||
this.props.onAction(this.props.content.primary_button.action);
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "INSTALL",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
}
|
||||
|
||||
onBlockButton() {
|
||||
this.props.onBlock();
|
||||
document.body.classList.remove("welcome", "hide-main", "amo");
|
||||
this.props.sendUserActionTelemetry({
|
||||
event: "BLOCK",
|
||||
id: this.props.UISurface,
|
||||
});
|
||||
// Re-enable the document for screen readers
|
||||
this.props.document
|
||||
.getElementById("root")
|
||||
.setAttribute("aria-hidden", "false");
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const customElement = (
|
||||
<img
|
||||
src={this.props.content.addon_icon}
|
||||
width="20px"
|
||||
height="20px"
|
||||
alt={ICON_ALT_TEXT}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<RichText
|
||||
customElements={{ icon: customElement }}
|
||||
amo_html={this.props.content.text}
|
||||
localization_id="amo_html"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { content } = this.props;
|
||||
return (
|
||||
<div className="ReturnToAMOOverlay">
|
||||
<div>
|
||||
<h2> {content.header} </h2>
|
||||
<div className="ReturnToAMOContainer">
|
||||
<div className="ReturnToAMOAddonContents">
|
||||
<p> {content.title} </p>
|
||||
<div className="ReturnToAMOText">
|
||||
<span> {this.renderText()} </span>
|
||||
</div>
|
||||
<button
|
||||
onClick={this.onClickAddExtension}
|
||||
className="puffy blue ReturnToAMOAddExtension"
|
||||
>
|
||||
{" "}
|
||||
<span className="icon icon-add" />{" "}
|
||||
{content.primary_button.label}{" "}
|
||||
</button>
|
||||
</div>
|
||||
<div className="ReturnToAMOIcon" />
|
||||
</div>
|
||||
<button
|
||||
onClick={this.onBlockButton}
|
||||
className="default grey ReturnToAMOGetStarted"
|
||||
>
|
||||
{" "}
|
||||
{content.secondary_button.label}{" "}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
.ReturnToAMOOverlay,
|
||||
.amo + body.hide-main { // sass-lint:disable-line no-qualifying-elements
|
||||
background: $grey-10;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2100;
|
||||
|
||||
.ReturnToAMOText {
|
||||
color: $grey-90;
|
||||
line-height: 32px;
|
||||
font-size: 23px;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
margin-inline-start: 6px;
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $grey-60;
|
||||
font-weight: 100;
|
||||
margin: 0 0 36px;
|
||||
font-size: 36px;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $grey-60;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.puffy {
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.blue {
|
||||
border: 0;
|
||||
color: $white;
|
||||
background-color: $blue-60;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background-color: $blue-70;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $blue-80;
|
||||
}
|
||||
}
|
||||
|
||||
.default {
|
||||
border-radius: 2px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.grey {
|
||||
border: 0;
|
||||
background-color: $grey-90-10;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background-color: $grey-90-20;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $grey-90-30;
|
||||
}
|
||||
}
|
||||
|
||||
.ReturnToAMOGetStarted {
|
||||
margin-top: 40px;
|
||||
float: right;
|
||||
|
||||
&:dir(rtl) {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ReturnToAMOAddExtension {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.ReturnToAMOContainer {
|
||||
width: 960px;
|
||||
background: $white;
|
||||
box-shadow: 0 1px 15px 0 $black-30;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
padding: 64px 64px 72px;
|
||||
}
|
||||
|
||||
.ReturnToAMOAddonContents {
|
||||
width: 560px;
|
||||
margin-top: 32px;
|
||||
margin-inline-end: 24px;
|
||||
}
|
||||
|
||||
.ReturnToAMOIcon {
|
||||
width: 292px;
|
||||
height: 254px;
|
||||
background-size: 292px 254px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('chrome://activity-stream/content/data/content/assets/gift-extension.svg');
|
||||
}
|
||||
|
||||
.icon-add {
|
||||
fill: $white;
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/* 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 { actionCreators as ac } from "common/Actions.jsm";
|
||||
import { ModalOverlayWrapper } from "../../components/ModalOverlay/ModalOverlay";
|
||||
import { FxASignupForm } from "../../components/FxASignupForm/FxASignupForm";
|
||||
import { addUtmParams } from "../FirstRun/addUtmParams";
|
||||
import React from "react";
|
||||
|
||||
// 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(", ");
|
||||
|
||||
export class Trailhead extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
this.onStartBlur = this.onStartBlur.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");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
ac.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 };
|
||||
return { value };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { UTMTerm } = props;
|
||||
const { content } = props.message;
|
||||
const innerClassName = ["trailhead", content && content.className]
|
||||
.filter(v => v)
|
||||
.join(" ");
|
||||
|
||||
return (
|
||||
<ModalOverlayWrapper
|
||||
innerClassName={innerClassName}
|
||||
onClose={this.closeModal}
|
||||
id="trailheadDialog"
|
||||
headerId="trailheadHeader"
|
||||
hasDismissIcon={true}
|
||||
>
|
||||
<div className="trailheadInner">
|
||||
<div className="trailheadContent">
|
||||
<h1 data-l10n-id={content.title.string_id} id="trailheadHeader" />
|
||||
{content.subtitle && (
|
||||
<p data-l10n-id={content.subtitle.string_id} />
|
||||
)}
|
||||
<ul className="trailheadBenefits">
|
||||
{content.benefits.map(item => (
|
||||
<li key={item.id} className={item.id}>
|
||||
<h2 data-l10n-id={item.title.string_id} />
|
||||
<p data-l10n-id={item.text.string_id} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a
|
||||
className="trailheadLearn"
|
||||
data-l10n-id={content.learn.text.string_id}
|
||||
href={addUtmParams(content.learn.url, UTMTerm)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
</div>
|
||||
<div className="trailhead-join-form">
|
||||
<FxASignupForm
|
||||
document={this.props.document}
|
||||
content={content}
|
||||
dispatch={this.props.dispatch}
|
||||
fxaEndpoint={this.props.fxaEndpoint}
|
||||
UTMTerm={UTMTerm}
|
||||
flowParams={this.props.flowParams}
|
||||
onClose={this.closeModal}
|
||||
showSignInLink={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="trailheadStart"
|
||||
data-l10n-id={content.skipButton.string_id}
|
||||
onBlur={this.onStartBlur}
|
||||
onClick={this.closeModal}
|
||||
/>
|
||||
</ModalOverlayWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Trailhead.defaultProps = {
|
||||
flowParams: { deviceId: "", flowId: "", flowBeginTime: "" },
|
||||
};
|
|
@ -1,3 +1,185 @@
|
|||
.trailhead {
|
||||
$benefit-icon-size: 62px;
|
||||
$benefit-icon-spacing: $benefit-icon-size + 12px;
|
||||
$benefit-icon-size-small: 40px;
|
||||
$benefit-icon-spacing-small: $benefit-icon-size-small + 12px;
|
||||
$responsive-breakpoint: 850px;
|
||||
$logo-size: 100px;
|
||||
|
||||
background: url('#{$image-path}trailhead/accounts-form-bg.jpg') bottom / cover;
|
||||
color: $white;
|
||||
height: auto;
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.trailheadInner {
|
||||
$content-spacing: 40px;
|
||||
|
||||
display: grid;
|
||||
grid-column-gap: $content-spacing;
|
||||
grid-template-columns: 5fr 3fr;
|
||||
padding: $content-spacing 60px;
|
||||
}
|
||||
|
||||
.trailheadContent {
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.trailheadLearn {
|
||||
display: block;
|
||||
margin-top: 30px;
|
||||
|
||||
@media (min-width: $responsive-breakpoint) {
|
||||
margin-inline-start: $benefit-icon-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trailhead-join-form {
|
||||
background: url('#{$image-path}trailhead/firefox-logo.png') top center / $logo-size no-repeat;
|
||||
color: $white;
|
||||
min-width: 260px;
|
||||
padding-top: $logo-size;
|
||||
}
|
||||
|
||||
&.syncCohort {
|
||||
left: calc(50% - 430px);
|
||||
width: 860px;
|
||||
|
||||
@media (max-width: 860px) {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.trailheadInner {
|
||||
grid-template-columns: 4fr 3fr;
|
||||
}
|
||||
|
||||
.trailheadContent {
|
||||
.trailheadBenefits {
|
||||
background: url('#{$image-path}sync-devices-trailhead.svg');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 200px;
|
||||
margin-inline-end: 60px;
|
||||
}
|
||||
|
||||
.trailheadLearn {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trailheadBenefits {
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
background-position: left 6px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: $benefit-icon-size-small;
|
||||
-moz-context-properties: fill;
|
||||
fill: $blue-50;
|
||||
list-style: none;
|
||||
padding-top: 8px;
|
||||
|
||||
|
||||
@media (min-width: $responsive-breakpoint) {
|
||||
background-position-y: 4px;
|
||||
background-size: $benefit-icon-size;
|
||||
margin-inline-end: 60px;
|
||||
padding-inline-start: $benefit-icon-spacing;
|
||||
}
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position-x: right;
|
||||
}
|
||||
|
||||
&.knowledge,
|
||||
&.monitor {
|
||||
background-image: url('#{$image-path}trailhead/benefit-knowledge.png');
|
||||
}
|
||||
|
||||
&.lockwise,
|
||||
&.privacy {
|
||||
background-image: url('#{$image-path}trailhead/benefit-privacy.png');
|
||||
}
|
||||
|
||||
&.products {
|
||||
background-image: url('#{$image-path}trailhead/benefit-products.png');
|
||||
}
|
||||
|
||||
&.sync {
|
||||
background-image: url('#{$image-path}trailhead/benefit-sync.png');
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: start;
|
||||
line-height: inherit;
|
||||
color: $violet-20;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
margin: 0 0 4px;
|
||||
padding-inline-start: $benefit-icon-spacing-small;
|
||||
|
||||
@media (min-width: $responsive-breakpoint) {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: $white;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.trailheadStart {
|
||||
border: 1px solid $white-50;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 0 auto 40px;
|
||||
min-width: 300px;
|
||||
padding: 14px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $trailhead-blue-60;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: dotted 1px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $trailhead-blue-70;
|
||||
}
|
||||
}
|
||||
|
||||
.trailheadInner,
|
||||
.trailheadStart {
|
||||
animation: fadeIn 0.4s;
|
||||
}
|
||||
}
|
||||
|
||||
.trailheadCards {
|
||||
background: var(--trailhead-cards-background-color);
|
||||
overflow: hidden;
|
|
@ -516,7 +516,7 @@ export class ASRouterAdminInner extends React.PureComponent {
|
|||
collapsedMessages: [],
|
||||
modifiedMessages: [],
|
||||
evaluationStatus: {},
|
||||
trailheadTriplet: "",
|
||||
trailhead: {},
|
||||
stringTargetingParameters: null,
|
||||
newStringTargetingParameters: null,
|
||||
copiedToClipboard: false,
|
||||
|
@ -1624,12 +1624,17 @@ export class ASRouterAdminInner extends React.PureComponent {
|
|||
}
|
||||
|
||||
renderTrailheadInfo() {
|
||||
const { trailheadInterrupt, trailheadTriplet } = this.state.trailhead;
|
||||
return (
|
||||
<table className="minimal-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Interrupt branch</td>
|
||||
<td>{trailheadInterrupt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Triplet branch</td>
|
||||
<td>{this.state.trailheadTriplet}</td>
|
||||
<td>{trailheadTriplet}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -42,6 +42,12 @@ function debounce(func, wait) {
|
|||
}
|
||||
|
||||
export class _Base extends React.PureComponent {
|
||||
componentWillMount() {
|
||||
if (this.props.isFirstrun) {
|
||||
global.document.body.classList.add("welcome", "hide-main");
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.updateTheme();
|
||||
}
|
||||
|
@ -55,6 +61,8 @@ export class _Base extends React.PureComponent {
|
|||
"activity-stream",
|
||||
// If we skipped the about:welcome overlay and removed the CSS classes
|
||||
// we don't want to add them back to the Activity Stream view
|
||||
document.body.classList.contains("welcome") ? "welcome" : "",
|
||||
document.body.classList.contains("hide-main") ? "hide-main" : "",
|
||||
document.body.classList.contains("inline-onboarding")
|
||||
? "inline-onboarding"
|
||||
: "",
|
||||
|
|
|
@ -172,9 +172,12 @@ input {
|
|||
@import '../asrouter/components/Button/Button';
|
||||
@import '../asrouter/components/SnippetBase/SnippetBase';
|
||||
@import '../asrouter/components/ModalOverlay/ModalOverlay';
|
||||
@import '../asrouter/templates/ReturnToAMO/ReturnToAMO';
|
||||
@import '../asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet';
|
||||
@import '../asrouter/templates/SimpleSnippet/SimpleSnippet';
|
||||
@import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
|
||||
@import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
|
||||
@import '../asrouter/templates/FirstRun/Triplets';
|
||||
@import '../asrouter/templates/EOYSnippet/EOYSnippet';
|
||||
@import '../asrouter/components/FxASignupForm/FxASignupForm';
|
||||
@import '../asrouter/templates/Trailhead/Trailhead';
|
||||
@import '../asrouter/templates/FullPageInterrupt/FullPageInterrupt';
|
||||
|
|
|
@ -3607,6 +3607,112 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
|
||||
.ReturnToAMOOverlay,
|
||||
.amo + body.hide-main {
|
||||
background: #F9F9FA;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2100; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText,
|
||||
.amo + body.hide-main .ReturnToAMOText {
|
||||
color: #0C0C0D;
|
||||
line-height: 32px;
|
||||
font-size: 23px;
|
||||
width: 100%; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText img,
|
||||
.amo + body.hide-main .ReturnToAMOText img {
|
||||
margin-inline-start: 6px;
|
||||
margin-inline-end: 6px; }
|
||||
.ReturnToAMOOverlay h2,
|
||||
.amo + body.hide-main h2 {
|
||||
color: #4A4A4F;
|
||||
font-weight: 100;
|
||||
margin: 0 0 36px;
|
||||
font-size: 36px;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1.2px; }
|
||||
.ReturnToAMOOverlay p,
|
||||
.amo + body.hide-main p {
|
||||
color: #4A4A4F;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px; }
|
||||
.ReturnToAMOOverlay .puffy,
|
||||
.amo + body.hide-main .puffy {
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .blue,
|
||||
.amo + body.hide-main .blue {
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
background-color: #0060DF; }
|
||||
.ReturnToAMOOverlay .blue:hover,
|
||||
.amo + body.hide-main .blue:hover {
|
||||
box-shadow: none;
|
||||
background-color: #003EAA; }
|
||||
.ReturnToAMOOverlay .blue:active,
|
||||
.amo + body.hide-main .blue:active {
|
||||
background-color: #002275; }
|
||||
.ReturnToAMOOverlay .default,
|
||||
.amo + body.hide-main .default {
|
||||
border-radius: 2px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .grey,
|
||||
.amo + body.hide-main .grey {
|
||||
border: 0;
|
||||
background-color: rgba(12, 12, 13, 0.1); }
|
||||
.ReturnToAMOOverlay .grey:hover,
|
||||
.amo + body.hide-main .grey:hover {
|
||||
box-shadow: none;
|
||||
background-color: rgba(12, 12, 13, 0.2); }
|
||||
.ReturnToAMOOverlay .grey:active,
|
||||
.amo + body.hide-main .grey:active {
|
||||
background-color: rgba(12, 12, 13, 0.3); }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted,
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted {
|
||||
margin-top: 40px;
|
||||
float: right; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted:dir(rtl),
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted:dir(rtl) {
|
||||
float: left; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddExtension,
|
||||
.amo + body.hide-main .ReturnToAMOAddExtension {
|
||||
margin-top: 20px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOContainer,
|
||||
.amo + body.hide-main .ReturnToAMOContainer {
|
||||
width: 960px;
|
||||
background: #FFF;
|
||||
box-shadow: 0 1px 15px 0 rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
padding: 64px 64px 72px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddonContents,
|
||||
.amo + body.hide-main .ReturnToAMOAddonContents {
|
||||
width: 560px;
|
||||
margin-top: 32px;
|
||||
margin-inline-end: 24px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOIcon,
|
||||
.amo + body.hide-main .ReturnToAMOIcon {
|
||||
width: 292px;
|
||||
height: 254px;
|
||||
background-size: 292px 254px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/gift-extension.svg"); }
|
||||
.ReturnToAMOOverlay .icon-add,
|
||||
.amo + body.hide-main .icon-add {
|
||||
fill: #FFF;
|
||||
vertical-align: sub; }
|
||||
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px; }
|
||||
.below-search-snippet.withButton {
|
||||
|
@ -4083,6 +4189,233 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
min-width: 80px;
|
||||
background-size: 140px; } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
|
||||
.fxaSignupForm {
|
||||
min-width: 260px;
|
||||
text-align: center; }
|
||||
.fxaSignupForm a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.fxaSignupForm input,
|
||||
.fxaSignupForm button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.fxaSignupForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.fxaSignupForm p {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.fxaSignupForm .fxa-terms {
|
||||
margin: 4px 30px 20px; }
|
||||
.fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.fxaSignupForm .fxa-signin {
|
||||
font-size: 16px;
|
||||
margin-top: 19px; }
|
||||
.fxaSignupForm .fxa-signin span {
|
||||
margin-inline-end: 5px; }
|
||||
.fxaSignupForm .fxa-signin button {
|
||||
background-color: initial;
|
||||
text-decoration: underline;
|
||||
color: #FFF;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
width: auto; }
|
||||
.fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
|
||||
background-color: initial; }
|
||||
.fxaSignupForm form {
|
||||
position: relative; }
|
||||
.fxaSignupForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.fxaSignupForm button,
|
||||
.fxaSignupForm input {
|
||||
width: 100%; }
|
||||
.fxaSignupForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.fxaSignupForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.fxaSignupForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.fxaSignupForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fxaSignupForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.fxaSignupForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.fxaSignupForm button:hover, .fxaSignupForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.fxaSignupForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.fxaSignupForm button:active {
|
||||
background-color: #054096; }
|
||||
|
||||
.trailhead {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
height: auto; }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.trailhead input,
|
||||
.trailhead button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.trailhead .trailheadInner {
|
||||
display: grid;
|
||||
grid-column-gap: 40px;
|
||||
grid-template-columns: 5fr 3fr;
|
||||
padding: 40px 60px; }
|
||||
.trailhead .trailheadContent h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 0; }
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
display: block;
|
||||
margin-top: 30px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
color: #FFF;
|
||||
min-width: 260px;
|
||||
padding-top: 100px; }
|
||||
.trailhead.syncCohort {
|
||||
left: calc(50% - 430px);
|
||||
width: 860px; }
|
||||
@media (max-width: 860px) {
|
||||
.trailhead.syncCohort {
|
||||
left: 0;
|
||||
width: 100%; } }
|
||||
.trailhead.syncCohort .trailheadInner {
|
||||
grid-template-columns: 4fr 3fr; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/sync-devices-trailhead.svg");
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 200px;
|
||||
margin-inline-end: 60px; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 0; }
|
||||
.trailhead .trailheadBenefits {
|
||||
padding: 0; }
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position: left 6px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 40px;
|
||||
-moz-context-properties: fill;
|
||||
fill: #0A84FF;
|
||||
list-style: none;
|
||||
padding-top: 8px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position-y: 4px;
|
||||
background-size: 62px;
|
||||
margin-inline-end: 60px;
|
||||
padding-inline-start: 74px; } }
|
||||
.trailhead .trailheadBenefits li:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
.trailhead .trailheadBenefits li.knowledge, .trailhead .trailheadBenefits li.monitor {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-knowledge.png"); }
|
||||
.trailhead .trailheadBenefits li.lockwise, .trailhead .trailheadBenefits li.privacy {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-privacy.png"); }
|
||||
.trailhead .trailheadBenefits li.products {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-products.png"); }
|
||||
.trailhead .trailheadBenefits li.sync {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-sync.png"); }
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
text-align: start;
|
||||
line-height: inherit;
|
||||
color: #CB9EFF;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
margin: 0 0 4px;
|
||||
padding-inline-start: 52px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
padding-inline-start: 0; } }
|
||||
.trailhead .trailheadBenefits p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 0 auto 40px;
|
||||
min-width: 300px;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
|
||||
background-color: #0250BB;
|
||||
border-color: transparent; }
|
||||
.trailhead .trailheadStart:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadStart:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadInner,
|
||||
.trailhead .trailheadStart {
|
||||
animation: fadeIn 0.4s; }
|
||||
|
||||
.trailheadCards {
|
||||
background: var(--trailhead-cards-background-color);
|
||||
overflow: hidden;
|
||||
|
@ -4239,39 +4572,171 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 21000;
|
||||
background-color: #FAFAFC; }
|
||||
.fullpage-wrapper + div {
|
||||
opacity: 0; }
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position-x: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; }
|
||||
.fullpage-wrapper .fullpage-icon:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position: center; } }
|
||||
.fullpage-wrapper .brand-logo {
|
||||
background-image: url("chrome://branding/content/about-logo.png");
|
||||
margin: 20px 10px 10px 20px;
|
||||
padding-bottom: 50px; }
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
align-self: center;
|
||||
margin: 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .welcome-title {
|
||||
color: #36296D;
|
||||
font-size: 46px;
|
||||
font-weight: 600;
|
||||
line-height: 62px; }
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
color: #7542E5;
|
||||
font-size: 20px;
|
||||
line-height: 27px; }
|
||||
.fullpage-wrapper .container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 300px; } }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-content {
|
||||
color: #4A4A4F;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
margin-inline-end: 2px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link {
|
||||
color: #0060DF;
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 30px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-title {
|
||||
margin: 0;
|
||||
color: #36296D;
|
||||
font-size: 36px;
|
||||
line-height: 48px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fx-systems-icons {
|
||||
height: 33px;
|
||||
display: block;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-systems.png");
|
||||
margin-bottom: 20px; }
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-inline-start: 36px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
margin-inline-start: 0; } }
|
||||
.fullpage-wrapper .fullpage-form .fxaSignupForm {
|
||||
width: 356px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
background: #FFF; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
margin: 4px 0 20px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
color: #4A4A4F;
|
||||
font-size: 12px;
|
||||
line-height: 16px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin {
|
||||
color: #4A4A4F;
|
||||
line-height: 30px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin button {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-form h3 {
|
||||
color: #36296D;
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
margin: 0;
|
||||
padding: 8px; }
|
||||
.fullpage-wrapper .fullpage-form h3 + p {
|
||||
color: #4A4A4F;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form input {
|
||||
background: #FFF;
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 2px; }
|
||||
.fullpage-wrapper .fullpage-form input:hover {
|
||||
border-color: #737373; }
|
||||
.fullpage-wrapper .fullpage-form input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fullpage-wrapper .fullpage-form button {
|
||||
color: #FFF;
|
||||
font-size: 16px; }
|
||||
.fullpage-wrapper .fullpage-form button:focus {
|
||||
outline: dotted 1px #737373; }
|
||||
.fullpage-wrapper .section-divider::after {
|
||||
content: '';
|
||||
display: block;
|
||||
border-bottom: 0.5px solid #D7D7DB; }
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-align: center;
|
||||
width: 320px;
|
||||
padding: 18px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingTitle {
|
||||
color: #0C0C0D; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingText {
|
||||
font-weight: normal;
|
||||
color: #4A4A4F;
|
||||
margin-top: 4px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton {
|
||||
color: #4A4A4F;
|
||||
background: rgba(12, 12, 13, 0.1); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
|
||||
background: rgba(12, 12, 13, 0.2); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:active {
|
||||
background: rgba(12, 12, 13, 0.3); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingMessageImage {
|
||||
height: 112px;
|
||||
width: 154px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 300px; } }
|
||||
|
|
|
@ -3610,6 +3610,112 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
|
||||
.ReturnToAMOOverlay,
|
||||
.amo + body.hide-main {
|
||||
background: #F9F9FA;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2100; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText,
|
||||
.amo + body.hide-main .ReturnToAMOText {
|
||||
color: #0C0C0D;
|
||||
line-height: 32px;
|
||||
font-size: 23px;
|
||||
width: 100%; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText img,
|
||||
.amo + body.hide-main .ReturnToAMOText img {
|
||||
margin-inline-start: 6px;
|
||||
margin-inline-end: 6px; }
|
||||
.ReturnToAMOOverlay h2,
|
||||
.amo + body.hide-main h2 {
|
||||
color: #4A4A4F;
|
||||
font-weight: 100;
|
||||
margin: 0 0 36px;
|
||||
font-size: 36px;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1.2px; }
|
||||
.ReturnToAMOOverlay p,
|
||||
.amo + body.hide-main p {
|
||||
color: #4A4A4F;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px; }
|
||||
.ReturnToAMOOverlay .puffy,
|
||||
.amo + body.hide-main .puffy {
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .blue,
|
||||
.amo + body.hide-main .blue {
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
background-color: #0060DF; }
|
||||
.ReturnToAMOOverlay .blue:hover,
|
||||
.amo + body.hide-main .blue:hover {
|
||||
box-shadow: none;
|
||||
background-color: #003EAA; }
|
||||
.ReturnToAMOOverlay .blue:active,
|
||||
.amo + body.hide-main .blue:active {
|
||||
background-color: #002275; }
|
||||
.ReturnToAMOOverlay .default,
|
||||
.amo + body.hide-main .default {
|
||||
border-radius: 2px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .grey,
|
||||
.amo + body.hide-main .grey {
|
||||
border: 0;
|
||||
background-color: rgba(12, 12, 13, 0.1); }
|
||||
.ReturnToAMOOverlay .grey:hover,
|
||||
.amo + body.hide-main .grey:hover {
|
||||
box-shadow: none;
|
||||
background-color: rgba(12, 12, 13, 0.2); }
|
||||
.ReturnToAMOOverlay .grey:active,
|
||||
.amo + body.hide-main .grey:active {
|
||||
background-color: rgba(12, 12, 13, 0.3); }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted,
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted {
|
||||
margin-top: 40px;
|
||||
float: right; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted:dir(rtl),
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted:dir(rtl) {
|
||||
float: left; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddExtension,
|
||||
.amo + body.hide-main .ReturnToAMOAddExtension {
|
||||
margin-top: 20px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOContainer,
|
||||
.amo + body.hide-main .ReturnToAMOContainer {
|
||||
width: 960px;
|
||||
background: #FFF;
|
||||
box-shadow: 0 1px 15px 0 rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
padding: 64px 64px 72px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddonContents,
|
||||
.amo + body.hide-main .ReturnToAMOAddonContents {
|
||||
width: 560px;
|
||||
margin-top: 32px;
|
||||
margin-inline-end: 24px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOIcon,
|
||||
.amo + body.hide-main .ReturnToAMOIcon {
|
||||
width: 292px;
|
||||
height: 254px;
|
||||
background-size: 292px 254px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/gift-extension.svg"); }
|
||||
.ReturnToAMOOverlay .icon-add,
|
||||
.amo + body.hide-main .icon-add {
|
||||
fill: #FFF;
|
||||
vertical-align: sub; }
|
||||
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px; }
|
||||
.below-search-snippet.withButton {
|
||||
|
@ -4086,6 +4192,233 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
min-width: 80px;
|
||||
background-size: 140px; } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
|
||||
.fxaSignupForm {
|
||||
min-width: 260px;
|
||||
text-align: center; }
|
||||
.fxaSignupForm a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.fxaSignupForm input,
|
||||
.fxaSignupForm button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.fxaSignupForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.fxaSignupForm p {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.fxaSignupForm .fxa-terms {
|
||||
margin: 4px 30px 20px; }
|
||||
.fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.fxaSignupForm .fxa-signin {
|
||||
font-size: 16px;
|
||||
margin-top: 19px; }
|
||||
.fxaSignupForm .fxa-signin span {
|
||||
margin-inline-end: 5px; }
|
||||
.fxaSignupForm .fxa-signin button {
|
||||
background-color: initial;
|
||||
text-decoration: underline;
|
||||
color: #FFF;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
width: auto; }
|
||||
.fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
|
||||
background-color: initial; }
|
||||
.fxaSignupForm form {
|
||||
position: relative; }
|
||||
.fxaSignupForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.fxaSignupForm button,
|
||||
.fxaSignupForm input {
|
||||
width: 100%; }
|
||||
.fxaSignupForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.fxaSignupForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.fxaSignupForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.fxaSignupForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fxaSignupForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.fxaSignupForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.fxaSignupForm button:hover, .fxaSignupForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.fxaSignupForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.fxaSignupForm button:active {
|
||||
background-color: #054096; }
|
||||
|
||||
.trailhead {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
height: auto; }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.trailhead input,
|
||||
.trailhead button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.trailhead .trailheadInner {
|
||||
display: grid;
|
||||
grid-column-gap: 40px;
|
||||
grid-template-columns: 5fr 3fr;
|
||||
padding: 40px 60px; }
|
||||
.trailhead .trailheadContent h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 0; }
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
display: block;
|
||||
margin-top: 30px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
color: #FFF;
|
||||
min-width: 260px;
|
||||
padding-top: 100px; }
|
||||
.trailhead.syncCohort {
|
||||
left: calc(50% - 430px);
|
||||
width: 860px; }
|
||||
@media (max-width: 860px) {
|
||||
.trailhead.syncCohort {
|
||||
left: 0;
|
||||
width: 100%; } }
|
||||
.trailhead.syncCohort .trailheadInner {
|
||||
grid-template-columns: 4fr 3fr; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/sync-devices-trailhead.svg");
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 200px;
|
||||
margin-inline-end: 60px; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 0; }
|
||||
.trailhead .trailheadBenefits {
|
||||
padding: 0; }
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position: left 6px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 40px;
|
||||
-moz-context-properties: fill;
|
||||
fill: #0A84FF;
|
||||
list-style: none;
|
||||
padding-top: 8px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position-y: 4px;
|
||||
background-size: 62px;
|
||||
margin-inline-end: 60px;
|
||||
padding-inline-start: 74px; } }
|
||||
.trailhead .trailheadBenefits li:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
.trailhead .trailheadBenefits li.knowledge, .trailhead .trailheadBenefits li.monitor {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-knowledge.png"); }
|
||||
.trailhead .trailheadBenefits li.lockwise, .trailhead .trailheadBenefits li.privacy {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-privacy.png"); }
|
||||
.trailhead .trailheadBenefits li.products {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-products.png"); }
|
||||
.trailhead .trailheadBenefits li.sync {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-sync.png"); }
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
text-align: start;
|
||||
line-height: inherit;
|
||||
color: #CB9EFF;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
margin: 0 0 4px;
|
||||
padding-inline-start: 52px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
padding-inline-start: 0; } }
|
||||
.trailhead .trailheadBenefits p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 0 auto 40px;
|
||||
min-width: 300px;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
|
||||
background-color: #0250BB;
|
||||
border-color: transparent; }
|
||||
.trailhead .trailheadStart:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadStart:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadInner,
|
||||
.trailhead .trailheadStart {
|
||||
animation: fadeIn 0.4s; }
|
||||
|
||||
.trailheadCards {
|
||||
background: var(--trailhead-cards-background-color);
|
||||
overflow: hidden;
|
||||
|
@ -4242,39 +4575,171 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 21000;
|
||||
background-color: #FAFAFC; }
|
||||
.fullpage-wrapper + div {
|
||||
opacity: 0; }
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position-x: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; }
|
||||
.fullpage-wrapper .fullpage-icon:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position: center; } }
|
||||
.fullpage-wrapper .brand-logo {
|
||||
background-image: url("chrome://branding/content/about-logo.png");
|
||||
margin: 20px 10px 10px 20px;
|
||||
padding-bottom: 50px; }
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
align-self: center;
|
||||
margin: 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .welcome-title {
|
||||
color: #36296D;
|
||||
font-size: 46px;
|
||||
font-weight: 600;
|
||||
line-height: 62px; }
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
color: #7542E5;
|
||||
font-size: 20px;
|
||||
line-height: 27px; }
|
||||
.fullpage-wrapper .container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 300px; } }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-content {
|
||||
color: #4A4A4F;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
margin-inline-end: 2px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link {
|
||||
color: #0060DF;
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 30px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-title {
|
||||
margin: 0;
|
||||
color: #36296D;
|
||||
font-size: 36px;
|
||||
line-height: 48px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fx-systems-icons {
|
||||
height: 33px;
|
||||
display: block;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-systems.png");
|
||||
margin-bottom: 20px; }
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-inline-start: 36px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
margin-inline-start: 0; } }
|
||||
.fullpage-wrapper .fullpage-form .fxaSignupForm {
|
||||
width: 356px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
background: #FFF; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
margin: 4px 0 20px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
color: #4A4A4F;
|
||||
font-size: 12px;
|
||||
line-height: 16px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin {
|
||||
color: #4A4A4F;
|
||||
line-height: 30px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin button {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-form h3 {
|
||||
color: #36296D;
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
margin: 0;
|
||||
padding: 8px; }
|
||||
.fullpage-wrapper .fullpage-form h3 + p {
|
||||
color: #4A4A4F;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form input {
|
||||
background: #FFF;
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 2px; }
|
||||
.fullpage-wrapper .fullpage-form input:hover {
|
||||
border-color: #737373; }
|
||||
.fullpage-wrapper .fullpage-form input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fullpage-wrapper .fullpage-form button {
|
||||
color: #FFF;
|
||||
font-size: 16px; }
|
||||
.fullpage-wrapper .fullpage-form button:focus {
|
||||
outline: dotted 1px #737373; }
|
||||
.fullpage-wrapper .section-divider::after {
|
||||
content: '';
|
||||
display: block;
|
||||
border-bottom: 0.5px solid #D7D7DB; }
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-align: center;
|
||||
width: 320px;
|
||||
padding: 18px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingTitle {
|
||||
color: #0C0C0D; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingText {
|
||||
font-weight: normal;
|
||||
color: #4A4A4F;
|
||||
margin-top: 4px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton {
|
||||
color: #4A4A4F;
|
||||
background: rgba(12, 12, 13, 0.1); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
|
||||
background: rgba(12, 12, 13, 0.2); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:active {
|
||||
background: rgba(12, 12, 13, 0.3); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingMessageImage {
|
||||
height: 112px;
|
||||
width: 154px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 300px; } }
|
||||
|
|
|
@ -3607,6 +3607,112 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
box-shadow: 0 0 0 5px #D7D7DB;
|
||||
transition: box-shadow 150ms; }
|
||||
|
||||
.ReturnToAMOOverlay,
|
||||
.amo + body.hide-main {
|
||||
background: #F9F9FA;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2100; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText,
|
||||
.amo + body.hide-main .ReturnToAMOText {
|
||||
color: #0C0C0D;
|
||||
line-height: 32px;
|
||||
font-size: 23px;
|
||||
width: 100%; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOText img,
|
||||
.amo + body.hide-main .ReturnToAMOText img {
|
||||
margin-inline-start: 6px;
|
||||
margin-inline-end: 6px; }
|
||||
.ReturnToAMOOverlay h2,
|
||||
.amo + body.hide-main h2 {
|
||||
color: #4A4A4F;
|
||||
font-weight: 100;
|
||||
margin: 0 0 36px;
|
||||
font-size: 36px;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1.2px; }
|
||||
.ReturnToAMOOverlay p,
|
||||
.amo + body.hide-main p {
|
||||
color: #4A4A4F;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin-bottom: 16px; }
|
||||
.ReturnToAMOOverlay .puffy,
|
||||
.amo + body.hide-main .puffy {
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .blue,
|
||||
.amo + body.hide-main .blue {
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
background-color: #0060DF; }
|
||||
.ReturnToAMOOverlay .blue:hover,
|
||||
.amo + body.hide-main .blue:hover {
|
||||
box-shadow: none;
|
||||
background-color: #003EAA; }
|
||||
.ReturnToAMOOverlay .blue:active,
|
||||
.amo + body.hide-main .blue:active {
|
||||
background-color: #002275; }
|
||||
.ReturnToAMOOverlay .default,
|
||||
.amo + body.hide-main .default {
|
||||
border-radius: 2px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px; }
|
||||
.ReturnToAMOOverlay .grey,
|
||||
.amo + body.hide-main .grey {
|
||||
border: 0;
|
||||
background-color: rgba(12, 12, 13, 0.1); }
|
||||
.ReturnToAMOOverlay .grey:hover,
|
||||
.amo + body.hide-main .grey:hover {
|
||||
box-shadow: none;
|
||||
background-color: rgba(12, 12, 13, 0.2); }
|
||||
.ReturnToAMOOverlay .grey:active,
|
||||
.amo + body.hide-main .grey:active {
|
||||
background-color: rgba(12, 12, 13, 0.3); }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted,
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted {
|
||||
margin-top: 40px;
|
||||
float: right; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOGetStarted:dir(rtl),
|
||||
.amo + body.hide-main .ReturnToAMOGetStarted:dir(rtl) {
|
||||
float: left; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddExtension,
|
||||
.amo + body.hide-main .ReturnToAMOAddExtension {
|
||||
margin-top: 20px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOContainer,
|
||||
.amo + body.hide-main .ReturnToAMOContainer {
|
||||
width: 960px;
|
||||
background: #FFF;
|
||||
box-shadow: 0 1px 15px 0 rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
padding: 64px 64px 72px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOAddonContents,
|
||||
.amo + body.hide-main .ReturnToAMOAddonContents {
|
||||
width: 560px;
|
||||
margin-top: 32px;
|
||||
margin-inline-end: 24px; }
|
||||
.ReturnToAMOOverlay .ReturnToAMOIcon,
|
||||
.amo + body.hide-main .ReturnToAMOIcon {
|
||||
width: 292px;
|
||||
height: 254px;
|
||||
background-size: 292px 254px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/gift-extension.svg"); }
|
||||
.ReturnToAMOOverlay .icon-add,
|
||||
.amo + body.hide-main .icon-add {
|
||||
fill: #FFF;
|
||||
vertical-align: sub; }
|
||||
|
||||
.below-search-snippet {
|
||||
margin: 0 auto 16px; }
|
||||
.below-search-snippet.withButton {
|
||||
|
@ -4083,6 +4189,233 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
min-width: 80px;
|
||||
background-size: 140px; } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
|
||||
.fxaSignupForm {
|
||||
min-width: 260px;
|
||||
text-align: center; }
|
||||
.fxaSignupForm a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.fxaSignupForm input,
|
||||
.fxaSignupForm button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.fxaSignupForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.fxaSignupForm p {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.fxaSignupForm .fxa-terms {
|
||||
margin: 4px 30px 20px; }
|
||||
.fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.fxaSignupForm .fxa-signin {
|
||||
font-size: 16px;
|
||||
margin-top: 19px; }
|
||||
.fxaSignupForm .fxa-signin span {
|
||||
margin-inline-end: 5px; }
|
||||
.fxaSignupForm .fxa-signin button {
|
||||
background-color: initial;
|
||||
text-decoration: underline;
|
||||
color: #FFF;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
width: auto; }
|
||||
.fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
|
||||
background-color: initial; }
|
||||
.fxaSignupForm form {
|
||||
position: relative; }
|
||||
.fxaSignupForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.fxaSignupForm button,
|
||||
.fxaSignupForm input {
|
||||
width: 100%; }
|
||||
.fxaSignupForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.fxaSignupForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.fxaSignupForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.fxaSignupForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fxaSignupForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.fxaSignupForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.fxaSignupForm button:hover, .fxaSignupForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.fxaSignupForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.fxaSignupForm button:active {
|
||||
background-color: #054096; }
|
||||
|
||||
.trailhead {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
height: auto; }
|
||||
.trailhead a {
|
||||
color: #FFF;
|
||||
text-decoration: underline; }
|
||||
.trailhead input,
|
||||
.trailhead button {
|
||||
border-radius: 4px;
|
||||
padding: 10px; }
|
||||
.trailhead .trailheadInner {
|
||||
display: grid;
|
||||
grid-column-gap: 40px;
|
||||
grid-template-columns: 5fr 3fr;
|
||||
padding: 40px 60px; }
|
||||
.trailhead .trailheadContent h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 0; }
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
display: block;
|
||||
margin-top: 30px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
color: #FFF;
|
||||
min-width: 260px;
|
||||
padding-top: 100px; }
|
||||
.trailhead.syncCohort {
|
||||
left: calc(50% - 430px);
|
||||
width: 860px; }
|
||||
@media (max-width: 860px) {
|
||||
.trailhead.syncCohort {
|
||||
left: 0;
|
||||
width: 100%; } }
|
||||
.trailhead.syncCohort .trailheadInner {
|
||||
grid-template-columns: 4fr 3fr; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
|
||||
background: url("chrome://activity-stream/content/data/content/assets/sync-devices-trailhead.svg");
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 200px;
|
||||
margin-inline-end: 60px; }
|
||||
.trailhead.syncCohort .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 0; }
|
||||
.trailhead .trailheadBenefits {
|
||||
padding: 0; }
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position: left 6px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 40px;
|
||||
-moz-context-properties: fill;
|
||||
fill: #0A84FF;
|
||||
list-style: none;
|
||||
padding-top: 8px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits li {
|
||||
background-position-y: 4px;
|
||||
background-size: 62px;
|
||||
margin-inline-end: 60px;
|
||||
padding-inline-start: 74px; } }
|
||||
.trailhead .trailheadBenefits li:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
.trailhead .trailheadBenefits li.knowledge, .trailhead .trailheadBenefits li.monitor {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-knowledge.png"); }
|
||||
.trailhead .trailheadBenefits li.lockwise, .trailhead .trailheadBenefits li.privacy {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-privacy.png"); }
|
||||
.trailhead .trailheadBenefits li.products {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-products.png"); }
|
||||
.trailhead .trailheadBenefits li.sync {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/benefit-sync.png"); }
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
text-align: start;
|
||||
line-height: inherit;
|
||||
color: #CB9EFF;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
margin: 0 0 4px;
|
||||
padding-inline-start: 52px; }
|
||||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadBenefits h2 {
|
||||
padding-inline-start: 0; } }
|
||||
.trailhead .trailheadBenefits p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 0 auto 40px;
|
||||
min-width: 300px;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
|
||||
background-color: #0250BB;
|
||||
border-color: transparent; }
|
||||
.trailhead .trailheadStart:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadStart:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadInner,
|
||||
.trailhead .trailheadStart {
|
||||
animation: fadeIn 0.4s; }
|
||||
|
||||
.trailheadCards {
|
||||
background: var(--trailhead-cards-background-color);
|
||||
overflow: hidden;
|
||||
|
@ -4239,39 +4572,171 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin: 10px 0 8px;
|
||||
align-self: start;
|
||||
font-size: 14px;
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
padding: 5px 14px;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm input[type='radio']:checked:focus + .donation-amount,
|
||||
.EOYSnippetForm input[type='radio']:not(:checked):focus + .donation-amount {
|
||||
border: 1px dotted var(--newtab-link-primary-color); }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
flex-direction: column;
|
||||
overflow-x: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 21000;
|
||||
background-color: #FAFAFC; }
|
||||
.fullpage-wrapper + div {
|
||||
opacity: 0; }
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position-x: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain; }
|
||||
.fullpage-wrapper .fullpage-icon:dir(rtl) {
|
||||
background-position-x: right; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-icon {
|
||||
background-position: center; } }
|
||||
.fullpage-wrapper .brand-logo {
|
||||
background-image: url("chrome://branding/content/about-logo.png");
|
||||
margin: 20px 10px 10px 20px;
|
||||
padding-bottom: 50px; }
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
align-self: center;
|
||||
margin: 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .welcome-title,
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .welcome-title {
|
||||
color: #36296D;
|
||||
font-size: 46px;
|
||||
font-weight: 600;
|
||||
line-height: 62px; }
|
||||
.fullpage-wrapper .welcome-subtitle {
|
||||
color: #7542E5;
|
||||
font-size: 20px;
|
||||
line-height: 27px; }
|
||||
.fullpage-wrapper .container {
|
||||
display: flex;
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
align-self: flex-end;
|
||||
display: flex; }
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 300px; } }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-content {
|
||||
color: #4A4A4F;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
margin-inline-end: 2px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link {
|
||||
color: #0060DF;
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 30px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-left-section .fullpage-left-title {
|
||||
margin: 0;
|
||||
color: #36296D;
|
||||
font-size: 36px;
|
||||
line-height: 48px; }
|
||||
.fullpage-wrapper .fullpage-left-section .fx-systems-icons {
|
||||
height: 33px;
|
||||
display: block;
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/trailhead/firefox-systems.png");
|
||||
margin-bottom: 20px; }
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-inline-start: 36px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .fullpage-form {
|
||||
margin-inline-start: 0; } }
|
||||
.fullpage-wrapper .fullpage-form .fxaSignupForm {
|
||||
width: 356px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
background: #FFF; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
margin: 4px 0 20px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
|
||||
color: #4A4A4F;
|
||||
font-size: 12px;
|
||||
line-height: 16px; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin {
|
||||
color: #4A4A4F;
|
||||
line-height: 30px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form .fxa-signin button {
|
||||
color: #0060DF; }
|
||||
.fullpage-wrapper .fullpage-form h3 {
|
||||
color: #36296D;
|
||||
font-weight: 400;
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
margin: 0;
|
||||
padding: 8px; }
|
||||
.fullpage-wrapper .fullpage-form h3 + p {
|
||||
color: #4A4A4F;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
opacity: 0.77; }
|
||||
.fullpage-wrapper .fullpage-form input {
|
||||
background: #FFF;
|
||||
border: 1px solid #D7D7DB;
|
||||
border-radius: 2px; }
|
||||
.fullpage-wrapper .fullpage-form input:hover {
|
||||
border-color: #737373; }
|
||||
.fullpage-wrapper .fullpage-form input.invalid {
|
||||
border-color: #D70022; }
|
||||
.fullpage-wrapper .fullpage-form button {
|
||||
color: #FFF;
|
||||
font-size: 16px; }
|
||||
.fullpage-wrapper .fullpage-form button:focus {
|
||||
outline: dotted 1px #737373; }
|
||||
.fullpage-wrapper .section-divider::after {
|
||||
content: '';
|
||||
display: block;
|
||||
border-bottom: 0.5px solid #D7D7DB; }
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-align: center;
|
||||
width: 320px;
|
||||
padding: 18px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingTitle {
|
||||
color: #0C0C0D; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingText {
|
||||
font-weight: normal;
|
||||
color: #4A4A4F;
|
||||
margin-top: 4px; }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton {
|
||||
color: #4A4A4F;
|
||||
background: rgba(12, 12, 13, 0.1); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
|
||||
background: rgba(12, 12, 13, 0.2); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingButton:active {
|
||||
background: rgba(12, 12, 13, 0.3); }
|
||||
.fullpage-wrapper .trailheadCard .onboardingMessageImage {
|
||||
height: 112px;
|
||||
width: 154px; }
|
||||
@media screen and (max-width: 975px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 300px; } }
|
||||
|
|
После Ширина: | Высота: | Размер: 12 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/accounts-form-bg.jpg
Executable file
После Ширина: | Высота: | Размер: 23 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/benefit-knowledge.png
Normal file
После Ширина: | Высота: | Размер: 4.2 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/benefit-privacy.png
Normal file
После Ширина: | Высота: | Размер: 8.0 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/benefit-products.png
Normal file
После Ширина: | Высота: | Размер: 4.7 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/benefit-sync.png
Normal file
После Ширина: | Высота: | Размер: 7.1 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/firefox-logo.png
Normal file
После Ширина: | Высота: | Размер: 30 KiB |
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/firefox-systems.png
Normal file
После Ширина: | Высота: | Размер: 17 KiB |
|
@ -1353,6 +1353,23 @@ This reports a failure in the Remote Settings loader to load messages for Activi
|
|||
}
|
||||
```
|
||||
|
||||
## Trailhead experiment enrollment ping
|
||||
|
||||
This reports an enrollment ping when a user gets enrolled in a Trailhead experiment. Note that this ping is only collected through the Mozilla Events telemetry pipeline.
|
||||
|
||||
```js
|
||||
{
|
||||
"category": "activity_stream",
|
||||
"method": "enroll",
|
||||
"object": "preference_study"
|
||||
"value": "activity-stream-firstup-trailhead-interrupts",
|
||||
"extra_keys": {
|
||||
"experimentType": "as-firstrun",
|
||||
"branch": ["supercharge" | "join" | "sync" | "privacy" ...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Callouts interaction pings
|
||||
|
||||
This reports when a user has seen or clicked a badge/notification in the browser toolbar in a non-PBM window
|
||||
|
|
|
@ -549,6 +549,7 @@ class _ASRouter {
|
|||
providers: [],
|
||||
messageBlockList: [],
|
||||
messageImpressions: {},
|
||||
trailheadInitialized: false,
|
||||
messages: [],
|
||||
groups: [],
|
||||
errors: [],
|
||||
|
@ -1037,7 +1038,7 @@ class _ASRouter {
|
|||
ASRouterTargeting.Environment,
|
||||
this._getMessagesContext()
|
||||
),
|
||||
trailheadTriplet: ASRouterPreferences.trailheadTriplet,
|
||||
trailhead: ASRouterPreferences.trailhead,
|
||||
errors: this.errors,
|
||||
},
|
||||
});
|
||||
|
@ -1056,6 +1057,18 @@ class _ASRouter {
|
|||
}
|
||||
}
|
||||
|
||||
async setTrailHeadMessageSeen() {
|
||||
if (!this.state.trailheadInitialized) {
|
||||
Services.prefs.setBoolPref(
|
||||
TRAILHEAD_CONFIG.DID_SEE_ABOUT_WELCOME_PREF,
|
||||
true
|
||||
);
|
||||
await this.setState({
|
||||
trailheadInitialized: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Return an object containing targeting parameters used to select messages
|
||||
_getMessagesContext() {
|
||||
const { messageImpressions, previousSessionEnd } = this.state;
|
||||
|
@ -1214,7 +1227,9 @@ class _ASRouter {
|
|||
// This is used to determine whether to block when action is triggered
|
||||
// Only block for dynamic triplets experiment and when there are more messages available
|
||||
blockOnClick:
|
||||
ASRouterPreferences.trailheadTriplet.startsWith("dynamic") &&
|
||||
ASRouterPreferences.trailhead.trailheadTriplet.startsWith(
|
||||
"dynamic"
|
||||
) &&
|
||||
allMessages.length >
|
||||
TRAILHEAD_CONFIG.DYNAMIC_TRIPLET_BUNDLE_LENGTH,
|
||||
}))
|
||||
|
@ -1350,7 +1365,8 @@ class _ASRouter {
|
|||
type: "SET_MESSAGE",
|
||||
data: {
|
||||
...message,
|
||||
trailheadTriplet: ASRouterPreferences.trailheadTriplet || "",
|
||||
trailheadTriplet:
|
||||
ASRouterPreferences.trailhead.trailheadTriplet || "",
|
||||
bundle: bundledMessages && bundledMessages.bundle,
|
||||
},
|
||||
});
|
||||
|
@ -1884,6 +1900,11 @@ class _ASRouter {
|
|||
async sendTriggerMessage(target, trigger) {
|
||||
await this.loadMessagesFromAllProviders();
|
||||
|
||||
if (trigger.id === "firstRun") {
|
||||
// On about welcome, set trailhead message seen on receiving firstrun trigger
|
||||
await this.setTrailHeadMessageSeen();
|
||||
}
|
||||
|
||||
const telemetryObject = { port: target.portID };
|
||||
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
// Return all the messages so that it can record the Reach event
|
||||
|
|
|
@ -13,13 +13,25 @@ const PROVIDER_PREF_BRANCH =
|
|||
const DEVTOOLS_PREF =
|
||||
"browser.newtabpage.activity-stream.asrouter.devtoolsEnabled";
|
||||
const FXA_USERNAME_PREF = "services.sync.username";
|
||||
const FIRST_RUN_TRIPLET_PREF = "trailhead.firstrun.newtab.triplets";
|
||||
const FIRST_RUN_PREF = "trailhead.firstrun.branches";
|
||||
const DEFAULT_FIRSTRUN_TRIPLET = "supercharge";
|
||||
const DEFAULT_FIRSTRUN_INTERRUPT = "join";
|
||||
|
||||
function getTrailheadConfigFromPref(value) {
|
||||
let [interrupt, triplet] = value.split("-");
|
||||
return {
|
||||
trailheadInterrupt: interrupt || DEFAULT_FIRSTRUN_INTERRUPT,
|
||||
trailheadTriplet: triplet || DEFAULT_FIRSTRUN_TRIPLET,
|
||||
};
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"trailheadTripletPref",
|
||||
FIRST_RUN_TRIPLET_PREF,
|
||||
""
|
||||
"trailheadPrefs",
|
||||
FIRST_RUN_PREF,
|
||||
"",
|
||||
null,
|
||||
getTrailheadConfigFromPref
|
||||
);
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
|
@ -107,8 +119,8 @@ class _ASRouterPreferences {
|
|||
}
|
||||
|
||||
// istanbul ignore next
|
||||
get trailheadTriplet() {
|
||||
return trailheadTripletPref;
|
||||
get trailhead() {
|
||||
return trailheadPrefs;
|
||||
}
|
||||
|
||||
get providers() {
|
||||
|
@ -232,10 +244,12 @@ this._ASRouterPreferences = _ASRouterPreferences;
|
|||
this.ASRouterPreferences = new _ASRouterPreferences();
|
||||
this.TEST_PROVIDERS = TEST_PROVIDERS;
|
||||
this.TARGETING_PREFERENCES = TARGETING_PREFERENCES;
|
||||
this.getTrailheadConfigFromPref = getTrailheadConfigFromPref;
|
||||
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"_ASRouterPreferences",
|
||||
"ASRouterPreferences",
|
||||
"TEST_PROVIDERS",
|
||||
"TARGETING_PREFERENCES",
|
||||
"getTrailheadConfigFromPref",
|
||||
];
|
||||
|
|
|
@ -420,8 +420,11 @@ const TargetingGetters = {
|
|||
get isFxAEnabled() {
|
||||
return isFxAEnabled;
|
||||
},
|
||||
get trailheadInterrupt() {
|
||||
return ASRouterPreferences.trailhead.trailheadInterrupt;
|
||||
},
|
||||
get trailheadTriplet() {
|
||||
return ASRouterPreferences.trailheadTriplet;
|
||||
return ASRouterPreferences.trailhead.trailheadTriplet;
|
||||
},
|
||||
get sync() {
|
||||
return {
|
||||
|
|
|
@ -3,6 +3,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
/* globals Localization */
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AttributionCode",
|
||||
"resource:///modules/AttributionCode.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AddonRepository",
|
||||
"resource://gre/modules/addons/AddonRepository.jsm"
|
||||
);
|
||||
const { FX_MONITOR_OAUTH_CLIENT_ID } = ChromeUtils.import(
|
||||
"resource://gre/modules/FxAccountsCommon.js"
|
||||
);
|
||||
|
@ -15,7 +25,146 @@ const L10N = new Localization([
|
|||
"browser/newtab/onboarding.ftl",
|
||||
]);
|
||||
|
||||
const TRAILHEAD_ONBOARDING_TEMPLATE = {
|
||||
trigger: { id: "firstRun" },
|
||||
template: "trailhead",
|
||||
includeBundle: {
|
||||
length: 3,
|
||||
template: "onboarding",
|
||||
trigger: { id: "showOnboarding" },
|
||||
},
|
||||
};
|
||||
|
||||
const TRAILHEAD_FULL_PAGE_CONTENT = {
|
||||
title: { string_id: "onboarding-welcome-body" },
|
||||
learn: {
|
||||
text: { string_id: "onboarding-welcome-learn-more" },
|
||||
url: "https://www.mozilla.org/firefox/accounts/",
|
||||
},
|
||||
form: {
|
||||
title: { string_id: "onboarding-welcome-form-header" },
|
||||
text: { string_id: "onboarding-join-form-body" },
|
||||
email: { string_id: "onboarding-fullpage-form-email" },
|
||||
button: { string_id: "onboarding-join-form-continue" },
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_WELCOME_CONTENT = {
|
||||
className: "welcomeCohort",
|
||||
benefits: ["sync", "monitor", "lockwise"].map(id => ({
|
||||
id,
|
||||
title: { string_id: `onboarding-benefit-${id}-title` },
|
||||
text: { string_id: `onboarding-benefit-${id}-text` },
|
||||
})),
|
||||
learn: {
|
||||
text: { string_id: "onboarding-welcome-modal-family-learn-more" },
|
||||
url: "https://www.mozilla.org/firefox/accounts/",
|
||||
},
|
||||
form: {
|
||||
title: { string_id: "onboarding-welcome-form-header" },
|
||||
text: { string_id: "onboarding-join-form-body" },
|
||||
email: { string_id: "onboarding-join-form-email" },
|
||||
button: { string_id: "onboarding-join-form-continue" },
|
||||
},
|
||||
skipButton: { string_id: "onboarding-start-browsing-button-label" },
|
||||
};
|
||||
|
||||
const ONBOARDING_MESSAGES = () => [
|
||||
{
|
||||
id: "TRAILHEAD_1",
|
||||
utm_term: "trailhead-join",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
...DEFAULT_WELCOME_CONTENT,
|
||||
title: { string_id: "onboarding-welcome-body" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_2",
|
||||
targeting: "trailheadInterrupt == 'sync'",
|
||||
utm_term: "trailhead-sync",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
className: "syncCohort",
|
||||
title: { string_id: "onboarding-sync-welcome-header" },
|
||||
subtitle: { string_id: "onboarding-sync-welcome-content" },
|
||||
benefits: [],
|
||||
learn: {
|
||||
text: { string_id: "onboarding-sync-welcome-learn-more-link" },
|
||||
url: "https://www.mozilla.org/firefox/accounts/",
|
||||
},
|
||||
form: {
|
||||
title: { string_id: "onboarding-sync-form-header" },
|
||||
text: { string_id: "onboarding-sync-form-sub-header" },
|
||||
email: { string_id: "onboarding-sync-form-input" },
|
||||
button: { string_id: "onboarding-sync-form-continue-button" },
|
||||
},
|
||||
skipButton: { string_id: "onboarding-sync-form-skip-login-button" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_3",
|
||||
targeting: "trailheadInterrupt == 'cards'",
|
||||
utm_term: "trailhead-cards",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_4",
|
||||
template: "trailhead",
|
||||
targeting: "trailheadInterrupt == 'nofirstrun'",
|
||||
trigger: { id: "firstRun" },
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_6",
|
||||
targeting: "trailheadInterrupt == 'modal_variant_a'",
|
||||
utm_term: "trailhead-modal_variant_a",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
...DEFAULT_WELCOME_CONTENT,
|
||||
title: { string_id: "onboarding-welcome-modal-get-body" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_7",
|
||||
targeting: "trailheadInterrupt == 'modal_variant_b'",
|
||||
utm_term: "trailhead-modal_variant_b",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
...DEFAULT_WELCOME_CONTENT,
|
||||
title: { string_id: "onboarding-welcome-modal-supercharge-body" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "TRAILHEAD_8",
|
||||
targeting: "trailheadInterrupt == 'modal_variant_c'",
|
||||
utm_term: "trailhead-modal_variant_c",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
...DEFAULT_WELCOME_CONTENT,
|
||||
title: { string_id: "onboarding-welcome-modal-privacy-body" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "FULL_PAGE_1",
|
||||
targeting: "trailheadInterrupt == 'full_page_d'",
|
||||
utm_term: "trailhead-full_page_d",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
...TRAILHEAD_FULL_PAGE_CONTENT,
|
||||
},
|
||||
template: "full_page_interrupt",
|
||||
},
|
||||
{
|
||||
id: "FULL_PAGE_2",
|
||||
targeting: "trailheadInterrupt == 'full_page_e'",
|
||||
utm_term: "trailhead-full_page_e",
|
||||
...TRAILHEAD_ONBOARDING_TEMPLATE,
|
||||
content: {
|
||||
className: "fullPageCardsAtTop",
|
||||
...TRAILHEAD_FULL_PAGE_CONTENT,
|
||||
},
|
||||
template: "full_page_interrupt",
|
||||
},
|
||||
{
|
||||
id: "EXTENDED_TRIPLETS_1",
|
||||
template: "extended_triplets",
|
||||
|
@ -299,6 +448,38 @@ const ONBOARDING_MESSAGES = () => [
|
|||
targeting: "trailheadTriplet == 'privacy'",
|
||||
trigger: { id: "showOnboarding" },
|
||||
},
|
||||
{
|
||||
id: "RETURN_TO_AMO_1",
|
||||
template: "return_to_amo_overlay",
|
||||
content: {
|
||||
header: { string_id: "onboarding-welcome-header" },
|
||||
title: { string_id: "return-to-amo-sub-header" },
|
||||
addon_icon: null,
|
||||
icon: "gift-extension",
|
||||
text: {
|
||||
string_id: "return-to-amo-addon-header",
|
||||
args: { "addon-name": null },
|
||||
},
|
||||
primary_button: {
|
||||
label: { string_id: "return-to-amo-extension-button" },
|
||||
action: {
|
||||
type: "INSTALL_ADDON_FROM_URL",
|
||||
data: { url: null, telemetrySource: "rtamo" },
|
||||
},
|
||||
},
|
||||
secondary_button: {
|
||||
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" },
|
||||
},
|
||||
{
|
||||
id: "FXA_ACCOUNTS_BADGE",
|
||||
template: "toolbar_badge",
|
||||
|
@ -354,6 +535,40 @@ const OnboardingMessageProvider = {
|
|||
continue;
|
||||
}
|
||||
|
||||
// We need some addon info if we are showing return to amo overlay, so fetch
|
||||
// that, and update the message accordingly
|
||||
if (msg.template === "return_to_amo_overlay") {
|
||||
try {
|
||||
const { name, iconURL, url } = await this.getAddonInfo();
|
||||
// If we do not have all the data from the AMO api to indicate to the user
|
||||
// what they are installing we don't want to show the message
|
||||
if (!name || !iconURL || !url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
msg.content.text.args["addon-name"] = name;
|
||||
msg.content.addon_icon = iconURL;
|
||||
msg.content.primary_button.action.data.url = url;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We know we want to show this message, so translate message strings
|
||||
const [
|
||||
primary_button_string,
|
||||
title_string,
|
||||
text_string,
|
||||
] = await L10N.formatMessages([
|
||||
{ id: msg.content.primary_button.label.string_id },
|
||||
{ id: msg.content.title.string_id },
|
||||
{ id: msg.content.text.string_id, args: msg.content.text.args },
|
||||
]);
|
||||
translatedMessage.content.primary_button.label =
|
||||
primary_button_string.value;
|
||||
translatedMessage.content.title = title_string.value;
|
||||
translatedMessage.content.text = text_string.value;
|
||||
}
|
||||
|
||||
// Translate any secondary buttons separately
|
||||
if (msg.content.secondary_button) {
|
||||
const [secondary_button_string] = await L10N.formatMessages([
|
||||
|
@ -372,6 +587,40 @@ const OnboardingMessageProvider = {
|
|||
}
|
||||
return translatedMessages;
|
||||
},
|
||||
async getAddonInfo() {
|
||||
try {
|
||||
let { content, source } = await AttributionCode.getAttrDataAsync();
|
||||
if (!content || source !== "addons.mozilla.org") {
|
||||
return null;
|
||||
}
|
||||
// Attribution data can be double encoded
|
||||
while (content.includes("%")) {
|
||||
try {
|
||||
const result = decodeURIComponent(content);
|
||||
if (result === content) {
|
||||
break;
|
||||
}
|
||||
content = result;
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const [addon] = await AddonRepository.getAddonsByIDs([content]);
|
||||
if (addon.sourceURI.scheme !== "https") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: addon.name,
|
||||
url: addon.sourceURI.spec,
|
||||
iconURL: addon.icons["64"] || addon.icons["32"],
|
||||
};
|
||||
} catch (e) {
|
||||
Cu.reportError(
|
||||
"Failed to get the latest add-on version for Return to AMO"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
this.OnboardingMessageProvider = OnboardingMessageProvider;
|
||||
|
||||
|
|
|
@ -845,6 +845,14 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
this.sendEvent(this.createUndesiredEvent(action));
|
||||
}
|
||||
|
||||
handleTrailheadEnrollEvent(action) {
|
||||
// Unlike `sendUTEvent`, we always send the event if AS's telemetry is enabled
|
||||
// regardless of `this.eventTelemetryEnabled`.
|
||||
if (this.telemetryEnabled) {
|
||||
this.utEvents.sendTrailheadEnrollEvent(action.data);
|
||||
}
|
||||
}
|
||||
|
||||
async sendPageTakeoverData() {
|
||||
if (this.telemetryEnabled) {
|
||||
const value = {};
|
||||
|
@ -958,6 +966,9 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
case at.TELEMETRY_PERFORMANCE_EVENT:
|
||||
this.sendEvent(this.createPerformanceEvent(action));
|
||||
break;
|
||||
case at.TRAILHEAD_ENROLL_EVENT:
|
||||
this.handleTrailheadEnrollEvent(action);
|
||||
break;
|
||||
case at.UNINIT:
|
||||
this.uninit();
|
||||
break;
|
||||
|
|
|
@ -23,6 +23,7 @@ this.UTEventReporting = class UTEventReporting {
|
|||
Services.telemetry.setEventRecordingEnabled("activity_stream", true);
|
||||
this.sendUserEvent = this.sendUserEvent.bind(this);
|
||||
this.sendSessionEndEvent = this.sendSessionEndEvent.bind(this);
|
||||
this.sendTrailheadEnrollEvent = this.sendTrailheadEnrollEvent.bind(this);
|
||||
}
|
||||
|
||||
_createExtras(data) {
|
||||
|
@ -60,6 +61,19 @@ this.UTEventReporting = class UTEventReporting {
|
|||
);
|
||||
}
|
||||
|
||||
sendTrailheadEnrollEvent(data) {
|
||||
Services.telemetry.recordEvent(
|
||||
"activity_stream",
|
||||
"enroll",
|
||||
"preference_study",
|
||||
data.experiment,
|
||||
{
|
||||
experimentType: data.type,
|
||||
branch: data.branch,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
Services.telemetry.setEventRecordingEnabled("activity_stream", false);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ prefs =
|
|||
browser.newtabpage.activity-stream.feeds.section.topstories=true
|
||||
browser.newtabpage.activity-stream.feeds.section.topstories.options={"provider_name":""}
|
||||
|
||||
[browser_aboutwelcome.js]
|
||||
[browser_aboutwelcome_actors.js]
|
||||
[browser_aboutwelcome_simplified.js]
|
||||
[browser_aboutwelcome_multistage.js]
|
||||
|
@ -29,6 +30,7 @@ prefs =
|
|||
[browser_discovery_card.js]
|
||||
[browser_getScreenshots.js]
|
||||
[browser_newtab_overrides.js]
|
||||
[browser_onboarding_rtamo.js]
|
||||
skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
|
||||
[browser_topsites_contextMenu_options.js]
|
||||
[browser_topsites_section.js]
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
"use strict";
|
||||
|
||||
const { ASRouter } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/ASRouter.jsm"
|
||||
);
|
||||
|
||||
const BRANCH_PREF = "trailhead.firstrun.branches";
|
||||
const SIMPLIFIED_WELCOME_ENABLED_PREF = "browser.aboutwelcome.enabled";
|
||||
|
||||
/**
|
||||
* Sets the trailhead branch pref to the passed value.
|
||||
*/
|
||||
async function setTrailheadBranch(value) {
|
||||
Services.prefs.setCharPref(BRANCH_PREF, value);
|
||||
// Set about:welcome to use trailhead flow
|
||||
Services.prefs.setBoolPref(SIMPLIFIED_WELCOME_ENABLED_PREF, false);
|
||||
|
||||
// Reset trailhead so it loads the new branch.
|
||||
Services.prefs.clearUserPref("trailhead.firstrun.didSeeAboutWelcome");
|
||||
await ASRouter.setState({ trailheadInitialized: false });
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(BRANCH_PREF);
|
||||
Services.prefs.clearUserPref(SIMPLIFIED_WELCOME_ENABLED_PREF);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a specific trailhead branch.
|
||||
*/
|
||||
async function test_trailhead_branch(
|
||||
branchName,
|
||||
expectedSelectors = [],
|
||||
unexpectedSelectors = []
|
||||
) {
|
||||
await setTrailheadBranch(branchName);
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:welcome",
|
||||
false
|
||||
);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
await ContentTask.spawn(
|
||||
browser,
|
||||
{ expectedSelectors, branchName, unexpectedSelectors },
|
||||
async ({
|
||||
expectedSelectors: expected,
|
||||
branchName: branch,
|
||||
unexpectedSelectors: unexpected,
|
||||
}) => {
|
||||
for (let selector of expected) {
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector(selector),
|
||||
`Should render ${selector} in the ${branch} branch`
|
||||
);
|
||||
}
|
||||
for (let selector of unexpected) {
|
||||
ok(
|
||||
!content.document.querySelector(selector),
|
||||
`Should not render ${selector} in the ${branch} branch`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the the various trailhead branches.
|
||||
*/
|
||||
add_task(async function test_trailhead_branches() {
|
||||
await test_trailhead_branch(
|
||||
"join-dynamic",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"button[data-l10n-id=onboarding-data-sync-button2]",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
"button[data-l10n-id=onboarding-browse-privately-button]",
|
||||
]
|
||||
);
|
||||
|
||||
// Validate sync card is not shown if user usesFirefoxSync
|
||||
await pushPrefs(["services.sync.username", "someone@foo.com"]);
|
||||
await test_trailhead_branch(
|
||||
"join-dynamic",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
"button[data-l10n-id=onboarding-browse-privately-button]",
|
||||
],
|
||||
// Unexpected selectors:
|
||||
["button[data-l10n-id=onboarding-data-sync-button2]"]
|
||||
);
|
||||
|
||||
// Validate multidevice card is not shown if user has mobile devices connected
|
||||
await pushPrefs(["services.sync.clients.devices.mobile", 1]);
|
||||
await test_trailhead_branch(
|
||||
"join-dynamic",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
],
|
||||
// Unexpected selectors:
|
||||
["button[data-l10n-id=onboarding-mobile-phone-button"]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"sync-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.syncCohort",
|
||||
"button[data-l10n-id=onboarding-data-sync-button2]",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
"button[data-l10n-id=onboarding-mobile-phone-button]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"modal_variant_a-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"p[data-l10n-id=onboarding-benefit-sync-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-monitor-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-lockwise-text]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"modal_variant_f-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"h3[data-l10n-id=onboarding-welcome-form-header]",
|
||||
"p[data-l10n-id=onboarding-benefit-sync-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-monitor-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-lockwise-text]",
|
||||
"button[data-l10n-id=onboarding-join-form-signin]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"join-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"h3[data-l10n-id=onboarding-welcome-form-header]",
|
||||
"p[data-l10n-id=onboarding-benefit-sync-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-monitor-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-lockwise-text]",
|
||||
"button[data-l10n-id=onboarding-join-form-signin]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"full_page_d-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead-fullpage",
|
||||
".trailheadCard",
|
||||
"p[data-l10n-id=onboarding-benefit-products-text]",
|
||||
"button[data-l10n-id=onboarding-join-form-continue]",
|
||||
"button[data-l10n-id=onboarding-join-form-signin]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"full_page_e-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".fullPageCardsAtTop",
|
||||
".trailhead-fullpage",
|
||||
".trailheadCard",
|
||||
"p[data-l10n-id=onboarding-benefit-products-text]",
|
||||
"button[data-l10n-id=onboarding-join-form-continue]",
|
||||
"button[data-l10n-id=onboarding-join-form-signin]",
|
||||
]
|
||||
);
|
||||
|
||||
await test_trailhead_branch(
|
||||
"nofirstrun",
|
||||
[],
|
||||
// Unexpected selectors:
|
||||
["#trailheadDialog", ".trailheadCards"]
|
||||
);
|
||||
|
||||
// Test trailhead default join-supercharge branch renders
|
||||
// correct when separate about welcome pref is false
|
||||
await test_trailhead_branch(
|
||||
"join-supercharge",
|
||||
// Expected selectors:
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
"h1[data-l10n-id=onboarding-welcome-header]",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
"h3[data-l10n-id=onboarding-welcome-form-header]",
|
||||
"p[data-l10n-id=onboarding-benefit-sync-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-monitor-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-lockwise-text]",
|
||||
],
|
||||
["h2[data-l10n-id=onboarding-fullpage-welcome-subheader]"]
|
||||
);
|
||||
});
|
|
@ -72,6 +72,17 @@ add_task(async function test_Separate_About_Welcome_branches() {
|
|||
"button.secondary",
|
||||
],
|
||||
// Unexpected selectors:
|
||||
[".trailhead.welcomeCohort", ".welcome-subtitle"]
|
||||
[
|
||||
".trailhead.welcomeCohort",
|
||||
".welcome-subtitle",
|
||||
"h3[data-l10n-id=onboarding-welcome-form-header]",
|
||||
"p[data-l10n-id=onboarding-benefit-sync-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-monitor-text]",
|
||||
"p[data-l10n-id=onboarding-benefit-lockwise-text]",
|
||||
"h1[data-l10n-id=onboarding-welcome-header]",
|
||||
"button[data-l10n-id=onboarding-data-sync-button2]",
|
||||
"button[data-l10n-id=onboarding-firefox-monitor-button]",
|
||||
"button[data-l10n-id=onboarding-browse-privately-button",
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
const { ASRouter } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/ASRouter.jsm"
|
||||
);
|
||||
const { OnboardingMessageProvider } = ChromeUtils.import(
|
||||
"resource://activity-stream/lib/OnboardingMessageProvider.jsm"
|
||||
);
|
||||
const { AttributionCode } = ChromeUtils.import(
|
||||
"resource:///modules/AttributionCode.jsm"
|
||||
);
|
||||
|
||||
const BRANCH_PREF = "trailhead.firstrun.branches";
|
||||
const SIMPLIFIED_WELCOME_ENABLED_PREF = "browser.aboutwelcome.enabled";
|
||||
|
||||
async function setRTAMOOnboarding() {
|
||||
await ASRouter.forceAttribution({
|
||||
campaign: "non-fx-button",
|
||||
source: "addons.mozilla.org",
|
||||
content: "iridium@particlecore.github.io",
|
||||
});
|
||||
AttributionCode._clearCache();
|
||||
const data = await AttributionCode.getAttrDataAsync();
|
||||
Assert.equal(
|
||||
data.source,
|
||||
"addons.mozilla.org",
|
||||
"Attribution data should be set"
|
||||
);
|
||||
|
||||
Services.prefs.setCharPref(BRANCH_PREF, "join-supercharge");
|
||||
// Set about:welcome to use trailhead flow
|
||||
Services.prefs.setBoolPref(SIMPLIFIED_WELCOME_ENABLED_PREF, false);
|
||||
|
||||
// Reset trailhead so it loads the new branch.
|
||||
Services.prefs.clearUserPref("trailhead.firstrun.didSeeAboutWelcome");
|
||||
await ASRouter.setState({ trailheadInitialized: false });
|
||||
ASRouter._updateMessageProviders();
|
||||
await ASRouter.loadMessagesFromAllProviders();
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
// Separate cleanup methods between mac and windows
|
||||
if (AppConstants.platform === "macosx") {
|
||||
const { path } = Services.dirsvc.get("GreD", Ci.nsIFile).parent.parent;
|
||||
const attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
|
||||
Ci.nsIMacAttributionService
|
||||
);
|
||||
attributionSvc.setReferrerUrl(path, "", true);
|
||||
}
|
||||
// Clear cache call is only possible in a testing environment
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(
|
||||
Ci.nsIEnvironment
|
||||
);
|
||||
env.set("XPCSHELL_TEST_PROFILE_DIR", "testing");
|
||||
Services.prefs.clearUserPref(BRANCH_PREF);
|
||||
Services.prefs.clearUserPref(SIMPLIFIED_WELCOME_ENABLED_PREF);
|
||||
await AttributionCode.deleteFileAsync();
|
||||
AttributionCode._clearCache();
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
// Store it in order to restore to the original value
|
||||
const { getAddonInfo } = OnboardingMessageProvider;
|
||||
// Prevent fetching the real addon url and making a network request
|
||||
OnboardingMessageProvider.getAddonInfo = () => ({
|
||||
name: "mochitest_name",
|
||||
iconURL: "mochitest_iconURL",
|
||||
url: "https://example.com",
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
OnboardingMessageProvider.getAddonInfo = getAddonInfo;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async () => {
|
||||
await setRTAMOOnboarding();
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:welcome" },
|
||||
async browser => {
|
||||
let modalText = await SpecialPowers.spawn(browser, [], async () => {
|
||||
// Wait for Activity Stream to load
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector(".activity-stream"),
|
||||
`Should render Activity Stream`
|
||||
);
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.body.classList.contains("welcome"),
|
||||
"The modal setup should be completed"
|
||||
);
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.body.classList.contains("hide-main"),
|
||||
"You shouldn't be able to see newtabpage content"
|
||||
);
|
||||
for (let selector of [
|
||||
// ReturnToAMO elements
|
||||
".ReturnToAMOOverlay",
|
||||
".ReturnToAMOContainer",
|
||||
".ReturnToAMOAddonContents",
|
||||
".ReturnToAMOIcon",
|
||||
]) {
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector(selector) !== null,
|
||||
`Should render ${selector}`
|
||||
);
|
||||
}
|
||||
|
||||
return content.document.querySelector(".ReturnToAMOText").innerText;
|
||||
});
|
||||
// Make sure strings are properly shown
|
||||
Assert.equal(modalText, "Now let’s get you mochitest_name.");
|
||||
}
|
||||
);
|
||||
});
|
|
@ -323,6 +323,27 @@ export const UTSessionPing = Joi.array().items(
|
|||
eventsTelemetryExtraKeys
|
||||
);
|
||||
|
||||
export const trailheadEnrollExtraKeys = Joi.object()
|
||||
.keys({
|
||||
experimentType: Joi.string().required(),
|
||||
branch: Joi.string().required(),
|
||||
})
|
||||
.options({ allowUnknown: false });
|
||||
|
||||
export const UTTrailheadEnrollPing = Joi.array().items(
|
||||
Joi.string()
|
||||
.required()
|
||||
.valid("activity_stream"),
|
||||
Joi.string()
|
||||
.required()
|
||||
.valid("enroll"),
|
||||
Joi.string()
|
||||
.required()
|
||||
.valid("preference_study"),
|
||||
Joi.string().required(),
|
||||
trailheadEnrollExtraKeys
|
||||
);
|
||||
|
||||
export function chaiAssertions(_chai, utils) {
|
||||
const { Assertion } = _chai;
|
||||
|
||||
|
|
|
@ -121,8 +121,8 @@ describe("ASRouter", () => {
|
|||
sandbox.spy(ASRouterPreferences, "uninit");
|
||||
sandbox.spy(ASRouterPreferences, "addListener");
|
||||
sandbox.spy(ASRouterPreferences, "removeListener");
|
||||
sandbox.stub(ASRouterPreferences, "trailheadTriplet").get(() => {
|
||||
return "test";
|
||||
sandbox.stub(ASRouterPreferences, "trailhead").get(() => {
|
||||
return { trailheadTriplet: "test" };
|
||||
});
|
||||
sandbox.replaceGetter(
|
||||
ASRouterPreferences,
|
||||
|
@ -583,7 +583,7 @@ describe("ASRouter", () => {
|
|||
providerPrefs: ASRouterPreferences.providers,
|
||||
userPrefs: ASRouterPreferences.getAllUserPreferences(),
|
||||
targetingParameters: {},
|
||||
trailheadTriplet: ASRouterPreferences.trailheadTriplet,
|
||||
trailhead: ASRouterPreferences.trailhead,
|
||||
errors: Router.errors,
|
||||
}),
|
||||
});
|
||||
|
@ -2111,7 +2111,7 @@ describe("ASRouter", () => {
|
|||
providerPrefs: ASRouterPreferences.providers,
|
||||
userPrefs: ASRouterPreferences.getAllUserPreferences(),
|
||||
targetingParameters: {},
|
||||
trailheadTriplet: ASRouterPreferences.trailheadTriplet,
|
||||
trailhead: ASRouterPreferences.trailhead,
|
||||
errors: Router.errors,
|
||||
}),
|
||||
});
|
||||
|
@ -2524,13 +2524,12 @@ describe("ASRouter", () => {
|
|||
});
|
||||
|
||||
it("should set blockOnClick property true for dynamic triplet and matching messages more than 3", async () => {
|
||||
sandbox.replaceGetter(
|
||||
ASRouterPreferences,
|
||||
"trailheadTriplet",
|
||||
function() {
|
||||
return "dynamic";
|
||||
}
|
||||
);
|
||||
sandbox.replaceGetter(ASRouterPreferences, "trailhead", function() {
|
||||
return {
|
||||
trailheadInterrupt: "join",
|
||||
trailheadTriplet: "dynamic",
|
||||
};
|
||||
});
|
||||
await Router.onMessage(msg);
|
||||
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
|
||||
const expectedBundle = [
|
||||
|
@ -2560,13 +2559,12 @@ describe("ASRouter", () => {
|
|||
});
|
||||
|
||||
it("should set blockOnClick property true for triplet branch name that starts with 'dynamic' and matching messages more than 3", async () => {
|
||||
sandbox.replaceGetter(
|
||||
ASRouterPreferences,
|
||||
"trailheadTriplet",
|
||||
function() {
|
||||
return "dynamic_test";
|
||||
}
|
||||
);
|
||||
sandbox.replaceGetter(ASRouterPreferences, "trailhead", function() {
|
||||
return {
|
||||
trailheadInterrupt: "join",
|
||||
trailheadTriplet: "dynamic_test",
|
||||
};
|
||||
});
|
||||
await Router.onMessage(msg);
|
||||
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
|
||||
const expectedBundle = [
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
_ASRouterPreferences,
|
||||
ASRouterPreferences as ASRouterPreferencesSingleton,
|
||||
getTrailheadConfigFromPref,
|
||||
TEST_PROVIDERS,
|
||||
} from "lib/ASRouterPreferences.jsm";
|
||||
const FAKE_PROVIDERS = [{ id: "foo" }, { id: "bar" }];
|
||||
|
@ -374,4 +375,26 @@ describe("ASRouterPreferences", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describe("#getTrailheadConfigFromPref", () => {
|
||||
it("should return trailHeadTriplet and trailHeadInterrupt", () => {
|
||||
let result = getTrailheadConfigFromPref("foo-bar");
|
||||
assert.propertyVal(result, "trailheadInterrupt", "foo");
|
||||
assert.propertyVal(result, "trailheadTriplet", "bar");
|
||||
});
|
||||
it("should return default values when pref is empty", () => {
|
||||
let result = getTrailheadConfigFromPref("");
|
||||
assert.propertyVal(result, "trailheadInterrupt", "join");
|
||||
assert.propertyVal(result, "trailheadTriplet", "supercharge");
|
||||
});
|
||||
it("should return default trailHeadTriplet and trailHeadInterrupt when no hyphen", () => {
|
||||
let result = getTrailheadConfigFromPref("control");
|
||||
assert.propertyVal(result, "trailheadInterrupt", "control");
|
||||
assert.propertyVal(result, "trailheadTriplet", "supercharge");
|
||||
});
|
||||
it("should return trailHeadTriplet and default trailHeadInterrupt when prefixed with hyphen", () => {
|
||||
let result = getTrailheadConfigFromPref("-control");
|
||||
assert.propertyVal(result, "trailheadInterrupt", "join");
|
||||
assert.propertyVal(result, "trailheadTriplet", "control");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -82,6 +82,32 @@ describe("#CachedTargetingGetter", () => {
|
|||
|
||||
assert(rejected);
|
||||
});
|
||||
it("should check targeted message before message without targeting", async () => {
|
||||
const messages = await OnboardingMessageProvider.getUntranslatedMessages();
|
||||
const stub = sandbox
|
||||
.stub(ASRouterTargeting, "checkMessageTargeting")
|
||||
.resolves();
|
||||
const context = {
|
||||
attributionData: {
|
||||
campaign: "non-fx-button",
|
||||
source: "addons.mozilla.org",
|
||||
},
|
||||
};
|
||||
await ASRouterTargeting.findMatchingMessage({
|
||||
messages,
|
||||
trigger: { id: "firstRun" },
|
||||
context,
|
||||
});
|
||||
|
||||
const messageCount = messages.filter(
|
||||
message => message.trigger && message.trigger.id === "firstRun"
|
||||
).length;
|
||||
|
||||
assert.equal(stub.callCount, messageCount);
|
||||
const calls = stub.getCalls().map(call => call.args[0]);
|
||||
const lastCall = calls[calls.length - 1];
|
||||
assert.equal(lastCall.id, "TRAILHEAD_1");
|
||||
});
|
||||
describe("sortMessagesByPriority", () => {
|
||||
it("should sort messages in descending priority order", async () => {
|
||||
const [
|
||||
|
|
|
@ -4,7 +4,11 @@ import docs from "content-src/asrouter/docs/targeting-attributes.md";
|
|||
// The following targeting parameters are either deprecated or should not be included in the docs for some reason.
|
||||
const SKIP_DOCS = [];
|
||||
// These are extra message context attributes via ASRouter.jsm
|
||||
const MESSAGE_CONTEXT_ATTRIBUTES = ["previousSessionEnd", "trailheadTriplet"];
|
||||
const MESSAGE_CONTEXT_ATTRIBUTES = [
|
||||
"previousSessionEnd",
|
||||
"trailheadInterrupt",
|
||||
"trailheadTriplet",
|
||||
];
|
||||
|
||||
function getHeadingsFromDocs() {
|
||||
const re = /### `(\w+)`/g;
|
||||
|
|
|
@ -5,8 +5,11 @@ import {
|
|||
import { GlobalOverrider } from "test/unit/utils";
|
||||
import { OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME } from "content-src/lib/init-store";
|
||||
import { FAKE_LOCAL_MESSAGES } from "./constants";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { Trailhead } from "../../../content-src/asrouter/templates/Trailhead/Trailhead";
|
||||
import { Triplets } from "../../../content-src/asrouter/templates/FirstRun/Triplets";
|
||||
import { actionCreators as ac } from "common/Actions.jsm";
|
||||
|
||||
let [FAKE_MESSAGE] = FAKE_LOCAL_MESSAGES;
|
||||
|
@ -174,6 +177,18 @@ describe("ASRouterUISurface", () => {
|
|||
assert.equal(footerPortal.childElementCount, 0);
|
||||
});
|
||||
|
||||
it("should render a trailhead message in the header portal", async () => {
|
||||
// wrapper = shallow(<ASRouterUISurface document={fakeDocument} />);
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.template === "trailhead");
|
||||
|
||||
wrapper.setState({ message });
|
||||
|
||||
assert.isTrue(headerPortal.childElementCount > 0);
|
||||
assert.equal(footerPortal.childElementCount, 0);
|
||||
});
|
||||
|
||||
it("should dispatch an event to select the correct theme", () => {
|
||||
const stub = sandbox.stub(window, "dispatchEvent");
|
||||
sandbox
|
||||
|
@ -222,7 +237,42 @@ describe("ASRouterUISurface", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("Triplet bundle Card", () => {
|
||||
describe("trailhead", () => {
|
||||
it("should render trailhead if a trailhead message is received", async () => {
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.template === "trailhead");
|
||||
wrapper.setState({ message });
|
||||
assert.lengthOf(wrapper.find(Trailhead), 1);
|
||||
});
|
||||
|
||||
it("should render Triplets if a trailhead message with bundle is received", async () => {
|
||||
const FAKE_TRIPLETS_BUNDLE = [
|
||||
{
|
||||
id: "test",
|
||||
content: {
|
||||
title: { string_id: "foo" },
|
||||
text: { string_id: "text1" },
|
||||
icon: "icon",
|
||||
primary_button: {
|
||||
label: { string_id: "button1" },
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: { args: "https://example.com/" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.template === "trailhead");
|
||||
wrapper.setState({
|
||||
message: { ...message, bundle: FAKE_TRIPLETS_BUNDLE },
|
||||
});
|
||||
assert.lengthOf(wrapper.find(Triplets), 1);
|
||||
});
|
||||
|
||||
it("should send NEW_TAB_MESSAGE_REQUEST if a bundle card id is blocked or cleared", async () => {
|
||||
sandbox.stub(ASRouterUtils, "sendMessage");
|
||||
const FAKE_TRIPLETS_BUNDLE_1 = [
|
||||
|
@ -242,8 +292,11 @@ describe("ASRouterUISurface", () => {
|
|||
},
|
||||
},
|
||||
];
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.id === "TRAILHEAD_1");
|
||||
wrapper.setState({
|
||||
message: { bundle: FAKE_TRIPLETS_BUNDLE_1 },
|
||||
message: { ...message, bundle: FAKE_TRIPLETS_BUNDLE_1 },
|
||||
});
|
||||
|
||||
wrapper.instance().clearMessage("CARD_1");
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
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";
|
||||
|
@ -72,7 +73,7 @@ describe("<FirstRun>", () => {
|
|||
async function setup() {
|
||||
sandbox = sinon.createSandbox();
|
||||
clock = sandbox.useFakeTimers();
|
||||
message = await getTestMessage("EXTENDED_TRIPLETS_1");
|
||||
message = await getTestMessage("TRAILHEAD_1");
|
||||
fakeDoc = {
|
||||
body: document.createElement("body"),
|
||||
head: document.createElement("head"),
|
||||
|
@ -110,21 +111,47 @@ describe("<FirstRun>", () => {
|
|||
it("should render", () => {
|
||||
assert.ok(wrapper);
|
||||
});
|
||||
describe("with triplets", () => {
|
||||
it("should render triplets", () => {
|
||||
describe("with both interrupt and triplets", () => {
|
||||
it("should render interrupt and triplets", () => {
|
||||
assert.lengthOf(wrapper.find(Interrupt), 1, "<Interrupt>");
|
||||
assert.lengthOf(wrapper.find(Triplets), 1, "<Triplets>");
|
||||
});
|
||||
it("should show the card panel and the content on the Triplets", () => {
|
||||
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-cards-card (for the extended-triplet message)", () => {
|
||||
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(tProps, "UTMTerm", "trailhead-cards-card");
|
||||
assert.propertyVal(iProps, "UTMTerm", "trailhead-join");
|
||||
assert.propertyVal(tProps, "UTMTerm", "trailhead-join-card");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with no triplets", () => {
|
||||
describe("with an interrupt but no triplets", () => {
|
||||
beforeEach(() => {
|
||||
message.bundle = []; // Empty triplets
|
||||
wrapper = mount(<FirstRun message={message} document={fakeDoc} />);
|
||||
});
|
||||
it("should render interrupt but no triplets", () => {
|
||||
assert.lengthOf(wrapper.find(Interrupt), 1, "<Interrupt>");
|
||||
assert.lengthOf(wrapper.find(Triplets), 0, "<Triplets>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with triplets but no interrupt", () => {
|
||||
it("should render interrupt but no triplets", () => {
|
||||
delete message.content; // Empty interrupt
|
||||
wrapper = mount(<FirstRun message={message} document={fakeDoc} />);
|
||||
|
||||
assert.lengthOf(wrapper.find(Interrupt), 0, "<Interrupt>");
|
||||
assert.lengthOf(wrapper.find(Triplets), 1, "<Triplets>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with no triplets or interrupt", () => {
|
||||
it("should render empty", () => {
|
||||
message = { type: "FOO_123" };
|
||||
wrapper = mount(<FirstRun message={message} document={fakeDoc} />);
|
||||
|
@ -138,6 +165,8 @@ describe("<FirstRun>", () => {
|
|||
wrapper = mount(
|
||||
<FirstRun message={message} document={fakeDoc} executeAction={stub} />
|
||||
);
|
||||
|
||||
assert.propertyVal(wrapper.find(Interrupt).props(), "executeAction", stub);
|
||||
assert.propertyVal(wrapper.find(Triplets).props(), "onAction", stub);
|
||||
});
|
||||
|
||||
|
@ -147,6 +176,7 @@ describe("<FirstRun>", () => {
|
|||
<FirstRun
|
||||
message={message}
|
||||
document={fakeDoc}
|
||||
dispatch={() => {}}
|
||||
fetchFlowParams={stub}
|
||||
fxaEndpoint="https://foo.com"
|
||||
/>
|
||||
|
@ -157,7 +187,12 @@ describe("<FirstRun>", () => {
|
|||
it("should load flow params onUpdate if fxaEndpoint is not defined on mount and then later defined", () => {
|
||||
const stub = sandbox.stub();
|
||||
wrapper = mount(
|
||||
<FirstRun message={message} document={fakeDoc} fetchFlowParams={stub} />
|
||||
<FirstRun
|
||||
message={message}
|
||||
document={fakeDoc}
|
||||
fetchFlowParams={stub}
|
||||
dispatch={() => {}}
|
||||
/>
|
||||
);
|
||||
assert.notCalled(stub);
|
||||
wrapper.setProps({ fxaEndpoint: "https://foo.com" });
|
||||
|
@ -170,6 +205,7 @@ describe("<FirstRun>", () => {
|
|||
<FirstRun
|
||||
message={message}
|
||||
document={fakeDoc}
|
||||
dispatch={() => {}}
|
||||
fetchFlowParams={stub}
|
||||
fxaEndpoint="https://foo.com"
|
||||
/>
|
||||
|
@ -183,6 +219,28 @@ describe("<FirstRun>", () => {
|
|||
assert.lengthOf(fakeDoc.head.querySelectorAll("link"), FLUENT_FILES.length);
|
||||
});
|
||||
|
||||
it("should show triplet content only when interrupt is not visible", () => {
|
||||
assert.lengthOf(wrapper.find(Interrupt), 1, "Interrupt shown");
|
||||
assert.propertyVal(wrapper.find(Triplets).props(), "showContent", false);
|
||||
assert.propertyVal(wrapper.find(Triplets).props(), "showCardPanel", true);
|
||||
|
||||
wrapper.setProps({ interruptCleared: true });
|
||||
|
||||
assert.propertyVal(wrapper.find(Triplets).props(), "showContent", true);
|
||||
assert.propertyVal(wrapper.find(Triplets).props(), "showCardPanel", true);
|
||||
});
|
||||
|
||||
it("should update didUserClearInterrupt state to false on close of interrupt", () => {
|
||||
assert.isFalse(wrapper.state().didUserClearInterrupt);
|
||||
// Simulate calling close interrupt
|
||||
wrapper
|
||||
.find(Interrupt)
|
||||
.find(".trailheadStart")
|
||||
.simulate("click");
|
||||
|
||||
assert.isTrue(wrapper.state().didUserClearInterrupt);
|
||||
});
|
||||
|
||||
it("should update didUserClearTriplets state to true on close of triplet", () => {
|
||||
assert.isFalse(wrapper.state().didUserClearTriplets);
|
||||
// Simulate calling close Triplets
|
||||
|
@ -193,6 +251,30 @@ describe("<FirstRun>", () => {
|
|||
assert.isTrue(wrapper.state().didUserClearTriplets);
|
||||
});
|
||||
|
||||
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 the interrupt when props.interruptCleared changes to true", () => {
|
||||
assert.lengthOf(wrapper.find(Interrupt), 1, "Interrupt shown");
|
||||
wrapper.setProps({ interruptCleared: true });
|
||||
|
||||
assert.lengthOf(wrapper.find(Interrupt), 0, "Interrupt hidden");
|
||||
});
|
||||
|
||||
it("should hide triplets when closeTriplets is called and block extended triplets after 500ms", () => {
|
||||
// Simulate calling next scene
|
||||
wrapper
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
import { mount } from "enzyme";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
import React from "react";
|
||||
import {
|
||||
FullPageInterrupt,
|
||||
FxAccounts,
|
||||
FxCards,
|
||||
} from "content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt";
|
||||
import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
|
||||
import { OnboardingCard } from "content-src/asrouter/templates/OnboardingMessage/OnboardingMessage";
|
||||
|
||||
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-get-started" },
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: { args: "https://example.com/" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const FAKE_FLOW_PARAMS = {
|
||||
deviceId: "foo",
|
||||
flowId: "abc1",
|
||||
flowBeginTime: 1234,
|
||||
};
|
||||
|
||||
describe("<FullPageInterrupt>", () => {
|
||||
let wrapper;
|
||||
let dummyNode;
|
||||
let dispatch;
|
||||
let onBlock;
|
||||
let sandbox;
|
||||
let onAction;
|
||||
let onBlockById;
|
||||
let sendTelemetryStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
dispatch = sandbox.stub();
|
||||
onBlock = sandbox.stub();
|
||||
onAction = sandbox.stub();
|
||||
onBlockById = sandbox.stub();
|
||||
sendTelemetryStub = sandbox.stub();
|
||||
|
||||
dummyNode = document.createElement("body");
|
||||
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
|
||||
const fakeDocument = {
|
||||
getElementById() {
|
||||
return dummyNode;
|
||||
},
|
||||
};
|
||||
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.id === "FULL_PAGE_1");
|
||||
|
||||
wrapper = mount(
|
||||
<FullPageInterrupt
|
||||
message={message}
|
||||
UTMTerm={message.utm_term}
|
||||
fxaEndpoint="https://accounts.firefox.com/endpoint/"
|
||||
dispatch={dispatch}
|
||||
onBlock={onBlock}
|
||||
onAction={onAction}
|
||||
onBlockById={onBlockById}
|
||||
cards={CARDS}
|
||||
document={fakeDocument}
|
||||
sendUserActionTelemetry={sendTelemetryStub}
|
||||
flowParams={FAKE_FLOW_PARAMS}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should trigger onBlock on removeOverlay", () => {
|
||||
wrapper.instance().removeOverlay();
|
||||
assert.calledOnce(onBlock);
|
||||
});
|
||||
|
||||
it("should render Full Page interrupt with accounts and triplet cards section", () => {
|
||||
assert.lengthOf(wrapper.find(FxAccounts), 1);
|
||||
assert.lengthOf(wrapper.find(FxCards), 1);
|
||||
});
|
||||
|
||||
it("should render FxASignupForm inside FxAccounts", () => {
|
||||
assert.lengthOf(wrapper.find(FxASignupForm), 1);
|
||||
});
|
||||
|
||||
it("should display learn more link on full page", () => {
|
||||
assert.ok(wrapper.find("a.fullpage-left-link").exists());
|
||||
});
|
||||
|
||||
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-full_page_d"
|
||||
);
|
||||
assert.calledWith(sendTelemetryStub, {
|
||||
event: "CLICK_BUTTON",
|
||||
message_id: CARDS[0].id,
|
||||
id: "TRAILHEAD",
|
||||
});
|
||||
});
|
||||
it("should not call blockById by default when a card button is clicked", () => {
|
||||
wrapper
|
||||
.find(OnboardingCard)
|
||||
.find("button.onboardingButton")
|
||||
.simulate("click");
|
||||
assert.notCalled(onBlockById);
|
||||
});
|
||||
it("should call blockById when blockOnClick on message is true", () => {
|
||||
CARDS[0].blockOnClick = true;
|
||||
wrapper
|
||||
.find(OnboardingCard)
|
||||
.find("button.onboardingButton")
|
||||
.simulate("click");
|
||||
assert.calledOnce(onBlockById);
|
||||
assert.calledWith(onBlockById, CARDS[0].id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
|
||||
import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("<FxASignupForm>", () => {
|
||||
let wrapper;
|
||||
let dummyNode;
|
||||
let dispatch;
|
||||
let onClose;
|
||||
let sandbox;
|
||||
|
||||
const FAKE_FLOW_PARAMS = {
|
||||
deviceId: "foo",
|
||||
flowId: "abc1",
|
||||
flowBeginTime: 1234,
|
||||
};
|
||||
|
||||
const FAKE_MESSAGE_CONTENT = {
|
||||
title: { string_id: "onboarding-welcome-body" },
|
||||
learn: {
|
||||
text: { string_id: "onboarding-welcome-learn-more" },
|
||||
url: "https://www.mozilla.org/firefox/accounts/",
|
||||
},
|
||||
form: {
|
||||
title: { string_id: "onboarding-welcome-form-header" },
|
||||
text: { string_id: "onboarding-join-form-body" },
|
||||
email: { string_id: "onboarding-fullpage-form-email" },
|
||||
button: { string_id: "onboarding-join-form-continue" },
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
dispatch = sandbox.stub();
|
||||
onClose = sandbox.stub();
|
||||
|
||||
dummyNode = document.createElement("body");
|
||||
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
|
||||
const fakeDocument = {
|
||||
getElementById() {
|
||||
return dummyNode;
|
||||
},
|
||||
};
|
||||
|
||||
wrapper = mount(
|
||||
<FxASignupForm
|
||||
document={fakeDocument}
|
||||
content={FAKE_MESSAGE_CONTENT}
|
||||
dispatch={dispatch}
|
||||
fxaEndpoint="https://accounts.firefox.com/endpoint/"
|
||||
UTMTerm="test-utm-term"
|
||||
flowParams={FAKE_FLOW_PARAMS}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should prevent submissions with no email", () => {
|
||||
const form = wrapper.find("form");
|
||||
const preventDefault = sandbox.stub();
|
||||
|
||||
form.simulate("submit", { preventDefault });
|
||||
|
||||
assert.calledOnce(preventDefault);
|
||||
assert.notCalled(dispatch);
|
||||
});
|
||||
|
||||
it("should not display signin link by default", () => {
|
||||
assert.notOk(
|
||||
wrapper
|
||||
.find("button[data-l10n-id='onboarding-join-form-signin']")
|
||||
.exists()
|
||||
);
|
||||
});
|
||||
|
||||
it("should display signin when showSignInLink is true", () => {
|
||||
wrapper.setProps({ showSignInLink: true });
|
||||
let signIn = wrapper.find(
|
||||
"button[data-l10n-id='onboarding-join-form-signin']"
|
||||
);
|
||||
assert.exists(signIn);
|
||||
});
|
||||
|
||||
it("should emit UserEvent SUBMIT_EMAIL when you submit a valid email", () => {
|
||||
let form = wrapper.find("form");
|
||||
assert.ok(form.exists());
|
||||
form.getDOMNode().elements.email.value = "a@b.c";
|
||||
|
||||
form.simulate("submit");
|
||||
assert.calledOnce(dispatch);
|
||||
assert.isUserEventAction(dispatch.firstCall.args[0]);
|
||||
assert.calledWith(
|
||||
dispatch,
|
||||
ac.UserEvent({
|
||||
event: at.SUBMIT_EMAIL,
|
||||
value: { has_flow_params: true },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should emit UserEvent SUBMIT_SIGNIN when submit with email disabled", () => {
|
||||
let form = wrapper.find("form");
|
||||
form.getDOMNode().elements.email.disabled = true;
|
||||
|
||||
form.simulate("submit");
|
||||
assert.calledOnce(dispatch);
|
||||
assert.isUserEventAction(dispatch.firstCall.args[0]);
|
||||
assert.calledWith(
|
||||
dispatch,
|
||||
ac.UserEvent({
|
||||
event: at.SUBMIT_SIGNIN,
|
||||
value: { has_flow_params: true },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
import { FullPageInterrupt } from "content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt";
|
||||
import { Interrupt } from "content-src/asrouter/templates/FirstRun/Interrupt";
|
||||
import { ReturnToAMO } from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
|
||||
import { Trailhead } from "content-src/asrouter/templates//Trailhead/Trailhead";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("<Interrupt>", () => {
|
||||
let wrapper;
|
||||
it("should render Return TO AMO when the message has a template of return_to_amo_overlay", () => {
|
||||
wrapper = shallow(
|
||||
<Interrupt
|
||||
message={{ id: "FOO", content: {}, template: "return_to_amo_overlay" }}
|
||||
/>
|
||||
);
|
||||
assert.lengthOf(wrapper.find(ReturnToAMO), 1);
|
||||
});
|
||||
it("should render Trailhead when the message has a template of trailhead", () => {
|
||||
wrapper = shallow(
|
||||
<Interrupt message={{ id: "FOO", content: {}, template: "trailhead" }} />
|
||||
);
|
||||
assert.lengthOf(wrapper.find(Trailhead), 1);
|
||||
});
|
||||
it("should render Full Page interrupt when the message has a template of full_page_interrupt", () => {
|
||||
wrapper = shallow(
|
||||
<Interrupt
|
||||
message={{ id: "FOO", content: {}, template: "full_page_interrupt" }}
|
||||
/>
|
||||
);
|
||||
assert.lengthOf(wrapper.find(FullPageInterrupt), 1);
|
||||
});
|
||||
it("should throw an error if another type of message is dispatched", () => {
|
||||
assert.throws(() => {
|
||||
wrapper = shallow(
|
||||
<Interrupt message={{ id: "FOO", template: "something" }} />
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -77,4 +77,220 @@ describe("OnboardingMessage", () => {
|
|||
.filter(msg => msg.template === "whatsnew_panel_message")
|
||||
.forEach(msg => assert.jsonSchema(msg.content, whatsNewSchema));
|
||||
});
|
||||
it("should decode the content field (double decoding)", async () => {
|
||||
const fakeContent = "foo%2540bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const [
|
||||
translatedMessage,
|
||||
] = await OnboardingMessageProvider.translateMessages(msgs);
|
||||
assert.propertyVal(
|
||||
translatedMessage.content.text.args,
|
||||
"addon-name",
|
||||
"foo@bar.org"
|
||||
);
|
||||
});
|
||||
it("should catch any decoding exceptions", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const [
|
||||
translatedMessage,
|
||||
] = await OnboardingMessageProvider.translateMessages(msgs);
|
||||
assert.propertyVal(
|
||||
translatedMessage.content.text.args,
|
||||
"addon-name",
|
||||
fakeContent
|
||||
);
|
||||
});
|
||||
it("should ignore attribution from sources other than mozilla.org", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.allizom.org" }),
|
||||
});
|
||||
|
||||
const [returnToAMOMsg] = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
assert.propertyVal(returnToAMOMsg.content.text.args, "addon-name", null);
|
||||
});
|
||||
it("should correctly add all addon information to the message after translation", async () => {
|
||||
const fakeContent = "foo%2540bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const [
|
||||
translatedMessage,
|
||||
] = await OnboardingMessageProvider.translateMessages(msgs);
|
||||
assert.propertyVal(
|
||||
translatedMessage.content.text.args,
|
||||
"addon-name",
|
||||
"foo@bar.org"
|
||||
);
|
||||
assert.propertyVal(translatedMessage.content, "addon_icon", "icon");
|
||||
assert.propertyVal(
|
||||
translatedMessage.content.primary_button.action.data,
|
||||
"url",
|
||||
"foo"
|
||||
);
|
||||
assert.propertyVal(
|
||||
translatedMessage.content.primary_button.action.data,
|
||||
"telemetrySource",
|
||||
"rtamo"
|
||||
);
|
||||
});
|
||||
it("should skip return_to_amo_overlay if any addon fields are missing", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
globals.set("AddonRepository", {
|
||||
getAddonsByIDs: ([content]) => [
|
||||
{
|
||||
name: content,
|
||||
sourceURI: { spec: "foo", scheme: "https" },
|
||||
icons: { 64: null },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
assert.lengthOf(translatedMessages, 0);
|
||||
});
|
||||
it("should skip return_to_amo_overlay if any addon fields are missing", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
globals.set("AddonRepository", {
|
||||
getAddonsByIDs: ([content]) => [
|
||||
{
|
||||
name: content,
|
||||
sourceURI: { spec: null, scheme: "https" },
|
||||
icons: { 64: "icon" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
assert.lengthOf(translatedMessages, 0);
|
||||
});
|
||||
it("should skip return_to_amo_overlay if any addon fields are missing", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
globals.set("AddonRepository", {
|
||||
getAddonsByIDs: ([content]) => [
|
||||
{
|
||||
name: null,
|
||||
sourceURI: { spec: "foo", scheme: "https" },
|
||||
icons: { 64: "icon" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
assert.lengthOf(translatedMessages, 0);
|
||||
});
|
||||
it("should skip return_to_amo_overlay if addon scheme is not https", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox
|
||||
.stub()
|
||||
.resolves({ content: fakeContent, source: "addons.mozilla.org" }),
|
||||
});
|
||||
globals.set("AddonRepository", {
|
||||
getAddonsByIDs: ([content]) => [
|
||||
{
|
||||
name: content,
|
||||
sourceURI: { spec: "foo", scheme: "http" },
|
||||
icons: { 64: "icon" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
assert.lengthOf(translatedMessages, 0);
|
||||
});
|
||||
it("should skip return_to_amo_overlay if getAddonInfo fails", async () => {
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox.stub().rejects(),
|
||||
});
|
||||
|
||||
const msgs = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).filter(({ id }) => id === "RETURN_TO_AMO_1");
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
assert.lengthOf(translatedMessages, 0);
|
||||
});
|
||||
it("should catch any exceptions fetching the addon information", async () => {
|
||||
const fakeContent = "foo%bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
getAttrDataAsync: sandbox.stub().resolves({ content: fakeContent }),
|
||||
});
|
||||
globals.set("AddonRepository", {
|
||||
getAddonsByIDs: sandbox.stub().rejects(),
|
||||
});
|
||||
|
||||
const msgs = await OnboardingMessageProvider.getUntranslatedMessages();
|
||||
const translatedMessages = await OnboardingMessageProvider.translateMessages(
|
||||
msgs
|
||||
);
|
||||
const returnToAMOMsgs = translatedMessages.filter(
|
||||
({ id }) => id === "RETURN_TO_AMO_1"
|
||||
);
|
||||
assert.lengthOf(translatedMessages, msgs.length - 1);
|
||||
assert.lengthOf(returnToAMOMsgs, 0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
|
||||
import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
|
||||
import { mount } from "enzyme";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
import React from "react";
|
||||
import { Trailhead } from "content-src/asrouter/templates/Trailhead/Trailhead";
|
||||
|
||||
export const CARDS = [
|
||||
{
|
||||
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-get-started" },
|
||||
action: {
|
||||
type: "OPEN_URL",
|
||||
data: { args: "https://example.com/" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe("<Trailhead>", () => {
|
||||
let wrapper;
|
||||
let dummyNode;
|
||||
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,
|
||||
json: () => Promise.resolve({ flowId: 123, flowBeginTime: 456 }),
|
||||
});
|
||||
|
||||
dummyNode = document.createElement("body");
|
||||
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
|
||||
const fakeDocument = {
|
||||
get activeElement() {
|
||||
return dummyNode;
|
||||
},
|
||||
get body() {
|
||||
return dummyNode;
|
||||
},
|
||||
getElementById() {
|
||||
return dummyNode;
|
||||
},
|
||||
};
|
||||
|
||||
const message = (
|
||||
await OnboardingMessageProvider.getUntranslatedMessages()
|
||||
).find(msg => msg.id === "TRAILHEAD_1");
|
||||
message.cards = CARDS;
|
||||
wrapper = mount(
|
||||
<Trailhead
|
||||
message={message}
|
||||
UTMTerm={message.utm_term}
|
||||
fxaEndpoint="https://accounts.firefox.com/endpoint"
|
||||
dispatch={dispatch}
|
||||
onAction={onAction}
|
||||
document={fakeDocument}
|
||||
onNextScene={onNextScene}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should render FxASignupForm with signup email", () => {
|
||||
assert.lengthOf(wrapper.find(FxASignupForm), 1);
|
||||
});
|
||||
|
||||
it("should emit UserEvent SKIPPED_SIGNIN and call nextScene 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(
|
||||
dispatch,
|
||||
ac.UserEvent({
|
||||
event: at.SKIPPED_SIGNIN,
|
||||
value: { has_flow_params: false },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should NOT emit UserEvent SKIPPED_SIGNIN when closeModal is triggered by visibilitychange event", () => {
|
||||
wrapper.instance().closeModal({ type: "visibilitychange" });
|
||||
assert.notCalled(dispatch);
|
||||
});
|
||||
|
||||
it("should prevent submissions with no email", () => {
|
||||
const form = wrapper.find("form");
|
||||
const preventDefault = sandbox.stub();
|
||||
|
||||
form.simulate("submit", { preventDefault });
|
||||
|
||||
assert.calledOnce(preventDefault);
|
||||
assert.notCalled(dispatch);
|
||||
});
|
||||
it("should emit UserEvent SUBMIT_EMAIL when you submit a valid email", () => {
|
||||
let form = wrapper.find("form");
|
||||
assert.ok(form.exists());
|
||||
form.getDOMNode().elements.email.value = "a@b.c";
|
||||
|
||||
form.simulate("submit");
|
||||
|
||||
assert.calledOnce(dispatch);
|
||||
assert.isUserEventAction(dispatch.firstCall.args[0]);
|
||||
assert.calledWith(
|
||||
dispatch,
|
||||
ac.UserEvent({
|
||||
event: at.SUBMIT_EMAIL,
|
||||
value: { has_flow_params: false },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should keep focus in dialog when blurring start button", () => {
|
||||
const skipButton = wrapper.find(".trailheadStart");
|
||||
sandbox.stub(dummyNode, "focus");
|
||||
|
||||
skipButton.simulate("blur", { relatedTarget: dummyNode });
|
||||
|
||||
assert.calledOnce(dummyNode.focus);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import { ReturnToAMO } from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
|
||||
|
||||
describe("<ReturnToAMO>", () => {
|
||||
let dispatch;
|
||||
let onReady;
|
||||
let sandbox;
|
||||
let wrapper;
|
||||
let dummyNode;
|
||||
let fakeDocument;
|
||||
let sendUserActionTelemetryStub;
|
||||
let content;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
dispatch = sandbox.stub();
|
||||
onReady = sandbox.stub();
|
||||
sendUserActionTelemetryStub = sandbox.stub();
|
||||
content = {
|
||||
primary_button: {},
|
||||
secondary_button: {},
|
||||
};
|
||||
dummyNode = document.createElement("body");
|
||||
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
|
||||
fakeDocument = {
|
||||
get activeElement() {
|
||||
return dummyNode;
|
||||
},
|
||||
get body() {
|
||||
return dummyNode;
|
||||
},
|
||||
getElementById() {
|
||||
return dummyNode;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe("not mounted", () => {
|
||||
it("should send an IMPRESSION on mount", () => {
|
||||
assert.notCalled(sendUserActionTelemetryStub);
|
||||
|
||||
wrapper = mount(
|
||||
<ReturnToAMO
|
||||
document={fakeDocument}
|
||||
onReady={onReady}
|
||||
dispatch={dispatch}
|
||||
content={content}
|
||||
onBlock={sandbox.stub()}
|
||||
onAction={sandbox.stub()}
|
||||
UISurface="NEWTAB_OVERLAY"
|
||||
sendUserActionTelemetry={sendUserActionTelemetryStub}
|
||||
/>
|
||||
);
|
||||
|
||||
assert.calledOnce(sendUserActionTelemetryStub);
|
||||
assert.calledWithExactly(sendUserActionTelemetryStub, {
|
||||
event: "IMPRESSION",
|
||||
id: wrapper.instance().props.UISurface,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("mounted", () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<ReturnToAMO
|
||||
document={fakeDocument}
|
||||
onReady={onReady}
|
||||
dispatch={dispatch}
|
||||
content={content}
|
||||
onBlock={sandbox.stub()}
|
||||
onAction={sandbox.stub()}
|
||||
UISurface="NEWTAB_OVERLAY"
|
||||
sendUserActionTelemetry={sendUserActionTelemetryStub}
|
||||
/>
|
||||
);
|
||||
|
||||
// Clear the IMPRESSION ping
|
||||
sendUserActionTelemetryStub.reset();
|
||||
});
|
||||
|
||||
it("should send telemetry on block", () => {
|
||||
wrapper.instance().onBlockButton();
|
||||
|
||||
assert.calledOnce(sendUserActionTelemetryStub);
|
||||
assert.calledWithExactly(sendUserActionTelemetryStub, {
|
||||
event: "BLOCK",
|
||||
id: wrapper.instance().props.UISurface,
|
||||
});
|
||||
});
|
||||
|
||||
it("should send telemetry on install", () => {
|
||||
wrapper.instance().onClickAddExtension();
|
||||
|
||||
assert.calledWithExactly(sendUserActionTelemetryStub, {
|
||||
event: "INSTALL",
|
||||
id: wrapper.instance().props.UISurface,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -45,6 +45,7 @@ describe("TelemetryFeed", () => {
|
|||
class UTEventReporting {
|
||||
sendUserEvent() {}
|
||||
sendSessionEndEvent() {}
|
||||
sendTrailheadEnrollEvent() {}
|
||||
uninit() {}
|
||||
}
|
||||
|
||||
|
@ -1544,6 +1545,15 @@ describe("TelemetryFeed", () => {
|
|||
assert.calledWith(eventCreator, action.data);
|
||||
assert.calledWith(sendEvent, eventCreator.returnValue);
|
||||
});
|
||||
it("should call .handleTrailheadEnrollEvent on a TRAILHEAD_ENROLL_EVENT action", () => {
|
||||
const data = { experiment: "foo", type: "bar", branch: "baz" };
|
||||
const action = { type: at.TRAILHEAD_ENROLL_EVENT, data };
|
||||
sandbox.spy(instance, "handleTrailheadEnrollEvent");
|
||||
|
||||
instance.onAction(action);
|
||||
|
||||
assert.calledWith(instance.handleTrailheadEnrollEvent, action);
|
||||
});
|
||||
});
|
||||
describe("#handleNewTabInit", () => {
|
||||
it("should set the session as preloaded if the browser is preloaded", () => {
|
||||
|
@ -1828,6 +1838,28 @@ describe("TelemetryFeed", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describe("#handleTrailheadEnrollEvent", () => {
|
||||
it("should send a TRAILHEAD_ENROLL_EVENT if the telemetry is enabled", () => {
|
||||
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
|
||||
const data = { experiment: "foo", type: "bar", branch: "baz" };
|
||||
instance = new TelemetryFeed();
|
||||
sandbox.stub(instance.utEvents, "sendTrailheadEnrollEvent");
|
||||
|
||||
instance.handleTrailheadEnrollEvent({ data });
|
||||
|
||||
assert.calledWith(instance.utEvents.sendTrailheadEnrollEvent, data);
|
||||
});
|
||||
it("should not send TRAILHEAD_ENROLL_EVENT if the telemetry is disabled", () => {
|
||||
FakePrefs.prototype.prefs[TELEMETRY_PREF] = false;
|
||||
const data = { experiment: "foo", type: "bar", branch: "baz" };
|
||||
instance = new TelemetryFeed();
|
||||
sandbox.stub(instance.utEvents, "sendTrailheadEnrollEvent");
|
||||
|
||||
instance.handleTrailheadEnrollEvent({ data });
|
||||
|
||||
assert.notCalled(instance.utEvents.sendTrailheadEnrollEvent);
|
||||
});
|
||||
});
|
||||
describe("#handleASRouterUserEvent", () => {
|
||||
it("should call sendStructuredIngestionEvent on known pingTypes", async () => {
|
||||
const data = {
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { UTSessionPing, UTUserEventPing } from "test/schemas/pings";
|
||||
import {
|
||||
UTSessionPing,
|
||||
UTTrailheadEnrollPing,
|
||||
UTUserEventPing,
|
||||
} from "test/schemas/pings";
|
||||
import { GlobalOverrider } from "test/unit/utils";
|
||||
import { UTEventReporting } from "lib/UTEventReporting.jsm";
|
||||
|
||||
|
@ -45,6 +49,21 @@ const FAKE_SESSION_PING_UT = [
|
|||
page: "about:newtab",
|
||||
},
|
||||
];
|
||||
const FAKE_TRAILHEAD_ENROLL_EVENT = {
|
||||
experiment: "activity-stream-trailhead-firstrun-interrupts",
|
||||
type: "as-firstrun",
|
||||
branch: "supercharge",
|
||||
};
|
||||
const FAKE_TRAILHEAD_ENROLL_EVENT_UT = [
|
||||
"activity_stream",
|
||||
"enroll",
|
||||
"preference_study",
|
||||
"activity-stream-trailhead-firstrun-interrupts",
|
||||
{
|
||||
experimentType: "as-firstrun",
|
||||
branch: "supercharge",
|
||||
},
|
||||
];
|
||||
|
||||
describe("UTEventReporting", () => {
|
||||
let globals;
|
||||
|
@ -90,6 +109,19 @@ describe("UTEventReporting", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#sendTrailheadEnrollEvent()", () => {
|
||||
it("should queue up the correct data to send to Events Telemetry", async () => {
|
||||
utEvents.sendTrailheadEnrollEvent(FAKE_TRAILHEAD_ENROLL_EVENT);
|
||||
assert.calledWithExactly(
|
||||
global.Services.telemetry.recordEvent,
|
||||
...FAKE_TRAILHEAD_ENROLL_EVENT_UT
|
||||
);
|
||||
|
||||
let ping = global.Services.telemetry.recordEvent.firstCall.args;
|
||||
assert.validate(ping, UTTrailheadEnrollPing);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#uninit()", () => {
|
||||
it("should call setEventRecordingEnabled with a false value", () => {
|
||||
assert.equal(
|
||||
|
|
|
@ -288,7 +288,7 @@ addTestsWithPrivilegedContentProcessPref(async function test_default_url() {
|
|||
});
|
||||
|
||||
addTestsWithPrivilegedContentProcessPref(async function test_welcome_url() {
|
||||
// Disable about:welcome to load newtab
|
||||
// Set about:welcome to use trailhead flow
|
||||
Services.prefs.setBoolPref(SIMPLIFIED_WELCOME_ENABLED_PREF, false);
|
||||
Assert.equal(
|
||||
aboutNewTabService.welcomeURL,
|
||||
|
|
|
@ -2,13 +2,40 @@
|
|||
# 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/.
|
||||
|
||||
### UI strings for the simplified onboarding / multistage about:welcome
|
||||
### UI strings for the simplified onboarding modal / about:welcome
|
||||
### Various strings use a non-breaking space to avoid a single dangling /
|
||||
### widowed word, so test on various window sizes if you also want this.
|
||||
|
||||
## Welcome page strings
|
||||
## These button action text can be split onto multiple lines, so use explicit
|
||||
## newlines in translations to control where the line break appears (e.g., to
|
||||
## avoid breaking quoted text).
|
||||
|
||||
onboarding-button-label-learn-more = Learn More
|
||||
onboarding-button-label-get-started = Get Started
|
||||
|
||||
## Welcome modal dialog strings
|
||||
|
||||
onboarding-welcome-header = Welcome to { -brand-short-name }
|
||||
onboarding-welcome-body = You’ve got the browser.<br/>Meet the rest of { -brand-product-name }.
|
||||
onboarding-welcome-learn-more = Learn more about the benefits.
|
||||
onboarding-welcome-modal-get-body = You’ve got the browser.<br/>Now get the most out of { -brand-product-name }.
|
||||
onboarding-welcome-modal-supercharge-body = Supercharge your privacy protection.
|
||||
onboarding-welcome-modal-privacy-body = You’ve got the browser. Let’s add more privacy protection.
|
||||
onboarding-welcome-modal-family-learn-more = Learn about the { -brand-product-name } family of products.
|
||||
onboarding-welcome-form-header = Start Here
|
||||
|
||||
onboarding-join-form-body = Enter your email address to get started.
|
||||
onboarding-join-form-email =
|
||||
.placeholder = Enter email
|
||||
onboarding-join-form-email-error = Valid email required
|
||||
onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
|
||||
onboarding-join-form-continue = Continue
|
||||
|
||||
# This message is followed by a link using onboarding-join-form-signin ("Sign In") as text.
|
||||
onboarding-join-form-signin-label = Already have an account?
|
||||
# Text for link to submit the sign in form
|
||||
onboarding-join-form-signin = Sign In
|
||||
|
||||
onboarding-start-browsing-button-label = Start Browsing
|
||||
onboarding-cards-dismiss =
|
||||
.title = Dismiss
|
||||
|
@ -110,6 +137,52 @@ onboarding-multistage-theme-description-alpenglow =
|
|||
Use a colorful appearance for buttons,
|
||||
menus, and windows.
|
||||
|
||||
## Welcome full page string
|
||||
|
||||
onboarding-fullpage-welcome-subheader = Let’s start exploring everything you can do.
|
||||
onboarding-fullpage-form-email =
|
||||
.placeholder = Your email address…
|
||||
|
||||
## Firefox Sync modal dialog strings.
|
||||
|
||||
onboarding-sync-welcome-header = Take { -brand-product-name } with You
|
||||
onboarding-sync-welcome-content = Get your bookmarks, history, passwords and other settings on all your devices.
|
||||
onboarding-sync-welcome-learn-more-link = Learn more about Firefox Accounts
|
||||
|
||||
onboarding-sync-form-input =
|
||||
.placeholder = Email
|
||||
|
||||
onboarding-sync-form-continue-button = Continue
|
||||
onboarding-sync-form-skip-login-button = Skip this step
|
||||
|
||||
## This is part of the line "Enter your email to continue to Firefox Sync"
|
||||
|
||||
onboarding-sync-form-header = Enter your email
|
||||
onboarding-sync-form-sub-header = to continue to { -sync-brand-name }
|
||||
|
||||
|
||||
## These are individual benefit messages shown with an image, title and
|
||||
## description.
|
||||
|
||||
onboarding-benefit-products-text = Get things done with a family of tools that respects your privacy across your devices.
|
||||
|
||||
# "Personal Data Promise" is a concept that should be translated consistently
|
||||
# across the product. It refers to a concept shown elsewhere to the user: "The
|
||||
# Firefox Personal Data Promise is the way we honor your data in everything we
|
||||
# make and do. We take less data. We keep it safe. And we make sure that we are
|
||||
# transparent about how we use it."
|
||||
onboarding-benefit-privacy-text = Everything we do honors our Personal Data Promise: Take less. Keep it safe. No secrets.
|
||||
|
||||
onboarding-benefit-sync-title = { -sync-brand-short-name }
|
||||
onboarding-benefit-sync-text = Take your bookmarks, passwords, history, and more everywhere you use { -brand-product-name }.
|
||||
|
||||
onboarding-benefit-monitor-title = { -monitor-brand-short-name }
|
||||
onboarding-benefit-monitor-text = Get notified when your personal info is in a known data breach.
|
||||
|
||||
onboarding-benefit-lockwise-title = { -lockwise-brand-short-name }
|
||||
onboarding-benefit-lockwise-text = Manage passwords that are protected and portable.
|
||||
|
||||
|
||||
## These strings belong to the individual onboarding messages.
|
||||
|
||||
## Each message has a title and a description of what the browser feature is.
|
||||
|
@ -168,3 +241,15 @@ onboarding-import-browser-settings-button = Import Chrome Data
|
|||
onboarding-personal-data-promise-title = Private by Design
|
||||
onboarding-personal-data-promise-text = { -brand-product-name } treats your data with respect by taking less of it, protecting it, and being clear about how we use it.
|
||||
onboarding-personal-data-promise-button = Read our Promise
|
||||
|
||||
## Message strings belonging to the Return to AMO flow
|
||||
|
||||
return-to-amo-sub-header = Great, you’ve got { -brand-short-name }
|
||||
|
||||
# <icon></icon> will be replaced with the icon belonging to the extension
|
||||
#
|
||||
# Variables:
|
||||
# $addon-name (String) - Name of the add-on
|
||||
return-to-amo-addon-header = Now let’s get you <icon></icon><b>{ $addon-name }.</b>
|
||||
return-to-amo-extension-button = Add the Extension
|
||||
return-to-amo-get-started-button = Get Started with { -brand-short-name }
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
"network.proxy.type": 1,
|
||||
"plugin.disable": true,
|
||||
"startup.homepage_override_url": "",
|
||||
"startup.homepage_welcome_url": ""
|
||||
"startup.homepage_welcome_url": "",
|
||||
"trailhead.firstrun.branches": "join-none"
|
||||
}
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
"network.proxy.type": 1,
|
||||
"plugin.disable": true,
|
||||
"startup.homepage_override_url": "",
|
||||
"startup.homepage_welcome_url": ""
|
||||
"startup.homepage_welcome_url": "",
|
||||
"trailhead.firstrun.branches": "join-none"
|
||||
}
|
||||
|
|
|
@ -85,3 +85,4 @@ user_pref("toolkit.telemetry.server", "https://127.0.0.1/telemetry-dummy/");
|
|||
user_pref("telemetry.fog.test.localhost_port", -1);
|
||||
user_pref("startup.homepage_welcome_url", "");
|
||||
user_pref("startup.homepage_welcome_url.additional", "");
|
||||
user_pref("trailhead.firstrun.branches", "join");
|
||||
|
|
|
@ -103,6 +103,25 @@ activity_stream:
|
|||
session_id: The ID of the Activity Stream session in which the event occurred
|
||||
page: about:home or about_newtab - the page where the event occurred
|
||||
user_prefs: An integer representaing a user's A-S settings.
|
||||
enroll:
|
||||
objects: ["preference_study"]
|
||||
release_channel_collection: opt-out
|
||||
description: >
|
||||
Sent when a user gets enrolled in a preference study.
|
||||
bug_numbers: [1549784]
|
||||
notification_emails:
|
||||
- "najiang@mozilla.com"
|
||||
- "edilee@mozilla.com"
|
||||
products:
|
||||
- "firefox"
|
||||
- "fennec"
|
||||
record_in_processes: ["main"]
|
||||
expiry_version: never
|
||||
extra_keys:
|
||||
experimentType: >
|
||||
For preference_study recipes, the type of experiment this is ("exp" or "exp-highpop").
|
||||
branch: >
|
||||
For preference_study recipes, the slug of the branch that was chosen for this client.
|
||||
|
||||
addonsManager:
|
||||
install:
|
||||
|
|