Bug 1576284 - Add Firefox wordmark, protection template and bug fixes to New Tab Page r=pdahiya

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ed Lee 2019-08-24 06:35:25 +00:00
Родитель 294674d0c5
Коммит 3cf7e1697b
38 изменённых файлов: 964 добавлений и 193 удалений

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

@ -62,6 +62,7 @@ const INITIAL_STATE = {
},
spocs: {
spocs_endpoint: "",
spocs_per_domain: 1,
lastUpdated: null,
data: {}, // {spocs: []}
loaded: false,
@ -597,7 +598,11 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
spocs: {
...INITIAL_STATE.DiscoveryStream.spocs,
spocs_endpoint:
action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
action.data.url ||
INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
spocs_per_domain:
action.data.spocs_per_domain ||
INITIAL_STATE.DiscoveryStream.spocs.spocs_per_domain,
},
};
case at.DISCOVERY_STREAM_SPOCS_UPDATE:

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

@ -38,6 +38,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [isWhatsNewPanelEnabled](#iswhatsnewpanelenabled)
* [earliestFirefoxVersion](#earliestfirefoxversion)
* [isFxABadgeEnabled](#isfxabadgeenabled)
* [totalBlockedCount](#totalblockedcount)
## Detailed usage
@ -518,3 +519,13 @@ Boolean pref that controls if the FxA toolbar button is badged by Messaging Syst
```ts
declare const isFxABadgeEnabled: boolean;
```
### `totalBlockedCount`
Total number of events from the content blocking database
#### Definition
```ts
declare const totalBlockedCount: number;
```

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

@ -31,6 +31,7 @@ export class Interrupt extends React.PureComponent {
>
<ReturnToAMO
{...message}
document={this.props.document}
UISurface="NEWTAB_OVERLAY"
onBlock={onDismiss}
onAction={executeAction}
@ -41,6 +42,7 @@ export class Interrupt extends React.PureComponent {
case "fxa_overlay":
return (
<StartupOverlay
document={this.props.document}
onBlock={onDismiss}
dispatch={dispatch}
fxa_endpoint={fxaEndpoint}

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

@ -24,6 +24,10 @@
}
},
"properties": {
"layout": {
"description": "Different message layouts",
"enum": ["tracking-protections"]
},
"published_date": {
"type": "integer",
"description": "The date/time (number of milliseconds elapsed since January 1, 1970 00:00:00 UTC) the message was published."
@ -34,6 +38,12 @@
{"description": "Id of localized string or message override of What's New message title"}
]
},
"subtitle": {
"allOf": [
{"$ref": "#/definitions/localizableText"},
{"description": "Id of localized string or message override of What's New message subtitle"}
]
},
"body": {
"allOf": [
{"$ref": "#/definitions/localizableText"},
@ -51,6 +61,10 @@
"type": "string",
"format": "uri"
},
"cta_type": {
"description": "Type of url open action",
"enum": ["OPEN_URL", "OPEN_ABOUT_PAGE"]
},
"icon_url": {
"description": "(optional) URL for the What's New message icon.",
"type": "string",

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

@ -24,6 +24,10 @@ export class ReturnToAMO extends React.PureComponent {
event: "IMPRESSION",
id: this.props.UISurface,
});
// Hide the page content from screen readers while the modal is open
this.props.document
.getElementById("root")
.setAttribute("aria-hidden", "true");
}
onClickAddExtension() {
@ -41,6 +45,10 @@ export class ReturnToAMO extends React.PureComponent {
event: "BLOCK",
id: this.props.UISurface,
});
// Re-enable the document for screen readers
this.props.document
.getElementById("root")
.setAttribute("aria-hidden", "false");
}
renderText() {

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

@ -91,7 +91,12 @@
}
@media (max-width: 865px) {
margin: 0 60px 0 0;
margin-inline-start: 0;
}
// There is an off-by-one gap between breakpoints; this is to prevent weirdness at exactly 610px.
@media (max-width: $break-point-medium - 1px) {
margin: auto;
}
// Disable breakpoints for now if discovery stream is enabled.
@ -142,6 +147,10 @@
@include full-width-styles;
}
@media (max-width: $break-point-medium) {
margin: auto;
}
// Disable breakpoints for now if discovery stream is enabled.
.ds-outer-wrapper-breakpoint-override & {
@include full-width-styles;
@ -170,10 +179,6 @@
inset-inline-end: 2%;
}
@media (max-width: 865px) {
margin-top: 10px;
}
.ds-outer-wrapper-breakpoint-override & {
inset-inline-end: -10%;
margin: auto;
@ -190,11 +195,25 @@
flex-shrink: 0;
margin: auto 0;
margin-inline-end: 10px;
@media (max-width: $break-point-medium) {
margin: auto;
}
}
.buttonContainer {
margin: auto;
margin-inline-end: 0;
@media (max-width: $break-point-medium) {
margin: auto;
}
}
}
button {
@media (max-width: $break-point-medium) {
margin: auto;
}
}

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

@ -33,12 +33,20 @@ export class StartupOverlay extends React.PureComponent {
setTimeout(() => {
this.setState({ show: true });
}, 10);
// Hide the page content from screen readers while the modal is open
this.props.document
.getElementById("root")
.setAttribute("aria-hidden", "true");
}
removeOverlay() {
window.removeEventListener("visibilitychange", this.removeOverlay);
document.body.classList.remove("hide-main", "fxa");
this.setState({ show: false });
// Re-enable the document for screen readers
this.props.document
.getElementById("root")
.setAttribute("aria-hidden", "false");
setTimeout(() => {
// Allow scrolling and fully remove overlay after animation finishes.

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

@ -7,6 +7,7 @@ import { CardGrid } from "content-src/components/DiscoveryStreamComponents/CardG
import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
import { connect } from "react-redux";
import { DSMessage } from "content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage";
import { DSTextPromo } from "content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo";
import { Hero } from "content-src/components/DiscoveryStreamComponents/Hero/Hero";
import { Highlights } from "content-src/components/DiscoveryStreamComponents/Highlights/Highlights";
import { HorizontalRule } from "content-src/components/DiscoveryStreamComponents/HorizontalRule/HorizontalRule";
@ -114,6 +115,17 @@ export class _DiscoveryStreamBase extends React.PureComponent {
return <Highlights />;
case "TopSites":
return <TopSites header={component.header} />;
case "TextPromo":
return (
<DSTextPromo
image={component.properties.image_src}
alt_text={component.properties.alt_text}
header={component.properties.excerpt}
cta_text={component.properties.cta_text}
cta_url={component.properties.cta_url}
subtitle={component.properties.context}
/>
);
case "Message":
return (
<DSMessage

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

@ -10,6 +10,7 @@ import React from "react";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { DSContextFooter } from "../DSContextFooter/DSContextFooter.jsx";
// Default Meta that displays CTA as link if cta_variant in layout is set as "link"
export const DefaultMeta = ({
source,
title,
@ -18,13 +19,14 @@ export const DefaultMeta = ({
context_type,
cta,
engagement,
cta_variant,
}) => (
<div className="meta">
<div className="info-wrap">
<p className="source clamp">{source}</p>
<header className="title clamp">{title}</header>
{excerpt && <p className="excerpt clamp">{excerpt}</p>}
{cta && (
{cta_variant === "link" && cta && (
<div role="link" className="cta-link icon icon-arrow" tabIndex="0">
{cta}
</div>
@ -38,7 +40,7 @@ export const DefaultMeta = ({
</div>
);
export const VariantMeta = ({
export const CTAButtonMeta = ({
source,
title,
excerpt,
@ -147,6 +149,8 @@ export class DSCard extends React.PureComponent {
<div className="ds-card placeholder" ref={this.setPlaceholderRef} />
);
}
const isButtonCTA = this.props.cta_variant === "button";
return (
<div className="ds-card">
<SafeAnchor
@ -162,8 +166,8 @@ export class DSCard extends React.PureComponent {
rawSource={this.props.raw_image_src}
/>
</div>
{this.props.cta_variant && (
<VariantMeta
{isButtonCTA ? (
<CTAButtonMeta
source={this.props.source}
title={this.props.title}
excerpt={this.props.excerpt}
@ -173,8 +177,7 @@ export class DSCard extends React.PureComponent {
cta={this.props.cta}
sponsor={this.props.sponsor}
/>
)}
{!this.props.cta_variant && (
) : (
<DefaultMeta
source={this.props.source}
title={this.props.title}
@ -183,6 +186,7 @@ export class DSCard extends React.PureComponent {
engagement={this.props.engagement}
context_type={this.props.context_type}
cta={this.props.cta}
cta_variant={this.props.cta_variant}
/>
)}
<ImpressionStats

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

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
export class DSTextPromo extends React.PureComponent {
render() {
return (
<div className="ds-text-promo">
<img src={this.props.image} alt={this.props.alt_text} />
<div className="text">
<h3>
{`${this.props.header}\u2003`}
<SafeAnchor className="ds-chevron-link" url={this.props.cta_url}>
{this.props.cta_text}
</SafeAnchor>
</h3>
<p className="subtitle">{this.props.subtitle}</p>
</div>
</div>
);
}
}

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

@ -0,0 +1,87 @@
.ds-text-promo {
display: flex;
max-width: 744px;
margin: 16px auto;
img {
width: 40px;
height: 40px;
margin: 0 12px 0 0;
border-radius: 4px;
}
.text {
line-height: 24px;
margin: -4.5px 0 0;
}
h3 {
@include dark-theme-only {
color: $grey-10;
}
margin: 0;
font-weight: 600;
font-size: 15px;
}
.subtitle {
@include dark-theme-only {
color: $grey-40;
}
font-size: 13px;
margin: 0;
color: $grey-50;
}
}
.ds-chevron-link {
color: $blue-60;
display: inline-block;
outline: 0;
&:hover {
text-decoration: underline;
}
&:active {
@include dark-theme-only {
color: $blue-50;
}
color: $blue-70;
&::after {
@include dark-theme-only {
background-color: $blue-50;
}
background-color: $blue-70;
}
}
&:focus {
@include dark-theme-only {
box-shadow: 0 0 0 2px $grey-80, 0 0 0 5px $blue-50-50;
}
box-shadow: 0 0 0 2px $white, 0 0 0 5px $blue-50-50;
border-radius: 2px;
}
&::after {
@include dark-theme-only {
background-color: $blue-40;
}
content: ' ';
mask: url('#{$image-path}glyph-caret-right.svg') 0 -8px no-repeat;
background-color: $blue-60;
margin: 0 0 0 4px;
width: 5px;
height: 8px;
text-decoration: none;
display: inline-block;
}
}

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

@ -13,8 +13,8 @@ $glyph-forward: url('chrome://browser/skin/forward.svg');
}
.logo-and-wordmark {
$logo-size: 97px;
$wordmark-size: 142px;
$logo-size: 96px;
$wordmark-size: 172px;
align-items: center;
display: flex;

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

@ -162,6 +162,7 @@ input {
@import '../components/DiscoveryStreamComponents/DSMessage/DSMessage';
@import '../components/DiscoveryStreamImpressionStats/ImpressionStats';
@import '../components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState';
@import '../components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo';
// AS Router
@import '../asrouter/components/Button/Button';

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

@ -53,6 +53,7 @@ $grey-90-90: rgba($grey-90, 0.9);
$blue-40-40: rgba($blue-40, 0.4);
$blue-50-50: rgba($blue-50, 0.5);
$blue-50-30: rgba($blue-50, 0.3);
$blue-50-50: rgba($blue-50, 0.5);
$black: #000;
$black-5: rgba($black, 0.05);

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

@ -1020,19 +1020,19 @@ main {
margin-bottom: 49px; }
.search-wrapper .logo-and-wordmark .logo {
background: url("chrome://branding/content/icon128.png") no-repeat center center;
background-size: 97px;
background-size: 96px;
display: inline-block;
height: 97px;
width: 97px; }
height: 96px;
width: 96px; }
.search-wrapper .logo-and-wordmark .wordmark {
background: url("../data/content/assets/firefox-wordmark.svg") no-repeat center center;
background-size: 142px;
background-size: 172px;
-moz-context-properties: fill;
display: inline-block;
fill: var(--newtab-search-wordmark-color);
height: 97px;
height: 96px;
margin-inline-start: 15px;
width: 142px; }
width: 172px; }
@media (max-width: 609px) {
.search-wrapper .logo-and-wordmark .logo {
background-size: 64px;
@ -2976,6 +2976,62 @@ main {
to {
transform: rotate(360deg); } }
.ds-text-promo {
display: flex;
max-width: 744px;
margin: 16px auto; }
.ds-text-promo img {
width: 40px;
height: 40px;
margin: 0 12px 0 0;
border-radius: 4px; }
.ds-text-promo .text {
line-height: 24px;
margin: -4.5px 0 0; }
.ds-text-promo h3 {
margin: 0;
font-weight: 600;
font-size: 15px; }
[lwt-newtab-brighttext] .ds-text-promo h3 {
color: #F9F9FA; }
.ds-text-promo .subtitle {
font-size: 13px;
margin: 0;
color: #737373; }
[lwt-newtab-brighttext] .ds-text-promo .subtitle {
color: #B1B1B3; }
.ds-chevron-link {
color: #0060DF;
display: inline-block;
outline: 0; }
.ds-chevron-link:hover {
text-decoration: underline; }
.ds-chevron-link:active {
color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active {
color: #0A84FF; }
.ds-chevron-link:active::after {
background-color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active::after {
background-color: #0A84FF; }
.ds-chevron-link:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5);
border-radius: 2px; }
[lwt-newtab-brighttext] .ds-chevron-link:focus {
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-chevron-link::after {
content: ' ';
mask: url("../data/content/assets/glyph-caret-right.svg") 0 -8px no-repeat;
background-color: #0060DF;
margin: 0 0 0 4px;
width: 5px;
height: 8px;
text-decoration: none;
display: inline-block; }
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;
@ -3359,7 +3415,10 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
margin: 0 60px; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: 0 60px 0 0; } }
margin-inline-start: 0; } }
@media (max-width: 609px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .innerWrapper {
align-items: flex-start;
background-color: transparent;
@ -3394,6 +3453,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet .icon {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; }
@ -3414,9 +3476,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
margin-top: 10px; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: -10%;
margin: auto; }
@ -3429,9 +3488,18 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
flex-shrink: 0;
margin: auto 0;
margin-inline-end: 10px; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .icon {
margin: auto; } }
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto;
margin-inline-end: 0; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet button {
margin: auto; } }
.SimpleBelowSearchSnippet .body {
display: inline;
position: sticky;

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

@ -1023,19 +1023,19 @@ main {
margin-bottom: 49px; }
.search-wrapper .logo-and-wordmark .logo {
background: url("chrome://branding/content/icon128.png") no-repeat center center;
background-size: 97px;
background-size: 96px;
display: inline-block;
height: 97px;
width: 97px; }
height: 96px;
width: 96px; }
.search-wrapper .logo-and-wordmark .wordmark {
background: url("../data/content/assets/firefox-wordmark.svg") no-repeat center center;
background-size: 142px;
background-size: 172px;
-moz-context-properties: fill;
display: inline-block;
fill: var(--newtab-search-wordmark-color);
height: 97px;
height: 96px;
margin-inline-start: 15px;
width: 142px; }
width: 172px; }
@media (max-width: 609px) {
.search-wrapper .logo-and-wordmark .logo {
background-size: 64px;
@ -2979,6 +2979,62 @@ main {
to {
transform: rotate(360deg); } }
.ds-text-promo {
display: flex;
max-width: 744px;
margin: 16px auto; }
.ds-text-promo img {
width: 40px;
height: 40px;
margin: 0 12px 0 0;
border-radius: 4px; }
.ds-text-promo .text {
line-height: 24px;
margin: -4.5px 0 0; }
.ds-text-promo h3 {
margin: 0;
font-weight: 600;
font-size: 15px; }
[lwt-newtab-brighttext] .ds-text-promo h3 {
color: #F9F9FA; }
.ds-text-promo .subtitle {
font-size: 13px;
margin: 0;
color: #737373; }
[lwt-newtab-brighttext] .ds-text-promo .subtitle {
color: #B1B1B3; }
.ds-chevron-link {
color: #0060DF;
display: inline-block;
outline: 0; }
.ds-chevron-link:hover {
text-decoration: underline; }
.ds-chevron-link:active {
color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active {
color: #0A84FF; }
.ds-chevron-link:active::after {
background-color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active::after {
background-color: #0A84FF; }
.ds-chevron-link:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5);
border-radius: 2px; }
[lwt-newtab-brighttext] .ds-chevron-link:focus {
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-chevron-link::after {
content: ' ';
mask: url("../data/content/assets/glyph-caret-right.svg") 0 -8px no-repeat;
background-color: #0060DF;
margin: 0 0 0 4px;
width: 5px;
height: 8px;
text-decoration: none;
display: inline-block; }
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;
@ -3362,7 +3418,10 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
margin: 0 60px; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: 0 60px 0 0; } }
margin-inline-start: 0; } }
@media (max-width: 609px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .innerWrapper {
align-items: flex-start;
background-color: transparent;
@ -3397,6 +3456,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet .icon {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; }
@ -3417,9 +3479,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
margin-top: 10px; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: -10%;
margin: auto; }
@ -3432,9 +3491,18 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
flex-shrink: 0;
margin: auto 0;
margin-inline-end: 10px; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .icon {
margin: auto; } }
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto;
margin-inline-end: 0; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet button {
margin: auto; } }
.SimpleBelowSearchSnippet .body {
display: inline;
position: sticky;

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

@ -1020,19 +1020,19 @@ main {
margin-bottom: 49px; }
.search-wrapper .logo-and-wordmark .logo {
background: url("chrome://branding/content/icon128.png") no-repeat center center;
background-size: 97px;
background-size: 96px;
display: inline-block;
height: 97px;
width: 97px; }
height: 96px;
width: 96px; }
.search-wrapper .logo-and-wordmark .wordmark {
background: url("../data/content/assets/firefox-wordmark.svg") no-repeat center center;
background-size: 142px;
background-size: 172px;
-moz-context-properties: fill;
display: inline-block;
fill: var(--newtab-search-wordmark-color);
height: 97px;
height: 96px;
margin-inline-start: 15px;
width: 142px; }
width: 172px; }
@media (max-width: 609px) {
.search-wrapper .logo-and-wordmark .logo {
background-size: 64px;
@ -2976,6 +2976,62 @@ main {
to {
transform: rotate(360deg); } }
.ds-text-promo {
display: flex;
max-width: 744px;
margin: 16px auto; }
.ds-text-promo img {
width: 40px;
height: 40px;
margin: 0 12px 0 0;
border-radius: 4px; }
.ds-text-promo .text {
line-height: 24px;
margin: -4.5px 0 0; }
.ds-text-promo h3 {
margin: 0;
font-weight: 600;
font-size: 15px; }
[lwt-newtab-brighttext] .ds-text-promo h3 {
color: #F9F9FA; }
.ds-text-promo .subtitle {
font-size: 13px;
margin: 0;
color: #737373; }
[lwt-newtab-brighttext] .ds-text-promo .subtitle {
color: #B1B1B3; }
.ds-chevron-link {
color: #0060DF;
display: inline-block;
outline: 0; }
.ds-chevron-link:hover {
text-decoration: underline; }
.ds-chevron-link:active {
color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active {
color: #0A84FF; }
.ds-chevron-link:active::after {
background-color: #003EAA; }
[lwt-newtab-brighttext] .ds-chevron-link:active::after {
background-color: #0A84FF; }
.ds-chevron-link:focus {
box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5);
border-radius: 2px; }
[lwt-newtab-brighttext] .ds-chevron-link:focus {
box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
.ds-chevron-link::after {
content: ' ';
mask: url("../data/content/assets/glyph-caret-right.svg") 0 -8px no-repeat;
background-color: #0060DF;
margin: 0 0 0 4px;
width: 5px;
height: 8px;
text-decoration: none;
display: inline-block; }
[lwt-newtab-brighttext] .ds-chevron-link::after {
background-color: #45A1FF; }
.ASRouterButton {
font-weight: 600;
font-size: 14px;
@ -3359,7 +3415,10 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
margin: 0 60px; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: 0 60px 0 0; } }
margin-inline-start: 0; } }
@media (max-width: 609px) {
.SimpleBelowSearchSnippet .innerWrapper {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .innerWrapper {
align-items: flex-start;
background-color: transparent;
@ -3394,6 +3453,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet .icon {
margin: auto; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet .icon {
height: 24px;
width: 24px; }
@ -3414,9 +3476,6 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
@media (max-width: 865px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
margin-top: 10px; } }
.ds-outer-wrapper-breakpoint-override .SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: -10%;
margin: auto; }
@ -3429,9 +3488,18 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
flex-shrink: 0;
margin: auto 0;
margin-inline-end: 10px; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .icon {
margin: auto; } }
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto;
margin-inline-end: 0; }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet.withButton .buttonContainer {
margin: auto; } }
@media (max-width: 610px) {
.SimpleBelowSearchSnippet button {
margin: auto; } }
.SimpleBelowSearchSnippet .body {
display: inline;
position: sticky;

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

@ -3142,7 +3142,9 @@ class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCompo
this.props.sendUserActionTelemetry({
event: "IMPRESSION",
id: this.props.UISurface
});
}); // Hide the page content from screen readers while the modal is open
this.props.document.getElementById("root").setAttribute("aria-hidden", "true");
}
onClickAddExtension() {
@ -3159,7 +3161,9 @@ class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCompo
this.props.sendUserActionTelemetry({
event: "BLOCK",
id: this.props.UISurface
});
}); // Re-enable the document for screen readers
this.props.document.getElementById("root").setAttribute("aria-hidden", "false");
}
renderText() {
@ -3247,7 +3251,9 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
this.setState({
show: true
});
}, 10);
}, 10); // Hide the page content from screen readers while the modal is open
this.props.document.getElementById("root").setAttribute("aria-hidden", "true");
}
removeOverlay() {
@ -3255,7 +3261,9 @@ class StartupOverlay extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureCo
document.body.classList.remove("hide-main", "fxa");
this.setState({
show: false
});
}); // Re-enable the document for screen readers
this.props.document.getElementById("root").setAttribute("aria-hidden", "false");
setTimeout(() => {
// Allow scrolling and fully remove overlay after animation finishes.
this.props.onBlock();
@ -7931,6 +7939,7 @@ class DSContextFooter_DSContextFooter extends external_React_default.a.PureCompo
// Default Meta that displays CTA as link if cta_variant in layout is set as "link"
const DefaultMeta = ({
source,
@ -7939,7 +7948,8 @@ const DefaultMeta = ({
context,
context_type,
cta,
engagement
engagement,
cta_variant
}) => external_React_default.a.createElement("div", {
className: "meta"
}, external_React_default.a.createElement("div", {
@ -7950,7 +7960,7 @@ const DefaultMeta = ({
className: "title clamp"
}, title), excerpt && external_React_default.a.createElement("p", {
className: "excerpt clamp"
}, excerpt), cta && external_React_default.a.createElement("div", {
}, excerpt), cta_variant === "link" && cta && external_React_default.a.createElement("div", {
role: "link",
className: "cta-link icon icon-arrow",
tabIndex: "0"
@ -7959,7 +7969,7 @@ const DefaultMeta = ({
context: context,
engagement: engagement
}));
const VariantMeta = ({
const CTAButtonMeta = ({
source,
title,
excerpt,
@ -8059,6 +8069,7 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
});
}
const isButtonCTA = this.props.cta_variant === "button";
return external_React_default.a.createElement("div", {
className: "ds-card"
}, external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
@ -8072,7 +8083,7 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
extraClassNames: "img",
source: this.props.image_src,
rawSource: this.props.raw_image_src
})), this.props.cta_variant && external_React_default.a.createElement(VariantMeta, {
})), isButtonCTA ? external_React_default.a.createElement(CTAButtonMeta, {
source: this.props.source,
title: this.props.title,
excerpt: this.props.excerpt,
@ -8081,14 +8092,15 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
engagement: this.props.engagement,
cta: this.props.cta,
sponsor: this.props.sponsor
}), !this.props.cta_variant && external_React_default.a.createElement(DefaultMeta, {
}) : external_React_default.a.createElement(DefaultMeta, {
source: this.props.source,
title: this.props.title,
excerpt: this.props.excerpt,
context: this.props.context,
engagement: this.props.engagement,
context_type: this.props.context_type,
cta: this.props.cta
cta: this.props.cta,
cta_variant: this.props.cta_variant
}), external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
campaignId: this.props.campaignId,
rows: [{
@ -8325,6 +8337,30 @@ class DSMessage_DSMessage extends external_React_default.a.PureComponent {
}, this.props.link_text)));
}
}
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
class DSTextPromo_DSTextPromo extends external_React_default.a.PureComponent {
render() {
return external_React_default.a.createElement("div", {
className: "ds-text-promo"
}, external_React_default.a.createElement("img", {
src: this.props.image,
alt: this.props.alt_text
}), external_React_default.a.createElement("div", {
className: "text"
}, external_React_default.a.createElement("h3", null, `${this.props.header}\u2003`, external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
className: "ds-chevron-link",
url: this.props.cta_url
}, this.props.cta_text)), external_React_default.a.createElement("p", {
className: "subtitle"
}, this.props.subtitle)));
}
}
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/List/List.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@ -9036,6 +9072,7 @@ const TopSites_TopSites_TopSites = Object(external_ReactRedux_["connect"])(state
const ALLOWED_CSS_URL_PREFIXES = ["chrome://", "resource://", "https://img-getpocket.cdn.mozilla.net/"];
const DUMMY_CSS_SELECTOR = "DUMMY#CSS.SELECTOR";
let rickRollCache = []; // Cache of random probability values for a spoc position
@ -9120,6 +9157,16 @@ class DiscoveryStreamBase_DiscoveryStreamBase extends external_React_default.a.P
header: component.header
});
case "TextPromo":
return external_React_default.a.createElement(DSTextPromo_DSTextPromo, {
image: component.properties.image_src,
alt_text: component.properties.alt_text,
header: component.properties.excerpt,
cta_text: component.properties.cta_text,
cta_url: component.properties.cta_url,
subtitle: component.properties.context
});
case "Message":
return external_React_default.a.createElement(DSMessage_DSMessage, {
title: component.header && component.header.title,
@ -12823,6 +12870,7 @@ const INITIAL_STATE = {
},
spocs: {
spocs_endpoint: "",
spocs_per_domain: 1,
lastUpdated: null,
data: {},
// {spocs: []}
@ -13439,7 +13487,8 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
case Actions["actionTypes"].DISCOVERY_STREAM_SPOCS_ENDPOINT:
return { ...prevState,
spocs: { ...INITIAL_STATE.DiscoveryStream.spocs,
spocs_endpoint: action.data || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint
spocs_endpoint: action.data.url || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
spocs_per_domain: action.data.spocs_per_domain || INITIAL_STATE.DiscoveryStream.spocs.spocs_per_domain
}
};
@ -13626,6 +13675,7 @@ class Interrupt_Interrupt extends external_React_default.a.PureComponent {
amo_html: message.content.text
})
}, external_React_default.a.createElement(ReturnToAMO["ReturnToAMO"], _extends({}, message, {
document: this.props.document,
UISurface: "NEWTAB_OVERLAY",
onBlock: onDismiss,
onAction: executeAction,
@ -13634,6 +13684,7 @@ class Interrupt_Interrupt extends external_React_default.a.PureComponent {
case "fxa_overlay":
return external_React_default.a.createElement(StartupOverlay["StartupOverlay"], {
document: this.props.document,
onBlock: onDismiss,
dispatch: dispatch,
fxa_endpoint: fxaEndpoint

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

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="38"><path fill="context-fill" d="M136.837 10.212H131.5l-5.7 9.9-5.644-9.9h-5.542l8.364 12.778-9.438 14.265h5.337l6.723-11.443 6.62 11.443h5.7l-9.391-14.471zM22.844 37.255h4.721V10.212h-4.721zm15.42-21.339l-.462-5.7h-4.054v27.039h4.721V23.306c0-4.205 3.079-8.878 6.466-8.878a8.5 8.5 0 0 1 2.361.308l.872-4.618A11.516 11.516 0 0 0 45.5 9.81c-3.284 0-5.8 2.053-7.236 6.106zM0 37.255h4.875V21.707h11.546v-3.849H4.875V5.8h13.342l.565-3.9H0zM25.153.163A3.139 3.139 0 0 0 21.869 3.4a3.129 3.129 0 0 0 3.284 3.182A3.143 3.143 0 0 0 28.489 3.4 3.153 3.153 0 0 0 25.153.163zm76.491 9.647c-7.7 0-12.11 5.585-12.11 13.949 0 8.57 4.362 14.112 12.059 14.112 7.646 0 12.059-5.8 12.059-14.163 0-8.57-4.31-13.898-12.008-13.898zm-.051 24.264c-4.516 0-6.979-3.284-6.979-10.315 0-7.081 2.515-10.152 7.03-10.152 4.465 0 6.928 3.071 6.928 10.1 0 7.083-2.463 10.367-6.979 10.367zM82.47 8.339c0-2.617 1.027-4.542 4-4.542a11.567 11.567 0 0 1 4.721 1.027l1.488-3.438A14.907 14.907 0 0 0 86.216 0c-5.49 0-8.467 3.447-8.467 7.963v2.249h-4.824v3.643h4.824v23.4h4.721v-23.4h6.056l.513-3.643H82.47zM59.952 9.81c-6.979 0-11.238 5.79-11.238 14.206 0 8.57 4.413 13.855 11.957 13.855a14.741 14.741 0 0 0 9.442-3.387l-2.053-2.822a11.384 11.384 0 0 1-7.03 2.361c-3.9 0-6.825-2.412-7.287-8.673h17.242c.052-.616.1-1.488.1-2.412.003-8.262-3.846-13.128-11.133-13.128zm6.466 12.051H53.743c.359-6 2.72-8.305 6.312-8.305 4.259 0 6.363 2.711 6.363 8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="172" height="42"><path fill="context-fill #20123a" d="M.19 2.82h25.72v7H7.57v9.43h18.34v6.9H7.57v15.14H.19zM34.65.13a4.14 4.14 0 0 1 4.27 4.33 4.12 4.12 0 0 1-4.32 4.32 4.09 4.09 0 0 1-4.27-4.22A4.27 4.27 0 0 1 34.65.13zM31 12.83h7.27v28.46H31zm28.35 7.91a5.89 5.89 0 0 0-3.53-1.27c-3 0-4.64 1.9-4.64 6.06v15.76H44V12.83h6.9v4.11a6.79 6.79 0 0 1 6.8-4.37A8.69 8.69 0 0 1 62.53 14zm3 6.48c0-8.17 6.06-15 14.65-15s14.59 6.06 14.59 14.49v3H69.48c.79 3.58 3.58 6 7.85 6a7.62 7.62 0 0 0 7.06-4.21l6.06 3.63c-3 4.43-7.27 6.75-13.33 6.75-9.22-.01-14.75-6.18-14.75-14.66zM69.59 24h15c-.79-3.63-3.74-5.63-7.59-5.63A7.31 7.31 0 0 0 69.59 24zM93.4 12.83h5.11v-1.42c0-7.75 3.27-11 10.44-11h2.53v6.31h-2.06c-3.37 0-4.11 1.16-4.11 4.69v1.42h6.17v6.54h-6v21.92h-7V19.37H93.4zm19.45 14.23a14.56 14.56 0 0 1 14.85-14.81 14.81 14.81 0 1 1 0 29.62c-8.85 0-14.85-6.49-14.85-14.81zm22.65 0a7.8 7.8 0 1 0-15.59 0 7.8 7.8 0 1 0 15.59 0zm16.86-.32l-10.27-13.91h8.53l6.06 8.75 6.22-8.75h8.38l-10.43 13.86 11 14.6h-8.49L156.53 32l-6.59 9.28h-8.48z"/></svg>

До

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

После

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

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

@ -0,0 +1 @@
<svg width="5" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M.248 8.204a.77.77 0 0 1 1.088.044L4.8 12l-3.464 3.752a.77.77 0 1 1-1.132-1.045L2.704 12l-2.5-2.707a.77.77 0 0 1 .044-1.089z" fill="#0C0C0D"/></svg>

После

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

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

После

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

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

@ -36,7 +36,7 @@ if you run into any problems.
You will also need to install:
- Node.js 7+ (On Mac, the best way to install Node.js is to use the [install link on the Node.js homepage](https://nodejs.org/en/))
- Node.js version 8 (To install this legacy version of Node, [use this URL](https://nodejs.org/en/download/releases/))
- npm (packaged with Node.js)
### Activity Stream Github repository

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

@ -742,6 +742,7 @@ class _ASRouter {
ToolbarPanelHub.init(this.waitForInitialized, {
getMessages: this.handleMessageRequest,
dispatch: this.dispatch,
handleUserAction: this.handleUserAction,
});
this._loadLocalProviders();
@ -1531,6 +1532,7 @@ class _ASRouter {
handleMessageRequest({
triggerId,
triggerParam,
triggerContext,
template,
provider,
returnAll = false,
@ -1552,13 +1554,21 @@ class _ASRouter {
if (returnAll) {
return this._findAllMessages(
msgs,
triggerId && { id: triggerId, param: triggerParam }
triggerId && {
id: triggerId,
param: triggerParam,
context: triggerContext,
}
);
}
return this._findMessage(
msgs,
triggerId && { id: triggerId, param: triggerParam }
triggerId && {
id: triggerId,
param: triggerParam,
context: triggerContext,
}
);
}
@ -1903,6 +1913,7 @@ class _ASRouter {
const message = await this.handleMessageRequest({
triggerId: trigger.id,
triggerParam: trigger.param,
triggerContext: trigger.context,
});
await this.setState({ lastMessageId: message ? message.id : null });

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

@ -56,6 +56,12 @@ XPCOMUtils.defineLazyServiceGetter(
"@mozilla.org/updates/update-manager;1",
"nsIUpdateManager"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
const FXA_USERNAME_PREF = "services.sync.username";
const FXA_ENABLED_PREF = "identity.fxaccounts.enabled";
@ -422,6 +428,9 @@ const TargetingGetters = {
false
);
},
get totalBlockedCount() {
return TrackingDBService.sumAllEvents();
},
};
this.ASRouterTargeting = {

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

@ -370,7 +370,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
) {
sendUpdate({
type: at.DISCOVERY_STREAM_SPOCS_ENDPOINT,
data: layout.spocs.url,
data: layout.spocs,
});
}
}
@ -1341,7 +1341,6 @@ defaultLayoutResp = {
components: [
{
type: "CardGrid",
cta_variant: false,
properties: {
items: 21,
},

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

@ -45,7 +45,7 @@ this.DownloadsManager = class DownloadsManager {
DownloadsViewUI.getSizeWithUnits(download) ||
DownloadsCommon.strings.sizeUnknown,
referrer: download.source.referrerInfo
? download.source.referrerInfo.originalReferrer
? download.source.referrerInfo.originalReferrer.spec
: null,
date_added: download.endTime,
};

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

@ -405,6 +405,7 @@ const ONBOARDING_MESSAGES = () => [
cta_url: `${Services.urlFormatter.formatURLPref(
"app.support.baseURL"
)}etp-promotions?as=u&utm_source=inproduct`,
cta_type: "OPEN_URL",
},
trigger: { id: "protectionsPanelOpen" },
},

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

@ -92,6 +92,7 @@ const MESSAGES = () => [
{
id: "WHATS_NEW_70_1",
template: "whatsnew_panel_message",
order: 3,
content: {
published_date: 1560969794394,
title: "Protection Is Our Focus",
@ -101,6 +102,7 @@ const MESSAGES = () => [
body:
"The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
cta_url: "https://blog.mozilla.org/",
cta_type: "OPEN_URL",
},
targeting: `firefoxVersion > 69`,
trigger: { id: "whatsNewPanelOpened" },
@ -108,6 +110,7 @@ const MESSAGES = () => [
{
id: "WHATS_NEW_70_2",
template: "whatsnew_panel_message",
order: 1,
content: {
published_date: 1560969794394,
title: "Another thing new in Firefox 70",
@ -115,6 +118,7 @@ const MESSAGES = () => [
"The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
link_text: "Learn more on our blog",
cta_url: "https://blog.mozilla.org/",
cta_type: "OPEN_URL",
},
targeting: `firefoxVersion > 69`,
trigger: { id: "whatsNewPanelOpened" },
@ -122,6 +126,7 @@ const MESSAGES = () => [
{
id: "WHATS_NEW_69_1",
template: "whatsnew_panel_message",
order: 1,
content: {
published_date: 1557346235089,
title: "Something new in Firefox 69",
@ -129,10 +134,31 @@ const MESSAGES = () => [
"The New Enhanced Tracking Protection, gives you the best level of protection and performance. Discover how this version is the safest version of firefox ever made.",
link_text: "Learn more on our blog",
cta_url: "https://blog.mozilla.org/",
cta_type: "OPEN_URL",
},
targeting: `firefoxVersion > 68`,
trigger: { id: "whatsNewPanelOpened" },
},
{
id: "WHATS_NEW_70_3",
template: "whatsnew_panel_message",
order: 2,
content: {
published_date: 1560969794394,
layout: "tracking-protections",
title: { string_id: "cfr-whatsnew-tracking-blocked-title" },
subtitle: { string_id: "cfr-whatsnew-tracking-blocked-subtitle" },
icon_url:
"resource://activity-stream/data/content/assets/protection-report-icon.png",
icon_alt: "Protection Report icon",
body: { string_id: "cfr-whatsnew-tracking-protect-body" },
link_text: { string_id: "cfr-whatsnew-tracking-blocked-link-text" },
cta_url: "protections",
cta_type: "OPEN_ABOUT_PAGE",
},
targeting: `firefoxVersion > 69 && totalBlockedCount > 0`,
trigger: { id: "whatsNewPanelOpened" },
},
];
const PanelTestProvider = {

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

@ -3,20 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
EveryWindow: "resource:///modules/EveryWindow.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
});
XPCOMUtils.defineLazyServiceGetter(
this,
"EveryWindow",
"resource:///modules/EveryWindow.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
const WHATSNEW_ENABLED_PREF = "browser.messaging-system.whatsNewPanel.enabled";
@ -43,9 +42,10 @@ class _ToolbarPanelHub {
this.state = null;
}
async init(waitForInitialized, { getMessages, dispatch }) {
async init(waitForInitialized, { getMessages, dispatch, handleUserAction }) {
this._getMessages = getMessages;
this._dispatch = dispatch;
this._handleUserAction = handleUserAction;
// Wait for ASRouter messages to become available in order to know
// if we can show the What's New panel
await waitForInitialized;
@ -137,23 +137,45 @@ class _ToolbarPanelHub {
});
}
// Render what's new messages into the panel.
async renderMessages(win, doc, containerId) {
const messages = (await this.messages).sort((m1, m2) => {
// Newer messages first and use `order` field to decide between messages
// with the same timestamp
_sortWhatsNewMessages(m1, m2) {
// Sort by published_date in descending order.
if (m1.content.published_date === m2.content.published_date) {
return 0;
// Ascending order
return m1.order - m2.order;
}
if (m1.content.published_date > m2.content.published_date) {
return -1;
}
return 1;
});
}
// Render what's new messages into the panel.
async renderMessages(win, doc, containerId) {
const messages = (await this.messages).sort(this._sortWhatsNewMessages);
const container = doc.getElementById(containerId);
if (messages && !container.querySelector(".whatsNew-message")) {
let previousDate = 0;
// Get and store any variable part of the message content
this.state.contentArguments = await this._contentArguments();
for (let message of messages) {
// Only render date if it is different from the one rendered before.
if (message.content.published_date !== previousDate) {
container.appendChild(
this._createElement(doc, "p", {
classList: "whatsNew-message-date",
content: new Date(
message.content.published_date
).toLocaleDateString("default", {
month: "long",
day: "numeric",
year: "numeric",
}),
})
);
}
container.appendChild(
this._createMessageElements(win, doc, message, previousDate)
);
@ -181,33 +203,36 @@ class _ToolbarPanelHub {
});
}
/**
* Attach click event listener defined in message payload
*/
_attachClickListener(win, element, message) {
element.addEventListener("click", () => {
this._handleUserAction({
target: win,
data: {
type: message.content.cta_type,
data: {
args: message.content.cta_url,
where: "tabshifted",
},
},
});
this.sendUserEventTelemetry(win, "CLICK", message);
});
}
_createMessageElements(win, doc, message, previousDate) {
const { content } = message;
const messageEl = this._createElement(doc, "div");
messageEl.classList.add("whatsNew-message");
// Only render date if it is different from the one rendered before.
if (content.published_date !== previousDate) {
messageEl.appendChild(
this._createDateElement(doc, content.published_date)
);
}
const wrapperEl = this._createElement(doc, "button");
// istanbul ignore next
wrapperEl.doCommand = () => {};
wrapperEl.classList.add("whatsNew-message-body");
messageEl.appendChild(wrapperEl);
wrapperEl.addEventListener("click", () => {
win.ownerGlobal.openLinkIn(content.cta_url, "tabshifted", {
private: false,
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
{}
),
csp: null,
});
this.sendUserEventTelemetry(win, "CLICK", message);
});
if (content.icon_url) {
wrapperEl.classList.add("has-icon");
@ -218,81 +243,141 @@ class _ToolbarPanelHub {
wrapperEl.appendChild(iconEl);
}
const titleEl = this._createElement(doc, "h2");
titleEl.classList.add("whatsNew-message-title");
this._setString(doc, titleEl, content.title);
wrapperEl.appendChild(titleEl);
const bodyEl = this._createElement(doc, "p");
this._setString(doc, bodyEl, content.body);
wrapperEl.appendChild(bodyEl);
wrapperEl.appendChild(this._createMessageContent(win, doc, content));
if (content.link_text) {
const linkEl = this._createElement(doc, "a");
linkEl.classList.add("text-link");
this._setString(doc, linkEl, content.link_text);
wrapperEl.appendChild(linkEl);
wrapperEl.appendChild(
this._createElement(doc, "a", {
classList: "text-link",
content: content.link_text,
})
);
}
// Attach event listener on entire message container
this._attachClickListener(win, wrapperEl, message);
return messageEl;
}
_createHeroElement(win, doc, content) {
/**
* Return message title (optional subtitle) and body
*/
_createMessageContent(win, doc, content) {
const wrapperEl = new win.DocumentFragment();
wrapperEl.appendChild(
this._createElement(doc, "h2", {
classList: "whatsNew-message-title",
content: content.title,
})
);
switch (content.layout) {
case "tracking-protections":
wrapperEl.appendChild(
this._createElement(doc, "h4", {
classList: "whatsNew-message-subtitle",
content: content.subtitle,
})
);
wrapperEl.appendChild(
this._createElement(doc, "h2", {
classList: "whatsNew-message-title-large",
content: this.state.contentArguments.blockedCount,
})
);
break;
}
wrapperEl.appendChild(
this._createElement(doc, "p", { content: content.body })
);
return wrapperEl;
}
_createHeroElement(win, doc, message) {
const messageEl = this._createElement(doc, "div");
messageEl.setAttribute("id", "protections-popup-message");
messageEl.classList.add("whatsNew-hero-message");
const wrapperEl = this._createElement(doc, "div");
wrapperEl.classList.add("whatsNew-message-body");
messageEl.appendChild(wrapperEl);
wrapperEl.addEventListener("click", () => {
win.ownerGlobal.openLinkIn(content.cta_url, "tabshifted", {
private: false,
relatedToCurrent: true,
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
{}
),
csp: null,
});
});
const titleEl = this._createElement(doc, "h2");
titleEl.classList.add("whatsNew-message-title");
this._setString(doc, titleEl, content.title);
wrapperEl.appendChild(titleEl);
const bodyEl = this._createElement(doc, "p");
this._setString(doc, bodyEl, content.body);
wrapperEl.appendChild(bodyEl);
this._attachClickListener(win, wrapperEl, message);
if (content.link_text) {
const linkEl = this._createElement(doc, "a");
linkEl.classList.add("text-link");
this._setString(doc, linkEl, content.link_text);
wrapperEl.appendChild(linkEl);
wrapperEl.appendChild(
this._createElement(doc, "h2", {
classList: "whatsNew-message-title",
content: message.content.title,
})
);
wrapperEl.appendChild(
this._createElement(doc, "p", { content: message.content.body })
);
if (message.content.link_text) {
wrapperEl.appendChild(
this._createElement(doc, "a", {
classList: "text-link",
content: message.content.link_text,
})
);
}
return messageEl;
}
_createElement(doc, elem) {
return doc.createElementNS("http://www.w3.org/1999/xhtml", elem);
_createElement(doc, elem, options = {}) {
const node = doc.createElementNS("http://www.w3.org/1999/xhtml", elem);
if (options.classList) {
node.classList.add(options.classList);
}
if (options.content) {
this._setString(doc, node, options.content);
}
_createDateElement(doc, date) {
const dateEl = this._createElement(doc, "p");
dateEl.classList.add("whatsNew-message-date");
dateEl.textContent = new Date(date).toLocaleDateString("default", {
month: "long",
day: "numeric",
year: "numeric",
});
return dateEl;
return node;
}
async _contentArguments() {
// Between now and 6 weeks ago
const dateTo = new Date();
const dateFrom = new Date(dateTo.getTime() - 42 * 24 * 60 * 60 * 1000);
const eventsByDate = await TrackingDBService.getEventsByDateRange(
dateFrom,
dateTo
);
// Count all events in the past 6 weeks
const totalEvents = eventsByDate.reduce(
(acc, day) => acc + day.getResultByName("count"),
0
);
return {
// Keys need to match variable names used in asrouter.ftl
// `earliestDate` will be either 6 weeks ago or when tracking recording
// started. Whichever is more recent.
earliestDate: new Date(
Math.max(
new Date(await TrackingDBService.getEarliestRecordedDate()),
dateFrom
)
).getTime(),
blockedCount: totalEvents.toLocaleString(),
};
}
// If `string_id` is present it means we are relying on fluent for translations.
// Otherwise, we have a vanilla string.
_setString(doc, el, stringObj) {
if (stringObj.string_id) {
doc.l10n.setAttributes(el, stringObj.string_id);
doc.l10n.setAttributes(
el,
stringObj.string_id,
// Pass all available arguments to Fluent
this.state.contentArguments
);
} else {
el.textContent = stringObj;
}
@ -395,7 +480,7 @@ class _ToolbarPanelHub {
triggerId: "protectionsPanelOpen",
});
if (message) {
const messageEl = this._createHeroElement(win, doc, message.content);
const messageEl = this._createHeroElement(win, doc, message);
container.appendChild(messageEl);
infoButton.addEventListener("click", toggleMessage);
this.sendUserEventTelemetry(win, "IMPRESSION", message.id);

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

@ -13,7 +13,7 @@ cd /mozilla-central && hg pull && hg update -C
cd /activity-stream && npm install . && npm run buildmc
# Build latest m-c with Activity Stream changes
cd /mozilla-central && ./mach build \
cd /mozilla-central && rm -rf ./objdir-frontend && ./mach build \
&& ./mach lint browser/components/newtab \
&& ./mach lint -l codespell browser/locales/en-US/browser/newtab \
&& ./mach test browser/components/newtab/test/browser --headless \

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

@ -214,6 +214,7 @@ describe("ASRouter", () => {
{
getMessages: Router.handleMessageRequest,
dispatch: Router.dispatch,
handleUserAction: Router.handleUserAction,
}
);
@ -930,7 +931,11 @@ describe("ASRouter", () => {
assert.deepEqual(result, [message2, message1]);
});
it("should forward trigger param info", async () => {
const trigger = { triggerId: "foo", triggerParam: "bar" };
const trigger = {
triggerId: "foo",
triggerParam: "bar",
triggerContext: "context",
};
const message1 = {
id: "1",
campaign: "foocampaign",
@ -951,6 +956,7 @@ describe("ASRouter", () => {
assert.calledWithExactly(stub, sinon.match.array, {
id: trigger.triggerId,
param: trigger.triggerParam,
context: trigger.triggerContext,
});
});
});
@ -1624,6 +1630,7 @@ describe("ASRouter", () => {
assert.deepEqual(Router._findMessage.firstCall.args[1], {
id: "firstRun",
param: undefined,
context: undefined,
});
});
it("consider the trigger when picking a message", async () => {

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

@ -1,13 +1,14 @@
import { PanelTestProvider } from "lib/PanelTestProvider.jsm";
import schema from "content-src/asrouter/schemas/panel/cfr-fxa-bookmark.schema.json";
import update_schema from "content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json";
import whats_new_schema from "content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json";
const messages = PanelTestProvider.getMessages();
describe("PanelTestProvider", () => {
it("should have a message", () => {
// Careful: when changing this number make sure that new messages also go
// through schema verifications.
assert.lengthOf(messages, 7);
assert.lengthOf(messages, 8);
});
it("should be a valid message", () => {
const fxaMessages = messages.filter(
@ -25,4 +26,14 @@ describe("PanelTestProvider", () => {
assert.jsonSchema(message.content, update_schema);
}
});
it("should be a valid message", () => {
const whatsNewMessages = messages.filter(
({ template }) => template === "whatsnew_panel_message"
);
for (let message of whatsNewMessages) {
assert.jsonSchema(message.content, whats_new_schema);
// Not part of `message.content` so it can't be enforced through schema
assert.property(message, "order");
}
});
});

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

@ -948,12 +948,13 @@ describe("Reducers", () => {
});
assert.isTrue(state.feeds.loaded);
});
it("should set spoc_endpoint with DISCOVERY_STREAM_SPOCS_ENDPOINT", () => {
it("should set spoc_endpoint and spocs_per_domain with DISCOVERY_STREAM_SPOCS_ENDPOINT", () => {
const state = DiscoveryStream(undefined, {
type: at.DISCOVERY_STREAM_SPOCS_ENDPOINT,
data: "foo.com",
data: { url: "foo.com", spocs_per_domain: 2 },
});
assert.equal(state.spocs.spocs_endpoint, "foo.com");
assert.equal(state.spocs.spocs_per_domain, 2);
});
it("should set spocs with DISCOVERY_STREAM_SPOCS_UPDATE", () => {
const data = {
@ -966,6 +967,7 @@ describe("Reducers", () => {
});
assert.deepEqual(state.spocs, {
spocs_endpoint: "",
spocs_per_domain: 1,
data: [1, 2, 3],
lastUpdated: 123,
loaded: true,

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

@ -2,7 +2,7 @@ import {
DSCard,
DefaultMeta,
PlaceholderDSCard,
VariantMeta,
CTAButtonMeta,
} from "content-src/components/DiscoveryStreamComponents/DSCard/DSCard";
import {
DSContextFooter,
@ -177,48 +177,54 @@ describe("<DSCard>", () => {
assert.notOk(meta.find(".cta-link").exists());
});
it("should render cta-link by default when item has cta", () => {
it("should not render cta-link by default when item has cta and cta_variant not link", () => {
wrapper.setProps({ cta: "test" });
const meta = wrapper.find(DefaultMeta);
assert.notOk(meta.find(".cta-link").exists());
});
it("should render cta-link by default when item has cta and cta_variant as link", () => {
wrapper.setProps({ cta: "test", cta_variant: "link" });
const meta = wrapper.find(DefaultMeta);
assert.equal(meta.find(".cta-link").text(), "test");
});
it("should not render cta-button for non spoc content", () => {
wrapper.setProps({ cta: "test", cta_variant: true });
const meta = wrapper.find(VariantMeta);
wrapper.setProps({ cta: "test", cta_variant: "button" });
const meta = wrapper.find(CTAButtonMeta);
assert.lengthOf(meta.find(".cta-button"), 0);
});
it("should render cta-button when item has cta and cta button variant is true and is spoc", () => {
it("should render cta-button when item has cta and cta_variant is button and is spoc", () => {
wrapper.setProps({
cta: "test",
cta_variant: true,
cta_variant: "button",
context: "Sponsored by Foo",
});
const meta = wrapper.find(VariantMeta);
const meta = wrapper.find(CTAButtonMeta);
assert.equal(meta.find(".cta-button").text(), "test");
});
it("should not render Sponsored by label in footer for spoc item with cta button variant", () => {
it("should not render Sponsored by label in footer for spoc item with cta_variant button", () => {
wrapper.setProps({
cta: "test",
context: "Sponsored by test",
cta_variant: true,
cta_variant: "button",
});
assert.ok(wrapper.find(VariantMeta).exists());
assert.ok(wrapper.find(CTAButtonMeta).exists());
assert.notOk(wrapper.find(DSContextFooter).exists());
});
it("should render sponsor text on top for spoc item and cta_variant true", () => {
it("should render sponsor text on top for spoc item and cta button variant", () => {
wrapper.setProps({
sponsor: "Test",
context: "Sponsored by test",
cta_variant: true,
cta_variant: "button",
});
assert.ok(wrapper.find(VariantMeta).exists());
const meta = wrapper.find(VariantMeta);
assert.ok(wrapper.find(CTAButtonMeta).exists());
const meta = wrapper.find(CTAButtonMeta);
assert.equal(meta.find(".source").text(), "Test · Sponsored");
});
});

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

@ -0,0 +1,26 @@
import { DSTextPromo } from "content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo";
import React from "react";
import { shallow } from "enzyme";
describe("<DSTextPromo>", () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<DSTextPromo />);
});
it("should render", () => {
assert.ok(wrapper.exists());
assert.ok(wrapper.find(".ds-text-promo").exists());
});
it("should render a header", () => {
wrapper.setProps({ header: "foo" });
assert.ok(wrapper.find(".text").exists());
});
it("should render a subtitle", () => {
wrapper.setProps({ subtitle: "foo" });
assert.ok(wrapper.find(".subtitle").exists());
});
});

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

@ -7,6 +7,8 @@ describe("<ReturnToAMO>", () => {
let onReady;
let sandbox;
let wrapper;
let dummyNode;
let fakeDocument;
let sendUserActionTelemetryStub;
let content;
beforeEach(() => {
@ -18,6 +20,19 @@ describe("<ReturnToAMO>", () => {
primary_button: {},
secondary_button: {},
};
dummyNode = document.createElement("body");
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
fakeDocument = {
get activeElement() {
return dummyNode;
},
get body() {
return dummyNode;
},
getElementById() {
return dummyNode;
},
};
});
afterEach(() => {
@ -30,6 +45,7 @@ describe("<ReturnToAMO>", () => {
wrapper = mount(
<ReturnToAMO
document={fakeDocument}
onReady={onReady}
dispatch={dispatch}
content={content}
@ -52,6 +68,7 @@ describe("<ReturnToAMO>", () => {
beforeEach(() => {
wrapper = mount(
<ReturnToAMO
document={fakeDocument}
onReady={onReady}
dispatch={dispatch}
content={content}

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

@ -5,6 +5,8 @@ import { StartupOverlay } from "content-src/asrouter/templates/StartupOverlay/St
describe("<StartupOverlay>", () => {
let wrapper;
let fakeDocument;
let dummyNode;
let dispatch;
let onBlock;
let sandbox;
@ -13,7 +15,27 @@ describe("<StartupOverlay>", () => {
dispatch = sandbox.stub();
onBlock = sandbox.stub();
wrapper = mount(<StartupOverlay onBlock={onBlock} dispatch={dispatch} />);
dummyNode = document.createElement("body");
sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
fakeDocument = {
get activeElement() {
return dummyNode;
},
get body() {
return dummyNode;
},
getElementById() {
return dummyNode;
},
};
wrapper = mount(
<StartupOverlay
onBlock={onBlock}
dispatch={dispatch}
document={fakeDocument}
/>
);
});
afterEach(() => {
@ -22,7 +44,14 @@ describe("<StartupOverlay>", () => {
it("should add show class after mount and timeout", async () => {
const clock = sandbox.useFakeTimers();
wrapper = mount(<StartupOverlay onBlock={onBlock} dispatch={dispatch} />);
// We need to mount here to trigger ComponentDidMount after the FakeTimers are added.
wrapper = mount(
<StartupOverlay
onBlock={onBlock}
dispatch={dispatch}
document={fakeDocument}
/>
);
assert.isFalse(
wrapper.find(".overlay-wrapper").hasClass("show"),
".overlay-wrapper does not have .show class"

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

@ -20,6 +20,9 @@ describe("ToolbarPanelHub", () => {
let waitForInitializedStub;
let isBrowserPrivateStub;
let fakeDispatch;
let getEarliestRecordedDateStub;
let getEventsByDateRangeStub;
let handleUserActionStub;
beforeEach(async () => {
sandbox = sinon.createSandbox();
@ -58,6 +61,10 @@ describe("ToolbarPanelHub", () => {
},
};
fakeWindow = {
// eslint-disable-next-line object-shorthand
DocumentFragment: function() {
return fakeElementById;
},
document: fakeDocument,
browser: {
ownerDocument: fakeDocument,
@ -94,6 +101,16 @@ describe("ToolbarPanelHub", () => {
globals.set("PrivateBrowsingUtils", {
isBrowserPrivate: isBrowserPrivateStub,
});
getEarliestRecordedDateStub = sandbox.stub();
getEventsByDateRangeStub = sandbox.stub();
globals.set("TrackingDBService", {
getEarliestRecordedDate: getEarliestRecordedDateStub.returns(
// A random date that's not the current timestamp
new Date() - 500
),
getEventsByDateRange: getEventsByDateRangeStub.returns([]),
});
handleUserActionStub = sandbox.stub();
});
afterEach(() => {
instance.uninit();
@ -242,6 +259,7 @@ describe("ToolbarPanelHub", () => {
instance.init(waitForInitializedStub, {
getMessages: getMessagesStub,
dispatch: fakeDispatch,
handleUserAction: handleUserActionStub,
});
});
it("should render messages to the panel on renderMessages()", async () => {
@ -250,32 +268,50 @@ describe("ToolbarPanelHub", () => {
);
messages[0].content.link_text = { string_id: "link_text_id" };
getMessagesStub.returns([messages[0], messages[2], messages[1]]);
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
for (let message of messages) {
assert.ok(
createdElements.find(
el =>
el.tagName === "h2" && el.textContent === message.content.title
)
);
assert.ok(
createdElements.find(
el => el.tagName === "p" && el.textContent === message.content.body
)
);
assert.ok(createdElements.find(el => el.tagName === "h2"));
if (message.content.layout === "tracking-protections") {
assert.ok(createdElements.find(el => el.tagName === "h4"));
}
assert.ok(createdElements.find(el => el.tagName === "p"));
}
// Call the click handler to make coverage happy.
eventListeners.click();
assert.calledOnce(fakeWindow.ownerGlobal.openLinkIn);
assert.calledOnce(handleUserActionStub);
});
it("should sort based on order field value", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m =>
m.template === "whatsnew_panel_message" &&
m.content.published_date === 1560969794394
);
messages.forEach(m => (m.content.title = m.order));
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
// Select the title elements that are supposed to be set to the same
// value as the `order` field of the message
const titleEls = createdElements
.filter(
el =>
el.classList.add.firstCall &&
el.classList.add.firstCall.args[0] === "whatsNew-message-title"
)
.map(el => el.textContent);
assert.deepEqual(titleEls, [1, 2, 3]);
});
it("should accept string for image attributes", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
m => m.id === "WHATS_NEW_70_1"
);
getMessagesStub.returns([messages[0], messages[2], messages[1]]);
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
@ -289,19 +325,55 @@ describe("ToolbarPanelHub", () => {
});
it("should accept fluent ids for image attributes", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
m => m.id === "WHATS_NEW_70_1"
);
messages[0].content.icon_alt = { string_id: "foo" };
getMessagesStub.returns([messages[0], messages[2], messages[1]]);
getMessagesStub.returns(messages);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
const imageEl = createdElements.find(el => el.tagName === "img");
assert.calledOnce(fakeDocument.l10n.setAttributes);
assert.calledWithExactly(fakeDocument.l10n.setAttributes, imageEl, "foo");
});
it("should accept fluent ids for elements attributes", async () => {
const [message] = (await PanelTestProvider.getMessages()).filter(
m =>
m.template === "whatsnew_panel_message" &&
m.content.layout === "tracking-protections"
);
getMessagesStub.returns([message]);
instance.state.contentArguments = { foo: "foo", bar: "bar" };
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
const subtitle = createdElements.find(el => el.tagName === "h4");
assert.calledWithExactly(
fakeDocument.l10n.setAttributes,
subtitle,
message.content.subtitle.string_id,
instance.state.contentArguments
);
});
it("should correctly compute blocker trackers and date", async () => {
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
);
getMessagesStub.returns(messages);
getEventsByDateRangeStub.returns([
{ getResultByName: sandbox.stub().returns(2) },
{ getResultByName: sandbox.stub().returns(3) },
]);
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledWithExactly(
fakeDocument.l10n.setAttributes,
sinon.match.object,
sinon.match.string,
{ blockedCount: "5", earliestDate: getEarliestRecordedDateStub() }
);
});
it("should only render unique dates (no duplicates)", async () => {
instance._createDateElement = sandbox.stub();
const messages = (await PanelTestProvider.getMessages()).filter(
m => m.template === "whatsnew_panel_message"
);
@ -312,7 +384,13 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.callCount(instance._createDateElement, uniqueDates.length);
const dateElements = createdElements.filter(
el =>
el.tagName === "p" &&
el.classList.add.firstCall &&
el.classList.add.firstCall.args[0] === "whatsNew-message-date"
);
assert.lengthOf(dateElements, uniqueDates.length);
});
it("should listen for panelhidden and remove the toolbar button", async () => {
getMessagesStub.returns([]);
@ -492,6 +570,7 @@ describe("ToolbarPanelHub", () => {
dispatch: fakeDispatch,
getMessages: () =>
onboardingMsgs.find(msg => msg.template === "protections_panel"),
handleUserAction: handleUserActionStub,
});
});
it("should remember it showed", async () => {
@ -522,7 +601,17 @@ describe("ToolbarPanelHub", () => {
eventListeners.click();
assert.calledOnce(fakeWindow.ownerGlobal.openLinkIn);
assert.calledOnce(handleUserActionStub);
assert.calledWithExactly(handleUserActionStub, {
target: fakeWindow,
data: {
type: "OPEN_URL",
data: {
args: sinon.match.string,
where: "tabshifted",
},
},
});
});
});
});