Bug 1548388 - Add focus states, fill telemetry and bug fixes to Activity Stream r=r1cky

Differential Revision: https://phabricator.services.mozilla.com/D29577

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ed Lee 2019-05-01 22:55:10 +00:00
Родитель 1610206611
Коммит f2e01bb6c1
461 изменённых файлов: 4343 добавлений и 859 удалений

Просмотреть файл

@ -1316,6 +1316,12 @@ pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", tru
pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", false); pref("browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", false);
#endif #endif
#ifdef NIGHTLY_BUILD
pref("trailhead.firstrun.cohort", 1);
#else
pref("trailhead.firstrun.cohort", 0);
#endif
// Enable the DOM fullscreen API. // Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true); pref("full-screen-api.enabled", true);

Просмотреть файл

@ -0,0 +1,17 @@
module.exports = {
"plugins": [
"jsx-a11y" // require("eslint-plugin-jsx-a11y")
],
"extends": "plugin:jsx-a11y/recommended",
"overrides": [{
// These files use fluent-dom to insert content
"files": [
"content-src/asrouter/templates/OnboardingMessage/**",
"content-src/asrouter/templates/Trailhead/**",
],
"rules": {
"jsx-a11y/anchor-has-content": 0,
"jsx-a11y/heading-has-content": 0,
}
}],
};

Просмотреть файл

@ -100,8 +100,9 @@ function templateHTML(options, html) {
<link rel="stylesheet" href="${options.baseUrl}css/activity-stream.css" /> <link rel="stylesheet" href="${options.baseUrl}css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root">${isPrerendered ? html : "<!-- Regular React Rendering -->"}</div> <div id="root">${isPrerendered ? html : "<!-- Regular React Rendering -->"}</div>
<div id="footer-snippets-container"></div>${options.noscripts ? "" : scriptRender} <div id="footer-asrouter-container"></div>${options.noscripts ? "" : scriptRender}
</body> </body>
</html> </html>
`; `;

Просмотреть файл

@ -46,10 +46,12 @@ for (const type of [
"DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_IMPRESSION_STATS",
"DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_RESET",
"DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LAYOUT_UPDATE",
"DISCOVERY_STREAM_LINK_BLOCKED",
"DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_LOADED_CONTENT",
"DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_OPT_OUT",
"DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_CAPS",
"DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_ENDPOINT",
"DISCOVERY_STREAM_SPOCS_FILL",
"DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOCS_UPDATE",
"DISCOVERY_STREAM_SPOC_IMPRESSION", "DISCOVERY_STREAM_SPOC_IMPRESSION",
"DOWNLOAD_CHANGED", "DOWNLOAD_CHANGED",
@ -291,6 +293,21 @@ function ASRouterUserEvent(data) {
}); });
} }
/**
* DiscoveryStreamSpocsFill - A telemetry ping indicating a SPOCS Fill event.
*
* @param {object} data Fields to include in the ping (spocs_fills, etc.)
* @param {int} importContext (For testing) Override the import context for testing.
* @return {object} An AlsoToMain action
*/
function DiscoveryStreamSpocsFill(data, importContext = globalImportContext) {
const action = {
type: actionTypes.DISCOVERY_STREAM_SPOCS_FILL,
data,
};
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
/** /**
* UndesiredEvent - A telemetry ping indicating an undesired state. * UndesiredEvent - A telemetry ping indicating an undesired state.
* *
@ -398,6 +415,7 @@ this.actionCreators = {
WebExtEvent, WebExtEvent,
DiscoveryStreamImpressionStats, DiscoveryStreamImpressionStats,
DiscoveryStreamLoadedContent, DiscoveryStreamLoadedContent,
DiscoveryStreamSpocsFill,
}; };
// These are helpers to test for certain kinds of actions // These are helpers to test for certain kinds of actions

Просмотреть файл

@ -533,7 +533,7 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
}; };
} }
return prevState; return prevState;
case at.PLACES_LINK_BLOCKED: case at.DISCOVERY_STREAM_LINK_BLOCKED:
return isNotReady() ? prevState : return isNotReady() ? prevState :
nextState(items => items.filter(item => item.url !== action.data.url)); nextState(items => items.filter(item => item.url !== action.data.url));

Просмотреть файл

@ -11,9 +11,11 @@ import ReactDOM from "react-dom";
import {ReturnToAMO} from "./templates/ReturnToAMO/ReturnToAMO"; import {ReturnToAMO} from "./templates/ReturnToAMO/ReturnToAMO";
import {SnippetsTemplates} from "./templates/template-manifest"; import {SnippetsTemplates} from "./templates/template-manifest";
import {StartupOverlay} from "./templates/StartupOverlay/StartupOverlay"; import {StartupOverlay} from "./templates/StartupOverlay/StartupOverlay";
import {Trailhead} from "./templates/Trailhead/Trailhead";
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child"; const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent"; const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
const TEMPLATES_ABOVE_PAGE = ["trailhead"];
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"]; const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
export const ASRouterUtils = { export const ASRouterUtils = {
@ -96,7 +98,8 @@ export class ASRouterUISurface extends React.PureComponent {
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this); this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
this.state = {message: {}, bundle: {}}; this.state = {message: {}, bundle: {}};
if (props.document) { if (props.document) {
this.portalContainer = props.document.getElementById("footer-snippets-container"); this.headerPortal = props.document.getElementById("header-asrouter-container");
this.footerPortal = props.document.getElementById("footer-asrouter-container");
} }
} }
@ -221,7 +224,8 @@ export class ASRouterUISurface extends React.PureComponent {
renderSnippets() { renderSnippets() {
if (this.state.bundle.template === "onboarding" || if (this.state.bundle.template === "onboarding" ||
this.state.message.template === "fxa_overlay" || this.state.message.template === "fxa_overlay" ||
this.state.message.template === "return_to_amo_overlay") { this.state.message.template === "return_to_amo_overlay" ||
this.state.message.template === "trailhead") {
return null; return null;
} }
const SnippetComponent = SnippetsTemplates[this.state.message.template]; const SnippetComponent = SnippetsTemplates[this.state.message.template];
@ -288,6 +292,20 @@ export class ASRouterUISurface extends React.PureComponent {
return null; return null;
} }
renderTrailhead() {
const {message} = this.state;
if (message.template === "trailhead") {
return (<Trailhead
message={message}
onAction={ASRouterUtils.executeAction}
onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
sendUserActionTelemetry={this.sendUserActionTelemetry}
dispatch={this.props.dispatch}
fxaEndpoint={this.props.fxaEndpoint} />);
}
return null;
}
renderPreviewBanner() { renderPreviewBanner() {
if (this.state.message.provider !== "preview") { if (this.state.message.provider !== "preview") {
return null; return null;
@ -305,6 +323,7 @@ export class ASRouterUISurface extends React.PureComponent {
const {message, bundle} = this.state; const {message, bundle} = this.state;
if (!message.id && !bundle.template) { return null; } if (!message.id && !bundle.template) { return null; }
const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(message.template); const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(message.template);
const shouldRenderInHeader = TEMPLATES_ABOVE_PAGE.includes(message.template);
return shouldRenderBelowSearch ? return shouldRenderBelowSearch ?
// Render special below search snippets in place; // Render special below search snippets in place;
@ -314,11 +333,12 @@ export class ASRouterUISurface extends React.PureComponent {
ReactDOM.createPortal( ReactDOM.createPortal(
<> <>
{this.renderPreviewBanner()} {this.renderPreviewBanner()}
{this.renderTrailhead()}
{this.renderFirstRunOverlay()} {this.renderFirstRunOverlay()}
{this.renderOnboarding()} {this.renderOnboarding()}
{this.renderSnippets()} {this.renderSnippets()}
</>, </>,
this.portalContainer shouldRenderInHeader ? this.headerPortal : this.footerPortal
); );
} }
} }

Просмотреть файл

@ -1,30 +1,51 @@
import React from "react"; import React from "react";
export class ModalOverlay extends React.PureComponent { export class ModalOverlayWrapper extends React.PureComponent {
constructor(props) {
super(props);
this.onKeyDown = this.onKeyDown.bind(this);
}
onKeyDown(event) {
if (event.key === "Escape") {
this.props.onClose();
}
}
componentWillMount() { componentWillMount() {
this.setState({active: true}); this.props.document.addEventListener("keydown", this.onKeyDown);
document.body.classList.add("modal-open"); this.props.document.body.classList.add("modal-open");
} }
componentWillUnmount() { componentWillUnmount() {
document.body.classList.remove("modal-open"); this.props.document.removeEventListener("keydown", this.onKeyDown);
this.setState({active: false}); this.props.document.body.classList.remove("modal-open");
} }
render() { render() {
const {active} = this.state; const {props} = this;
return (<React.Fragment>
<div className="modalOverlayOuter active" onClick={props.onClose} role="presentation" />
<div className={`modalOverlayInner active ${props.innerClassName || ""}`}>
{props.children}
</div>
</React.Fragment>);
}
}
ModalOverlayWrapper.defaultProps = {document: global.document};
export class ModalOverlay extends React.PureComponent {
render() {
const {title, button_label} = this.props; const {title, button_label} = this.props;
return ( return (
<div> <ModalOverlayWrapper onClose={this.props.onDoneButton}>
<div className={`modalOverlayOuter ${active ? "active" : ""}`} />
<div className={`modalOverlayInner ${active ? "active" : ""}`}>
<h2> {title} </h2> <h2> {title} </h2>
{this.props.children} {this.props.children}
<div className="footer"> <div className="footer">
<button tabIndex="2" onClick={this.props.onDoneButton} className="button primary modalButton"> {button_label} </button> <button className="button primary modalButton"
onClick={this.props.onDoneButton}> {button_label} </button>
</div> </div>
</div> </ModalOverlayWrapper>);
</div>
);
} }
} }

Просмотреть файл

@ -5,8 +5,7 @@
} }
.modalOverlayOuter { .modalOverlayOuter {
background: $white; background: var(--newtab-overlay-color);
opacity: 0.93;
height: 100%; height: 100%;
position: fixed; position: fixed;
top: 0; top: 0;

Просмотреть файл

@ -28,6 +28,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [sync](#sync) * [sync](#sync)
* [topFrecentSites](#topfrecentsites) * [topFrecentSites](#topfrecentsites)
* [totalBookmarksCount](#totalbookmarkscount) * [totalBookmarksCount](#totalbookmarkscount)
* [trailheadCohort](#trailheadcohort)
* [usesFirefoxSync](#usesfirefoxsync) * [usesFirefoxSync](#usesfirefoxsync)
* [xpinstallEnabled](#xpinstallEnabled) * [xpinstallEnabled](#xpinstallEnabled)
* [hasPinnedTabs](#haspinnedtabs) * [hasPinnedTabs](#haspinnedtabs)
@ -424,6 +425,11 @@ Total number of bookmarks.
declare const totalBookmarksCount: number; declare const totalBookmarksCount: number;
``` ```
### `trailheadCohort`
(67+ only) Experiment cohort for special trailhead project
### `usesFirefoxSync` ### `usesFirefoxSync`
Does the user use Firefox sync? Does the user use Firefox sync?

Просмотреть файл

@ -1,7 +1,13 @@
import {ModalOverlay} from "../../components/ModalOverlay/ModalOverlay"; import {ModalOverlay} from "../../components/ModalOverlay/ModalOverlay";
import React from "react"; import React from "react";
class OnboardingCard extends React.PureComponent { const FLUENT_FILES = [
"branding/brand.ftl",
"browser/branding/sync-brand.ftl",
"browser/newtab/onboarding.ftl",
];
export class OnboardingCard extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.onClick = this.onClick.bind(this); this.onClick = this.onClick.bind(this);
@ -20,16 +26,19 @@ class OnboardingCard extends React.PureComponent {
render() { render() {
const {content} = this.props; const {content} = this.props;
const className = this.props.className || "onboardingMessage";
return ( return (
<div className="onboardingMessage"> <div className={className}>
<div className={`onboardingMessageImage ${content.icon}`} /> <div className={`onboardingMessageImage ${content.icon}`} />
<div className="onboardingContent"> <div className="onboardingContent">
<span> <span>
<h3> {content.title} </h3> <h3 className="onboardingTitle" data-l10n-id={content.title.string_id} />
<p> {content.text} </p> <p className="onboardingText" data-l10n-id={content.text.string_id} />
</span> </span>
<span> <span className="onboardingButtonContainer">
<button tabIndex="1" className="button onboardingButton" onClick={this.onClick}> {content.primary_button.label} </button> <button data-l10n-id={content.primary_button.label.string_id}
className="button onboardingButton"
onClick={this.onClick} />
</span> </span>
</div> </div>
</div> </div>
@ -38,6 +47,14 @@ class OnboardingCard extends React.PureComponent {
} }
export class OnboardingMessage extends React.PureComponent { export class OnboardingMessage extends React.PureComponent {
componentWillMount() {
FLUENT_FILES.forEach(file => {
const link = document.head.appendChild(document.createElement("link"));
link.href = file;
link.rel = "localization";
});
}
render() { render() {
const {props} = this; const {props} = this;
const {button_label, header} = props.extraTemplateStrings; const {button_label, header} = props.extraTemplateStrings;

Просмотреть файл

@ -55,43 +55,6 @@
height: 250px; height: 250px;
} }
.onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle;
@media(max-width: 850px) {
height: 75px;
min-width: 80px;
background-size: 80px;
}
&.addons {
background-image: url('resource://activity-stream/data/content/assets/illustration-addons@2x.png');
}
&.privatebrowsing {
background-image: url('resource://activity-stream/data/content/assets/illustration-privatebrowsing@2x.png');
}
&.screenshots {
background-image: url('resource://activity-stream/data/content/assets/illustration-screenshots@2x.png');
}
&.gift {
background-image: url('resource://activity-stream/data/content/assets/illustration-gift@2x.png');
}
&.sync {
background-image: url('resource://activity-stream/data/content/assets/illustration-sync@2x.png');
}
}
.onboardingContent { .onboardingContent {
height: 175px; height: 175px;
@ -164,3 +127,84 @@
content: none; content: none;
} }
} }
// Also used for Trailhead
.onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle;
@media(max-width: 850px) {
height: 75px;
min-width: 80px;
background-size: 80px;
}
&.addons {
background-image: url('#{$image-path}illustration-addons@2x.png');
}
&.privatebrowsing {
background-image: url('#{$image-path}illustration-privatebrowsing@2x.png');
}
&.screenshots {
background-image: url('#{$image-path}illustration-screenshots@2x.png');
}
&.gift {
background-image: url('#{$image-path}illustration-gift@2x.png');
}
&.sync {
background-image: url('#{$image-path}illustration-sync@2x.png');
}
&.devices {
background-image: url('#{$image-path}trailhead/card-illo-devices.png');
}
&.fbcont {
background-image: url('#{$image-path}trailhead/card-illo-fbcont.png');
}
&.ffmonitor {
background-image: url('#{$image-path}trailhead/card-illo-ffmonitor.png');
}
&.ffsend {
background-image: url('#{$image-path}trailhead/card-illo-ffsend.png');
}
&.lockwise {
background-image: url('#{$image-path}trailhead/card-illo-lockwise.png');
}
&.mobile {
background-image: url('#{$image-path}trailhead/card-illo-mobile.png');
}
&.pledge {
background-image: url('#{$image-path}trailhead/card-illo-pledge.png');
}
&.pocket {
background-image: url('#{$image-path}trailhead/card-illo-pocket.png');
}
&.private {
background-image: url('#{$image-path}trailhead/card-illo-private.png');
}
&.sendtab {
background-image: url('#{$image-path}trailhead/card-illo-sendtab.png');
}
&.tracking {
background-image: url('#{$image-path}trailhead/card-illo-tracking.png');
}
}

Просмотреть файл

@ -0,0 +1,184 @@
import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import {ModalOverlayWrapper} from "../../components/ModalOverlay/ModalOverlay";
import {OnboardingCard} from "../OnboardingMessage/OnboardingMessage";
import React from "react";
const FLUENT_FILES = [
"branding/brand.ftl",
"browser/branding/sync-brand.ftl",
// These are finalized strings exposed to localizers
"browser/newtab/onboarding.ftl",
// These are WIP/in-development strings that only get used if the string
// doesn't already exist in onboarding.ftl above
"trailhead.ftl",
];
export class Trailhead extends React.PureComponent {
constructor(props) {
super(props);
this.closeModal = this.closeModal.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onInputInvalid = this.onInputInvalid.bind(this);
this.state = {
emailInput: "",
isModalOpen: true,
flowId: "",
flowBeginTime: 0,
};
this.didFetch = false;
}
async componentWillMount() {
FLUENT_FILES.forEach(file => {
const link = document.head.appendChild(document.createElement("link"));
link.href = file;
link.rel = "localization";
});
if (this.props.fxaEndpoint && !this.didFetch) {
try {
this.didFetch = true;
const fxaParams = "entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun&utm_term=trailhead&form_type=email";
const response = await fetch(`${this.props.fxaEndpoint}/metrics-flow?${fxaParams}`, {credentials: "omit"});
if (response.status === 200) {
const {flowId, flowBeginTime} = await response.json();
this.setState({flowId, flowBeginTime});
} else {
this.props.dispatch(ac.OnlyToMain({type: at.TELEMETRY_UNDESIRED_EVENT, data: {event: "FXA_METRICS_FETCH_ERROR", value: response.status}}));
}
} catch (error) {
this.props.dispatch(ac.OnlyToMain({type: at.TELEMETRY_UNDESIRED_EVENT, data: {event: "FXA_METRICS_ERROR"}}));
}
}
}
componentDidMount() {
// We need to remove hide-main since we should show it underneath everything that has rendered
global.document.body.classList.remove("hide-main");
// Add inline-onboarding class to disable fixed search header and fixed positioned settings icon
global.document.body.classList.add("inline-onboarding");
}
componentDidUnmount() {
global.document.body.classList.remove("inline-onboarding");
}
onInputChange(e) {
let error = e.target.previousSibling;
this.setState({emailInput: e.target.value});
error.classList.remove("active");
e.target.classList.remove("invalid");
}
onSubmit() {
this.props.dispatch(ac.UserEvent({event: "SUBMIT_EMAIL", ...this._getFormInfo()}));
global.addEventListener("visibilitychange", this.closeModal);
}
closeModal() {
global.removeEventListener("visibilitychange", this.closeModal);
global.document.body.classList.remove("welcome");
this.setState({isModalOpen: false});
this.props.dispatch(ac.UserEvent({event: "SKIPPED_SIGNIN", ...this._getFormInfo()}));
}
/**
* Report to telemetry additional information about the form submission.
*/
_getFormInfo() {
const value = {has_flow_params: this.state.flowId.length > 0};
return {value};
}
onInputInvalid(e) {
let error = e.target.previousSibling;
error.classList.add("active");
e.target.classList.add("invalid");
e.preventDefault(); // Override built-in form validation popup
e.target.focus();
}
render() {
const {props} = this;
const {bundle: cards, content} = props.message;
return (<>
{this.state.isModalOpen && content ? <ModalOverlayWrapper innerClassName={`trailhead ${content.className}`} onClose={this.closeModal}>
<div className="trailheadInner">
<div className="trailheadContent">
<h1 data-l10n-id={content.title.string_id}>{content.title.value}</h1>
{content.subtitle &&
<p data-l10n-id={content.subtitle.string_id}>{content.subtitle.value}</p>
}
<ul className="trailheadBenefits">
{content.benefits.map(item => (
<li key={item.id} className={item.id}>
<h3 data-l10n-id={item.title.string_id}>{item.title.value}</h3>
<p data-l10n-id={item.text.string_id}>{item.text.value}</p>
</li>
))}
</ul>
<a className="trailheadLearn" data-l10n-id={content.learn.text.string_id} href={content.learn.url}>
{content.learn.text.value}
</a>
</div>
<div className="trailheadForm">
<h3 data-l10n-id={content.form.title.string_id}>{content.form.title.value}</h3>
<p data-l10n-id={content.form.text.string_id}>{content.form.text.value}</p>
<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="trailhead" />
<input name="flow_id" type="hidden" value={this.state.flowId} />
<input name="flow_begin_time" type="hidden" value={this.state.flowBeginTime} />
<p data-l10n-id="onboarding-join-form-email-error" className="error" />
<input
data-l10n-id={content.form.email.string_id}
placeholder={content.form.email.placeholder}
name="email"
type="email"
required="true"
onInvalid={this.onInputInvalid}
onChange={this.onInputChange} />
<p className="trailheadTerms" data-l10n-id="onboarding-join-form-legal">
<a data-l10n-name="terms"
href="https://accounts.firefox.com/legal/terms" />
<a data-l10n-name="privacy"
href="https://accounts.firefox.com/legal/privacy" />
</p>
<button data-l10n-id={content.form.button.string_id} type="submit">
{content.form.button.value}
</button>
</form>
</div>
</div>
<button className="trailheadStart"
data-l10n-id={content.skipButton.string_id}
onClick={this.closeModal}>{content.skipButton.value}</button>
</ModalOverlayWrapper> : null}
{(cards && cards.length) ? <div className="trailheadCards">
<div className="trailheadCardsInner">
<h1 data-l10n-id="onboarding-welcome-header" />
<div className="trailheadCardGrid">
{cards.map(card => (
<OnboardingCard key={card.id}
className="trailheadCard"
sendUserActionTelemetry={props.sendUserActionTelemetry}
onAction={props.onAction}
UISurface="TRAILHEAD"
{...card} />
))}
</div>
</div>
</div> : null}
</>);
}
}

Просмотреть файл

@ -0,0 +1,337 @@
.trailhead {
$benefit-icon-size: 62px;
$benefit-icon-spacing: $benefit-icon-size + 12px;
background: url('#{$image-path}trailhead/accounts-form-bg.jpg') bottom / cover;
color: $white;
height: auto;
top: 100px;
@media (max-height: 700px) {
position: absolute;
top: 20px;
}
a {
color: $white;
text-decoration: underline;
}
input,
button {
border-radius: 4px;
padding: 10px;
}
.trailheadInner {
$content-spacing: 40px;
display: grid;
grid-column-gap: $content-spacing;
grid-template-columns: 5fr 3fr;
padding: $content-spacing 60px;
}
.trailheadContent {
h1 {
font-size: 36px;
font-weight: 200;
line-height: 46px;
margin: 0;
}
.trailheadLearn {
display: block;
margin-top: 30px;
margin-inline-start: $benefit-icon-spacing;
}
}
&.syncCohort {
left: calc(50% - 430px);
width: 860px;
@media (max-width: 860px) {
left: 0;
width: 100%;
}
.trailheadInner {
grid-template-columns: 4fr 3fr;
}
.trailheadContent {
.trailheadBenefits {
background: url('#{$image-path}sync-devices.svg');
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
height: 200px;
margin-inline-end: 60px;
}
.trailheadLearn {
margin-inline-start: 0;
}
}
}
.trailheadBenefits {
padding: 0;
li {
background-position: left 4px;
background-repeat: no-repeat;
background-size: $benefit-icon-size;
-moz-context-properties: fill;
fill: $blue-50;
list-style: none;
padding-inline-start: $benefit-icon-spacing;
&:dir(rtl) {
background-position-x: right;
}
&.knowledge {
background-image: url('#{$image-path}trailhead/benefit-knowledge.png');
}
&.privacy {
background-image: url('#{$image-path}trailhead/benefit-privacy.png');
}
&.products {
background-image: url('#{$image-path}trailhead/benefit-products.png');
}
}
h3 {
color: $violet-20;
font-size: 22px;
font-weight: 400;
margin-bottom: 4px;
}
p {
color: $white;
font-size: 15px;
line-height: 22px;
margin: 4px 0 15px;
margin-inline-end: 60px;
}
}
.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 {
border: 0;
width: 100%;
}
input {
background-color: $white;
color: $grey-70;
font-size: 15px;
}
button {
background-color: $blue-60;
cursor: pointer;
display: block;
font-size: 15px;
font-weight: 400;
padding: 14px;
&:hover,
&:focus {
background-color: $trailhead-blue-60;
}
&:focus {
outline: dotted 1px;
}
}
}
.trailheadStart {
border: 1px solid $white-50;
cursor: pointer;
display: block;
font-size: 15px;
font-weight: 400;
margin: 0 auto 40px;
min-width: 300px;
padding: 14px;
&:hover,
&:focus {
background-color: $trailhead-blue-60;
border-color: transparent;
}
&:focus {
outline: dotted 1px;
}
}
}
.trailheadCards {
background: var(--trailhead-cards-background-color);
text-align: center;
h1 {
font-size: 36px;
font-weight: 200;
margin: 0 0 40px;
color: var(--trailhead-header-text-color);
}
}
.trailheadCardsInner {
margin: auto;
padding: 40px $section-horizontal-padding;
@media (min-width: $break-point-medium) {
width: $wrapper-max-width-medium;
}
@media (min-width: $break-point-large) {
width: $wrapper-max-width-large;
}
@media (min-width: $break-point-widest) {
width: $wrapper-max-width-widest;
}
}
.trailheadCardGrid {
display: grid;
grid-gap: $base-gutter;
margin: 0;
@media (min-width: $break-point-medium) {
grid-template-columns: repeat(auto-fit, $card-width);
}
@media (min-width: $break-point-widest) {
grid-template-columns: repeat(auto-fit, $card-width-large);
}
}
.trailheadCard {
position: relative;
background: var(--newtab-card-background-color);
border-radius: 4px;
box-shadow: var(--newtab-card-shadow);
font-size: 13px;
padding: 20px;
@media (min-width: $break-point-widest) {
font-size: 15px;
padding: 40px;
}
.onboardingTitle {
font-weight: normal;
color: var(--newtab-text-primary-color);
margin: 10px 0 4px;
font-size: 15px;
@media (min-width: $break-point-widest) {
font-size: 18px;
}
}
.onboardingText {
margin: 0 0 60px;
color: var(--newtab-text-conditional-color);
line-height: 1.5;
font-weight: 200;
}
.onboardingButton {
color: var(--newtab-text-conditional-color);
background: var(--trailhead-card-button-background-color);
border: 0;
height: 30px;
min-width: 70%;
padding: 0 14px;
&:focus,
&:hover {
box-shadow: none;
background: var(--trailhead-card-button-background-hover-color);
}
}
.onboardingButtonContainer {
height: 60px;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
}
}
.inline-onboarding {
.outer-wrapper {
position: relative;
.prefs-button {
button {
position: absolute;
}
}
}
}
// If the window is too short, we need to allow scrolling so user can get to Start Browsing button.
@media (max-height: 700px) {
.activity-stream.welcome.inline-onboarding {
overflow: auto;
}
}

Просмотреть файл

@ -72,6 +72,7 @@ export class _Base extends React.PureComponent {
// we don't want to add them back to the Activity Stream view // we don't want to add them back to the Activity Stream view
document.body.classList.contains("welcome") ? "welcome" : "", document.body.classList.contains("welcome") ? "welcome" : "",
document.body.classList.contains("hide-main") ? "hide-main" : "", document.body.classList.contains("hide-main") ? "hide-main" : "",
document.body.classList.contains("inline-onboarding") ? "inline-onboarding" : "",
].filter(v => v).join(" "); ].filter(v => v).join(" ");
global.document.body.className = bodyClassName; global.document.body.className = bodyClassName;
} }
@ -158,7 +159,7 @@ export class BaseContent extends React.PureComponent {
</ErrorBoundary> </ErrorBoundary>
</div> </div>
} }
<ASRouterUISurface dispatch={this.props.dispatch} /> <ASRouterUISurface fxaEndpoint={this.props.Prefs.values.fxa_endpoint} dispatch={this.props.dispatch} />
<div className={`body-wrapper${(initialized ? " on" : "")}`}> <div className={`body-wrapper${(initialized ? " on" : "")}`}>
{isDiscoveryStream ? ( {isDiscoveryStream ? (
<ErrorBoundary className="borderless-error"> <ErrorBoundary className="borderless-error">

Просмотреть файл

@ -1,3 +1,4 @@
import {actionCreators as ac} from "common/Actions.jsm";
import {CardGrid} from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid"; import {CardGrid} from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid";
import {CollapsibleSection} from "content-src/components/CollapsibleSection/CollapsibleSection"; import {CollapsibleSection} from "content-src/components/CollapsibleSection/CollapsibleSection";
import {connect} from "react-redux"; import {connect} from "react-redux";
@ -168,12 +169,19 @@ export class _DiscoveryStreamBase extends React.PureComponent {
render() { render() {
// Select layout render data by adding spocs and position to recommendations // Select layout render data by adding spocs and position to recommendations
const layoutRender = selectLayoutRender(this.props.DiscoveryStream, this.props.Prefs.values, rickRollCache); const {layoutRender, spocsFill} = selectLayoutRender(this.props.DiscoveryStream, this.props.Prefs.values, rickRollCache);
const {config, feeds, spocs} = this.props.DiscoveryStream; const {config, feeds, spocs} = this.props.DiscoveryStream;
if (!spocs.loaded || !feeds.loaded) { if (!spocs.loaded || !feeds.loaded) {
return null; return null;
} }
// Send SPOCS Fill if any. Note that it should not send it again if the same
// page gets re-rendered by state changes.
if (spocsFill.length && !this._spocsFillSent) {
this.props.dispatch(ac.DiscoveryStreamSpocsFill({spoc_fills: spocsFill}));
this._spocsFillSent = true;
}
// Allow rendering without extracting special components // Allow rendering without extracting special components
if (!config.collapsible) { if (!config.collapsible) {
return this.renderLayout(layoutRender); return this.renderLayout(layoutRender);

Просмотреть файл

@ -15,6 +15,10 @@ $col4-header-font-size: 14;
border-radius: 4px; border-radius: 4px;
} }
.ds-card-link:focus {
@include ds-fade-in;
}
&.ds-card-grid-border { &.ds-card-grid-border {
.ds-card:not(.placeholder) { .ds-card:not(.placeholder) {
@include dark-theme-only { @include dark-theme-only {

Просмотреть файл

@ -62,6 +62,10 @@ $excerpt-line-height: 20;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; height: 100%;
&:focus {
@include ds-fade-in;
}
} }
.meta { .meta {

Просмотреть файл

@ -67,6 +67,10 @@ $card-header-in-hero-line-height: 20;
padding-top: 16px; padding-top: 16px;
height: 100%; height: 100%;
&:focus {
@include ds-fade-in;
}
@at-root .ds-hero-no-border .ds-hero-item .wrapper { @at-root .ds-hero-no-border .ds-hero-item .wrapper {
border-top: 0; border-top: 0;
border-bottom: 0; border-bottom: 0;

Просмотреть файл

@ -86,6 +86,10 @@ $item-line-height: 20;
} }
} }
.ds-list-item-link:focus {
@include ds-fade-in;
}
.ds-list-numbers { .ds-list-numbers {
$counter-whitespace: ($item-line-height - $item-font-size) * 1px; $counter-whitespace: ($item-line-height - $item-font-size) * 1px;
$counter-size: 32px; $counter-size: 32px;

Просмотреть файл

@ -8,6 +8,10 @@
.top-site-outer { .top-site-outer {
padding: 0 12px; padding: 0 12px;
.top-site-inner > a:-moz-any(.active, :focus) .tile {
@include ds-fade-in;
}
} }
.top-sites-list { .top-sites-list {

Просмотреть файл

@ -246,7 +246,7 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
} }
@media (min-height: 701px) { @media (min-height: 701px) {
.fixed-search { body:not(.inline-onboarding) .fixed-search {
main { main {
padding-top: 146px; padding-top: 146px;
} }

Просмотреть файл

@ -2,6 +2,9 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
const {layout, feeds, spocs} = state; const {layout, feeds, spocs} = state;
let spocIndex = 0; let spocIndex = 0;
let bufferRollCache = []; let bufferRollCache = [];
// Records the chosen and unchosen spocs by the probability selection.
let chosenSpocs = new Set();
let unchosenSpocs = new Set();
// rickRollCache stores random probability values for each spoc position. This cache is empty // rickRollCache stores random probability values for each spoc position. This cache is empty
// on page refresh and gets filled with random values on first render inside maybeInjectSpocs. // on page refresh and gets filled with random values on first render inside maybeInjectSpocs.
@ -13,6 +16,11 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
spocs.data.spocs && spocs.data.spocs.length) { spocs.data.spocs && spocs.data.spocs.length) {
const recommendations = [...data.recommendations]; const recommendations = [...data.recommendations];
for (let position of spocsConfig.positions) { for (let position of spocsConfig.positions) {
const spoc = spocs.data.spocs[spocIndex];
if (!spoc) {
break;
}
// Cache random number for a position // Cache random number for a position
let rickRoll; let rickRoll;
if (isFirstRun) { if (isFirstRun) {
@ -23,8 +31,12 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
bufferRollCache.push(rickRoll); bufferRollCache.push(rickRoll);
} }
if (spocs.data.spocs[spocIndex] && rickRoll <= spocsConfig.probability) { if (rickRoll <= spocsConfig.probability) {
recommendations.splice(position.index, 0, spocs.data.spocs[spocIndex++]); spocIndex++;
recommendations.splice(position.index, 0, spoc);
chosenSpocs.add(spoc);
} else {
unchosenSpocs.add(spoc);
} }
} }
@ -51,7 +63,7 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
filterArray.push(...DS_COMPONENTS); filterArray.push(...DS_COMPONENTS);
} }
return layout.map(row => ({ const layoutRender = layout.map(row => ({
...row, ...row,
// Loops through desired components and adds a .data property // Loops through desired components and adds a .data property
@ -94,4 +106,28 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
return {...component, data}; return {...component, data};
}), }),
})).filter(row => row.components.length); })).filter(row => row.components.length);
// Generate the payload for the SPOCS Fill ping. Note that a SPOC could be rejected
// by the `probability_selection` first, then gets chosen for the next position. For
// all other SPOCS that never went through the probabilistic selection, its reason will
// be "out_of_position".
let spocsFill = [];
if (spocs.data && spocs.data.spocs) {
const chosenSpocsFill = [...chosenSpocs]
.map(spoc => ({id: spoc.id, reason: "n/a", displayed: 1, full_recalc: 0}));
const unchosenSpocsFill = [...unchosenSpocs]
.filter(spoc => !chosenSpocs.has(spoc))
.map(spoc => ({id: spoc.id, reason: "probability_selection", displayed: 0, full_recalc: 0}));
const outOfPositionSpocsFill = spocs.data.spocs.slice(spocIndex)
.filter(spoc => !unchosenSpocs.has(spoc))
.map(spoc => ({id: spoc.id, reason: "out_of_position", displayed: 0, full_recalc: 0}));
spocsFill = [
...chosenSpocsFill,
...unchosenSpocsFill,
...outOfPositionSpocsFill,
];
}
return {spocsFill, layoutRender};
}; };

Просмотреть файл

@ -170,3 +170,4 @@ input {
@import '../asrouter/templates/OnboardingMessage/OnboardingMessage'; @import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
@import '../asrouter/templates/EOYSnippet/EOYSnippet'; @import '../asrouter/templates/EOYSnippet/EOYSnippet';
@import '../asrouter/templates/StartupOverlay/StartupOverlay'; @import '../asrouter/templates/StartupOverlay/StartupOverlay';
@import '../asrouter/templates/Trailhead/Trailhead';

Просмотреть файл

@ -41,3 +41,10 @@
border-bottom: 1px solid $grey-30; border-bottom: 1px solid $grey-30;
} }
@mixin ds-fade-in {
box-shadow: 0 0 0 1px $blue-50 inset, 0 0 0 1px $blue-50, 0 0 0 5px $blue-50-30;
transition: box-shadow 150ms;
border-radius: 4px;
outline: none;
}

Просмотреть файл

@ -78,6 +78,12 @@ body {
--newtab-snippets-background-color: #{$white}; --newtab-snippets-background-color: #{$white};
--newtab-snippets-hairline-color: transparent; --newtab-snippets-hairline-color: transparent;
// Trailhead
--trailhead-header-text-color: #{$trailhead-purple};
--trailhead-cards-background-color: #{$grey-20};
--trailhead-card-button-background-color: #{$grey-90-10};
--trailhead-card-button-background-hover-color: #{$grey-90-20};
&[lwt-newtab-brighttext] { &[lwt-newtab-brighttext] {
// General styles // General styles
--newtab-background-color: #{$grey-80}; --newtab-background-color: #{$grey-80};
@ -136,5 +142,11 @@ body {
// Snippets // Snippets
--newtab-snippets-background-color: #{$grey-70}; --newtab-snippets-background-color: #{$grey-70};
--newtab-snippets-hairline-color: #{$white-10}; --newtab-snippets-hairline-color: #{$white-10};
// Trailhead
--trailhead-header-text-color: #{$white-60};
--trailhead-cards-background-color: #{$grey-90-10};
--trailhead-card-button-background-color: #{$grey-90-30};
--trailhead-card-button-background-hover-color: #{$grey-90-40};
} }
} }

Просмотреть файл

@ -19,6 +19,7 @@ $teal-70: #008EA4;
$teal-80: #005A71; $teal-80: #005A71;
$red-60: #D70022; $red-60: #D70022;
$yellow-50: #FFE900; $yellow-50: #FFE900;
$violet-20: #CB9EFF;
// Photon opacity from http://design.firefox.com/photon/visuals/color.html#opacity // Photon opacity from http://design.firefox.com/photon/visuals/color.html#opacity
$grey-10-10: rgba($grey-10, 0.1); $grey-10-10: rgba($grey-10, 0.1);
@ -45,6 +46,8 @@ $grey-90-70: rgba($grey-90, 0.7);
$grey-90-80: rgba($grey-90, 0.8); $grey-90-80: rgba($grey-90, 0.8);
$grey-90-90: rgba($grey-90, 0.9); $grey-90-90: rgba($grey-90, 0.9);
$blue-50-30: rgba($blue-50, 0.3);
$black: #000; $black: #000;
$black-5: rgba($black, 0.05); $black-5: rgba($black, 0.05);
$black-10: rgba($black, 0.1); $black-10: rgba($black, 0.1);
@ -57,6 +60,9 @@ $black-30: rgba($black, 0.3);
// Other colors // Other colors
$white: #FFF; $white: #FFF;
$white-10: rgba($white, 0.1); $white-10: rgba($white, 0.1);
$white-50: rgba($white, 0.5);
$white-60: rgba($white, 0.6);
$white-70: rgba($white, 0.7);
$pocket-teal: #50BCB6; $pocket-teal: #50BCB6;
$pocket-red: #EF4056; $pocket-red: #EF4056;
$shadow-10: rgba(12, 12, 13, 0.1); $shadow-10: rgba(12, 12, 13, 0.1);
@ -72,6 +78,8 @@ $about-welcome-gradient: linear-gradient(to bottom, $blue-70 40%, $aw-extra-blue
$about-welcome-extra-links: #676F7E; $about-welcome-extra-links: #676F7E;
$firefox-wordmark-default-color: #363959; $firefox-wordmark-default-color: #363959;
$firefox-wordmark-darktheme-color: $white; $firefox-wordmark-darktheme-color: $white;
$trailhead-purple: #2B2156;
$trailhead-blue-60: #0250BB;
// Photon transitions from http://design.firefox.com/photon/motion/duration-and-easing.html // Photon transitions from http://design.firefox.com/photon/motion/duration-and-easing.html
$photon-easing: cubic-bezier(0.07, 0.95, 0, 1); $photon-easing: cubic-bezier(0.07, 0.95, 0, 1);

Просмотреть файл

@ -67,7 +67,11 @@ body {
--newtab-card-placeholder-color: #D7D7DB; --newtab-card-placeholder-color: #D7D7DB;
--newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); --newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--newtab-snippets-background-color: #FFF; --newtab-snippets-background-color: #FFF;
--newtab-snippets-hairline-color: transparent; } --newtab-snippets-hairline-color: transparent;
--trailhead-header-text-color: #2B2156;
--trailhead-cards-background-color: #EDEDF0;
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.2); }
body[lwt-newtab-brighttext] { body[lwt-newtab-brighttext] {
--newtab-background-color: #2A2A2E; --newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8); --newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -111,7 +115,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F; --newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2); --newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--newtab-snippets-background-color: #38383D; --newtab-snippets-background-color: #38383D;
--newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1); } --newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1);
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4); }
.icon { .icon {
background-position: center center; background-position: center center;
@ -1139,9 +1147,9 @@ main {
visibility: hidden; } } visibility: hidden; } }
@media (min-height: 701px) { @media (min-height: 701px) {
.fixed-search main { body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; } padding-top: 146px; }
.fixed-search .search-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color); background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color); border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px; height: 95px;
@ -1151,19 +1159,19 @@ main {
top: 0; top: 0;
width: 100%; width: 100%;
z-index: 9; } z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; } height: 35px; }
.fixed-search .search-wrapper input { body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 16px; background-position-x: 16px;
background-size: 16px; } background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } background-position-x: right 16px; }
.fixed-search .search-handoff-button { body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px; background-position-x: 12px;
background-size: 24px; } background-size: 24px; }
.fixed-search .search-handoff-button:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-handoff-button:dir(rtl) {
background-position-x: right 12px; } background-position-x: right 12px; }
.fixed-search .search-handoff-button .fake-caret { body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } } top: 10px; } }
.contentSearchSuggestionTable { .contentSearchSuggestionTable {
@ -1876,6 +1884,11 @@ main {
border-radius: 4px; } border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card { [lwt-newtab-brighttext] .ds-card-grid .ds-card {
background: none; } background: none; }
.ds-card-grid .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); } box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
@ -1969,6 +1982,11 @@ main {
border-top: 1px solid #4A4A4F; } border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper { [lwt-newtab-brighttext] .ds-hero .wrapper {
color: #D7D7DB; } color: #D7D7DB; }
.ds-hero .wrapper:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-hero-no-border .ds-hero-item .wrapper { .ds-hero-no-border .ds-hero-item .wrapper {
border-top: 0; border-top: 0;
border-bottom: 0; border-bottom: 0;
@ -2204,6 +2222,12 @@ main {
[lwt-newtab-brighttext] .ds-list a { [lwt-newtab-brighttext] .ds-list a {
color: #F9F9FA; } color: #F9F9FA; }
.ds-list-item-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-list-numbers .ds-list-item { .ds-list-numbers .ds-list-item {
counter-increment: list; } counter-increment: list; }
@ -2403,6 +2427,11 @@ main {
margin: 0 -25px; } margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer { .ds-top-sites .top-sites .top-site-outer {
padding: 0 12px; } padding: 0 12px; }
.ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-top-sites .top-sites .top-sites-list { .ds-top-sites .top-sites .top-sites-list {
margin: 0 -12px; } margin: 0 -12px; }
@ -2572,6 +2601,11 @@ main {
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; } height: 100%; }
.ds-card .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card .meta { .ds-card .meta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -2799,8 +2833,7 @@ main {
overflow: hidden; } overflow: hidden; }
.modalOverlayOuter { .modalOverlayOuter {
background: #FFF; background: var(--newtab-overlay-color);
opacity: 0.93;
height: 100%; height: 100%;
position: fixed; position: fixed;
top: 0; top: 0;
@ -3264,29 +3297,6 @@ main {
@media (max-width: 650px) { @media (max-width: 650px) {
.onboardingMessage { .onboardingMessage {
height: 250px; } } height: 250px; } }
.onboardingMessage .onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessage .onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessage .onboardingMessageImage.addons {
background-image: url("resource://activity-stream/data/content/assets/illustration-addons@2x.png"); }
.onboardingMessage .onboardingMessageImage.privatebrowsing {
background-image: url("resource://activity-stream/data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessage .onboardingMessageImage.screenshots {
background-image: url("resource://activity-stream/data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessage .onboardingMessageImage.gift {
background-image: url("resource://activity-stream/data/content/assets/illustration-gift@2x.png"); }
.onboardingMessage .onboardingMessageImage.sync {
background-image: url("resource://activity-stream/data/content/assets/illustration-sync@2x.png"); }
.onboardingMessage .onboardingContent { .onboardingMessage .onboardingContent {
height: 175px; } height: 175px; }
.onboardingMessage .onboardingContent > span > h3 { .onboardingMessage .onboardingContent > span > h3 {
@ -3337,6 +3347,52 @@ main {
.onboardingMessage:last-child::before { .onboardingMessage:last-child::before {
content: none; } content: none; }
.onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessageImage.addons {
background-image: url("../data/content/assets/illustration-addons@2x.png"); }
.onboardingMessageImage.privatebrowsing {
background-image: url("../data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessageImage.screenshots {
background-image: url("../data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessageImage.gift {
background-image: url("../data/content/assets/illustration-gift@2x.png"); }
.onboardingMessageImage.sync {
background-image: url("../data/content/assets/illustration-sync@2x.png"); }
.onboardingMessageImage.devices {
background-image: url("../data/content/assets/trailhead/card-illo-devices.png"); }
.onboardingMessageImage.fbcont {
background-image: url("../data/content/assets/trailhead/card-illo-fbcont.png"); }
.onboardingMessageImage.ffmonitor {
background-image: url("../data/content/assets/trailhead/card-illo-ffmonitor.png"); }
.onboardingMessageImage.ffsend {
background-image: url("../data/content/assets/trailhead/card-illo-ffsend.png"); }
.onboardingMessageImage.lockwise {
background-image: url("../data/content/assets/trailhead/card-illo-lockwise.png"); }
.onboardingMessageImage.mobile {
background-image: url("../data/content/assets/trailhead/card-illo-mobile.png"); }
.onboardingMessageImage.pledge {
background-image: url("../data/content/assets/trailhead/card-illo-pledge.png"); }
.onboardingMessageImage.pocket {
background-image: url("../data/content/assets/trailhead/card-illo-pocket.png"); }
.onboardingMessageImage.private {
background-image: url("../data/content/assets/trailhead/card-illo-private.png"); }
.onboardingMessageImage.sendtab {
background-image: url("../data/content/assets/trailhead/card-illo-sendtab.png"); }
.onboardingMessageImage.tracking {
background-image: url("../data/content/assets/trailhead/card-illo-tracking.png"); }
.EOYSnippetForm { .EOYSnippetForm {
margin: 10px 0 8px; margin: 10px 0 8px;
align-self: start; align-self: start;
@ -3631,3 +3687,224 @@ a.firstrun-link {
100% { 100% {
opacity: 1; opacity: 1;
transform: translateY(0); } } transform: translateY(0); } }
.trailhead {
background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
color: #FFF;
height: auto;
top: 100px; }
@media (max-height: 700px) {
.trailhead {
position: absolute;
top: 20px; } }
.trailhead a {
color: #FFF;
text-decoration: underline; }
.trailhead input,
.trailhead button {
border-radius: 4px;
padding: 10px; }
.trailhead .trailheadInner {
display: grid;
grid-column-gap: 40px;
grid-template-columns: 5fr 3fr;
padding: 40px 60px; }
.trailhead .trailheadContent h1 {
font-size: 36px;
font-weight: 200;
line-height: 46px;
margin: 0; }
.trailhead .trailheadContent .trailheadLearn {
display: block;
margin-top: 30px;
margin-inline-start: 74px; }
.trailhead.syncCohort {
left: calc(50% - 430px);
width: 860px; }
@media (max-width: 860px) {
.trailhead.syncCohort {
left: 0;
width: 100%; } }
.trailhead.syncCohort .trailheadInner {
grid-template-columns: 4fr 3fr; }
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
background: url("../data/content/assets/sync-devices.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
height: 200px;
margin-inline-end: 60px; }
.trailhead.syncCohort .trailheadContent .trailheadLearn {
margin-inline-start: 0; }
.trailhead .trailheadBenefits {
padding: 0; }
.trailhead .trailheadBenefits li {
background-position: left 4px;
background-repeat: no-repeat;
background-size: 62px;
-moz-context-properties: fill;
fill: #0A84FF;
list-style: none;
padding-inline-start: 74px; }
.trailhead .trailheadBenefits li:dir(rtl) {
background-position-x: right; }
.trailhead .trailheadBenefits li.knowledge {
background-image: url("../data/content/assets/trailhead/benefit-knowledge.png"); }
.trailhead .trailheadBenefits li.privacy {
background-image: url("../data/content/assets/trailhead/benefit-privacy.png"); }
.trailhead .trailheadBenefits li.products {
background-image: url("../data/content/assets/trailhead/benefit-products.png"); }
.trailhead .trailheadBenefits h3 {
color: #CB9EFF;
font-size: 22px;
font-weight: 400;
margin-bottom: 4px; }
.trailhead .trailheadBenefits p {
color: #FFF;
font-size: 15px;
line-height: 22px;
margin: 4px 0 15px;
margin-inline-end: 60px; }
.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 {
border: 0;
width: 100%; }
.trailhead .trailheadForm input {
background-color: #FFF;
color: #38383D;
font-size: 15px; }
.trailhead .trailheadForm button {
background-color: #0060DF;
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 .trailheadStart {
border: 1px solid rgba(255, 255, 255, 0.5);
cursor: pointer;
display: block;
font-size: 15px;
font-weight: 400;
margin: 0 auto 40px;
min-width: 300px;
padding: 14px; }
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
background-color: #0250BB;
border-color: transparent; }
.trailhead .trailheadStart:focus {
outline: dotted 1px; }
.trailheadCards {
background: var(--trailhead-cards-background-color);
text-align: center; }
.trailheadCards h1 {
font-size: 36px;
font-weight: 200;
margin: 0 0 40px;
color: var(--trailhead-header-text-color); }
.trailheadCardsInner {
margin: auto;
padding: 40px 25px; }
@media (min-width: 610px) {
.trailheadCardsInner {
width: 530px; } }
@media (min-width: 866px) {
.trailheadCardsInner {
width: 786px; } }
@media (min-width: 1122px) {
.trailheadCardsInner {
width: 1042px; } }
.trailheadCardGrid {
display: grid;
grid-gap: 32px;
margin: 0; }
@media (min-width: 610px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 224px); } }
@media (min-width: 1122px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 309px); } }
.trailheadCard {
position: relative;
background: var(--newtab-card-background-color);
border-radius: 4px;
box-shadow: var(--newtab-card-shadow);
font-size: 13px;
padding: 20px; }
@media (min-width: 1122px) {
.trailheadCard {
font-size: 15px;
padding: 40px; } }
.trailheadCard .onboardingTitle {
font-weight: normal;
color: var(--newtab-text-primary-color);
margin: 10px 0 4px;
font-size: 15px; }
@media (min-width: 1122px) {
.trailheadCard .onboardingTitle {
font-size: 18px; } }
.trailheadCard .onboardingText {
margin: 0 0 60px;
color: var(--newtab-text-conditional-color);
line-height: 1.5;
font-weight: 200; }
.trailheadCard .onboardingButton {
color: var(--newtab-text-conditional-color);
background: var(--trailhead-card-button-background-color);
border: 0;
height: 30px;
min-width: 70%;
padding: 0 14px; }
.trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
box-shadow: none;
background: var(--trailhead-card-button-background-hover-color); }
.trailheadCard .onboardingButtonContainer {
height: 60px;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center; }
.inline-onboarding .outer-wrapper {
position: relative; }
.inline-onboarding .outer-wrapper .prefs-button button {
position: absolute; }
@media (max-height: 700px) {
.activity-stream.welcome.inline-onboarding {
overflow: auto; } }

Просмотреть файл

@ -70,7 +70,11 @@ body {
--newtab-card-placeholder-color: #D7D7DB; --newtab-card-placeholder-color: #D7D7DB;
--newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); --newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--newtab-snippets-background-color: #FFF; --newtab-snippets-background-color: #FFF;
--newtab-snippets-hairline-color: transparent; } --newtab-snippets-hairline-color: transparent;
--trailhead-header-text-color: #2B2156;
--trailhead-cards-background-color: #EDEDF0;
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.2); }
body[lwt-newtab-brighttext] { body[lwt-newtab-brighttext] {
--newtab-background-color: #2A2A2E; --newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8); --newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -114,7 +118,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F; --newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2); --newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--newtab-snippets-background-color: #38383D; --newtab-snippets-background-color: #38383D;
--newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1); } --newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1);
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4); }
.icon { .icon {
background-position: center center; background-position: center center;
@ -1142,9 +1150,9 @@ main {
visibility: hidden; } } visibility: hidden; } }
@media (min-height: 701px) { @media (min-height: 701px) {
.fixed-search main { body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; } padding-top: 146px; }
.fixed-search .search-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color); background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color); border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px; height: 95px;
@ -1154,19 +1162,19 @@ main {
top: 0; top: 0;
width: 100%; width: 100%;
z-index: 9; } z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; } height: 35px; }
.fixed-search .search-wrapper input { body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 16px; background-position-x: 16px;
background-size: 16px; } background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } background-position-x: right 16px; }
.fixed-search .search-handoff-button { body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px; background-position-x: 12px;
background-size: 24px; } background-size: 24px; }
.fixed-search .search-handoff-button:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-handoff-button:dir(rtl) {
background-position-x: right 12px; } background-position-x: right 12px; }
.fixed-search .search-handoff-button .fake-caret { body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } } top: 10px; } }
.contentSearchSuggestionTable { .contentSearchSuggestionTable {
@ -1879,6 +1887,11 @@ main {
border-radius: 4px; } border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card { [lwt-newtab-brighttext] .ds-card-grid .ds-card {
background: none; } background: none; }
.ds-card-grid .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); } box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
@ -1972,6 +1985,11 @@ main {
border-top: 1px solid #4A4A4F; } border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper { [lwt-newtab-brighttext] .ds-hero .wrapper {
color: #D7D7DB; } color: #D7D7DB; }
.ds-hero .wrapper:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-hero-no-border .ds-hero-item .wrapper { .ds-hero-no-border .ds-hero-item .wrapper {
border-top: 0; border-top: 0;
border-bottom: 0; border-bottom: 0;
@ -2207,6 +2225,12 @@ main {
[lwt-newtab-brighttext] .ds-list a { [lwt-newtab-brighttext] .ds-list a {
color: #F9F9FA; } color: #F9F9FA; }
.ds-list-item-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-list-numbers .ds-list-item { .ds-list-numbers .ds-list-item {
counter-increment: list; } counter-increment: list; }
@ -2406,6 +2430,11 @@ main {
margin: 0 -25px; } margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer { .ds-top-sites .top-sites .top-site-outer {
padding: 0 12px; } padding: 0 12px; }
.ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-top-sites .top-sites .top-sites-list { .ds-top-sites .top-sites .top-sites-list {
margin: 0 -12px; } margin: 0 -12px; }
@ -2575,6 +2604,11 @@ main {
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; } height: 100%; }
.ds-card .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card .meta { .ds-card .meta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -2802,8 +2836,7 @@ main {
overflow: hidden; } overflow: hidden; }
.modalOverlayOuter { .modalOverlayOuter {
background: #FFF; background: var(--newtab-overlay-color);
opacity: 0.93;
height: 100%; height: 100%;
position: fixed; position: fixed;
top: 0; top: 0;
@ -3267,29 +3300,6 @@ main {
@media (max-width: 650px) { @media (max-width: 650px) {
.onboardingMessage { .onboardingMessage {
height: 250px; } } height: 250px; } }
.onboardingMessage .onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessage .onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessage .onboardingMessageImage.addons {
background-image: url("resource://activity-stream/data/content/assets/illustration-addons@2x.png"); }
.onboardingMessage .onboardingMessageImage.privatebrowsing {
background-image: url("resource://activity-stream/data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessage .onboardingMessageImage.screenshots {
background-image: url("resource://activity-stream/data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessage .onboardingMessageImage.gift {
background-image: url("resource://activity-stream/data/content/assets/illustration-gift@2x.png"); }
.onboardingMessage .onboardingMessageImage.sync {
background-image: url("resource://activity-stream/data/content/assets/illustration-sync@2x.png"); }
.onboardingMessage .onboardingContent { .onboardingMessage .onboardingContent {
height: 175px; } height: 175px; }
.onboardingMessage .onboardingContent > span > h3 { .onboardingMessage .onboardingContent > span > h3 {
@ -3340,6 +3350,52 @@ main {
.onboardingMessage:last-child::before { .onboardingMessage:last-child::before {
content: none; } content: none; }
.onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessageImage.addons {
background-image: url("../data/content/assets/illustration-addons@2x.png"); }
.onboardingMessageImage.privatebrowsing {
background-image: url("../data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessageImage.screenshots {
background-image: url("../data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessageImage.gift {
background-image: url("../data/content/assets/illustration-gift@2x.png"); }
.onboardingMessageImage.sync {
background-image: url("../data/content/assets/illustration-sync@2x.png"); }
.onboardingMessageImage.devices {
background-image: url("../data/content/assets/trailhead/card-illo-devices.png"); }
.onboardingMessageImage.fbcont {
background-image: url("../data/content/assets/trailhead/card-illo-fbcont.png"); }
.onboardingMessageImage.ffmonitor {
background-image: url("../data/content/assets/trailhead/card-illo-ffmonitor.png"); }
.onboardingMessageImage.ffsend {
background-image: url("../data/content/assets/trailhead/card-illo-ffsend.png"); }
.onboardingMessageImage.lockwise {
background-image: url("../data/content/assets/trailhead/card-illo-lockwise.png"); }
.onboardingMessageImage.mobile {
background-image: url("../data/content/assets/trailhead/card-illo-mobile.png"); }
.onboardingMessageImage.pledge {
background-image: url("../data/content/assets/trailhead/card-illo-pledge.png"); }
.onboardingMessageImage.pocket {
background-image: url("../data/content/assets/trailhead/card-illo-pocket.png"); }
.onboardingMessageImage.private {
background-image: url("../data/content/assets/trailhead/card-illo-private.png"); }
.onboardingMessageImage.sendtab {
background-image: url("../data/content/assets/trailhead/card-illo-sendtab.png"); }
.onboardingMessageImage.tracking {
background-image: url("../data/content/assets/trailhead/card-illo-tracking.png"); }
.EOYSnippetForm { .EOYSnippetForm {
margin: 10px 0 8px; margin: 10px 0 8px;
align-self: start; align-self: start;
@ -3634,3 +3690,224 @@ a.firstrun-link {
100% { 100% {
opacity: 1; opacity: 1;
transform: translateY(0); } } transform: translateY(0); } }
.trailhead {
background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
color: #FFF;
height: auto;
top: 100px; }
@media (max-height: 700px) {
.trailhead {
position: absolute;
top: 20px; } }
.trailhead a {
color: #FFF;
text-decoration: underline; }
.trailhead input,
.trailhead button {
border-radius: 4px;
padding: 10px; }
.trailhead .trailheadInner {
display: grid;
grid-column-gap: 40px;
grid-template-columns: 5fr 3fr;
padding: 40px 60px; }
.trailhead .trailheadContent h1 {
font-size: 36px;
font-weight: 200;
line-height: 46px;
margin: 0; }
.trailhead .trailheadContent .trailheadLearn {
display: block;
margin-top: 30px;
margin-inline-start: 74px; }
.trailhead.syncCohort {
left: calc(50% - 430px);
width: 860px; }
@media (max-width: 860px) {
.trailhead.syncCohort {
left: 0;
width: 100%; } }
.trailhead.syncCohort .trailheadInner {
grid-template-columns: 4fr 3fr; }
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
background: url("../data/content/assets/sync-devices.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
height: 200px;
margin-inline-end: 60px; }
.trailhead.syncCohort .trailheadContent .trailheadLearn {
margin-inline-start: 0; }
.trailhead .trailheadBenefits {
padding: 0; }
.trailhead .trailheadBenefits li {
background-position: left 4px;
background-repeat: no-repeat;
background-size: 62px;
-moz-context-properties: fill;
fill: #0A84FF;
list-style: none;
padding-inline-start: 74px; }
.trailhead .trailheadBenefits li:dir(rtl) {
background-position-x: right; }
.trailhead .trailheadBenefits li.knowledge {
background-image: url("../data/content/assets/trailhead/benefit-knowledge.png"); }
.trailhead .trailheadBenefits li.privacy {
background-image: url("../data/content/assets/trailhead/benefit-privacy.png"); }
.trailhead .trailheadBenefits li.products {
background-image: url("../data/content/assets/trailhead/benefit-products.png"); }
.trailhead .trailheadBenefits h3 {
color: #CB9EFF;
font-size: 22px;
font-weight: 400;
margin-bottom: 4px; }
.trailhead .trailheadBenefits p {
color: #FFF;
font-size: 15px;
line-height: 22px;
margin: 4px 0 15px;
margin-inline-end: 60px; }
.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 {
border: 0;
width: 100%; }
.trailhead .trailheadForm input {
background-color: #FFF;
color: #38383D;
font-size: 15px; }
.trailhead .trailheadForm button {
background-color: #0060DF;
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 .trailheadStart {
border: 1px solid rgba(255, 255, 255, 0.5);
cursor: pointer;
display: block;
font-size: 15px;
font-weight: 400;
margin: 0 auto 40px;
min-width: 300px;
padding: 14px; }
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
background-color: #0250BB;
border-color: transparent; }
.trailhead .trailheadStart:focus {
outline: dotted 1px; }
.trailheadCards {
background: var(--trailhead-cards-background-color);
text-align: center; }
.trailheadCards h1 {
font-size: 36px;
font-weight: 200;
margin: 0 0 40px;
color: var(--trailhead-header-text-color); }
.trailheadCardsInner {
margin: auto;
padding: 40px 25px; }
@media (min-width: 610px) {
.trailheadCardsInner {
width: 530px; } }
@media (min-width: 866px) {
.trailheadCardsInner {
width: 786px; } }
@media (min-width: 1122px) {
.trailheadCardsInner {
width: 1042px; } }
.trailheadCardGrid {
display: grid;
grid-gap: 32px;
margin: 0; }
@media (min-width: 610px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 224px); } }
@media (min-width: 1122px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 309px); } }
.trailheadCard {
position: relative;
background: var(--newtab-card-background-color);
border-radius: 4px;
box-shadow: var(--newtab-card-shadow);
font-size: 13px;
padding: 20px; }
@media (min-width: 1122px) {
.trailheadCard {
font-size: 15px;
padding: 40px; } }
.trailheadCard .onboardingTitle {
font-weight: normal;
color: var(--newtab-text-primary-color);
margin: 10px 0 4px;
font-size: 15px; }
@media (min-width: 1122px) {
.trailheadCard .onboardingTitle {
font-size: 18px; } }
.trailheadCard .onboardingText {
margin: 0 0 60px;
color: var(--newtab-text-conditional-color);
line-height: 1.5;
font-weight: 200; }
.trailheadCard .onboardingButton {
color: var(--newtab-text-conditional-color);
background: var(--trailhead-card-button-background-color);
border: 0;
height: 30px;
min-width: 70%;
padding: 0 14px; }
.trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
box-shadow: none;
background: var(--trailhead-card-button-background-hover-color); }
.trailheadCard .onboardingButtonContainer {
height: 60px;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center; }
.inline-onboarding .outer-wrapper {
position: relative; }
.inline-onboarding .outer-wrapper .prefs-button button {
position: absolute; }
@media (max-height: 700px) {
.activity-stream.welcome.inline-onboarding {
overflow: auto; } }

Просмотреть файл

@ -67,7 +67,11 @@ body {
--newtab-card-placeholder-color: #D7D7DB; --newtab-card-placeholder-color: #D7D7DB;
--newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); --newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--newtab-snippets-background-color: #FFF; --newtab-snippets-background-color: #FFF;
--newtab-snippets-hairline-color: transparent; } --newtab-snippets-hairline-color: transparent;
--trailhead-header-text-color: #2B2156;
--trailhead-cards-background-color: #EDEDF0;
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.2); }
body[lwt-newtab-brighttext] { body[lwt-newtab-brighttext] {
--newtab-background-color: #2A2A2E; --newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8); --newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -111,7 +115,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F; --newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2); --newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--newtab-snippets-background-color: #38383D; --newtab-snippets-background-color: #38383D;
--newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1); } --newtab-snippets-hairline-color: rgba(255, 255, 255, 0.1);
--trailhead-header-text-color: rgba(255, 255, 255, 0.6);
--trailhead-cards-background-color: rgba(12, 12, 13, 0.1);
--trailhead-card-button-background-color: rgba(12, 12, 13, 0.3);
--trailhead-card-button-background-hover-color: rgba(12, 12, 13, 0.4); }
.icon { .icon {
background-position: center center; background-position: center center;
@ -1139,9 +1147,9 @@ main {
visibility: hidden; } } visibility: hidden; } }
@media (min-height: 701px) { @media (min-height: 701px) {
.fixed-search main { body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; } padding-top: 146px; }
.fixed-search .search-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color); background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color); border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px; height: 95px;
@ -1151,19 +1159,19 @@ main {
top: 0; top: 0;
width: 100%; width: 100%;
z-index: 9; } z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper { body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; } height: 35px; }
.fixed-search .search-wrapper input { body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 16px; background-position-x: 16px;
background-size: 16px; } background-size: 16px; }
.fixed-search .search-wrapper input:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-wrapper input:dir(rtl) {
background-position-x: right 16px; } background-position-x: right 16px; }
.fixed-search .search-handoff-button { body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px; background-position-x: 12px;
background-size: 24px; } background-size: 24px; }
.fixed-search .search-handoff-button:dir(rtl) { body:not(.inline-onboarding) .fixed-search .search-handoff-button:dir(rtl) {
background-position-x: right 12px; } background-position-x: right 12px; }
.fixed-search .search-handoff-button .fake-caret { body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } } top: 10px; } }
.contentSearchSuggestionTable { .contentSearchSuggestionTable {
@ -1876,6 +1884,11 @@ main {
border-radius: 4px; } border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card { [lwt-newtab-brighttext] .ds-card-grid .ds-card {
background: none; } background: none; }
.ds-card-grid .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); } box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
[lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) { [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
@ -1969,6 +1982,11 @@ main {
border-top: 1px solid #4A4A4F; } border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper { [lwt-newtab-brighttext] .ds-hero .wrapper {
color: #D7D7DB; } color: #D7D7DB; }
.ds-hero .wrapper:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-hero-no-border .ds-hero-item .wrapper { .ds-hero-no-border .ds-hero-item .wrapper {
border-top: 0; border-top: 0;
border-bottom: 0; border-bottom: 0;
@ -2204,6 +2222,12 @@ main {
[lwt-newtab-brighttext] .ds-list a { [lwt-newtab-brighttext] .ds-list a {
color: #F9F9FA; } color: #F9F9FA; }
.ds-list-item-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-list-numbers .ds-list-item { .ds-list-numbers .ds-list-item {
counter-increment: list; } counter-increment: list; }
@ -2403,6 +2427,11 @@ main {
margin: 0 -25px; } margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer { .ds-top-sites .top-sites .top-site-outer {
padding: 0 12px; } padding: 0 12px; }
.ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-top-sites .top-sites .top-sites-list { .ds-top-sites .top-sites .top-sites-list {
margin: 0 -12px; } margin: 0 -12px; }
@ -2572,6 +2601,11 @@ main {
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: 100%; } height: 100%; }
.ds-card .ds-card-link:focus {
box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
transition: box-shadow 150ms;
border-radius: 4px;
outline: none; }
.ds-card .meta { .ds-card .meta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -2799,8 +2833,7 @@ main {
overflow: hidden; } overflow: hidden; }
.modalOverlayOuter { .modalOverlayOuter {
background: #FFF; background: var(--newtab-overlay-color);
opacity: 0.93;
height: 100%; height: 100%;
position: fixed; position: fixed;
top: 0; top: 0;
@ -3264,29 +3297,6 @@ main {
@media (max-width: 650px) { @media (max-width: 650px) {
.onboardingMessage { .onboardingMessage {
height: 250px; } } height: 250px; } }
.onboardingMessage .onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessage .onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessage .onboardingMessageImage.addons {
background-image: url("resource://activity-stream/data/content/assets/illustration-addons@2x.png"); }
.onboardingMessage .onboardingMessageImage.privatebrowsing {
background-image: url("resource://activity-stream/data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessage .onboardingMessageImage.screenshots {
background-image: url("resource://activity-stream/data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessage .onboardingMessageImage.gift {
background-image: url("resource://activity-stream/data/content/assets/illustration-gift@2x.png"); }
.onboardingMessage .onboardingMessageImage.sync {
background-image: url("resource://activity-stream/data/content/assets/illustration-sync@2x.png"); }
.onboardingMessage .onboardingContent { .onboardingMessage .onboardingContent {
height: 175px; } height: 175px; }
.onboardingMessage .onboardingContent > span > h3 { .onboardingMessage .onboardingContent > span > h3 {
@ -3337,6 +3347,52 @@ main {
.onboardingMessage:last-child::before { .onboardingMessage:last-child::before {
content: none; } content: none; }
.onboardingMessageImage {
height: 100px;
width: 120px;
background-size: 120px;
background-position: center center;
background-repeat: no-repeat;
display: inline-block;
vertical-align: middle; }
@media (max-width: 850px) {
.onboardingMessageImage {
height: 75px;
min-width: 80px;
background-size: 80px; } }
.onboardingMessageImage.addons {
background-image: url("../data/content/assets/illustration-addons@2x.png"); }
.onboardingMessageImage.privatebrowsing {
background-image: url("../data/content/assets/illustration-privatebrowsing@2x.png"); }
.onboardingMessageImage.screenshots {
background-image: url("../data/content/assets/illustration-screenshots@2x.png"); }
.onboardingMessageImage.gift {
background-image: url("../data/content/assets/illustration-gift@2x.png"); }
.onboardingMessageImage.sync {
background-image: url("../data/content/assets/illustration-sync@2x.png"); }
.onboardingMessageImage.devices {
background-image: url("../data/content/assets/trailhead/card-illo-devices.png"); }
.onboardingMessageImage.fbcont {
background-image: url("../data/content/assets/trailhead/card-illo-fbcont.png"); }
.onboardingMessageImage.ffmonitor {
background-image: url("../data/content/assets/trailhead/card-illo-ffmonitor.png"); }
.onboardingMessageImage.ffsend {
background-image: url("../data/content/assets/trailhead/card-illo-ffsend.png"); }
.onboardingMessageImage.lockwise {
background-image: url("../data/content/assets/trailhead/card-illo-lockwise.png"); }
.onboardingMessageImage.mobile {
background-image: url("../data/content/assets/trailhead/card-illo-mobile.png"); }
.onboardingMessageImage.pledge {
background-image: url("../data/content/assets/trailhead/card-illo-pledge.png"); }
.onboardingMessageImage.pocket {
background-image: url("../data/content/assets/trailhead/card-illo-pocket.png"); }
.onboardingMessageImage.private {
background-image: url("../data/content/assets/trailhead/card-illo-private.png"); }
.onboardingMessageImage.sendtab {
background-image: url("../data/content/assets/trailhead/card-illo-sendtab.png"); }
.onboardingMessageImage.tracking {
background-image: url("../data/content/assets/trailhead/card-illo-tracking.png"); }
.EOYSnippetForm { .EOYSnippetForm {
margin: 10px 0 8px; margin: 10px 0 8px;
align-self: start; align-self: start;
@ -3631,3 +3687,224 @@ a.firstrun-link {
100% { 100% {
opacity: 1; opacity: 1;
transform: translateY(0); } } transform: translateY(0); } }
.trailhead {
background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
color: #FFF;
height: auto;
top: 100px; }
@media (max-height: 700px) {
.trailhead {
position: absolute;
top: 20px; } }
.trailhead a {
color: #FFF;
text-decoration: underline; }
.trailhead input,
.trailhead button {
border-radius: 4px;
padding: 10px; }
.trailhead .trailheadInner {
display: grid;
grid-column-gap: 40px;
grid-template-columns: 5fr 3fr;
padding: 40px 60px; }
.trailhead .trailheadContent h1 {
font-size: 36px;
font-weight: 200;
line-height: 46px;
margin: 0; }
.trailhead .trailheadContent .trailheadLearn {
display: block;
margin-top: 30px;
margin-inline-start: 74px; }
.trailhead.syncCohort {
left: calc(50% - 430px);
width: 860px; }
@media (max-width: 860px) {
.trailhead.syncCohort {
left: 0;
width: 100%; } }
.trailhead.syncCohort .trailheadInner {
grid-template-columns: 4fr 3fr; }
.trailhead.syncCohort .trailheadContent .trailheadBenefits {
background: url("../data/content/assets/sync-devices.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
height: 200px;
margin-inline-end: 60px; }
.trailhead.syncCohort .trailheadContent .trailheadLearn {
margin-inline-start: 0; }
.trailhead .trailheadBenefits {
padding: 0; }
.trailhead .trailheadBenefits li {
background-position: left 4px;
background-repeat: no-repeat;
background-size: 62px;
-moz-context-properties: fill;
fill: #0A84FF;
list-style: none;
padding-inline-start: 74px; }
.trailhead .trailheadBenefits li:dir(rtl) {
background-position-x: right; }
.trailhead .trailheadBenefits li.knowledge {
background-image: url("../data/content/assets/trailhead/benefit-knowledge.png"); }
.trailhead .trailheadBenefits li.privacy {
background-image: url("../data/content/assets/trailhead/benefit-privacy.png"); }
.trailhead .trailheadBenefits li.products {
background-image: url("../data/content/assets/trailhead/benefit-products.png"); }
.trailhead .trailheadBenefits h3 {
color: #CB9EFF;
font-size: 22px;
font-weight: 400;
margin-bottom: 4px; }
.trailhead .trailheadBenefits p {
color: #FFF;
font-size: 15px;
line-height: 22px;
margin: 4px 0 15px;
margin-inline-end: 60px; }
.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 {
border: 0;
width: 100%; }
.trailhead .trailheadForm input {
background-color: #FFF;
color: #38383D;
font-size: 15px; }
.trailhead .trailheadForm button {
background-color: #0060DF;
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 .trailheadStart {
border: 1px solid rgba(255, 255, 255, 0.5);
cursor: pointer;
display: block;
font-size: 15px;
font-weight: 400;
margin: 0 auto 40px;
min-width: 300px;
padding: 14px; }
.trailhead .trailheadStart:hover, .trailhead .trailheadStart:focus {
background-color: #0250BB;
border-color: transparent; }
.trailhead .trailheadStart:focus {
outline: dotted 1px; }
.trailheadCards {
background: var(--trailhead-cards-background-color);
text-align: center; }
.trailheadCards h1 {
font-size: 36px;
font-weight: 200;
margin: 0 0 40px;
color: var(--trailhead-header-text-color); }
.trailheadCardsInner {
margin: auto;
padding: 40px 25px; }
@media (min-width: 610px) {
.trailheadCardsInner {
width: 530px; } }
@media (min-width: 866px) {
.trailheadCardsInner {
width: 786px; } }
@media (min-width: 1122px) {
.trailheadCardsInner {
width: 1042px; } }
.trailheadCardGrid {
display: grid;
grid-gap: 32px;
margin: 0; }
@media (min-width: 610px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 224px); } }
@media (min-width: 1122px) {
.trailheadCardGrid {
grid-template-columns: repeat(auto-fit, 309px); } }
.trailheadCard {
position: relative;
background: var(--newtab-card-background-color);
border-radius: 4px;
box-shadow: var(--newtab-card-shadow);
font-size: 13px;
padding: 20px; }
@media (min-width: 1122px) {
.trailheadCard {
font-size: 15px;
padding: 40px; } }
.trailheadCard .onboardingTitle {
font-weight: normal;
color: var(--newtab-text-primary-color);
margin: 10px 0 4px;
font-size: 15px; }
@media (min-width: 1122px) {
.trailheadCard .onboardingTitle {
font-size: 18px; } }
.trailheadCard .onboardingText {
margin: 0 0 60px;
color: var(--newtab-text-conditional-color);
line-height: 1.5;
font-weight: 200; }
.trailheadCard .onboardingButton {
color: var(--newtab-text-conditional-color);
background: var(--trailhead-card-button-background-color);
border: 0;
height: 30px;
min-width: 70%;
padding: 0 14px; }
.trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
box-shadow: none;
background: var(--trailhead-card-button-background-hover-color); }
.trailheadCard .onboardingButtonContainer {
height: 60px;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center; }
.inline-onboarding .outer-wrapper {
position: relative; }
.inline-onboarding .outer-wrapper .prefs-button button {
position: absolute; }
@media (max-height: 700px) {
.activity-stream.welcome.inline-onboarding {
overflow: auto; } }

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 23 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 26 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 46 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 61 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 27 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 27 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 31 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 24 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Двоичные данные
browser/components/newtab/data/content/assets/trailhead/firefox-logo.png Executable file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 30 KiB

Просмотреть файл

@ -0,0 +1,139 @@
# 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/.
## The following feature names must be treated as a brand, and kept in English.
## They cannot be:
## - Declined to adapt to grammatical case.
## - Transliterated.
## - Translated.
-facebook-container-brand-name = Facebook Container
-lockwise-brand-name = Firefox Lockwise
-monitor-brand-name = Firefox Monitor
-pocket-brand-name = Pocket
-send-brand-name = Firefox Send
# 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/.
## UI strings for the simplified onboarding modal
onboarding-button-label-learn-more = Learn More
onboarding-button-label-try-now = Try It Now
onboarding-button-label-get-started = Get Started
onboarding-welcome-header = Welcome to { -brand-short-name }
onboarding-welcome-body = Youve got the browser.<br/>Meet the rest of { -brand-product-name }.
onboarding-welcome-learn-more = Learn more about the benefits.
onboarding-join-form-header = Join { -brand-product-name }
onboarding-join-form-body = Enter your email address to get started.
onboarding-join-form-email =
.placeholder = Enter email
onboarding-join-form-email-error = Valid email required
onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
onboarding-join-form-continue = Continue
onboarding-start-browsing-button-label = Start Browsing
## These are individual benefit messages shown with an image, title and
## description.
onboarding-benefit-products-title = Useful Products
onboarding-benefit-products-text = Get things done with a family of tools that respects your privacy across your devices.
onboarding-benefit-knowledge-title = Practical Knowledge
onboarding-benefit-knowledge-text = Learn everything you need to know to stay smarter and safer online.
onboarding-benefit-privacy-title = True Privacy
# "Personal Data Promise" should be treated as a brand and refers to a concept
# shown elsewhere to the user: "The Firefox Personal Data Promise is the way we
# honor your data in everything we make and do. We take less data. We keep it
# safe. And we make sure that we are transparent about how we use it."
onboarding-benefit-privacy-text = Everything we do honors our Personal Data Promise: Take less. Keep it safe. No secrets.
## These strings belong to the individual onboarding messages.
## Each message has a title and a description of what the browser feature is.
## Each message also has an associated button for the user to try the feature.
## The string for the button is found above, in the UI strings section
onboarding-private-browsing-title = Private Browsing
onboarding-private-browsing-text = Browse by yourself. Private Browsing with Content Blocking blocks online trackers that follow you around the web.
onboarding-screenshots-title = Screenshots
onboarding-screenshots-text = Take, save and share screenshots - without leaving { -brand-short-name }. Capture a region or an entire page as you browse. Then save to the web for easy access and sharing.
onboarding-addons-title = Add-ons
onboarding-addons-text = Add even more features that make { -brand-short-name } work harder for you. Compare prices, check the weather or express your personality with a custom theme.
onboarding-ghostery-title = Ghostery
onboarding-ghostery-text = Browse faster, smarter, or safer with extensions like Ghostery, which lets you block annoying ads.
# Note: "Sync" in this case is a generic verb, as in "to synchronize"
onboarding-fxa-title = Sync
onboarding-fxa-text = Sign up for a { -fxaccount-brand-name } and sync your bookmarks, passwords, and open tabs everywhere you use { -brand-short-name }.
onboarding-tracking-protection-title = Control How Youre Tracked
onboarding-tracking-protection-text = Dont like when ads follow you around? { -brand-short-name } helps you control how advertisers track your activity online.
onboarding-tracking-protection-button = Learn More
onboarding-data-sync-title = Take Your Settings with You
# "Sync" is short for synchronize.
onboarding-data-sync-text = Sync your bookmarks and passwords everywhere you use { -brand-product-name }.
onboarding-data-sync-button = Turn on { -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-button = Sign up for Alerts
onboarding-private-browsing-title = Browse Privately
onboarding-private-browsing-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
onboarding-private-browsing-button = Open a Private Window
onboarding-firefox-send-title = Keep Your Shared Files Private
onboarding-firefox-send-text = { -send-brand-name } protects the files you share with end-to-end encryption and a link that automatically expires.
onboarding-firefox-send-button = Try { -send-brand-name }
onboarding-mobile-phone-title = Get { -brand-product-name } on Your Phone
onboarding-mobile-phone-text = Download { -brand-product-name } for iOS or Android and sync your data across devices.
# "Mobile" is short for mobile/cellular phone, "Browser" is short for web
# browser.
onboarding-mobile-phone-button = Download Mobile Browser
onboarding-privacy-right-title = Privacy is Your Right
onboarding-privacy-right-text = { -brand-short-name } treats your data with respect by taking less, protecting it, and being clear about how we use it.
onboarding-privacy-right-button = Learn More
onboarding-send-tabs-title = Instantly Send Yourself Tabs
# "Send Tabs" refers to "Send Tab to Device" feature that appears when opening a
# tab's context menu.
onboarding-send-tabs-text = Send Tabs instantly shares pages between your devices without having to copy, paste, or leave the browser.
onboarding-send-tabs-button = Start Using Send Tabs
onboarding-pocket-anywhere-title = Read and Listen Anywhere
# "downtime" refers to the user's free/spare time.
onboarding-pocket-anywhere-text = { -pocket-brand-name } saves your favorite stories so you can read, listen, and watch during your downtime, even if youre offline.
onboarding-pocket-anywhere-button = Try { -pocket-brand-name }
onboarding-lockwise-passwords-title = Take Your Passwords Everywhere
# "many places" conveys that Lockwise is available outside of Firefox.
onboarding-lockwise-passwords-text = { -lockwise-brand-name } saves your passwords in a secure place so you can easily log into your accounts.
onboarding-lockwise-passwords-button = Get { -lockwise-brand-name }
onboarding-facebook-container-title = Set Boundaries with Facebook
onboarding-facebook-container-text = { -facebook-container-brand-name } keeps your Facebook identity separate from everything else, making it harder to track you across the web.
onboarding-facebook-container-button = Add the Extension
## Message strings belonging to the Return to AMO flow
return-to-amo-sub-header = Great, youve got { -brand-short-name }
# <icon></icon> will be replaced with the icon belonging to the extension
#
# Variables:
# $addon-name (String) - Name of the add-on
return-to-amo-addon-header = Now lets get you <icon></icon><b>{ $addon-name }.</b>
return-to-amo-extension-button = Add the Extension
return-to-amo-get-started-button = Get Started with { -brand-short-name }

Просмотреть файл

@ -161,6 +161,30 @@ Schema definitions/validations that can be used for tests can be found in `syste
} }
``` ```
# Example Discovery Stream `SPOCS Fill` log
```js
{
// both "client_id" and "session_id" are set to "n/a" in this ping.
"client_id": "n/a",
"session_id": "n/a",
"impression_id": "{005deed0-e3e4-4c02-a041-17405fd703f6}",
"addon_version": "20180710100040",
"locale": "en-US",
"version": "68",
"release_channel": "release",
"spoc_fills": [
{"id": 10000, displayed: 0, reason: "frequency_cap", full_recalc: 1},
{"id": 10001, displayed: 0, reason: "blocked_by_user", full_recalc: 1},
{"id": 10002, displayed: 0, reason: "below_min_score", full_recalc: 1},
{"id": 10003, displayed: 0, reason: "campaign_duplicate", full_recalc: 1},
{"id": 10004, displayed: 0, reason: "probability_selection", full_recalc: 0},
{"id": 10004, displayed: 0, reason: "out_of_position", full_recalc: 0},
{"id": 10005, displayed: 1, reason: "n/a", full_recalc: 0}
]
}
```
# Example Activity Stream `Router` Pings # Example Activity Stream `Router` Pings
```js ```js
@ -237,6 +261,9 @@ and losing focus. | :one:
| `profile_creation_date` | [Optional] An integer to record the age of the Firefox profile as the total number of days since the UNIX epoch. | :one: | `profile_creation_date` | [Optional] An integer to record the age of the Firefox profile as the total number of days since the UNIX epoch. | :one:
| `message_id` | [required] A string identifier of the message in Activity Stream Router. | :one: | `message_id` | [required] A string identifier of the message in Activity Stream Router. | :one:
| `has_flow_params` | [required] One of [true, false]. A boolean identifier that indicates if Firefox Accounts flow parameters are set or unset. | :one: | `has_flow_params` | [required] One of [true, false]. A boolean identifier that indicates if Firefox Accounts flow parameters are set or unset. | :one:
| `displayed` | [required] 1: a SPOC is displayed; 0: non-displayed | :one:
| `reason` | [required] The reason if a SPOC is not displayed, "n/a" for the displayed, one of ("frequency_cap", "blocked_by_user", "campaign_duplicate", "probability_selection", "below_min_score", "out_of_position", "n/a") | :one:
| `full_recalc` | [required] Is it a full SPOCS recalculation: 0: false; 1: true. Recalculation case: 1). fetch SPOCS from Pocket endpoint. Non-recalculation cases: 1). An impression updates the SPOCS; 2). Any action that triggers the `selectLayoutRender ` | :one:
**Where:** **Where:**

Просмотреть файл

@ -855,6 +855,32 @@ This reports all the loaded content (a list of `id`s and positions) when the use
} }
``` ```
### Discovery Stream SPOCS Fill ping
This reports the internal status of Pocket SPOCS (Sponsored Contents).
```js
{
// both "client_id" and "session_id" are set to "n/a" in this ping.
"client_id": "n/a",
"session_id": "n/a",
"impression_id": "{005deed0-e3e4-4c02-a041-17405fd703f6}",
"addon_version": "20180710100040",
"locale": "en-US",
"version": "68",
"release_channel": "release",
"spoc_fills": [
{"id": 10000, displayed: 0, reason: "frequency_cap", full_recalc: 1},
{"id": 10001, displayed: 0, reason: "blocked_by_user", full_recalc: 1},
{"id": 10002, displayed: 0, reason: "below_min_score", full_recalc: 1},
{"id": 10003, displayed: 0, reason: "campaign_duplicate", full_recalc: 1},
{"id": 10004, displayed: 0, reason: "probability_selection", full_recalc: 0},
{"id": 10004, displayed: 0, reason: "out_of_position", full_recalc: 0},
{"id": 10005, displayed: 1, reason: "n/a", full_recalc: 0}
]
}
```
## Undesired event pings ## Undesired event pings
These pings record the undesired events happen in the addon for further investigation. These pings record the undesired events happen in the addon for further investigation.

Просмотреть файл

@ -2,6 +2,9 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
[localization] en-US.jar:
trailhead.ftl (./data/trailhead.wip)
browser.jar: browser.jar:
% resource activity-stream %res/activity-stream/ contentaccessible=yes % resource activity-stream %res/activity-stream/ contentaccessible=yes
res/activity-stream/lib/ (./lib/*) res/activity-stream/lib/ (./lib/*)

Просмотреть файл

@ -695,18 +695,34 @@ class _ASRouter {
} }
async _getBundledMessages(originalMessage, target, trigger, force = false) { async _getBundledMessages(originalMessage, target, trigger, force = false) {
let result = [{content: originalMessage.content, id: originalMessage.id, order: originalMessage.order || 0}]; let result = [];
let bundleLength;
let bundleTemplate;
let originalId;
if (originalMessage.includeBundle) {
// The original message is not part of the bundle, so don't include it
bundleLength = originalMessage.includeBundle.length;
bundleTemplate = originalMessage.includeBundle.template;
} else {
// The original message is part of the bundle
bundleLength = originalMessage.bundled;
bundleTemplate = originalMessage.template;
originalId = originalMessage.id;
// Add in a copy of the first message
result.push({content: originalMessage.content, id: originalMessage.id, order: originalMessage.order || 0});
}
// First, find all messages of same template. These are potential matching targeting candidates // First, find all messages of same template. These are potential matching targeting candidates
let bundledMessagesOfSameTemplate = this._getUnblockedMessages() let bundledMessagesOfSameTemplate = this._getUnblockedMessages()
.filter(msg => msg.bundled && msg.template === originalMessage.template && msg.id !== originalMessage.id); .filter(msg => msg.bundled && msg.template === bundleTemplate && msg.id !== originalId);
if (force) { if (force) {
// Forcefully show the messages without targeting matching - this is for about:newtab#asrouter to show the messages // Forcefully show the messages without targeting matching - this is for about:newtab#asrouter to show the messages
for (const message of bundledMessagesOfSameTemplate) { for (const message of bundledMessagesOfSameTemplate) {
result.push({content: message.content, id: message.id}); result.push({content: message.content, id: message.id});
// Stop once we have enough messages to fill a bundle // Stop once we have enough messages to fill a bundle
if (result.length === originalMessage.bundled) { if (result.length === bundleLength) {
break; break;
} }
} }
@ -723,14 +739,14 @@ class _ASRouter {
result.push({content: message.content, id: message.id, order: message.order || 0}); result.push({content: message.content, id: message.id, order: message.order || 0});
bundledMessagesOfSameTemplate.splice(bundledMessagesOfSameTemplate.findIndex(msg => msg.id === message.id), 1); bundledMessagesOfSameTemplate.splice(bundledMessagesOfSameTemplate.findIndex(msg => msg.id === message.id), 1);
// Stop once we have enough messages to fill a bundle // Stop once we have enough messages to fill a bundle
if (result.length === originalMessage.bundled) { if (result.length === bundleLength) {
break; break;
} }
} }
} }
// If we did not find enough messages to fill the bundle, do not send the bundle down // If we did not find enough messages to fill the bundle, do not send the bundle down
if (result.length < originalMessage.bundled) { if (result.length < bundleLength) {
return null; return null;
} }
@ -739,7 +755,12 @@ class _ASRouter {
// handle finding these strings on its own. See bug 1488973 // handle finding these strings on its own. See bug 1488973
const extraTemplateStrings = await this._extraTemplateStrings(originalMessage); const extraTemplateStrings = await this._extraTemplateStrings(originalMessage);
return {bundle: this._orderBundle(result), ...(extraTemplateStrings && {extraTemplateStrings}), provider: originalMessage.provider, template: originalMessage.template}; return {
bundle: this._orderBundle(result),
...(extraTemplateStrings && {extraTemplateStrings}),
provider: originalMessage.provider,
template: originalMessage.template,
};
} }
async _extraTemplateStrings(originalMessage) { async _extraTemplateStrings(originalMessage) {
@ -776,7 +797,16 @@ class _ASRouter {
} else if (message.bundled) { } else if (message.bundled) {
const bundledMessages = await this._getBundledMessages(message, target, trigger, force); const bundledMessages = await this._getBundledMessages(message, target, trigger, force);
const action = bundledMessages ? {type: "SET_BUNDLED_MESSAGES", data: bundledMessages} : {type: "CLEAR_ALL"}; const action = bundledMessages ? {type: "SET_BUNDLED_MESSAGES", data: bundledMessages} : {type: "CLEAR_ALL"};
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, action); target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
} catch (e) {}
// For nested bundled messages, look for the desired bundle
} else if (message.includeBundle) {
const bundledMessages = await this._getBundledMessages(message, target, message.includeBundle.trigger, force);
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "SET_MESSAGE", data: {...message, bundle: bundledMessages && bundledMessages.bundle}});
} catch (e) {}
// CFR doorhanger // CFR doorhanger
} else if (message.template === "cfr_doorhanger") { } else if (message.template === "cfr_doorhanger") {
@ -788,7 +818,9 @@ class _ASRouter {
// New tab single messages // New tab single messages
} else { } else {
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "SET_MESSAGE", data: message}); target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "SET_MESSAGE", data: message});
} catch (e) {}
} }
} }

Просмотреть файл

@ -172,6 +172,9 @@ function sortMessagesByTargeting(messages) {
} }
const TargetingGetters = { const TargetingGetters = {
get trailheadCohort() {
return Services.prefs.getIntPref("trailhead.firstrun.cohort", 0);
},
get locale() { get locale() {
return Services.locale.appLocaleAsLangTag; return Services.locale.appLocaleAsLangTag;
}, },

Просмотреть файл

@ -42,6 +42,32 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
this._prefCache = {}; this._prefCache = {};
} }
/**
* Send SPOCS Fill telemetry.
* @param {object} filteredItems An object keyed on filter reasons, and the value
* is a list of SPOCS.
* @param {boolean} fullRecalc A boolean indicating if it's a full recalculation.
* Calling `loadSpocs` will be treated as a full recalculation.
* Whereas responding the action "DISCOVERY_STREAM_SPOC_IMPRESSION"
* is not a full recalculation.
*/
_sendSpocsFill(filteredItems, fullRecalc) {
const full_recalc = fullRecalc ? 1 : 0;
const spocsFill = [];
for (const [reason, items] of Object.entries(filteredItems)) {
items.forEach(item => {
// Only send SPOCS (i.e. it has a campaign_id)
if (item.campaign_id) {
spocsFill.push({reason, full_recalc, id: item.id, displayed: 0});
}
});
}
if (spocsFill.length) {
this.store.dispatch(ac.DiscoveryStreamSpocsFill({spoc_fills: spocsFill}));
}
}
finalLayoutEndpoint(url, apiKey) { finalLayoutEndpoint(url, apiKey) {
if (url.includes("$apiKey") && !apiKey) { if (url.includes("$apiKey") && !apiKey) {
throw new Error(`Layout Endpoint - An API key was specified but none configured: ${url}`); throw new Error(`Layout Endpoint - An API key was specified but none configured: ${url}`);
@ -266,9 +292,10 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
filterRecommendations(feed) { filterRecommendations(feed) {
if (feed && feed.data && feed.data.recommendations && feed.data.recommendations.length) { if (feed && feed.data && feed.data.recommendations && feed.data.recommendations.length) {
const {data} = this.filterBlocked(feed.data, "recommendations");
return { return {
...feed, ...feed,
data: this.filterBlocked(feed.data, "recommendations"), data,
}; };
} }
return feed; return feed;
@ -366,13 +393,17 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
data: {}, data: {},
}; };
let {data, filtered: frequencyCapped} = this.frequencyCapSpocs(spocs.data);
let {data: newSpocs, filtered} = this.transform(data);
sendUpdate({ sendUpdate({
type: at.DISCOVERY_STREAM_SPOCS_UPDATE, type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: { data: {
lastUpdated: spocs.lastUpdated, lastUpdated: spocs.lastUpdated,
spocs: this.transform(this.frequencyCapSpocs(spocs.data)), spocs: newSpocs,
}, },
}); });
this._sendSpocsFill({...filtered, frequency_cap: frequencyCapped}, true);
} }
async loadAffinityScoresCache() { async loadAffinityScoresCache() {
@ -418,11 +449,19 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
} }
scoreItems(items) { scoreItems(items) {
return items.map(item => this.scoreItem(item)) const filtered = [];
const data = items.map(item => this.scoreItem(item))
// Remove spocs that are scored too low. // Remove spocs that are scored too low.
.filter(s => s.score >= s.min_score) .filter(s => {
if (s.score >= s.min_score) {
return true;
}
filtered.push(s);
return false;
})
// Sort by highest scores. // Sort by highest scores.
.sort((a, b) => b.score - a.score); .sort((a, b) => b.score - a.score);
return {data, filtered};
} }
scoreItem(item) { scoreItem(item) {
@ -441,30 +480,40 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
} }
filterBlocked(data, type) { filterBlocked(data, type) {
const filtered = [];
if (data && data[type] && data[type].length) { if (data && data[type] && data[type].length) {
const filteredItems = data[type].filter(item => !NewTabUtils.blockedLinks.isBlocked({"url": item.url})); const filteredItems = data[type].filter(item => {
const blocked = NewTabUtils.blockedLinks.isBlocked({"url": item.url});
if (blocked) {
filtered.push(item);
}
return !blocked;
});
return { return {
data: {
...data, ...data,
[type]: filteredItems, [type]: filteredItems,
},
filtered,
}; };
} }
return data; return {data, filtered};
} }
transform(spocs) { transform(spocs) {
const data = this.filterBlocked(spocs, "spocs"); const {data, filtered: blockedItems} = this.filterBlocked(spocs, "spocs");
if (data && data.spocs && data.spocs.length) { if (data && data.spocs && data.spocs.length) {
const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1; const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
const campaignMap = {}; const campaignMap = {};
return { const campaignDuplicates = [];
...data,
// This order of operations is intended. // This order of operations is intended.
// scoreItems must be first because it creates this.score. // scoreItems must be first because it creates this.score.
spocs: this.scoreItems(data.spocs) const {data: items, filtered: belowMinScoreItems} = this.scoreItems(data.spocs);
// This removes campaign dupes. // This removes campaign dupes.
// We do this only after scoring and sorting because that way // We do this only after scoring and sorting because that way
// we can keep the first item we see, and end up keeping the highest scored. // we can keep the first item we see, and end up keeping the highest scored.
.filter(s => { const newSpocs = items.filter(s => {
if (!campaignMap[s.campaign_id]) { if (!campaignMap[s.campaign_id]) {
campaignMap[s.campaign_id] = 1; campaignMap[s.campaign_id] = 1;
return true; return true;
@ -472,14 +521,26 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
campaignMap[s.campaign_id]++; campaignMap[s.campaign_id]++;
return true; return true;
} }
campaignDuplicates.push(s);
return false; return false;
}), });
return {
data: {...data, spocs: newSpocs},
filtered: {
blocked_by_user: blockedItems,
below_min_score: belowMinScoreItems,
campaign_duplicate: campaignDuplicates,
},
}; };
} }
return data; return {data, filtered: {blocked: blockedItems}};
} }
// Filter spocs based on frequency caps // Filter spocs based on frequency caps
//
// @param {Object} data An object that might have a SPOCS array.
// @returns {Object} An object with a property `data` as the result, and a property
// `filterItems` as the frequency capped items.
frequencyCapSpocs(data) { frequencyCapSpocs(data) {
if (data && data.spocs && data.spocs.length) { if (data && data.spocs && data.spocs.length) {
const {spocs} = data; const {spocs} = data;
@ -499,9 +560,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
if (caps.length) { if (caps.length) {
this.store.dispatch({type: at.DISCOVERY_STREAM_SPOCS_CAPS, data: caps}); this.store.dispatch({type: at.DISCOVERY_STREAM_SPOCS_CAPS, data: caps});
} }
return result; return {data: result, filtered: caps};
} }
return data; return {data, filtered: []};
} }
// Frequency caps are based on campaigns, which may include multiple spocs. // Frequency caps are based on campaigns, which may include multiple spocs.
@ -549,7 +610,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
if (this.isExpired({cachedData, key: "feed", url: feedUrl, isStartup})) { if (this.isExpired({cachedData, key: "feed", url: feedUrl, isStartup})) {
const feedResponse = await this.fetchFromEndpoint(feedUrl); const feedResponse = await this.fetchFromEndpoint(feedUrl);
if (feedResponse) { if (feedResponse) {
const scoredItems = this.scoreItems(feedResponse.recommendations); const {data: scoredItems} = this.scoreItems(feedResponse.recommendations);
const {recsExpireTime} = feedResponse.settings; const {recsExpireTime} = feedResponse.settings;
const recommendations = this.rotate(scoredItems, recsExpireTime); const recommendations = this.rotate(scoredItems, recsExpireTime);
this.componentFeedFetched = true; this.componentFeedFetched = true;
@ -867,10 +928,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
// Apply frequency capping to SPOCs in the redux store, only update the // Apply frequency capping to SPOCs in the redux store, only update the
// store if the SPOCs are changed. // store if the SPOCs are changed.
const {spocs} = this.store.getState().DiscoveryStream; const {spocs} = this.store.getState().DiscoveryStream;
const newSpocs = this.frequencyCapSpocs(spocs.data); const {data: newSpocs, filtered} = this.frequencyCapSpocs(spocs.data);
const prevSpocs = spocs.data.spocs || []; if (filtered.length) {
const currentSpocs = newSpocs.spocs || [];
if (prevSpocs.length !== currentSpocs.length) {
this.store.dispatch(ac.AlsoToPreloaded({ this.store.dispatch(ac.AlsoToPreloaded({
type: at.DISCOVERY_STREAM_SPOCS_UPDATE, type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: { data: {
@ -878,9 +937,24 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
spocs: newSpocs, spocs: newSpocs,
}, },
})); }));
this._sendSpocsFill({frequency_cap: filtered}, false);
} }
} }
break; break;
case at.PLACES_LINK_BLOCKED:
if (this.showSpocs) {
const {spocs} = this.store.getState().DiscoveryStream;
const spocsList = spocs.data.spocs || [];
const filtered = spocsList.filter(s => s.url === action.data.url);
if (filtered.length) {
this._sendSpocsFill({blocked_by_user: filtered}, false);
}
}
this.store.dispatch(ac.BroadcastToContent({
type: at.DISCOVERY_STREAM_LINK_BLOCKED,
data: action.data,
}));
break;
case at.UNINIT: case at.UNINIT:
// When this feed is shutting down: // When this feed is shutting down:
this.uninitPrefs(); this.uninitPrefs();

Просмотреть файл

@ -99,7 +99,7 @@ const ONBOARDING_MESSAGES = async () => ([
}, },
}, },
}, },
targeting: "attributionData.campaign != 'non-fx-button' && attributionData.source != 'addons.mozilla.org'", targeting: "trailheadCohort == 0 && attributionData.campaign != 'non-fx-button' && attributionData.source != 'addons.mozilla.org'",
trigger: {id: "showOnboarding"}, trigger: {id: "showOnboarding"},
}, },
{ {
@ -119,7 +119,7 @@ const ONBOARDING_MESSAGES = async () => ([
}, },
}, },
}, },
targeting: "providerCohorts.onboarding == 'ghostery'", targeting: "trailheadCohort == 0 && providerCohorts.onboarding == 'ghostery'",
trigger: {id: "showOnboarding"}, trigger: {id: "showOnboarding"},
}, },
{ {
@ -139,7 +139,279 @@ const ONBOARDING_MESSAGES = async () => ([
}, },
}, },
}, },
targeting: "attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'", targeting: "trailheadCohort == 0 && attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_1",
template: "trailhead",
targeting: "trailheadCohort == 1",
trigger: {id: "firstRun"},
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
content: {
className: "joinCohort",
title: {string_id: "onboarding-welcome-body"},
benefits: ["products", "knowledge", "privacy"].map(id => (
{
id,
title: {string_id: `onboarding-benefit-${id}-title`},
text: {string_id: `onboarding-benefit-${id}-text`},
}
)),
learn: {
text: {string_id: "onboarding-welcome-learn-more"},
url: "https://www.mozilla.org/firefox/accounts/",
},
form: {
title: {string_id: "onboarding-join-form-header"},
text: {string_id: "onboarding-join-form-body"},
email: {string_id: "onboarding-join-form-email"},
button: {string_id: "onboarding-join-form-continue"},
},
skipButton: {string_id: "onboarding-start-browsing-button-label"},
},
},
{
id: "TRAILHEAD_2",
template: "trailhead",
targeting: "trailheadCohort == 2",
trigger: {id: "firstRun"},
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
content: {
className: "syncCohort",
title: {value: "Take Firefox with You"},
subtitle: {value: "Get your bookmarks, history, passwords and other settings on all your devices."},
benefits: [],
learn: {
text: {string_id: "onboarding-welcome-learn-more"},
url: "https://www.mozilla.org/firefox/accounts/",
},
form: {
title: {value: "Enter your email"},
text: {value: "to continue to Firefox Sync"},
email: {placeholder: "Email"},
button: {string_id: "onboarding-join-form-continue"},
},
skipButton: {value: "Skip this step"},
},
},
{
id: "TRAILHEAD_3",
template: "trailhead",
targeting: "trailheadCohort == 3",
trigger: {id: "firstRun"},
includeBundle: {length: 3, template: "onboarding", trigger: {id: "showOnboarding"}},
},
{
id: "TRAILHEAD_4",
template: "trailhead",
targeting: "trailheadCohort == 4",
trigger: {id: "firstRun"},
},
{
id: "TRAILHEAD_CARD_1",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-tracking-protection-title"},
text: {string_id: "onboarding-tracking-protection-text"},
icon: "tracking",
primary_button: {
label: {string_id: "onboarding-tracking-protection-button"},
action: {
type: "OPEN_PREFERENCES_PAGE",
data: {category: "privacy-trackingprotection"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_2",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-data-sync-title"},
text: {string_id: "onboarding-data-sync-text"},
icon: "devices",
primary_button: {
label: {string_id: "onboarding-data-sync-button"},
action: {
type: "OPEN_URL",
data: {args: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=activity-stream-firstrun&utm_source=activity-stream&utm_campaign=firstrun", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_3",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-firefox-monitor-title"},
text: {string_id: "onboarding-firefox-monitor-text"},
icon: "ffmonitor",
primary_button: {
label: {string_id: "onboarding-firefox-monitor-button"},
action: {
type: "OPEN_URL",
data: {args: "https://monitor.firefox.com/", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_4",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-private-browsing-title"},
text: {string_id: "onboarding-private-browsing-text"},
icon: "private",
primary_button: {
label: {string_id: "onboarding-private-browsing-button"},
action: {type: "OPEN_PRIVATE_BROWSER_WINDOW"},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_5",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-firefox-send-title"},
text: {string_id: "onboarding-firefox-send-text"},
icon: "ffsend",
primary_button: {
label: {string_id: "onboarding-firefox-send-button"},
action: {
type: "OPEN_URL",
data: {args: "https://send.firefox.com/?utm_source=activity-stream?utm_medium=referral?utm_campaign=firstrun", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_6",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-mobile-phone-title"},
text: {string_id: "onboarding-mobile-phone-text"},
icon: "mobile",
primary_button: {
label: {string_id: "onboarding-mobile-phone-button"},
action: {
type: "OPEN_URL",
data: {args: "https://www.mozilla.org/firefox/mobile/", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_7",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-privacy-right-title"},
text: {string_id: "onboarding-privacy-right-text"},
icon: "pledge",
primary_button: {
label: {string_id: "onboarding-privacy-right-button"},
action: {
type: "OPEN_URL",
data: {args: "https://www.mozilla.org/?privacy-right", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_8",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-send-tabs-title"},
text: {string_id: "onboarding-send-tabs-text"},
icon: "sendtab",
primary_button: {
label: {string_id: "onboarding-send-tabs-button"},
action: {
type: "OPEN_URL",
data: {args: "https://blog.mozilla.org/firefox/send-tabs-a-better-way/", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_9",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-pocket-anywhere-title"},
text: {string_id: "onboarding-pocket-anywhere-text"},
icon: "pocket",
primary_button: {
label: {string_id: "onboarding-pocket-anywhere-button"},
action: {
type: "OPEN_URL",
data: {args: "https://getpocket.com/firefox_learnmore", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_10",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-lockwise-passwords-title"},
text: {string_id: "onboarding-lockwise-passwords-text"},
icon: "lockwise",
primary_button: {
label: {string_id: "onboarding-lockwise-passwords-button"},
action: {
type: "OPEN_URL",
data: {args: "https://lockwise.firefox.com/", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"},
},
{
id: "TRAILHEAD_CARD_11",
template: "onboarding",
bundled: 3,
content: {
title: {string_id: "onboarding-facebook-container-title"},
text: {string_id: "onboarding-facebook-container-text"},
icon: "fbcont",
primary_button: {
label: {string_id: "onboarding-facebook-container-button"},
action: {
type: "OPEN_URL",
data: {args: "https://addons.mozilla.org/firefox/addon/facebook-container/", where: "tabshifted"},
},
},
},
targeting: "trailheadCohort > 0",
trigger: {id: "showOnboarding"}, trigger: {id: "showOnboarding"},
}, },
{ {
@ -219,15 +491,6 @@ const OnboardingMessageProvider = {
} }
} }
const [primary_button_string, title_string, text_string] = await L10N.formatMessages([
{id: msg.content.primary_button.label.string_id},
{id: msg.content.title.string_id},
{id: msg.content.text.string_id, args: msg.content.text.args},
]);
translatedMessage.content.primary_button.label = primary_button_string.value;
translatedMessage.content.title = title_string.value;
translatedMessage.content.text = text_string.value;
// Translate any secondary buttons separately // Translate any secondary buttons separately
if (msg.content.secondary_button) { if (msg.content.secondary_button) {
const [secondary_button_string] = await L10N.formatMessages([{id: msg.content.secondary_button.label.string_id}]); const [secondary_button_string] = await L10N.formatMessages([{id: msg.content.secondary_button.label.string_id}]);

Просмотреть файл

@ -477,6 +477,18 @@ this.TelemetryFeed = class TelemetryFeed {
); );
} }
createSpocsFillPing(data) {
return Object.assign(
this.createPing(null),
data,
{
impression_id: this._impressionId,
client_id: "n/a",
session_id: "n/a",
}
);
}
createUserEvent(action) { createUserEvent(action) {
return Object.assign( return Object.assign(
this.createPing(au.getPortIdOfSender(action)), this.createPing(au.getPortIdOfSender(action)),
@ -733,6 +745,9 @@ this.TelemetryFeed = class TelemetryFeed {
case at.DISCOVERY_STREAM_LOADED_CONTENT: case at.DISCOVERY_STREAM_LOADED_CONTENT:
this.handleDiscoveryStreamLoadedContent(au.getPortIdOfSender(action), action.data); this.handleDiscoveryStreamLoadedContent(au.getPortIdOfSender(action), action.data);
break; break;
case at.DISCOVERY_STREAM_SPOCS_FILL:
this.handleDiscoveryStreamSpocsFill(action.data);
break;
case at.TELEMETRY_UNDESIRED_EVENT: case at.TELEMETRY_UNDESIRED_EVENT:
this.handleUndesiredEvent(action); this.handleUndesiredEvent(action);
break; break;
@ -805,6 +820,33 @@ this.TelemetryFeed = class TelemetryFeed {
session.loadedContentSets = loadedContentSets; session.loadedContentSets = loadedContentSets;
} }
/**
* Handl SPOCS Fill actions from Discovery Stream.
*
* @param {Object} data
* The SPOCS Fill event structured as:
* {
* spoc_fills: [
* {
* id: 123,
* displayed: 0,
* reason: "frequency_cap",
* full_recalc: 1
* },
* {
* id: 124,
* displayed: 1,
* reason: "n/a",
* full_recalc: 1
* }
* ]
* }
*/
handleDiscoveryStreamSpocsFill(data) {
const payload = this.createSpocsFillPing(data);
this.sendStructuredIngestionEvent(payload, "spocs-fills", "1");
}
/** /**
* Take all enumerable members of the data object and merge them into * Take all enumerable members of the data object and merge them into
* the session.perf object for the given port, so that it is sent to the * the session.perf object for the given port, so that it is sent to the

Просмотреть файл

@ -93,6 +93,7 @@ prefs_home_header=Firefoxen hasiera-orriko edukia
prefs_home_description=Aukeratu zein eduki nahi duzun Firefoxen hasiera-orriko pantailan. prefs_home_description=Aukeratu zein eduki nahi duzun Firefoxen hasiera-orriko pantailan.
prefs_content_discovery_header=Firefoxen hasiera prefs_content_discovery_header=Firefoxen hasiera
prefs_content_discovery_description=Firefoxen hasierako edukien aurkikuntzaren bidez kalitate altuko artikulu esanguratsuak aurki ditzakezu webean. prefs_content_discovery_description=Firefoxen hasierako edukien aurkikuntzaren bidez kalitate altuko artikulu esanguratsuak aurki ditzakezu webean.
prefs_content_discovery_button=Desgaitu edukien aurkikuntza prefs_content_discovery_button=Desgaitu edukien aurkikuntza
@ -190,7 +191,7 @@ section_menu_action_privacy_notice=Pribatutasun-oharra
# LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
# firstrun of the browser, they give an introduction to Firefox and Sync. # firstrun of the browser, they give an introduction to Firefox and Sync.
firstrun_title=Eraman Firefox aldean firstrun_title=Eraman Firefox aldean
firstrun_content=Izan laster-markak, historia, pasahitzak eta beste ezarpenak eskura zure gailu guztietatik. firstrun_content=Izan laster-markak, historia, pasahitzak eta beste ezarpenak eskura zure gailu guztietan.
firstrun_learn_more_link=Firefox kontuei buruzko argibide gehiago firstrun_learn_more_link=Firefox kontuei buruzko argibide gehiago
# LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header): # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):

Просмотреть файл

@ -93,6 +93,8 @@ prefs_home_description=ਉਹ ਸਮੱਗਰੀ ਚੁਣੋ ਜੋ ਤੁਸ
prefs_content_discovery_header=ਫਾਇਰਫਾਕਸ ਮੁੱਖ ਸਫ਼ਾ prefs_content_discovery_header=ਫਾਇਰਫਾਕਸ ਮੁੱਖ ਸਫ਼ਾ
prefs_content_discovery_button=ਸਮੱਗਰੀ ਖੋਜ ਬੰਦ ਕਰੋ
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
# plural forms used in a drop down of multiple row options (1 row, 2 rows). # plural forms used in a drop down of multiple row options (1 row, 2 rows).
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals

Просмотреть файл

@ -20,6 +20,7 @@ cd /mozilla-central && ./mach build \
&& ./mach test --log-tbpl test_run_log \ && ./mach test --log-tbpl test_run_log \
browser/base/content/test/about/browser_aboutHome_search_telemetry.js \ browser/base/content/test/about/browser_aboutHome_search_telemetry.js \
browser/base/content/test/static/browser_parsable_css.js \ browser/base/content/test/static/browser_parsable_css.js \
browser/base/content/test/tabs/browser_new_tab_in_privileged_process_pref.js \
browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js \ browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js \
browser/components/preferences/in-content/tests/browser_hometab_restore_defaults.js \ browser/components/preferences/in-content/tests/browser_hometab_restore_defaults.js \
browser/components/preferences/in-content/tests/browser_newtab_menu.js \ browser/components/preferences/in-content/tests/browser_newtab_menu.js \

47
browser/components/newtab/package-lock.json сгенерированный
Просмотреть файл

@ -708,6 +708,16 @@
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"aria-query": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
"integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7",
"commander": "^2.11.0"
}
},
"arr-diff": { "arr-diff": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
@ -856,6 +866,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true "dev": true
}, },
"ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
"dev": true
},
"astral-regex": { "astral-regex": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@ -913,6 +929,15 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true "dev": true
}, },
"axobject-query": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
"integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
"dev": true,
"requires": {
"ast-types-flow": "0.0.7"
}
},
"babel-code-frame": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -2222,6 +2247,12 @@
"es5-ext": "^0.10.9" "es5-ext": "^0.10.9"
} }
}, },
"damerau-levenshtein": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
"integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
"dev": true
},
"dashdash": { "dashdash": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -3085,6 +3116,22 @@
"vscode-json-languageservice": "^3.2.1" "vscode-json-languageservice": "^3.2.1"
} }
}, },
"eslint-plugin-jsx-a11y": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz",
"integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==",
"dev": true,
"requires": {
"aria-query": "^3.0.0",
"array-includes": "^3.0.3",
"ast-types-flow": "^0.0.7",
"axobject-query": "^2.0.2",
"damerau-levenshtein": "^1.0.4",
"emoji-regex": "^7.0.2",
"has": "^1.0.3",
"jsx-ast-utils": "^2.0.1"
}
},
"eslint-plugin-mozilla": { "eslint-plugin-mozilla": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-mozilla/-/eslint-plugin-mozilla-1.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-mozilla/-/eslint-plugin-mozilla-1.1.1.tgz",

Просмотреть файл

@ -36,6 +36,7 @@
"eslint-plugin-fetch-options": "0.0.4", "eslint-plugin-fetch-options": "0.0.4",
"eslint-plugin-import": "2.16.0", "eslint-plugin-import": "2.16.0",
"eslint-plugin-json": "1.4.0", "eslint-plugin-json": "1.4.0",
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-mozilla": "1.1.1", "eslint-plugin-mozilla": "1.1.1",
"eslint-plugin-no-unsanitized": "3.0.2", "eslint-plugin-no-unsanitized": "3.0.2",
"eslint-plugin-promise": "4.0.1", "eslint-plugin-promise": "4.0.1",
@ -135,6 +136,7 @@
"debugcoverage": "open logs/coverage/index.html", "debugcoverage": "open logs/coverage/index.html",
"lint": "npm-run-all lint:*", "lint": "npm-run-all lint:*",
"lint:eslint": "esw --ext=.js,.jsm,.json,.jsx .", "lint:eslint": "esw --ext=.js,.jsm,.json,.jsx .",
"lint:jsx-a11y": "esw --config=.eslintrc.jsx-a11y.js --ext=.jsx content-src/asrouter/components/ModalOverlay content-src/asrouter/templates/OnboardingMessage content-src/asrouter/templates/Trailhead",
"lint:sasslint": "sass-lint -v -q", "lint:sasslint": "sass-lint -v -q",
"strings-import": "node ./bin/strings-import.js", "strings-import": "node ./bin/strings-import.js",
"test": "npm run testmc", "test": "npm run testmc",

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Просмотреть файл

@ -9,7 +9,8 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
</body> </body>
</html> </html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" /> <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head> </head>
<body class="activity-stream"> <body class="activity-stream">
<div id="header-asrouter-container"></div>
<div id="root"><!-- Regular React Rendering --></div> <div id="root"><!-- Regular React Rendering --></div>
<div id="footer-snippets-container"></div> <div id="footer-asrouter-container"></div>
<script src="chrome://browser/content/contentSearchUI.js"></script> <script src="chrome://browser/content/contentSearchUI.js"></script>
<script src="chrome://browser/content/contentTheme.js"></script> <script src="chrome://browser/content/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script> <script src="resource://activity-stream/vendor/react.js"></script>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше