summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/newtab/content-src/activity-stream.jsx5
-rw-r--r--browser/components/newtab/content-src/components/Base/Base.jsx150
-rw-r--r--browser/components/newtab/content-src/components/Base/_Base.scss38
-rw-r--r--browser/components/newtab/content-src/components/Card/Card.jsx5
-rw-r--r--browser/components/newtab/content-src/components/Card/types.mjs (renamed from browser/components/newtab/content-src/components/Card/types.js)0
-rw-r--r--browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx2
-rw-r--r--browser/components/newtab/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx5
-rw-r--r--browser/components/newtab/content-src/components/ConfirmDialog/ConfirmDialog.jsx2
-rw-r--r--browser/components/newtab/content-src/components/ContextMenu/ContextMenu.jsx4
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx15
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx4
-rw-r--r--browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss4
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx11
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamAdmin/SimpleHashRouter.jsx8
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx6
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx9
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx11
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx5
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx5
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx5
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx2
-rw-r--r--browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx11
-rw-r--r--browser/components/newtab/content-src/components/LinkMenu/LinkMenu.jsx2
-rw-r--r--browser/components/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx2
-rw-r--r--browser/components/newtab/content-src/components/Search/Search.jsx5
-rw-r--r--browser/components/newtab/content-src/components/Sections/Sections.jsx9
-rw-r--r--browser/components/newtab/content-src/components/TopSites/SearchShortcutsForm.jsx5
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSite.jsx5
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx5
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSiteImpressionWrapper.jsx6
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSites.jsx7
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSitesConstants.mjs (renamed from browser/components/newtab/content-src/components/TopSites/TopSitesConstants.js)0
-rw-r--r--browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx100
-rw-r--r--browser/components/newtab/content-src/components/WallpapersSection/_WallpapersSection.scss87
-rw-r--r--browser/components/newtab/content-src/lib/constants.js38
-rw-r--r--browser/components/newtab/content-src/lib/constants.mjs38
-rw-r--r--browser/components/newtab/content-src/lib/detect-user-session-start.js82
-rw-r--r--browser/components/newtab/content-src/lib/detect-user-session-start.mjs82
-rw-r--r--browser/components/newtab/content-src/lib/init-store.js140
-rw-r--r--browser/components/newtab/content-src/lib/init-store.mjs143
-rw-r--r--browser/components/newtab/content-src/lib/link-menu-options.js309
-rw-r--r--browser/components/newtab/content-src/lib/link-menu-options.mjs309
-rw-r--r--browser/components/newtab/content-src/lib/perf-service.js104
-rw-r--r--browser/components/newtab/content-src/lib/perf-service.mjs102
-rw-r--r--browser/components/newtab/content-src/lib/screenshot-utils.js61
-rw-r--r--browser/components/newtab/content-src/lib/screenshot-utils.mjs61
-rw-r--r--browser/components/newtab/content-src/lib/selectLayoutRender.mjs (renamed from browser/components/newtab/content-src/lib/selectLayoutRender.js)0
-rw-r--r--browser/components/newtab/content-src/styles/_activity-stream.scss12
55 files changed, 1195 insertions, 840 deletions
diff --git a/browser/components/newtab/content-src/activity-stream.jsx b/browser/components/newtab/content-src/activity-stream.jsx
index c588e8e850..57ba9f9c92 100644
--- a/browser/components/newtab/content-src/activity-stream.jsx
+++ b/browser/components/newtab/content-src/activity-stream.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { Base } from "content-src/components/Base/Base";
import { DetectUserSessionStart } from "content-src/lib/detect-user-session-start";
import { initStore } from "content-src/lib/init-store";
diff --git a/browser/components/newtab/content-src/components/Base/Base.jsx b/browser/components/newtab/content-src/components/Base/Base.jsx
index 20402b09f5..1738f8f51a 100644
--- a/browser/components/newtab/content-src/components/Base/Base.jsx
+++ b/browser/components/newtab/content-src/components/Base/Base.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { DiscoveryStreamAdmin } from "content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin";
import { ConfirmDialog } from "content-src/components/ConfirmDialog/ConfirmDialog";
import { connect } from "react-redux";
@@ -16,6 +13,9 @@ import React from "react";
import { Search } from "content-src/components/Search/Search";
import { Sections } from "content-src/components/Sections/Sections";
+const VISIBLE = "visible";
+const VISIBILITY_CHANGE_EVENT = "visibilitychange";
+
export const PrefsButton = ({ onClick, icon }) => (
<div className="prefs-button">
<button
@@ -76,7 +76,7 @@ export class _Base extends React.PureComponent {
]
.filter(v => v)
.join(" ");
- global.document.body.className = bodyClassName;
+ globalThis.document.body.className = bodyClassName;
}
render() {
@@ -110,17 +110,75 @@ export class BaseContent extends React.PureComponent {
this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.setPref = this.setPref.bind(this);
- this.state = { fixedSearch: false };
+ this.updateWallpaper = this.updateWallpaper.bind(this);
+ this.prefersDarkQuery = null;
+ this.handleColorModeChange = this.handleColorModeChange.bind(this);
+ this.state = {
+ fixedSearch: false,
+ firstVisibleTimestamp: null,
+ colorMode: "",
+ };
+ }
+
+ setFirstVisibleTimestamp() {
+ if (!this.state.firstVisibleTimestamp) {
+ this.setState({
+ firstVisibleTimestamp: Date.now(),
+ });
+ }
}
componentDidMount() {
global.addEventListener("scroll", this.onWindowScroll);
global.addEventListener("keydown", this.handleOnKeyDown);
+ if (this.props.document.visibilityState === VISIBLE) {
+ this.setFirstVisibleTimestamp();
+ } else {
+ this._onVisibilityChange = () => {
+ if (this.props.document.visibilityState === VISIBLE) {
+ this.setFirstVisibleTimestamp();
+ this.props.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ this._onVisibilityChange = null;
+ }
+ };
+ this.props.document.addEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ // track change event to dark/light mode
+ this.prefersDarkQuery = globalThis.matchMedia(
+ "(prefers-color-scheme: dark)"
+ );
+
+ this.prefersDarkQuery.addEventListener(
+ "change",
+ this.handleColorModeChange
+ );
+ this.handleColorModeChange();
+ }
+
+ handleColorModeChange() {
+ const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
+ this.setState({ colorMode });
}
componentWillUnmount() {
+ this.prefersDarkQuery?.removeEventListener(
+ "change",
+ this.handleColorModeChange
+ );
global.removeEventListener("scroll", this.onWindowScroll);
global.removeEventListener("keydown", this.handleOnKeyDown);
+ if (this._onVisibilityChange) {
+ this.props.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
}
onWindowScroll() {
@@ -160,11 +218,79 @@ export class BaseContent extends React.PureComponent {
this.props.dispatch(ac.SetPref(pref, value));
}
+ renderWallpaperAttribution() {
+ const { wallpaperList } = this.props.Wallpapers;
+ const activeWallpaper =
+ this.props.Prefs.values[
+ `newtabWallpapers.wallpaper-${this.state.colorMode}`
+ ];
+ const selected = wallpaperList.find(wp => wp.title === activeWallpaper);
+ // make sure a wallpaper is selected and that the attribution also exists
+ if (!selected?.attribution) {
+ return null;
+ }
+
+ const { name, webpage } = selected.attribution;
+ if (activeWallpaper && wallpaperList && name.url) {
+ return (
+ <p
+ className={`wallpaper-attribution`}
+ key={name}
+ data-l10n-id="newtab-wallpaper-attribution"
+ data-l10n-args={JSON.stringify({
+ author_string: name.string,
+ author_url: name.url,
+ webpage_string: webpage.string,
+ webpage_url: webpage.url,
+ })}
+ >
+ <a data-l10n-name="name-link" href={name.url}>
+ {name.string}
+ </a>
+ <a data-l10n-name="webpage-link" href={webpage.url}>
+ {webpage.string}
+ </a>
+ </p>
+ );
+ }
+ return null;
+ }
+
+ async updateWallpaper() {
+ const prefs = this.props.Prefs.values;
+ const { wallpaperList } = this.props.Wallpapers;
+
+ if (wallpaperList) {
+ const lightWallpaper =
+ wallpaperList.find(
+ wp => wp.title === prefs["newtabWallpapers.wallpaper-light"]
+ ) || "";
+ const darkWallpaper =
+ wallpaperList.find(
+ wp => wp.title === prefs["newtabWallpapers.wallpaper-dark"]
+ ) || "";
+ global.document?.body.style.setProperty(
+ `--newtab-wallpaper-light`,
+ `url(${lightWallpaper?.wallpaperUrl || ""})`
+ );
+
+ global.document?.body.style.setProperty(
+ `--newtab-wallpaper-dark`,
+ `url(${darkWallpaper?.wallpaperUrl || ""})`
+ );
+ }
+ }
+
render() {
const { props } = this;
const { App } = props;
const { initialized, customizeMenuVisible } = App;
const prefs = props.Prefs.values;
+
+ const activeWallpaper =
+ prefs[`newtabWallpapers.wallpaper-${this.state.colorMode}`];
+ const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
+
const { pocketConfig } = prefs;
const isDiscoveryStream =
@@ -215,6 +341,9 @@ export class BaseContent extends React.PureComponent {
]
.filter(v => v)
.join(" ");
+ if (wallpapersEnabled) {
+ this.updateWallpaper();
+ }
return (
<div>
@@ -224,6 +353,8 @@ export class BaseContent extends React.PureComponent {
openPreferences={this.openPreferences}
setPref={this.setPref}
enabledSections={enabledSections}
+ wallpapersEnabled={wallpapersEnabled}
+ activeWallpaper={activeWallpaper}
pocketRegion={pocketRegion}
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
mayHaveSponsoredStories={mayHaveSponsoredStories}
@@ -252,6 +383,7 @@ export class BaseContent extends React.PureComponent {
<DiscoveryStreamBase
locale={props.App.locale}
mayHaveSponsoredStories={mayHaveSponsoredStories}
+ firstVisibleTimestamp={this.state.firstVisibleTimestamp}
/>
</ErrorBoundary>
) : (
@@ -259,6 +391,7 @@ export class BaseContent extends React.PureComponent {
)}
</div>
<ConfirmDialog />
+ {wallpapersEnabled && this.renderWallpaperAttribution()}
</main>
</div>
</div>
@@ -266,10 +399,15 @@ export class BaseContent extends React.PureComponent {
}
}
+BaseContent.defaultProps = {
+ document: global.document,
+};
+
export const Base = connect(state => ({
App: state.App,
Prefs: state.Prefs,
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
Search: state.Search,
+ Wallpapers: state.Wallpapers,
}))(_Base);
diff --git a/browser/components/newtab/content-src/components/Base/_Base.scss b/browser/components/newtab/content-src/components/Base/_Base.scss
index 1282173df5..a9141e0923 100644
--- a/browser/components/newtab/content-src/components/Base/_Base.scss
+++ b/browser/components/newtab/content-src/components/Base/_Base.scss
@@ -24,10 +24,17 @@
}
main {
- margin: auto;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
width: $wrapper-default-width;
padding: 0;
+ .vertical-center-wrapper {
+ margin: auto 0;
+ }
+
section {
margin-bottom: $section-spacing;
position: relative;
@@ -124,3 +131,32 @@ main {
}
}
}
+
+.wallpaper-attribution {
+ padding: 0 $section-horizontal-padding;
+ font-size: 14px;
+
+ &.theme-light {
+ display: inline-block;
+
+ @include dark-theme-only {
+ display: none;
+ }
+ }
+
+ &.theme-dark {
+ display: none;
+
+ @include dark-theme-only {
+ display: inline-block;
+ }
+ }
+
+ a {
+ color: var(--newtab-element-color);
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+}
diff --git a/browser/components/newtab/content-src/components/Card/Card.jsx b/browser/components/newtab/content-src/components/Card/Card.jsx
index 9d03377f1b..da5e0346d7 100644
--- a/browser/components/newtab/content-src/components/Card/Card.jsx
+++ b/browser/components/newtab/content-src/components/Card/Card.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { cardContextTypes } from "./types";
import { connect } from "react-redux";
import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton";
diff --git a/browser/components/newtab/content-src/components/Card/types.js b/browser/components/newtab/content-src/components/Card/types.mjs
index 0b17eea408..0b17eea408 100644
--- a/browser/components/newtab/content-src/components/Card/types.js
+++ b/browser/components/newtab/content-src/components/Card/types.mjs
diff --git a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
index 98bf88fbea..2046617ad6 100644
--- a/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
+++ b/browser/components/newtab/content-src/components/CollapsibleSection/CollapsibleSection.jsx
@@ -119,7 +119,7 @@ export class _CollapsibleSection extends React.PureComponent {
}
_CollapsibleSection.defaultProps = {
- document: global.document || {
+ document: globalThis.document || {
addEventListener: () => {},
removeEventListener: () => {},
visibilityState: "hidden",
diff --git a/browser/components/newtab/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx b/browser/components/newtab/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
index 4efd8c712e..ffcc6b62f4 100644
--- a/browser/components/newtab/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
+++ b/browser/components/newtab/content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { perfService as perfSvc } from "content-src/lib/perf-service";
import React from "react";
diff --git a/browser/components/newtab/content-src/components/ConfirmDialog/ConfirmDialog.jsx b/browser/components/newtab/content-src/components/ConfirmDialog/ConfirmDialog.jsx
index f69e540079..734f261b27 100644
--- a/browser/components/newtab/content-src/components/ConfirmDialog/ConfirmDialog.jsx
+++ b/browser/components/newtab/content-src/components/ConfirmDialog/ConfirmDialog.jsx
@@ -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/. */
-import { actionCreators as ac, actionTypes } from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes } from "common/Actions.mjs";
import { connect } from "react-redux";
import React from "react";
diff --git a/browser/components/newtab/content-src/components/ContextMenu/ContextMenu.jsx b/browser/components/newtab/content-src/components/ContextMenu/ContextMenu.jsx
index 5ea6a57f71..458f65e644 100644
--- a/browser/components/newtab/content-src/components/ContextMenu/ContextMenu.jsx
+++ b/browser/components/newtab/content-src/components/ContextMenu/ContextMenu.jsx
@@ -26,12 +26,12 @@ export class ContextMenu extends React.PureComponent {
componentDidMount() {
this.onShow();
setTimeout(() => {
- global.addEventListener("click", this.hideContext);
+ globalThis.addEventListener("click", this.hideContext);
}, 0);
}
componentWillUnmount() {
- global.removeEventListener("click", this.hideContext);
+ globalThis.removeEventListener("click", this.hideContext);
}
onClick(event) {
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 298dedcee5..1dd13fc965 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
@@ -3,8 +3,9 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
+import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
export class ContentSection extends React.PureComponent {
constructor(props) {
@@ -98,6 +99,9 @@ export class ContentSection extends React.PureComponent {
mayHaveRecentSaves,
openPreferences,
spocMessageVariant,
+ wallpapersEnabled,
+ activeWallpaper,
+ setPref,
} = this.props;
const {
topSitesEnabled,
@@ -111,6 +115,15 @@ export class ContentSection extends React.PureComponent {
return (
<div className="home-section">
+ {wallpapersEnabled && (
+ <div className="wallpapers-section">
+ <h2 data-l10n-id="newtab-wallpaper-title"></h2>
+ <WallpapersSection
+ setPref={setPref}
+ activeWallpaper={activeWallpaper}
+ />
+ </div>
+ )}
<div id="shortcuts-section" className="section">
<moz-toggle
id="shortcuts-toggle"
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
index 54dcd550c4..f1c723fed2 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/CustomizeMenu.jsx
@@ -2,7 +2,6 @@
* 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 { BackgroundsSection } from "content-src/components/CustomizeMenu/BackgroundsSection/BackgroundsSection";
import { ContentSection } from "content-src/components/CustomizeMenu/ContentSection/ContentSection";
import { connect } from "react-redux";
import React from "react";
@@ -62,11 +61,12 @@ export class _CustomizeMenu extends React.PureComponent {
data-l10n-id="newtab-custom-close-button"
ref={c => (this.closeButton = c)}
/>
- <BackgroundsSection />
<ContentSection
openPreferences={this.props.openPreferences}
setPref={this.props.setPref}
enabledSections={this.props.enabledSections}
+ wallpapersEnabled={this.props.wallpapersEnabled}
+ activeWallpaper={this.props.activeWallpaper}
pocketRegion={this.props.pocketRegion}
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
diff --git a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
index 579e455a3f..c20da5ce50 100644
--- a/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
+++ b/browser/components/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss
@@ -119,6 +119,10 @@
grid-row-gap: 32px;
padding: 0 16px;
+ .wallpapers-section h2 {
+ font-size: inherit;
+ }
+
.section {
moz-toggle {
margin-bottom: 10px;
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
index 3c31a5a29f..8b9d64dfc1 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { connect } from "react-redux";
import React from "react";
import { SimpleHashRouter } from "./SimpleHashRouter";
@@ -445,9 +442,9 @@ export class CollapseToggle extends React.PureComponent {
setBodyClass() {
if (this.renderAdmin && !this.state.collapsed) {
- global.document.body.classList.add("no-scroll");
+ globalThis.document.body.classList.add("no-scroll");
} else {
- global.document.body.classList.remove("no-scroll");
+ globalThis.document.body.classList.remove("no-scroll");
}
}
@@ -460,7 +457,7 @@ export class CollapseToggle extends React.PureComponent {
}
componentWillUnmount() {
- global.document.body.classList.remove("no-scroll");
+ globalThis.document.body.classList.remove("no-scroll");
}
render() {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/SimpleHashRouter.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/SimpleHashRouter.jsx
index 9c3fd8579c..bc7b0c42c5 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/SimpleHashRouter.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamAdmin/SimpleHashRouter.jsx
@@ -8,19 +8,19 @@ export class SimpleHashRouter extends React.PureComponent {
constructor(props) {
super(props);
this.onHashChange = this.onHashChange.bind(this);
- this.state = { hash: global.location.hash };
+ this.state = { hash: globalThis.location.hash };
}
onHashChange() {
- this.setState({ hash: global.location.hash });
+ this.setState({ hash: globalThis.location.hash });
}
componentWillMount() {
- global.addEventListener("hashchange", this.onHashChange);
+ globalThis.addEventListener("hashchange", this.onHashChange);
}
componentWillUnmount() {
- global.removeEventListener("hashchange", this.onHashChange);
+ globalThis.removeEventListener("hashchange", this.onHashChange);
}
render() {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
index 0f0ee51ab9..8b5826dd82 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
@@ -164,7 +164,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
privacyNoticeURL={component.properties.privacyNoticeURL}
/>
);
- case "CollectionCardGrid":
+ case "CollectionCardGrid": {
const { DiscoveryStream } = this.props;
return (
<CollectionCardGrid
@@ -178,6 +178,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
dispatch={this.props.dispatch}
/>
);
+ }
case "CardGrid":
return (
<CardGrid
@@ -200,6 +201,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
editorsPicksHeader={component.properties.editorsPicksHeader}
recentSavesEnabled={this.props.DiscoveryStream.recentSavesEnabled}
hideDescriptions={this.props.DiscoveryStream.hideDescriptions}
+ firstVisibleTimestamp={this.props.firstVisibleTimestamp}
/>
);
case "HorizontalRule":
@@ -384,6 +386,6 @@ export const DiscoveryStreamBase = connect(state => ({
DiscoveryStream: state.DiscoveryStream,
Prefs: state.Prefs,
Sections: state.Sections,
- document: global.document,
+ document: globalThis.document,
App: state.App,
}))(_DiscoveryStreamBase);
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 cf00361df2..2a9497d1b4 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -8,10 +8,7 @@ import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDi
import { TopicsWidget } from "../TopicsWidget/TopicsWidget.jsx";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import React, { useEffect, useState, useRef, useCallback } from "react";
import { connect, useSelector } from "react-redux";
const PREF_ONBOARDING_EXPERIENCE_DISMISSED =
@@ -31,7 +28,7 @@ export function DSSubHeader({ children }) {
);
}
-export function OnboardingExperience({ dispatch, windowObj = global }) {
+export function OnboardingExperience({ dispatch, windowObj = globalThis }) {
const [dismissed, setDismissed] = useState(false);
const [maxHeight, setMaxHeight] = useState(null);
const heightElement = useRef(null);
@@ -361,6 +358,7 @@ export class _CardGrid extends React.PureComponent {
url={rec.url}
id={rec.id}
shim={rec.shim}
+ fetchTimestamp={rec.fetchTimestamp}
type={this.props.type}
context={rec.context}
sponsor={rec.sponsor}
@@ -377,6 +375,7 @@ export class _CardGrid extends React.PureComponent {
ctaButtonVariant={ctaButtonVariant}
spocMessageVariant={spocMessageVariant}
recommendation_id={rec.recommendation_id}
+ firstVisibleTimestamp={this.props.firstVisibleTimestamp}
/>
)
);
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx
index d089a5c8ab..4f3f150a9b 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CollectionCardGrid/CollectionCardGrid.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { CardGrid } from "content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid";
import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss";
import { LinkMenuOptions } from "content-src/lib/link-menu-options";
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 f3e1eab503..b3d965530d 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { DSImage } from "../DSImage/DSImage.jsx";
import { DSLinkMenu } from "../DSLinkMenu/DSLinkMenu";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
@@ -198,6 +195,8 @@ export class _DSCard extends React.PureComponent {
...(this.props.shim && this.props.shim.click
? { shim: this.props.shim.click }
: {}),
+ fetchTimestamp: this.props.fetchTimestamp,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp,
},
})
);
@@ -245,6 +244,8 @@ export class _DSCard extends React.PureComponent {
...(this.props.shim && this.props.shim.save
? { shim: this.props.shim.save }
: {}),
+ fetchTimestamp: this.props.fetchTimestamp,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp,
},
})
);
@@ -441,10 +442,12 @@ export class _DSCard extends React.PureComponent {
? { shim: this.props.shim.impression }
: {}),
recommendation_id: this.props.recommendation_id,
+ fetchTimestamp: this.props.fetchTimestamp,
},
]}
dispatch={this.props.dispatch}
source={this.props.type}
+ firstVisibleTimestamp={this.props.firstVisibleTimestamp}
/>
</SafeAnchor>
{ctaButtonVariant === "variant-b" && (
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 6c0641cfc1..80af05c585 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
@@ -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/. */
-import { cardContextTypes } from "../../Card/types.js";
+import { cardContextTypes } from "../../Card/types.mjs";
import { SponsoredContentHighlight } from "../FeatureHighlight/SponsoredContentHighlight";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
index ff3886b407..ed90f68606 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import React from "react";
export class DSEmptyState extends React.PureComponent {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
index b75063940c..107adca4da 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
@@ -4,7 +4,7 @@
import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton";
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import React from "react";
export class DSLinkMenu extends React.PureComponent {
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 b251fb0401..2275f8b22b 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
@@ -3,10 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { ModalOverlayWrapper } from "content-src/components/ModalOverlay/ModalOverlay";
export class DSPrivacyModal extends React.PureComponent {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup.jsx
index b7e3205646..0a4d687c65 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSSignup/DSSignup.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
import { ContextMenuButton } from "content-src/components/ContextMenu/ContextMenuButton";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx
index 02a3326eb7..fc52decdf8 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/DSTextPromo.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss";
import { DSImage } from "../DSImage/DSImage.jsx";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
index 792be40ba3..c650453393 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
@@ -3,7 +3,7 @@
* 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";
+import { actionCreators as ac } from "common/Actions.mjs";
export function FeatureHighlight({
message,
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx
index 1062c3cade..43865c177c 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import React from "react";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText";
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 72ec94e1fe..b586730713 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import React from "react";
export class SafeAnchor extends React.PureComponent {
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx
index 1fe2343b94..59b44198a2 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx
@@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
import { connect } from "react-redux";
diff --git a/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx b/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
index 1eb4863271..9342fcd27a 100644
--- a/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { TOP_SITES_SOURCE } from "../TopSites/TopSitesConstants";
import React from "react";
@@ -100,7 +97,9 @@ export class ImpressionStats extends React.PureComponent {
type: this.props.flightId ? "spoc" : "organic",
...(link.shim ? { shim: link.shim } : {}),
recommendation_id: link.recommendation_id,
+ fetchTimestamp: link.fetchTimestamp,
})),
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp,
})
);
this.impressionCardGuids = cards.map(link => link.id);
@@ -244,8 +243,8 @@ export class ImpressionStats extends React.PureComponent {
}
ImpressionStats.defaultProps = {
- IntersectionObserver: global.IntersectionObserver,
- document: global.document,
+ IntersectionObserver: globalThis.IntersectionObserver,
+ document: globalThis.document,
rows: [],
source: "",
};
diff --git a/browser/components/newtab/content-src/components/LinkMenu/LinkMenu.jsx b/browser/components/newtab/content-src/components/LinkMenu/LinkMenu.jsx
index 650a03eb95..65b1f38623 100644
--- a/browser/components/newtab/content-src/components/LinkMenu/LinkMenu.jsx
+++ b/browser/components/newtab/content-src/components/LinkMenu/LinkMenu.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { connect } from "react-redux";
import { ContextMenu } from "content-src/components/ContextMenu/ContextMenu";
import { LinkMenuOptions } from "content-src/lib/link-menu-options";
diff --git a/browser/components/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx b/browser/components/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx
index fdfdf22db2..5d902b43ba 100644
--- a/browser/components/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx
+++ b/browser/components/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx
@@ -53,4 +53,4 @@ export class ModalOverlayWrapper extends React.PureComponent {
}
}
-ModalOverlayWrapper.defaultProps = { document: global.document };
+ModalOverlayWrapper.defaultProps = { document: globalThis.document };
diff --git a/browser/components/newtab/content-src/components/Search/Search.jsx b/browser/components/newtab/content-src/components/Search/Search.jsx
index 64308963c9..ef7a3757d3 100644
--- a/browser/components/newtab/content-src/components/Search/Search.jsx
+++ b/browser/components/newtab/content-src/components/Search/Search.jsx
@@ -4,10 +4,7 @@
/* globals ContentSearchUIController, ContentSearchHandoffUIController */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { connect } from "react-redux";
import { IS_NEWTAB } from "content-src/lib/constants";
import React from "react";
diff --git a/browser/components/newtab/content-src/components/Sections/Sections.jsx b/browser/components/newtab/content-src/components/Sections/Sections.jsx
index e72e9145ad..01b50f6918 100644
--- a/browser/components/newtab/content-src/components/Sections/Sections.jsx
+++ b/browser/components/newtab/content-src/components/Sections/Sections.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { Card, PlaceholderCard } from "content-src/components/Card/Card";
import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
import { ComponentPerfTimer } from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
@@ -33,7 +30,7 @@ export class Section extends React.PureComponent {
let cardsPerRow = CARDS_PER_ROW_DEFAULT;
if (
props.compactCards &&
- global.matchMedia(`(min-width: 1072px)`).matches
+ globalThis.matchMedia(`(min-width: 1072px)`).matches
) {
// If the section has compact cards and the viewport is wide enough, we show
// 4 columns instead of 3.
@@ -326,7 +323,7 @@ export class Section extends React.PureComponent {
}
Section.defaultProps = {
- document: global.document,
+ document: globalThis.document,
rows: [],
emptyState: {},
pref: {},
diff --git a/browser/components/newtab/content-src/components/TopSites/SearchShortcutsForm.jsx b/browser/components/newtab/content-src/components/TopSites/SearchShortcutsForm.jsx
index 4324c019f6..2d504c52ab 100644
--- a/browser/components/newtab/content-src/components/TopSites/SearchShortcutsForm.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/SearchShortcutsForm.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import React from "react";
import { TOP_SITES_SOURCE } from "./TopSitesConstants";
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
index c0932104af..3d63398e0e 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import {
MIN_RICH_FAVICON_SIZE,
MIN_SMALL_FAVICON_SIZE,
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx b/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
index 7dd61bdc93..9ca8991735 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { A11yLinkButton } from "content-src/components/A11yLinkButton/A11yLinkButton";
import React from "react";
import { TOP_SITES_SOURCE } from "./TopSitesConstants";
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSiteImpressionWrapper.jsx b/browser/components/newtab/content-src/components/TopSites/TopSiteImpressionWrapper.jsx
index 580809dd57..b654a803c7 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSiteImpressionWrapper.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSiteImpressionWrapper.jsx
@@ -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/. */
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import React from "react";
const VISIBLE = "visible";
@@ -142,8 +142,8 @@ export class TopSiteImpressionWrapper extends React.PureComponent {
}
TopSiteImpressionWrapper.defaultProps = {
- IntersectionObserver: global.IntersectionObserver,
- document: global.document,
+ IntersectionObserver: globalThis.IntersectionObserver,
+ document: globalThis.document,
actionType: null,
tile: null,
};
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
index ba7676fd10..d9a12aa97d 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
@@ -2,10 +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/. */
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { MIN_RICH_FAVICON_SIZE, TOP_SITES_SOURCE } from "./TopSitesConstants";
import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
import { ComponentPerfTimer } from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
@@ -93,7 +90,7 @@ export class _TopSites extends React.PureComponent {
// We hide 2 sites per row when not in the wide layout.
let sitesPerRow = TOP_SITES_MAX_SITES_PER_ROW;
// $break-point-widest = 1072px (from _variables.scss)
- if (!global.matchMedia(`(min-width: 1072px)`).matches) {
+ if (!globalThis.matchMedia(`(min-width: 1072px)`).matches) {
sitesPerRow -= 2;
}
return this.props.TopSites.rows.slice(
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSitesConstants.js b/browser/components/newtab/content-src/components/TopSites/TopSitesConstants.mjs
index f488896238..f488896238 100644
--- a/browser/components/newtab/content-src/components/TopSites/TopSitesConstants.js
+++ b/browser/components/newtab/content-src/components/TopSites/TopSitesConstants.mjs
diff --git a/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx b/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx
new file mode 100644
index 0000000000..0b51a146f5
--- /dev/null
+++ b/browser/components/newtab/content-src/components/WallpapersSection/WallpapersSection.jsx
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import React from "react";
+import { connect } from "react-redux";
+
+export class _WallpapersSection extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleReset = this.handleReset.bind(this);
+ this.prefersHighContrastQuery = null;
+ this.prefersDarkQuery = null;
+ }
+
+ componentDidMount() {
+ this.prefersDarkQuery = globalThis.matchMedia(
+ "(prefers-color-scheme: dark)"
+ );
+ }
+
+ handleChange(event) {
+ const { id } = event.target;
+ const prefs = this.props.Prefs.values;
+ const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
+ this.props.setPref(`newtabWallpapers.wallpaper-${colorMode}`, id);
+ // bug 1892095
+ if (
+ prefs["newtabWallpapers.wallpaper-dark"] === "" &&
+ colorMode === "light"
+ ) {
+ this.props.setPref(
+ "newtabWallpapers.wallpaper-dark",
+ id.replace("light", "dark")
+ );
+ }
+
+ if (
+ prefs["newtabWallpapers.wallpaper-light"] === "" &&
+ colorMode === "dark"
+ ) {
+ this.props.setPref(
+ `newtabWallpapers.wallpaper-light`,
+ id.replace("dark", "light")
+ );
+ }
+ }
+
+ handleReset() {
+ const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
+ this.props.setPref(`newtabWallpapers.wallpaper-${colorMode}`, "");
+ }
+
+ render() {
+ const { wallpaperList } = this.props.Wallpapers;
+ const { activeWallpaper } = this.props;
+ return (
+ <div>
+ <fieldset className="wallpaper-list">
+ {wallpaperList.map(({ title, theme, fluent_id }) => {
+ return (
+ <>
+ <input
+ onChange={this.handleChange}
+ type="radio"
+ name={`wallpaper-${title}`}
+ id={title}
+ value={title}
+ checked={title === activeWallpaper}
+ aria-checked={title === activeWallpaper}
+ className={`wallpaper-input theme-${theme} ${title}`}
+ />
+ <label
+ htmlFor={title}
+ className="sr-only"
+ data-l10n-id={fluent_id}
+ >
+ {fluent_id}
+ </label>
+ </>
+ );
+ })}
+ </fieldset>
+ <button
+ className="wallpapers-reset"
+ onClick={this.handleReset}
+ data-l10n-id="newtab-wallpaper-reset"
+ />
+ </div>
+ );
+ }
+}
+
+export const WallpapersSection = connect(state => {
+ return {
+ Wallpapers: state.Wallpapers,
+ Prefs: state.Prefs,
+ };
+})(_WallpapersSection);
diff --git a/browser/components/newtab/content-src/components/WallpapersSection/_WallpapersSection.scss b/browser/components/newtab/content-src/components/WallpapersSection/_WallpapersSection.scss
new file mode 100644
index 0000000000..689661750b
--- /dev/null
+++ b/browser/components/newtab/content-src/components/WallpapersSection/_WallpapersSection.scss
@@ -0,0 +1,87 @@
+.wallpaper-list {
+ display: grid;
+ gap: 16px;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-rows: 86px;
+ margin: 16px 0;
+ padding: 0;
+ border: none;
+
+ .wallpaper-input,
+ .sr-only {
+ &.theme-light {
+ display: inline-block;
+
+ @include dark-theme-only {
+ display: none;
+ }
+ }
+
+ &.theme-dark {
+ display: none;
+
+ @include dark-theme-only {
+ display: inline-block;
+ }
+ }
+ }
+
+ .wallpaper-input {
+ appearance: none;
+ margin: 0;
+ padding: 0;
+ height: 86px;
+ width: 100%;
+ box-shadow: $shadow-secondary;
+ border-radius: 8px;
+ background-clip: content-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ cursor: pointer;
+ outline: 2px solid transparent;
+
+ $wallpapers: dark-landscape, dark-color, dark-mountain, dark-panda, dark-sky, dark-beach, light-beach, light-color, light-landscape, light-mountain, light-panda, light-sky;
+
+ @each $wallpaper in $wallpapers {
+ &.#{$wallpaper} {
+ background-image: url('chrome://activity-stream/content/data/content/assets/wallpapers/#{$wallpaper}.avif')
+ }
+ }
+
+ &:checked {
+ outline-color: var(--color-accent-primary-active);
+ }
+
+ &:focus-visible {
+ outline-color: var(--newtab-primary-action-background);
+ }
+
+ &:hover {
+ filter: brightness(55%);
+ outline-color: transparent;
+ }
+ }
+
+ // visually hide label, but still read by screen readers
+ .sr-only {
+ opacity: 0;
+ overflow: hidden;
+ position: absolute;
+ pointer-events: none;
+ }
+}
+
+.wallpapers-reset {
+ background: none;
+ border: none;
+ text-decoration: underline;
+ margin-inline: auto;
+ display: block;
+ font-size: var(--font-size-small);
+ color: var(--newtab-text-primary-color);
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
diff --git a/browser/components/newtab/content-src/lib/constants.js b/browser/components/newtab/content-src/lib/constants.js
deleted file mode 100644
index 2c96160b4b..0000000000
--- a/browser/components/newtab/content-src/lib/constants.js
+++ /dev/null
@@ -1,38 +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/. */
-
-export const IS_NEWTAB =
- global.document && global.document.documentURI === "about:newtab";
-export const NEWTAB_DARK_THEME = {
- ntp_background: {
- r: 42,
- g: 42,
- b: 46,
- a: 1,
- },
- ntp_card_background: {
- r: 66,
- g: 65,
- b: 77,
- a: 1,
- },
- ntp_text: {
- r: 249,
- g: 249,
- b: 250,
- a: 1,
- },
- sidebar: {
- r: 56,
- g: 56,
- b: 61,
- a: 1,
- },
- sidebar_text: {
- r: 249,
- g: 249,
- b: 250,
- a: 1,
- },
-};
diff --git a/browser/components/newtab/content-src/lib/constants.mjs b/browser/components/newtab/content-src/lib/constants.mjs
new file mode 100644
index 0000000000..4f07a77e29
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/constants.mjs
@@ -0,0 +1,38 @@
+/* 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/. */
+
+export const IS_NEWTAB =
+ globalThis.document && globalThis.document.documentURI === "about:newtab";
+export const NEWTAB_DARK_THEME = {
+ ntp_background: {
+ r: 42,
+ g: 42,
+ b: 46,
+ a: 1,
+ },
+ ntp_card_background: {
+ r: 66,
+ g: 65,
+ b: 77,
+ a: 1,
+ },
+ ntp_text: {
+ r: 249,
+ g: 249,
+ b: 250,
+ a: 1,
+ },
+ sidebar: {
+ r: 56,
+ g: 56,
+ b: 61,
+ a: 1,
+ },
+ sidebar_text: {
+ r: 249,
+ g: 249,
+ b: 250,
+ a: 1,
+ },
+};
diff --git a/browser/components/newtab/content-src/lib/detect-user-session-start.js b/browser/components/newtab/content-src/lib/detect-user-session-start.js
deleted file mode 100644
index 43aa388967..0000000000
--- a/browser/components/newtab/content-src/lib/detect-user-session-start.js
+++ /dev/null
@@ -1,82 +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/. */
-
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
-import { perfService as perfSvc } from "content-src/lib/perf-service";
-
-const VISIBLE = "visible";
-const VISIBILITY_CHANGE_EVENT = "visibilitychange";
-
-export class DetectUserSessionStart {
- constructor(store, options = {}) {
- this._store = store;
- // Overrides for testing
- this.document = options.document || global.document;
- this._perfService = options.perfService || perfSvc;
- this._onVisibilityChange = this._onVisibilityChange.bind(this);
- }
-
- /**
- * sendEventOrAddListener - Notify immediately if the page is already visible,
- * or else set up a listener for when visibility changes.
- * This is needed for accurate session tracking for telemetry,
- * because tabs are pre-loaded.
- */
- sendEventOrAddListener() {
- if (this.document.visibilityState === VISIBLE) {
- // If the document is already visible, to the user, send a notification
- // immediately that a session has started.
- this._sendEvent();
- } else {
- // If the document is not visible, listen for when it does become visible.
- this.document.addEventListener(
- VISIBILITY_CHANGE_EVENT,
- this._onVisibilityChange
- );
- }
- }
-
- /**
- * _sendEvent - Sends a message to the main process to indicate the current
- * tab is now visible to the user, includes the
- * visibility_event_rcvd_ts time in ms from the UNIX epoch.
- */
- _sendEvent() {
- this._perfService.mark("visibility_event_rcvd_ts");
-
- try {
- let visibility_event_rcvd_ts =
- this._perfService.getMostRecentAbsMarkStartByName(
- "visibility_event_rcvd_ts"
- );
-
- this._store.dispatch(
- ac.AlsoToMain({
- type: at.SAVE_SESSION_PERF_DATA,
- data: { visibility_event_rcvd_ts },
- })
- );
- } catch (ex) {
- // If this failed, it's likely because the `privacy.resistFingerprinting`
- // pref is true. We should at least not blow up.
- }
- }
-
- /**
- * _onVisibilityChange - If the visibility has changed to visible, sends a notification
- * and removes the event listener. This should only be called once per tab.
- */
- _onVisibilityChange() {
- if (this.document.visibilityState === VISIBLE) {
- this._sendEvent();
- this.document.removeEventListener(
- VISIBILITY_CHANGE_EVENT,
- this._onVisibilityChange
- );
- }
- }
-}
diff --git a/browser/components/newtab/content-src/lib/detect-user-session-start.mjs b/browser/components/newtab/content-src/lib/detect-user-session-start.mjs
new file mode 100644
index 0000000000..d4c36efd4a
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/detect-user-session-start.mjs
@@ -0,0 +1,82 @@
+/* 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 {
+ actionCreators as ac,
+ actionTypes as at,
+} from "../../common/Actions.mjs";
+import { perfService as perfSvc } from "./perf-service.mjs";
+
+const VISIBLE = "visible";
+const VISIBILITY_CHANGE_EVENT = "visibilitychange";
+
+export class DetectUserSessionStart {
+ constructor(store, options = {}) {
+ this._store = store;
+ // Overrides for testing
+ this.document = options.document || globalThis.document;
+ this._perfService = options.perfService || perfSvc;
+ this._onVisibilityChange = this._onVisibilityChange.bind(this);
+ }
+
+ /**
+ * sendEventOrAddListener - Notify immediately if the page is already visible,
+ * or else set up a listener for when visibility changes.
+ * This is needed for accurate session tracking for telemetry,
+ * because tabs are pre-loaded.
+ */
+ sendEventOrAddListener() {
+ if (this.document.visibilityState === VISIBLE) {
+ // If the document is already visible, to the user, send a notification
+ // immediately that a session has started.
+ this._sendEvent();
+ } else {
+ // If the document is not visible, listen for when it does become visible.
+ this.document.addEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ }
+
+ /**
+ * _sendEvent - Sends a message to the main process to indicate the current
+ * tab is now visible to the user, includes the
+ * visibility_event_rcvd_ts time in ms from the UNIX epoch.
+ */
+ _sendEvent() {
+ this._perfService.mark("visibility_event_rcvd_ts");
+
+ try {
+ let visibility_event_rcvd_ts =
+ this._perfService.getMostRecentAbsMarkStartByName(
+ "visibility_event_rcvd_ts"
+ );
+
+ this._store.dispatch(
+ ac.AlsoToMain({
+ type: at.SAVE_SESSION_PERF_DATA,
+ data: { visibility_event_rcvd_ts },
+ })
+ );
+ } catch (ex) {
+ // If this failed, it's likely because the `privacy.resistFingerprinting`
+ // pref is true. We should at least not blow up.
+ }
+ }
+
+ /**
+ * _onVisibilityChange - If the visibility has changed to visible, sends a notification
+ * and removes the event listener. This should only be called once per tab.
+ */
+ _onVisibilityChange() {
+ if (this.document.visibilityState === VISIBLE) {
+ this._sendEvent();
+ this.document.removeEventListener(
+ VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
+ }
+ }
+}
diff --git a/browser/components/newtab/content-src/lib/init-store.js b/browser/components/newtab/content-src/lib/init-store.js
deleted file mode 100644
index f0ab2db86a..0000000000
--- a/browser/components/newtab/content-src/lib/init-store.js
+++ /dev/null
@@ -1,140 +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/. */
-
-/* eslint-env mozilla/remote-page */
-
-import {
- actionCreators as ac,
- actionTypes as at,
- actionUtils as au,
-} from "common/Actions.sys.mjs";
-import { applyMiddleware, combineReducers, createStore } from "redux";
-
-export const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
-export const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
-export const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
-
-/**
- * A higher-order function which returns a reducer that, on MERGE_STORE action,
- * will return the action.data object merged into the previous state.
- *
- * For all other actions, it merely calls mainReducer.
- *
- * Because we want this to merge the entire state object, it's written as a
- * higher order function which takes the main reducer (itself often a call to
- * combineReducers) as a parameter.
- *
- * @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
- * @return {function} a reducer that, on MERGE_STORE_ACTION action,
- * will return the action.data object merged
- * into the previous state, and the result
- * of calling mainReducer otherwise.
- */
-function mergeStateReducer(mainReducer) {
- return (prevState, action) => {
- if (action.type === MERGE_STORE_ACTION) {
- return { ...prevState, ...action.data };
- }
-
- return mainReducer(prevState, action);
- };
-}
-
-/**
- * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
- */
-const messageMiddleware = () => next => action => {
- const skipLocal = action.meta && action.meta.skipLocal;
- if (au.isSendToMain(action)) {
- RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
- }
- if (!skipLocal) {
- next(action);
- }
-};
-
-export const rehydrationMiddleware = ({ getState }) => {
- // NB: The parameter here is MiddlewareAPI which looks like a Store and shares
- // the same getState, so attached properties are accessible from the store.
- getState.didRehydrate = false;
- getState.didRequestInitialState = false;
- return next => action => {
- if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
- // Startup messages can be safely ignored by the about:home document
- // stored in the startup cache.
- if (
- window.__FROM_STARTUP_CACHE__ &&
- action.meta &&
- action.meta.isStartup
- ) {
- return null;
- }
- return next(action);
- }
-
- const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
- const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
-
- if (isRehydrationRequest) {
- getState.didRequestInitialState = true;
- return next(action);
- }
-
- if (isMergeStoreAction) {
- getState.didRehydrate = true;
- return next(action);
- }
-
- // If init happened after our request was made, we need to re-request
- if (getState.didRequestInitialState && action.type === at.INIT) {
- return next(ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST }));
- }
-
- if (
- au.isBroadcastToContent(action) ||
- au.isSendToOneContent(action) ||
- au.isSendToPreloaded(action)
- ) {
- // Note that actions received before didRehydrate will not be dispatched
- // because this could negatively affect preloading and the the state
- // will be replaced by rehydration anyway.
- return null;
- }
-
- return next(action);
- };
-};
-
-/**
- * initStore - Create a store and listen for incoming actions
- *
- * @param {object} reducers An object containing Redux reducers
- * @param {object} intialState (optional) The initial state of the store, if desired
- * @return {object} A redux store
- */
-export function initStore(reducers, initialState) {
- const store = createStore(
- mergeStateReducer(combineReducers(reducers)),
- initialState,
- global.RPMAddMessageListener &&
- applyMiddleware(rehydrationMiddleware, messageMiddleware)
- );
-
- if (global.RPMAddMessageListener) {
- global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
- try {
- store.dispatch(msg.data);
- } catch (ex) {
- console.error("Content msg:", msg, "Dispatch error: ", ex);
- dump(
- `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${
- ex.stack
- }`
- );
- }
- });
- }
-
- return store;
-}
diff --git a/browser/components/newtab/content-src/lib/init-store.mjs b/browser/components/newtab/content-src/lib/init-store.mjs
new file mode 100644
index 0000000000..85b3b0b470
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/init-store.mjs
@@ -0,0 +1,143 @@
+/* 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/. */
+
+/* eslint-env mozilla/remote-page */
+
+import {
+ actionCreators as ac,
+ actionTypes as at,
+ actionUtils as au,
+} from "../../common/Actions.mjs";
+// We disable import checking here as redux is installed via the npm packages
+// at the newtab level, rather than in the top-level package.json.
+// eslint-disable-next-line import/no-unresolved
+import { applyMiddleware, combineReducers, createStore } from "redux";
+
+export const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
+export const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
+export const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
+
+/**
+ * A higher-order function which returns a reducer that, on MERGE_STORE action,
+ * will return the action.data object merged into the previous state.
+ *
+ * For all other actions, it merely calls mainReducer.
+ *
+ * Because we want this to merge the entire state object, it's written as a
+ * higher order function which takes the main reducer (itself often a call to
+ * combineReducers) as a parameter.
+ *
+ * @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
+ * @return {function} a reducer that, on MERGE_STORE_ACTION action,
+ * will return the action.data object merged
+ * into the previous state, and the result
+ * of calling mainReducer otherwise.
+ */
+function mergeStateReducer(mainReducer) {
+ return (prevState, action) => {
+ if (action.type === MERGE_STORE_ACTION) {
+ return { ...prevState, ...action.data };
+ }
+
+ return mainReducer(prevState, action);
+ };
+}
+
+/**
+ * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
+ */
+const messageMiddleware = () => next => action => {
+ const skipLocal = action.meta && action.meta.skipLocal;
+ if (au.isSendToMain(action)) {
+ RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
+ }
+ if (!skipLocal) {
+ next(action);
+ }
+};
+
+export const rehydrationMiddleware = ({ getState }) => {
+ // NB: The parameter here is MiddlewareAPI which looks like a Store and shares
+ // the same getState, so attached properties are accessible from the store.
+ getState.didRehydrate = false;
+ getState.didRequestInitialState = false;
+ return next => action => {
+ if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
+ // Startup messages can be safely ignored by the about:home document
+ // stored in the startup cache.
+ if (
+ window.__FROM_STARTUP_CACHE__ &&
+ action.meta &&
+ action.meta.isStartup
+ ) {
+ return null;
+ }
+ return next(action);
+ }
+
+ const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
+ const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
+
+ if (isRehydrationRequest) {
+ getState.didRequestInitialState = true;
+ return next(action);
+ }
+
+ if (isMergeStoreAction) {
+ getState.didRehydrate = true;
+ return next(action);
+ }
+
+ // If init happened after our request was made, we need to re-request
+ if (getState.didRequestInitialState && action.type === at.INIT) {
+ return next(ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+ }
+
+ if (
+ au.isBroadcastToContent(action) ||
+ au.isSendToOneContent(action) ||
+ au.isSendToPreloaded(action)
+ ) {
+ // Note that actions received before didRehydrate will not be dispatched
+ // because this could negatively affect preloading and the the state
+ // will be replaced by rehydration anyway.
+ return null;
+ }
+
+ return next(action);
+ };
+};
+
+/**
+ * initStore - Create a store and listen for incoming actions
+ *
+ * @param {object} reducers An object containing Redux reducers
+ * @param {object} intialState (optional) The initial state of the store, if desired
+ * @return {object} A redux store
+ */
+export function initStore(reducers, initialState) {
+ const store = createStore(
+ mergeStateReducer(combineReducers(reducers)),
+ initialState,
+ globalThis.RPMAddMessageListener &&
+ applyMiddleware(rehydrationMiddleware, messageMiddleware)
+ );
+
+ if (globalThis.RPMAddMessageListener) {
+ globalThis.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
+ try {
+ store.dispatch(msg.data);
+ } catch (ex) {
+ console.error("Content msg:", msg, "Dispatch error: ", ex);
+ dump(
+ `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${
+ ex.stack
+ }`
+ );
+ }
+ });
+ }
+
+ return store;
+}
diff --git a/browser/components/newtab/content-src/lib/link-menu-options.js b/browser/components/newtab/content-src/lib/link-menu-options.js
deleted file mode 100644
index 12e47259c1..0000000000
--- a/browser/components/newtab/content-src/lib/link-menu-options.js
+++ /dev/null
@@ -1,309 +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/. */
-
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
-
-const _OpenInPrivateWindow = site => ({
- id: "newtab-menu-open-new-private-window",
- icon: "new-window-private",
- action: ac.OnlyToMain({
- type: at.OPEN_PRIVATE_WINDOW,
- data: { url: site.url, referrer: site.referrer },
- }),
- userEvent: "OPEN_PRIVATE_WINDOW",
-});
-
-/**
- * List of functions that return items that can be included as menu options in a
- * LinkMenu. All functions take the site as the first parameter, and optionally
- * the index of the site.
- */
-export const LinkMenuOptions = {
- Separator: () => ({ type: "separator" }),
- EmptyItem: () => ({ type: "empty" }),
- ShowPrivacyInfo: () => ({
- id: "newtab-menu-show-privacy-info",
- icon: "info",
- action: {
- type: at.SHOW_PRIVACY_INFO,
- },
- userEvent: "SHOW_PRIVACY_INFO",
- }),
- AboutSponsored: site => ({
- id: "newtab-menu-show-privacy-info",
- icon: "info",
- action: ac.AlsoToMain({
- type: at.ABOUT_SPONSORED_TOP_SITES,
- data: {
- advertiser_name: (site.label || site.hostname).toLocaleLowerCase(),
- position: site.sponsored_position,
- tile_id: site.sponsored_tile_id,
- },
- }),
- userEvent: "TOPSITE_SPONSOR_INFO",
- }),
- RemoveBookmark: site => ({
- id: "newtab-menu-remove-bookmark",
- icon: "bookmark-added",
- action: ac.AlsoToMain({
- type: at.DELETE_BOOKMARK_BY_ID,
- data: site.bookmarkGuid,
- }),
- userEvent: "BOOKMARK_DELETE",
- }),
- AddBookmark: site => ({
- id: "newtab-menu-bookmark",
- icon: "bookmark-hollow",
- action: ac.AlsoToMain({
- type: at.BOOKMARK_URL,
- data: { url: site.url, title: site.title, type: site.type },
- }),
- userEvent: "BOOKMARK_ADD",
- }),
- OpenInNewWindow: site => ({
- id: "newtab-menu-open-new-window",
- icon: "new-window",
- action: ac.AlsoToMain({
- type: at.OPEN_NEW_WINDOW,
- data: {
- referrer: site.referrer,
- typedBonus: site.typedBonus,
- url: site.url,
- sponsored_tile_id: site.sponsored_tile_id,
- },
- }),
- userEvent: "OPEN_NEW_WINDOW",
- }),
- // This blocks the url for regular stories,
- // but also sends a message to DiscoveryStream with flight_id.
- // If DiscoveryStream sees this message for a flight_id
- // it also blocks it on the flight_id.
- BlockUrl: (site, index, eventSource) => {
- return LinkMenuOptions.BlockUrls([site], index, eventSource);
- },
- // Same as BlockUrl, cept can work on an array of sites.
- BlockUrls: (tiles, pos, eventSource) => ({
- id: "newtab-menu-dismiss",
- icon: "dismiss",
- action: ac.AlsoToMain({
- type: at.BLOCK_URL,
- data: tiles.map(site => ({
- url: site.original_url || site.open_url || site.url,
- // pocket_id is only for pocket stories being in highlights, and then dismissed.
- pocket_id: site.pocket_id,
- // used by PlacesFeed and TopSitesFeed for sponsored top sites blocking.
- isSponsoredTopSite: site.sponsored_position,
- ...(site.flight_id ? { flight_id: site.flight_id } : {}),
- // If not sponsored, hostname could be anything (Cat3 Data!).
- // So only put in advertiser_name for sponsored topsites.
- ...(site.sponsored_position
- ? {
- advertiser_name: (
- site.label || site.hostname
- )?.toLocaleLowerCase(),
- }
- : {}),
- position: pos,
- ...(site.sponsored_tile_id ? { tile_id: site.sponsored_tile_id } : {}),
- is_pocket_card: site.type === "CardGrid",
- })),
- }),
- impression: ac.ImpressionStats({
- source: eventSource,
- block: 0,
- tiles: tiles.map((site, index) => ({
- id: site.guid,
- pos: pos + index,
- ...(site.shim && site.shim.delete ? { shim: site.shim.delete } : {}),
- })),
- }),
- userEvent: "BLOCK",
- }),
-
- // This is an option for web extentions which will result in remove items from
- // memory and notify the web extenion, rather than using the built-in block list.
- WebExtDismiss: (site, index, eventSource) => ({
- id: "menu_action_webext_dismiss",
- string_id: "newtab-menu-dismiss",
- icon: "dismiss",
- action: ac.WebExtEvent(at.WEBEXT_DISMISS, {
- source: eventSource,
- url: site.url,
- action_position: index,
- }),
- }),
- DeleteUrl: (site, index, eventSource, isEnabled, siteInfo) => ({
- id: "newtab-menu-delete-history",
- icon: "delete",
- action: {
- type: at.DIALOG_OPEN,
- data: {
- onConfirm: [
- ac.AlsoToMain({
- type: at.DELETE_HISTORY_URL,
- data: {
- url: site.url,
- pocket_id: site.pocket_id,
- forceBlock: site.bookmarkGuid,
- },
- }),
- ac.UserEvent(
- Object.assign(
- { event: "DELETE", source: eventSource, action_position: index },
- siteInfo
- )
- ),
- ],
- eventSource,
- body_string_id: [
- "newtab-confirm-delete-history-p1",
- "newtab-confirm-delete-history-p2",
- ],
- confirm_button_string_id: "newtab-topsites-delete-history-button",
- cancel_button_string_id: "newtab-topsites-cancel-button",
- icon: "modal-delete",
- },
- },
- userEvent: "DIALOG_OPEN",
- }),
- ShowFile: site => ({
- id: "newtab-menu-show-file",
- icon: "search",
- action: ac.OnlyToMain({
- type: at.SHOW_DOWNLOAD_FILE,
- data: { url: site.url },
- }),
- }),
- OpenFile: site => ({
- id: "newtab-menu-open-file",
- icon: "open-file",
- action: ac.OnlyToMain({
- type: at.OPEN_DOWNLOAD_FILE,
- data: { url: site.url },
- }),
- }),
- CopyDownloadLink: site => ({
- id: "newtab-menu-copy-download-link",
- icon: "copy",
- action: ac.OnlyToMain({
- type: at.COPY_DOWNLOAD_LINK,
- data: { url: site.url },
- }),
- }),
- GoToDownloadPage: site => ({
- id: "newtab-menu-go-to-download-page",
- icon: "download",
- action: ac.OnlyToMain({
- type: at.OPEN_LINK,
- data: { url: site.referrer },
- }),
- disabled: !site.referrer,
- }),
- RemoveDownload: site => ({
- id: "newtab-menu-remove-download",
- icon: "delete",
- action: ac.OnlyToMain({
- type: at.REMOVE_DOWNLOAD_FILE,
- data: { url: site.url },
- }),
- }),
- PinTopSite: (site, index) => ({
- id: "newtab-menu-pin",
- icon: "pin",
- action: ac.AlsoToMain({
- type: at.TOP_SITES_PIN,
- data: {
- site,
- index,
- },
- }),
- userEvent: "PIN",
- }),
- UnpinTopSite: site => ({
- id: "newtab-menu-unpin",
- icon: "unpin",
- action: ac.AlsoToMain({
- type: at.TOP_SITES_UNPIN,
- data: { site: { url: site.url } },
- }),
- userEvent: "UNPIN",
- }),
- SaveToPocket: (site, index, eventSource = "CARDGRID") => ({
- id: "newtab-menu-save-to-pocket",
- icon: "pocket-save",
- action: ac.AlsoToMain({
- type: at.SAVE_TO_POCKET,
- data: {
- site: { url: site.url, title: site.title },
- },
- }),
- impression: ac.ImpressionStats({
- source: eventSource,
- pocket: 0,
- tiles: [
- {
- id: site.guid,
- pos: index,
- ...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
- },
- ],
- }),
- userEvent: "SAVE_TO_POCKET",
- }),
- DeleteFromPocket: site => ({
- id: "newtab-menu-delete-pocket",
- icon: "pocket-delete",
- action: ac.AlsoToMain({
- type: at.DELETE_FROM_POCKET,
- data: { pocket_id: site.pocket_id },
- }),
- userEvent: "DELETE_FROM_POCKET",
- }),
- ArchiveFromPocket: site => ({
- id: "newtab-menu-archive-pocket",
- icon: "pocket-archive",
- action: ac.AlsoToMain({
- type: at.ARCHIVE_FROM_POCKET,
- data: { pocket_id: site.pocket_id },
- }),
- userEvent: "ARCHIVE_FROM_POCKET",
- }),
- EditTopSite: (site, index) => ({
- id: "newtab-menu-edit-topsites",
- icon: "edit",
- action: {
- type: at.TOP_SITES_EDIT,
- data: { index },
- },
- }),
- CheckBookmark: site =>
- site.bookmarkGuid
- ? LinkMenuOptions.RemoveBookmark(site)
- : LinkMenuOptions.AddBookmark(site),
- CheckPinTopSite: (site, index) =>
- site.isPinned
- ? LinkMenuOptions.UnpinTopSite(site)
- : LinkMenuOptions.PinTopSite(site, index),
- CheckSavedToPocket: (site, index, source) =>
- site.pocket_id
- ? LinkMenuOptions.DeleteFromPocket(site)
- : LinkMenuOptions.SaveToPocket(site, index, source),
- CheckBookmarkOrArchive: site =>
- site.pocket_id
- ? LinkMenuOptions.ArchiveFromPocket(site)
- : LinkMenuOptions.CheckBookmark(site),
- CheckArchiveFromPocket: site =>
- site.pocket_id
- ? LinkMenuOptions.ArchiveFromPocket(site)
- : LinkMenuOptions.EmptyItem(),
- CheckDeleteFromPocket: site =>
- site.pocket_id
- ? LinkMenuOptions.DeleteFromPocket(site)
- : LinkMenuOptions.EmptyItem(),
- OpenInPrivateWindow: (site, index, eventSource, isEnabled) =>
- isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem(),
-};
diff --git a/browser/components/newtab/content-src/lib/link-menu-options.mjs b/browser/components/newtab/content-src/lib/link-menu-options.mjs
new file mode 100644
index 0000000000..f10a5e34c6
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/link-menu-options.mjs
@@ -0,0 +1,309 @@
+/* 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 {
+ actionCreators as ac,
+ actionTypes as at,
+} from "../../common/Actions.mjs";
+
+const _OpenInPrivateWindow = site => ({
+ id: "newtab-menu-open-new-private-window",
+ icon: "new-window-private",
+ action: ac.OnlyToMain({
+ type: at.OPEN_PRIVATE_WINDOW,
+ data: { url: site.url, referrer: site.referrer },
+ }),
+ userEvent: "OPEN_PRIVATE_WINDOW",
+});
+
+/**
+ * List of functions that return items that can be included as menu options in a
+ * LinkMenu. All functions take the site as the first parameter, and optionally
+ * the index of the site.
+ */
+export const LinkMenuOptions = {
+ Separator: () => ({ type: "separator" }),
+ EmptyItem: () => ({ type: "empty" }),
+ ShowPrivacyInfo: () => ({
+ id: "newtab-menu-show-privacy-info",
+ icon: "info",
+ action: {
+ type: at.SHOW_PRIVACY_INFO,
+ },
+ userEvent: "SHOW_PRIVACY_INFO",
+ }),
+ AboutSponsored: site => ({
+ id: "newtab-menu-show-privacy-info",
+ icon: "info",
+ action: ac.AlsoToMain({
+ type: at.ABOUT_SPONSORED_TOP_SITES,
+ data: {
+ advertiser_name: (site.label || site.hostname).toLocaleLowerCase(),
+ position: site.sponsored_position,
+ tile_id: site.sponsored_tile_id,
+ },
+ }),
+ userEvent: "TOPSITE_SPONSOR_INFO",
+ }),
+ RemoveBookmark: site => ({
+ id: "newtab-menu-remove-bookmark",
+ icon: "bookmark-added",
+ action: ac.AlsoToMain({
+ type: at.DELETE_BOOKMARK_BY_ID,
+ data: site.bookmarkGuid,
+ }),
+ userEvent: "BOOKMARK_DELETE",
+ }),
+ AddBookmark: site => ({
+ id: "newtab-menu-bookmark",
+ icon: "bookmark-hollow",
+ action: ac.AlsoToMain({
+ type: at.BOOKMARK_URL,
+ data: { url: site.url, title: site.title, type: site.type },
+ }),
+ userEvent: "BOOKMARK_ADD",
+ }),
+ OpenInNewWindow: site => ({
+ id: "newtab-menu-open-new-window",
+ icon: "new-window",
+ action: ac.AlsoToMain({
+ type: at.OPEN_NEW_WINDOW,
+ data: {
+ referrer: site.referrer,
+ typedBonus: site.typedBonus,
+ url: site.url,
+ sponsored_tile_id: site.sponsored_tile_id,
+ },
+ }),
+ userEvent: "OPEN_NEW_WINDOW",
+ }),
+ // This blocks the url for regular stories,
+ // but also sends a message to DiscoveryStream with flight_id.
+ // If DiscoveryStream sees this message for a flight_id
+ // it also blocks it on the flight_id.
+ BlockUrl: (site, index, eventSource) => {
+ return LinkMenuOptions.BlockUrls([site], index, eventSource);
+ },
+ // Same as BlockUrl, cept can work on an array of sites.
+ BlockUrls: (tiles, pos, eventSource) => ({
+ id: "newtab-menu-dismiss",
+ icon: "dismiss",
+ action: ac.AlsoToMain({
+ type: at.BLOCK_URL,
+ data: tiles.map(site => ({
+ url: site.original_url || site.open_url || site.url,
+ // pocket_id is only for pocket stories being in highlights, and then dismissed.
+ pocket_id: site.pocket_id,
+ // used by PlacesFeed and TopSitesFeed for sponsored top sites blocking.
+ isSponsoredTopSite: site.sponsored_position,
+ ...(site.flight_id ? { flight_id: site.flight_id } : {}),
+ // If not sponsored, hostname could be anything (Cat3 Data!).
+ // So only put in advertiser_name for sponsored topsites.
+ ...(site.sponsored_position
+ ? {
+ advertiser_name: (
+ site.label || site.hostname
+ )?.toLocaleLowerCase(),
+ }
+ : {}),
+ position: pos,
+ ...(site.sponsored_tile_id ? { tile_id: site.sponsored_tile_id } : {}),
+ is_pocket_card: site.type === "CardGrid",
+ })),
+ }),
+ impression: ac.ImpressionStats({
+ source: eventSource,
+ block: 0,
+ tiles: tiles.map((site, index) => ({
+ id: site.guid,
+ pos: pos + index,
+ ...(site.shim && site.shim.delete ? { shim: site.shim.delete } : {}),
+ })),
+ }),
+ userEvent: "BLOCK",
+ }),
+
+ // This is an option for web extentions which will result in remove items from
+ // memory and notify the web extenion, rather than using the built-in block list.
+ WebExtDismiss: (site, index, eventSource) => ({
+ id: "menu_action_webext_dismiss",
+ string_id: "newtab-menu-dismiss",
+ icon: "dismiss",
+ action: ac.WebExtEvent(at.WEBEXT_DISMISS, {
+ source: eventSource,
+ url: site.url,
+ action_position: index,
+ }),
+ }),
+ DeleteUrl: (site, index, eventSource, isEnabled, siteInfo) => ({
+ id: "newtab-menu-delete-history",
+ icon: "delete",
+ action: {
+ type: at.DIALOG_OPEN,
+ data: {
+ onConfirm: [
+ ac.AlsoToMain({
+ type: at.DELETE_HISTORY_URL,
+ data: {
+ url: site.url,
+ pocket_id: site.pocket_id,
+ forceBlock: site.bookmarkGuid,
+ },
+ }),
+ ac.UserEvent(
+ Object.assign(
+ { event: "DELETE", source: eventSource, action_position: index },
+ siteInfo
+ )
+ ),
+ ],
+ eventSource,
+ body_string_id: [
+ "newtab-confirm-delete-history-p1",
+ "newtab-confirm-delete-history-p2",
+ ],
+ confirm_button_string_id: "newtab-topsites-delete-history-button",
+ cancel_button_string_id: "newtab-topsites-cancel-button",
+ icon: "modal-delete",
+ },
+ },
+ userEvent: "DIALOG_OPEN",
+ }),
+ ShowFile: site => ({
+ id: "newtab-menu-show-file",
+ icon: "search",
+ action: ac.OnlyToMain({
+ type: at.SHOW_DOWNLOAD_FILE,
+ data: { url: site.url },
+ }),
+ }),
+ OpenFile: site => ({
+ id: "newtab-menu-open-file",
+ icon: "open-file",
+ action: ac.OnlyToMain({
+ type: at.OPEN_DOWNLOAD_FILE,
+ data: { url: site.url },
+ }),
+ }),
+ CopyDownloadLink: site => ({
+ id: "newtab-menu-copy-download-link",
+ icon: "copy",
+ action: ac.OnlyToMain({
+ type: at.COPY_DOWNLOAD_LINK,
+ data: { url: site.url },
+ }),
+ }),
+ GoToDownloadPage: site => ({
+ id: "newtab-menu-go-to-download-page",
+ icon: "download",
+ action: ac.OnlyToMain({
+ type: at.OPEN_LINK,
+ data: { url: site.referrer },
+ }),
+ disabled: !site.referrer,
+ }),
+ RemoveDownload: site => ({
+ id: "newtab-menu-remove-download",
+ icon: "delete",
+ action: ac.OnlyToMain({
+ type: at.REMOVE_DOWNLOAD_FILE,
+ data: { url: site.url },
+ }),
+ }),
+ PinTopSite: (site, index) => ({
+ id: "newtab-menu-pin",
+ icon: "pin",
+ action: ac.AlsoToMain({
+ type: at.TOP_SITES_PIN,
+ data: {
+ site,
+ index,
+ },
+ }),
+ userEvent: "PIN",
+ }),
+ UnpinTopSite: site => ({
+ id: "newtab-menu-unpin",
+ icon: "unpin",
+ action: ac.AlsoToMain({
+ type: at.TOP_SITES_UNPIN,
+ data: { site: { url: site.url } },
+ }),
+ userEvent: "UNPIN",
+ }),
+ SaveToPocket: (site, index, eventSource = "CARDGRID") => ({
+ id: "newtab-menu-save-to-pocket",
+ icon: "pocket-save",
+ action: ac.AlsoToMain({
+ type: at.SAVE_TO_POCKET,
+ data: {
+ site: { url: site.url, title: site.title },
+ },
+ }),
+ impression: ac.ImpressionStats({
+ source: eventSource,
+ pocket: 0,
+ tiles: [
+ {
+ id: site.guid,
+ pos: index,
+ ...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
+ },
+ ],
+ }),
+ userEvent: "SAVE_TO_POCKET",
+ }),
+ DeleteFromPocket: site => ({
+ id: "newtab-menu-delete-pocket",
+ icon: "pocket-delete",
+ action: ac.AlsoToMain({
+ type: at.DELETE_FROM_POCKET,
+ data: { pocket_id: site.pocket_id },
+ }),
+ userEvent: "DELETE_FROM_POCKET",
+ }),
+ ArchiveFromPocket: site => ({
+ id: "newtab-menu-archive-pocket",
+ icon: "pocket-archive",
+ action: ac.AlsoToMain({
+ type: at.ARCHIVE_FROM_POCKET,
+ data: { pocket_id: site.pocket_id },
+ }),
+ userEvent: "ARCHIVE_FROM_POCKET",
+ }),
+ EditTopSite: (site, index) => ({
+ id: "newtab-menu-edit-topsites",
+ icon: "edit",
+ action: {
+ type: at.TOP_SITES_EDIT,
+ data: { index },
+ },
+ }),
+ CheckBookmark: site =>
+ site.bookmarkGuid
+ ? LinkMenuOptions.RemoveBookmark(site)
+ : LinkMenuOptions.AddBookmark(site),
+ CheckPinTopSite: (site, index) =>
+ site.isPinned
+ ? LinkMenuOptions.UnpinTopSite(site)
+ : LinkMenuOptions.PinTopSite(site, index),
+ CheckSavedToPocket: (site, index, source) =>
+ site.pocket_id
+ ? LinkMenuOptions.DeleteFromPocket(site)
+ : LinkMenuOptions.SaveToPocket(site, index, source),
+ CheckBookmarkOrArchive: site =>
+ site.pocket_id
+ ? LinkMenuOptions.ArchiveFromPocket(site)
+ : LinkMenuOptions.CheckBookmark(site),
+ CheckArchiveFromPocket: site =>
+ site.pocket_id
+ ? LinkMenuOptions.ArchiveFromPocket(site)
+ : LinkMenuOptions.EmptyItem(),
+ CheckDeleteFromPocket: site =>
+ site.pocket_id
+ ? LinkMenuOptions.DeleteFromPocket(site)
+ : LinkMenuOptions.EmptyItem(),
+ OpenInPrivateWindow: (site, index, eventSource, isEnabled) =>
+ isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem(),
+};
diff --git a/browser/components/newtab/content-src/lib/perf-service.js b/browser/components/newtab/content-src/lib/perf-service.js
deleted file mode 100644
index 6ea99ce877..0000000000
--- a/browser/components/newtab/content-src/lib/perf-service.js
+++ /dev/null
@@ -1,104 +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/. */
-
-"use strict";
-
-let usablePerfObj = window.performance;
-
-export function _PerfService(options) {
- // For testing, so that we can use a fake Window.performance object with
- // known state.
- if (options && options.performanceObj) {
- this._perf = options.performanceObj;
- } else {
- this._perf = usablePerfObj;
- }
-}
-
-_PerfService.prototype = {
- /**
- * Calls the underlying mark() method on the appropriate Window.performance
- * object to add a mark with the given name to the appropriate performance
- * timeline.
- *
- * @param {String} name the name to give the current mark
- * @return {void}
- */
- mark: function mark(str) {
- this._perf.mark(str);
- },
-
- /**
- * Calls the underlying getEntriesByName on the appropriate Window.performance
- * object.
- *
- * @param {String} name
- * @param {String} type eg "mark"
- * @return {Array} Performance* objects
- */
- getEntriesByName: function getEntriesByName(name, type) {
- return this._perf.getEntriesByName(name, type);
- },
-
- /**
- * The timeOrigin property from the appropriate performance object.
- * Used to ensure that timestamps from the add-on code and the content code
- * are comparable.
- *
- * @note If this is called from a context without a window
- * (eg a JSM in chrome), it will return the timeOrigin of the XUL hidden
- * window, which appears to be the first created window (and thus
- * timeOrigin) in the browser. Note also, however, there is also a private
- * hidden window, presumably for private browsing, which appears to be
- * created dynamically later. Exactly how/when that shows up needs to be
- * investigated.
- *
- * @return {Number} A double of milliseconds with a precision of 0.5us.
- */
- get timeOrigin() {
- return this._perf.timeOrigin;
- },
-
- /**
- * Returns the "absolute" version of performance.now(), i.e. one that
- * should ([bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
- * be comparable across both chrome and content.
- *
- * @return {Number}
- */
- absNow: function absNow() {
- return this.timeOrigin + this._perf.now();
- },
-
- /**
- * This returns the absolute startTime from the most recent performance.mark()
- * with the given name.
- *
- * @param {String} name the name to lookup the start time for
- *
- * @return {Number} the returned start time, as a DOMHighResTimeStamp
- *
- * @throws {Error} "No Marks with the name ..." if none are available
- *
- * @note Always surround calls to this by try/catch. Otherwise your code
- * may fail when the `privacy.resistFingerprinting` pref is true. When
- * this pref is set, all attempts to get marks will likely fail, which will
- * cause this method to throw.
- *
- * See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
- * for more info.
- */
- getMostRecentAbsMarkStartByName(name) {
- let entries = this.getEntriesByName(name, "mark");
-
- if (!entries.length) {
- throw new Error(`No marks with the name ${name}`);
- }
-
- let mostRecentEntry = entries[entries.length - 1];
- return this._perf.timeOrigin + mostRecentEntry.startTime;
- },
-};
-
-export const perfService = new _PerfService();
diff --git a/browser/components/newtab/content-src/lib/perf-service.mjs b/browser/components/newtab/content-src/lib/perf-service.mjs
new file mode 100644
index 0000000000..25fc430726
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/perf-service.mjs
@@ -0,0 +1,102 @@
+/* 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/. */
+
+let usablePerfObj = window.performance;
+
+export function _PerfService(options) {
+ // For testing, so that we can use a fake Window.performance object with
+ // known state.
+ if (options && options.performanceObj) {
+ this._perf = options.performanceObj;
+ } else {
+ this._perf = usablePerfObj;
+ }
+}
+
+_PerfService.prototype = {
+ /**
+ * Calls the underlying mark() method on the appropriate Window.performance
+ * object to add a mark with the given name to the appropriate performance
+ * timeline.
+ *
+ * @param {String} name the name to give the current mark
+ * @return {void}
+ */
+ mark: function mark(str) {
+ this._perf.mark(str);
+ },
+
+ /**
+ * Calls the underlying getEntriesByName on the appropriate Window.performance
+ * object.
+ *
+ * @param {String} name
+ * @param {String} type eg "mark"
+ * @return {Array} Performance* objects
+ */
+ getEntriesByName: function getEntriesByName(entryName, type) {
+ return this._perf.getEntriesByName(entryName, type);
+ },
+
+ /**
+ * The timeOrigin property from the appropriate performance object.
+ * Used to ensure that timestamps from the add-on code and the content code
+ * are comparable.
+ *
+ * @note If this is called from a context without a window
+ * (eg a JSM in chrome), it will return the timeOrigin of the XUL hidden
+ * window, which appears to be the first created window (and thus
+ * timeOrigin) in the browser. Note also, however, there is also a private
+ * hidden window, presumably for private browsing, which appears to be
+ * created dynamically later. Exactly how/when that shows up needs to be
+ * investigated.
+ *
+ * @return {Number} A double of milliseconds with a precision of 0.5us.
+ */
+ get timeOrigin() {
+ return this._perf.timeOrigin;
+ },
+
+ /**
+ * Returns the "absolute" version of performance.now(), i.e. one that
+ * should ([bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
+ * be comparable across both chrome and content.
+ *
+ * @return {Number}
+ */
+ absNow: function absNow() {
+ return this.timeOrigin + this._perf.now();
+ },
+
+ /**
+ * This returns the absolute startTime from the most recent performance.mark()
+ * with the given name.
+ *
+ * @param {String} name the name to lookup the start time for
+ *
+ * @return {Number} the returned start time, as a DOMHighResTimeStamp
+ *
+ * @throws {Error} "No Marks with the name ..." if none are available
+ *
+ * @note Always surround calls to this by try/catch. Otherwise your code
+ * may fail when the `privacy.resistFingerprinting` pref is true. When
+ * this pref is set, all attempts to get marks will likely fail, which will
+ * cause this method to throw.
+ *
+ * See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
+ * for more info.
+ */
+ getMostRecentAbsMarkStartByName(entryName) {
+ let entries = this.getEntriesByName(entryName, "mark");
+
+ if (!entries.length) {
+ throw new Error(`No marks with the name ${entryName}`);
+ }
+
+ let mostRecentEntry = entries[entries.length - 1];
+ return this._perf.timeOrigin + mostRecentEntry.startTime;
+ },
+};
+
+export const perfService = new _PerfService();
diff --git a/browser/components/newtab/content-src/lib/screenshot-utils.js b/browser/components/newtab/content-src/lib/screenshot-utils.js
deleted file mode 100644
index 7ea93f12ae..0000000000
--- a/browser/components/newtab/content-src/lib/screenshot-utils.js
+++ /dev/null
@@ -1,61 +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/. */
-
-/**
- * List of helper functions for screenshot-based images.
- *
- * There are two kinds of images:
- * 1. Remote Image: This is the image from the main process and it refers to
- * the image in the React props. This can either be an object with the `data`
- * and `path` properties, if it is a blob, or a string, if it is a normal image.
- * 2. Local Image: This is the image object in the content process and it refers
- * to the image *object* in the React component's state. All local image
- * objects have the `url` property, and an additional property `path`, if they
- * are blobs.
- */
-export const ScreenshotUtils = {
- isBlob(isLocal, image) {
- return !!(
- image &&
- image.path &&
- ((!isLocal && image.data) || (isLocal && image.url))
- );
- },
-
- // This should always be called with a remote image and not a local image.
- createLocalImageObject(remoteImage) {
- if (!remoteImage) {
- return null;
- }
- if (this.isBlob(false, remoteImage)) {
- return {
- url: global.URL.createObjectURL(remoteImage.data),
- path: remoteImage.path,
- };
- }
- return { url: remoteImage };
- },
-
- // Revokes the object URL of the image if the local image is a blob.
- // This should always be called with a local image and not a remote image.
- maybeRevokeBlobObjectURL(localImage) {
- if (this.isBlob(true, localImage)) {
- global.URL.revokeObjectURL(localImage.url);
- }
- },
-
- // Checks if remoteImage and localImage are the same.
- isRemoteImageLocal(localImage, remoteImage) {
- // Both remoteImage and localImage are present.
- if (remoteImage && localImage) {
- return this.isBlob(false, remoteImage)
- ? localImage.path === remoteImage.path
- : localImage.url === remoteImage;
- }
-
- // This will only handle the remaining three possible outcomes.
- // (i.e. everything except when both image and localImage are present)
- return !remoteImage && !localImage;
- },
-};
diff --git a/browser/components/newtab/content-src/lib/screenshot-utils.mjs b/browser/components/newtab/content-src/lib/screenshot-utils.mjs
new file mode 100644
index 0000000000..2d1342be4f
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/screenshot-utils.mjs
@@ -0,0 +1,61 @@
+/* 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/. */
+
+/**
+ * List of helper functions for screenshot-based images.
+ *
+ * There are two kinds of images:
+ * 1. Remote Image: This is the image from the main process and it refers to
+ * the image in the React props. This can either be an object with the `data`
+ * and `path` properties, if it is a blob, or a string, if it is a normal image.
+ * 2. Local Image: This is the image object in the content process and it refers
+ * to the image *object* in the React component's state. All local image
+ * objects have the `url` property, and an additional property `path`, if they
+ * are blobs.
+ */
+export const ScreenshotUtils = {
+ isBlob(isLocal, image) {
+ return !!(
+ image &&
+ image.path &&
+ ((!isLocal && image.data) || (isLocal && image.url))
+ );
+ },
+
+ // This should always be called with a remote image and not a local image.
+ createLocalImageObject(remoteImage) {
+ if (!remoteImage) {
+ return null;
+ }
+ if (this.isBlob(false, remoteImage)) {
+ return {
+ url: globalThis.URL.createObjectURL(remoteImage.data),
+ path: remoteImage.path,
+ };
+ }
+ return { url: remoteImage };
+ },
+
+ // Revokes the object URL of the image if the local image is a blob.
+ // This should always be called with a local image and not a remote image.
+ maybeRevokeBlobObjectURL(localImage) {
+ if (this.isBlob(true, localImage)) {
+ globalThis.URL.revokeObjectURL(localImage.url);
+ }
+ },
+
+ // Checks if remoteImage and localImage are the same.
+ isRemoteImageLocal(localImage, remoteImage) {
+ // Both remoteImage and localImage are present.
+ if (remoteImage && localImage) {
+ return this.isBlob(false, remoteImage)
+ ? localImage.path === remoteImage.path
+ : localImage.url === remoteImage;
+ }
+
+ // This will only handle the remaining three possible outcomes.
+ // (i.e. everything except when both image and localImage are present)
+ return !remoteImage && !localImage;
+ },
+};
diff --git a/browser/components/newtab/content-src/lib/selectLayoutRender.js b/browser/components/newtab/content-src/lib/selectLayoutRender.mjs
index 8ef4dd428f..8ef4dd428f 100644
--- a/browser/components/newtab/content-src/lib/selectLayoutRender.js
+++ b/browser/components/newtab/content-src/lib/selectLayoutRender.mjs
diff --git a/browser/components/newtab/content-src/styles/_activity-stream.scss b/browser/components/newtab/content-src/styles/_activity-stream.scss
index 88ed530b6a..d2e66667b2 100644
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -21,6 +21,17 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Ubuntu, 'Helvetica Neue', sans-serif;
font-size: 16px;
+
+ // rules for HNT wallpapers
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
+ background-attachment: fixed;
+ background-image: var(--newtab-wallpaper-light, '');
+
+ @media (prefers-color-scheme: dark) {
+ background-image: var(--newtab-wallpaper-dark, '');
+ }
}
.no-scroll {
@@ -137,6 +148,7 @@ input {
@import '../components/ContextMenu/ContextMenu';
@import '../components/ConfirmDialog/ConfirmDialog';
@import '../components/CustomizeMenu/CustomizeMenu';
+@import '../components/WallpapersSection/WallpapersSection';
@import '../components/Card/Card';
@import '../components/CollapsibleSection/CollapsibleSection';
@import '../components/DiscoveryStreamAdmin/DiscoveryStreamAdmin';