diff --git a/browser/components/newtab/content-src/asrouter/components/Button/_Button.scss b/browser/components/newtab/content-src/asrouter/components/Button/_Button.scss
index 6100691aaaab..3a44c9f65ae9 100644
--- a/browser/components/newtab/content-src/asrouter/components/Button/_Button.scss
+++ b/browser/components/newtab/content-src/asrouter/components/Button/_Button.scss
@@ -38,6 +38,10 @@
&:active {
background-color: $grey-90-30;
}
+
+ &:focus {
+ box-shadow: 0 0 0 1px $blue-50 inset, 0 0 0 1px $blue-50, 0 0 0 4px $blue-50-30;
+ }
}
}
diff --git a/browser/components/newtab/content-src/asrouter/docs/user-actions.md b/browser/components/newtab/content-src/asrouter/docs/user-actions.md
new file mode 100644
index 000000000000..8893c1c6b04d
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/docs/user-actions.md
@@ -0,0 +1,141 @@
+# User Actions
+
+A subset of actions are available to messages via fields like `button_action` for snippets, or `primary_action` for CFRs.
+
+## Usage
+
+For snippets, you should add the action type in `button_action` and any additional parameters in `button_action_args. For example:
+
+```json
+{
+ "button_action": "OPEN_ABOUT_PAGE",
+ "button_action_args": "config"
+}
+```
+
+## Available Actions
+
+### `OPEN_APPLICATIONS_MENU`
+
+* args: (none)
+
+Opens the applications menu.
+
+### `OPEN_PRIVATE_BROWSER_WINDOW`
+
+* args: (none)
+
+Opens a new private browsing window.
+
+
+### `OPEN_URL`
+
+* args: `string` (a url)
+
+Opens a given url.
+
+Example:
+
+```json
+{
+ "button_action": "OPEN_URL",
+ "button_action_args": "https://foo.com"
+}
+```
+
+### `OPEN_ABOUT_PAGE`
+
+* args: `string` (a valid about page without the `about:` prefix)
+
+Opens a given about page
+
+Example:
+
+```json
+{
+ "button_action": "OPEN_ABOUT_PAGE",
+ "button_action_args": "config"
+}
+```
+
+### `OPEN_PREFERENCES_PAGE`
+
+* args: `string` (a category accessible via a `#`)
+
+Opens `about:preferences` with an optional category accessible via a `#` in the URL (e.g. `about:preferences#home`).
+
+Example:
+
+```json
+{
+ "button_action": "OPEN_PREFERENCES_PAGE",
+ "button_action_args": "home"
+}
+```
+
+### `SHOW_FIREFOX_ACCOUNTS`
+
+* args: (none)
+
+Opens Firefox accounts sign-up page. Encodes some information that the origin was from snippets by default.
+
+### `PIN_CURRENT_TAB`
+
+* args: (none)
+
+Pins the currently focused tab.
+
+### `ENABLE_FIREFOX_MONITOR`
+
+* args:
+```ts
+{
+ url: string;
+ flowRequestParams: {
+ entrypoint: string;
+ utm_term: string;
+ form_type: string;
+ }
+}
+```
+
+Opens an oauth flow to enable Firefox Monitor at a given `url` and adds Firefox metrics that user given a set of `flowRequestParams`.
+
+### `url`
+
+The URL should start with `https://monitor.firefox.com/oauth/init` and add various metrics tags as search params, including:
+
+* `utm_source`
+* `utm_campaign`
+* `form_type`
+* `entrypoint`
+
+You should verify the values of these search params with whoever is doing the data analysis (e.g. Leif Oines).
+
+### `flowRequestParams`
+
+These params are used by Firefox to add information specific to that individual user to the final oauth URL. You should include:
+
+* `entrypoint`
+* `utm_term`
+* `form_type`
+
+The `entrypoint` and `form_type` values should match the encoded values in your `url`.
+
+You should verify the values with whoever is doing the data analysis (e.g. Leif Oines).
+
+### Example
+
+```json
+{
+ "button_action": "ENABLE_FIREFOX_MONITOR",
+ "button_action_args": {
+ "url": "https://monitor.firefox.com/oauth/init?utm_source=snippets&utm_campaign=monitor-snippet-test&form_type=email&entrypoint=newtab",
+ "flowRequestParams": {
+ "entrypoint": "snippets",
+ "utm_term": "monitor",
+ "form_type": "email"
+ }
+ }
+}
+```
diff --git a/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/_SimpleBelowSearchSnippet.scss b/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/_SimpleBelowSearchSnippet.scss
index f542ab958b19..7add9706ec1c 100644
--- a/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/_SimpleBelowSearchSnippet.scss
+++ b/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/_SimpleBelowSearchSnippet.scss
@@ -29,6 +29,7 @@
.blockButton {
display: block;
+ opacity: 1;
// larger inset if discovery stream is enabled.
.ds-outer-wrapper-breakpoint-override & {
@@ -105,6 +106,11 @@
inset-inline-end: 20px;
opacity: 1;
top: 50%;
+
+ &:focus {
+ box-shadow: 0 0 0 1px $blue-50 inset, 0 0 0 1px $blue-50, 0 0 0 4px $blue-50-30;
+ border-radius: 2px;
+ }
}
.title {
@@ -149,12 +155,17 @@
background-color: transparent;
.blockButton {
- display: none;
+ display: block;
inset-inline-end: -15%;
- opacity: 1;
+ opacity: 0;
margin: auto;
top: unset;
+ &:focus {
+ opacity: 1;
+ box-shadow: none;
+ }
+
@media (max-width: 1120px) {
inset-inline-end: 2%;
}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
index 22652a5a4ec4..ee6f59139a9f 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -36,6 +36,7 @@ export class CardGrid extends React.PureComponent {
pocket_id={rec.pocket_id}
context_type={rec.context_type}
bookmarkGuid={rec.bookmarkGuid}
+ engagement={rec.engagement}
cta={rec.cta}
cta_variant={this.props.cta_variant}
/>
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
index cfb7d1272a79..20d49a936460 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -17,6 +17,7 @@ export const DefaultMeta = ({
context,
context_type,
cta,
+ engagement,
}) => (
@@ -29,7 +30,11 @@ export const DefaultMeta = ({
)}
-
+
);
@@ -40,6 +45,7 @@ export const VariantMeta = ({
context,
context_type,
cta,
+ engagement,
sponsor,
}) => (
@@ -51,9 +57,13 @@ export const VariantMeta = ({
{excerpt &&
{excerpt}
}
- {cta && }
+ {context && cta && }
{!context && (
-
+
)}
);
@@ -63,6 +73,13 @@ export class DSCard extends React.PureComponent {
super(props);
this.onLinkClick = this.onLinkClick.bind(this);
+ this.setPlaceholderRef = element => {
+ this.placholderElement = element;
+ };
+
+ this.state = {
+ isSeen: false,
+ };
}
onLinkClick(event) {
@@ -93,9 +110,45 @@ export class DSCard extends React.PureComponent {
}
}
+ onSeen(entries) {
+ if (this.state) {
+ const entry = entries.find(e => e.isIntersecting);
+
+ if (entry) {
+ if (this.placholderElement) {
+ this.observer.unobserve(this.placholderElement);
+ }
+
+ // Stop observing since element has been seen
+ this.setState({
+ isSeen: true,
+ });
+ }
+ }
+ }
+
+ componentDidMount() {
+ if (this.placholderElement) {
+ this.observer = new IntersectionObserver(this.onSeen.bind(this));
+ this.observer.observe(this.placholderElement);
+ }
+ }
+
+ componentWillUnmount() {
+ // Remove observer on unmount
+ if (this.observer && this.placholderElement) {
+ this.observer.unobserve(this.placholderElement);
+ }
+ }
+
render() {
+ if (this.props.placeholder || !this.state.isSeen) {
+ return (
+
+ );
+ }
return (
-
+
@@ -126,6 +180,7 @@ export class DSCard extends React.PureComponent {
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}
/>
@@ -145,20 +200,18 @@ export class DSCard extends React.PureComponent {
source={this.props.type}
/>
- {!this.props.placeholder && (
-
- )}
+
);
}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
index 2791edc9f07a..cfec408c8bd9 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -13,14 +13,7 @@ $excerpt-line-height: 20;
background: transparent;
box-shadow: inset $inner-box-shadow;
border-radius: 4px;
-
- .ds-card-link {
- cursor: default;
- }
-
- .img-wrapper {
- opacity: 0;
- }
+ min-height: 300px;
}
.img-wrapper {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
index e9976bc5b881..a7bd0b9ba3fd 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
@@ -21,20 +21,24 @@ export const StatusMessage = ({ icon, fluentID }) => (
export class DSContextFooter extends React.PureComponent {
render() {
- const { context, context_type } = this.props;
+ const { context, context_type, engagement } = this.props;
const { icon, fluentID } = cardContextTypes[context_type] || {};
return (
{context &&
{context}
}
- {!context && context_type && (
+ {!context && (context_type || engagement) && (
-
+ {engagement && !context_type ? (
+ {engagement}
+ ) : (
+
+ )}
)}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
index d0bdb65c0664..4c4aa7b93e59 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
@@ -8,6 +8,7 @@ $status-dark-green: #7C6;
position: relative;
.story-sponsored-label,
+ .story-view-count,
.status-message {
@include dark-theme-only {
color: $grey-40;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
index f4c99fc025c6..79d15f3f9ec2 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -78,6 +78,7 @@ export class Hero extends React.PureComponent {
source={rec.domain}
pocket_id={rec.pocket_id}
bookmarkGuid={rec.bookmarkGuid}
+ engagement={rec.engagement}
/>
)
);
@@ -112,6 +113,7 @@ export class Hero extends React.PureComponent {
)
);
diff --git a/browser/components/newtab/css/activity-stream-linux.css b/browser/components/newtab/css/activity-stream-linux.css
index da0305a10a78..0db2caf10538 100644
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -1991,7 +1991,8 @@ main {
margin: 0 0 12px; }
.ds-hero .ds-card.placeholder {
margin-bottom: 20px;
- padding-bottom: 20px; }
+ padding-bottom: 20px;
+ min-height: 180px; }
.ds-hero .img-wrapper {
margin: 0 0 12px; }
.ds-hero .ds-hero-item {
@@ -2635,11 +2636,8 @@ main {
.ds-card.placeholder {
background: transparent;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
- border-radius: 4px; }
- .ds-card.placeholder .ds-card-link {
- cursor: default; }
- .ds-card.placeholder .img-wrapper {
- opacity: 0; }
+ border-radius: 4px;
+ min-height: 300px; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {
@@ -2783,12 +2781,14 @@ main {
margin-top: 12px;
position: relative; }
.story-footer .story-sponsored-label,
+ .story-footer .story-view-count,
.story-footer .status-message {
-webkit-line-clamp: 1;
font-size: 13px;
line-height: 24px;
color: #737373; }
[lwt-newtab-brighttext] .story-footer .story-sponsored-label, [lwt-newtab-brighttext]
+ .story-footer .story-view-count, [lwt-newtab-brighttext]
.story-footer .status-message {
color: #B1B1B3; }
.story-footer .status-message {
@@ -3003,6 +3003,8 @@ main {
background-color: rgba(12, 12, 13, 0.2); }
.ASRouterButton.secondary:active {
background-color: rgba(12, 12, 13, 0.3); }
+ .ASRouterButton.secondary:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3); }
[lwt-newtab-brighttext] .secondary {
background-color: rgba(249, 249, 250, 0.1); }
@@ -3313,7 +3315,8 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
background-color: var(--newtab-element-hover-color); }
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
- display: block; }
+ display: block;
+ opacity: 1; }
.ds-outer-wrapper-breakpoint-override .below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
inset-inline-end: -8%; }
@media (max-width: 865px) {
@@ -3371,6 +3374,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
inset-inline-end: 20px;
opacity: 1;
top: 50%; }
+ .SimpleBelowSearchSnippet .blockButton:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3);
+ border-radius: 2px; }
.SimpleBelowSearchSnippet .title {
font-size: inherit;
margin: 0; }
@@ -3397,11 +3403,14 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
min-height: 60px;
background-color: transparent; }
.SimpleBelowSearchSnippet.withButton .blockButton {
- display: none;
+ display: block;
inset-inline-end: -15%;
- opacity: 1;
+ opacity: 0;
margin: auto;
top: unset; }
+ .SimpleBelowSearchSnippet.withButton .blockButton:focus {
+ opacity: 1;
+ box-shadow: none; }
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
diff --git a/browser/components/newtab/css/activity-stream-mac.css b/browser/components/newtab/css/activity-stream-mac.css
index 8a4e050fb280..4ca26d997107 100644
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -1994,7 +1994,8 @@ main {
margin: 0 0 12px; }
.ds-hero .ds-card.placeholder {
margin-bottom: 20px;
- padding-bottom: 20px; }
+ padding-bottom: 20px;
+ min-height: 180px; }
.ds-hero .img-wrapper {
margin: 0 0 12px; }
.ds-hero .ds-hero-item {
@@ -2638,11 +2639,8 @@ main {
.ds-card.placeholder {
background: transparent;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
- border-radius: 4px; }
- .ds-card.placeholder .ds-card-link {
- cursor: default; }
- .ds-card.placeholder .img-wrapper {
- opacity: 0; }
+ border-radius: 4px;
+ min-height: 300px; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {
@@ -2786,12 +2784,14 @@ main {
margin-top: 12px;
position: relative; }
.story-footer .story-sponsored-label,
+ .story-footer .story-view-count,
.story-footer .status-message {
-webkit-line-clamp: 1;
font-size: 13px;
line-height: 24px;
color: #737373; }
[lwt-newtab-brighttext] .story-footer .story-sponsored-label, [lwt-newtab-brighttext]
+ .story-footer .story-view-count, [lwt-newtab-brighttext]
.story-footer .status-message {
color: #B1B1B3; }
.story-footer .status-message {
@@ -3006,6 +3006,8 @@ main {
background-color: rgba(12, 12, 13, 0.2); }
.ASRouterButton.secondary:active {
background-color: rgba(12, 12, 13, 0.3); }
+ .ASRouterButton.secondary:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3); }
[lwt-newtab-brighttext] .secondary {
background-color: rgba(249, 249, 250, 0.1); }
@@ -3316,7 +3318,8 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
background-color: var(--newtab-element-hover-color); }
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
- display: block; }
+ display: block;
+ opacity: 1; }
.ds-outer-wrapper-breakpoint-override .below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
inset-inline-end: -8%; }
@media (max-width: 865px) {
@@ -3374,6 +3377,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
inset-inline-end: 20px;
opacity: 1;
top: 50%; }
+ .SimpleBelowSearchSnippet .blockButton:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3);
+ border-radius: 2px; }
.SimpleBelowSearchSnippet .title {
font-size: inherit;
margin: 0; }
@@ -3400,11 +3406,14 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
min-height: 60px;
background-color: transparent; }
.SimpleBelowSearchSnippet.withButton .blockButton {
- display: none;
+ display: block;
inset-inline-end: -15%;
- opacity: 1;
+ opacity: 0;
margin: auto;
top: unset; }
+ .SimpleBelowSearchSnippet.withButton .blockButton:focus {
+ opacity: 1;
+ box-shadow: none; }
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
diff --git a/browser/components/newtab/css/activity-stream-windows.css b/browser/components/newtab/css/activity-stream-windows.css
index 27687c3435a6..26a491a8e725 100644
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -1991,7 +1991,8 @@ main {
margin: 0 0 12px; }
.ds-hero .ds-card.placeholder {
margin-bottom: 20px;
- padding-bottom: 20px; }
+ padding-bottom: 20px;
+ min-height: 180px; }
.ds-hero .img-wrapper {
margin: 0 0 12px; }
.ds-hero .ds-hero-item {
@@ -2635,11 +2636,8 @@ main {
.ds-card.placeholder {
background: transparent;
box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
- border-radius: 4px; }
- .ds-card.placeholder .ds-card-link {
- cursor: default; }
- .ds-card.placeholder .img-wrapper {
- opacity: 0; }
+ border-radius: 4px;
+ min-height: 300px; }
.ds-card .img-wrapper {
width: 100%; }
.ds-card .img {
@@ -2783,12 +2781,14 @@ main {
margin-top: 12px;
position: relative; }
.story-footer .story-sponsored-label,
+ .story-footer .story-view-count,
.story-footer .status-message {
-webkit-line-clamp: 1;
font-size: 13px;
line-height: 24px;
color: #737373; }
[lwt-newtab-brighttext] .story-footer .story-sponsored-label, [lwt-newtab-brighttext]
+ .story-footer .story-view-count, [lwt-newtab-brighttext]
.story-footer .status-message {
color: #B1B1B3; }
.story-footer .status-message {
@@ -3003,6 +3003,8 @@ main {
background-color: rgba(12, 12, 13, 0.2); }
.ASRouterButton.secondary:active {
background-color: rgba(12, 12, 13, 0.3); }
+ .ASRouterButton.secondary:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3); }
[lwt-newtab-brighttext] .secondary {
background-color: rgba(249, 249, 250, 0.1); }
@@ -3313,7 +3315,8 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
.below-search-snippet.withButton .snippet-hover-wrapper:hover {
background-color: var(--newtab-element-hover-color); }
.below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
- display: block; }
+ display: block;
+ opacity: 1; }
.ds-outer-wrapper-breakpoint-override .below-search-snippet.withButton .snippet-hover-wrapper:hover .blockButton {
inset-inline-end: -8%; }
@media (max-width: 865px) {
@@ -3371,6 +3374,9 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
inset-inline-end: 20px;
opacity: 1;
top: 50%; }
+ .SimpleBelowSearchSnippet .blockButton:focus {
+ box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 4px rgba(10, 132, 255, 0.3);
+ border-radius: 2px; }
.SimpleBelowSearchSnippet .title {
font-size: inherit;
margin: 0; }
@@ -3397,11 +3403,14 @@ body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
min-height: 60px;
background-color: transparent; }
.SimpleBelowSearchSnippet.withButton .blockButton {
- display: none;
+ display: block;
inset-inline-end: -15%;
- opacity: 1;
+ opacity: 0;
margin: auto;
top: unset; }
+ .SimpleBelowSearchSnippet.withButton .blockButton:focus {
+ opacity: 1;
+ box-shadow: none; }
@media (max-width: 1120px) {
.SimpleBelowSearchSnippet.withButton .blockButton {
inset-inline-end: 2%; } }
diff --git a/browser/components/newtab/data/content/activity-stream.bundle.js b/browser/components/newtab/data/content/activity-stream.bundle.js
index 4e48d2cebf8c..5d410ff0c9b3 100644
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -7895,7 +7895,8 @@ class DSContextFooter_DSContextFooter extends external_React_default.a.PureCompo
render() {
const {
context,
- context_type
+ context_type,
+ engagement
} = this.props;
const {
icon,
@@ -7907,11 +7908,13 @@ class DSContextFooter_DSContextFooter extends external_React_default.a.PureCompo
className: "story-sponsored-label clamp"
}, context), external_React_default.a.createElement(external_ReactTransitionGroup_["TransitionGroup"], {
component: null
- }, !context && context_type && external_React_default.a.createElement(external_ReactTransitionGroup_["CSSTransition"], {
+ }, !context && (context_type || engagement) && external_React_default.a.createElement(external_ReactTransitionGroup_["CSSTransition"], {
key: fluentID,
timeout: ANIMATION_DURATION,
classNames: "story-animate"
- }, external_React_default.a.createElement(StatusMessage, {
+ }, engagement && !context_type ? external_React_default.a.createElement("div", {
+ className: "story-view-count"
+ }, engagement) : external_React_default.a.createElement(StatusMessage, {
icon: icon,
fluentID: fluentID
}))));
@@ -7935,7 +7938,8 @@ const DefaultMeta = ({
excerpt,
context,
context_type,
- cta
+ cta,
+ engagement
}) => external_React_default.a.createElement("div", {
className: "meta"
}, external_React_default.a.createElement("div", {
@@ -7952,7 +7956,8 @@ const DefaultMeta = ({
tabIndex: "0"
}, cta)), external_React_default.a.createElement(DSContextFooter_DSContextFooter, {
context_type: context_type,
- context: context
+ context: context,
+ engagement: engagement
}));
const VariantMeta = ({
source,
@@ -7961,6 +7966,7 @@ const VariantMeta = ({
context,
context_type,
cta,
+ engagement,
sponsor
}) => external_React_default.a.createElement("div", {
className: "meta"
@@ -7972,16 +7978,25 @@ const VariantMeta = ({
className: "title clamp"
}, title), excerpt && external_React_default.a.createElement("p", {
className: "excerpt clamp"
-}, excerpt)), cta && external_React_default.a.createElement("button", {
+}, excerpt)), context && cta && external_React_default.a.createElement("button", {
className: "button cta-button"
}, cta), !context && external_React_default.a.createElement(DSContextFooter_DSContextFooter, {
context_type: context_type,
- context: context
+ context: context,
+ engagement: engagement
}));
class DSCard_DSCard extends external_React_default.a.PureComponent {
constructor(props) {
super(props);
this.onLinkClick = this.onLinkClick.bind(this);
+
+ this.setPlaceholderRef = element => {
+ this.placholderElement = element;
+ };
+
+ this.state = {
+ isSeen: false
+ };
}
onLinkClick(event) {
@@ -8005,9 +8020,47 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
}
}
+ onSeen(entries) {
+ if (this.state) {
+ const entry = entries.find(e => e.isIntersecting);
+
+ if (entry) {
+ if (this.placholderElement) {
+ this.observer.unobserve(this.placholderElement);
+ } // Stop observing since element has been seen
+
+
+ this.setState({
+ isSeen: true
+ });
+ }
+ }
+ }
+
+ componentDidMount() {
+ if (this.placholderElement) {
+ this.observer = new IntersectionObserver(this.onSeen.bind(this));
+ this.observer.observe(this.placholderElement);
+ }
+ }
+
+ componentWillUnmount() {
+ // Remove observer on unmount
+ if (this.observer && this.placholderElement) {
+ this.observer.unobserve(this.placholderElement);
+ }
+ }
+
render() {
+ if (this.props.placeholder || !this.state.isSeen) {
+ return external_React_default.a.createElement("div", {
+ className: "ds-card placeholder",
+ ref: this.setPlaceholderRef
+ });
+ }
+
return external_React_default.a.createElement("div", {
- className: `ds-card${this.props.placeholder ? " placeholder" : ""}`
+ className: "ds-card"
}, external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
className: "ds-card-link",
dispatch: this.props.dispatch,
@@ -8025,6 +8078,7 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
excerpt: this.props.excerpt,
context: this.props.context,
context_type: this.props.context_type,
+ engagement: this.props.engagement,
cta: this.props.cta,
sponsor: this.props.sponsor
}), !this.props.cta_variant && external_React_default.a.createElement(DefaultMeta, {
@@ -8032,6 +8086,7 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
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
}), external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
@@ -8045,7 +8100,7 @@ class DSCard_DSCard extends external_React_default.a.PureComponent {
}],
dispatch: this.props.dispatch,
source: this.props.type
- })), !this.props.placeholder && external_React_default.a.createElement(DSLinkMenu_DSLinkMenu, {
+ })), external_React_default.a.createElement(DSLinkMenu_DSLinkMenu, {
id: this.props.id,
index: this.props.pos,
dispatch: this.props.dispatch,
@@ -8192,6 +8247,7 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
pocket_id: rec.pocket_id,
context_type: rec.context_type,
bookmarkGuid: rec.bookmarkGuid,
+ engagement: rec.engagement,
cta: rec.cta,
cta_variant: this.props.cta_variant
}));
@@ -8336,7 +8392,8 @@ class List_ListItem extends external_React_default.a.PureComponent {
className: "ds-list-item-excerpt clamp"
}, this.props.excerpt)), external_React_default.a.createElement(DSContextFooter_DSContextFooter, {
context: this.props.context,
- context_type: this.props.context_type
+ context_type: this.props.context_type,
+ engagement: this.props.engagement
})), external_React_default.a.createElement(DSImage_DSImage, {
extraClassNames: "ds-list-image",
source: this.props.image_src,
@@ -8400,7 +8457,8 @@ function _List(props) {
type: props.type,
url: rec.url,
pocket_id: rec.pocket_id,
- bookmarkGuid: rec.bookmarkGuid
+ bookmarkGuid: rec.bookmarkGuid,
+ engagement: rec.engagement
}));
}
@@ -8513,7 +8571,8 @@ class Hero_Hero extends external_React_default.a.PureComponent {
context_type: rec.context_type,
source: rec.domain,
pocket_id: rec.pocket_id,
- bookmarkGuid: rec.bookmarkGuid
+ bookmarkGuid: rec.bookmarkGuid,
+ engagement: rec.engagement
}));
}
@@ -8548,7 +8607,8 @@ class Hero_Hero extends external_React_default.a.PureComponent {
className: "excerpt clamp"
}, heroRec.excerpt)), external_React_default.a.createElement(DSContextFooter_DSContextFooter, {
context: heroRec.context,
- context_type: heroRec.context_type
+ context_type: heroRec.context_type,
+ engagement: heroRec.engagement
})), external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
campaignId: heroRec.campaign_id,
rows: [{
diff --git a/browser/components/newtab/lib/OnboardingMessageProvider.jsm b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
index 95e0acd180b9..caed83722d45 100644
--- a/browser/components/newtab/lib/OnboardingMessageProvider.jsm
+++ b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
@@ -399,10 +399,9 @@ const ONBOARDING_MESSAGES = () => [
id: "PROTECTIONS_PANEL_1",
template: "protections_panel",
content: {
- title: "Browse without being followed",
- body:
- "Keep your data to yourself. Firefox protects you from many of the most common trackers that follow what you do online.",
- link_text: "Learn more",
+ title: { string_id: "cfr-protections-panel-header" },
+ body: { string_id: "cfr-protections-panel-body" },
+ link_text: { string_id: "cfr-protections-panel-link-text" },
cta_url: `${Services.urlFormatter.formatURLPref(
"app.support.baseURL"
)}etp-promotions?as=u&utm_source=inproduct`,
diff --git a/browser/components/newtab/lib/ToolbarBadgeHub.jsm b/browser/components/newtab/lib/ToolbarBadgeHub.jsm
index 9a3c91950be9..f2f2cc3efcfe 100644
--- a/browser/components/newtab/lib/ToolbarBadgeHub.jsm
+++ b/browser/components/newtab/lib/ToolbarBadgeHub.jsm
@@ -48,6 +48,11 @@ ChromeUtils.defineModuleGetter(
"clearInterval",
"resource://gre/modules/Timer.jsm"
);
+ChromeUtils.defineModuleGetter(
+ this,
+ "requestIdleCallback",
+ "resource://gre/modules/Timer.jsm"
+);
// Frequency at which to check for new messages
const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000;
@@ -257,17 +262,17 @@ class _ToolbarBadgeHub {
}
registerBadgeToAllWindows(message) {
- // Impression should be added when the badge becomes visible
- this._addImpression(message);
- // Send a telemetry ping when adding the notification badge
- this.sendUserEventTelemetry("IMPRESSION", message);
-
if (message.template === "update_action") {
this.executeAction({ ...message.content.action, message_id: message.id });
// No badge to set only an action to execute
return;
}
+ // Impression should be added when the badge becomes visible
+ this._addImpression(message);
+ // Send a telemetry ping when adding the notification badge
+ this.sendUserEventTelemetry("IMPRESSION", message);
+
EveryWindow.registerCallback(
this.id,
win => {
@@ -297,7 +302,7 @@ class _ToolbarBadgeHub {
if (message.content.delay) {
this.state.showBadgeTimeoutId = setTimeout(() => {
- this.registerBadgeToAllWindows(message);
+ requestIdleCallback(() => this.registerBadgeToAllWindows(message));
}, message.content.delay);
} else {
this.registerBadgeToAllWindows(message);
diff --git a/browser/components/newtab/lib/ToolbarPanelHub.jsm b/browser/components/newtab/lib/ToolbarPanelHub.jsm
index 2d00a138e274..74a6a64f751c 100644
--- a/browser/components/newtab/lib/ToolbarPanelHub.jsm
+++ b/browser/components/newtab/lib/ToolbarPanelHub.jsm
@@ -378,6 +378,8 @@ class _ToolbarPanelHub {
*/
async insertProtectionPanelMessage(event) {
const win = event.target.ownerGlobal;
+ this.maybeInsertFTL(win);
+
const doc = event.target.ownerDocument;
const container = doc.getElementById("messaging-system-message-container");
const infoButton = doc.getElementById("protections-popup-info-button");
diff --git a/browser/components/newtab/locales-src/asrouter.ftl b/browser/components/newtab/locales-src/asrouter.ftl
index 882f15e2e2c1..a2b1d4926fa4 100644
--- a/browser/components/newtab/locales-src/asrouter.ftl
+++ b/browser/components/newtab/locales-src/asrouter.ftl
@@ -78,6 +78,12 @@ cfr-doorhanger-bookmark-fxa-close-btn-tooltip =
.aria-label = Close button
.title = Close
+## Protections panel
+
+cfr-protections-panel-header = Browse without being followed
+cfr-protections-panel-body = Keep your data to yourself. { -brand-short-name } protects you from many of the most common trackers that follow what you do online.
+cfr-protections-panel-link-text = Learn more
+
## What's New toolbar button and panel
cfr-whatsnew-button =
diff --git a/browser/components/newtab/locales-src/onboarding.ftl b/browser/components/newtab/locales-src/onboarding.ftl
index 3651d6a0c991..d05bcccc2ce0 100644
--- a/browser/components/newtab/locales-src/onboarding.ftl
+++ b/browser/components/newtab/locales-src/onboarding.ftl
@@ -11,7 +11,6 @@
## avoid breaking quoted text).
onboarding-button-label-learn-more = Learn More
-onboarding-button-label-try-now = Try It Now
onboarding-button-label-get-started = Get Started
## Welcome modal dialog strings
@@ -76,21 +75,6 @@ onboarding-benefit-privacy-text = Everything we do honors our Personal Data Prom
## 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-title2 = Protection From Tracking
onboarding-tracking-protection-text2 = { -brand-short-name } helps stop websites from tracking you online, making it harder for ads to follow you around the web.
diff --git a/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx
index a04508c541df..337e6409c8ba 100644
--- a/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/FirstRun.test.jsx
@@ -16,7 +16,7 @@ const FAKE_TRIPLETS = [
text: { string_id: "onboarding-private-browsing-text" },
icon: "icon",
primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
+ label: { string_id: "onboarding-button-label-get-started" },
action: {
type: "OPEN_URL",
data: { args: "https://example.com/" },
diff --git a/browser/components/newtab/test/unit/asrouter/templates/OnboardingMessage.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/OnboardingMessage.test.jsx
index 9eebf9af7102..928b6b981b7c 100644
--- a/browser/components/newtab/test/unit/asrouter/templates/OnboardingMessage.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/OnboardingMessage.test.jsx
@@ -22,7 +22,7 @@ const L10N_CONTENT = {
text: { string_id: "onboarding-private-browsing-text" },
icon: "icon",
primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
+ label: { string_id: "onboarding-button-label-get-started" },
action: { type: "SOME_TYPE" },
},
};
diff --git a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
index 3817b9005e53..73770d0d37ea 100644
--- a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
@@ -11,7 +11,7 @@ export const CARDS = [
text: { string_id: "onboarding-private-browsing-text" },
icon: "icon",
primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
+ label: { string_id: "onboarding-button-label-get-started" },
action: {
type: "OPEN_URL",
data: { args: "https://example.com/" },
diff --git a/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx b/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx
index 7698112e6c23..43fc27f40ba2 100644
--- a/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Triplets.test.jsx
@@ -11,7 +11,7 @@ const CARDS = [
text: { string_id: "onboarding-private-browsing-text" },
icon: "icon",
primary_button: {
- label: { string_id: "onboarding-button-label-try-now" },
+ label: { string_id: "onboarding-button-label-get-started" },
action: {
type: "OPEN_URL",
data: { args: "https://example.com/" },
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
index fda636bd2f46..23821f83c0eb 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
@@ -20,6 +20,7 @@ describe("", () => {
beforeEach(() => {
wrapper = shallow();
+ wrapper.setState({ isSeen: true });
sandbox = sinon.createSandbox();
});
@@ -79,6 +80,7 @@ describe("", () => {
it("should render badges for pocket, bookmark when not a spoc element ", () => {
wrapper = mount();
+ wrapper.setState({ isSeen: true });
const contextFooter = wrapper.find(DSContextFooter);
assert.lengthOf(contextFooter.find(StatusMessage), 1);
@@ -87,6 +89,7 @@ describe("", () => {
it("should render Sponsored Context for a spoc element", () => {
const context = "Sponsored by Foo";
wrapper = mount();
+ wrapper.setState({ isSeen: true });
const contextFooter = wrapper.find(DSContextFooter);
assert.lengthOf(contextFooter.find(StatusMessage), 0);
@@ -99,6 +102,7 @@ describe("", () => {
beforeEach(() => {
dispatch = sandbox.stub();
wrapper = shallow();
+ wrapper.setState({ isSeen: true });
});
it("should call dispatch with the correct events", () => {
@@ -160,6 +164,7 @@ describe("", () => {
describe("DSCard with CTA", () => {
beforeEach(() => {
wrapper = mount();
+ wrapper.setState({ isSeen: true });
});
it("should render Default Meta", () => {
@@ -178,9 +183,19 @@ describe("", () => {
assert.equal(meta.find(".cta-link").text(), "test");
});
- it("should render cta-button when item has cta and cta button variant is true", () => {
+ it("should not render cta-button for non spoc content", () => {
wrapper.setProps({ cta: "test", cta_variant: true });
const meta = wrapper.find(VariantMeta);
+ 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", () => {
+ wrapper.setProps({
+ cta: "test",
+ cta_variant: true,
+ context: "Sponsored by Foo",
+ });
+ const meta = wrapper.find(VariantMeta);
assert.equal(meta.find(".cta-button").text(), "test");
});
@@ -207,6 +222,44 @@ describe("", () => {
assert.equal(meta.find(".source").text(), "Test ยท Sponsored");
});
});
+ describe("DSCard with Intersection Observer", () => {
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it("should render card when seen", () => {
+ let card = wrapper.find("div.ds-card.placeholder");
+ assert.lengthOf(card, 1);
+
+ wrapper.instance().observer = {
+ unobserve: sandbox.stub(),
+ };
+ wrapper.instance().placholderElement = "element";
+
+ wrapper.instance().onSeen([
+ {
+ isIntersecting: true,
+ },
+ ]);
+
+ assert.isTrue(wrapper.instance().state.isSeen);
+ card = wrapper.find("div.ds-card.placeholder");
+ assert.lengthOf(card, 0);
+ assert.lengthOf(wrapper.find(SafeAnchor), 1);
+ assert.calledOnce(wrapper.instance().observer.unobserve);
+ assert.calledWith(wrapper.instance().observer.unobserve, "element");
+ });
+
+ it("should setup proper placholder ref for isSeen", () => {
+ wrapper.instance().setPlaceholderRef("element");
+ assert.equal(wrapper.instance().placholderElement, "element");
+ });
+
+ it("should setup observer on componentDidMount", () => {
+ wrapper = mount();
+ assert.isTrue(!!wrapper.instance().observer);
+ });
+ });
});
describe(" component", () => {
@@ -221,21 +274,21 @@ describe(" component", () => {
it("should contain placeholder div", () => {
const wrapper = shallow();
+ wrapper.setState({ isSeen: true });
const card = wrapper.find("div.ds-card.placeholder");
assert.lengthOf(card, 1);
});
it("should not be clickable", () => {
const wrapper = shallow();
+ wrapper.setState({ isSeen: true });
const anchor = wrapper.find("SafeAnchor.ds-card-link");
- assert.lengthOf(anchor, 1);
-
- const linkClick = anchor.prop("onLinkClick");
- assert.isUndefined(linkClick);
+ assert.lengthOf(anchor, 0);
});
it("should not have context menu", () => {
const wrapper = shallow();
+ wrapper.setState({ isSeen: true });
const linkMenu = wrapper.find(DSLinkMenu);
assert.lengthOf(linkMenu, 0);
});
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
index 232134e4dcf3..a36a0fcac14e 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
@@ -12,6 +12,7 @@ describe("", () => {
const bookmarkBadge = "bookmark";
const removeBookmarkBadge = "removedBookmark";
const context = "Sponsored by Babel";
+ const engagement = "Popular";
beforeEach(() => {
wrapper = mount();
@@ -26,19 +27,32 @@ describe("", () => {
assert.isTrue(wrapper.exists());
assert.isOk(wrapper.find(".story-footer"));
});
+ it("should render an engagement status if no badge and spoc passed", () => {
+ wrapper = mount();
+
+ const engagementLabel = wrapper.find(".story-view-count");
+ assert.equal(engagementLabel.text(), engagement);
+ });
it("should render a badge if a proper badge prop is passed", () => {
- wrapper = mount();
+ wrapper = mount(
+
+ );
const { fluentID } = cardContextTypes[bookmarkBadge];
+ assert.lengthOf(wrapper.find(".story-view-count"), 0);
const statusLabel = wrapper.find(".story-context-label");
- assert.isOk(statusLabel);
assert.equal(statusLabel.prop("data-l10n-id"), fluentID);
});
it("should only render a sponsored context if pass a sponsored context", async () => {
wrapper = mount(
-
+
);
+ assert.lengthOf(wrapper.find(".story-view-count"), 0);
assert.lengthOf(wrapper.find(StatusMessage), 0);
assert.equal(wrapper.find(".story-sponsored-label").text(), context);
});
diff --git a/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js b/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
index 838995cf68eb..4761fa3a8e8b 100644
--- a/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
+++ b/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
@@ -23,6 +23,7 @@ describe("ToolbarBadgeHub", () => {
let getStringPrefStub;
let clearUserPrefStub;
let setStringPrefStub;
+ let requestIdleCallbackStub;
beforeEach(async () => {
globals = new GlobalOverrider();
sandbox = sinon.createSandbox();
@@ -67,7 +68,9 @@ describe("ToolbarBadgeHub", () => {
getStringPrefStub = sandbox.stub();
clearUserPrefStub = sandbox.stub();
setStringPrefStub = sandbox.stub();
+ requestIdleCallbackStub = sandbox.stub().callsFake(fn => fn());
globals.set({
+ requestIdleCallback: requestIdleCallbackStub,
EveryWindow: everyWindowStub,
PrivateBrowsingUtils: { isBrowserPrivate: isBrowserPrivateStub },
setTimeout: setTimeoutStub,
@@ -562,6 +565,8 @@ describe("ToolbarBadgeHub", () => {
instance.registerBadgeToAllWindows,
msg_with_delay
);
+ // Delayed actions should be executed inside requestIdleCallback
+ assert.calledOnce(requestIdleCallbackStub);
});
});
describe("#sendUserEventTelemetry", () => {
diff --git a/browser/locales/en-US/browser/newtab/asrouter.ftl b/browser/locales/en-US/browser/newtab/asrouter.ftl
index 882f15e2e2c1..a2b1d4926fa4 100644
--- a/browser/locales/en-US/browser/newtab/asrouter.ftl
+++ b/browser/locales/en-US/browser/newtab/asrouter.ftl
@@ -78,6 +78,12 @@ cfr-doorhanger-bookmark-fxa-close-btn-tooltip =
.aria-label = Close button
.title = Close
+## Protections panel
+
+cfr-protections-panel-header = Browse without being followed
+cfr-protections-panel-body = Keep your data to yourself. { -brand-short-name } protects you from many of the most common trackers that follow what you do online.
+cfr-protections-panel-link-text = Learn more
+
## What's New toolbar button and panel
cfr-whatsnew-button =
diff --git a/browser/locales/en-US/browser/newtab/onboarding.ftl b/browser/locales/en-US/browser/newtab/onboarding.ftl
index 3651d6a0c991..d05bcccc2ce0 100644
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -11,7 +11,6 @@
## avoid breaking quoted text).
onboarding-button-label-learn-more = Learn More
-onboarding-button-label-try-now = Try It Now
onboarding-button-label-get-started = Get Started
## Welcome modal dialog strings
@@ -76,21 +75,6 @@ onboarding-benefit-privacy-text = Everything we do honors our Personal Data Prom
## 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-title2 = Protection From Tracking
onboarding-tracking-protection-text2 = { -brand-short-name } helps stop websites from tracking you online, making it harder for ads to follow you around the web.