summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab')
-rw-r--r--browser/components/newtab/.eslintrc.js5
-rw-r--r--browser/components/newtab/AboutNewTabService.sys.mjs4
-rw-r--r--browser/components/newtab/components/CustomElements/paragraph.js2
-rw-r--r--browser/components/newtab/content-src/components/Base/Base.jsx15
-rw-r--r--browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx17
-rw-r--r--browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx2
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx19
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx1
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss35
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx7
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx8
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx76
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss32
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx9
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss3
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx6
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx74
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx34
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss98
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss33
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx9
-rw-r--r--browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx2
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSite.jsx79
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSites.jsx2
-rw-r--r--browser/components/newtab/content-src/components/TopSites/_TopSites.scss19
-rw-r--r--browser/components/newtab/content-src/lib/init-store.js2
-rw-r--r--browser/components/newtab/content-src/lib/link-menu-options.js2
-rw-r--r--browser/components/newtab/content-src/lib/selectLayoutRender.js2
-rw-r--r--browser/components/newtab/content-src/styles/_activity-stream.scss2
-rw-r--r--browser/components/newtab/content-src/styles/_icons.scss4
-rw-r--r--browser/components/newtab/css/activity-stream-linux.css176
-rw-r--r--browser/components/newtab/css/activity-stream-mac.css176
-rw-r--r--browser/components/newtab/css/activity-stream-windows.css176
-rw-r--r--browser/components/newtab/data/content/activity-stream.bundle.js290
-rw-r--r--browser/components/newtab/data/content/assets/sponsor-message-icon.svg6
-rw-r--r--browser/components/newtab/docs/index.rst2
-rw-r--r--browser/components/newtab/docs/v2-system-addon/preferences.md2
-rw-r--r--browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md8
-rw-r--r--browser/components/newtab/karma.mc.config.js8
-rw-r--r--browser/components/newtab/lib/ActivityStream.sys.mjs4
-rw-r--r--browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs2
-rw-r--r--browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs19
-rw-r--r--browser/components/newtab/lib/DownloadsManager.sys.mjs2
-rw-r--r--browser/components/newtab/lib/PersonalityProvider/RecipeExecutor.mjs4
-rw-r--r--browser/components/newtab/lib/PrefsFeed.sys.mjs4
-rw-r--r--browser/components/newtab/lib/RecommendationProvider.sys.mjs2
-rw-r--r--browser/components/newtab/lib/SectionsManager.sys.mjs4
-rw-r--r--browser/components/newtab/lib/Store.sys.mjs2
-rw-r--r--browser/components/newtab/lib/TelemetryFeed.sys.mjs10
-rw-r--r--browser/components/newtab/lib/TopSitesFeed.sys.mjs6
-rw-r--r--browser/components/newtab/loaders/inject-loader.js59
-rw-r--r--browser/components/newtab/metrics.yaml21
-rw-r--r--browser/components/newtab/package-lock.json230
-rw-r--r--browser/components/newtab/package.json6
-rw-r--r--browser/components/newtab/test/browser/browser_newtab_ping.js2
-rw-r--r--browser/components/newtab/test/schemas/pings.js2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx8
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/FeatureHighlight.test.jsx60
-rw-r--r--browser/components/newtab/test/unit/content-src/components/ModalOverlay.test.jsx69
-rw-r--r--browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx52
-rw-r--r--browser/components/newtab/test/unit/lib/AboutPreferences.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/ActivityStream.test.js10
-rw-r--r--browser/components/newtab/test/unit/lib/PersonalityProvider/PersonalityProvider.test.js4
-rw-r--r--browser/components/newtab/test/unit/lib/PersonalityProvider/RecipeExecutor.test.js2
-rw-r--r--browser/components/newtab/test/unit/unit-entry.js14
-rw-r--r--browser/components/newtab/test/unit/utils.js10
-rw-r--r--browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js69
-rw-r--r--browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js90
-rw-r--r--browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry_glean.js238
-rw-r--r--browser/components/newtab/test/xpcshell/test_HighlightsFeed.js4
-rw-r--r--browser/components/newtab/test/xpcshell/test_PlacesFeed.js10
-rw-r--r--browser/components/newtab/test/xpcshell/test_Store.js2
-rw-r--r--browser/components/newtab/test/xpcshell/test_TelemetryFeed.js32
-rw-r--r--browser/components/newtab/test/xpcshell/xpcshell.toml6
-rw-r--r--browser/components/newtab/tools/resourceUriPlugin.js9
-rw-r--r--browser/components/newtab/webpack.system-addon.config.js3
77 files changed, 1521 insertions, 1001 deletions
diff --git a/browser/components/newtab/.eslintrc.js b/browser/components/newtab/.eslintrc.js
index 5fc7a4dcff..f541cdd988 100644
--- a/browser/components/newtab/.eslintrc.js
+++ b/browser/components/newtab/.eslintrc.js
@@ -58,7 +58,7 @@ module.exports = {
{
// Use a configuration that's appropriate for modules, workers and
// non-production files.
- files: ["*.jsm", "lib/cache.worker.js", "test/**"],
+ files: ["lib/cache.worker.js", "test/**"],
rules: {
"no-implicit-globals": "off",
},
@@ -123,15 +123,12 @@ module.exports = {
"consistent-this": ["error", "use-bind"],
eqeqeq: "error",
"func-name-matching": "error",
- "getter-return": "error",
"guard-for-in": "error",
- "max-depth": ["error", 4],
"max-nested-callbacks": ["error", 4],
"max-params": ["error", 6],
"max-statements": ["error", 50],
"new-cap": ["error", { newIsCap: true, capIsNew: false }],
"no-alert": "error",
- "no-console": ["error", { allow: ["error"] }],
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-eq-null": "error",
diff --git a/browser/components/newtab/AboutNewTabService.sys.mjs b/browser/components/newtab/AboutNewTabService.sys.mjs
index e73e1b1880..73502fcb4f 100644
--- a/browser/components/newtab/AboutNewTabService.sys.mjs
+++ b/browser/components/newtab/AboutNewTabService.sys.mjs
@@ -339,7 +339,7 @@ export const AboutHomeStartupCacheChild = {
});
},
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic === "memory-pressure" && this._cacheWorker) {
this._cacheWorker.terminate();
this._cacheWorker = null;
@@ -447,7 +447,7 @@ class BaseAboutNewTabService {
return this.defaultURL;
}
- aboutHomeChannel(uri, loadInfo) {
+ aboutHomeChannel() {
throw Components.Exception(
"AboutHomeChannel not implemented for this process.",
Cr.NS_ERROR_NOT_IMPLEMENTED
diff --git a/browser/components/newtab/components/CustomElements/paragraph.js b/browser/components/newtab/components/CustomElements/paragraph.js
index dce8a229a4..f9818dbde9 100644
--- a/browser/components/newtab/components/CustomElements/paragraph.js
+++ b/browser/components/newtab/components/CustomElements/paragraph.js
@@ -49,7 +49,7 @@
return ["fluent-remote-id"];
}
- attributeChangedCallback(name, oldValue, newValue) {
+ attributeChangedCallback() {
this.render();
}
diff --git a/browser/components/newtab/content-src/components/Base/Base.jsx b/browser/components/newtab/content-src/components/Base/Base.jsx
index 0580267f26..20402b09f5 100644
--- a/browser/components/newtab/content-src/components/Base/Base.jsx
+++ b/browser/components/newtab/content-src/components/Base/Base.jsx
@@ -165,6 +165,7 @@ export class BaseContent extends React.PureComponent {
const { App } = props;
const { initialized, customizeMenuVisible } = App;
const prefs = props.Prefs.values;
+ const { pocketConfig } = prefs;
const isDiscoveryStream =
props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
@@ -172,6 +173,14 @@ export class BaseContent extends React.PureComponent {
section => section.id !== "topstories"
);
+ let spocMessageVariant = "";
+ if (
+ props.App.locale?.startsWith("en-") &&
+ pocketConfig?.spocMessageVariant === "variant-c"
+ ) {
+ spocMessageVariant = pocketConfig.spocMessageVariant;
+ }
+
const pocketEnabled =
prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
const noSectionsEnabled =
@@ -218,6 +227,7 @@ export class BaseContent extends React.PureComponent {
pocketRegion={pocketRegion}
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
mayHaveSponsoredStories={mayHaveSponsoredStories}
+ spocMessageVariant={spocMessageVariant}
showing={customizeMenuVisible}
/>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions*/}
@@ -239,7 +249,10 @@ export class BaseContent extends React.PureComponent {
<div className={`body-wrapper${initialized ? " on" : ""}`}>
{isDiscoveryStream ? (
<ErrorBoundary className="borderless-error">
- <DiscoveryStreamBase locale={props.App.locale} />
+ <DiscoveryStreamBase
+ locale={props.App.locale}
+ mayHaveSponsoredStories={mayHaveSponsoredStories}
+ />
</ErrorBoundary>
) : (
<Sections />
diff --git a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
index 679e8e137f..98bf88fbea 100644
--- a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
+++ b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
@@ -4,6 +4,7 @@
import { ErrorBoundary } from "content-src/components/ErrorBoundary/ErrorBoundary";
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
+import { SponsoredContentHighlight } from "../DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight";
import React from "react";
import { connect } from "react-redux";
@@ -48,7 +49,14 @@ export class _CollapsibleSection extends React.PureComponent {
render() {
const { isAnimating, maxHeight, menuButtonHover, showContextMenu } =
this.state;
- const { id, collapsed, learnMore, title, subTitle } = this.props;
+ const {
+ id,
+ collapsed,
+ learnMore,
+ title,
+ subTitle,
+ mayHaveSponsoredStories,
+ } = this.props;
const active = menuButtonHover || showContextMenu;
let bodyStyle;
if (isAnimating && !collapsed) {
@@ -91,6 +99,13 @@ export class _CollapsibleSection extends React.PureComponent {
<FluentOrText message={subTitle} />
</span>
)}
+ {mayHaveSponsoredStories &&
+ this.props.spocMessageVariant === "variant-a" && (
+ <SponsoredContentHighlight
+ position="inset-block-start inset-inline-start"
+ dispatch={this.props.dispatch}
+ />
+ )}
</h3>
</div>
<ErrorBoundary className="section-body-fallback">
diff --git a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
index 0364f5386a..58df7d012b 100644
--- a/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
+++ b/browser/components/newtab/content-src/components/ContextMenu/ContextMenuButton.jsx
@@ -16,7 +16,7 @@ export class ContextMenuButton extends React.PureComponent {
this.onUpdate = this.onUpdate.bind(this);
}
- openContextMenu(isKeyBoard, event) {
+ openContextMenu(isKeyBoard) {
if (this.props.onUpdate) {
this.props.onUpdate(true);
}
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
index 57ed935e93..298dedcee5 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
@@ -4,6 +4,7 @@
import React from "react";
import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
export class ContentSection extends React.PureComponent {
constructor(props) {
@@ -96,6 +97,7 @@ export class ContentSection extends React.PureComponent {
mayHaveSponsoredStories,
mayHaveRecentSaves,
openPreferences,
+ spocMessageVariant,
} = this.props;
const {
topSitesEnabled,
@@ -254,6 +256,23 @@ export class ContentSection extends React.PureComponent {
</label>
</div>
+ {pocketRegion &&
+ mayHaveSponsoredStories &&
+ spocMessageVariant === "variant-c" && (
+ <div className="sponsored-content-info">
+ <div className="icon icon-help"></div>
+ <div>
+ Sponsored content supports our mission to build a better web.{" "}
+ <SafeAnchor
+ dispatch={this.props.dispatch}
+ url="https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ >
+ Find out how
+ </SafeAnchor>
+ </div>
+ </div>
+ )}
+
<span className="divider" role="separator"></span>
<div>
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
index 3d33f6fde7..54dcd550c4 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
@@ -71,6 +71,7 @@ export class _CustomizeMenu extends React.PureComponent {
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
+ spocMessageVariant={this.props.spocMessageVariant}
dispatch={this.props.dispatch}
/>
</div>
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
index f534b8701b..579e455a3f 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
@@ -143,6 +143,10 @@
border-radius: 4px;
appearance: none;
background-color: var(--newtab-element-secondary-color);
+
+ &:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+ }
}
.sponsored-checkbox:checked {
@@ -151,6 +155,14 @@
background: url('chrome://global/skin/icons/check.svg') center no-repeat;
background-color: var(--newtab-primary-action-background);
+ &:hover {
+ background-color: var(--newtab-primary-element-hover-color);
+ }
+
+ &:active {
+ background-color: var(--newtab-primary-element-active-color);
+ }
+
@media (forced-colors: active) {
fill: $black;
}
@@ -178,6 +190,10 @@
background-origin: content-box;
background-color: var(--newtab-background-color-secondary);
+ &:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+ }
+
&:dir(rtl) {
background-position-x: left;
}
@@ -201,6 +217,25 @@
}
}
+ .sponsored-content-info {
+ display: flex;
+ gap: var(--space-small);
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--newtab-element-secondary-color);
+ padding: var(--space-small) var(--space-large);
+
+ .icon-help {
+ flex-shrink: 0;
+ color: var(--color-accent-primary);
+ height: 20px;
+ }
+
+ a {
+ color: var(--newtab-primary-action-background);
+ }
+ }
+
.divider {
border-top: 1px var(--newtab-border-color) solid;
margin: 0 -16px;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
index 0112013391..3c31a5a29f 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
@@ -143,7 +143,7 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
);
}
- restorePrefDefaults(event) {
+ restorePrefDefaults() {
this.props.dispatch(
ac.OnlyToMain({
type: at.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS,
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
index dff122b366..0f0ee51ab9 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
@@ -110,7 +110,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
});
}
- renderComponent(component, embedWidth) {
+ renderComponent(component) {
switch (component.type) {
case "Highlights":
return <Highlights />;
@@ -196,6 +196,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
onboardingExperience={component.properties.onboardingExperience}
ctaButtonSponsors={component.properties.ctaButtonSponsors}
ctaButtonVariant={component.properties.ctaButtonVariant}
+ spocMessageVariant={component.properties.spocMessageVariant}
editorsPicksHeader={component.properties.editorsPicksHeader}
recentSavesEnabled={this.props.DiscoveryStream.recentSavesEnabled}
hideDescriptions={this.props.DiscoveryStream.hideDescriptions}
@@ -218,7 +219,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
}
render() {
- const { locale } = this.props;
+ const { locale, mayHaveSponsoredStories } = this.props;
// Select layout render data by adding spocs and position to recommendations
const { layoutRender } = selectLayoutRender({
state: this.props.DiscoveryStream,
@@ -322,6 +323,8 @@ export class _DiscoveryStreamBase extends React.PureComponent {
showPrefName={topStories.pref.feed}
title={sectionTitle}
subTitle={subTitle}
+ mayHaveSponsoredStories={mayHaveSponsoredStories}
+ spocMessageVariant={message?.properties?.spocMessageVariant}
eventSource="CARDGRID"
>
{this.renderLayout(layoutRender)}
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 f13e0eb7ed..cf00361df2 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -31,11 +31,7 @@ export function DSSubHeader({ children }) {
);
}
-export function OnboardingExperience({
- children,
- dispatch,
- windowObj = global,
-}) {
+export function OnboardingExperience({ dispatch, windowObj = global }) {
const [dismissed, setDismissed] = useState(false);
const [maxHeight, setMaxHeight] = useState(null);
const heightElement = useRef(null);
@@ -330,6 +326,7 @@ export class _CardGrid extends React.PureComponent {
onboardingExperience,
ctaButtonSponsors,
ctaButtonVariant,
+ spocMessageVariant,
widgets,
recentSavesEnabled,
hideDescriptions,
@@ -378,6 +375,7 @@ export class _CardGrid extends React.PureComponent {
saveToPocketCard={saveToPocketCard}
ctaButtonSponsors={ctaButtonSponsors}
ctaButtonVariant={ctaButtonVariant}
+ spocMessageVariant={spocMessageVariant}
recommendation_id={rec.recommendation_id}
/>
)
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 6aef56fb33..f3e1eab503 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -85,8 +85,9 @@ export const DefaultMeta = ({
sponsor,
sponsored_by_override,
saveToPocketCard,
- isRecentSave,
ctaButtonVariant,
+ dispatch,
+ spocMessageVariant,
}) => (
<div className="meta">
<div className="info-wrap">
@@ -100,9 +101,7 @@ export const DefaultMeta = ({
sponsored_by_override={sponsored_by_override}
/>
)}
- <header title={title} className="title clamp">
- {title}
- </header>
+ <header className="title clamp">{title}</header>
{excerpt && <p className="excerpt clamp">{excerpt}</p>}
</div>
{!newSponsoredLabel && (
@@ -113,6 +112,8 @@ export const DefaultMeta = ({
sponsored_by_override={sponsored_by_override}
cta_button_variant={ctaButtonVariant}
source={source}
+ dispatch={dispatch}
+ spocMessageVariant={spocMessageVariant}
/>
)}
{/* Sponsored label is normally in the way of any message.
@@ -183,7 +184,7 @@ export class _DSCard extends React.PureComponent {
];
}
- onLinkClick(event) {
+ onLinkClick() {
if (this.props.dispatch) {
this.props.dispatch(
ac.DiscoveryStreamUserEvent({
@@ -223,7 +224,7 @@ export class _DSCard extends React.PureComponent {
}
}
- onSaveClick(event) {
+ onSaveClick() {
if (this.props.dispatch) {
this.props.dispatch(
ac.AlsoToMain({
@@ -408,43 +409,28 @@ export class _DSCard extends React.PureComponent {
};
return (
- <div
+ <article
className={`ds-card ${compactImagesClassName} ${imageGradientClassName} ${titleLinesName} ${descLinesClassName} ${ctaButtonClassName} ${ctaButtonVariantClassName}`}
ref={this.setContextMenuButtonHostRef}
>
+ <div className="img-wrapper">
+ <DSImage
+ extraClassNames="img"
+ source={this.props.image_src}
+ rawSource={this.props.raw_image_src}
+ sizes={this.dsImageSizes}
+ url={this.props.url}
+ title={this.props.title}
+ isRecentSave={isRecentSave}
+ />
+ </div>
<SafeAnchor
className="ds-card-link"
dispatch={this.props.dispatch}
onLinkClick={!this.props.placeholder ? this.onLinkClick : undefined}
url={this.props.url}
+ title={this.props.title}
>
- <div className="img-wrapper">
- <DSImage
- extraClassNames="img"
- source={this.props.image_src}
- rawSource={this.props.raw_image_src}
- sizes={this.dsImageSizes}
- url={this.props.url}
- title={this.props.title}
- isRecentSave={isRecentSave}
- />
- </div>
- {ctaButtonVariant === "variant-b" && (
- <div className="cta-header">Shop Now</div>
- )}
- <DefaultMeta
- source={source}
- title={this.props.title}
- excerpt={excerpt}
- newSponsoredLabel={newSponsoredLabel}
- timeToRead={timeToRead}
- context={this.props.context}
- context_type={this.props.context_type}
- sponsor={this.props.sponsor}
- sponsored_by_override={this.props.sponsored_by_override}
- saveToPocketCard={saveToPocketCard}
- ctaButtonVariant={ctaButtonVariant}
- />
<ImpressionStats
flightId={this.props.flightId}
rows={[
@@ -461,6 +447,24 @@ export class _DSCard extends React.PureComponent {
source={this.props.type}
/>
</SafeAnchor>
+ {ctaButtonVariant === "variant-b" && (
+ <div className="cta-header">Shop Now</div>
+ )}
+ <DefaultMeta
+ source={source}
+ title={this.props.title}
+ excerpt={excerpt}
+ newSponsoredLabel={newSponsoredLabel}
+ timeToRead={timeToRead}
+ context={this.props.context}
+ context_type={this.props.context_type}
+ sponsor={this.props.sponsor}
+ sponsored_by_override={this.props.sponsored_by_override}
+ saveToPocketCard={saveToPocketCard}
+ ctaButtonVariant={ctaButtonVariant}
+ dispatch={this.props.dispatch}
+ spocMessageVariant={this.props.spocMessageVariant}
+ />
{saveToPocketCard && (
<div className="card-stp-button-hover-background">
<div className="card-stp-button-position-wrapper">
@@ -512,7 +516,7 @@ export class _DSCard extends React.PureComponent {
isRecentSave={isRecentSave}
/>
)}
- </div>
+ </article>
);
}
}
@@ -526,4 +530,4 @@ export const DSCard = connect(state => ({
DiscoveryStream: state.DiscoveryStream,
}))(_DSCard);
-export const PlaceholderDSCard = props => <DSCard placeholder={true} />;
+export const PlaceholderDSCard = () => <DSCard placeholder={true} />;
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 92afedff26..9004e609df 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -118,6 +118,16 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
transform: scale(1);
}
}
+
+ header {
+ color: var(--newtab-primary-action-background);
+ }
+ }
+
+ &:active {
+ header {
+ color: var(--newtab-primary-element-active-color);
+ }
}
.img {
@@ -131,31 +141,15 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
}
.ds-card-link {
+ position: absolute;
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
text-decoration: none;
- &:hover {
- header {
- color: var(--newtab-primary-action-background);
- }
- }
-
&:focus {
@include ds-focus;
transition: none;
-
- header {
- color: var(--newtab-primary-action-background);
- }
- }
-
- &:active {
- header {
- color: var(--newtab-primary-element-active-color);
- }
}
}
@@ -255,7 +249,7 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
- column-gap: var(--space-small);
+ gap: 0 var(--space-small);
margin-top: 0;
}
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 5c7e79685e..6c0641cfc1 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
@@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { cardContextTypes } from "../../Card/types.js";
+import { SponsoredContentHighlight } from "../FeatureHighlight/SponsoredContentHighlight";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
import React from "react";
@@ -82,6 +83,8 @@ export class DSContextFooter extends React.PureComponent {
sponsored_by_override,
cta_button_variant,
source,
+ spocMessageVariant,
+ dispatch,
} = this.props;
const sponsorLabel = SponsorLabel({
@@ -119,6 +122,12 @@ export class DSContextFooter extends React.PureComponent {
return (
<div className="story-footer">
{sponsorLabel}
+ {sponsorLabel && spocMessageVariant === "variant-b" && (
+ <SponsoredContentHighlight
+ dispatch={dispatch}
+ position="inset-block-end inset-inline-start"
+ />
+ )}
{dsMessageLabel}
</div>
);
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 c23bb1c661..b28b498682 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss
@@ -2,7 +2,10 @@
color: var(--newtab-text-secondary-color);
inset-inline-start: 0;
margin-top: 12px;
+ display: flex;
+ gap: var(--space-large);
position: relative;
+ pointer-events: none;
.story-sponsored-label span {
display: inline-block;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
index f342c9829b..b251fb0401 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
@@ -17,7 +17,7 @@ export class DSPrivacyModal extends React.PureComponent {
this.onManageLinkClick = this.onManageLinkClick.bind(this);
}
- onLearnLinkClick(event) {
+ onLearnLinkClick() {
this.props.dispatch(
ac.DiscoveryStreamUserEvent({
event: "CLICK_PRIVACY_INFO",
@@ -26,7 +26,7 @@ export class DSPrivacyModal extends React.PureComponent {
);
}
- onManageLinkClick(event) {
+ onManageLinkClick() {
this.props.dispatch(ac.OnlyToMain({ type: at.SETTINGS_OPEN }));
}
@@ -50,7 +50,7 @@ export class DSPrivacyModal extends React.PureComponent {
className="modal-link modal-link-privacy"
data-l10n-id="newtab-privacy-modal-link"
onClick={this.onLearnLinkClick}
- href="https://help.getpocket.com/article/1142-firefox-new-tab-recommendations-faq"
+ href="https://support.mozilla.org/kb/pocket-recommendations-firefox-new-tab"
/>
<button
className="modal-link modal-link-manage"
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
new file mode 100644
index 0000000000..792be40ba3
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
@@ -0,0 +1,74 @@
+/* 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, { useState, useCallback, useRef, useEffect } from "react";
+import { actionCreators as ac } from "common/Actions.sys.mjs";
+
+export function FeatureHighlight({
+ message,
+ icon,
+ toggle,
+ position = "top-left",
+ title,
+ ariaLabel,
+ feature = "FEATURE_HIGHLIGHT_DEFAULT",
+ dispatch = () => {},
+ windowObj = global,
+}) {
+ const [opened, setOpened] = useState(false);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handleOutsideClick = e => {
+ if (!ref?.current?.contains(e.target)) {
+ setOpened(false);
+ }
+ };
+
+ windowObj.document.addEventListener("click", handleOutsideClick);
+ return () => {
+ windowObj.document.removeEventListener("click", handleOutsideClick);
+ };
+ }, [windowObj]);
+
+ const onToggleClick = useCallback(() => {
+ if (!opened) {
+ dispatch(
+ ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "FEATURE_HIGHLIGHT",
+ value: {
+ feature,
+ },
+ })
+ );
+ }
+ setOpened(!opened);
+ }, [dispatch, feature, opened]);
+
+ const openedClassname = opened ? `opened` : `closed`;
+ return (
+ <div ref={ref} className="feature-highlight">
+ <button
+ title={title}
+ aria-haspopup="true"
+ aria-label={ariaLabel}
+ className="toggle-button"
+ onClick={onToggleClick}
+ >
+ {toggle}
+ </button>
+ <div className={`feature-highlight-modal ${position} ${openedClassname}`}>
+ <div className="message-icon">{icon}</div>
+ <p>{message}</p>
+ <button
+ title="Dismiss"
+ aria-label="Close sponsored content more info popup"
+ className="icon icon-dismiss"
+ onClick={() => setOpened(false)}
+ ></button>
+ </div>
+ </div>
+ );
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx
new file mode 100644
index 0000000000..8fdd03be6d
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.jsx
@@ -0,0 +1,34 @@
+/* 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 { FeatureHighlight } from "./FeatureHighlight";
+import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
+import React from "react";
+
+export function SponsoredContentHighlight({ position, dispatch }) {
+ return (
+ <div className="sponsored-content-highlight">
+ <FeatureHighlight
+ position={position}
+ ariaLabel="Sponsored content supports our mission to build a better web."
+ title="Sponsored content more info"
+ feature="SPONSORED_CONTENT_INFO"
+ dispatch={dispatch}
+ message={
+ <span>
+ Sponsored content supports our mission to build a better web.{" "}
+ <SafeAnchor
+ dispatch={dispatch}
+ url="https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ >
+ Find out how
+ </SafeAnchor>
+ </span>
+ }
+ icon={<div className="sponsored-message-icon"></div>}
+ toggle={<div className="icon icon-help"></div>}
+ />
+ </div>
+ );
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss
new file mode 100644
index 0000000000..c0fdd52f58
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss
@@ -0,0 +1,98 @@
+.feature-highlight {
+ position: relative;
+ // This is needed because in 1 case this is positioned under a link
+ // and in an element that's not clickable.
+ pointer-events: auto;
+ z-index: 1;
+
+ .feature-highlight-modal {
+ position: absolute;
+ display: flex;
+ opacity: 0;
+ visibility: hidden;
+ cursor: default;
+ justify-content: space-between;
+ border-radius: var(--border-radius-small);
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 15%);
+ width: 298px;
+ transition: opacity 0.3s, visibility 0.3s;
+
+ .icon-dismiss {
+ flex-shrink: 0;
+ cursor: pointer;
+ background-size: $smaller-icon-size;
+ height: $smaller-icon-size;
+ width: $smaller-icon-size;
+ margin: var(--space-medium);
+ color: var(--icon-color);
+ border: none;
+ }
+
+ .message-icon {
+ margin-block: var(--space-large);
+ margin-inline: var(--space-large) var(--space-medium);
+ }
+
+ &.opened {
+ opacity: 1;
+ visibility: visible;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 4px 4px 6px -2px rgba(0, 0, 0, 15%);
+ }
+
+ &.inset-block-start {
+ inset-block-end: 100%;
+ margin-bottom: var(--space-xlarge);
+
+ &::after {
+ inset-block-end: -12px;
+ transform: rotate(45deg);
+ }
+ }
+
+ &.inset-block-end {
+ inset-block-start: 100%;
+ margin-top: var(--space-xlarge);
+
+ &::after {
+ inset-block-start: -12px;
+ transform: rotate(225deg);
+ }
+ }
+
+ &.inset-inline-start {
+ inset-inline-end: calc(var(--space-xxlarge) * -1);
+
+ &::after {
+ inset-inline-end: calc(var(--space-xxlarge) - 12px);
+ }
+ }
+
+ &.inset-inline-end {
+ inset-inline-start: calc(var(--space-xxlarge) * -1);
+
+ &::after {
+ inset-inline-start: calc(var(--space-xxlarge) - 12px);
+ }
+ }
+
+ p {
+ font-size: var(--font-size-small);
+ font-weight: normal;
+ margin: var(--space-large) 0;
+ }
+ }
+
+ .toggle-button {
+ border: none;
+ padding: 0;
+ }
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss
new file mode 100644
index 0000000000..1dc024b7c0
--- /dev/null
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_SponsoredContentHighlight.scss
@@ -0,0 +1,33 @@
+.sponsored-content-highlight {
+ float: inline-end;
+
+ .sponsored-message-icon {
+ background-image: url('chrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg');
+ background-size: 18px;
+ height: 18px;
+ width: 18px;
+ }
+
+ .icon-help {
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ background-size: 20px;
+ color: var(--icon-color);
+ vertical-align: text-bottom;
+ }
+
+ .feature-highlight-modal {
+ &.inset-inline-start {
+ &::after {
+ margin-inline-end: 11px;
+ }
+ }
+
+ &.inset-inline-end {
+ &::after {
+ margin-inline-start: 9px;
+ }
+ }
+ }
+}
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
index cfbc6fe6cb..72ec94e1fe 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
@@ -55,9 +55,14 @@ export class SafeAnchor extends React.PureComponent {
}
render() {
- const { url, className } = this.props;
+ const { url, className, title } = this.props;
return (
- <a href={this.safeURI(url)} className={className} onClick={this.onClick}>
+ <a
+ href={this.safeURI(url)}
+ title={title}
+ className={className}
+ onClick={this.onClick}
+ >
{this.props.children}
</a>
);
diff --git a/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx b/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
index 1834a0a521..6bf1614ce7 100644
--- a/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
+++ b/browser/components/newtab/content-src/components/ErrorBoundary/ErrorBoundary.jsx
@@ -52,7 +52,7 @@ export class ErrorBoundary extends React.PureComponent {
this.state = { hasError: false };
}
- componentDidCatch(error, info) {
+ componentDidCatch() {
this.setState({ hasError: true });
}
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
index b7f0038558..c0932104af 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
@@ -159,7 +159,10 @@ export class TopSiteLink extends React.PureComponent {
// If we have tabbed to a search shortcut top site, and we click 'enter',
// we should execute the onClick function. This needs to be added because
// search top sites are anchor tags without an href. See bug 1483135
- if (this.props.link.searchTopSite && event.key === "Enter") {
+ if (
+ event.key === "Enter" &&
+ (this.props.link.searchTopSite || this.props.isAddButton)
+ ) {
this.props.onClick(event);
}
}
@@ -250,8 +253,15 @@ export class TopSiteLink extends React.PureComponent {
}
render() {
- const { children, className, isDraggable, link, onClick, title } =
- this.props;
+ const {
+ children,
+ className,
+ isDraggable,
+ link,
+ onClick,
+ title,
+ isAddButton,
+ } = this.props;
const topSiteOuterClassName = `top-site-outer${
className ? ` ${className}` : ""
}${link.isDragged ? " dragged" : ""}${
@@ -266,6 +276,10 @@ export class TopSiteLink extends React.PureComponent {
selectedColor,
} = this.calculateStyle();
+ let addButtonl10n = {
+ "data-l10n-id": "newtab-topsites-add-shortcut-label",
+ };
+
let draggableProps = {};
if (isDraggable) {
draggableProps = {
@@ -380,14 +394,14 @@ export class TopSiteLink extends React.PureComponent {
: ""
}`}
>
- <span dir="auto">
+ <span dir="auto" {...(isAddButton && { ...addButtonl10n })}>
{link.isPinned && <div className="icon icon-pin-small" />}
{title || <br />}
- <span
- className="sponsored-label"
- data-l10n-id="newtab-topsite-sponsored"
- />
</span>
+ <span
+ className="sponsored-label"
+ data-l10n-id="newtab-topsite-sponsored"
+ />
</div>
</a>
{children}
@@ -635,19 +649,23 @@ export class TopSitePlaceholder extends React.PureComponent {
}
render() {
+ let addButtonProps = {};
+ if (this.props.isAddButton) {
+ addButtonProps = {
+ title: "newtab-topsites-add-shortcut-label",
+ onClick: this.onEditButtonClick,
+ };
+ }
+
return (
<TopSiteLink
{...this.props}
- className={`placeholder ${this.props.className || ""}`}
+ {...(this.props.isAddButton ? { ...addButtonProps } : {})}
+ className={`placeholder ${this.props.className || ""} ${
+ this.props.isAddButton ? "add-button" : ""
+ }`}
isDraggable={false}
- >
- <button
- aria-haspopup="dialog"
- className="context-menu-button edit-button icon"
- data-l10n-id="newtab-menu-topsites-placeholder-tooltip"
- onClick={this.onEditButtonClick}
- />
- </TopSiteLink>
+ />
);
}
}
@@ -758,6 +776,17 @@ export class _TopSiteList extends React.PureComponent {
// Make a copy of the sites to truncate or extend to desired length
let topSites = this.props.TopSites.rows.slice();
topSites.length = this.props.TopSitesRows * TOP_SITES_MAX_SITES_PER_ROW;
+ // if topSites do not fill an entire row add 'Add shortcut' button to array of topSites
+ // (there should only be one of these)
+ let firstPlaceholder = topSites.findIndex(Object.is.bind(null, undefined));
+ // make sure placeholder exists and there already isnt a add button
+ if (firstPlaceholder && !topSites.includes(site => site.isAddButton)) {
+ topSites[firstPlaceholder] = { isAddButton: true };
+ } else if (topSites.includes(site => site.isAddButton)) {
+ topSites.push(
+ topSites.splice(topSites.indexOf({ isAddButton: true }), 1)[0]
+ );
+ }
return topSites;
}
@@ -855,8 +884,20 @@ export class _TopSiteList extends React.PureComponent {
let topSiteLink;
// Use a placeholder if the link is empty or it's rendering a sponsored
// tile for the about:home startup cache.
- if (!link || (props.App.isForStartupCache && isSponsored(link))) {
- topSiteLink = <TopSitePlaceholder {...slotProps} {...commonProps} />;
+ if (
+ !link ||
+ (props.App.isForStartupCache && isSponsored(link)) ||
+ topSites[i]?.isAddButton
+ ) {
+ if (link) {
+ topSiteLink = (
+ <TopSitePlaceholder
+ {...slotProps}
+ {...commonProps}
+ isAddButton={topSites[i] && topSites[i].isAddButton}
+ />
+ );
+ }
} else {
topSiteLink = (
<TopSite
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
index c69156c514..ba7676fd10 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
@@ -206,7 +206,7 @@ export class _TopSites extends React.PureComponent {
}
}
-export const TopSites = connect((state, props) => ({
+export const TopSites = connect(state => ({
TopSites: state.TopSites,
Prefs: state.Prefs,
TopSitesRows: state.Prefs.values.topSitesRows,
diff --git a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
index ff2e2df826..4e4019513d 100644
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
+++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
@@ -246,7 +246,24 @@ $letter-fallback-color: $white;
&.placeholder {
.tile {
- box-shadow: $inner-box-shadow;
+ box-shadow: $shadow-card;
+ cursor: default;
+ }
+
+ &.add-button {
+ .tile {
+ background-color: var(--button-background-color);
+
+ .icon-wrapper {
+ background-image: url('chrome://global/skin/icons/plus-20.svg');
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
+ }
+ }
}
}
diff --git a/browser/components/newtab/content-src/lib/init-store.js b/browser/components/newtab/content-src/lib/init-store.js
index 20fcedc6c0..f0ab2db86a 100644
--- a/browser/components/newtab/content-src/lib/init-store.js
+++ b/browser/components/newtab/content-src/lib/init-store.js
@@ -44,7 +44,7 @@ function mergeStateReducer(mainReducer) {
/**
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
-const messageMiddleware = store => next => action => {
+const messageMiddleware = () => next => action => {
const skipLocal = action.meta && action.meta.skipLocal;
if (au.isSendToMain(action)) {
RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
diff --git a/browser/components/newtab/content-src/lib/link-menu-options.js b/browser/components/newtab/content-src/lib/link-menu-options.js
index caac738170..12e47259c1 100644
--- a/browser/components/newtab/content-src/lib/link-menu-options.js
+++ b/browser/components/newtab/content-src/lib/link-menu-options.js
@@ -25,7 +25,7 @@ const _OpenInPrivateWindow = site => ({
export const LinkMenuOptions = {
Separator: () => ({ type: "separator" }),
EmptyItem: () => ({ type: "empty" }),
- ShowPrivacyInfo: site => ({
+ ShowPrivacyInfo: () => ({
id: "newtab-menu-show-privacy-info",
icon: "info",
action: {
diff --git a/browser/components/newtab/content-src/lib/selectLayoutRender.js b/browser/components/newtab/content-src/lib/selectLayoutRender.js
index aa3eb927d2..8ef4dd428f 100644
--- a/browser/components/newtab/content-src/lib/selectLayoutRender.js
+++ b/browser/components/newtab/content-src/lib/selectLayoutRender.js
@@ -2,7 +2,7 @@
* 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/. */
-export const selectLayoutRender = ({ state = {}, prefs = {}, locale = "" }) => {
+export const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const { layout, feeds, spocs } = state;
let spocIndexPlacementMap = {};
diff --git a/browser/components/newtab/content-src/styles/_activity-stream.scss b/browser/components/newtab/content-src/styles/_activity-stream.scss
index 8bd3a7a397..88ed530b6a 100644
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -166,6 +166,8 @@ input {
@import '../components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal';
@import '../components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink';
@import '../components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget';
+@import '../components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight';
+@import '../components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight';
// AS Router
@import '../../../asrouter/content-src/components/Button/Button';
diff --git a/browser/components/newtab/content-src/styles/_icons.scss b/browser/components/newtab/content-src/styles/_icons.scss
index 4074f0a6a6..8be97ad9ae 100644
--- a/browser/components/newtab/content-src/styles/_icons.scss
+++ b/browser/components/newtab/content-src/styles/_icons.scss
@@ -70,6 +70,10 @@
background-image: url('chrome://global/skin/icons/info.svg');
}
+ &.icon-help {
+ background-image: url('chrome://global/skin/icons/help.svg');
+ }
+
&.icon-new-window {
@include flip-icon;
diff --git a/browser/components/newtab/css/activity-stream-linux.css b/browser/components/newtab/css/activity-stream-linux.css
index 336c7573ab..8773159737 100644
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -142,6 +142,9 @@ input {
.icon.icon-info {
background-image: url("chrome://global/skin/icons/info.svg");
}
+.icon.icon-help {
+ background-image: url("chrome://global/skin/icons/help.svg");
+}
.icon.icon-new-window {
background-image: url("chrome://activity-stream/content/data/content/assets/glyph-newWindow-16.svg");
}
@@ -722,7 +725,20 @@ main section {
inset-inline-end: -6px;
}
.top-site-outer.placeholder .tile {
- box-shadow: 0 0 0 1px var(--newtab-inner-box-shadow-color);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ cursor: default;
+}
+.top-site-outer.placeholder.add-button .tile {
+ background-color: var(--button-background-color);
+}
+.top-site-outer.placeholder.add-button .tile .icon-wrapper {
+ background-image: url("chrome://global/skin/icons/plus-20.svg");
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
}
.top-site-outer .title {
color: var(--newtab-text-primary-color);
@@ -1699,12 +1715,21 @@ main section {
appearance: none;
background-color: var(--newtab-element-secondary-color);
}
+.home-section .section .sponsored-checkbox:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .sponsored-checkbox:checked {
-moz-context-properties: fill;
fill: var(--newtab-primary-element-text-color);
background: url("chrome://global/skin/icons/check.svg") center no-repeat;
background-color: var(--newtab-primary-action-background);
}
+.home-section .section .sponsored-checkbox:checked:hover {
+ background-color: var(--newtab-primary-element-hover-color);
+}
+.home-section .section .sponsored-checkbox:checked:active {
+ background-color: var(--newtab-primary-element-active-color);
+}
@media (forced-colors: active) {
.home-section .section .sponsored-checkbox:checked {
fill: #000;
@@ -1731,6 +1756,9 @@ main section {
background-origin: content-box;
background-color: var(--newtab-background-color-secondary);
}
+.home-section .section .selector:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .selector:dir(rtl) {
background-position-x: left;
}
@@ -1747,6 +1775,22 @@ main section {
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
+.home-section .sponsored-content-info {
+ display: flex;
+ gap: var(--space-small);
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--newtab-element-secondary-color);
+ padding: var(--space-small) var(--space-large);
+}
+.home-section .sponsored-content-info .icon-help {
+ flex-shrink: 0;
+ color: var(--color-accent-primary);
+ height: 20px;
+}
+.home-section .sponsored-content-info a {
+ color: var(--newtab-primary-action-background);
+}
.home-section .divider {
border-top: 1px var(--newtab-border-color) solid;
margin: 0 -16px;
@@ -3518,6 +3562,12 @@ main section {
opacity: 1;
transform: scale(1);
}
+.ds-card.active header, .ds-card:focus-within header, .ds-card:hover header {
+ color: var(--newtab-primary-action-background);
+}
+.ds-card:active header {
+ color: var(--newtab-primary-element-active-color);
+}
.ds-card .img {
height: 0;
padding-top: 50%;
@@ -3527,26 +3577,17 @@ main section {
box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15);
}
.ds-card .ds-card-link {
+ position: absolute;
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
text-decoration: none;
}
-.ds-card .ds-card-link:hover header {
- color: var(--newtab-primary-action-background);
-}
.ds-card .ds-card-link:focus {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
transition: none;
}
-.ds-card .ds-card-link:focus header {
- color: var(--newtab-primary-action-background);
-}
-.ds-card .ds-card-link:active header {
- color: var(--newtab-primary-element-active-color);
-}
.ds-card .meta {
display: flex;
flex-direction: column;
@@ -3623,7 +3664,7 @@ main section {
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
- column-gap: var(--space-small);
+ gap: 0 var(--space-small);
margin-top: 0;
}
.ds-card.ds-card-cta-button.variant-a .story-cta-button, .ds-card.ds-card-cta-button.variant-b .story-cta-button {
@@ -3667,7 +3708,10 @@ main section {
color: var(--newtab-text-secondary-color);
inset-inline-start: 0;
margin-top: 12px;
+ display: flex;
+ gap: var(--space-large);
position: relative;
+ pointer-events: none;
}
.story-footer .story-sponsored-label span {
display: inline-block;
@@ -4203,6 +4247,112 @@ main section {
transition: box-shadow 150ms;
}
+.feature-highlight {
+ position: relative;
+ pointer-events: auto;
+ z-index: 1;
+}
+.feature-highlight .feature-highlight-modal {
+ position: absolute;
+ display: flex;
+ opacity: 0;
+ visibility: hidden;
+ cursor: default;
+ justify-content: space-between;
+ border-radius: var(--border-radius-small);
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ width: 298px;
+ transition: opacity 0.3s, visibility 0.3s;
+}
+.feature-highlight .feature-highlight-modal .icon-dismiss {
+ flex-shrink: 0;
+ cursor: pointer;
+ background-size: 12px;
+ height: 12px;
+ width: 12px;
+ margin: var(--space-medium);
+ color: var(--icon-color);
+ border: none;
+}
+.feature-highlight .feature-highlight-modal .message-icon {
+ margin-block: var(--space-large);
+ margin-inline: var(--space-large) var(--space-medium);
+}
+.feature-highlight .feature-highlight-modal.opened {
+ opacity: 1;
+ visibility: visible;
+}
+.feature-highlight .feature-highlight-modal::after {
+ content: "";
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 4px 4px 6px -2px rgba(0, 0, 0, 0.15);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start {
+ inset-block-end: 100%;
+ margin-bottom: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start::after {
+ inset-block-end: -12px;
+ transform: rotate(45deg);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end {
+ inset-block-start: 100%;
+ margin-top: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end::after {
+ inset-block-start: -12px;
+ transform: rotate(225deg);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start {
+ inset-inline-end: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start::after {
+ inset-inline-end: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end {
+ inset-inline-start: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end::after {
+ inset-inline-start: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal p {
+ font-size: var(--font-size-small);
+ font-weight: normal;
+ margin: var(--space-large) 0;
+}
+.feature-highlight .toggle-button {
+ border: none;
+ padding: 0;
+}
+
+.sponsored-content-highlight {
+ float: inline-end;
+}
+.sponsored-content-highlight .sponsored-message-icon {
+ background-image: url("chrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg");
+ background-size: 18px;
+ height: 18px;
+ width: 18px;
+}
+.sponsored-content-highlight .icon-help {
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ background-size: 20px;
+ color: var(--icon-color);
+ vertical-align: text-bottom;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-start::after {
+ margin-inline-end: 11px;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-end::after {
+ margin-inline-start: 9px;
+}
+
.ASRouterButton {
font-weight: 600;
font-size: 14px;
diff --git a/browser/components/newtab/css/activity-stream-mac.css b/browser/components/newtab/css/activity-stream-mac.css
index 01eff46415..87b942818a 100644
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -146,6 +146,9 @@ input {
.icon.icon-info {
background-image: url("chrome://global/skin/icons/info.svg");
}
+.icon.icon-help {
+ background-image: url("chrome://global/skin/icons/help.svg");
+}
.icon.icon-new-window {
background-image: url("chrome://activity-stream/content/data/content/assets/glyph-newWindow-16.svg");
}
@@ -726,7 +729,20 @@ main section {
inset-inline-end: -6px;
}
.top-site-outer.placeholder .tile {
- box-shadow: 0 0 0 1px var(--newtab-inner-box-shadow-color);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ cursor: default;
+}
+.top-site-outer.placeholder.add-button .tile {
+ background-color: var(--button-background-color);
+}
+.top-site-outer.placeholder.add-button .tile .icon-wrapper {
+ background-image: url("chrome://global/skin/icons/plus-20.svg");
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
}
.top-site-outer .title {
color: var(--newtab-text-primary-color);
@@ -1703,12 +1719,21 @@ main section {
appearance: none;
background-color: var(--newtab-element-secondary-color);
}
+.home-section .section .sponsored-checkbox:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .sponsored-checkbox:checked {
-moz-context-properties: fill;
fill: var(--newtab-primary-element-text-color);
background: url("chrome://global/skin/icons/check.svg") center no-repeat;
background-color: var(--newtab-primary-action-background);
}
+.home-section .section .sponsored-checkbox:checked:hover {
+ background-color: var(--newtab-primary-element-hover-color);
+}
+.home-section .section .sponsored-checkbox:checked:active {
+ background-color: var(--newtab-primary-element-active-color);
+}
@media (forced-colors: active) {
.home-section .section .sponsored-checkbox:checked {
fill: #000;
@@ -1735,6 +1760,9 @@ main section {
background-origin: content-box;
background-color: var(--newtab-background-color-secondary);
}
+.home-section .section .selector:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .selector:dir(rtl) {
background-position-x: left;
}
@@ -1751,6 +1779,22 @@ main section {
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
+.home-section .sponsored-content-info {
+ display: flex;
+ gap: var(--space-small);
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--newtab-element-secondary-color);
+ padding: var(--space-small) var(--space-large);
+}
+.home-section .sponsored-content-info .icon-help {
+ flex-shrink: 0;
+ color: var(--color-accent-primary);
+ height: 20px;
+}
+.home-section .sponsored-content-info a {
+ color: var(--newtab-primary-action-background);
+}
.home-section .divider {
border-top: 1px var(--newtab-border-color) solid;
margin: 0 -16px;
@@ -3522,6 +3566,12 @@ main section {
opacity: 1;
transform: scale(1);
}
+.ds-card.active header, .ds-card:focus-within header, .ds-card:hover header {
+ color: var(--newtab-primary-action-background);
+}
+.ds-card:active header {
+ color: var(--newtab-primary-element-active-color);
+}
.ds-card .img {
height: 0;
padding-top: 50%;
@@ -3531,26 +3581,17 @@ main section {
box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15);
}
.ds-card .ds-card-link {
+ position: absolute;
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
text-decoration: none;
}
-.ds-card .ds-card-link:hover header {
- color: var(--newtab-primary-action-background);
-}
.ds-card .ds-card-link:focus {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
transition: none;
}
-.ds-card .ds-card-link:focus header {
- color: var(--newtab-primary-action-background);
-}
-.ds-card .ds-card-link:active header {
- color: var(--newtab-primary-element-active-color);
-}
.ds-card .meta {
display: flex;
flex-direction: column;
@@ -3627,7 +3668,7 @@ main section {
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
- column-gap: var(--space-small);
+ gap: 0 var(--space-small);
margin-top: 0;
}
.ds-card.ds-card-cta-button.variant-a .story-cta-button, .ds-card.ds-card-cta-button.variant-b .story-cta-button {
@@ -3671,7 +3712,10 @@ main section {
color: var(--newtab-text-secondary-color);
inset-inline-start: 0;
margin-top: 12px;
+ display: flex;
+ gap: var(--space-large);
position: relative;
+ pointer-events: none;
}
.story-footer .story-sponsored-label span {
display: inline-block;
@@ -4207,6 +4251,112 @@ main section {
transition: box-shadow 150ms;
}
+.feature-highlight {
+ position: relative;
+ pointer-events: auto;
+ z-index: 1;
+}
+.feature-highlight .feature-highlight-modal {
+ position: absolute;
+ display: flex;
+ opacity: 0;
+ visibility: hidden;
+ cursor: default;
+ justify-content: space-between;
+ border-radius: var(--border-radius-small);
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ width: 298px;
+ transition: opacity 0.3s, visibility 0.3s;
+}
+.feature-highlight .feature-highlight-modal .icon-dismiss {
+ flex-shrink: 0;
+ cursor: pointer;
+ background-size: 12px;
+ height: 12px;
+ width: 12px;
+ margin: var(--space-medium);
+ color: var(--icon-color);
+ border: none;
+}
+.feature-highlight .feature-highlight-modal .message-icon {
+ margin-block: var(--space-large);
+ margin-inline: var(--space-large) var(--space-medium);
+}
+.feature-highlight .feature-highlight-modal.opened {
+ opacity: 1;
+ visibility: visible;
+}
+.feature-highlight .feature-highlight-modal::after {
+ content: "";
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 4px 4px 6px -2px rgba(0, 0, 0, 0.15);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start {
+ inset-block-end: 100%;
+ margin-bottom: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start::after {
+ inset-block-end: -12px;
+ transform: rotate(45deg);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end {
+ inset-block-start: 100%;
+ margin-top: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end::after {
+ inset-block-start: -12px;
+ transform: rotate(225deg);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start {
+ inset-inline-end: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start::after {
+ inset-inline-end: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end {
+ inset-inline-start: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end::after {
+ inset-inline-start: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal p {
+ font-size: var(--font-size-small);
+ font-weight: normal;
+ margin: var(--space-large) 0;
+}
+.feature-highlight .toggle-button {
+ border: none;
+ padding: 0;
+}
+
+.sponsored-content-highlight {
+ float: inline-end;
+}
+.sponsored-content-highlight .sponsored-message-icon {
+ background-image: url("chrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg");
+ background-size: 18px;
+ height: 18px;
+ width: 18px;
+}
+.sponsored-content-highlight .icon-help {
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ background-size: 20px;
+ color: var(--icon-color);
+ vertical-align: text-bottom;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-start::after {
+ margin-inline-end: 11px;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-end::after {
+ margin-inline-start: 9px;
+}
+
.ASRouterButton {
font-weight: 600;
font-size: 14px;
diff --git a/browser/components/newtab/css/activity-stream-windows.css b/browser/components/newtab/css/activity-stream-windows.css
index 69a2459268..25370fdf19 100644
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -142,6 +142,9 @@ input {
.icon.icon-info {
background-image: url("chrome://global/skin/icons/info.svg");
}
+.icon.icon-help {
+ background-image: url("chrome://global/skin/icons/help.svg");
+}
.icon.icon-new-window {
background-image: url("chrome://activity-stream/content/data/content/assets/glyph-newWindow-16.svg");
}
@@ -722,7 +725,20 @@ main section {
inset-inline-end: -6px;
}
.top-site-outer.placeholder .tile {
- box-shadow: 0 0 0 1px var(--newtab-inner-box-shadow-color);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ cursor: default;
+}
+.top-site-outer.placeholder.add-button .tile {
+ background-color: var(--button-background-color);
+}
+.top-site-outer.placeholder.add-button .tile .icon-wrapper {
+ background-image: url("chrome://global/skin/icons/plus-20.svg");
+ background-size: cover;
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+ fill: var(--icon-color);
+ -moz-context-properties: fill;
}
.top-site-outer .title {
color: var(--newtab-text-primary-color);
@@ -1699,12 +1715,21 @@ main section {
appearance: none;
background-color: var(--newtab-element-secondary-color);
}
+.home-section .section .sponsored-checkbox:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .sponsored-checkbox:checked {
-moz-context-properties: fill;
fill: var(--newtab-primary-element-text-color);
background: url("chrome://global/skin/icons/check.svg") center no-repeat;
background-color: var(--newtab-primary-action-background);
}
+.home-section .section .sponsored-checkbox:checked:hover {
+ background-color: var(--newtab-primary-element-hover-color);
+}
+.home-section .section .sponsored-checkbox:checked:active {
+ background-color: var(--newtab-primary-element-active-color);
+}
@media (forced-colors: active) {
.home-section .section .sponsored-checkbox:checked {
fill: #000;
@@ -1731,6 +1756,9 @@ main section {
background-origin: content-box;
background-color: var(--newtab-background-color-secondary);
}
+.home-section .section .selector:hover {
+ background-color: var(--newtab-element-secondary-hover-color);
+}
.home-section .section .selector:dir(rtl) {
background-position-x: left;
}
@@ -1747,6 +1775,22 @@ main section {
.home-section .section .more-info-top-wrapper .check-wrapper {
margin-top: 10px;
}
+.home-section .sponsored-content-info {
+ display: flex;
+ gap: var(--space-small);
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--newtab-element-secondary-color);
+ padding: var(--space-small) var(--space-large);
+}
+.home-section .sponsored-content-info .icon-help {
+ flex-shrink: 0;
+ color: var(--color-accent-primary);
+ height: 20px;
+}
+.home-section .sponsored-content-info a {
+ color: var(--newtab-primary-action-background);
+}
.home-section .divider {
border-top: 1px var(--newtab-border-color) solid;
margin: 0 -16px;
@@ -3518,6 +3562,12 @@ main section {
opacity: 1;
transform: scale(1);
}
+.ds-card.active header, .ds-card:focus-within header, .ds-card:hover header {
+ color: var(--newtab-primary-action-background);
+}
+.ds-card:active header {
+ color: var(--newtab-primary-element-active-color);
+}
.ds-card .img {
height: 0;
padding-top: 50%;
@@ -3527,26 +3577,17 @@ main section {
box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15);
}
.ds-card .ds-card-link {
+ position: absolute;
height: 100%;
- display: flex;
- flex-direction: column;
+ width: 100%;
text-decoration: none;
}
-.ds-card .ds-card-link:hover header {
- color: var(--newtab-primary-action-background);
-}
.ds-card .ds-card-link:focus {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
transition: none;
}
-.ds-card .ds-card-link:focus header {
- color: var(--newtab-primary-action-background);
-}
-.ds-card .ds-card-link:active header {
- color: var(--newtab-primary-element-active-color);
-}
.ds-card .meta {
display: flex;
flex-direction: column;
@@ -3623,7 +3664,7 @@ main section {
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
- column-gap: var(--space-small);
+ gap: 0 var(--space-small);
margin-top: 0;
}
.ds-card.ds-card-cta-button.variant-a .story-cta-button, .ds-card.ds-card-cta-button.variant-b .story-cta-button {
@@ -3667,7 +3708,10 @@ main section {
color: var(--newtab-text-secondary-color);
inset-inline-start: 0;
margin-top: 12px;
+ display: flex;
+ gap: var(--space-large);
position: relative;
+ pointer-events: none;
}
.story-footer .story-sponsored-label span {
display: inline-block;
@@ -4203,6 +4247,112 @@ main section {
transition: box-shadow 150ms;
}
+.feature-highlight {
+ position: relative;
+ pointer-events: auto;
+ z-index: 1;
+}
+.feature-highlight .feature-highlight-modal {
+ position: absolute;
+ display: flex;
+ opacity: 0;
+ visibility: hidden;
+ cursor: default;
+ justify-content: space-between;
+ border-radius: var(--border-radius-small);
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+ width: 298px;
+ transition: opacity 0.3s, visibility 0.3s;
+}
+.feature-highlight .feature-highlight-modal .icon-dismiss {
+ flex-shrink: 0;
+ cursor: pointer;
+ background-size: 12px;
+ height: 12px;
+ width: 12px;
+ margin: var(--space-medium);
+ color: var(--icon-color);
+ border: none;
+}
+.feature-highlight .feature-highlight-modal .message-icon {
+ margin-block: var(--space-large);
+ margin-inline: var(--space-large) var(--space-medium);
+}
+.feature-highlight .feature-highlight-modal.opened {
+ opacity: 1;
+ visibility: visible;
+}
+.feature-highlight .feature-highlight-modal::after {
+ content: "";
+ position: absolute;
+ height: 24px;
+ width: 24px;
+ background: var(--newtab-background-color-secondary);
+ box-shadow: 4px 4px 6px -2px rgba(0, 0, 0, 0.15);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start {
+ inset-block-end: 100%;
+ margin-bottom: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-start::after {
+ inset-block-end: -12px;
+ transform: rotate(45deg);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end {
+ inset-block-start: 100%;
+ margin-top: var(--space-xlarge);
+}
+.feature-highlight .feature-highlight-modal.inset-block-end::after {
+ inset-block-start: -12px;
+ transform: rotate(225deg);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start {
+ inset-inline-end: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-start::after {
+ inset-inline-end: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end {
+ inset-inline-start: calc(var(--space-xxlarge) * -1);
+}
+.feature-highlight .feature-highlight-modal.inset-inline-end::after {
+ inset-inline-start: calc(var(--space-xxlarge) - 12px);
+}
+.feature-highlight .feature-highlight-modal p {
+ font-size: var(--font-size-small);
+ font-weight: normal;
+ margin: var(--space-large) 0;
+}
+.feature-highlight .toggle-button {
+ border: none;
+ padding: 0;
+}
+
+.sponsored-content-highlight {
+ float: inline-end;
+}
+.sponsored-content-highlight .sponsored-message-icon {
+ background-image: url("chrome://activity-stream/content/data/content/assets/sponsor-message-icon.svg");
+ background-size: 18px;
+ height: 18px;
+ width: 18px;
+}
+.sponsored-content-highlight .icon-help {
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ background-size: 20px;
+ color: var(--icon-color);
+ vertical-align: text-bottom;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-start::after {
+ margin-inline-end: 11px;
+}
+.sponsored-content-highlight .feature-highlight-modal.inset-inline-end::after {
+ margin-inline-start: 9px;
+}
+
.ASRouterButton {
font-weight: 600;
font-size: 14px;
diff --git a/browser/components/newtab/data/content/activity-stream.bundle.js b/browser/components/newtab/data/content/activity-stream.bundle.js
index 14691a95f4..8904ba87d1 100644
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -682,7 +682,7 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
}
}));
}
- restorePrefDefaults(event) {
+ restorePrefDefaults() {
this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS
}));
@@ -1423,7 +1423,7 @@ const LinkMenuOptions = {
EmptyItem: () => ({
type: "empty"
}),
- ShowPrivacyInfo: site => ({
+ ShowPrivacyInfo: () => ({
id: "newtab-menu-show-privacy-info",
icon: "info",
action: {
@@ -1820,7 +1820,7 @@ class ContextMenuButton extends (external_React_default()).PureComponent {
this.onKeyDown = this.onKeyDown.bind(this);
this.onUpdate = this.onUpdate.bind(this);
}
- openContextMenu(isKeyBoard, event) {
+ openContextMenu(isKeyBoard) {
if (this.props.onUpdate) {
this.props.onUpdate(true);
}
@@ -2213,10 +2213,12 @@ class SafeAnchor extends (external_React_default()).PureComponent {
render() {
const {
url,
- className
+ className,
+ title
} = this.props;
return /*#__PURE__*/external_React_default().createElement("a", {
href: this.safeURI(url),
+ title: title,
className: className,
onClick: this.onClick
}, this.props.children);
@@ -2253,6 +2255,102 @@ const cardContextTypes = {
icon: "download"
}
};
+;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.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/. */
+
+
+
+function FeatureHighlight({
+ message,
+ icon,
+ toggle,
+ position = "top-left",
+ title,
+ ariaLabel,
+ feature = "FEATURE_HIGHLIGHT_DEFAULT",
+ dispatch = () => {},
+ windowObj = __webpack_require__.g
+}) {
+ const [opened, setOpened] = (0,external_React_namespaceObject.useState)(false);
+ const ref = (0,external_React_namespaceObject.useRef)(null);
+ (0,external_React_namespaceObject.useEffect)(() => {
+ const handleOutsideClick = e => {
+ if (!ref?.current?.contains(e.target)) {
+ setOpened(false);
+ }
+ };
+ windowObj.document.addEventListener("click", handleOutsideClick);
+ return () => {
+ windowObj.document.removeEventListener("click", handleOutsideClick);
+ };
+ }, [windowObj]);
+ const onToggleClick = (0,external_React_namespaceObject.useCallback)(() => {
+ if (!opened) {
+ dispatch(actionCreators.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "FEATURE_HIGHLIGHT",
+ value: {
+ feature
+ }
+ }));
+ }
+ setOpened(!opened);
+ }, [dispatch, feature, opened]);
+ const openedClassname = opened ? `opened` : `closed`;
+ return /*#__PURE__*/external_React_default().createElement("div", {
+ ref: ref,
+ className: "feature-highlight"
+ }, /*#__PURE__*/external_React_default().createElement("button", {
+ title: title,
+ "aria-haspopup": "true",
+ "aria-label": ariaLabel,
+ className: "toggle-button",
+ onClick: onToggleClick
+ }, toggle), /*#__PURE__*/external_React_default().createElement("div", {
+ className: `feature-highlight-modal ${position} ${openedClassname}`
+ }, /*#__PURE__*/external_React_default().createElement("div", {
+ className: "message-icon"
+ }, icon), /*#__PURE__*/external_React_default().createElement("p", null, message), /*#__PURE__*/external_React_default().createElement("button", {
+ title: "Dismiss",
+ "aria-label": "Close sponsored content more info popup",
+ className: "icon icon-dismiss",
+ onClick: () => setOpened(false)
+ })));
+}
+;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight.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/. */
+
+
+
+
+function SponsoredContentHighlight({
+ position,
+ dispatch
+}) {
+ return /*#__PURE__*/external_React_default().createElement("div", {
+ className: "sponsored-content-highlight"
+ }, /*#__PURE__*/external_React_default().createElement(FeatureHighlight, {
+ position: position,
+ ariaLabel: "Sponsored content supports our mission to build a better web.",
+ title: "Sponsored content more info",
+ feature: "SPONSORED_CONTENT_INFO",
+ dispatch: dispatch,
+ message: /*#__PURE__*/external_React_default().createElement("span", null, "Sponsored content supports our mission to build a better web.", " ", /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
+ dispatch: dispatch,
+ url: "https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ }, "Find out how")),
+ icon: /*#__PURE__*/external_React_default().createElement("div", {
+ className: "sponsored-message-icon"
+ }),
+ toggle: /*#__PURE__*/external_React_default().createElement("div", {
+ className: "icon icon-help"
+ })
+ }));
+}
;// CONCATENATED MODULE: external "ReactTransitionGroup"
const external_ReactTransitionGroup_namespaceObject = ReactTransitionGroup;
;// CONCATENATED MODULE: ./content-src/components/FluentOrText/FluentOrText.jsx
@@ -2305,6 +2403,7 @@ class FluentOrText extends (external_React_default()).PureComponent {
+
// Animation time is mirrored in DSContextFooter.scss
const ANIMATION_DURATION = 3000;
const DSMessageLabel = props => {
@@ -2385,7 +2484,9 @@ class DSContextFooter extends (external_React_default()).PureComponent {
sponsor,
sponsored_by_override,
cta_button_variant,
- source
+ source,
+ spocMessageVariant,
+ dispatch
} = this.props;
const sponsorLabel = SponsorLabel({
sponsored_by_override,
@@ -2414,7 +2515,10 @@ class DSContextFooter extends (external_React_default()).PureComponent {
if (sponsorLabel || dsMessageLabel) {
return /*#__PURE__*/external_React_default().createElement("div", {
className: "story-footer"
- }, sponsorLabel, dsMessageLabel);
+ }, sponsorLabel, sponsorLabel && spocMessageVariant === "variant-b" && /*#__PURE__*/external_React_default().createElement(SponsoredContentHighlight, {
+ dispatch: dispatch,
+ position: "inset-block-end inset-inline-start"
+ }), dsMessageLabel);
}
return null;
}
@@ -2517,8 +2621,9 @@ const DefaultMeta = ({
sponsor,
sponsored_by_override,
saveToPocketCard,
- isRecentSave,
- ctaButtonVariant
+ ctaButtonVariant,
+ dispatch,
+ spocMessageVariant
}) => /*#__PURE__*/external_React_default().createElement("div", {
className: "meta"
}, /*#__PURE__*/external_React_default().createElement("div", {
@@ -2531,7 +2636,6 @@ const DefaultMeta = ({
sponsor: sponsor,
sponsored_by_override: sponsored_by_override
}), /*#__PURE__*/external_React_default().createElement("header", {
- title: title,
className: "title clamp"
}, title), excerpt && /*#__PURE__*/external_React_default().createElement("p", {
className: "excerpt clamp"
@@ -2541,7 +2645,9 @@ const DefaultMeta = ({
sponsor: sponsor,
sponsored_by_override: sponsored_by_override,
cta_button_variant: ctaButtonVariant,
- source: source
+ source: source,
+ dispatch: dispatch,
+ spocMessageVariant: spocMessageVariant
}), newSponsoredLabel && /*#__PURE__*/external_React_default().createElement(DSMessageFooter, {
context_type: context_type,
context: null,
@@ -2592,7 +2698,7 @@ class _DSCard extends (external_React_default()).PureComponent {
height: 101
}];
}
- onLinkClick(event) {
+ onLinkClick() {
if (this.props.dispatch) {
this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
event: "CLICK",
@@ -2624,7 +2730,7 @@ class _DSCard extends (external_React_default()).PureComponent {
}));
}
}
- onSaveClick(event) {
+ onSaveClick() {
if (this.props.dispatch) {
this.props.dispatch(actionCreators.AlsoToMain({
type: actionTypes.SAVE_TO_POCKET,
@@ -2780,14 +2886,9 @@ class _DSCard extends (external_React_default()).PureComponent {
"data-l10n-id": "newtab-pocket-save"
})));
};
- return /*#__PURE__*/external_React_default().createElement("div", {
+ return /*#__PURE__*/external_React_default().createElement("article", {
className: `ds-card ${compactImagesClassName} ${imageGradientClassName} ${titleLinesName} ${descLinesClassName} ${ctaButtonClassName} ${ctaButtonVariantClassName}`,
ref: this.setContextMenuButtonHostRef
- }, /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
- className: "ds-card-link",
- dispatch: this.props.dispatch,
- onLinkClick: !this.props.placeholder ? this.onLinkClick : undefined,
- url: this.props.url
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "img-wrapper"
}, /*#__PURE__*/external_React_default().createElement(DSImage, {
@@ -2798,6 +2899,24 @@ class _DSCard extends (external_React_default()).PureComponent {
url: this.props.url,
title: this.props.title,
isRecentSave: isRecentSave
+ })), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
+ className: "ds-card-link",
+ dispatch: this.props.dispatch,
+ onLinkClick: !this.props.placeholder ? this.onLinkClick : undefined,
+ url: this.props.url,
+ title: this.props.title
+ }, /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
+ flightId: this.props.flightId,
+ rows: [{
+ id: this.props.id,
+ pos: this.props.pos,
+ ...(this.props.shim && this.props.shim.impression ? {
+ shim: this.props.shim.impression
+ } : {}),
+ recommendation_id: this.props.recommendation_id
+ }],
+ dispatch: this.props.dispatch,
+ source: this.props.type
})), ctaButtonVariant === "variant-b" && /*#__PURE__*/external_React_default().createElement("div", {
className: "cta-header"
}, "Shop Now"), /*#__PURE__*/external_React_default().createElement(DefaultMeta, {
@@ -2811,20 +2930,10 @@ class _DSCard extends (external_React_default()).PureComponent {
sponsor: this.props.sponsor,
sponsored_by_override: this.props.sponsored_by_override,
saveToPocketCard: saveToPocketCard,
- ctaButtonVariant: ctaButtonVariant
- }), /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
- flightId: this.props.flightId,
- rows: [{
- id: this.props.id,
- pos: this.props.pos,
- ...(this.props.shim && this.props.shim.impression ? {
- shim: this.props.shim.impression
- } : {}),
- recommendation_id: this.props.recommendation_id
- }],
+ ctaButtonVariant: ctaButtonVariant,
dispatch: this.props.dispatch,
- source: this.props.type
- })), saveToPocketCard && /*#__PURE__*/external_React_default().createElement("div", {
+ spocMessageVariant: this.props.spocMessageVariant
+ }), saveToPocketCard && /*#__PURE__*/external_React_default().createElement("div", {
className: "card-stp-button-hover-background"
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "card-stp-button-position-wrapper"
@@ -2874,7 +2983,7 @@ const DSCard = (0,external_ReactRedux_namespaceObject.connect)(state => ({
App: state.App,
DiscoveryStream: state.DiscoveryStream
}))(_DSCard);
-const PlaceholderDSCard = props => /*#__PURE__*/external_React_default().createElement(DSCard, {
+const PlaceholderDSCard = () => /*#__PURE__*/external_React_default().createElement(DSCard, {
placeholder: true
});
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
@@ -3163,7 +3272,6 @@ function DSSubHeader({
}, children));
}
function OnboardingExperience({
- children,
dispatch,
windowObj = __webpack_require__.g
}) {
@@ -3409,6 +3517,7 @@ class _CardGrid extends (external_React_default()).PureComponent {
onboardingExperience,
ctaButtonSponsors,
ctaButtonVariant,
+ spocMessageVariant,
widgets,
recentSavesEnabled,
hideDescriptions,
@@ -3454,6 +3563,7 @@ class _CardGrid extends (external_React_default()).PureComponent {
saveToPocketCard: saveToPocketCard,
ctaButtonSponsors: ctaButtonSponsors,
ctaButtonVariant: ctaButtonVariant,
+ spocMessageVariant: spocMessageVariant,
recommendation_id: rec.recommendation_id
}));
}
@@ -3776,7 +3886,7 @@ class ErrorBoundary extends (external_React_default()).PureComponent {
hasError: false
};
}
- componentDidCatch(error, info) {
+ componentDidCatch() {
this.setState({
hasError: true
});
@@ -3803,6 +3913,7 @@ ErrorBoundary.defaultProps = {
+
/**
* A section that can collapse. As of bug 1710937, it can no longer collapse.
* See bug 1727365 for follow-up work to simplify this component.
@@ -3853,7 +3964,8 @@ class _CollapsibleSection extends (external_React_default()).PureComponent {
collapsed,
learnMore,
title,
- subTitle
+ subTitle,
+ mayHaveSponsoredStories
} = this.props;
const active = menuButtonHover || showContextMenu;
let bodyStyle;
@@ -3899,7 +4011,10 @@ class _CollapsibleSection extends (external_React_default()).PureComponent {
className: "section-sub-title"
}, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
message: subTitle
- })))), /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
+ })), mayHaveSponsoredStories && this.props.spocMessageVariant === "variant-a" && /*#__PURE__*/external_React_default().createElement(SponsoredContentHighlight, {
+ position: "inset-block-start inset-inline-start",
+ dispatch: this.props.dispatch
+ }))), /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
className: "section-body-fallback"
}, /*#__PURE__*/external_React_default().createElement("div", {
ref: this.onBodyMount,
@@ -4013,13 +4128,13 @@ class DSPrivacyModal extends (external_React_default()).PureComponent {
this.onLearnLinkClick = this.onLearnLinkClick.bind(this);
this.onManageLinkClick = this.onManageLinkClick.bind(this);
}
- onLearnLinkClick(event) {
+ onLearnLinkClick() {
this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
event: "CLICK_PRIVACY_INFO",
source: "DS_PRIVACY_MODAL"
}));
}
- onManageLinkClick(event) {
+ onManageLinkClick() {
this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.SETTINGS_OPEN
}));
@@ -4044,7 +4159,7 @@ class DSPrivacyModal extends (external_React_default()).PureComponent {
className: "modal-link modal-link-privacy",
"data-l10n-id": "newtab-privacy-modal-link",
onClick: this.onLearnLinkClick,
- href: "https://help.getpocket.com/article/1142-firefox-new-tab-recommendations-faq"
+ href: "https://support.mozilla.org/kb/pocket-recommendations-firefox-new-tab"
}), /*#__PURE__*/external_React_default().createElement("button", {
className: "modal-link modal-link-manage",
"data-l10n-id": "newtab-privacy-modal-button-manage",
@@ -6477,7 +6592,7 @@ class TopSiteLink extends (external_React_default()).PureComponent {
// If we have tabbed to a search shortcut top site, and we click 'enter',
// we should execute the onClick function. This needs to be added because
// search top sites are anchor tags without an href. See bug 1483135
- if (this.props.link.searchTopSite && event.key === "Enter") {
+ if (event.key === "Enter" && (this.props.link.searchTopSite || this.props.isAddButton)) {
this.props.onClick(event);
}
}
@@ -6572,7 +6687,8 @@ class TopSiteLink extends (external_React_default()).PureComponent {
isDraggable,
link,
onClick,
- title
+ title,
+ isAddButton
} = this.props;
const topSiteOuterClassName = `top-site-outer${className ? ` ${className}` : ""}${link.isDragged ? " dragged" : ""}${link.searchTopSite ? " search-shortcut" : ""}`;
const [letterFallback] = title;
@@ -6583,6 +6699,9 @@ class TopSiteLink extends (external_React_default()).PureComponent {
imageClassName,
selectedColor
} = this.calculateStyle();
+ let addButtonl10n = {
+ "data-l10n-id": "newtab-topsites-add-shortcut-label"
+ };
let draggableProps = {};
if (isDraggable) {
draggableProps = {
@@ -6674,14 +6793,16 @@ class TopSiteLink extends (external_React_default()).PureComponent {
className: "top-site-icon search-topsite"
})), /*#__PURE__*/external_React_default().createElement("div", {
className: `title${link.isPinned ? " has-icon pinned" : ""}${link.type === SPOC_TYPE || link.show_sponsored_label ? " sponsored" : ""}`
- }, /*#__PURE__*/external_React_default().createElement("span", {
+ }, /*#__PURE__*/external_React_default().createElement("span", TopSite_extends({
dir: "auto"
- }, link.isPinned && /*#__PURE__*/external_React_default().createElement("div", {
+ }, isAddButton && {
+ ...addButtonl10n
+ }), link.isPinned && /*#__PURE__*/external_React_default().createElement("div", {
className: "icon icon-pin-small"
- }), title || /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("span", {
+ }), title || /*#__PURE__*/external_React_default().createElement("br", null)), /*#__PURE__*/external_React_default().createElement("span", {
className: "sponsored-label",
"data-l10n-id": "newtab-topsite-sponsored"
- })))), children, impressionStats));
+ }))), children, impressionStats));
}
}
TopSiteLink.defaultProps = {
@@ -6903,14 +7024,18 @@ class TopSitePlaceholder extends (external_React_default()).PureComponent {
});
}
render() {
- return /*#__PURE__*/external_React_default().createElement(TopSiteLink, TopSite_extends({}, this.props, {
- className: `placeholder ${this.props.className || ""}`,
+ let addButtonProps = {};
+ if (this.props.isAddButton) {
+ addButtonProps = {
+ title: "newtab-topsites-add-shortcut-label",
+ onClick: this.onEditButtonClick
+ };
+ }
+ return /*#__PURE__*/external_React_default().createElement(TopSiteLink, TopSite_extends({}, this.props, this.props.isAddButton ? {
+ ...addButtonProps
+ } : {}, {
+ className: `placeholder ${this.props.className || ""} ${this.props.isAddButton ? "add-button" : ""}`,
isDraggable: false
- }), /*#__PURE__*/external_React_default().createElement("button", {
- "aria-haspopup": "dialog",
- className: "context-menu-button edit-button icon",
- "data-l10n-id": "newtab-menu-topsites-placeholder-tooltip",
- onClick: this.onEditButtonClick
}));
}
}
@@ -7004,6 +7129,19 @@ class _TopSiteList extends (external_React_default()).PureComponent {
// Make a copy of the sites to truncate or extend to desired length
let topSites = this.props.TopSites.rows.slice();
topSites.length = this.props.TopSitesRows * TOP_SITES_MAX_SITES_PER_ROW;
+ // if topSites do not fill an entire row add 'Add shortcut' button to array of topSites
+ // (there should only be one of these)
+ let firstPlaceholder = topSites.findIndex(Object.is.bind(null, undefined));
+ // make sure placeholder exists and there already isnt a add button
+ if (firstPlaceholder && !topSites.includes(site => site.isAddButton)) {
+ topSites[firstPlaceholder] = {
+ isAddButton: true
+ };
+ } else if (topSites.includes(site => site.isAddButton)) {
+ topSites.push(topSites.splice(topSites.indexOf({
+ isAddButton: true
+ }), 1)[0]);
+ }
return topSites;
}
@@ -7090,8 +7228,12 @@ class _TopSiteList extends (external_React_default()).PureComponent {
let topSiteLink;
// Use a placeholder if the link is empty or it's rendering a sponsored
// tile for the about:home startup cache.
- if (!link || props.App.isForStartupCache && isSponsored(link)) {
- topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSitePlaceholder, TopSite_extends({}, slotProps, commonProps));
+ if (!link || props.App.isForStartupCache && isSponsored(link) || topSites[i]?.isAddButton) {
+ if (link) {
+ topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSitePlaceholder, TopSite_extends({}, slotProps, commonProps, {
+ isAddButton: topSites[i] && topSites[i].isAddButton
+ }));
+ }
} else {
topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSite, TopSite_extends({
link: link,
@@ -7551,7 +7693,7 @@ class _TopSites extends (external_React_default()).PureComponent {
}))))));
}
}
-const TopSites_TopSites = (0,external_ReactRedux_namespaceObject.connect)((state, props) => ({
+const TopSites_TopSites = (0,external_ReactRedux_namespaceObject.connect)(state => ({
TopSites: state.TopSites,
Prefs: state.Prefs,
TopSitesRows: state.Prefs.values.topSitesRows
@@ -8053,8 +8195,7 @@ class SectionTitle extends (external_React_default()).PureComponent {
const selectLayoutRender = ({
state = {},
- prefs = {},
- locale = ""
+ prefs = {}
}) => {
const {
layout,
@@ -8337,7 +8478,7 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
});
});
}
- renderComponent(component, embedWidth) {
+ renderComponent(component) {
switch (component.type) {
case "Highlights":
return /*#__PURE__*/external_React_default().createElement(Highlights, null);
@@ -8417,6 +8558,7 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
onboardingExperience: component.properties.onboardingExperience,
ctaButtonSponsors: component.properties.ctaButtonSponsors,
ctaButtonVariant: component.properties.ctaButtonVariant,
+ spocMessageVariant: component.properties.spocMessageVariant,
editorsPicksHeader: component.properties.editorsPicksHeader,
recentSavesEnabled: this.props.DiscoveryStream.recentSavesEnabled,
hideDescriptions: this.props.DiscoveryStream.hideDescriptions
@@ -8443,7 +8585,8 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
}
render() {
const {
- locale
+ locale,
+ mayHaveSponsoredStories
} = this.props;
// Select layout render data by adding spocs and position to recommendations
const {
@@ -8538,6 +8681,8 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
showPrefName: topStories.pref.feed,
title: sectionTitle,
subTitle: subTitle,
+ mayHaveSponsoredStories: mayHaveSponsoredStories,
+ spocMessageVariant: message?.properties?.spocMessageVariant,
eventSource: "CARDGRID"
}, this.renderLayout(layoutRender)), this.renderLayout([{
width: 12,
@@ -8594,6 +8739,7 @@ class BackgroundsSection extends (external_React_default()).PureComponent {
+
class ContentSection extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@@ -8671,7 +8817,8 @@ class ContentSection extends (external_React_default()).PureComponent {
pocketRegion,
mayHaveSponsoredStories,
mayHaveRecentSaves,
- openPreferences
+ openPreferences,
+ spocMessageVariant
} = this.props;
const {
topSitesEnabled,
@@ -8805,7 +8952,14 @@ class ContentSection extends (external_React_default()).PureComponent {
"data-eventSource": "HIGHLIGHTS",
"data-l10n-id": "newtab-custom-recent-toggle",
"data-l10n-attrs": "label, description"
- }))), /*#__PURE__*/external_React_default().createElement("span", {
+ }))), pocketRegion && mayHaveSponsoredStories && spocMessageVariant === "variant-c" && /*#__PURE__*/external_React_default().createElement("div", {
+ className: "sponsored-content-info"
+ }, /*#__PURE__*/external_React_default().createElement("div", {
+ className: "icon icon-help"
+ }), /*#__PURE__*/external_React_default().createElement("div", null, "Sponsored content supports our mission to build a better web.", " ", /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
+ dispatch: this.props.dispatch,
+ url: "https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
+ }, "Find out how"))), /*#__PURE__*/external_React_default().createElement("span", {
className: "divider",
role: "separator"
}), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
@@ -8877,6 +9031,7 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled,
+ spocMessageVariant: this.props.spocMessageVariant,
dispatch: this.props.dispatch
}))));
}
@@ -9253,8 +9408,15 @@ class BaseContent extends (external_React_default()).PureComponent {
customizeMenuVisible
} = App;
const prefs = props.Prefs.values;
+ const {
+ pocketConfig
+ } = prefs;
const isDiscoveryStream = props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
let filteredSections = props.Sections.filter(section => section.id !== "topstories");
+ let spocMessageVariant = "";
+ if (props.App.locale?.startsWith("en-") && pocketConfig?.spocMessageVariant === "variant-c") {
+ spocMessageVariant = pocketConfig.spocMessageVariant;
+ }
const pocketEnabled = prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
const noSectionsEnabled = !prefs["feeds.topsites"] && !pocketEnabled && filteredSections.filter(section => section.enabled).length === 0;
const searchHandoffEnabled = prefs["improvesearch.handoffToAwesomebar"];
@@ -9282,6 +9444,7 @@ class BaseContent extends (external_React_default()).PureComponent {
pocketRegion: pocketRegion,
mayHaveSponsoredTopSites: mayHaveSponsoredTopSites,
mayHaveSponsoredStories: mayHaveSponsoredStories,
+ spocMessageVariant: spocMessageVariant,
showing: customizeMenuVisible
}), /*#__PURE__*/external_React_default().createElement("div", {
className: outerClassName,
@@ -9296,7 +9459,8 @@ class BaseContent extends (external_React_default()).PureComponent {
}, isDiscoveryStream ? /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
className: "borderless-error"
}, /*#__PURE__*/external_React_default().createElement(DiscoveryStreamBase, {
- locale: props.App.locale
+ locale: props.App.locale,
+ mayHaveSponsoredStories: mayHaveSponsoredStories
})) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null))));
}
}
@@ -9420,7 +9584,7 @@ function mergeStateReducer(mainReducer) {
/**
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
*/
-const messageMiddleware = store => next => action => {
+const messageMiddleware = () => next => action => {
const skipLocal = action.meta && action.meta.skipLocal;
if (actionUtils.isSendToMain(action)) {
RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
diff --git a/browser/components/newtab/data/content/assets/sponsor-message-icon.svg b/browser/components/newtab/data/content/assets/sponsor-message-icon.svg
new file mode 100644
index 0000000000..09e171fcd9
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/sponsor-message-icon.svg
@@ -0,0 +1,6 @@
+<!-- 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 https://mozilla.org/MPL/2.0/. -->
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path id="Vector" d="M17.7598 0.927468L17.7505 0.920946C17.5 0.742695 17.1526 0.537416 16.7513 0.36344C15.9486 0.0162127 14.9513 -0.104324 14.0229 0.0989256C13.094 0.299531 12.2334 0.821423 11.6408 1.4615C11.3448 1.78286 11.1105 2.10874 10.9539 2.37127C10.9148 2.43505 10.8774 2.49984 10.8417 2.56557C10.7874 2.58861 10.7323 2.61339 10.6777 2.64187C10.461 2.74947 10.2348 2.88486 10.0191 3.05558C9.80233 3.22492 9.59191 3.42393 9.39579 3.64631C9.19845 3.8681 9.02216 4.11675 8.85927 4.37703C8.69518 4.63719 8.55497 4.91406 8.42867 5.19531L8.3365 5.40722L8.25361 5.62142L8.21238 5.72837L8.17554 5.83604L8.10242 6.0507C8.07953 6.12229 8.05913 6.19407 8.03757 6.26529C8.01717 6.3367 7.99326 6.40728 7.97616 6.4784L7.96345 6.52753C7.85559 6.36663 7.73933 6.21689 7.61557 6.08175C7.47438 5.92767 7.32573 5.79054 7.17472 5.66931C6.87206 5.42701 6.55805 5.25419 6.26847 5.13166C6.11579 5.06849 5.95919 5.01524 5.79965 4.97225L5.80171 4.96497L5.80175 4.96493C5.80175 4.96493 5.66129 4.86359 5.43844 4.74994C5.21392 4.63187 4.90727 4.50245 4.5599 4.40456C3.86526 4.20939 3.03654 4.22109 2.29995 4.48988C1.5626 4.75661 0.916726 5.27843 0.50298 5.86724C0.296469 6.16273 0.140971 6.45507 0.0418817 6.68701L0.0388746 6.69382C-0.0591635 6.91747 0.0342372 7.17977 0.251472 7.29129L0.259877 7.2956C0.484394 7.41364 0.791044 7.54309 1.13842 7.64098C1.83305 7.83615 2.66178 7.82445 3.39837 7.55566C4.13572 7.28897 4.78159 6.76715 5.19533 6.17831C5.28317 6.05241 5.36509 5.9225 5.44083 5.78898C5.55814 5.82094 5.67307 5.86104 5.7848 5.90901C5.97815 5.99462 6.18796 6.11342 6.38926 6.2795C6.48958 6.36264 7.27754 7.18586 7.27754 8.1353C7.30831 8.29686 7.32318 8.46104 7.32192 8.62549L7.31761 9.25589L7.30844 10.5451L7.25928 17.4565V17.4666C7.26207 17.8571 8.67623 17.8471 8.67344 17.4565L8.62428 10.5451L8.62066 10.0378C8.6275 10.0019 8.63098 9.96477 8.63055 9.9268L8.62616 9.52747L8.62537 9.42773L8.62703 9.32288L8.63062 9.08666C8.63073 9.00272 8.63558 8.91153 8.63971 8.81573C8.64446 8.72001 8.64696 8.6189 8.6541 8.51416L8.67797 8.18747C8.68681 8.07465 8.70105 7.95904 8.71279 7.83952C8.73964 7.60124 8.77456 7.35118 8.82075 7.09518L8.83738 6.99851L8.85746 6.90218L8.89792 6.70726C8.91028 6.64136 8.92894 6.57741 8.94426 6.51177C8.96067 6.44655 8.97589 6.38047 8.99339 6.315L9.04976 6.11932L9.07806 6.02092L9.1103 5.92404L9.17483 5.72949L9.24736 5.53838C9.34681 5.28502 9.45771 5.03656 9.5881 4.80653C9.71741 4.57611 9.85711 4.35677 10.0131 4.16396C10.1681 3.97031 10.3335 3.79825 10.5031 3.65178C10.6719 3.50385 10.8481 3.38752 11.0166 3.2931C11.0791 3.25607 11.1424 3.22644 11.2036 3.19782C11.445 3.36107 11.759 3.54048 12.1159 3.69522C12.9186 4.04244 13.9159 4.16298 14.8443 3.95973C15.7733 3.75909 16.6338 3.2372 17.2264 2.59716C17.5224 2.27576 17.7567 1.94988 17.9134 1.68735L17.9181 1.67964C18.0723 1.42686 18.0028 1.09677 17.7598 0.927468Z" fill="#2BA52E"/>
+</svg>
diff --git a/browser/components/newtab/docs/index.rst b/browser/components/newtab/docs/index.rst
index 48cf01c331..5a11688267 100644
--- a/browser/components/newtab/docs/index.rst
+++ b/browser/components/newtab/docs/index.rst
@@ -8,7 +8,7 @@ Some of these source files (such as ``.js``, ``.jsx``, and ``.scss``) require an
We are working on migrating this to work with ``mach``, but in the meantime, please
follow the following steps if you need to make changes in this directory:
-For ``.jsm`` or ``.sys.mjs`` files (system modules)
+For ``.sys.mjs`` files (system modules)
---------------------------------------------------
No build step is necessary. Use ``mach`` and run mochitests according to your regular Firefox workflow.
diff --git a/browser/components/newtab/docs/v2-system-addon/preferences.md b/browser/components/newtab/docs/v2-system-addon/preferences.md
index ec6ba82491..d709e830e4 100644
--- a/browser/components/newtab/docs/v2-system-addon/preferences.md
+++ b/browser/components/newtab/docs/v2-system-addon/preferences.md
@@ -43,7 +43,7 @@ pref in the following files:
You can see an example in [this patch](https://github.com/mozilla/activity-stream/pull/2977).
-## Reading, setting, and observing preferences from `.jsm`s
+## Reading, setting, and observing preferences from `.sys.mjs`s
To read/set/observe Activity Stream preferences, construct a `Prefs` instance found in `lib/ActivityStreamPrefs.sys.mjs`.
diff --git a/browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md b/browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md
index c3dd369a2d..bc0b2e8b53 100644
--- a/browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md
+++ b/browser/components/newtab/docs/v2-system-addon/unit_testing_guide.md
@@ -4,7 +4,7 @@
Our unit tests in Activity Stream are written with mocha, chai, and sinon, and run
with karma. They include unit tests for both content code (React components, etc.)
-and `.jsm`s.
+and `.sys.mjs`s.
You can find unit tests in `tests/unit`.
@@ -32,7 +32,7 @@ If you are creating a new test, add it to a subdirectory of the `tests/unit`
that corresponds to the file you are testing. Tests should end with `.test.js` or
`.test.jsx` if the test includes any jsx.
-For example, if the file you are testing is `lib/Foo.jsm`, the test
+For example, if the file you are testing is `lib/Foo.sys.mjs`, the test
file should be `test/unit/lib/Foo.test.js`
## Mocha tests
@@ -96,9 +96,9 @@ assert.isUserEventAction({type: "FOO"});
assert.isUserEventAction(ac.UserEvent({event: "BLOOP"}));
```
-## Overriding globals in `.jsm`s
+## Overriding globals in `.sys.mjs`s
-Most `.jsm`s you will be testing use `Cu.import` or `XPCOMUtils` to inject globals.
+Most `.sys.mjs`s you will be testing use `Cu.import` or `XPCOMUtils` to inject globals.
In order to add mocks/stubs/fakes for these globals, you should use the `GlobalOverrider`
utility in `test/unit/utils`:
diff --git a/browser/components/newtab/karma.mc.config.js b/browser/components/newtab/karma.mc.config.js
index 78ef457865..fa3ac14587 100644
--- a/browser/components/newtab/karma.mc.config.js
+++ b/browser/components/newtab/karma.mc.config.js
@@ -209,18 +209,10 @@ module.exports = function (config) {
webpack: {
mode: "none",
devtool: "inline-source-map",
- // This loader allows us to override required files in tests
- resolveLoader: {
- alias: { inject: path.join(__dirname, "loaders/inject-loader") },
- },
// This resolve config allows us to import with paths relative to the root directory, e.g. "lib/ActivityStream.sys.mjs"
resolve: {
extensions: [".js", ".jsx"],
modules: [PATHS.moduleResolveDirectory, "node_modules"],
- fallback: {
- stream: require.resolve("stream-browserify"),
- buffer: require.resolve("buffer"),
- },
alias: {
asrouter: path.join(__dirname, "../asrouter"),
},
diff --git a/browser/components/newtab/lib/ActivityStream.sys.mjs b/browser/components/newtab/lib/ActivityStream.sys.mjs
index f2287fe45e..f46e8aadf0 100644
--- a/browser/components/newtab/lib/ActivityStream.sys.mjs
+++ b/browser/components/newtab/lib/ActivityStream.sys.mjs
@@ -306,7 +306,7 @@ export const PREFS_CONFIG = new Map([
"discoverystream.config",
{
title: "Configuration for the new pocket new tab",
- getValue: ({ geo, locale }) => {
+ getValue: () => {
return JSON.stringify({
api_key_pref: "extensions.pocket.oAuthConsumerKey",
collapsible: true,
@@ -689,7 +689,7 @@ export class ActivityStream {
}
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
switch (topic) {
case "intl:app-locales-changed":
case lazy.Region.REGION_TOPIC:
diff --git a/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs b/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
index de9d2cb800..5392a421ca 100644
--- a/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
+++ b/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
@@ -67,7 +67,7 @@ export class ActivityStreamMessageChannel {
* @param {object} store A redux store
* @return {function} Redux middleware
*/
- middleware(store) {
+ middleware() {
return next => action => {
const skipMain = action.meta && action.meta.skipMain;
if (au.isSendToOneContent(action)) {
diff --git a/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs b/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
index 257036b9da..ee08462503 100644
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
@@ -331,7 +331,7 @@ export class DiscoveryStreamFeed {
},
});
},
- error(error) {},
+ error() {},
});
}
}
@@ -606,6 +606,13 @@ export class DiscoveryStreamFeed {
) {
ctaButtonVariant = pocketConfig.ctaButtonVariant;
}
+ let spocMessageVariant = "";
+ if (
+ pocketConfig.spocMessageVariant === "variant-a" ||
+ pocketConfig.spocMessageVariant === "variant-b"
+ ) {
+ spocMessageVariant = pocketConfig.spocMessageVariant;
+ }
const prepConfArr = arr => {
return arr
@@ -681,6 +688,9 @@ export class DiscoveryStreamFeed {
// For now button variants are for experimentation and English only.
ctaButtonSponsors: this.locale.startsWith("en-") ? ctaButtonSponsors : [],
ctaButtonVariant: this.locale.startsWith("en-") ? ctaButtonVariant : "",
+ spocMessageVariant: this.locale.startsWith("en-")
+ ? spocMessageVariant
+ : "",
});
sendUpdate({
@@ -2040,6 +2050,7 @@ export class DiscoveryStreamFeed {
`onboardingExperience` Show new users some UI explaining Pocket above the Pocket section.
`ctaButtonSponsors` An array of sponsors we want to show a cta button on the card for.
`ctaButtonVariant` Sets the variant for the cta sponsor button.
+ `spocMessageVariant` Sets the variant for the sponsor message dialog.
*/
getHardcodedLayout = ({
spocsUrl = SPOCS_URL,
@@ -2063,6 +2074,7 @@ getHardcodedLayout = ({
onboardingExperience = false,
ctaButtonSponsors = [],
ctaButtonVariant = "",
+ spocMessageVariant = "",
}) => ({
lastUpdate: Date.now(),
spocs: {
@@ -2144,7 +2156,9 @@ getHardcodedLayout = ({
link_url: "https://getpocket.com/firefox/new_tab_learn_more",
icon: "chrome://global/skin/icons/pocket.svg",
},
- properties: {},
+ properties: {
+ spocMessageVariant,
+ },
styles: {
".ds-message": "margin-bottom: -20px",
},
@@ -2162,6 +2176,7 @@ getHardcodedLayout = ({
onboardingExperience,
ctaButtonSponsors,
ctaButtonVariant,
+ spocMessageVariant,
},
widgets: {
positions: widgetPositions.map(position => {
diff --git a/browser/components/newtab/lib/DownloadsManager.sys.mjs b/browser/components/newtab/lib/DownloadsManager.sys.mjs
index f095645d41..a9a57222ee 100644
--- a/browser/components/newtab/lib/DownloadsManager.sys.mjs
+++ b/browser/components/newtab/lib/DownloadsManager.sys.mjs
@@ -16,7 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
const DOWNLOAD_CHANGED_DELAY_TIME = 1000; // time in ms to delay timer for downloads changed events
export class DownloadsManager {
- constructor(store) {
+ constructor() {
this._downloadData = null;
this._store = null;
this._downloadItems = new Map();
diff --git a/browser/components/newtab/lib/PersonalityProvider/RecipeExecutor.mjs b/browser/components/newtab/lib/PersonalityProvider/RecipeExecutor.mjs
index 4f420c0812..ed15f8a59a 100644
--- a/browser/components/newtab/lib/PersonalityProvider/RecipeExecutor.mjs
+++ b/browser/components/newtab/lib/PersonalityProvider/RecipeExecutor.mjs
@@ -161,7 +161,7 @@ export class RecipeExecutor {
* Config:
* Not configurable
*/
- conditionallyNmfTag(item, config) {
+ conditionallyNmfTag(item) {
let nestedNmfTags = {};
let parentTags = {};
let parentWeights = {};
@@ -1052,7 +1052,7 @@ export class RecipeExecutor {
} else if (config.operation === "overwrite") {
op = (a, b) => b;
} else if (config.operation === "count") {
- op = (a, b) => a + 1;
+ op = a => a + 1;
} else {
return null;
}
diff --git a/browser/components/newtab/lib/PrefsFeed.sys.mjs b/browser/components/newtab/lib/PrefsFeed.sys.mjs
index 1c6f9b0d45..bb2502ac55 100644
--- a/browser/components/newtab/lib/PrefsFeed.sys.mjs
+++ b/browser/components/newtab/lib/PrefsFeed.sys.mjs
@@ -79,7 +79,7 @@ export class PrefsFeed {
/**
* Handler for when experiment data updates.
*/
- onExperimentUpdated(event, reason) {
+ onExperimentUpdated() {
const value = lazy.NimbusFeatures.newtab.getAllVariables() || {};
this.store.dispatch(
ac.BroadcastToContent({
@@ -238,7 +238,7 @@ export class PrefsFeed {
}
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
switch (topic) {
case lazy.Region.REGION_TOPIC:
this.store.dispatch(
diff --git a/browser/components/newtab/lib/RecommendationProvider.sys.mjs b/browser/components/newtab/lib/RecommendationProvider.sys.mjs
index 03e976544f..875c90492b 100644
--- a/browser/components/newtab/lib/RecommendationProvider.sys.mjs
+++ b/browser/components/newtab/lib/RecommendationProvider.sys.mjs
@@ -242,7 +242,7 @@ export class RecommendationProvider {
);
}
- async observe(subject, topic, data) {
+ async observe(subject, topic) {
switch (topic) {
case "idle-daily":
await this.updatePersonalizationScores();
diff --git a/browser/components/newtab/lib/SectionsManager.sys.mjs b/browser/components/newtab/lib/SectionsManager.sys.mjs
index 96bba0c9ea..069ddbb224 100644
--- a/browser/components/newtab/lib/SectionsManager.sys.mjs
+++ b/browser/components/newtab/lib/SectionsManager.sys.mjs
@@ -31,7 +31,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
* `${feed_pref_name}.options`.
*/
-const BUILT_IN_SECTIONS = ({ newtab, pocketNewtab }) => ({
+const BUILT_IN_SECTIONS = ({ pocketNewtab }) => ({
"feeds.section.topstories": options => ({
id: "topstories",
pref: {
@@ -107,7 +107,7 @@ const BUILT_IN_SECTIONS = ({ newtab, pocketNewtab }) => ({
shouldSendImpressionStats: true,
dedupeFrom: ["highlights"],
}),
- "feeds.section.highlights": options => ({
+ "feeds.section.highlights": () => ({
id: "highlights",
pref: {
titleString: {
diff --git a/browser/components/newtab/lib/Store.sys.mjs b/browser/components/newtab/lib/Store.sys.mjs
index 3a4fdfa98d..ea66c36f50 100644
--- a/browser/components/newtab/lib/Store.sys.mjs
+++ b/browser/components/newtab/lib/Store.sys.mjs
@@ -144,7 +144,7 @@ export class Store {
this._messageChannel.simulateMessagesForExistingTabs();
}
- async _initIndexedDB(telemetryKey) {
+ async _initIndexedDB() {
// "snippets" is the name of one storage space, but these days it is used
// not for snippet-related data (snippets were removed in bug 1715158),
// but storage for impression or session data for all ASRouter messages.
diff --git a/browser/components/newtab/lib/TelemetryFeed.sys.mjs b/browser/components/newtab/lib/TelemetryFeed.sys.mjs
index 99bed168a8..1a9e9e3d34 100644
--- a/browser/components/newtab/lib/TelemetryFeed.sys.mjs
+++ b/browser/components/newtab/lib/TelemetryFeed.sys.mjs
@@ -714,8 +714,8 @@ export class TelemetryFeed {
});
const session = this.sessions.get(au.getPortIdOfSender(action));
switch (action.data?.event) {
- case "CLICK":
- const { card_type, topic, recommendation_id, tile_id, shim } =
+ case "CLICK": {
+ const { card_type, topic, recommendation_id, tile_id, shim, feature } =
action.data.value ?? {};
if (
action.data.source === "POPULAR_TOPICS" ||
@@ -725,6 +725,11 @@ export class TelemetryFeed {
newtab_visit_id: session.session_id,
topic,
});
+ } else if (action.data.source === "FEATURE_HIGHLIGHT") {
+ Glean.newtab.tooltipClick.record({
+ newtab_visit_id: session.session_id,
+ feature,
+ });
} else if (["spoc", "organic"].includes(card_type)) {
Glean.pocket.click.record({
newtab_visit_id: session.session_id,
@@ -739,6 +744,7 @@ export class TelemetryFeed {
}
}
break;
+ }
case "SAVE_TO_POCKET":
Glean.pocket.save.record({
newtab_visit_id: session.session_id,
diff --git a/browser/components/newtab/lib/TopSitesFeed.sys.mjs b/browser/components/newtab/lib/TopSitesFeed.sys.mjs
index db21411fdd..796211085b 100644
--- a/browser/components/newtab/lib/TopSitesFeed.sys.mjs
+++ b/browser/components/newtab/lib/TopSitesFeed.sys.mjs
@@ -165,7 +165,7 @@ class TopSitesTelemetry {
{},
...Object.entries(this.allSponsoredTiles)
.filter(
- ([k, v]) =>
+ ([, v]) =>
v.display_fail_reason === null ||
v.display_fail_reason === undefined
)
@@ -196,8 +196,8 @@ class TopSitesTelemetry {
clearTilesForProvider(provider) {
Object.entries(this.allSponsoredTiles)
- .filter(([k, v]) => k.startsWith(provider))
- .map(([k, v]) => delete this.allSponsoredTiles[k]);
+ .filter(([k]) => k.startsWith(provider))
+ .map(([k]) => delete this.allSponsoredTiles[k]);
}
_getAdvertiser(tile) {
diff --git a/browser/components/newtab/loaders/inject-loader.js b/browser/components/newtab/loaders/inject-loader.js
deleted file mode 100644
index 8729baf270..0000000000
--- a/browser/components/newtab/loaders/inject-loader.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/* 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/. */
-
-// Note: this is based on https://github.com/plasticine/inject-loader,
-// patched to make istanbul work properly
-
-const loaderUtils = require("loader-utils");
-const QUOTE_REGEX_STRING = "['|\"]{1}";
-
-const hasOnlyExcludeFlags = query =>
- Object.keys(query).filter(key => query[key] === true).length === 0;
-const escapePath = path => path.replace("/", "\\/");
-
-function createRequireStringRegex(query) {
- const regexArray = [];
-
- // if there is no query then replace everything
- if (Object.keys(query).length === 0) {
- regexArray.push("([^\\)]+)");
- } else if (hasOnlyExcludeFlags(query)) {
- // if there are only negation matches in the query then replace everything
- // except them
- Object.keys(query).forEach(key =>
- regexArray.push(`(?!${QUOTE_REGEX_STRING}${escapePath(key)})`)
- );
- regexArray.push("([^\\)]+)");
- } else {
- regexArray.push(`(${QUOTE_REGEX_STRING}(`);
- regexArray.push(
- Object.keys(query)
- .map(key => escapePath(key))
- .join("|")
- );
- regexArray.push(`)${QUOTE_REGEX_STRING})`);
- }
-
- // Wrap the regex to match `require()`
- regexArray.unshift("require\\(");
- regexArray.push("\\)");
-
- return new RegExp(regexArray.join(""), "g");
-}
-
-module.exports = function inject(src) {
- if (this.cacheable) {
- this.cacheable();
- }
- const regex = createRequireStringRegex(
- loaderUtils.urlToRequest(this.resourcePath) || {}
- );
-
- return `module.exports = function inject(injections) {
- var module = {exports: {}};
- var exports = module.exports;
- ${src.replace(regex, "(injections[$1] || /* istanbul ignore next */ $&)")}
- return module.exports;
-}\n`;
-};
diff --git a/browser/components/newtab/metrics.yaml b/browser/components/newtab/metrics.yaml
index 04845fcda0..bd74e609ad 100644
--- a/browser/components/newtab/metrics.yaml
+++ b/browser/components/newtab/metrics.yaml
@@ -212,6 +212,27 @@ newtab:
- newtab
lifetime: application
+ tooltip_click:
+ type: event
+ description: >
+ Recorded when a feature highlight tooltip is opened.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1888983
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1888983
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - sdowne@mozilla.com
+ expires: never
+ extra_keys:
+ newtab_visit_id: *newtab_visit_id
+ feature:
+ description: The feature that was clicked on.
+ type: string
+ send_in_pings:
+ - newtab
+
newtab.search:
enabled:
lifetime: application
diff --git a/browser/components/newtab/package-lock.json b/browser/components/newtab/package-lock.json
index 7a9fead1b8..2da0b42dbe 100644
--- a/browser/components/newtab/package-lock.json
+++ b/browser/components/newtab/package-lock.json
@@ -21,10 +21,7 @@
"@babel/core": "7.23.5",
"@babel/preset-react": "7.23.3",
"@jsdevtools/coverage-istanbul-loader": "^3.0.5",
- "acorn": "8.5.0",
"babel-loader": "8.2.3",
- "babel-plugin-jsm-to-esmodules": "0.6.0",
- "buffer": "6.0.3",
"chai": "4.3.4",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
@@ -39,7 +36,6 @@
"karma-sinon": "1.0.5",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "5.0.0",
- "loader-utils": "3.2.1",
"lodash": "4.17.21",
"mocha": "9.2.2",
"mock-raf": "1.0.1",
@@ -51,8 +47,6 @@
"sass": "1.43.4",
"shelljs": "0.8.5",
"sinon": "12.0.1",
- "stream-browserify": "3.0.0",
- "util": "0.10.4",
"webpack": "5.89.0",
"webpack-cli": "4.9.1",
"yamscripts": "0.1.0"
@@ -1008,6 +1002,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1273,38 +1268,12 @@
"node": ">=4.0.0"
}
},
- "node_modules/babel-plugin-jsm-to-esmodules": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jsm-to-esmodules/-/babel-plugin-jsm-to-esmodules-0.6.0.tgz",
- "integrity": "sha512-463Yuq2sLkjoGHl5vPYUQQONnDjxnmxZuhsR1swL5N76hDFGyYZAVd6HoS4E02jBF8bORpS4aFmdr1XjEZ0buQ==",
- "dev": true
- },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -1437,30 +1406,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -3217,26 +3162,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
"node_modules/import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
@@ -4140,15 +4065,6 @@
"node": ">=6.11.5"
}
},
- "node_modules/loader-utils": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz",
- "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==",
- "dev": true,
- "engines": {
- "node": ">= 12.13.0"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -5546,20 +5462,6 @@
"node": ">=4"
}
},
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -6112,16 +6014,6 @@
"node": ">= 0.6"
}
},
- "node_modules/stream-browserify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
- "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
- "dev": true,
- "dependencies": {
- "inherits": "~2.0.4",
- "readable-stream": "^3.5.0"
- }
- },
"node_modules/streamroller": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
@@ -6136,15 +6028,6 @@
"node": ">=8.0"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -6638,27 +6521,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/util": {
- "version": "0.10.4",
- "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
- "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
- "dev": true,
- "dependencies": {
- "inherits": "2.0.3"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
- },
- "node_modules/util/node_modules/inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
- "dev": true
- },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -7943,7 +7805,8 @@
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"acorn-import-assertions": {
"version": "1.9.0",
@@ -8133,24 +7996,12 @@
}
}
},
- "babel-plugin-jsm-to-esmodules": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-jsm-to-esmodules/-/babel-plugin-jsm-to-esmodules-0.6.0.tgz",
- "integrity": "sha512-463Yuq2sLkjoGHl5vPYUQQONnDjxnmxZuhsR1swL5N76hDFGyYZAVd6HoS4E02jBF8bORpS4aFmdr1XjEZ0buQ==",
- "dev": true
- },
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "dev": true
- },
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -8249,16 +8100,6 @@
"update-browserslist-db": "^1.0.13"
}
},
- "buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "dev": true,
- "requires": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -9584,12 +9425,6 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
- "ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true
- },
"import-local": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
@@ -10245,12 +10080,6 @@
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true
},
- "loader-utils": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz",
- "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==",
- "dev": true
- },
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -11275,17 +11104,6 @@
"path-type": "^3.0.0"
}
},
- "readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- },
"readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -11708,16 +11526,6 @@
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"dev": true
},
- "stream-browserify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
- "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
- "dev": true,
- "requires": {
- "inherits": "~2.0.4",
- "readable-stream": "^3.5.0"
- }
- },
"streamroller": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
@@ -11729,15 +11537,6 @@
"fs-extra": "^8.1.0"
}
},
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.2.0"
- }
- },
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -12061,29 +11860,6 @@
"punycode": "^2.1.0"
}
},
- "util": {
- "version": "0.10.4",
- "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
- "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
- "dev": true,
- "requires": {
- "inherits": "2.0.3"
- },
- "dependencies": {
- "inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
- "dev": true
- }
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
- },
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
diff --git a/browser/components/newtab/package.json b/browser/components/newtab/package.json
index b878deb975..0724ae78ee 100644
--- a/browser/components/newtab/package.json
+++ b/browser/components/newtab/package.json
@@ -19,10 +19,7 @@
"@babel/core": "7.23.5",
"@babel/preset-react": "7.23.3",
"@jsdevtools/coverage-istanbul-loader": "^3.0.5",
- "acorn": "8.5.0",
"babel-loader": "8.2.3",
- "babel-plugin-jsm-to-esmodules": "0.6.0",
- "buffer": "6.0.3",
"chai": "4.3.4",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
@@ -37,7 +34,6 @@
"karma-sinon": "1.0.5",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "5.0.0",
- "loader-utils": "3.2.1",
"lodash": "4.17.21",
"mocha": "9.2.2",
"mock-raf": "1.0.1",
@@ -49,8 +45,6 @@
"sass": "1.43.4",
"shelljs": "0.8.5",
"sinon": "12.0.1",
- "stream-browserify": "3.0.0",
- "util": "0.10.4",
"webpack": "5.89.0",
"webpack-cli": "4.9.1",
"yamscripts": "0.1.0"
diff --git a/browser/components/newtab/test/browser/browser_newtab_ping.js b/browser/components/newtab/test/browser/browser_newtab_ping.js
index 009305eb7a..4ed2fd7200 100644
--- a/browser/components/newtab/test/browser/browser_newtab_ping.js
+++ b/browser/components/newtab/test/browser/browser_newtab_ping.js
@@ -187,7 +187,7 @@ add_task(async function test_newtab_doesnt_send_nimbus() {
let { sessions } =
AboutNewTab.activityStream.store.feeds.get("feeds.telemetry");
return !Array.from(sessions.entries()).filter(
- ([k, v]) => v.session_id === sessionId
+ ([, v]) => v.session_id === sessionId
).length;
}, "Waiting for sessions to clean up.");
// Session ended without a ping being sent. Success!
diff --git a/browser/components/newtab/test/schemas/pings.js b/browser/components/newtab/test/schemas/pings.js
index 825083066e..fb52602bd4 100644
--- a/browser/components/newtab/test/schemas/pings.js
+++ b/browser/components/newtab/test/schemas/pings.js
@@ -143,7 +143,7 @@ export const UTSessionPing = Joi.array().items(
eventsTelemetryExtraKeys
);
-export function chaiAssertions(_chai, utils) {
+export function chaiAssertions(_chai) {
const { Assertion } = _chai;
Assertion.addMethod("validate", function (schema, schemaName) {
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 dad4d19fa5..1d572ee3ce 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
@@ -54,9 +54,9 @@ describe("<DSCard>", () => {
it("should render a SafeAnchor", () => {
wrapper.setProps({ url: "https://foo.com" });
- assert.equal(wrapper.children().at(0).type(), SafeAnchor);
+ assert.equal(wrapper.children().at(1).type(), SafeAnchor);
assert.propertyVal(
- wrapper.children().at(0).props(),
+ wrapper.children().at(1).props(),
"url",
"https://foo.com"
);
@@ -64,14 +64,14 @@ describe("<DSCard>", () => {
it("should pass onLinkClick prop", () => {
assert.propertyVal(
- wrapper.children().at(0).props(),
+ wrapper.children().at(1).props(),
"onLinkClick",
wrapper.instance().onLinkClick
);
});
it("should render DSLinkMenu", () => {
- assert.equal(wrapper.children().at(1).type(), DSLinkMenu);
+ assert.equal(wrapper.children().at(3).type(), DSLinkMenu);
});
it("should start with no .active class", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/FeatureHighlight.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/FeatureHighlight.test.jsx
new file mode 100644
index 0000000000..2d7ef66ecf
--- /dev/null
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/FeatureHighlight.test.jsx
@@ -0,0 +1,60 @@
+import { FeatureHighlight } from "content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight";
+import { SponsoredContentHighlight } from "content-src/components/DiscoveryStreamComponents/FeatureHighlight/SponsoredContentHighlight";
+import React from "react";
+import { mount } from "enzyme";
+
+describe("<FeatureHighlight>", () => {
+ let wrapper;
+ let fakeWindow;
+
+ beforeEach(() => {
+ wrapper = mount(<FeatureHighlight />);
+ });
+
+ it("should render", () => {
+ assert.ok(wrapper.exists());
+ assert.ok(wrapper.find(".feature-highlight").exists());
+ });
+
+ it("should render a title", () => {
+ wrapper.setProps({ message: "foo" });
+ assert.ok(wrapper.find(".feature-highlight-modal p").exists());
+ assert.equal(wrapper.find(".feature-highlight-modal p").text(), "foo");
+ });
+
+ it("should open a modal", () => {
+ assert.ok(wrapper.find(".feature-highlight-modal.closed").exists());
+ wrapper.find(".toggle-button").simulate("click");
+ assert.ok(wrapper.find(".feature-highlight-modal.opened").exists());
+ wrapper.find(".icon-dismiss").simulate("click");
+ assert.ok(wrapper.find(".feature-highlight-modal.closed").exists());
+ });
+
+ it("should close a modal if clicking outside", () => {
+ fakeWindow = {
+ document: {
+ addEventListener: (event, handler) => {
+ fakeWindow.document.handleOutsideClick = handler;
+ },
+ removeEventListener: () => {},
+ },
+ };
+ wrapper.setProps({ windowObj: fakeWindow });
+
+ wrapper.find(".toggle-button").simulate("click");
+ fakeWindow.document.handleOutsideClick({ target: null });
+ });
+});
+
+describe("<SponsoredContentHighlight>", () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = mount(<SponsoredContentHighlight />);
+ });
+
+ it("should render", () => {
+ assert.ok(wrapper.exists());
+ assert.ok(wrapper.find(".sponsored-content-highlight").exists());
+ });
+});
diff --git a/browser/components/newtab/test/unit/content-src/components/ModalOverlay.test.jsx b/browser/components/newtab/test/unit/content-src/components/ModalOverlay.test.jsx
new file mode 100644
index 0000000000..2320e16fc3
--- /dev/null
+++ b/browser/components/newtab/test/unit/content-src/components/ModalOverlay.test.jsx
@@ -0,0 +1,69 @@
+import { ModalOverlayWrapper } from "content-src/components/ModalOverlay/ModalOverlay";
+import { mount } from "enzyme";
+import React from "react";
+
+describe("ModalOverlayWrapper", () => {
+ let fakeDoc;
+ let sandbox;
+ let header;
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ header = document.createElement("div");
+
+ fakeDoc = {
+ addEventListener: sandbox.stub(),
+ removeEventListener: sandbox.stub(),
+ body: { classList: { add: sandbox.stub(), remove: sandbox.stub() } },
+ getElementById() {
+ return header;
+ },
+ };
+ });
+ afterEach(() => {
+ sandbox.restore();
+ });
+ it("should add eventListener and a class on mount", async () => {
+ mount(<ModalOverlayWrapper document={fakeDoc} />);
+ assert.calledOnce(fakeDoc.addEventListener);
+ assert.calledWith(fakeDoc.body.classList.add, "modal-open");
+ });
+
+ it("should remove eventListener on unmount", async () => {
+ const wrapper = mount(<ModalOverlayWrapper document={fakeDoc} />);
+ wrapper.unmount();
+ assert.calledOnce(fakeDoc.addEventListener);
+ assert.calledOnce(fakeDoc.removeEventListener);
+ assert.calledWith(fakeDoc.body.classList.remove, "modal-open");
+ });
+
+ it("should call props.onClose on an Escape key", async () => {
+ const onClose = sandbox.stub();
+ mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />);
+
+ // Simulate onkeydown being called
+ const [, callback] = fakeDoc.addEventListener.firstCall.args;
+ callback({ key: "Escape" });
+
+ assert.calledOnce(onClose);
+ });
+
+ it("should not call props.onClose on other keys than Escape", async () => {
+ const onClose = sandbox.stub();
+ mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />);
+
+ // Simulate onkeydown being called
+ const [, callback] = fakeDoc.addEventListener.firstCall.args;
+ callback({ key: "Ctrl" });
+
+ assert.notCalled(onClose);
+ });
+
+ it("should not call props.onClose when clicked outside dialog", async () => {
+ const onClose = sandbox.stub();
+ const wrapper = mount(
+ <ModalOverlayWrapper document={fakeDoc} onClose={onClose} />
+ );
+ wrapper.find("div.modalOverlayOuter.active").simulate("click");
+ assert.notCalled(onClose);
+ });
+});
diff --git a/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx b/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
index 1977066f0d..798bb9b8c7 100644
--- a/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
@@ -1488,20 +1488,21 @@ describe("<TopSiteList>", () => {
TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW
);
});
- it("should fill with placeholders if TopSites rows is less than TopSitesRows", () => {
+ it("should add a single placeholder is there is availible space in the row", () => {
const rows = [{ url: "https://foo.com" }, { url: "https://bar.com" }];
+ const availibleRows = 1;
const wrapper = shallow(
<TopSiteList
{...DEFAULT_PROPS}
TopSites={{ rows }}
- TopSitesRows={1}
+ TopSitesRows={availibleRows}
App={{ APP }}
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
assert.lengthOf(
wrapper.find(TopSitePlaceholder),
- TOP_SITES_MAX_SITES_PER_ROW - 2,
+ availibleRows >= wrapper.find(TopSite).length ? 0 : 1,
"placeholders"
);
});
@@ -1522,11 +1523,7 @@ describe("<TopSiteList>", () => {
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
- assert.lengthOf(
- wrapper.find(TopSitePlaceholder),
- TOP_SITES_MAX_SITES_PER_ROW - 2,
- "placeholders"
- );
+ assert.lengthOf(wrapper.find(TopSitePlaceholder), 4, "placeholders");
});
it("should fill any holes in TopSites with placeholders", () => {
const rows = [{ url: "https://foo.com" }];
@@ -1540,11 +1537,7 @@ describe("<TopSiteList>", () => {
/>
);
assert.lengthOf(wrapper.find(TopSite), 2, "topSites");
- assert.lengthOf(
- wrapper.find(TopSitePlaceholder),
- TOP_SITES_MAX_SITES_PER_ROW - 2,
- "placeholders"
- );
+ assert.lengthOf(wrapper.find(TopSitePlaceholder), 1, "placeholders");
});
it("should update state onDragStart and clear it onDragEnd", () => {
const wrapper = shallow(<TopSiteList {...DEFAULT_PROPS} App={{ APP }} />);
@@ -1638,6 +1631,7 @@ describe("<TopSiteList>", () => {
App={{ APP }}
/>
);
+ const addButton = { isAddButton: true };
let instance = wrapper.instance();
instance.setState({
draggedIndex: 0,
@@ -1652,7 +1646,7 @@ describe("<TopSiteList>", () => {
site2,
draggedSite,
site3,
- null,
+ addButton,
null,
null,
null,
@@ -1662,7 +1656,7 @@ describe("<TopSiteList>", () => {
site2,
site3,
draggedSite,
- null,
+ addButton,
null,
null,
null,
@@ -1671,7 +1665,7 @@ describe("<TopSiteList>", () => {
assert.deepEqual(instance._makeTopSitesPreview(3), [
site2,
site3,
- null,
+ addButton,
draggedSite,
null,
null,
@@ -1683,7 +1677,7 @@ describe("<TopSiteList>", () => {
site2,
draggedSite,
site3,
- null,
+ addButton,
null,
null,
null,
@@ -1693,7 +1687,7 @@ describe("<TopSiteList>", () => {
site3,
site2,
draggedSite,
- null,
+ addButton,
null,
null,
null,
@@ -1704,7 +1698,7 @@ describe("<TopSiteList>", () => {
site2,
draggedSite,
site3,
- null,
+ addButton,
null,
null,
null,
@@ -1714,7 +1708,7 @@ describe("<TopSiteList>", () => {
site2,
site3,
draggedSite,
- null,
+ addButton,
null,
null,
null,
@@ -1725,7 +1719,7 @@ describe("<TopSiteList>", () => {
site2,
draggedSite,
site3,
- null,
+ addButton,
null,
null,
null,
@@ -1735,7 +1729,7 @@ describe("<TopSiteList>", () => {
site2,
site3,
draggedSite,
- null,
+ addButton,
null,
null,
null,
@@ -1752,7 +1746,7 @@ describe("<TopSiteList>", () => {
draggedSite,
site1,
site3,
- null,
+ addButton,
null,
null,
null,
@@ -1762,7 +1756,7 @@ describe("<TopSiteList>", () => {
site1,
site3,
draggedSite,
- null,
+ addButton,
null,
null,
null,
@@ -1779,7 +1773,7 @@ describe("<TopSiteList>", () => {
draggedSite,
site2,
site1,
- null,
+ addButton,
null,
null,
null,
@@ -1797,7 +1791,7 @@ describe("<TopSiteList>", () => {
draggedSite,
site2,
site1,
- null,
+ addButton,
null,
null,
null,
@@ -1822,13 +1816,13 @@ describe("<TopSiteList>", () => {
});
describe("TopSitePlaceholder", () => {
- it("should dispatch a TOP_SITES_EDIT action when edit-button is clicked", () => {
+ it("should dispatch a TOP_SITES_EDIT action when the addbutton is clicked", () => {
const dispatch = sinon.spy();
const wrapper = shallow(
- <TopSitePlaceholder dispatch={dispatch} index={7} />
+ <TopSitePlaceholder dispatch={dispatch} index={7} isAddButton={true} />
);
- wrapper.find(".edit-button").first().simulate("click");
+ wrapper.find(".add-button").first().simulate("click");
assert.calledOnce(dispatch);
assert.calledWithExactly(dispatch, {
diff --git a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
index 7438d8247c..20765608fa 100644
--- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
+++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
@@ -146,7 +146,7 @@ describe("AboutPreferences Feed", () => {
},
},
createProcessingInstruction: sandbox.stub(),
- createElementNS: sandbox.stub().callsFake((NS, el) => node),
+ createElementNS: sandbox.stub().callsFake(() => node),
getElementById: sandbox.stub().returns(node),
insertBefore: sandbox.stub().returnsArg(0),
querySelector: sandbox
diff --git a/browser/components/newtab/test/unit/lib/ActivityStream.test.js b/browser/components/newtab/test/unit/lib/ActivityStream.test.js
index c127060021..b9deba1069 100644
--- a/browser/components/newtab/test/unit/lib/ActivityStream.test.js
+++ b/browser/components/newtab/test/unit/lib/ActivityStream.test.js
@@ -417,13 +417,11 @@ describe("ActivityStream", () => {
clock = sinon.useFakeTimers();
// Have addObserver cause prefHasUserValue to now return true then observe
- sandbox
- .stub(global.Services.obs, "addObserver")
- .callsFake((pref, obs) => {
- setTimeout(() => {
- Services.obs.notifyObservers("US", "browser-region-updated");
- });
+ sandbox.stub(global.Services.obs, "addObserver").callsFake(() => {
+ setTimeout(() => {
+ Services.obs.notifyObservers("US", "browser-region-updated");
});
+ });
});
afterEach(() => clock.restore());
diff --git a/browser/components/newtab/test/unit/lib/PersonalityProvider/PersonalityProvider.test.js b/browser/components/newtab/test/unit/lib/PersonalityProvider/PersonalityProvider.test.js
index 0058fd7c76..0feabe978e 100644
--- a/browser/components/newtab/test/unit/lib/PersonalityProvider/PersonalityProvider.test.js
+++ b/browser/components/newtab/test/unit/lib/PersonalityProvider/PersonalityProvider.test.js
@@ -19,7 +19,7 @@ describe("Personality Provider", () => {
RemoteSettingsOffStub = sandbox.stub().returns();
RemoteSettingsGetStub = sandbox.stub().returns([]);
- RemoteSettingsStub = name => ({
+ RemoteSettingsStub = () => ({
get: RemoteSettingsGetStub,
on: RemoteSettingsOnStub,
off: RemoteSettingsOffStub,
@@ -142,7 +142,7 @@ describe("Personality Provider", () => {
},
]);
sinon.spy(instance, "getAttachment");
- RemoteSettingsStub = name => ({
+ RemoteSettingsStub = () => ({
get: RemoteSettingsGetStub,
on: RemoteSettingsOnStub,
off: RemoteSettingsOffStub,
diff --git a/browser/components/newtab/test/unit/lib/PersonalityProvider/RecipeExecutor.test.js b/browser/components/newtab/test/unit/lib/PersonalityProvider/RecipeExecutor.test.js
index fdbcae9613..bf8ed1fc2b 100644
--- a/browser/components/newtab/test/unit/lib/PersonalityProvider/RecipeExecutor.test.js
+++ b/browser/components/newtab/test/unit/lib/PersonalityProvider/RecipeExecutor.test.js
@@ -6,7 +6,7 @@ class MockTagger {
this.mode = mode;
this.tagScoreMap = tagScoreMap;
}
- tagTokens(tokens) {
+ tagTokens() {
if (this.mode === "nb") {
// eslint-disable-next-line prefer-destructuring
let tag = Object.keys(this.tagScoreMap)[0];
diff --git a/browser/components/newtab/test/unit/unit-entry.js b/browser/components/newtab/test/unit/unit-entry.js
index 5b32269ca8..73b4b8fcb5 100644
--- a/browser/components/newtab/test/unit/unit-entry.js
+++ b/browser/components/newtab/test/unit/unit-entry.js
@@ -97,8 +97,8 @@ const TEST_GLOBAL = {
JSWindowActorParent,
JSWindowActorChild,
AboutReaderParent: {
- addMessageListener: (messageName, listener) => {},
- removeMessageListener: (messageName, listener) => {},
+ addMessageListener: (_messageName, _listener) => {},
+ removeMessageListener: (_messageName, _listener) => {},
},
AboutWelcomeTelemetry: class {
submitGleanPingForPing() {}
@@ -281,8 +281,8 @@ const TEST_GLOBAL = {
},
dump() {},
EveryWindow: {
- registerCallback: (id, init, uninit) => {},
- unregisterCallback: id => {},
+ registerCallback: (_id, _init, _uninit) => {},
+ unregisterCallback: _id => {},
},
setTimeout: window.setTimeout.bind(window),
clearTimeout: window.clearTimeout.bind(window),
@@ -402,7 +402,7 @@ const TEST_GLOBAL = {
},
urlFormatter: { formatURL: str => str, formatURLPref: str => str },
mm: {
- addMessageListener: (msg, cb) => this.receiveMessage(),
+ addMessageListener: (_msg, _cb) => this.receiveMessage(),
removeMessageListener() {},
},
obs: {
@@ -412,7 +412,7 @@ const TEST_GLOBAL = {
},
telemetry: {
setEventRecordingEnabled: () => {},
- recordEvent: eventDetails => {},
+ recordEvent: _eventDetails => {},
scalarSet: () => {},
keyedScalarAdd: () => {},
},
@@ -570,7 +570,7 @@ const TEST_GLOBAL = {
finish: () => {},
},
Sampling: {
- ratioSample(seed, ratios) {
+ ratioSample(_seed, _ratios) {
return Promise.resolve(0);
},
},
diff --git a/browser/components/newtab/test/unit/utils.js b/browser/components/newtab/test/unit/utils.js
index 22069b8635..e10a284476 100644
--- a/browser/components/newtab/test/unit/utils.js
+++ b/browser/components/newtab/test/unit/utils.js
@@ -176,7 +176,7 @@ export class FakensIPrefBranch {
prefHasUserValue(prefName) {
return this.prefs.has(prefName);
}
- prefIsLocked(prefName) {
+ prefIsLocked(_prefName) {
return false;
}
}
@@ -187,7 +187,7 @@ export class FakensIPrefBranch {
*/
export class FakensIPrefService extends FakensIPrefBranch {
getBranch() {}
- getDefaultBranch(prefix) {
+ getDefaultBranch(_prefix) {
return {
setBoolPref() {},
setIntPref() {},
@@ -208,8 +208,8 @@ export class FakePrefs extends FakensIPrefBranch {
ignore(prefName, callback) {
super.removeObserver(prefName, callback);
}
- observeBranch(listener) {}
- ignoreBranch(listener) {}
+ observeBranch(_listener) {}
+ ignoreBranch(_listener) {}
set(prefName, value) {
this.prefs.set(prefName, value);
@@ -312,7 +312,7 @@ FakePerformance.prototype = {
return 10000.234;
},
// XXX assumes type == "mark"
- getEntriesByName(name, type) {
+ getEntriesByName(name, _type) {
if (this.marks.has(name)) {
return this.marks.get(name);
}
diff --git a/browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js b/browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js
deleted file mode 100644
index 3d83f473d5..0000000000
--- a/browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-"use strict";
-
-const { AboutWelcomeDefaults } = ChromeUtils.importESModule(
- "resource:///modules/aboutwelcome/AboutWelcomeDefaults.sys.mjs"
-);
-const { sinon } = ChromeUtils.importESModule(
- "resource://testing-common/Sinon.sys.mjs"
-);
-const { AttributionCode } = ChromeUtils.importESModule(
- "resource:///modules/AttributionCode.sys.mjs"
-);
-const { AddonRepository } = ChromeUtils.importESModule(
- "resource://gre/modules/addons/AddonRepository.sys.mjs"
-);
-
-const TEST_ATTRIBUTION_DATA = {
- source: "addons.mozilla.org",
- medium: "referral",
- campaign: "non-fx-button",
- content: "rta:iridium%40particlecore.github.io",
-};
-
-add_task(async function test_handleAddonInfoNotFound() {
- let sandbox = sinon.createSandbox();
- const stub = sandbox.stub(AttributionCode, "getAttrDataAsync").resolves(null);
- let result = await AboutWelcomeDefaults.getAttributionContent();
- equal(stub.callCount, 1, "Call was made");
- equal(result, null, "No data is returned");
-
- sandbox.restore();
-});
-
-add_task(async function test_UAAttribution() {
- let sandbox = sinon.createSandbox();
- const stub = sandbox
- .stub(AttributionCode, "getAttrDataAsync")
- .resolves({ ua: "test" });
- let result = await AboutWelcomeDefaults.getAttributionContent();
- equal(stub.callCount, 1, "Call was made");
- equal(result.template, undefined, "Template was not returned");
- equal(result.ua, "test", "UA was returned");
-
- sandbox.restore();
-});
-
-add_task(async function test_formatAttributionData() {
- let sandbox = sinon.createSandbox();
- const TEST_ADDON_INFO = {
- sourceURI: { scheme: "https", spec: "https://test.xpi" },
- name: "Test Add-on",
- icons: { 64: "http://test.svg" },
- };
- sandbox
- .stub(AttributionCode, "getAttrDataAsync")
- .resolves(TEST_ATTRIBUTION_DATA);
- sandbox.stub(AddonRepository, "getAddonsByIDs").resolves([TEST_ADDON_INFO]);
- let result = await AboutWelcomeDefaults.getAttributionContent(
- TEST_ATTRIBUTION_DATA
- );
- equal(AddonRepository.getAddonsByIDs.callCount, 1, "Retrieve addon content");
- equal(result.template, "return_to_amo", "RTAMO template returned");
- equal(result.name, TEST_ADDON_INFO.name, "AddonInfo returned");
-
- sandbox.restore();
-});
diff --git a/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js b/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js
deleted file mode 100644
index b8339fb39f..0000000000
--- a/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-"use strict";
-
-const { AboutWelcomeTelemetry } = ChromeUtils.importESModule(
- "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs"
-);
-const { AttributionCode } = ChromeUtils.importESModule(
- "resource:///modules/AttributionCode.sys.mjs"
-);
-const { sinon } = ChromeUtils.importESModule(
- "resource://testing-common/Sinon.sys.mjs"
-);
-const TELEMETRY_PREF = "browser.newtabpage.activity-stream.telemetry";
-
-add_setup(function setup() {
- do_get_profile();
- Services.fog.initializeFOG();
-});
-
-add_task(function test_enabled() {
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(TELEMETRY_PREF);
- });
- Services.prefs.setBoolPref(TELEMETRY_PREF, true);
-
- const AWTelemetry = new AboutWelcomeTelemetry();
-
- equal(AWTelemetry.telemetryEnabled, true, "Telemetry should be on");
-
- Services.prefs.setBoolPref(TELEMETRY_PREF, false);
-
- equal(AWTelemetry.telemetryEnabled, false, "Telemetry should be off");
-});
-
-add_task(async function test_pingPayload() {
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(TELEMETRY_PREF);
- });
- Services.prefs.setBoolPref(TELEMETRY_PREF, true);
- const AWTelemetry = new AboutWelcomeTelemetry();
- sinon.stub(AWTelemetry, "_createPing").resolves({ event: "MOCHITEST" });
-
- let pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(Glean.messagingSystem.event.testGetValue(), "MOCHITEST");
- });
- await AWTelemetry.sendTelemetry();
-
- ok(pingSubmitted, "Glean ping was submitted");
-});
-
-add_task(function test_mayAttachAttribution() {
- const sandbox = sinon.createSandbox();
- const AWTelemetry = new AboutWelcomeTelemetry();
-
- sandbox.stub(AttributionCode, "getCachedAttributionData").returns(null);
-
- let ping = AWTelemetry._maybeAttachAttribution({});
-
- equal(ping.attribution, undefined, "Should not set attribution if it's null");
-
- sandbox.restore();
- sandbox.stub(AttributionCode, "getCachedAttributionData").returns({});
- ping = AWTelemetry._maybeAttachAttribution({});
-
- equal(
- ping.attribution,
- undefined,
- "Should not set attribution if it's empty"
- );
-
- const attr = {
- source: "google.com",
- medium: "referral",
- campaign: "Firefox-Brand-US-Chrome",
- content: "(not set)",
- experiment: "(not set)",
- variation: "(not set)",
- ua: "chrome",
- };
- sandbox.restore();
- sandbox.stub(AttributionCode, "getCachedAttributionData").returns(attr);
- ping = AWTelemetry._maybeAttachAttribution({});
-
- equal(ping.attribution, attr, "Should set attribution if it presents");
-});
diff --git a/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry_glean.js b/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry_glean.js
deleted file mode 100644
index 5191f05d04..0000000000
--- a/browser/components/newtab/test/xpcshell/test_AboutWelcomeTelemetry_glean.js
+++ /dev/null
@@ -1,238 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-"use strict";
-
-const { AboutWelcomeTelemetry } = ChromeUtils.importESModule(
- "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs"
-);
-const TELEMETRY_PREF = "browser.newtabpage.activity-stream.telemetry";
-
-add_setup(function setup() {
- do_get_profile();
- Services.fog.initializeFOG();
-});
-
-// We recognize two kinds of unexpected data that might reach
-// `submitGleanPingForPing`: unknown keys, and keys with unexpectedly-complex
-// data (ie, non-scalar).
-// We report the keys in special metrics to aid in system health monitoring.
-add_task(function test_weird_data() {
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(TELEMETRY_PREF);
- });
- Services.prefs.setBoolPref(TELEMETRY_PREF, true);
-
- const AWTelemetry = new AboutWelcomeTelemetry();
-
- const unknownKey = "some_unknown_key";
- const camelUnknownKey = AWTelemetry._snakeToCamelCase(unknownKey);
-
- let pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.unknownKeys[camelUnknownKey].testGetValue(),
- 1,
- "caught the unknown key"
- );
- // TODO(bug 1600008): Also check the for-testing overall count.
- Assert.equal(Glean.messagingSystem.unknownKeyCount.testGetValue(), 1);
- });
- AWTelemetry.submitGleanPingForPing({
- [unknownKey]: "value doesn't matter",
- });
-
- Assert.ok(pingSubmitted, "Ping with unknown keys was submitted");
-
- const invalidNestedDataKey = "event";
- pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.invalidNestedData[
- invalidNestedDataKey
- ].testGetValue("messaging-system"),
- 1,
- "caught the invalid nested data"
- );
- });
- AWTelemetry.submitGleanPingForPing({
- [invalidNestedDataKey]: { this_should: "not be", complex: "data" },
- });
-
- Assert.ok(pingSubmitted, "Ping with invalid nested data submitted");
-});
-
-// `event_context` is weird. It's an object, but it might have been stringified
-// before being provided for recording.
-add_task(function test_event_context() {
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(TELEMETRY_PREF);
- });
- Services.prefs.setBoolPref(TELEMETRY_PREF, true);
-
- const AWTelemetry = new AboutWelcomeTelemetry();
-
- const eventContext = {
- reason: "reason",
- page: "page",
- source: "source",
- something_else: "not specifically handled",
- screen_family: "family",
- screen_id: "screen_id",
- screen_index: 0,
- screen_initlals: "screen_initials",
- };
- const stringifiedEC = JSON.stringify(eventContext);
-
- let pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.eventReason.testGetValue(),
- eventContext.reason,
- "event_context.reason also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventPage.testGetValue(),
- eventContext.page,
- "event_context.page also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventSource.testGetValue(),
- eventContext.source,
- "event_context.source also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenFamily.testGetValue(),
- eventContext.screen_family,
- "event_context.screen_family also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenId.testGetValue(),
- eventContext.screen_id,
- "event_context.screen_id also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenIndex.testGetValue(),
- eventContext.screen_index,
- "event_context.screen_index also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenInitials.testGetValue(),
- eventContext.screen_initials,
- "event_context.screen_initials also in own metric."
- );
-
- Assert.equal(
- Glean.messagingSystem.eventContext.testGetValue(),
- stringifiedEC,
- "whole event_context added as text."
- );
- });
- AWTelemetry.submitGleanPingForPing({
- event_context: eventContext,
- });
- Assert.ok(pingSubmitted, "Ping with object event_context submitted");
-
- pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.eventReason.testGetValue(),
- eventContext.reason,
- "event_context.reason also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventPage.testGetValue(),
- eventContext.page,
- "event_context.page also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventSource.testGetValue(),
- eventContext.source,
- "event_context.source also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenFamily.testGetValue(),
- eventContext.screen_family,
- "event_context.screen_family also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenId.testGetValue(),
- eventContext.screen_id,
- "event_context.screen_id also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenIndex.testGetValue(),
- eventContext.screen_index,
- "event_context.screen_index also in own metric."
- );
- Assert.equal(
- Glean.messagingSystem.eventScreenInitials.testGetValue(),
- eventContext.screen_initials,
- "event_context.screen_initials also in own metric."
- );
-
- Assert.equal(
- Glean.messagingSystem.eventContext.testGetValue(),
- stringifiedEC,
- "whole event_context added as text."
- );
- });
- AWTelemetry.submitGleanPingForPing({
- event_context: stringifiedEC,
- });
- Assert.ok(pingSubmitted, "Ping with string event_context submitted");
-});
-
-// For event_context to be more useful, we want to make sure we don't error
-// in cases where it doesn't make much sense, such as a plain string that
-// doesnt attempt to represent a valid object.
-add_task(function test_context_errors() {
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref(TELEMETRY_PREF);
- });
- Services.prefs.setBoolPref(TELEMETRY_PREF, true);
-
- const AWTelemetry = new AboutWelcomeTelemetry();
-
- let weird_context_ping = {
- event_context: "oops, this string isn't a valid JS object!",
- };
-
- let pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.eventContextParseError.testGetValue(),
- undefined,
- "this poorly formed context shouldn't register because it was not an object!"
- );
- });
-
- AWTelemetry.submitGleanPingForPing(weird_context_ping);
-
- Assert.ok(pingSubmitted, "Ping with unknown keys was submitted");
-
- weird_context_ping = {
- event_context:
- "{oops : {'this string isn't a valid JS object, but it sure looks like one!}}'",
- };
-
- pingSubmitted = false;
- GleanPings.messagingSystem.testBeforeNextSubmit(() => {
- pingSubmitted = true;
- Assert.equal(
- Glean.messagingSystem.eventContextParseError.testGetValue(),
- 1,
- "this poorly formed context should register because it was not an object!"
- );
- });
-
- AWTelemetry.submitGleanPingForPing(weird_context_ping);
-
- Assert.ok(pingSubmitted, "Ping with unknown keys was submitted");
-});
diff --git a/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js b/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
index 233eb6df73..31a03947cd 100644
--- a/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
@@ -23,7 +23,9 @@ const {
BOOKMARKS_RESTORE_SUCCESS_EVENT,
BOOKMARKS_RESTORE_FAILED_EVENT,
SECTION_ID,
-} = ChromeUtils.import("resource://activity-stream/lib/HighlightsFeed.jsm");
+} = ChromeUtils.importESModule(
+ "resource://activity-stream/lib/HighlightsFeed.sys.mjs"
+);
const FAKE_LINKS = new Array(20)
.fill(null)
diff --git a/browser/components/newtab/test/xpcshell/test_PlacesFeed.js b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
index 8e7c42d639..19f9e343f5 100644
--- a/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
@@ -424,7 +424,7 @@ add_task(async function test_onAction_OPEN_LINK() {
data: { url: "https://foo.com" },
_target: {
browser: {
- ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" },
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "current" },
},
},
};
@@ -450,7 +450,7 @@ add_task(async function test_onAction_OPEN_LINK_referrer() {
data: { url: "https://foo.com", referrer: "https://foo.com/ref" },
_target: {
browser: {
- ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "tab" },
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "tab" },
},
},
};
@@ -496,7 +496,7 @@ add_task(async function test_onAction_OPEN_LINK_typed_bonus() {
},
_target: {
browser: {
- ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "tab" },
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "tab" },
},
},
};
@@ -524,7 +524,7 @@ add_task(async function test_onAction_OPEN_LINK_pocket() {
},
_target: {
browser: {
- ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" },
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "current" },
},
},
};
@@ -551,7 +551,7 @@ add_task(async function test_onAction_OPEN_LINK_not_http() {
data: { url: "file:///foo.com" },
_target: {
browser: {
- ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" },
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "current" },
},
},
};
diff --git a/browser/components/newtab/test/xpcshell/test_Store.js b/browser/components/newtab/test/xpcshell/test_Store.js
index b05ad36cd6..d872aed61c 100644
--- a/browser/components/newtab/test/xpcshell/test_Store.js
+++ b/browser/components/newtab/test/xpcshell/test_Store.js
@@ -41,7 +41,7 @@ add_task(async function test_messagechannel() {
let sandbox = sinon.createSandbox();
sandbox
.stub(ActivityStreamMessageChannel.prototype, "middleware")
- .returns(s => next => action => next(action));
+ .returns(() => next => action => next(action));
let store = new Store();
info(
diff --git a/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js b/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
index b54d6094ad..59d82f5583 100644
--- a/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
@@ -2860,6 +2860,38 @@ add_task(async function test_handleDiscoveryStreamUserEvent_popular_click() {
sandbox.restore();
});
+add_task(async function test_handleDiscoveryStreamUserEvent_tooltip_click() {
+ info(
+ "TelemetryFeed.handleDiscoveryStreamUserEvent instruments a " +
+ "tooltip click"
+ );
+
+ let sandbox = sinon.createSandbox();
+ let instance = new TelemetryFeed();
+ Services.fog.testResetFOG();
+ const feature = "SPONSORED_CONTENT_INFO";
+ let action = ac.DiscoveryStreamUserEvent({
+ event: "CLICK",
+ source: "FEATURE_HIGHLIGHT",
+ value: {
+ feature,
+ },
+ });
+
+ const SESSION_ID = "decafc0ffee";
+ sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID });
+
+ instance.handleDiscoveryStreamUserEvent(action);
+ let tooltipClicks = Glean.newtab.tooltipClick.testGetValue();
+ Assert.equal(tooltipClicks.length, 1, "Recorded 1 click");
+ Assert.deepEqual(tooltipClicks[0].extra, {
+ newtab_visit_id: SESSION_ID,
+ feature,
+ });
+
+ sandbox.restore();
+});
+
add_task(
async function test_handleDiscoveryStreamUserEvent_organic_top_stories_click() {
info(
diff --git a/browser/components/newtab/test/xpcshell/xpcshell.toml b/browser/components/newtab/test/xpcshell/xpcshell.toml
index a8470913af..87d73669d3 100644
--- a/browser/components/newtab/test/xpcshell/xpcshell.toml
+++ b/browser/components/newtab/test/xpcshell/xpcshell.toml
@@ -14,12 +14,6 @@ skip-if = ["socketprocess_networking"] # Bug 1759035
["test_AboutNewTab.js"]
-["test_AboutWelcomeAttribution.js"]
-
-["test_AboutWelcomeTelemetry.js"]
-
-["test_AboutWelcomeTelemetry_glean.js"]
-
["test_HighlightsFeed.js"]
["test_PlacesFeed.js"]
diff --git a/browser/components/newtab/tools/resourceUriPlugin.js b/browser/components/newtab/tools/resourceUriPlugin.js
index 938822f410..a2244f27fa 100644
--- a/browser/components/newtab/tools/resourceUriPlugin.js
+++ b/browser/components/newtab/tools/resourceUriPlugin.js
@@ -6,6 +6,8 @@
// and translating the uri into a relative filesytem path where the file may be
// found when running within the Karma / Mocha test framework.
+const path = require("path");
+
module.exports = {
ResourceUriPlugin: class ResourceUriPlugin {
/**
@@ -48,7 +50,12 @@ module.exports = {
if (!url.href.match(regex)) {
continue;
}
- const pathname = url.href.replace(regex, replacement);
+ // path.join() is necessary to normalize the path on Windows.
+ // Without it, the path may contain backslashes, resulting in
+ // different build output on Windows than on Unix systems.
+ const pathname = path.join(
+ url.href.replace(regex, replacement)
+ );
resourceData.path = pathname;
resourceData.query = url.search;
resourceData.fragment = url.hash;
diff --git a/browser/components/newtab/webpack.system-addon.config.js b/browser/components/newtab/webpack.system-addon.config.js
index 8f54186ab4..a0400ec39e 100644
--- a/browser/components/newtab/webpack.system-addon.config.js
+++ b/browser/components/newtab/webpack.system-addon.config.js
@@ -50,9 +50,6 @@ module.exports = (env = {}) => ({
resolve: {
extensions: [".js", ".jsx"],
modules: ["node_modules", "."],
- fallback: {
- stream: require.resolve("stream-browserify"),
- },
},
externals: {
"prop-types": "PropTypes",