зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1588215 - Add modal-less welcome, send-tab recipes and bug fixes to New Tab Page r=Mardak,fluent-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D49020 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
e54b007827
Коммит
d16004d6cb
|
@ -45,6 +45,8 @@ module.exports = {
|
|||
"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",
|
||||
|
|
|
@ -123,6 +123,7 @@ for (const type of [
|
|||
"SNIPPETS_RESET",
|
||||
"SNIPPET_BLOCKED",
|
||||
"SUBMIT_EMAIL",
|
||||
"SUBMIT_SIGNIN",
|
||||
"SYSTEM_TICK",
|
||||
"TELEMETRY_IMPRESSION_STATS",
|
||||
"TELEMETRY_PERFORMANCE_EVENT",
|
||||
|
@ -154,6 +155,7 @@ for (const type of [
|
|||
// as call-to-action buttons in snippets, onboarding tour, etc.
|
||||
const ASRouterActions = {};
|
||||
for (const type of [
|
||||
"HIGHLIGHT_FEATURE",
|
||||
"INSTALL_ADDON_FROM_URL",
|
||||
"OPEN_APPLICATIONS_MENU",
|
||||
"OPEN_PRIVATE_BROWSER_WINDOW",
|
||||
|
|
|
@ -21,6 +21,7 @@ const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
|
|||
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
|
||||
const TEMPLATES_ABOVE_PAGE = [
|
||||
"trailhead",
|
||||
"full_page_interrupt",
|
||||
"return_to_amo_overlay",
|
||||
"extended_triplets",
|
||||
];
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/* 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 } 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="service" type="hidden" value="sync" />
|
||||
<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="firstrun" />
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,9 +29,9 @@ To test telemetry pings, complete the the following steps:
|
|||
- In about:config, set:
|
||||
- `browser.newtabpage.activity-stream.telemetry` to `true`
|
||||
- `browser.ping-centre.log` to `true`
|
||||
- Open the Browser Toolbox devtools (Tools > Developer > Browser Toolbox) and switch to the console tab. Add a filter for for `activity-stream-router` to only display relevant pings:
|
||||
- Open the Browser Toolbox devtools (Tools > Web Developer > Browser Toolbox) and switch to the console tab. Add a filter for for `activity-stream` to only display relevant pings:
|
||||
|
||||
![Devtools telemetry pong](./telemetry-screenshot.png)
|
||||
![Devtools telemetry ping](./telemetry-screenshot.png)
|
||||
|
||||
You should now see pings show up as you view/interact with ASR messages/templates.
|
||||
|
||||
|
|
|
@ -40,6 +40,8 @@ Please note that some targeting attributes require stricter controls on the tele
|
|||
* [totalBlockedCount](#totalblockedcount)
|
||||
* [recentBookmarks](#recentbookmarks)
|
||||
* [userPrefs](#userprefs)
|
||||
* [attachedFxAOAuthClients](#attachedfxaoauthclients)
|
||||
* [platformName](#platformname)
|
||||
|
||||
## Detailed usage
|
||||
|
||||
|
@ -555,3 +557,39 @@ declare const userPrefs: {
|
|||
snippets: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `attachedFxAOAuthClients`
|
||||
|
||||
Information about connected services associated with the FxA Account.
|
||||
|
||||
#### Definition
|
||||
|
||||
```
|
||||
interface OAuthClient {
|
||||
id: string;
|
||||
// FxA service name
|
||||
name: string;
|
||||
lastAccessTime: UnixEpochNumber;
|
||||
}
|
||||
|
||||
declare const attachedFxAOAuthClients: Array<OAuthClient>
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```javascript
|
||||
{
|
||||
id: "7377719276ad44ee",
|
||||
name: "Pocket",
|
||||
lastAccessTime: 1513599164000
|
||||
}
|
||||
```
|
||||
|
||||
### `platformName`
|
||||
|
||||
[Platform information](https://searchfox.org/mozilla-central/rev/05a22d864814cb1e4352faa4004e1f975c7d2eb9/toolkit/modules/AppConstants.jsm#156).
|
||||
|
||||
#### Definition
|
||||
|
||||
```
|
||||
declare const platformName = "linux" | "win" | "macosx" | "android" | "other";
|
||||
```
|
||||
|
|
|
@ -180,6 +180,7 @@ export class FirstRun extends React.PureComponent {
|
|||
{isInterruptVisible ? (
|
||||
<Interrupt
|
||||
document={props.document}
|
||||
cards={triplets}
|
||||
message={interrupt}
|
||||
onNextScene={this.closeInterrupt}
|
||||
UTMTerm={UTMTerm}
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
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,
|
||||
|
@ -38,6 +40,21 @@ export class Interrupt extends React.PureComponent {
|
|||
/>
|
||||
</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}
|
||||
/>
|
||||
);
|
||||
case "trailhead":
|
||||
return (
|
||||
<Trailhead
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/* 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}
|
||||
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) {
|
||||
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 });
|
||||
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,259 @@
|
|||
.activity-stream {
|
||||
&.welcome {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:not(.welcome) {
|
||||
.fullpage-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fullpage-wrapper {
|
||||
$responsive-breakpoint: 850px;
|
||||
$responsive-width: 352px;
|
||||
$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;
|
||||
background-size: 154px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $responsive-breakpoint) {
|
||||
width: $responsive-width;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"title": "ToolbarBadgeMessage",
|
||||
"description": "A template that specifies to which element in the browser toolbar to add a notification.",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
|
@ -21,6 +21,17 @@
|
|||
"delay": {
|
||||
"type": "number",
|
||||
"description": "Optional delay in ms after which to show the notification"
|
||||
},
|
||||
"badgeDescription": {
|
||||
"type": "object",
|
||||
"description": "This is used in combination with the badged button to offer a text based alternative to the visual badging. Example 'New Feature: What's New'",
|
||||
"properties": {
|
||||
"string_id": {
|
||||
"type": "string",
|
||||
"description": "Fluent string id"
|
||||
}
|
||||
},
|
||||
"required": ["string_id"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
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";
|
||||
|
||||
|
@ -22,14 +23,7 @@ export class Trailhead extends React.PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
this.onInputChange = this.onInputChange.bind(this);
|
||||
this.onStartBlur = this.onStartBlur.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onInputInvalid = this.onInputInvalid.bind(this);
|
||||
|
||||
this.state = {
|
||||
emailInput: "",
|
||||
};
|
||||
}
|
||||
|
||||
get dialog() {
|
||||
|
@ -44,18 +38,6 @@ export class Trailhead extends React.PureComponent {
|
|||
this.props.document
|
||||
.getElementById("root")
|
||||
.setAttribute("aria-hidden", "true");
|
||||
// Start with focus in the email input box
|
||||
const input = this.dialog.querySelector("input[name=email]");
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onInputChange(e) {
|
||||
let error = e.target.previousSibling;
|
||||
this.setState({ emailInput: e.target.value });
|
||||
error.classList.remove("active");
|
||||
e.target.classList.remove("invalid");
|
||||
}
|
||||
|
||||
onStartBlur(event) {
|
||||
|
@ -72,24 +54,6 @@ export class Trailhead extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onSubmit(event) {
|
||||
// Dynamically require the email on submission so screen readers don't read
|
||||
// out it's always required because there's also ways to skip the modal
|
||||
const { email } = event.target.elements;
|
||||
if (!email.value.length) {
|
||||
email.required = true;
|
||||
email.checkValidity();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch(
|
||||
ac.UserEvent({ event: "SUBMIT_EMAIL", ...this._getFormInfo() })
|
||||
);
|
||||
|
||||
global.addEventListener("visibilitychange", this.closeModal);
|
||||
}
|
||||
|
||||
closeModal(ev) {
|
||||
global.removeEventListener("visibilitychange", this.closeModal);
|
||||
this.props.document.body.classList.remove("welcome");
|
||||
|
@ -116,14 +80,6 @@ export class Trailhead extends React.PureComponent {
|
|||
return { value };
|
||||
}
|
||||
|
||||
onInputInvalid(e) {
|
||||
let error = e.target.previousSibling;
|
||||
error.classList.add("active");
|
||||
e.target.classList.add("invalid");
|
||||
e.preventDefault(); // Override built-in form validation popup
|
||||
e.target.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { UTMTerm } = props;
|
||||
|
@ -162,90 +118,16 @@ export class Trailhead extends React.PureComponent {
|
|||
rel="noopener noreferrer"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="joinFormHeader"
|
||||
aria-describedby="joinFormBody"
|
||||
className="trailheadForm"
|
||||
>
|
||||
<h3
|
||||
id="joinFormHeader"
|
||||
data-l10n-id={content.form.title.string_id}
|
||||
<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}
|
||||
/>
|
||||
<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="service" type="hidden" value="sync" />
|
||||
<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="firstrun" />
|
||||
<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="trailheadTerms"
|
||||
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"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
$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;
|
||||
|
@ -47,6 +48,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
@ -141,103 +149,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.trailheadForm {
|
||||
$logo-size: 100px;
|
||||
|
||||
background: url('#{$image-path}trailhead/firefox-logo.png') top center / $logo-size no-repeat;
|
||||
min-width: 260px;
|
||||
padding-top: $logo-size;
|
||||
text-align: center;
|
||||
|
||||
h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $white;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.trailheadTerms {
|
||||
margin: 4px 30px 20px;
|
||||
|
||||
a,
|
||||
& {
|
||||
color: $white-70;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trailheadStart {
|
||||
border: 1px solid $white-50;
|
||||
cursor: pointer;
|
||||
|
@ -484,19 +395,3 @@
|
|||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.firstrun-title {
|
||||
background: url('chrome://branding/content/about-logo.png') top left no-repeat;
|
||||
background-size: 90px 90px;
|
||||
margin: 40px 0 10px;
|
||||
padding-top: 110px;
|
||||
|
||||
@media screen and (max-width: 790px) {
|
||||
background: url('chrome://branding/content/about-logo.png') top center no-repeat;
|
||||
background-size: 90px 90px;
|
||||
}
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position: top right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,6 +238,7 @@ export class DSCard extends React.PureComponent {
|
|||
pocket_id={this.props.pocket_id}
|
||||
shim={this.props.shim}
|
||||
bookmarkGuid={this.props.bookmarkGuid}
|
||||
campaignId={this.props.campaignId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -125,6 +125,7 @@ $excerpt-line-height: 20;
|
|||
border-radius: 4px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 5px 8px 7px;
|
||||
border: 0;
|
||||
color: $grey-90;
|
||||
|
@ -164,6 +165,7 @@ $excerpt-line-height: 20;
|
|||
}
|
||||
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
width: auto;
|
||||
|
|
|
@ -60,7 +60,7 @@ export class DSImage extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.idleCallbackId = window.requestIdleCallback(
|
||||
this.idleCallbackId = this.props.windowObj.requestIdleCallback(
|
||||
this.onIdleCallback.bind(this)
|
||||
);
|
||||
this.observer = new IntersectionObserver(this.onSeen.bind(this), {
|
||||
|
@ -77,6 +77,9 @@ export class DSImage extends React.PureComponent {
|
|||
if (this.observer) {
|
||||
this.observer.unobserve(ReactDOM.findDOMNode(this));
|
||||
}
|
||||
if (this.idleCallbackId) {
|
||||
this.props.windowObj.cancelIdleCallback(this.idleCallbackId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -160,4 +163,5 @@ DSImage.defaultProps = {
|
|||
extraClassNames: null, // Additional classnames to append to component
|
||||
optimize: true, // Measure parent container to request exact sizes
|
||||
alt_text: null,
|
||||
windowObj: window, // Added to support unit tests
|
||||
};
|
||||
|
|
|
@ -76,6 +76,7 @@ export class DSLinkMenu extends React.PureComponent {
|
|||
pocket_id: this.props.pocket_id,
|
||||
shim: this.props.shim,
|
||||
bookmarkGuid: this.props.bookmarkGuid,
|
||||
campaign_id: this.props.campaignId,
|
||||
}}
|
||||
/>
|
||||
</ContextMenuButton>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from "react";
|
||||
import { actionCreators as ac } from "common/Actions.jsm";
|
||||
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
|
||||
import { ModalOverlayWrapper } from "content-src/asrouter/components/ModalOverlay/ModalOverlay";
|
||||
|
||||
|
@ -10,6 +11,16 @@ export class DSPrivacyModal extends React.PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
this.onLinkClick = this.onLinkClick.bind(this);
|
||||
}
|
||||
|
||||
onLinkClick(event) {
|
||||
this.props.dispatch(
|
||||
ac.UserEvent({
|
||||
event: "CLICK_PRIVACY_INFO",
|
||||
source: "DS_PRIVACY_MODAL",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
|
@ -28,7 +39,10 @@ export class DSPrivacyModal extends React.PureComponent {
|
|||
<div className="privacy-notice">
|
||||
<h3 data-l10n-id="newtab-privacy-modal-header" />
|
||||
<p data-l10n-id="newtab-privacy-modal-paragraph" />
|
||||
<SafeAnchor url="https://www.mozilla.org/en-US/privacy/firefox/">
|
||||
<SafeAnchor
|
||||
onLinkClick={this.onLinkClick}
|
||||
url="https://www.mozilla.org/en-US/privacy/firefox/"
|
||||
>
|
||||
<span data-l10n-id="newtab-privacy-modal-link" />
|
||||
</SafeAnchor>
|
||||
</div>
|
||||
|
|
|
@ -142,6 +142,7 @@ export class Hero extends React.PureComponent {
|
|||
pocket_id={heroRec.pocket_id}
|
||||
shim={heroRec.shim}
|
||||
bookmarkGuid={heroRec.bookmarkGuid}
|
||||
campaignId={heroRec.campaign_id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -116,6 +116,7 @@ export class ListItem extends React.PureComponent {
|
|||
pocket_id={this.props.pocket_id}
|
||||
shim={this.props.shim}
|
||||
bookmarkGuid={this.props.bookmarkGuid}
|
||||
campaignId={this.props.campaignId}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
|
|
|
@ -28,6 +28,7 @@ export const LinkMenuOptions = {
|
|||
action: {
|
||||
type: at.SHOW_PRIVACY_INFO,
|
||||
},
|
||||
userEvent: "SHOW_PRIVACY_INFO",
|
||||
}),
|
||||
RemoveBookmark: site => ({
|
||||
id: "newtab-menu-remove-bookmark",
|
||||
|
@ -60,12 +61,20 @@ export const LinkMenuOptions = {
|
|||
}),
|
||||
userEvent: "OPEN_NEW_WINDOW",
|
||||
}),
|
||||
// This blocks the url for regular stories,
|
||||
// but also sends a message to DiscoveryStream with campaign_id.
|
||||
// If DiscoveryStream sees this message for a campaign_id
|
||||
// it also blocks it on the campaign_id.
|
||||
BlockUrl: (site, index, eventSource) => ({
|
||||
id: "newtab-menu-dismiss",
|
||||
icon: "dismiss",
|
||||
action: ac.AlsoToMain({
|
||||
type: at.BLOCK_URL,
|
||||
data: { url: site.open_url || site.url, pocket_id: site.pocket_id },
|
||||
data: {
|
||||
url: site.open_url || site.url,
|
||||
pocket_id: site.pocket_id,
|
||||
...(site.campaign_id ? { campaign_id: site.campaign_id } : {}),
|
||||
},
|
||||
}),
|
||||
impression: ac.ImpressionStats({
|
||||
source: eventSource,
|
||||
|
|
|
@ -176,4 +176,6 @@ input {
|
|||
@import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
|
||||
@import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
|
||||
@import '../asrouter/templates/EOYSnippet/EOYSnippet';
|
||||
@import '../asrouter/components/FxASignupForm/FxASignupForm';
|
||||
@import '../asrouter/templates/Trailhead/Trailhead';
|
||||
@import '../asrouter/templates/FullPageInterrupt/FullPageInterrupt';
|
||||
|
|
|
@ -70,6 +70,7 @@ $white-10: rgba($white, 0.1);
|
|||
$white-50: rgba($white, 0.5);
|
||||
$white-60: rgba($white, 0.6);
|
||||
$white-70: rgba($white, 0.7);
|
||||
$ghost-white: #FAFAFC;
|
||||
$pocket-teal: #50BCB6;
|
||||
$pocket-red: #EF4056;
|
||||
$shadow-10: rgba(12, 12, 13, 0.1);
|
||||
|
@ -85,7 +86,9 @@ $about-welcome-gradient: linear-gradient(to bottom, $blue-70 40%, $aw-extra-blue
|
|||
$about-welcome-extra-links: #676F7E;
|
||||
$firefox-wordmark-default-color: #363959;
|
||||
$firefox-wordmark-darktheme-color: $white;
|
||||
$trailhead-violet: #7542E5;
|
||||
$trailhead-purple: #2B2156;
|
||||
$trailhead-purple-80: #36296D;
|
||||
$trailhead-blue-60: #0250BB;
|
||||
$trailhead-blue-70: #054096;
|
||||
|
||||
|
|
|
@ -2725,6 +2725,7 @@ main {
|
|||
border-radius: 4px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 5px 8px 7px;
|
||||
border: 0;
|
||||
color: #0C0C0D;
|
||||
|
@ -2747,6 +2748,7 @@ main {
|
|||
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
|
||||
.ds-card .meta .cta-link {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
width: auto;
|
||||
|
@ -3936,6 +3938,84 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
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("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
|
@ -3963,6 +4043,11 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("../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; }
|
||||
|
@ -4023,65 +4108,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadForm {
|
||||
background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
min-width: 260px;
|
||||
padding-top: 100px;
|
||||
text-align: center; }
|
||||
.trailhead .trailheadForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.trailhead .trailheadForm p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms {
|
||||
margin: 4px 30px 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.trailhead .trailheadForm form {
|
||||
position: relative; }
|
||||
.trailhead .trailheadForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.trailhead .trailheadForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadForm button:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
|
@ -4258,14 +4284,172 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top left no-repeat;
|
||||
background-size: 90px 90px;
|
||||
margin: 40px 0 10px;
|
||||
padding-top: 110px; }
|
||||
@media screen and (max-width: 790px) {
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top center no-repeat;
|
||||
background-size: 90px 90px; } }
|
||||
.firstrun-title:dir(rtl) {
|
||||
background-position: top right; }
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
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: #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: 850px) {
|
||||
.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: 850px) {
|
||||
.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;
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 352px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 352px; } }
|
||||
.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("../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: 850px) {
|
||||
.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;
|
||||
background-size: 154px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 352px; } }
|
||||
|
|
|
@ -2728,6 +2728,7 @@ main {
|
|||
border-radius: 4px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 5px 8px 7px;
|
||||
border: 0;
|
||||
color: #0C0C0D;
|
||||
|
@ -2750,6 +2751,7 @@ main {
|
|||
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
|
||||
.ds-card .meta .cta-link {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
width: auto;
|
||||
|
@ -3939,6 +3941,84 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
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("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
|
@ -3966,6 +4046,11 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("../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; }
|
||||
|
@ -4026,65 +4111,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadForm {
|
||||
background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
min-width: 260px;
|
||||
padding-top: 100px;
|
||||
text-align: center; }
|
||||
.trailhead .trailheadForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.trailhead .trailheadForm p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms {
|
||||
margin: 4px 30px 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.trailhead .trailheadForm form {
|
||||
position: relative; }
|
||||
.trailhead .trailheadForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.trailhead .trailheadForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadForm button:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
|
@ -4261,14 +4287,172 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top left no-repeat;
|
||||
background-size: 90px 90px;
|
||||
margin: 40px 0 10px;
|
||||
padding-top: 110px; }
|
||||
@media screen and (max-width: 790px) {
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top center no-repeat;
|
||||
background-size: 90px 90px; } }
|
||||
.firstrun-title:dir(rtl) {
|
||||
background-position: top right; }
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
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: #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: 850px) {
|
||||
.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: 850px) {
|
||||
.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;
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 352px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 352px; } }
|
||||
.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("../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: 850px) {
|
||||
.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;
|
||||
background-size: 154px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 352px; } }
|
||||
|
|
|
@ -2725,6 +2725,7 @@ main {
|
|||
border-radius: 4px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 5px 8px 7px;
|
||||
border: 0;
|
||||
color: #0C0C0D;
|
||||
|
@ -2747,6 +2748,7 @@ main {
|
|||
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
|
||||
.ds-card .meta .cta-link {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
width: auto;
|
||||
|
@ -3936,6 +3938,84 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
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("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
|
||||
color: #FFF;
|
||||
|
@ -3963,6 +4043,11 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
@media (min-width: 850px) {
|
||||
.trailhead .trailheadContent .trailheadLearn {
|
||||
margin-inline-start: 74px; } }
|
||||
.trailhead .trailhead-join-form {
|
||||
background: url("../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; }
|
||||
|
@ -4023,65 +4108,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 4px 0 15px; }
|
||||
.trailhead .trailheadForm {
|
||||
background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
|
||||
min-width: 260px;
|
||||
padding-top: 100px;
|
||||
text-align: center; }
|
||||
.trailhead .trailheadForm h3 {
|
||||
font-size: 36px;
|
||||
font-weight: 200;
|
||||
line-height: 46px;
|
||||
margin: 12px 0 4px; }
|
||||
.trailhead .trailheadForm p {
|
||||
color: #FFF;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms {
|
||||
margin: 4px 30px 20px; }
|
||||
.trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
line-height: 20px; }
|
||||
.trailhead .trailheadForm form {
|
||||
position: relative; }
|
||||
.trailhead .trailheadForm form .error.active {
|
||||
inset-inline-start: 0;
|
||||
z-index: 0; }
|
||||
.trailhead .trailheadForm button,
|
||||
.trailhead .trailheadForm input {
|
||||
width: 100%; }
|
||||
.trailhead .trailheadForm input {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #737373;
|
||||
box-shadow: none;
|
||||
color: #38383D;
|
||||
font-size: 15px;
|
||||
transition: border-color 150ms, box-shadow 150ms; }
|
||||
.trailhead .trailheadForm input:hover {
|
||||
border-color: #0C0C0D; }
|
||||
.trailhead .trailheadForm input:focus {
|
||||
border-color: #0A84FF;
|
||||
box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
|
||||
.trailhead .trailheadForm input.invalid {
|
||||
border-color: #D70022; }
|
||||
.trailhead .trailheadForm input.invalid:focus {
|
||||
box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
|
||||
.trailhead .trailheadForm button {
|
||||
background-color: #0060DF;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
padding: 14px; }
|
||||
.trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
|
||||
background-color: #0250BB; }
|
||||
.trailhead .trailheadForm button:focus {
|
||||
outline: dotted 1px; }
|
||||
.trailhead .trailheadForm button:active {
|
||||
background-color: #054096; }
|
||||
.trailhead .trailheadStart {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
cursor: pointer;
|
||||
|
@ -4258,14 +4284,172 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
|
|||
opacity: 1;
|
||||
transform: translateY(0); } }
|
||||
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top left no-repeat;
|
||||
background-size: 90px 90px;
|
||||
margin: 40px 0 10px;
|
||||
padding-top: 110px; }
|
||||
@media screen and (max-width: 790px) {
|
||||
.firstrun-title {
|
||||
background: url("chrome://branding/content/about-logo.png") top center no-repeat;
|
||||
background-size: 90px 90px; } }
|
||||
.firstrun-title:dir(rtl) {
|
||||
background-position: top right; }
|
||||
.activity-stream.welcome {
|
||||
overflow: hidden; }
|
||||
|
||||
.activity-stream:not(.welcome) .fullpage-wrapper {
|
||||
display: none; }
|
||||
|
||||
.fullpage-wrapper {
|
||||
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: #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: 850px) {
|
||||
.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: 850px) {
|
||||
.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;
|
||||
align-self: center;
|
||||
padding: 50px 0; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .container {
|
||||
flex-direction: column;
|
||||
width: 352px;
|
||||
text-align: center; } }
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
position: relative;
|
||||
width: 538px;
|
||||
font-size: 18px;
|
||||
line-height: 30px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .fullpage-left-section {
|
||||
width: 352px; } }
|
||||
.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("../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: 850px) {
|
||||
.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;
|
||||
background-size: 154px; }
|
||||
@media screen and (max-width: 850px) {
|
||||
.fullpage-wrapper .trailheadCard {
|
||||
width: 352px; } }
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/firefox-systems.png
Normal file
Двоичные данные
browser/components/newtab/data/content/assets/trailhead/firefox-systems.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 17 KiB |
|
@ -1,6 +1,6 @@
|
|||
# Metrics we collect
|
||||
|
||||
By default, the about:newtab and about:home pages in Firefox (the pages you see when you open a new tab and when you start the browser), will send data back to Mozilla servers about usage of these pages. The intent is to collect data in order to improve the user's experience while using Activity Stream. Data about your specific browsing behaior or the sites you visit is **never transmitted to any Mozilla server**. At any time, it is easy to **turn off** this data collection by [opting out of Firefox telemetry](https://support.mozilla.org/kb/share-telemetry-data-mozilla-help-improve-firefox).
|
||||
By default, the about:newtab, about:welcome and about:home pages in Firefox (the pages you see when you open a new tab and when you start the browser), will send data back to Mozilla servers about usage of these pages. The intent is to collect data in order to improve the user's experience while using Activity Stream. Data about your specific browsing behaior or the sites you visit is **never transmitted to any Mozilla server**. At any time, it is easy to **turn off** this data collection by [opting out of Firefox telemetry](https://support.mozilla.org/kb/share-telemetry-data-mozilla-help-improve-firefox).
|
||||
|
||||
Data is sent to our servers in the form of discreet HTTPS 'pings' or messages whenever you do some action on the Activity Stream about:home, about:newtab or about:welcome pages. We try to minimize the amount and frequency of pings by batching them together. Pings are sent in [JSON serialized format](http://www.json.org/).
|
||||
|
||||
|
@ -169,6 +169,43 @@ A user event ping includes some basic metadata (tab id, addon version, etc.) as
|
|||
}
|
||||
```
|
||||
|
||||
#### Showing privacy information
|
||||
|
||||
```js
|
||||
{
|
||||
"event": "SHOW_PRIVACY_INFO",
|
||||
"source": "TOP_SITES",
|
||||
"action_position": 2,
|
||||
|
||||
// Basic metadata
|
||||
"action": "activity_stream_event",
|
||||
"page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
|
||||
"client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
|
||||
"session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
|
||||
"addon_version": "20180710100040",
|
||||
"locale": "en-US",
|
||||
"user_prefs": 7
|
||||
}
|
||||
```
|
||||
|
||||
#### Clicking on privacy information link
|
||||
|
||||
```js
|
||||
{
|
||||
"event": "CLICK_PRIVACY_INFO",
|
||||
"source": "DS_PRIVACY_MODAL",
|
||||
|
||||
// Basic metadata
|
||||
"action": "activity_stream_event",
|
||||
"page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
|
||||
"client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
|
||||
"session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
|
||||
"addon_version": "20180710100040",
|
||||
"locale": "en-US",
|
||||
"user_prefs": 7
|
||||
}
|
||||
```
|
||||
|
||||
#### Deleting a search shortcut
|
||||
```js
|
||||
{
|
||||
|
@ -409,7 +446,7 @@ A user event ping includes some basic metadata (tab id, addon version, etc.) as
|
|||
|
||||
```js
|
||||
{
|
||||
"event": ["SUBMIT_EMAIL" | "SKIPPED_SIGNIN"],
|
||||
"event": ["SUBMIT_EMAIL" | "SUBMIT_SIGNIN" | "SKIPPED_SIGNIN"],
|
||||
"value": {
|
||||
"has_flow_params": false,
|
||||
}
|
||||
|
|
|
@ -1893,6 +1893,20 @@ class _ASRouter {
|
|||
case ra.OPEN_APPLICATIONS_MENU:
|
||||
UITour.showMenu(target.browser.ownerGlobal, action.data.args);
|
||||
break;
|
||||
case ra.HIGHLIGHT_FEATURE:
|
||||
const highlight = await UITour.getTarget(
|
||||
target.browser.ownerGlobal,
|
||||
action.data.args
|
||||
);
|
||||
if (highlight) {
|
||||
await UITour.showHighlight(
|
||||
target.browser.ownerGlobal,
|
||||
highlight,
|
||||
"none",
|
||||
{ autohide: true }
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ra.INSTALL_ADDON_FROM_URL:
|
||||
this._updateOnboardingState();
|
||||
await MessageLoaderUtils.installAddonFromURL(
|
||||
|
|
|
@ -21,6 +21,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AttributionCode: "resource:///modules/AttributionCode.jsm",
|
||||
FilterExpressions:
|
||||
"resource://gre/modules/components-utils/FilterExpressions.jsm",
|
||||
fxAccounts: "resource://gre/modules/FxAccounts.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
|
@ -460,6 +461,12 @@ const TargetingGetters = {
|
|||
get totalBlockedCount() {
|
||||
return TrackingDBService.sumAllEvents();
|
||||
},
|
||||
get attachedFxAOAuthClients() {
|
||||
return this.usesFirefoxSync ? fxAccounts.listAttachedOAuthClients() : [];
|
||||
},
|
||||
get platformName() {
|
||||
return AppConstants.platform;
|
||||
},
|
||||
};
|
||||
|
||||
this.ASRouterTargeting = {
|
||||
|
|
|
@ -77,6 +77,64 @@ function checkURLMatch(aLocationURI, { hosts, matchPatternSet }, aRequest) {
|
|||
* have idempotent `init` and `uninit` methods.
|
||||
*/
|
||||
this.ASRouterTriggerListeners = new Map([
|
||||
[
|
||||
"openArticleURL",
|
||||
{
|
||||
id: "openArticleURL",
|
||||
_initialized: false,
|
||||
_triggerHandler: null,
|
||||
_hosts: new Set(),
|
||||
_matchPatternSet: null,
|
||||
readerModeEvent: "Reader:UpdateReaderButton",
|
||||
|
||||
init(triggerHandler, hosts, patterns) {
|
||||
if (!this._initialized) {
|
||||
this.receiveMessage = this.receiveMessage.bind(this);
|
||||
Services.mm.addMessageListener(this.readerModeEvent, this);
|
||||
this._triggerHandler = triggerHandler;
|
||||
this._initialized = true;
|
||||
}
|
||||
if (patterns) {
|
||||
if (this._matchPatternSet) {
|
||||
this._matchPatternSet = new MatchPatternSet(
|
||||
new Set([...this._matchPatternSet.patterns, ...patterns]),
|
||||
MATCH_PATTERN_OPTIONS
|
||||
);
|
||||
} else {
|
||||
this._matchPatternSet = new MatchPatternSet(
|
||||
patterns,
|
||||
MATCH_PATTERN_OPTIONS
|
||||
);
|
||||
}
|
||||
}
|
||||
if (hosts) {
|
||||
hosts.forEach(h => this._hosts.add(h));
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage({ data, target }) {
|
||||
if (data && data.isArticle) {
|
||||
const match = checkURLMatch(target.currentURI, {
|
||||
hosts: this._hosts,
|
||||
matchPatternSet: this._matchPatternSet,
|
||||
});
|
||||
if (match) {
|
||||
this._triggerHandler(target, { id: this.id, param: match });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
if (this._initialized) {
|
||||
Services.mm.removeMessageListener(this.readerModeEvent, this);
|
||||
this._initialized = false;
|
||||
this._triggerHandler = null;
|
||||
this._hosts = new Set();
|
||||
this._matchPatternSet = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"openBookmarkedURL",
|
||||
{
|
||||
|
@ -435,14 +493,7 @@ this.ASRouterTriggerListeners = new Map([
|
|||
this,
|
||||
"SiteProtection:ContentBlockingEvent"
|
||||
);
|
||||
|
||||
for (let win of Services.wm.getEnumerator("navigator:browser")) {
|
||||
if (isPrivateWindow(win)) {
|
||||
continue;
|
||||
}
|
||||
win.gBrowser.removeTabsProgressListener(this);
|
||||
}
|
||||
|
||||
EveryWindow.unregisterCallback(this.id);
|
||||
this.onLocationChange = null;
|
||||
this._initialized = false;
|
||||
}
|
||||
|
|
|
@ -467,6 +467,14 @@ const PREFS_CONFIG = new Map([
|
|||
},
|
||||
],
|
||||
// See browser/app/profile/firefox.js for other ASR preferences. They must be defined there to enable roll-outs.
|
||||
[
|
||||
"discoverystream.campaign.blocks",
|
||||
{
|
||||
title: "Track campaign blocks",
|
||||
skipBroadcast: true,
|
||||
value: "{}",
|
||||
},
|
||||
],
|
||||
[
|
||||
"discoverystream.config",
|
||||
{
|
||||
|
|
|
@ -62,6 +62,7 @@ const PREF_TOPSTORIES = "feeds.section.topstories";
|
|||
const PREF_SPOCS_CLEAR_ENDPOINT = "discoverystream.endpointSpocsClear";
|
||||
const PREF_SHOW_SPONSORED = "showSponsored";
|
||||
const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
|
||||
const PREF_CAMPAIGN_BLOCKS = "discoverystream.campaign.blocks";
|
||||
const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
|
||||
|
||||
let defaultLayoutResp;
|
||||
|
@ -773,8 +774,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
filterBlocked(data) {
|
||||
const filtered = [];
|
||||
if (data && data.length) {
|
||||
let campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
|
||||
const filteredItems = data.filter(item => {
|
||||
const blocked = NewTabUtils.blockedLinks.isBlocked({ url: item.url });
|
||||
const blocked =
|
||||
NewTabUtils.blockedLinks.isBlocked({ url: item.url }) ||
|
||||
campaigns[item.campaign_id];
|
||||
if (blocked) {
|
||||
filtered.push(item);
|
||||
}
|
||||
|
@ -838,7 +842,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
// `filterItems` as the frequency capped items.
|
||||
frequencyCapSpocs(spocs) {
|
||||
if (spocs && spocs.length) {
|
||||
const impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
|
||||
const impressions = this.readDataPref(PREF_SPOC_IMPRESSIONS);
|
||||
const caps = [];
|
||||
const result = spocs.filter(s => {
|
||||
const isBelow = this.isBelowFrequencyCap(impressions, s);
|
||||
|
@ -1009,7 +1013,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
recsExpireTime * 1000 || DEFAULT_RECS_EXPIRE_TIME,
|
||||
DEFAULT_RECS_EXPIRE_TIME
|
||||
);
|
||||
const impressions = this.readImpressionsPref(PREF_REC_IMPRESSIONS);
|
||||
const impressions = this.readDataPref(PREF_REC_IMPRESSIONS);
|
||||
const expired = [];
|
||||
const active = [];
|
||||
for (const item of recommendations) {
|
||||
|
@ -1122,7 +1126,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
|
||||
async reset() {
|
||||
this.resetImpressionPrefs();
|
||||
this.resetDataPrefs();
|
||||
await this.resetCache();
|
||||
if (this.loaded) {
|
||||
Services.obs.removeObserver(this, "idle-daily");
|
||||
|
@ -1137,9 +1141,10 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
await this.cache.set("affinities", {});
|
||||
}
|
||||
|
||||
resetImpressionPrefs() {
|
||||
this.writeImpressionsPref(PREF_SPOC_IMPRESSIONS, {});
|
||||
this.writeImpressionsPref(PREF_REC_IMPRESSIONS, {});
|
||||
resetDataPrefs() {
|
||||
this.writeDataPref(PREF_SPOC_IMPRESSIONS, {});
|
||||
this.writeDataPref(PREF_REC_IMPRESSIONS, {});
|
||||
this.writeDataPref(PREF_CAMPAIGN_BLOCKS, {});
|
||||
}
|
||||
|
||||
resetState() {
|
||||
|
@ -1164,20 +1169,28 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
|
||||
recordCampaignImpression(campaignId) {
|
||||
let impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
|
||||
let impressions = this.readDataPref(PREF_SPOC_IMPRESSIONS);
|
||||
|
||||
const timeStamps = impressions[campaignId] || [];
|
||||
timeStamps.push(Date.now());
|
||||
impressions = { ...impressions, [campaignId]: timeStamps };
|
||||
|
||||
this.writeImpressionsPref(PREF_SPOC_IMPRESSIONS, impressions);
|
||||
this.writeDataPref(PREF_SPOC_IMPRESSIONS, impressions);
|
||||
}
|
||||
|
||||
recordTopRecImpressions(recId) {
|
||||
let impressions = this.readImpressionsPref(PREF_REC_IMPRESSIONS);
|
||||
let impressions = this.readDataPref(PREF_REC_IMPRESSIONS);
|
||||
if (!impressions[recId]) {
|
||||
impressions = { ...impressions, [recId]: Date.now() };
|
||||
this.writeImpressionsPref(PREF_REC_IMPRESSIONS, impressions);
|
||||
this.writeDataPref(PREF_REC_IMPRESSIONS, impressions);
|
||||
}
|
||||
}
|
||||
|
||||
recordBlockCampaignId(campaignId) {
|
||||
const campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
|
||||
if (!campaigns[campaignId]) {
|
||||
campaigns[campaignId] = 1;
|
||||
this.writeDataPref(PREF_CAMPAIGN_BLOCKS, campaigns);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1214,17 +1227,17 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
);
|
||||
}
|
||||
|
||||
writeImpressionsPref(pref, impressions) {
|
||||
writeDataPref(pref, impressions) {
|
||||
this.store.dispatch(ac.SetPref(pref, JSON.stringify(impressions)));
|
||||
}
|
||||
|
||||
readImpressionsPref(pref) {
|
||||
readDataPref(pref) {
|
||||
const prefVal = this.store.getState().Prefs.values[pref];
|
||||
return prefVal ? JSON.parse(prefVal) : {};
|
||||
}
|
||||
|
||||
cleanUpImpressionPref(isExpired, pref) {
|
||||
const impressions = this.readImpressionsPref(pref);
|
||||
const impressions = this.readDataPref(pref);
|
||||
let changed = false;
|
||||
|
||||
Object.keys(impressions).forEach(id => {
|
||||
|
@ -1235,7 +1248,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
});
|
||||
|
||||
if (changed) {
|
||||
this.writeImpressionsPref(pref, impressions);
|
||||
this.writeDataPref(pref, impressions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1331,6 +1344,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
}
|
||||
}
|
||||
break;
|
||||
// This is fired from the browser, it has no concept of spocs, campaign or pocket.
|
||||
// We match the blocked url with our available spoc urls to see if there is a match.
|
||||
// I suspect we *could* instead do this in BLOCK_URL but I'm not sure.
|
||||
case at.PLACES_LINK_BLOCKED:
|
||||
if (this.showSpocs) {
|
||||
const spocsState = this.store.getState().DiscoveryStream.spocs;
|
||||
|
@ -1346,6 +1362,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
this._sendSpocsFill({ blocked_by_user: filtered }, false);
|
||||
|
||||
// If we're blocking a spoc, we want a slightly different treatment for open tabs.
|
||||
// AlsoToPreloaded updates the source data and preloaded tabs with a new spoc.
|
||||
// BroadcastToContent updates open tabs with a non spoc instead of a new spoc.
|
||||
this.store.dispatch(
|
||||
ac.AlsoToPreloaded({
|
||||
type: at.DISCOVERY_STREAM_LINK_BLOCKED,
|
||||
|
@ -1372,6 +1390,16 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
|||
// When this feed is shutting down:
|
||||
this.uninitPrefs();
|
||||
break;
|
||||
case at.BLOCK_URL: {
|
||||
// If we block a story that also has a campaign_id
|
||||
// we want to record that as blocked too.
|
||||
// This is because a single campaign might have slightly different urls.
|
||||
const { campaign_id } = action.data;
|
||||
if (campaign_id) {
|
||||
this.recordBlockCampaignId(campaign_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case at.PREF_CHANGED:
|
||||
switch (action.data.name) {
|
||||
case PREF_CONFIG:
|
||||
|
|
|
@ -54,6 +54,20 @@ const TRAILHEAD_MODAL_VARIANT_CONTENT = {
|
|||
skipButton: { string_id: "onboarding-start-browsing-button-label" },
|
||||
};
|
||||
|
||||
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 JOIN_CONTENT = {
|
||||
className: "joinCohort",
|
||||
title: { string_id: "onboarding-welcome-body" },
|
||||
|
@ -158,6 +172,27 @@ const ONBOARDING_MESSAGES = () => [
|
|||
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",
|
||||
|
@ -233,7 +268,7 @@ const ONBOARDING_MESSAGES = () => [
|
|||
order: 2,
|
||||
content: {
|
||||
title: { string_id: "onboarding-firefox-monitor-title" },
|
||||
text: { string_id: "onboarding-firefox-monitor-text" },
|
||||
text: { string_id: "onboarding-firefox-monitor-text2" },
|
||||
icon: "ffmonitor",
|
||||
primary_button: {
|
||||
label: { string_id: "onboarding-firefox-monitor-button" },
|
||||
|
@ -461,6 +496,7 @@ const ONBOARDING_MESSAGES = () => [
|
|||
delay: 5 * ONE_MINUTE,
|
||||
target: "whats-new-menu-button",
|
||||
action: { id: "show-whatsnew-button" },
|
||||
badgeDescription: { string_id: "cfr-badge-reader-label-newfeature" },
|
||||
},
|
||||
priority: 1,
|
||||
trigger: { id: "toolbarBadgeUpdate" },
|
||||
|
|
|
@ -240,6 +240,110 @@ const MESSAGES = () => [
|
|||
patterns: ["*://*/*.pdf"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "SEND_TAB_CFR",
|
||||
template: "cfr_doorhanger",
|
||||
content: {
|
||||
layout: "icon_and_message",
|
||||
category: "cfrFeatures",
|
||||
notification_text: { string_id: "cfr-doorhanger-extension-notification" },
|
||||
heading_text: { string_id: "cfr-doorhanger-send-tab-header" },
|
||||
info_icon: {
|
||||
label: { string_id: "cfr-doorhanger-extension-sumo-link" },
|
||||
sumo_path: "https://example.com",
|
||||
},
|
||||
text: { string_id: "cfr-doorhanger-send-tab-body" },
|
||||
icon: "chrome://branding/content/icon64.png",
|
||||
buttons: {
|
||||
primary: {
|
||||
label: { string_id: "cfr-doorhanger-send-tab-ok-button" },
|
||||
action: {
|
||||
type: "HIGHLIGHT_FEATURE",
|
||||
data: { args: "pageAction-sendToDevice" },
|
||||
},
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
label: { string_id: "cfr-doorhanger-extension-cancel-button" },
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
{
|
||||
label: {
|
||||
string_id: "cfr-doorhanger-extension-never-show-recommendation",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: {
|
||||
string_id: "cfr-doorhanger-extension-manage-settings-button",
|
||||
},
|
||||
action: {
|
||||
type: "OPEN_PREFERENCES_PAGE",
|
||||
data: { category: "general-cfrfeatures" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
targeting: "true",
|
||||
trigger: {
|
||||
// Match any URL that has a Reader Mode icon
|
||||
id: "openArticleURL",
|
||||
patterns: ["*://*/*"],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "SEND_RECIPE_TAB_CFR",
|
||||
template: "cfr_doorhanger",
|
||||
// Higher priority because this has the same targeting rules as
|
||||
// SEND_TAB_CFR but is more specific
|
||||
priority: 1,
|
||||
content: {
|
||||
layout: "icon_and_message",
|
||||
category: "cfrFeatures",
|
||||
notification_text: { string_id: "cfr-doorhanger-extension-notification" },
|
||||
heading_text: { string_id: "cfr-doorhanger-send-tab-recipe-header" },
|
||||
info_icon: {
|
||||
label: { string_id: "cfr-doorhanger-extension-sumo-link" },
|
||||
sumo_path: "https://example.com",
|
||||
},
|
||||
text: { string_id: "cfr-doorhanger-send-tab-body" },
|
||||
icon: "chrome://branding/content/icon64.png",
|
||||
buttons: {
|
||||
primary: {
|
||||
label: { string_id: "cfr-doorhanger-send-tab-ok-button" },
|
||||
action: {
|
||||
type: "HIGHLIGHT_FEATURE",
|
||||
data: { args: "pageAction-sendToDevice" },
|
||||
},
|
||||
},
|
||||
secondary: [
|
||||
{
|
||||
label: { string_id: "cfr-doorhanger-extension-cancel-button" },
|
||||
action: { type: "CANCEL" },
|
||||
},
|
||||
{
|
||||
label: {
|
||||
string_id: "cfr-doorhanger-extension-never-show-recommendation",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: {
|
||||
string_id: "cfr-doorhanger-extension-manage-settings-button",
|
||||
},
|
||||
action: {
|
||||
type: "OPEN_PREFERENCES_PAGE",
|
||||
data: { category: "general-cfrfeatures" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
targeting: "true",
|
||||
trigger: {
|
||||
id: "openArticleURL",
|
||||
params: ["www.allrecipes.com", "allrecipes.com"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const PanelTestProvider = {
|
||||
|
|
|
@ -255,6 +255,9 @@ class PlacesFeed {
|
|||
/**
|
||||
* observe - An observer for the LINK_BLOCKED_EVENT.
|
||||
* Called when a link is blocked.
|
||||
* Links can be blocked outside of newtab,
|
||||
* which is why we need to listen to this
|
||||
* on such a generic level.
|
||||
*
|
||||
* @param {null} subject
|
||||
* @param {str} topic The name of the event
|
||||
|
|
|
@ -3,57 +3,25 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EveryWindow",
|
||||
"resource:///modules/EveryWindow.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ToolbarPanelHub",
|
||||
"resource://activity-stream/lib/ToolbarPanelHub.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Services",
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"setTimeout",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"clearTimeout",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Services",
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"setInterval",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"clearInterval",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"requestIdleCallback",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EveryWindow: "resource:///modules/EveryWindow.jsm",
|
||||
ToolbarPanelHub: "resource://activity-stream/lib/ToolbarPanelHub.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
});
|
||||
|
||||
const {
|
||||
setInterval,
|
||||
clearInterval,
|
||||
requestIdleCallback,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
// Frequency at which to check for new messages
|
||||
const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000;
|
||||
let notificationsByWindow = new WeakMap();
|
||||
|
@ -151,6 +119,10 @@ class _ToolbarBadgeHub {
|
|||
}
|
||||
}
|
||||
|
||||
maybeInsertFTL(win) {
|
||||
win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
|
||||
}
|
||||
|
||||
executeAction({ id, data, message_id }) {
|
||||
switch (id) {
|
||||
case "show-whatsnew-button":
|
||||
|
@ -234,6 +206,15 @@ class _ToolbarBadgeHub {
|
|||
.querySelector(".toolbarbutton-badge")
|
||||
.classList.remove("feature-callout");
|
||||
toolbarButton.removeAttribute("badged");
|
||||
// Remove id used for for aria-label badge description
|
||||
const notificationDescription = toolbarButton.querySelector(
|
||||
"#toolbarbutton-notification-description"
|
||||
);
|
||||
if (notificationDescription) {
|
||||
notificationDescription.remove();
|
||||
toolbarButton.removeAttribute("aria-labelledby");
|
||||
toolbarButton.removeAttribute("aria-describedby");
|
||||
}
|
||||
}
|
||||
|
||||
addToolbarNotification(win, message) {
|
||||
|
@ -243,11 +224,40 @@ class _ToolbarBadgeHub {
|
|||
}
|
||||
let toolbarbutton = document.getElementById(message.content.target);
|
||||
if (toolbarbutton) {
|
||||
const badge = toolbarbutton.querySelector(".toolbarbutton-badge");
|
||||
badge.classList.add("feature-callout");
|
||||
toolbarbutton.setAttribute("badged", true);
|
||||
toolbarbutton
|
||||
.querySelector(".toolbarbutton-badge")
|
||||
.classList.add("feature-callout");
|
||||
|
||||
// If we have additional aria-label information for the notification
|
||||
// we add this content to the hidden `toolbarbutton-text` node.
|
||||
// We then use `aria-labelledby` to link this description to the button
|
||||
// that received the notification badge.
|
||||
if (message.content.badgeDescription) {
|
||||
// Insert strings as soon as we know we're showing them
|
||||
this.maybeInsertFTL(win);
|
||||
toolbarbutton.setAttribute(
|
||||
"aria-labelledby",
|
||||
`toolbarbutton-notification-description ${message.content.target}`
|
||||
);
|
||||
// Because tooltiptext is different to the label, it gets duplicated as
|
||||
// the description. Setting `describedby` to the same value as
|
||||
// `labelledby` will be detected by the a11y code and the description
|
||||
// will be removed.
|
||||
toolbarbutton.setAttribute(
|
||||
"aria-describedby",
|
||||
`toolbarbutton-notification-description ${message.content.target}`
|
||||
);
|
||||
const descriptionEl = document.createElement("span");
|
||||
descriptionEl.setAttribute(
|
||||
"id",
|
||||
"toolbarbutton-notification-description"
|
||||
);
|
||||
descriptionEl.setAttribute("hidden", true);
|
||||
document.l10n.setAttributes(
|
||||
descriptionEl,
|
||||
message.content.badgeDescription.string_id
|
||||
);
|
||||
toolbarbutton.appendChild(descriptionEl);
|
||||
}
|
||||
// `mousedown` event required because of the `onmousedown` defined on
|
||||
// the button that prevents `click` events from firing
|
||||
toolbarbutton.addEventListener("mousedown", this.removeAllNotifications);
|
||||
|
|
|
@ -86,6 +86,10 @@ cfr-protections-panel-link-text = Learn more
|
|||
|
||||
## What's New toolbar button and panel
|
||||
|
||||
# This string is used by screen readers to offer a text based alternative for
|
||||
# the notification icon
|
||||
cfr-badge-reader-label-newfeature = New feature:
|
||||
|
||||
cfr-whatsnew-button =
|
||||
.label = What’s New
|
||||
.tooltiptext = What’s New
|
||||
|
|
|
@ -32,11 +32,22 @@ 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
|
||||
.aria-label = Dismiss
|
||||
|
||||
## 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
|
||||
|
@ -98,7 +109,7 @@ onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere
|
|||
onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
|
||||
|
||||
onboarding-firefox-monitor-title = Stay Alert to Data Breaches
|
||||
onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
|
||||
onboarding-firefox-monitor-text2 = { -monitor-brand-name } monitors if your email has appeared in a known data breach and alerts you if it appears in a new breach.
|
||||
onboarding-firefox-monitor-button = Sign up for Alerts
|
||||
|
||||
onboarding-browse-privately-title = Browse Privately
|
||||
|
|
|
@ -102,6 +102,31 @@ add_task(async function test_trailhead_branches() {
|
|||
]
|
||||
);
|
||||
|
||||
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(
|
||||
"cards-multidevice",
|
||||
// Expected selectors:
|
||||
|
|
|
@ -37,6 +37,11 @@ ChromeUtils.defineModuleGetter(
|
|||
"TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
|
||||
// ASRouterTargeting.isMatch
|
||||
add_task(async function should_do_correct_targeting() {
|
||||
|
@ -445,6 +450,18 @@ add_task(async function checkdevToolsOpenedCount() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function check_platformName() {
|
||||
const message = {
|
||||
id: "foo",
|
||||
targeting: `platformName == "${AppConstants.platform}"`,
|
||||
};
|
||||
is(
|
||||
await ASRouterTargeting.findMatchingMessage({ messages: [message] }),
|
||||
message,
|
||||
"should select correct item by platformName"
|
||||
);
|
||||
});
|
||||
|
||||
AddonTestUtils.initMochitest(this);
|
||||
|
||||
add_task(async function checkAddonsInfo() {
|
||||
|
|
|
@ -20,6 +20,41 @@ async function openURLInWindow(window, url) {
|
|||
await BrowserTestUtils.browserLoaded(selectedBrowser, false, url);
|
||||
}
|
||||
|
||||
add_task(async function check_openArticleURL() {
|
||||
const TEST_URL =
|
||||
"https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
|
||||
const articleTrigger = ASRouterTriggerListeners.get("openArticleURL");
|
||||
|
||||
// Previously initialized by the Router
|
||||
articleTrigger.uninit();
|
||||
|
||||
// Initialize the trigger with a new triggerHandler that resolves a promise
|
||||
// with the URL match
|
||||
const listenerTriggered = new Promise(resolve =>
|
||||
articleTrigger.init((browser, match) => resolve(match), ["example.com"])
|
||||
);
|
||||
|
||||
const win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await openURLInWindow(win, TEST_URL);
|
||||
// Send a message from the content page (the TEST_URL) to the parent
|
||||
// This should trigger the `receiveMessage` cb in the articleTrigger
|
||||
await ContentTask.spawn(win.gBrowser.selectedBrowser, null, async () => {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
|
||||
});
|
||||
|
||||
await listenerTriggered.then(data =>
|
||||
is(
|
||||
data.param.url,
|
||||
TEST_URL,
|
||||
"We should match on the TEST_URL as a website article"
|
||||
)
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
articleTrigger.uninit();
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
add_task(async function check_openURL_listener() {
|
||||
const TEST_URL =
|
||||
"https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
|
||||
|
|
|
@ -33,8 +33,23 @@ async function setRTAMOOnboarding() {
|
|||
ASRouter._updateMessageProviders();
|
||||
await ASRouter.loadMessagesFromAllProviders();
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
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);
|
||||
await AttributionCode.deleteFileAsync();
|
||||
AttributionCode._clearCache();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -120,6 +120,9 @@ export const UserEventAction = Joi.object().keys({
|
|||
"ARCHIVE_FROM_POCKET",
|
||||
"SKIPPED_SIGNIN",
|
||||
"SUBMIT_EMAIL",
|
||||
"SUBMIT_SIGNIN",
|
||||
"SHOW_PRIVACY_INFO",
|
||||
"CLICK_PRIVACY_INFO",
|
||||
]).required(),
|
||||
source: Joi.valid(["TOP_SITES", "TOP_STORIES", "HIGHLIGHTS"]),
|
||||
action_position: Joi.number().integer(),
|
||||
|
|
|
@ -2304,9 +2304,17 @@ describe("ASRouter", () => {
|
|||
|
||||
describe("#UITour", () => {
|
||||
let showMenuStub;
|
||||
const highlightTarget = { target: "target" };
|
||||
beforeEach(() => {
|
||||
showMenuStub = sandbox.stub();
|
||||
globals.set("UITour", { showMenu: showMenuStub });
|
||||
globals.set("UITour", {
|
||||
showMenu: showMenuStub,
|
||||
getTarget: sandbox
|
||||
.stub()
|
||||
.withArgs(sinon.match.object, "pageAaction-sendToDevice")
|
||||
.resolves(highlightTarget),
|
||||
showHighlight: sandbox.stub(),
|
||||
});
|
||||
});
|
||||
it("should call UITour.showMenu with the correct params on OPEN_APPLICATIONS_MENU", async () => {
|
||||
const msg = fakeExecuteUserAction({
|
||||
|
@ -2322,6 +2330,21 @@ describe("ASRouter", () => {
|
|||
"appMenu"
|
||||
);
|
||||
});
|
||||
it("should call UITour.showHighlight with the correct params on HIGHLIGHT_FEATURE", async () => {
|
||||
const msg = fakeExecuteUserAction({
|
||||
type: "HIGHLIGHT_FEATURE",
|
||||
data: { args: "pageAction-sendToDevice" },
|
||||
});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(UITour.getTarget);
|
||||
assert.calledOnce(UITour.showHighlight);
|
||||
assert.calledWith(
|
||||
UITour.showHighlight,
|
||||
msg.target.browser.ownerGlobal,
|
||||
highlightTarget
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("valid preview endpoint", () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ describe("ASRouterTriggerListeners", () => {
|
|||
const bookmarkedURLListener = ASRouterTriggerListeners.get(
|
||||
"openBookmarkedURL"
|
||||
);
|
||||
const openArticleURLListener = ASRouterTriggerListeners.get("openArticleURL");
|
||||
const hosts = ["www.mozilla.com", "www.mozilla.org"];
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -91,6 +92,75 @@ describe("ASRouterTriggerListeners", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("openArticleURL", () => {
|
||||
describe("#init", () => {
|
||||
beforeEach(() => {
|
||||
globals.set(
|
||||
"MatchPatternSet",
|
||||
sandbox.stub().callsFake(patterns => ({
|
||||
patterns: new Set(patterns),
|
||||
matches: url => patterns.includes(url),
|
||||
}))
|
||||
);
|
||||
sandbox.stub(global.Services.mm, "addMessageListener");
|
||||
sandbox.stub(global.Services.mm, "removeMessageListener");
|
||||
});
|
||||
afterEach(() => {
|
||||
openArticleURLListener.uninit();
|
||||
});
|
||||
it("setup an event listener on init", () => {
|
||||
openArticleURLListener.init(sandbox.stub(), hosts, hosts);
|
||||
|
||||
assert.calledOnce(global.Services.mm.addMessageListener);
|
||||
assert.calledWithExactly(
|
||||
global.Services.mm.addMessageListener,
|
||||
openArticleURLListener.readerModeEvent,
|
||||
sinon.match.object
|
||||
);
|
||||
});
|
||||
it("should call triggerHandler correctly for matches [host match]", () => {
|
||||
const stub = sandbox.stub();
|
||||
const target = { currentURI: { host: hosts[0], spec: hosts[1] } };
|
||||
openArticleURLListener.init(stub, hosts, hosts);
|
||||
|
||||
const [
|
||||
,
|
||||
{ receiveMessage },
|
||||
] = global.Services.mm.addMessageListener.firstCall.args;
|
||||
receiveMessage({ data: { isArticle: true }, target });
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, target, {
|
||||
id: openArticleURLListener.id,
|
||||
param: { host: hosts[0], url: hosts[1] },
|
||||
});
|
||||
});
|
||||
it("should call triggerHandler correctly for matches [pattern match]", () => {
|
||||
const stub = sandbox.stub();
|
||||
const target = { currentURI: { host: null, spec: hosts[1] } };
|
||||
openArticleURLListener.init(stub, hosts, hosts);
|
||||
|
||||
const [
|
||||
,
|
||||
{ receiveMessage },
|
||||
] = global.Services.mm.addMessageListener.firstCall.args;
|
||||
receiveMessage({ data: { isArticle: true }, target });
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, target, {
|
||||
id: openArticleURLListener.id,
|
||||
param: { host: null, url: hosts[1] },
|
||||
});
|
||||
});
|
||||
it("should remove the message listener", () => {
|
||||
openArticleURLListener.init(sandbox.stub(), hosts, hosts);
|
||||
openArticleURLListener.uninit();
|
||||
|
||||
assert.calledOnce(global.Services.mm.removeMessageListener);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("frequentVisits", () => {
|
||||
let _triggerHandler;
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -8,7 +8,7 @@ describe("PanelTestProvider", () => {
|
|||
it("should have a message", () => {
|
||||
// Careful: when changing this number make sure that new messages also go
|
||||
// through schema verifications.
|
||||
assert.lengthOf(messages, 9);
|
||||
assert.lengthOf(messages, 11);
|
||||
});
|
||||
it("should be a valid message", () => {
|
||||
const fxaMessages = messages.filter(
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
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 sendTelemetryStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
dispatch = sandbox.stub();
|
||||
onBlock = sandbox.stub();
|
||||
onAction = 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}
|
||||
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",
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 },
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
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";
|
||||
|
@ -20,6 +21,14 @@ describe("<Interrupt>", () => {
|
|||
);
|
||||
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(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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";
|
||||
|
@ -75,6 +76,10 @@ describe("<Trailhead>", () => {
|
|||
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());
|
||||
|
|
|
@ -2,7 +2,7 @@ import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDi
|
|||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
describe("<DSTextPromo>", () => {
|
||||
describe("<DSDismiss>", () => {
|
||||
const fakeSpoc = {
|
||||
url: "https://foo.com",
|
||||
guid: "1234",
|
||||
|
|
|
@ -111,4 +111,23 @@ describe("Discovery Stream <DSImage>", () => {
|
|||
|
||||
assert.calledOnce(observer.unobserve);
|
||||
});
|
||||
describe("DSImage with Idle Callback", () => {
|
||||
let wrapper;
|
||||
let windowStub = {
|
||||
requestIdleCallback: sinon.stub().returns(1),
|
||||
cancelIdleCallback: sinon.stub(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<DSImage windowObj={windowStub} />);
|
||||
});
|
||||
|
||||
it("should call requestIdleCallback on componentDidMount", () => {
|
||||
assert.calledOnce(windowStub.requestIdleCallback);
|
||||
});
|
||||
|
||||
it("should call cancelIdleCallback on componentWillUnmount", () => {
|
||||
wrapper.instance().componentWillUnmount();
|
||||
assert.calledOnce(windowStub.cancelIdleCallback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { DSPrivacyModal } from "content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { actionCreators as ac } from "common/Actions.jsm";
|
||||
import React from "react";
|
||||
|
||||
describe("Discovery Stream <DSPrivacyModal>", () => {
|
||||
let sandbox;
|
||||
let dispatch;
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
dispatch = sandbox.stub();
|
||||
wrapper = shallow(<DSPrivacyModal dispatch={dispatch} />);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should contain a privacy notice", () => {
|
||||
|
@ -16,10 +25,20 @@ describe("Discovery Stream <DSPrivacyModal>", () => {
|
|||
});
|
||||
|
||||
it("should call dispatch when modal is closed", () => {
|
||||
let dispatch = sandbox.stub();
|
||||
let wrapper = shallow(<DSPrivacyModal dispatch={dispatch} />);
|
||||
|
||||
wrapper.instance().closeModal();
|
||||
assert.calledOnce(dispatch);
|
||||
});
|
||||
|
||||
it("should call dispatch with the correct events", () => {
|
||||
wrapper.instance().onLinkClick();
|
||||
|
||||
assert.calledOnce(dispatch);
|
||||
assert.calledWith(
|
||||
dispatch,
|
||||
ac.UserEvent({
|
||||
event: "CLICK_PRIVACY_INFO",
|
||||
source: "DS_PRIVACY_MODAL",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -892,7 +892,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
first: Date.now() - recsExpireTime * 1000,
|
||||
third: Date.now(),
|
||||
};
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
|
||||
const result = feed.rotate(
|
||||
feedResponse.recommendations,
|
||||
|
@ -1107,7 +1107,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
const fakeImpressions = {
|
||||
seen: [Date.now() - 1],
|
||||
};
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
|
||||
const { data: result, filtered } = feed.frequencyCapSpocs(fakeSpocs);
|
||||
|
||||
|
@ -1224,16 +1224,29 @@ describe("DiscoveryStreamFeed", () => {
|
|||
|
||||
describe("#recordCampaignImpression", () => {
|
||||
it("should return false if time based cap is hit", () => {
|
||||
sandbox.stub(feed, "readImpressionsPref").returns({});
|
||||
sandbox.stub(feed, "writeImpressionsPref").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns({});
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.recordCampaignImpression("seen");
|
||||
|
||||
assert.calledWith(
|
||||
feed.writeImpressionsPref,
|
||||
SPOC_IMPRESSION_TRACKING_PREF,
|
||||
{ seen: [0] }
|
||||
);
|
||||
assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
|
||||
seen: [0],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#recordBlockCampaignId", () => {
|
||||
it("should call writeDataPref with new campaign id added", () => {
|
||||
sandbox.stub(feed, "readDataPref").returns({ "1234": 1 });
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.recordBlockCampaignId("5678");
|
||||
|
||||
assert.calledOnce(feed.readDataPref);
|
||||
assert.calledWith(feed.writeDataPref, "discoverystream.campaign.blocks", {
|
||||
"1234": 1,
|
||||
"5678": 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1267,39 +1280,35 @@ describe("DiscoveryStreamFeed", () => {
|
|||
"campaign-2": [Date.now() - 1],
|
||||
"campaign-3": [Date.now() - 1],
|
||||
};
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "writeImpressionsPref").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.cleanUpCampaignImpressionPref(fakeSpocs);
|
||||
|
||||
assert.calledWith(
|
||||
feed.writeImpressionsPref,
|
||||
SPOC_IMPRESSION_TRACKING_PREF,
|
||||
{ "campaign-2": [-1] }
|
||||
);
|
||||
assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
|
||||
"campaign-2": [-1],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#recordTopRecImpressions", () => {
|
||||
it("should add a rec id to the rec impression pref", () => {
|
||||
sandbox.stub(feed, "readImpressionsPref").returns({});
|
||||
sandbox.stub(feed, "writeImpressionsPref");
|
||||
sandbox.stub(feed, "readDataPref").returns({});
|
||||
sandbox.stub(feed, "writeDataPref");
|
||||
|
||||
feed.recordTopRecImpressions("rec");
|
||||
|
||||
assert.calledWith(
|
||||
feed.writeImpressionsPref,
|
||||
REC_IMPRESSION_TRACKING_PREF,
|
||||
{ rec: 0 }
|
||||
);
|
||||
assert.calledWith(feed.writeDataPref, REC_IMPRESSION_TRACKING_PREF, {
|
||||
rec: 0,
|
||||
});
|
||||
});
|
||||
it("should not add an impression if it already exists", () => {
|
||||
sandbox.stub(feed, "readImpressionsPref").returns({ rec: 4 });
|
||||
sandbox.stub(feed, "writeImpressionsPref");
|
||||
sandbox.stub(feed, "readDataPref").returns({ rec: 4 });
|
||||
sandbox.stub(feed, "writeDataPref");
|
||||
|
||||
feed.recordTopRecImpressions("rec");
|
||||
|
||||
assert.notCalled(feed.writeImpressionsPref);
|
||||
assert.notCalled(feed.writeDataPref);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1336,20 +1345,19 @@ describe("DiscoveryStreamFeed", () => {
|
|||
rec3: Date.now() - 1,
|
||||
rec5: Date.now() - 1,
|
||||
};
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "writeImpressionsPref").returns();
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "writeDataPref").returns();
|
||||
|
||||
feed.cleanUpTopRecImpressionPref(newFeeds);
|
||||
|
||||
assert.calledWith(
|
||||
feed.writeImpressionsPref,
|
||||
REC_IMPRESSION_TRACKING_PREF,
|
||||
{ rec2: -1, rec3: -1 }
|
||||
);
|
||||
assert.calledWith(feed.writeDataPref, REC_IMPRESSION_TRACKING_PREF, {
|
||||
rec2: -1,
|
||||
rec3: -1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#writeImpressionsPref", () => {
|
||||
describe("#writeDataPref", () => {
|
||||
it("should call Services.prefs.setStringPref", () => {
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
const fakeImpressions = {
|
||||
|
@ -1357,7 +1365,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
bar: [Date.now() - 1],
|
||||
};
|
||||
|
||||
feed.writeImpressionsPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
|
||||
feed.writeDataPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
|
||||
|
||||
assert.calledWithMatch(feed.store.dispatch, {
|
||||
data: {
|
||||
|
@ -1369,7 +1377,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#readImpressionsPref", () => {
|
||||
describe("#readDataPref", () => {
|
||||
it("should return what's in Services.prefs.getStringPref", () => {
|
||||
const fakeImpressions = {
|
||||
foo: [Date.now() - 1],
|
||||
|
@ -1377,7 +1385,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
};
|
||||
setPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
|
||||
|
||||
const result = feed.readImpressionsPref(SPOC_IMPRESSION_TRACKING_PREF);
|
||||
const result = feed.readDataPref(SPOC_IMPRESSION_TRACKING_PREF);
|
||||
|
||||
assert.deepEqual(result, fakeImpressions);
|
||||
});
|
||||
|
@ -1463,7 +1471,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
];
|
||||
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
|
||||
await feed.onAction({
|
||||
|
@ -1484,7 +1492,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
Object.defineProperty(feed, "showSpocs", { get: () => true });
|
||||
const fakeImpressions = {};
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
|
||||
await feed.onAction({
|
||||
|
@ -1499,7 +1507,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
Object.defineProperty(feed, "showSpocs", { get: () => true });
|
||||
const fakeImpressions = {};
|
||||
sandbox.stub(feed, "recordCampaignImpression").returns();
|
||||
sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
|
||||
sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
|
||||
sandbox.spy(feed.store, "dispatch");
|
||||
sandbox.spy(feed, "frequencyCapSpocs");
|
||||
|
||||
|
@ -1622,6 +1630,21 @@ describe("DiscoveryStreamFeed", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#onAction: BLOCK_URL", () => {
|
||||
it("should call recordBlockCampaignId whith BLOCK_URL", async () => {
|
||||
sandbox.stub(feed, "recordBlockCampaignId").returns();
|
||||
|
||||
await feed.onAction({
|
||||
type: at.BLOCK_URL,
|
||||
data: {
|
||||
campaign_id: "1234",
|
||||
},
|
||||
});
|
||||
|
||||
assert.calledWith(feed.recordBlockCampaignId, "1234");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onAction: INIT", () => {
|
||||
it("should be .loaded=false before initialization", () => {
|
||||
assert.isFalse(feed.loaded);
|
||||
|
@ -1725,7 +1748,7 @@ describe("DiscoveryStreamFeed", () => {
|
|||
assert.calledOnce(feed.resetCache);
|
||||
});
|
||||
it("should dispatch DISCOVERY_STREAM_LAYOUT_RESET from DISCOVERY_STREAM_CONFIG_CHANGE", async () => {
|
||||
sandbox.stub(feed, "resetImpressionPrefs");
|
||||
sandbox.stub(feed, "resetDataPrefs");
|
||||
sandbox.stub(feed, "resetCache").resolves();
|
||||
sandbox.stub(feed, "enable").resolves();
|
||||
setPref(CONFIG_PREF_NAME, { enabled: true });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { _ToolbarBadgeHub } from "lib/ToolbarBadgeHub.jsm";
|
||||
import { GlobalOverrider } from "test/unit/utils";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
import { _ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm";
|
||||
import { _ToolbarPanelHub, ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm";
|
||||
|
||||
describe("ToolbarBadgeHub", () => {
|
||||
let sandbox;
|
||||
|
@ -23,6 +23,7 @@ describe("ToolbarBadgeHub", () => {
|
|||
let clearUserPrefStub;
|
||||
let setStringPrefStub;
|
||||
let requestIdleCallbackStub;
|
||||
let fakeWindow;
|
||||
beforeEach(async () => {
|
||||
globals = new GlobalOverrider();
|
||||
sandbox = sinon.createSandbox();
|
||||
|
@ -44,6 +45,8 @@ describe("ToolbarBadgeHub", () => {
|
|||
removeAttribute: sandbox.stub(),
|
||||
querySelector: sandbox.stub(),
|
||||
addEventListener: sandbox.stub(),
|
||||
remove: sandbox.stub(),
|
||||
appendChild: sandbox.stub(),
|
||||
};
|
||||
// Share the same element when selecting child nodes
|
||||
fakeElement.querySelector.returns(fakeElement);
|
||||
|
@ -54,7 +57,8 @@ describe("ToolbarBadgeHub", () => {
|
|||
clearTimeoutStub = sandbox.stub();
|
||||
setTimeoutStub = sandbox.stub();
|
||||
setIntervalStub = sandbox.stub();
|
||||
const fakeWindow = {
|
||||
fakeWindow = {
|
||||
MozXULElement: { insertFTLIfNeeded: sandbox.stub() },
|
||||
ownerGlobal: {
|
||||
gBrowser: {
|
||||
selectedBrowser: "browser",
|
||||
|
@ -68,6 +72,7 @@ describe("ToolbarBadgeHub", () => {
|
|||
setStringPrefStub = sandbox.stub();
|
||||
requestIdleCallbackStub = sandbox.stub().callsFake(fn => fn());
|
||||
globals.set({
|
||||
ToolbarPanelHub,
|
||||
requestIdleCallback: requestIdleCallbackStub,
|
||||
EveryWindow: everyWindowStub,
|
||||
PrivateBrowsingUtils: { isBrowserPrivate: isBrowserPrivateStub },
|
||||
|
@ -202,8 +207,12 @@ describe("ToolbarBadgeHub", () => {
|
|||
let target;
|
||||
let fakeDocument;
|
||||
beforeEach(() => {
|
||||
fakeDocument = { getElementById: sandbox.stub().returns(fakeElement) };
|
||||
target = { browser: { ownerDocument: fakeDocument } };
|
||||
fakeDocument = {
|
||||
getElementById: sandbox.stub().returns(fakeElement),
|
||||
createElement: sandbox.stub().returns(fakeElement),
|
||||
l10n: { setAttributes: sandbox.stub() },
|
||||
};
|
||||
target = { ...fakeWindow, browser: { ownerDocument: fakeDocument } };
|
||||
});
|
||||
it("shouldn't do anything if target element is not found", () => {
|
||||
fakeDocument.getElementById.returns(null);
|
||||
|
@ -252,6 +261,41 @@ describe("ToolbarBadgeHub", () => {
|
|||
message_id: whatsnewMessage.id,
|
||||
});
|
||||
});
|
||||
it("should create a description element", () => {
|
||||
sandbox.stub(instance, "executeAction");
|
||||
instance.addToolbarNotification(target, whatsnewMessage);
|
||||
|
||||
assert.calledOnce(fakeDocument.createElement);
|
||||
assert.calledWithExactly(fakeDocument.createElement, "span");
|
||||
});
|
||||
it("should set description id to element and to button", () => {
|
||||
sandbox.stub(instance, "executeAction");
|
||||
instance.addToolbarNotification(target, whatsnewMessage);
|
||||
|
||||
assert.calledWithExactly(
|
||||
fakeElement.setAttribute,
|
||||
"id",
|
||||
"toolbarbutton-notification-description"
|
||||
);
|
||||
assert.calledWithExactly(
|
||||
fakeElement.setAttribute,
|
||||
"aria-labelledby",
|
||||
`toolbarbutton-notification-description ${
|
||||
whatsnewMessage.content.target
|
||||
}`
|
||||
);
|
||||
});
|
||||
it("should attach fluent id to description", () => {
|
||||
sandbox.stub(instance, "executeAction");
|
||||
instance.addToolbarNotification(target, whatsnewMessage);
|
||||
|
||||
assert.calledOnce(fakeDocument.l10n.setAttributes);
|
||||
assert.calledWithExactly(
|
||||
fakeDocument.l10n.setAttributes,
|
||||
fakeElement,
|
||||
whatsnewMessage.content.badgeDescription.string_id
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("registerBadgeNotificationListener", () => {
|
||||
let msg_no_delay;
|
||||
|
@ -418,10 +462,13 @@ describe("ToolbarBadgeHub", () => {
|
|||
it("should remove the notification", () => {
|
||||
instance.removeToolbarNotification(fakeElement);
|
||||
|
||||
assert.calledOnce(fakeElement.removeAttribute);
|
||||
assert.calledThrice(fakeElement.removeAttribute);
|
||||
assert.calledWithExactly(fakeElement.removeAttribute, "badged");
|
||||
assert.calledWithExactly(fakeElement.removeAttribute, "aria-labelledby");
|
||||
assert.calledWithExactly(fakeElement.removeAttribute, "aria-describedby");
|
||||
assert.calledOnce(fakeElement.classList.remove);
|
||||
assert.calledWithExactly(fakeElement.classList.remove, "feature-callout");
|
||||
assert.calledOnce(fakeElement.remove);
|
||||
});
|
||||
});
|
||||
describe("removeAllNotifications", () => {
|
||||
|
|
|
@ -209,7 +209,7 @@ const TEST_GLOBAL = {
|
|||
},
|
||||
urlFormatter: { formatURL: str => str, formatURLPref: str => str },
|
||||
mm: {
|
||||
addMessageListener: (msg, cb) => cb(),
|
||||
addMessageListener: (msg, cb) => this.receiveMessage(),
|
||||
removeMessageListener() {},
|
||||
},
|
||||
appShell: { hiddenDOMWindow: { performance: new FakePerformance() } },
|
||||
|
|
|
@ -86,6 +86,10 @@ cfr-protections-panel-link-text = Learn more
|
|||
|
||||
## What's New toolbar button and panel
|
||||
|
||||
# This string is used by screen readers to offer a text based alternative for
|
||||
# the notification icon
|
||||
cfr-badge-reader-label-newfeature = New feature:
|
||||
|
||||
cfr-whatsnew-button =
|
||||
.label = What’s New
|
||||
.tooltiptext = What’s New
|
||||
|
|
|
@ -32,11 +32,22 @@ 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
|
||||
.aria-label = Dismiss
|
||||
|
||||
## 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
|
||||
|
@ -98,7 +109,7 @@ onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere
|
|||
onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
|
||||
|
||||
onboarding-firefox-monitor-title = Stay Alert to Data Breaches
|
||||
onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
|
||||
onboarding-firefox-monitor-text2 = { -monitor-brand-name } monitors if your email has appeared in a known data breach and alerts you if it appears in a new breach.
|
||||
onboarding-firefox-monitor-button = Sign up for Alerts
|
||||
|
||||
onboarding-browse-privately-title = Browse Privately
|
||||
|
|
Загрузка…
Ссылка в новой задаче