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);
#endif
#ifdef NIGHTLY_BUILD
pref("trailhead.firstrun.cohort", 1);
#else
pref("trailhead.firstrun.cohort", 0);
#endif
// Enable the DOM fullscreen API.
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" />
</head>
<body class="activity-stream">
<div id="header-asrouter-container"></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>
</html>
`;

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

@ -46,10 +46,12 @@ for (const type of [
"DISCOVERY_STREAM_IMPRESSION_STATS",
"DISCOVERY_STREAM_LAYOUT_RESET",
"DISCOVERY_STREAM_LAYOUT_UPDATE",
"DISCOVERY_STREAM_LINK_BLOCKED",
"DISCOVERY_STREAM_LOADED_CONTENT",
"DISCOVERY_STREAM_OPT_OUT",
"DISCOVERY_STREAM_SPOCS_CAPS",
"DISCOVERY_STREAM_SPOCS_ENDPOINT",
"DISCOVERY_STREAM_SPOCS_FILL",
"DISCOVERY_STREAM_SPOCS_UPDATE",
"DISCOVERY_STREAM_SPOC_IMPRESSION",
"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.
*
@ -398,6 +415,7 @@ this.actionCreators = {
WebExtEvent,
DiscoveryStreamImpressionStats,
DiscoveryStreamLoadedContent,
DiscoveryStreamSpocsFill,
};
// These are helpers to test for certain kinds of actions

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

@ -533,7 +533,7 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
};
}
return prevState;
case at.PLACES_LINK_BLOCKED:
case at.DISCOVERY_STREAM_LINK_BLOCKED:
return isNotReady() ? prevState :
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 {SnippetsTemplates} from "./templates/template-manifest";
import {StartupOverlay} from "./templates/StartupOverlay/StartupOverlay";
import {Trailhead} from "./templates/Trailhead/Trailhead";
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
const TEMPLATES_ABOVE_PAGE = ["trailhead"];
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
export const ASRouterUtils = {
@ -96,7 +98,8 @@ export class ASRouterUISurface extends React.PureComponent {
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
this.state = {message: {}, bundle: {}};
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() {
if (this.state.bundle.template === "onboarding" ||
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;
}
const SnippetComponent = SnippetsTemplates[this.state.message.template];
@ -288,6 +292,20 @@ export class ASRouterUISurface extends React.PureComponent {
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() {
if (this.state.message.provider !== "preview") {
return null;
@ -305,6 +323,7 @@ export class ASRouterUISurface extends React.PureComponent {
const {message, bundle} = this.state;
if (!message.id && !bundle.template) { return null; }
const shouldRenderBelowSearch = TEMPLATES_BELOW_SEARCH.includes(message.template);
const shouldRenderInHeader = TEMPLATES_ABOVE_PAGE.includes(message.template);
return shouldRenderBelowSearch ?
// Render special below search snippets in place;
@ -314,11 +333,12 @@ export class ASRouterUISurface extends React.PureComponent {
ReactDOM.createPortal(
<>
{this.renderPreviewBanner()}
{this.renderTrailhead()}
{this.renderFirstRunOverlay()}
{this.renderOnboarding()}
{this.renderSnippets()}
</>,
this.portalContainer
shouldRenderInHeader ? this.headerPortal : this.footerPortal
);
}
}

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

@ -1,30 +1,51 @@
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() {
this.setState({active: true});
document.body.classList.add("modal-open");
this.props.document.addEventListener("keydown", this.onKeyDown);
this.props.document.body.classList.add("modal-open");
}
componentWillUnmount() {
document.body.classList.remove("modal-open");
this.setState({active: false});
this.props.document.removeEventListener("keydown", this.onKeyDown);
this.props.document.body.classList.remove("modal-open");
}
render() {
const {active} = this.state;
const {title, button_label} = this.props;
return (
<div>
<div className={`modalOverlayOuter ${active ? "active" : ""}`} />
<div className={`modalOverlayInner ${active ? "active" : ""}`}>
<h2> {title} </h2>
{this.props.children}
<div className="footer">
<button tabIndex="2" onClick={this.props.onDoneButton} className="button primary modalButton"> {button_label} </button>
</div>
</div>
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;
return (
<ModalOverlayWrapper onClose={this.props.onDoneButton}>
<h2> {title} </h2>
{this.props.children}
<div className="footer">
<button className="button primary modalButton"
onClick={this.props.onDoneButton}> {button_label} </button>
</div>
</ModalOverlayWrapper>);
}
}

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

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

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

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

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

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

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

@ -55,43 +55,6 @@
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 {
height: 175px;
@ -164,3 +127,84 @@
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
document.body.classList.contains("welcome") ? "welcome" : "",
document.body.classList.contains("hide-main") ? "hide-main" : "",
document.body.classList.contains("inline-onboarding") ? "inline-onboarding" : "",
].filter(v => v).join(" ");
global.document.body.className = bodyClassName;
}
@ -158,7 +159,7 @@ export class BaseContent extends React.PureComponent {
</ErrorBoundary>
</div>
}
<ASRouterUISurface dispatch={this.props.dispatch} />
<ASRouterUISurface fxaEndpoint={this.props.Prefs.values.fxa_endpoint} dispatch={this.props.dispatch} />
<div className={`body-wrapper${(initialized ? " on" : "")}`}>
{isDiscoveryStream ? (
<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 {CollapsibleSection} from "content-src/components/CollapsibleSection/CollapsibleSection";
import {connect} from "react-redux";
@ -168,12 +169,19 @@ export class _DiscoveryStreamBase extends React.PureComponent {
render() {
// 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;
if (!spocs.loaded || !feeds.loaded) {
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
if (!config.collapsible) {
return this.renderLayout(layoutRender);

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -2,6 +2,9 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
const {layout, feeds, spocs} = state;
let spocIndex = 0;
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
// 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) {
const recommendations = [...data.recommendations];
for (let position of spocsConfig.positions) {
const spoc = spocs.data.spocs[spocIndex];
if (!spoc) {
break;
}
// Cache random number for a position
let rickRoll;
if (isFirstRun) {
@ -23,8 +31,12 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
bufferRollCache.push(rickRoll);
}
if (spocs.data.spocs[spocIndex] && rickRoll <= spocsConfig.probability) {
recommendations.splice(position.index, 0, spocs.data.spocs[spocIndex++]);
if (rickRoll <= spocsConfig.probability) {
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);
}
return layout.map(row => ({
const layoutRender = layout.map(row => ({
...row,
// Loops through desired components and adds a .data property
@ -94,4 +106,28 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
return {...component, data};
}),
})).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/EOYSnippet/EOYSnippet';
@import '../asrouter/templates/StartupOverlay/StartupOverlay';
@import '../asrouter/templates/Trailhead/Trailhead';

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

@ -41,3 +41,10 @@
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-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] {
// General styles
--newtab-background-color: #{$grey-80};
@ -136,5 +142,11 @@ body {
// Snippets
--newtab-snippets-background-color: #{$grey-70};
--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;
$red-60: #D70022;
$yellow-50: #FFE900;
$violet-20: #CB9EFF;
// Photon opacity from http://design.firefox.com/photon/visuals/color.html#opacity
$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-90: rgba($grey-90, 0.9);
$blue-50-30: rgba($blue-50, 0.3);
$black: #000;
$black-5: rgba($black, 0.05);
$black-10: rgba($black, 0.1);
@ -57,6 +60,9 @@ $black-30: rgba($black, 0.3);
// Other colors
$white: #FFF;
$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-red: #EF4056;
$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;
$firefox-wordmark-default-color: #363959;
$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-easing: cubic-bezier(0.07, 0.95, 0, 1);

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

@ -67,7 +67,11 @@ body {
--newtab-card-placeholder-color: #D7D7DB;
--newtab-card-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--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] {
--newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -111,7 +115,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--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 {
background-position: center center;
@ -1139,9 +1147,9 @@ main {
visibility: hidden; } }
@media (min-height: 701px) {
.fixed-search main {
body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
@ -1151,19 +1159,19 @@ main {
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 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; }
.fixed-search .search-handoff-button {
body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px;
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; }
.fixed-search .search-handoff-button .fake-caret {
body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } }
.contentSearchSuggestionTable {
@ -1876,6 +1884,11 @@ main {
border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card {
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) {
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) {
@ -1969,6 +1982,11 @@ main {
border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper {
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 {
border-top: 0;
border-bottom: 0;
@ -2204,6 +2222,12 @@ main {
[lwt-newtab-brighttext] .ds-list a {
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 {
counter-increment: list; }
@ -2403,6 +2427,11 @@ main {
margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer {
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 {
margin: 0 -12px; }
@ -2572,6 +2601,11 @@ main {
flex-direction: column;
justify-content: space-between;
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 {
display: flex;
flex-direction: column;
@ -2799,8 +2833,7 @@ main {
overflow: hidden; }
.modalOverlayOuter {
background: #FFF;
opacity: 0.93;
background: var(--newtab-overlay-color);
height: 100%;
position: fixed;
top: 0;
@ -3264,29 +3297,6 @@ main {
@media (max-width: 650px) {
.onboardingMessage {
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 {
height: 175px; }
.onboardingMessage .onboardingContent > span > h3 {
@ -3337,6 +3347,52 @@ main {
.onboardingMessage:last-child::before {
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 {
margin: 10px 0 8px;
align-self: start;
@ -3631,3 +3687,224 @@ a.firstrun-link {
100% {
opacity: 1;
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-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--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] {
--newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -114,7 +118,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--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 {
background-position: center center;
@ -1142,9 +1150,9 @@ main {
visibility: hidden; } }
@media (min-height: 701px) {
.fixed-search main {
body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
@ -1154,19 +1162,19 @@ main {
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 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; }
.fixed-search .search-handoff-button {
body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px;
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; }
.fixed-search .search-handoff-button .fake-caret {
body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } }
.contentSearchSuggestionTable {
@ -1879,6 +1887,11 @@ main {
border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card {
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) {
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) {
@ -1972,6 +1985,11 @@ main {
border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper {
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 {
border-top: 0;
border-bottom: 0;
@ -2207,6 +2225,12 @@ main {
[lwt-newtab-brighttext] .ds-list a {
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 {
counter-increment: list; }
@ -2406,6 +2430,11 @@ main {
margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer {
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 {
margin: 0 -12px; }
@ -2575,6 +2604,11 @@ main {
flex-direction: column;
justify-content: space-between;
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 {
display: flex;
flex-direction: column;
@ -2802,8 +2836,7 @@ main {
overflow: hidden; }
.modalOverlayOuter {
background: #FFF;
opacity: 0.93;
background: var(--newtab-overlay-color);
height: 100%;
position: fixed;
top: 0;
@ -3267,29 +3300,6 @@ main {
@media (max-width: 650px) {
.onboardingMessage {
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 {
height: 175px; }
.onboardingMessage .onboardingContent > span > h3 {
@ -3340,6 +3350,52 @@ main {
.onboardingMessage:last-child::before {
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 {
margin: 10px 0 8px;
align-self: start;
@ -3634,3 +3690,224 @@ a.firstrun-link {
100% {
opacity: 1;
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-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
--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] {
--newtab-background-color: #2A2A2E;
--newtab-border-primary-color: rgba(249, 249, 250, 0.8);
@ -111,7 +115,11 @@ body {
--newtab-card-placeholder-color: #4A4A4F;
--newtab-card-shadow: 0 1px 8px 0 rgba(12, 12, 13, 0.2);
--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 {
background-position: center center;
@ -1139,9 +1147,9 @@ main {
visibility: hidden; } }
@media (min-height: 701px) {
.fixed-search main {
body:not(.inline-onboarding) .fixed-search main {
padding-top: 146px; }
.fixed-search .search-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper {
background-color: var(--newtab-search-header-background-color);
border-bottom: solid 1px var(--newtab-border-secondary-color);
height: 95px;
@ -1151,19 +1159,19 @@ main {
top: 0;
width: 100%;
z-index: 9; }
.fixed-search .search-wrapper .search-inner-wrapper {
body:not(.inline-onboarding) .fixed-search .search-wrapper .search-inner-wrapper {
height: 35px; }
.fixed-search .search-wrapper input {
body:not(.inline-onboarding) .fixed-search .search-wrapper input {
background-position-x: 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; }
.fixed-search .search-handoff-button {
body:not(.inline-onboarding) .fixed-search .search-handoff-button {
background-position-x: 12px;
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; }
.fixed-search .search-handoff-button .fake-caret {
body:not(.inline-onboarding) .fixed-search .search-handoff-button .fake-caret {
top: 10px; } }
.contentSearchSuggestionTable {
@ -1876,6 +1884,11 @@ main {
border-radius: 4px; }
[lwt-newtab-brighttext] .ds-card-grid .ds-card {
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) {
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) {
@ -1969,6 +1982,11 @@ main {
border-top: 1px solid #4A4A4F; }
[lwt-newtab-brighttext] .ds-hero .wrapper {
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 {
border-top: 0;
border-bottom: 0;
@ -2204,6 +2222,12 @@ main {
[lwt-newtab-brighttext] .ds-list a {
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 {
counter-increment: list; }
@ -2403,6 +2427,11 @@ main {
margin: 0 -25px; }
.ds-top-sites .top-sites .top-site-outer {
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 {
margin: 0 -12px; }
@ -2572,6 +2601,11 @@ main {
flex-direction: column;
justify-content: space-between;
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 {
display: flex;
flex-direction: column;
@ -2799,8 +2833,7 @@ main {
overflow: hidden; }
.modalOverlayOuter {
background: #FFF;
opacity: 0.93;
background: var(--newtab-overlay-color);
height: 100%;
position: fixed;
top: 0;
@ -3264,29 +3297,6 @@ main {
@media (max-width: 650px) {
.onboardingMessage {
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 {
height: 175px; }
.onboardingMessage .onboardingContent > span > h3 {
@ -3337,6 +3347,52 @@ main {
.onboardingMessage:last-child::before {
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 {
margin: 10px 0 8px;
align-self: start;
@ -3631,3 +3687,224 @@ a.firstrun-link {
100% {
opacity: 1;
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
```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:
| `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:
| `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:**

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

@ -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
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
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
[localization] en-US.jar:
trailhead.ftl (./data/trailhead.wip)
browser.jar:
% resource activity-stream %res/activity-stream/ contentaccessible=yes
res/activity-stream/lib/ (./lib/*)

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

@ -695,18 +695,34 @@ class _ASRouter {
}
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
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) {
// Forcefully show the messages without targeting matching - this is for about:newtab#asrouter to show the messages
for (const message of bundledMessagesOfSameTemplate) {
result.push({content: message.content, id: message.id});
// Stop once we have enough messages to fill a bundle
if (result.length === originalMessage.bundled) {
if (result.length === bundleLength) {
break;
}
}
@ -723,14 +739,14 @@ class _ASRouter {
result.push({content: message.content, id: message.id, order: message.order || 0});
bundledMessagesOfSameTemplate.splice(bundledMessagesOfSameTemplate.findIndex(msg => msg.id === message.id), 1);
// Stop once we have enough messages to fill a bundle
if (result.length === originalMessage.bundled) {
if (result.length === bundleLength) {
break;
}
}
}
// 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;
}
@ -739,7 +755,12 @@ class _ASRouter {
// handle finding these strings on its own. See bug 1488973
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) {
@ -776,7 +797,16 @@ class _ASRouter {
} else if (message.bundled) {
const bundledMessages = await this._getBundledMessages(message, target, trigger, force);
const action = bundledMessages ? {type: "SET_BUNDLED_MESSAGES", data: bundledMessages} : {type: "CLEAR_ALL"};
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
try {
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
} else if (message.template === "cfr_doorhanger") {
@ -788,7 +818,9 @@ class _ASRouter {
// New tab single messages
} else {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "SET_MESSAGE", data: message});
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "SET_MESSAGE", data: message});
} catch (e) {}
}
}

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

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

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

@ -42,6 +42,32 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
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) {
if (url.includes("$apiKey") && !apiKey) {
throw new Error(`Layout Endpoint - An API key was specified but none configured: ${url}`);
@ -266,9 +292,10 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
filterRecommendations(feed) {
if (feed && feed.data && feed.data.recommendations && feed.data.recommendations.length) {
const {data} = this.filterBlocked(feed.data, "recommendations");
return {
...feed,
data: this.filterBlocked(feed.data, "recommendations"),
data,
};
}
return feed;
@ -366,13 +393,17 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
data: {},
};
let {data, filtered: frequencyCapped} = this.frequencyCapSpocs(spocs.data);
let {data: newSpocs, filtered} = this.transform(data);
sendUpdate({
type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: {
lastUpdated: spocs.lastUpdated,
spocs: this.transform(this.frequencyCapSpocs(spocs.data)),
spocs: newSpocs,
},
});
this._sendSpocsFill({...filtered, frequency_cap: frequencyCapped}, true);
}
async loadAffinityScoresCache() {
@ -418,11 +449,19 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
}
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.
.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((a, b) => b.score - a.score);
return {data, filtered};
}
scoreItem(item) {
@ -441,45 +480,67 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
}
filterBlocked(data, type) {
const filtered = [];
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 {
...data,
[type]: filteredItems,
data: {
...data,
[type]: filteredItems,
},
filtered,
};
}
return data;
return {data, filtered};
}
transform(spocs) {
const data = this.filterBlocked(spocs, "spocs");
const {data, filtered: blockedItems} = this.filterBlocked(spocs, "spocs");
if (data && data.spocs && data.spocs.length) {
const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
const campaignMap = {};
const campaignDuplicates = [];
// This order of operations is intended.
// scoreItems must be first because it creates this.score.
const {data: items, filtered: belowMinScoreItems} = this.scoreItems(data.spocs);
// This removes campaign dupes.
// 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.
const newSpocs = items.filter(s => {
if (!campaignMap[s.campaign_id]) {
campaignMap[s.campaign_id] = 1;
return true;
} else if (campaignMap[s.campaign_id] < spocsPerDomain) {
campaignMap[s.campaign_id]++;
return true;
}
campaignDuplicates.push(s);
return false;
});
return {
...data,
// This order of operations is intended.
// scoreItems must be first because it creates this.score.
spocs: this.scoreItems(data.spocs)
// This removes campaign dupes.
// 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.
.filter(s => {
if (!campaignMap[s.campaign_id]) {
campaignMap[s.campaign_id] = 1;
return true;
} else if (campaignMap[s.campaign_id] < spocsPerDomain) {
campaignMap[s.campaign_id]++;
return true;
}
return false;
}),
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
//
// @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) {
if (data && data.spocs && data.spocs.length) {
const {spocs} = data;
@ -499,9 +560,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
if (caps.length) {
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.
@ -549,7 +610,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
if (this.isExpired({cachedData, key: "feed", url: feedUrl, isStartup})) {
const feedResponse = await this.fetchFromEndpoint(feedUrl);
if (feedResponse) {
const scoredItems = this.scoreItems(feedResponse.recommendations);
const {data: scoredItems} = this.scoreItems(feedResponse.recommendations);
const {recsExpireTime} = feedResponse.settings;
const recommendations = this.rotate(scoredItems, recsExpireTime);
this.componentFeedFetched = true;
@ -867,10 +928,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
// Apply frequency capping to SPOCs in the redux store, only update the
// store if the SPOCs are changed.
const {spocs} = this.store.getState().DiscoveryStream;
const newSpocs = this.frequencyCapSpocs(spocs.data);
const prevSpocs = spocs.data.spocs || [];
const currentSpocs = newSpocs.spocs || [];
if (prevSpocs.length !== currentSpocs.length) {
const {data: newSpocs, filtered} = this.frequencyCapSpocs(spocs.data);
if (filtered.length) {
this.store.dispatch(ac.AlsoToPreloaded({
type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
data: {
@ -878,9 +937,24 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
spocs: newSpocs,
},
}));
this._sendSpocsFill({frequency_cap: filtered}, false);
}
}
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:
// When this feed is shutting down:
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"},
},
{
@ -119,7 +119,7 @@ const ONBOARDING_MESSAGES = async () => ([
},
},
},
targeting: "providerCohorts.onboarding == 'ghostery'",
targeting: "trailheadCohort == 0 && providerCohorts.onboarding == 'ghostery'",
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"},
},
{
@ -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
if (msg.content.secondary_button) {
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) {
return Object.assign(
this.createPing(au.getPortIdOfSender(action)),
@ -733,6 +745,9 @@ this.TelemetryFeed = class TelemetryFeed {
case at.DISCOVERY_STREAM_LOADED_CONTENT:
this.handleDiscoveryStreamLoadedContent(au.getPortIdOfSender(action), action.data);
break;
case at.DISCOVERY_STREAM_SPOCS_FILL:
this.handleDiscoveryStreamSpocsFill(action.data);
break;
case at.TELEMETRY_UNDESIRED_EVENT:
this.handleUndesiredEvent(action);
break;
@ -805,6 +820,33 @@ this.TelemetryFeed = class TelemetryFeed {
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
* 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_content_discovery_header=Firefoxen hasiera
prefs_content_discovery_description=Firefoxen hasierako edukien aurkikuntzaren bidez kalitate altuko artikulu esanguratsuak aurki ditzakezu webean.
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
# firstrun of the browser, they give an introduction to Firefox and Sync.
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
# LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):

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

@ -93,6 +93,8 @@ prefs_home_description=ਉਹ ਸਮੱਗਰੀ ਚੁਣੋ ਜੋ ਤੁਸ
prefs_content_discovery_header=ਫਾਇਰਫਾਕਸ ਮੁੱਖ ਸਫ਼ਾ
prefs_content_discovery_button=ਸਮੱਗਰੀ ਖੋਜ ਬੰਦ ਕਰੋ
# 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).
# 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 \
browser/base/content/test/about/browser_aboutHome_search_telemetry.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/preferences/in-content/tests/browser_hometab_restore_defaults.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"
}
},
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
@ -856,6 +866,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@ -913,6 +929,15 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"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": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -2222,6 +2247,12 @@
"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": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@ -3085,6 +3116,22 @@
"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": {
"version": "1.1.1",
"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-import": "2.16.0",
"eslint-plugin-json": "1.4.0",
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-mozilla": "1.1.1",
"eslint-plugin-no-unsanitized": "3.0.2",
"eslint-plugin-promise": "4.0.1",
@ -135,6 +136,7 @@
"debugcoverage": "open logs/coverage/index.html",
"lint": "npm-run-all lint:*",
"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",
"strings-import": "node ./bin/strings-import.js",
"test": "npm run testmc",

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -9,8 +9,9 @@
<link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
</head>
<body class="activity-stream">
<div id="header-asrouter-container"></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/contentTheme.js"></script>
<script src="resource://activity-stream/vendor/react.js"></script>

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