summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/newtab/.eslintrc.js8
-rw-r--r--browser/components/newtab/common/Actions.mjs463
-rw-r--r--browser/components/newtab/common/Actions.sys.mjs457
-rw-r--r--browser/components/newtab/common/Reducers.sys.mjs15
-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
-rw-r--r--browser/components/newtab/css/activity-stream-linux.css150
-rw-r--r--browser/components/newtab/css/activity-stream-mac.css150
-rw-r--r--browser/components/newtab/css/activity-stream-windows.css150
-rw-r--r--browser/components/newtab/data/content/activity-stream.bundle.js942
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-beach.avifbin0 -> 4043 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-color.avifbin0 -> 2413 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-landscape.avifbin0 -> 9381 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-mountain.avifbin0 -> 11602 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-panda.avifbin0 -> 4606 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/dark-sky.avifbin0 -> 2216 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-beach.avifbin0 -> 3806 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-color.avifbin0 -> 2267 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-landscape.avifbin0 -> 2527 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-mountain.avifbin0 -> 5915 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-panda.avifbin0 -> 8667 bytes
-rw-r--r--browser/components/newtab/data/content/assets/wallpapers/light-sky.avifbin0 -> 2540 bytes
-rw-r--r--browser/components/newtab/karma.mc.config.js22
-rw-r--r--browser/components/newtab/lib/AboutPreferences.sys.mjs2
-rw-r--r--browser/components/newtab/lib/ActivityStream.sys.mjs30
-rw-r--r--browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs2
-rw-r--r--browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs39
-rw-r--r--browser/components/newtab/lib/DownloadsManager.sys.mjs2
-rw-r--r--browser/components/newtab/lib/FaviconFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/HighlightsFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/NewTabInit.sys.mjs2
-rw-r--r--browser/components/newtab/lib/PlacesFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/PrefsFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/RecommendationProvider.sys.mjs2
-rw-r--r--browser/components/newtab/lib/SectionsManager.sys.mjs4
-rw-r--r--browser/components/newtab/lib/SystemTickFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/TelemetryFeed.sys.mjs49
-rw-r--r--browser/components/newtab/lib/TopSitesFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/TopStoriesFeed.sys.mjs2
-rw-r--r--browser/components/newtab/lib/WallpaperFeed.sys.mjs117
-rw-r--r--browser/components/newtab/metrics.yaml31
-rw-r--r--browser/components/newtab/test/browser/browser_as_load_location.js2
-rw-r--r--browser/components/newtab/test/browser/browser_newtab_overrides.js4
-rw-r--r--browser/components/newtab/test/schemas/pings.js5
-rw-r--r--browser/components/newtab/test/unit/common/Actions.test.js2
-rw-r--r--browser/components/newtab/test/unit/common/Reducers.test.js2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/Base.test.jsx79
-rw-r--r--browser/components/newtab/test/unit/content-src/components/Card.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/ComponentPerfTimer.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/ConfirmDialog.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/CustomiseMenu.test.jsx2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamAdmin.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardGrid.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx22
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx73
-rw-r--r--browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/TopicsWidget.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/Sections.test.jsx2
-rw-r--r--browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx5
-rw-r--r--browser/components/newtab/test/unit/content-src/components/TopSites/TopSiteImpressionWrapper.test.jsx2
-rw-r--r--browser/components/newtab/test/unit/content-src/lib/detect-user-session-start.test.js5
-rw-r--r--browser/components/newtab/test/unit/content-src/lib/init-store.test.js5
-rw-r--r--browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/AboutPreferences.test.js5
-rw-r--r--browser/components/newtab/test/unit/lib/ActivityStream.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js7
-rw-r--r--browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js30
-rw-r--r--browser/components/newtab/test/unit/lib/DownloadsManager.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/FaviconFeed.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/NewTabInit.test.js5
-rw-r--r--browser/components/newtab/test/unit/lib/PrefsFeed.test.js5
-rw-r--r--browser/components/newtab/test/unit/lib/RecommendationProvider.test.js5
-rw-r--r--browser/components/newtab/test/unit/lib/SectionsManager.test.js2
-rw-r--r--browser/components/newtab/test/unit/lib/SystemTickFeed.test.js2
-rw-r--r--browser/components/newtab/test/xpcshell/test_HighlightsFeed.js2
-rw-r--r--browser/components/newtab/test/xpcshell/test_PlacesFeed.js2
-rw-r--r--browser/components/newtab/test/xpcshell/test_TelemetryFeed.js56
-rw-r--r--browser/components/newtab/test/xpcshell/test_TopSitesFeed.js2
-rw-r--r--browser/components/newtab/test/xpcshell/test_WallpaperFeed.js115
-rw-r--r--browser/components/newtab/test/xpcshell/xpcshell.toml2
-rw-r--r--browser/components/newtab/webpack.system-addon.config.js2
135 files changed, 3415 insertions, 1765 deletions
diff --git a/browser/components/newtab/.eslintrc.js b/browser/components/newtab/.eslintrc.js
index f541cdd988..29114a055a 100644
--- a/browser/components/newtab/.eslintrc.js
+++ b/browser/components/newtab/.eslintrc.js
@@ -15,11 +15,7 @@ module.exports = {
{
// TODO: Bug 1773467 - Move these to .mjs or figure out a generic way
// to identify these as modules.
- files: [
- "content-src/**/*.js",
- "test/schemas/**/*.js",
- "test/unit/**/*.js",
- ],
+ files: ["test/schemas/**/*.js", "test/unit/**/*.js"],
parserOptions: {
sourceType: "module",
},
@@ -92,8 +88,6 @@ module.exports = {
},
],
rules: {
- "fetch-options/no-fetch-credentials": "error",
-
"react/jsx-boolean-value": ["error", "always"],
"react/jsx-key": "error",
"react/jsx-no-bind": [
diff --git a/browser/components/newtab/common/Actions.mjs b/browser/components/newtab/common/Actions.mjs
new file mode 100644
index 0000000000..7273d80220
--- /dev/null
+++ b/browser/components/newtab/common/Actions.mjs
@@ -0,0 +1,463 @@
+/* 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/. */
+
+// This file is accessed from both content and system scopes.
+
+export const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
+export const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
+export const PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
+export const UI_CODE = 1;
+export const BACKGROUND_PROCESS = 2;
+
+/**
+ * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
+ * Use this in action creators if you need different logic
+ * for ui/background processes.
+ */
+export const globalImportContext =
+ typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE;
+
+// Create an object that avoids accidental differing key/value pairs:
+// {
+// INIT: "INIT",
+// UNINIT: "UNINIT"
+// }
+export const actionTypes = {};
+
+for (const type of [
+ "ABOUT_SPONSORED_TOP_SITES",
+ "ADDONS_INFO_REQUEST",
+ "ADDONS_INFO_RESPONSE",
+ "ARCHIVE_FROM_POCKET",
+ "AS_ROUTER_INITIALIZED",
+ "AS_ROUTER_PREF_CHANGED",
+ "AS_ROUTER_TARGETING_UPDATE",
+ "AS_ROUTER_TELEMETRY_USER_EVENT",
+ "BLOCK_URL",
+ "BOOKMARK_URL",
+ "CLEAR_PREF",
+ "COPY_DOWNLOAD_LINK",
+ "DELETE_BOOKMARK_BY_ID",
+ "DELETE_FROM_POCKET",
+ "DELETE_HISTORY_URL",
+ "DIALOG_CANCEL",
+ "DIALOG_OPEN",
+ "DISABLE_SEARCH",
+ "DISCOVERY_STREAM_COLLECTION_DISMISSIBLE_TOGGLE",
+ "DISCOVERY_STREAM_CONFIG_CHANGE",
+ "DISCOVERY_STREAM_CONFIG_RESET",
+ "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS",
+ "DISCOVERY_STREAM_CONFIG_SETUP",
+ "DISCOVERY_STREAM_CONFIG_SET_VALUE",
+ "DISCOVERY_STREAM_DEV_EXPIRE_CACHE",
+ "DISCOVERY_STREAM_DEV_IDLE_DAILY",
+ "DISCOVERY_STREAM_DEV_SYNC_RS",
+ "DISCOVERY_STREAM_DEV_SYSTEM_TICK",
+ "DISCOVERY_STREAM_EXPERIMENT_DATA",
+ "DISCOVERY_STREAM_FEEDS_UPDATE",
+ "DISCOVERY_STREAM_FEED_UPDATE",
+ "DISCOVERY_STREAM_IMPRESSION_STATS",
+ "DISCOVERY_STREAM_LAYOUT_RESET",
+ "DISCOVERY_STREAM_LAYOUT_UPDATE",
+ "DISCOVERY_STREAM_LINK_BLOCKED",
+ "DISCOVERY_STREAM_LOADED_CONTENT",
+ "DISCOVERY_STREAM_PERSONALIZATION_INIT",
+ "DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED",
+ "DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE",
+ "DISCOVERY_STREAM_PERSONALIZATION_RESET",
+ "DISCOVERY_STREAM_PERSONALIZATION_TOGGLE",
+ "DISCOVERY_STREAM_PERSONALIZATION_UPDATED",
+ "DISCOVERY_STREAM_POCKET_STATE_INIT",
+ "DISCOVERY_STREAM_POCKET_STATE_SET",
+ "DISCOVERY_STREAM_PREFS_SETUP",
+ "DISCOVERY_STREAM_RECENT_SAVES",
+ "DISCOVERY_STREAM_RETRY_FEED",
+ "DISCOVERY_STREAM_SPOCS_CAPS",
+ "DISCOVERY_STREAM_SPOCS_ENDPOINT",
+ "DISCOVERY_STREAM_SPOCS_PLACEMENTS",
+ "DISCOVERY_STREAM_SPOCS_UPDATE",
+ "DISCOVERY_STREAM_SPOC_BLOCKED",
+ "DISCOVERY_STREAM_SPOC_IMPRESSION",
+ "DISCOVERY_STREAM_USER_EVENT",
+ "DOWNLOAD_CHANGED",
+ "FAKE_FOCUS_SEARCH",
+ "FILL_SEARCH_TERM",
+ "HANDOFF_SEARCH_TO_AWESOMEBAR",
+ "HIDE_PERSONALIZE",
+ "HIDE_PRIVACY_INFO",
+ "INIT",
+ "NEW_TAB_INIT",
+ "NEW_TAB_INITIAL_STATE",
+ "NEW_TAB_LOAD",
+ "NEW_TAB_REHYDRATED",
+ "NEW_TAB_STATE_REQUEST",
+ "NEW_TAB_UNLOAD",
+ "OPEN_DOWNLOAD_FILE",
+ "OPEN_LINK",
+ "OPEN_NEW_WINDOW",
+ "OPEN_PRIVATE_WINDOW",
+ "OPEN_WEBEXT_SETTINGS",
+ "PARTNER_LINK_ATTRIBUTION",
+ "PLACES_BOOKMARKS_REMOVED",
+ "PLACES_BOOKMARK_ADDED",
+ "PLACES_HISTORY_CLEARED",
+ "PLACES_LINKS_CHANGED",
+ "PLACES_LINKS_DELETED",
+ "PLACES_LINK_BLOCKED",
+ "PLACES_SAVED_TO_POCKET",
+ "POCKET_CTA",
+ "POCKET_LINK_DELETED_OR_ARCHIVED",
+ "POCKET_LOGGED_IN",
+ "POCKET_WAITING_FOR_SPOC",
+ "PREFS_INITIAL_VALUES",
+ "PREF_CHANGED",
+ "PREVIEW_REQUEST",
+ "PREVIEW_REQUEST_CANCEL",
+ "PREVIEW_RESPONSE",
+ "REMOVE_DOWNLOAD_FILE",
+ "RICH_ICON_MISSING",
+ "SAVE_SESSION_PERF_DATA",
+ "SAVE_TO_POCKET",
+ "SCREENSHOT_UPDATED",
+ "SECTION_DEREGISTER",
+ "SECTION_DISABLE",
+ "SECTION_ENABLE",
+ "SECTION_MOVE",
+ "SECTION_OPTIONS_CHANGED",
+ "SECTION_REGISTER",
+ "SECTION_UPDATE",
+ "SECTION_UPDATE_CARD",
+ "SETTINGS_CLOSE",
+ "SETTINGS_OPEN",
+ "SET_PREF",
+ "SHOW_DOWNLOAD_FILE",
+ "SHOW_FIREFOX_ACCOUNTS",
+ "SHOW_PERSONALIZE",
+ "SHOW_PRIVACY_INFO",
+ "SHOW_SEARCH",
+ "SKIPPED_SIGNIN",
+ "SOV_UPDATED",
+ "SUBMIT_EMAIL",
+ "SUBMIT_SIGNIN",
+ "SYSTEM_TICK",
+ "TELEMETRY_IMPRESSION_STATS",
+ "TELEMETRY_USER_EVENT",
+ "TOP_SITES_CANCEL_EDIT",
+ "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL",
+ "TOP_SITES_EDIT",
+ "TOP_SITES_INSERT",
+ "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL",
+ "TOP_SITES_ORGANIC_IMPRESSION_STATS",
+ "TOP_SITES_PIN",
+ "TOP_SITES_PREFS_UPDATED",
+ "TOP_SITES_SPONSORED_IMPRESSION_STATS",
+ "TOP_SITES_UNPIN",
+ "TOP_SITES_UPDATED",
+ "TOTAL_BOOKMARKS_REQUEST",
+ "TOTAL_BOOKMARKS_RESPONSE",
+ "UNINIT",
+ "UPDATE_PINNED_SEARCH_SHORTCUTS",
+ "UPDATE_SEARCH_SHORTCUTS",
+ "UPDATE_SECTION_PREFS",
+ "WALLPAPERS_SET",
+ "WEBEXT_CLICK",
+ "WEBEXT_DISMISS",
+]) {
+ actionTypes[type] = type;
+}
+
+// Helper function for creating routed actions between content and main
+// Not intended to be used by consumers
+function _RouteMessage(action, options) {
+ const meta = action.meta ? { ...action.meta } : {};
+ if (!options || !options.from || !options.to) {
+ throw new Error(
+ "Routed Messages must have options as the second parameter, and must at least include a .from and .to property."
+ );
+ }
+ // For each of these fields, if they are passed as an option,
+ // add them to the action. If they are not defined, remove them.
+ ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(
+ o => {
+ if (typeof options[o] !== "undefined") {
+ meta[o] = options[o];
+ } else if (meta[o]) {
+ delete meta[o];
+ }
+ }
+ );
+ return { ...action, meta };
+}
+
+/**
+ * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
+ *
+ * @param {object} action Any redux action (required)
+ * @param {object} options
+ * @param {bool} skipLocal Used by OnlyToMain to skip the main reducer
+ * @param {string} fromTarget The id of the content port from which the action originated. (optional)
+ * @return {object} An action with added .meta properties
+ */
+function AlsoToMain(action, fromTarget, skipLocal) {
+ return _RouteMessage(action, {
+ from: CONTENT_MESSAGE_TYPE,
+ to: MAIN_MESSAGE_TYPE,
+ fromTarget,
+ skipLocal,
+ });
+}
+
+/**
+ * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
+ *
+ * @param {object} action Any redux action (required)
+ * @param {object} options
+ * @param {string} fromTarget The id of the content port from which the action originated. (optional)
+ * @return {object} An action with added .meta properties
+ */
+function OnlyToMain(action, fromTarget) {
+ return AlsoToMain(action, fromTarget, true);
+}
+
+/**
+ * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
+ *
+ * @param {object} action Any redux action (required)
+ * @return {object} An action with added .meta properties
+ */
+function BroadcastToContent(action) {
+ return _RouteMessage(action, {
+ from: MAIN_MESSAGE_TYPE,
+ to: CONTENT_MESSAGE_TYPE,
+ });
+}
+
+/**
+ * AlsoToOneContent - Creates a message that will be will be dispatched to the main store
+ * and also sent to a particular Content process.
+ *
+ * @param {object} action Any redux action (required)
+ * @param {string} target The id of a content port
+ * @param {bool} skipMain Used by OnlyToOneContent to skip the main process
+ * @return {object} An action with added .meta properties
+ */
+function AlsoToOneContent(action, target, skipMain) {
+ if (!target) {
+ throw new Error(
+ "You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent"
+ );
+ }
+ return _RouteMessage(action, {
+ from: MAIN_MESSAGE_TYPE,
+ to: CONTENT_MESSAGE_TYPE,
+ toTarget: target,
+ skipMain,
+ });
+}
+
+/**
+ * OnlyToOneContent - Creates a message that will be sent to a particular Content process
+ * and skip the main reducer.
+ *
+ * @param {object} action Any redux action (required)
+ * @param {string} target The id of a content port
+ * @return {object} An action with added .meta properties
+ */
+function OnlyToOneContent(action, target) {
+ return AlsoToOneContent(action, target, true);
+}
+
+/**
+ * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
+ *
+ * @param {object} action Any redux action (required)
+ * @return {object} An action with added .meta properties
+ */
+function AlsoToPreloaded(action) {
+ return _RouteMessage(action, {
+ from: MAIN_MESSAGE_TYPE,
+ to: PRELOAD_MESSAGE_TYPE,
+ });
+}
+
+/**
+ * UserEvent - A telemetry ping indicating a user action. This should only
+ * be sent from the UI during a user session.
+ *
+ * @param {object} data Fields to include in the ping (source, etc.)
+ * @return {object} An AlsoToMain action
+ */
+function UserEvent(data) {
+ return AlsoToMain({
+ type: actionTypes.TELEMETRY_USER_EVENT,
+ data,
+ });
+}
+
+/**
+ * DiscoveryStreamUserEvent - A telemetry ping indicating a user action from Discovery Stream. This should only
+ * be sent from the UI during a user session.
+ *
+ * @param {object} data Fields to include in the ping (source, etc.)
+ * @return {object} An AlsoToMain action
+ */
+function DiscoveryStreamUserEvent(data) {
+ return AlsoToMain({
+ type: actionTypes.DISCOVERY_STREAM_USER_EVENT,
+ data,
+ });
+}
+
+/**
+ * ASRouterUserEvent - A telemetry ping indicating a user action from AS router. This should only
+ * be sent from the UI during a user session.
+ *
+ * @param {object} data Fields to include in the ping (source, etc.)
+ * @return {object} An AlsoToMain action
+ */
+function ASRouterUserEvent(data) {
+ return AlsoToMain({
+ type: actionTypes.AS_ROUTER_TELEMETRY_USER_EVENT,
+ data,
+ });
+}
+
+/**
+ * ImpressionStats - A telemetry ping indicating an impression stats.
+ *
+ * @param {object} data Fields to include in the ping
+ * @param {int} importContext (For testing) Override the import context for testing.
+ * #return {object} An action. For UI code, a AlsoToMain action.
+ */
+function ImpressionStats(data, importContext = globalImportContext) {
+ const action = {
+ type: actionTypes.TELEMETRY_IMPRESSION_STATS,
+ data,
+ };
+ return importContext === UI_CODE ? AlsoToMain(action) : action;
+}
+
+/**
+ * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream.
+ *
+ * @param {object} data Fields to include in the ping
+ * @param {int} importContext (For testing) Override the import context for testing.
+ * #return {object} An action. For UI code, a AlsoToMain action.
+ */
+function DiscoveryStreamImpressionStats(
+ data,
+ importContext = globalImportContext
+) {
+ const action = {
+ type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS,
+ data,
+ };
+ return importContext === UI_CODE ? AlsoToMain(action) : action;
+}
+
+/**
+ * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream.
+ *
+ * @param {object} data Fields to include in the ping
+ * @param {int} importContext (For testing) Override the import context for testing.
+ * #return {object} An action. For UI code, a AlsoToMain action.
+ */
+function DiscoveryStreamLoadedContent(
+ data,
+ importContext = globalImportContext
+) {
+ const action = {
+ type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT,
+ data,
+ };
+ return importContext === UI_CODE ? AlsoToMain(action) : action;
+}
+
+function SetPref(prefName, value, importContext = globalImportContext) {
+ const action = {
+ type: actionTypes.SET_PREF,
+ data: { name: prefName, value },
+ };
+ return importContext === UI_CODE ? AlsoToMain(action) : action;
+}
+
+function WebExtEvent(type, data, importContext = globalImportContext) {
+ if (!data || !data.source) {
+ throw new Error(
+ 'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.'
+ );
+ }
+ const action = { type, data };
+ return importContext === UI_CODE ? AlsoToMain(action) : action;
+}
+
+export const actionCreators = {
+ BroadcastToContent,
+ UserEvent,
+ DiscoveryStreamUserEvent,
+ ASRouterUserEvent,
+ ImpressionStats,
+ AlsoToOneContent,
+ OnlyToOneContent,
+ AlsoToMain,
+ OnlyToMain,
+ AlsoToPreloaded,
+ SetPref,
+ WebExtEvent,
+ DiscoveryStreamImpressionStats,
+ DiscoveryStreamLoadedContent,
+};
+
+// These are helpers to test for certain kinds of actions
+export const actionUtils = {
+ isSendToMain(action) {
+ if (!action.meta) {
+ return false;
+ }
+ return (
+ action.meta.to === MAIN_MESSAGE_TYPE &&
+ action.meta.from === CONTENT_MESSAGE_TYPE
+ );
+ },
+ isBroadcastToContent(action) {
+ if (!action.meta) {
+ return false;
+ }
+ if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
+ return true;
+ }
+ return false;
+ },
+ isSendToOneContent(action) {
+ if (!action.meta) {
+ return false;
+ }
+ if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
+ return true;
+ }
+ return false;
+ },
+ isSendToPreloaded(action) {
+ if (!action.meta) {
+ return false;
+ }
+ return (
+ action.meta.to === PRELOAD_MESSAGE_TYPE &&
+ action.meta.from === MAIN_MESSAGE_TYPE
+ );
+ },
+ isFromMain(action) {
+ if (!action.meta) {
+ return false;
+ }
+ return (
+ action.meta.from === MAIN_MESSAGE_TYPE &&
+ action.meta.to === CONTENT_MESSAGE_TYPE
+ );
+ },
+ getPortIdOfSender(action) {
+ return (action.meta && action.meta.fromTarget) || null;
+ },
+ _RouteMessage,
+};
diff --git a/browser/components/newtab/common/Actions.sys.mjs b/browser/components/newtab/common/Actions.sys.mjs
deleted file mode 100644
index df5c9f0c91..0000000000
--- a/browser/components/newtab/common/Actions.sys.mjs
+++ /dev/null
@@ -1,457 +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 MAIN_MESSAGE_TYPE = "ActivityStream:Main";
-export const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
-export const PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
-export const UI_CODE = 1;
-export const BACKGROUND_PROCESS = 2;
-
-/**
- * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
- * Use this in action creators if you need different logic
- * for ui/background processes.
- */
-export const globalImportContext =
- typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE;
-
-// Create an object that avoids accidental differing key/value pairs:
-// {
-// INIT: "INIT",
-// UNINIT: "UNINIT"
-// }
-export const actionTypes = {};
-
-for (const type of [
- "ABOUT_SPONSORED_TOP_SITES",
- "ADDONS_INFO_REQUEST",
- "ADDONS_INFO_RESPONSE",
- "ARCHIVE_FROM_POCKET",
- "AS_ROUTER_INITIALIZED",
- "AS_ROUTER_PREF_CHANGED",
- "AS_ROUTER_TARGETING_UPDATE",
- "AS_ROUTER_TELEMETRY_USER_EVENT",
- "BLOCK_URL",
- "BOOKMARK_URL",
- "CLEAR_PREF",
- "COPY_DOWNLOAD_LINK",
- "DELETE_BOOKMARK_BY_ID",
- "DELETE_FROM_POCKET",
- "DELETE_HISTORY_URL",
- "DIALOG_CANCEL",
- "DIALOG_OPEN",
- "DISABLE_SEARCH",
- "DISCOVERY_STREAM_COLLECTION_DISMISSIBLE_TOGGLE",
- "DISCOVERY_STREAM_CONFIG_CHANGE",
- "DISCOVERY_STREAM_CONFIG_RESET",
- "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS",
- "DISCOVERY_STREAM_CONFIG_SETUP",
- "DISCOVERY_STREAM_CONFIG_SET_VALUE",
- "DISCOVERY_STREAM_DEV_EXPIRE_CACHE",
- "DISCOVERY_STREAM_DEV_IDLE_DAILY",
- "DISCOVERY_STREAM_DEV_SYNC_RS",
- "DISCOVERY_STREAM_DEV_SYSTEM_TICK",
- "DISCOVERY_STREAM_EXPERIMENT_DATA",
- "DISCOVERY_STREAM_FEEDS_UPDATE",
- "DISCOVERY_STREAM_FEED_UPDATE",
- "DISCOVERY_STREAM_IMPRESSION_STATS",
- "DISCOVERY_STREAM_LAYOUT_RESET",
- "DISCOVERY_STREAM_LAYOUT_UPDATE",
- "DISCOVERY_STREAM_LINK_BLOCKED",
- "DISCOVERY_STREAM_LOADED_CONTENT",
- "DISCOVERY_STREAM_PERSONALIZATION_INIT",
- "DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED",
- "DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE",
- "DISCOVERY_STREAM_PERSONALIZATION_RESET",
- "DISCOVERY_STREAM_PERSONALIZATION_TOGGLE",
- "DISCOVERY_STREAM_PERSONALIZATION_UPDATED",
- "DISCOVERY_STREAM_POCKET_STATE_INIT",
- "DISCOVERY_STREAM_POCKET_STATE_SET",
- "DISCOVERY_STREAM_PREFS_SETUP",
- "DISCOVERY_STREAM_RECENT_SAVES",
- "DISCOVERY_STREAM_RETRY_FEED",
- "DISCOVERY_STREAM_SPOCS_CAPS",
- "DISCOVERY_STREAM_SPOCS_ENDPOINT",
- "DISCOVERY_STREAM_SPOCS_PLACEMENTS",
- "DISCOVERY_STREAM_SPOCS_UPDATE",
- "DISCOVERY_STREAM_SPOC_BLOCKED",
- "DISCOVERY_STREAM_SPOC_IMPRESSION",
- "DISCOVERY_STREAM_USER_EVENT",
- "DOWNLOAD_CHANGED",
- "FAKE_FOCUS_SEARCH",
- "FILL_SEARCH_TERM",
- "HANDOFF_SEARCH_TO_AWESOMEBAR",
- "HIDE_PERSONALIZE",
- "HIDE_PRIVACY_INFO",
- "INIT",
- "NEW_TAB_INIT",
- "NEW_TAB_INITIAL_STATE",
- "NEW_TAB_LOAD",
- "NEW_TAB_REHYDRATED",
- "NEW_TAB_STATE_REQUEST",
- "NEW_TAB_UNLOAD",
- "OPEN_DOWNLOAD_FILE",
- "OPEN_LINK",
- "OPEN_NEW_WINDOW",
- "OPEN_PRIVATE_WINDOW",
- "OPEN_WEBEXT_SETTINGS",
- "PARTNER_LINK_ATTRIBUTION",
- "PLACES_BOOKMARKS_REMOVED",
- "PLACES_BOOKMARK_ADDED",
- "PLACES_HISTORY_CLEARED",
- "PLACES_LINKS_CHANGED",
- "PLACES_LINKS_DELETED",
- "PLACES_LINK_BLOCKED",
- "PLACES_SAVED_TO_POCKET",
- "POCKET_CTA",
- "POCKET_LINK_DELETED_OR_ARCHIVED",
- "POCKET_LOGGED_IN",
- "POCKET_WAITING_FOR_SPOC",
- "PREFS_INITIAL_VALUES",
- "PREF_CHANGED",
- "PREVIEW_REQUEST",
- "PREVIEW_REQUEST_CANCEL",
- "PREVIEW_RESPONSE",
- "REMOVE_DOWNLOAD_FILE",
- "RICH_ICON_MISSING",
- "SAVE_SESSION_PERF_DATA",
- "SAVE_TO_POCKET",
- "SCREENSHOT_UPDATED",
- "SECTION_DEREGISTER",
- "SECTION_DISABLE",
- "SECTION_ENABLE",
- "SECTION_MOVE",
- "SECTION_OPTIONS_CHANGED",
- "SECTION_REGISTER",
- "SECTION_UPDATE",
- "SECTION_UPDATE_CARD",
- "SETTINGS_CLOSE",
- "SETTINGS_OPEN",
- "SET_PREF",
- "SHOW_DOWNLOAD_FILE",
- "SHOW_FIREFOX_ACCOUNTS",
- "SHOW_PERSONALIZE",
- "SHOW_PRIVACY_INFO",
- "SHOW_SEARCH",
- "SKIPPED_SIGNIN",
- "SOV_UPDATED",
- "SUBMIT_EMAIL",
- "SUBMIT_SIGNIN",
- "SYSTEM_TICK",
- "TELEMETRY_IMPRESSION_STATS",
- "TELEMETRY_USER_EVENT",
- "TOP_SITES_CANCEL_EDIT",
- "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL",
- "TOP_SITES_EDIT",
- "TOP_SITES_INSERT",
- "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL",
- "TOP_SITES_ORGANIC_IMPRESSION_STATS",
- "TOP_SITES_PIN",
- "TOP_SITES_PREFS_UPDATED",
- "TOP_SITES_SPONSORED_IMPRESSION_STATS",
- "TOP_SITES_UNPIN",
- "TOP_SITES_UPDATED",
- "TOTAL_BOOKMARKS_REQUEST",
- "TOTAL_BOOKMARKS_RESPONSE",
- "UNINIT",
- "UPDATE_PINNED_SEARCH_SHORTCUTS",
- "UPDATE_SEARCH_SHORTCUTS",
- "UPDATE_SECTION_PREFS",
- "WEBEXT_CLICK",
- "WEBEXT_DISMISS",
-]) {
- actionTypes[type] = type;
-}
-
-// Helper function for creating routed actions between content and main
-// Not intended to be used by consumers
-function _RouteMessage(action, options) {
- const meta = action.meta ? { ...action.meta } : {};
- if (!options || !options.from || !options.to) {
- throw new Error(
- "Routed Messages must have options as the second parameter, and must at least include a .from and .to property."
- );
- }
- // For each of these fields, if they are passed as an option,
- // add them to the action. If they are not defined, remove them.
- ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(
- o => {
- if (typeof options[o] !== "undefined") {
- meta[o] = options[o];
- } else if (meta[o]) {
- delete meta[o];
- }
- }
- );
- return { ...action, meta };
-}
-
-/**
- * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
- *
- * @param {object} action Any redux action (required)
- * @param {object} options
- * @param {bool} skipLocal Used by OnlyToMain to skip the main reducer
- * @param {string} fromTarget The id of the content port from which the action originated. (optional)
- * @return {object} An action with added .meta properties
- */
-function AlsoToMain(action, fromTarget, skipLocal) {
- return _RouteMessage(action, {
- from: CONTENT_MESSAGE_TYPE,
- to: MAIN_MESSAGE_TYPE,
- fromTarget,
- skipLocal,
- });
-}
-
-/**
- * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
- *
- * @param {object} action Any redux action (required)
- * @param {object} options
- * @param {string} fromTarget The id of the content port from which the action originated. (optional)
- * @return {object} An action with added .meta properties
- */
-function OnlyToMain(action, fromTarget) {
- return AlsoToMain(action, fromTarget, true);
-}
-
-/**
- * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
- *
- * @param {object} action Any redux action (required)
- * @return {object} An action with added .meta properties
- */
-function BroadcastToContent(action) {
- return _RouteMessage(action, {
- from: MAIN_MESSAGE_TYPE,
- to: CONTENT_MESSAGE_TYPE,
- });
-}
-
-/**
- * AlsoToOneContent - Creates a message that will be will be dispatched to the main store
- * and also sent to a particular Content process.
- *
- * @param {object} action Any redux action (required)
- * @param {string} target The id of a content port
- * @param {bool} skipMain Used by OnlyToOneContent to skip the main process
- * @return {object} An action with added .meta properties
- */
-function AlsoToOneContent(action, target, skipMain) {
- if (!target) {
- throw new Error(
- "You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent"
- );
- }
- return _RouteMessage(action, {
- from: MAIN_MESSAGE_TYPE,
- to: CONTENT_MESSAGE_TYPE,
- toTarget: target,
- skipMain,
- });
-}
-
-/**
- * OnlyToOneContent - Creates a message that will be sent to a particular Content process
- * and skip the main reducer.
- *
- * @param {object} action Any redux action (required)
- * @param {string} target The id of a content port
- * @return {object} An action with added .meta properties
- */
-function OnlyToOneContent(action, target) {
- return AlsoToOneContent(action, target, true);
-}
-
-/**
- * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
- *
- * @param {object} action Any redux action (required)
- * @return {object} An action with added .meta properties
- */
-function AlsoToPreloaded(action) {
- return _RouteMessage(action, {
- from: MAIN_MESSAGE_TYPE,
- to: PRELOAD_MESSAGE_TYPE,
- });
-}
-
-/**
- * UserEvent - A telemetry ping indicating a user action. This should only
- * be sent from the UI during a user session.
- *
- * @param {object} data Fields to include in the ping (source, etc.)
- * @return {object} An AlsoToMain action
- */
-function UserEvent(data) {
- return AlsoToMain({
- type: actionTypes.TELEMETRY_USER_EVENT,
- data,
- });
-}
-
-/**
- * DiscoveryStreamUserEvent - A telemetry ping indicating a user action from Discovery Stream. This should only
- * be sent from the UI during a user session.
- *
- * @param {object} data Fields to include in the ping (source, etc.)
- * @return {object} An AlsoToMain action
- */
-function DiscoveryStreamUserEvent(data) {
- return AlsoToMain({
- type: actionTypes.DISCOVERY_STREAM_USER_EVENT,
- data,
- });
-}
-
-/**
- * ASRouterUserEvent - A telemetry ping indicating a user action from AS router. This should only
- * be sent from the UI during a user session.
- *
- * @param {object} data Fields to include in the ping (source, etc.)
- * @return {object} An AlsoToMain action
- */
-function ASRouterUserEvent(data) {
- return AlsoToMain({
- type: actionTypes.AS_ROUTER_TELEMETRY_USER_EVENT,
- data,
- });
-}
-
-/**
- * ImpressionStats - A telemetry ping indicating an impression stats.
- *
- * @param {object} data Fields to include in the ping
- * @param {int} importContext (For testing) Override the import context for testing.
- * #return {object} An action. For UI code, a AlsoToMain action.
- */
-function ImpressionStats(data, importContext = globalImportContext) {
- const action = {
- type: actionTypes.TELEMETRY_IMPRESSION_STATS,
- data,
- };
- return importContext === UI_CODE ? AlsoToMain(action) : action;
-}
-
-/**
- * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream.
- *
- * @param {object} data Fields to include in the ping
- * @param {int} importContext (For testing) Override the import context for testing.
- * #return {object} An action. For UI code, a AlsoToMain action.
- */
-function DiscoveryStreamImpressionStats(
- data,
- importContext = globalImportContext
-) {
- const action = {
- type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS,
- data,
- };
- return importContext === UI_CODE ? AlsoToMain(action) : action;
-}
-
-/**
- * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream.
- *
- * @param {object} data Fields to include in the ping
- * @param {int} importContext (For testing) Override the import context for testing.
- * #return {object} An action. For UI code, a AlsoToMain action.
- */
-function DiscoveryStreamLoadedContent(
- data,
- importContext = globalImportContext
-) {
- const action = {
- type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT,
- data,
- };
- return importContext === UI_CODE ? AlsoToMain(action) : action;
-}
-
-function SetPref(name, value, importContext = globalImportContext) {
- const action = { type: actionTypes.SET_PREF, data: { name, value } };
- return importContext === UI_CODE ? AlsoToMain(action) : action;
-}
-
-function WebExtEvent(type, data, importContext = globalImportContext) {
- if (!data || !data.source) {
- throw new Error(
- 'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.'
- );
- }
- const action = { type, data };
- return importContext === UI_CODE ? AlsoToMain(action) : action;
-}
-
-export const actionCreators = {
- BroadcastToContent,
- UserEvent,
- DiscoveryStreamUserEvent,
- ASRouterUserEvent,
- ImpressionStats,
- AlsoToOneContent,
- OnlyToOneContent,
- AlsoToMain,
- OnlyToMain,
- AlsoToPreloaded,
- SetPref,
- WebExtEvent,
- DiscoveryStreamImpressionStats,
- DiscoveryStreamLoadedContent,
-};
-
-// These are helpers to test for certain kinds of actions
-export const actionUtils = {
- isSendToMain(action) {
- if (!action.meta) {
- return false;
- }
- return (
- action.meta.to === MAIN_MESSAGE_TYPE &&
- action.meta.from === CONTENT_MESSAGE_TYPE
- );
- },
- isBroadcastToContent(action) {
- if (!action.meta) {
- return false;
- }
- if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
- return true;
- }
- return false;
- },
- isSendToOneContent(action) {
- if (!action.meta) {
- return false;
- }
- if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
- return true;
- }
- return false;
- },
- isSendToPreloaded(action) {
- if (!action.meta) {
- return false;
- }
- return (
- action.meta.to === PRELOAD_MESSAGE_TYPE &&
- action.meta.from === MAIN_MESSAGE_TYPE
- );
- },
- isFromMain(action) {
- if (!action.meta) {
- return false;
- }
- return (
- action.meta.from === MAIN_MESSAGE_TYPE &&
- action.meta.to === CONTENT_MESSAGE_TYPE
- );
- },
- getPortIdOfSender(action) {
- return (action.meta && action.meta.fromTarget) || null;
- },
- _RouteMessage,
-};
diff --git a/browser/components/newtab/common/Reducers.sys.mjs b/browser/components/newtab/common/Reducers.sys.mjs
index d4f879b834..326217538d 100644
--- a/browser/components/newtab/common/Reducers.sys.mjs
+++ b/browser/components/newtab/common/Reducers.sys.mjs
@@ -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 { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
import { Dedupe } from "resource://activity-stream/common/Dedupe.sys.mjs";
export const TOP_SITES_DEFAULT_ROWS = 1;
@@ -101,6 +101,9 @@ export const INITIAL_STATE = {
// Hide the search box after handing off to AwesomeBar and user starts typing.
hide: false,
},
+ Wallpapers: {
+ wallpaperList: [],
+ },
};
function App(prevState = INITIAL_STATE.App, action) {
@@ -841,6 +844,15 @@ function Search(prevState = INITIAL_STATE.Search, action) {
}
}
+function Wallpapers(prevState = INITIAL_STATE.Wallpapers, action) {
+ switch (action.type) {
+ case at.WALLPAPERS_SET:
+ return { wallpaperList: action.data };
+ default:
+ return prevState;
+ }
+}
+
export const reducers = {
TopSites,
App,
@@ -852,4 +864,5 @@ export const reducers = {
Personalization,
DiscoveryStream,
Search,
+ Wallpapers,
};
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';
diff --git a/browser/components/newtab/css/activity-stream-linux.css b/browser/components/newtab/css/activity-stream-linux.css
index 8773159737..131ffac535 100644
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -276,6 +276,16 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
+ background-attachment: fixed;
+ background-image: var(--newtab-wallpaper-light, "");
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-image: var(--newtab-wallpaper-dark, "");
+ }
}
.no-scroll {
@@ -405,10 +415,16 @@ input[type=text], input[type=search] {
}
main {
- margin: auto;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
width: 274px;
padding: 0;
}
+main .vertical-center-wrapper {
+ margin: auto 0;
+}
main section {
margin-bottom: 20px;
position: relative;
@@ -489,6 +505,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
+.wallpaper-attribution {
+ padding: 0 25px;
+ font-size: 14px;
+}
+.wallpaper-attribution.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
+ display: none;
+}
+.wallpaper-attribution.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
+ display: inline-block;
+}
+.wallpaper-attribution a {
+ color: var(--newtab-element-color);
+}
+.wallpaper-attribution a:hover {
+ text-decoration: none;
+}
+
.as-error-fallback {
align-items: center;
border-radius: 3px;
@@ -1694,6 +1733,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
+.home-section .wallpapers-section h2 {
+ font-size: inherit;
+}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@@ -1830,6 +1872,112 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
+.wallpaper-list {
+ display: grid;
+ gap: 16px;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-rows: 86px;
+ margin: 16px 0;
+ padding: 0;
+ border: none;
+}
+.wallpaper-list .wallpaper-input.theme-light,
+.wallpaper-list .sr-only.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
+ display: none;
+}
+.wallpaper-list .wallpaper-input.theme-dark,
+.wallpaper-list .sr-only.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
+ display: inline-block;
+}
+.wallpaper-list .wallpaper-input {
+ appearance: none;
+ margin: 0;
+ padding: 0;
+ height: 86px;
+ width: 100%;
+ box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
+ border-radius: 8px;
+ background-clip: content-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ cursor: pointer;
+ outline: 2px solid transparent;
+}
+.wallpaper-list .wallpaper-input.dark-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.dark-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
+}
+.wallpaper-list .wallpaper-input.dark-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.dark-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
+}
+.wallpaper-list .wallpaper-input.dark-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
+}
+.wallpaper-list .wallpaper-input.dark-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
+}
+.wallpaper-list .wallpaper-input.light-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.light-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.light-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-panda.avif");
+}
+.wallpaper-list .wallpaper-input.light-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
+}
+.wallpaper-list .wallpaper-input:checked {
+ outline-color: var(--color-accent-primary-active);
+}
+.wallpaper-list .wallpaper-input:focus-visible {
+ outline-color: var(--newtab-primary-action-background);
+}
+.wallpaper-list .wallpaper-input:hover {
+ filter: brightness(55%);
+ outline-color: transparent;
+}
+.wallpaper-list .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;
+}
+.wallpapers-reset:hover {
+ text-decoration: none;
+}
+
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);
diff --git a/browser/components/newtab/css/activity-stream-mac.css b/browser/components/newtab/css/activity-stream-mac.css
index 87b942818a..416209d511 100644
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -280,6 +280,16 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
+ background-attachment: fixed;
+ background-image: var(--newtab-wallpaper-light, "");
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-image: var(--newtab-wallpaper-dark, "");
+ }
}
.no-scroll {
@@ -409,10 +419,16 @@ input[type=text], input[type=search] {
}
main {
- margin: auto;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
width: 274px;
padding: 0;
}
+main .vertical-center-wrapper {
+ margin: auto 0;
+}
main section {
margin-bottom: 20px;
position: relative;
@@ -493,6 +509,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
+.wallpaper-attribution {
+ padding: 0 25px;
+ font-size: 14px;
+}
+.wallpaper-attribution.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
+ display: none;
+}
+.wallpaper-attribution.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
+ display: inline-block;
+}
+.wallpaper-attribution a {
+ color: var(--newtab-element-color);
+}
+.wallpaper-attribution a:hover {
+ text-decoration: none;
+}
+
.as-error-fallback {
align-items: center;
border-radius: 3px;
@@ -1698,6 +1737,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
+.home-section .wallpapers-section h2 {
+ font-size: inherit;
+}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@@ -1834,6 +1876,112 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
+.wallpaper-list {
+ display: grid;
+ gap: 16px;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-rows: 86px;
+ margin: 16px 0;
+ padding: 0;
+ border: none;
+}
+.wallpaper-list .wallpaper-input.theme-light,
+.wallpaper-list .sr-only.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
+ display: none;
+}
+.wallpaper-list .wallpaper-input.theme-dark,
+.wallpaper-list .sr-only.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
+ display: inline-block;
+}
+.wallpaper-list .wallpaper-input {
+ appearance: none;
+ margin: 0;
+ padding: 0;
+ height: 86px;
+ width: 100%;
+ box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
+ border-radius: 8px;
+ background-clip: content-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ cursor: pointer;
+ outline: 2px solid transparent;
+}
+.wallpaper-list .wallpaper-input.dark-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.dark-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
+}
+.wallpaper-list .wallpaper-input.dark-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.dark-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
+}
+.wallpaper-list .wallpaper-input.dark-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
+}
+.wallpaper-list .wallpaper-input.dark-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
+}
+.wallpaper-list .wallpaper-input.light-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.light-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.light-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-panda.avif");
+}
+.wallpaper-list .wallpaper-input.light-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
+}
+.wallpaper-list .wallpaper-input:checked {
+ outline-color: var(--color-accent-primary-active);
+}
+.wallpaper-list .wallpaper-input:focus-visible {
+ outline-color: var(--newtab-primary-action-background);
+}
+.wallpaper-list .wallpaper-input:hover {
+ filter: brightness(55%);
+ outline-color: transparent;
+}
+.wallpaper-list .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;
+}
+.wallpapers-reset:hover {
+ text-decoration: none;
+}
+
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);
diff --git a/browser/components/newtab/css/activity-stream-windows.css b/browser/components/newtab/css/activity-stream-windows.css
index 25370fdf19..f6118e3c18 100644
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -276,6 +276,16 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
+ background-attachment: fixed;
+ background-image: var(--newtab-wallpaper-light, "");
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-image: var(--newtab-wallpaper-dark, "");
+ }
}
.no-scroll {
@@ -405,10 +415,16 @@ input[type=text], input[type=search] {
}
main {
- margin: auto;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
width: 274px;
padding: 0;
}
+main .vertical-center-wrapper {
+ margin: auto 0;
+}
main section {
margin-bottom: 20px;
position: relative;
@@ -489,6 +505,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
+.wallpaper-attribution {
+ padding: 0 25px;
+ font-size: 14px;
+}
+.wallpaper-attribution.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
+ display: none;
+}
+.wallpaper-attribution.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
+ display: inline-block;
+}
+.wallpaper-attribution a {
+ color: var(--newtab-element-color);
+}
+.wallpaper-attribution a:hover {
+ text-decoration: none;
+}
+
.as-error-fallback {
align-items: center;
border-radius: 3px;
@@ -1694,6 +1733,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
+.home-section .wallpapers-section h2 {
+ font-size: inherit;
+}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@@ -1830,6 +1872,112 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
+.wallpaper-list {
+ display: grid;
+ gap: 16px;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-rows: 86px;
+ margin: 16px 0;
+ padding: 0;
+ border: none;
+}
+.wallpaper-list .wallpaper-input.theme-light,
+.wallpaper-list .sr-only.theme-light {
+ display: inline-block;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
+ display: none;
+}
+.wallpaper-list .wallpaper-input.theme-dark,
+.wallpaper-list .sr-only.theme-dark {
+ display: none;
+}
+[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
+[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
+ display: inline-block;
+}
+.wallpaper-list .wallpaper-input {
+ appearance: none;
+ margin: 0;
+ padding: 0;
+ height: 86px;
+ width: 100%;
+ box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
+ border-radius: 8px;
+ background-clip: content-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ cursor: pointer;
+ outline: 2px solid transparent;
+}
+.wallpaper-list .wallpaper-input.dark-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.dark-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
+}
+.wallpaper-list .wallpaper-input.dark-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.dark-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
+}
+.wallpaper-list .wallpaper-input.dark-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
+}
+.wallpaper-list .wallpaper-input.dark-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-beach {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
+}
+.wallpaper-list .wallpaper-input.light-color {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
+}
+.wallpaper-list .wallpaper-input.light-landscape {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
+}
+.wallpaper-list .wallpaper-input.light-mountain {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
+}
+.wallpaper-list .wallpaper-input.light-panda {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-panda.avif");
+}
+.wallpaper-list .wallpaper-input.light-sky {
+ background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
+}
+.wallpaper-list .wallpaper-input:checked {
+ outline-color: var(--color-accent-primary-active);
+}
+.wallpaper-list .wallpaper-input:focus-visible {
+ outline-color: var(--newtab-primary-action-background);
+}
+.wallpaper-list .wallpaper-input:hover {
+ filter: brightness(55%);
+ outline-color: transparent;
+}
+.wallpaper-list .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;
+}
+.wallpapers-reset:hover {
+ text-decoration: none;
+}
+
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);
diff --git a/browser/components/newtab/data/content/activity-stream.bundle.js b/browser/components/newtab/data/content/activity-stream.bundle.js
index 8904ba87d1..395e8c5bb3 100644
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -70,11 +70,13 @@ __webpack_require__.d(__webpack_exports__, {
renderWithoutState: () => (/* binding */ renderWithoutState)
});
-;// CONCATENATED MODULE: ./common/Actions.sys.mjs
+;// CONCATENATED MODULE: ./common/Actions.mjs
/* 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/. */
+// This file is accessed from both content and system scopes.
+
const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
const PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
@@ -231,6 +233,7 @@ for (const type of [
"UPDATE_PINNED_SEARCH_SHORTCUTS",
"UPDATE_SEARCH_SHORTCUTS",
"UPDATE_SECTION_PREFS",
+ "WALLPAPERS_SET",
"WEBEXT_CLICK",
"WEBEXT_DISMISS",
]) {
@@ -444,8 +447,11 @@ function DiscoveryStreamLoadedContent(
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
-function SetPref(name, value, importContext = globalImportContext) {
- const action = { type: actionTypes.SET_PREF, data: { name, value } };
+function SetPref(prefName, value, importContext = globalImportContext) {
+ const action = {
+ type: actionTypes.SET_PREF,
+ data: { name: prefName, value },
+ };
return importContext === UI_CODE ? AlsoToMain(action) : action;
}
@@ -545,19 +551,19 @@ class SimpleHashRouter extends (external_React_default()).PureComponent {
super(props);
this.onHashChange = this.onHashChange.bind(this);
this.state = {
- hash: __webpack_require__.g.location.hash
+ hash: globalThis.location.hash
};
}
onHashChange() {
this.setState({
- hash: __webpack_require__.g.location.hash
+ hash: globalThis.location.hash
});
}
componentWillMount() {
- __webpack_require__.g.addEventListener("hashchange", this.onHashChange);
+ globalThis.addEventListener("hashchange", this.onHashChange);
}
componentWillUnmount() {
- __webpack_require__.g.removeEventListener("hashchange", this.onHashChange);
+ globalThis.removeEventListener("hashchange", this.onHashChange);
}
render() {
const [, ...routes] = this.state.hash.split("-");
@@ -882,9 +888,9 @@ class CollapseToggle extends (external_React_default()).PureComponent {
}
setBodyClass() {
if (this.renderAdmin && !this.state.collapsed) {
- __webpack_require__.g.document.body.classList.add("no-scroll");
+ globalThis.document.body.classList.add("no-scroll");
} else {
- __webpack_require__.g.document.body.classList.remove("no-scroll");
+ globalThis.document.body.classList.remove("no-scroll");
}
}
componentDidMount() {
@@ -894,7 +900,7 @@ class CollapseToggle extends (external_React_default()).PureComponent {
this.setBodyClass();
}
componentWillUnmount() {
- __webpack_require__.g.document.body.classList.remove("no-scroll");
+ globalThis.document.body.classList.remove("no-scroll");
}
render() {
const {
@@ -1262,11 +1268,11 @@ class ContextMenu extends (external_React_default()).PureComponent {
componentDidMount() {
this.onShow();
setTimeout(() => {
- __webpack_require__.g.addEventListener("click", this.hideContext);
+ globalThis.addEventListener("click", this.hideContext);
}, 0);
}
componentWillUnmount() {
- __webpack_require__.g.removeEventListener("click", this.hideContext);
+ globalThis.removeEventListener("click", this.hideContext);
}
onClick(event) {
// Eat all clicks on the context menu so they don't bubble up to window.
@@ -1392,23 +1398,21 @@ class _ContextMenuItem extends (external_React_default()).PureComponent {
const ContextMenuItem = (0,external_ReactRedux_namespaceObject.connect)(state => ({
Prefs: state.Prefs
}))(_ContextMenuItem);
-;// CONCATENATED MODULE: ./content-src/lib/link-menu-options.js
+;// CONCATENATED MODULE: ./content-src/lib/link-menu-options.mjs
/* 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/. */
+
const _OpenInPrivateWindow = site => ({
id: "newtab-menu-open-new-private-window",
icon: "new-window-private",
action: actionCreators.OnlyToMain({
type: actionTypes.OPEN_PRIVATE_WINDOW,
- data: {
- url: site.url,
- referrer: site.referrer
- }
+ data: { url: site.url, referrer: site.referrer },
}),
- userEvent: "OPEN_PRIVATE_WINDOW"
+ userEvent: "OPEN_PRIVATE_WINDOW",
});
/**
@@ -1417,19 +1421,15 @@ const _OpenInPrivateWindow = site => ({
* the index of the site.
*/
const LinkMenuOptions = {
- Separator: () => ({
- type: "separator"
- }),
- EmptyItem: () => ({
- type: "empty"
- }),
+ Separator: () => ({ type: "separator" }),
+ EmptyItem: () => ({ type: "empty" }),
ShowPrivacyInfo: () => ({
id: "newtab-menu-show-privacy-info",
icon: "info",
action: {
- type: actionTypes.SHOW_PRIVACY_INFO
+ type: actionTypes.SHOW_PRIVACY_INFO,
},
- userEvent: "SHOW_PRIVACY_INFO"
+ userEvent: "SHOW_PRIVACY_INFO",
}),
AboutSponsored: site => ({
id: "newtab-menu-show-privacy-info",
@@ -1439,32 +1439,28 @@ const LinkMenuOptions = {
data: {
advertiser_name: (site.label || site.hostname).toLocaleLowerCase(),
position: site.sponsored_position,
- tile_id: site.sponsored_tile_id
- }
+ tile_id: site.sponsored_tile_id,
+ },
}),
- userEvent: "TOPSITE_SPONSOR_INFO"
+ userEvent: "TOPSITE_SPONSOR_INFO",
}),
RemoveBookmark: site => ({
id: "newtab-menu-remove-bookmark",
icon: "bookmark-added",
action: actionCreators.AlsoToMain({
type: actionTypes.DELETE_BOOKMARK_BY_ID,
- data: site.bookmarkGuid
+ data: site.bookmarkGuid,
}),
- userEvent: "BOOKMARK_DELETE"
+ userEvent: "BOOKMARK_DELETE",
}),
AddBookmark: site => ({
id: "newtab-menu-bookmark",
icon: "bookmark-hollow",
action: actionCreators.AlsoToMain({
type: actionTypes.BOOKMARK_URL,
- data: {
- url: site.url,
- title: site.title,
- type: site.type
- }
+ data: { url: site.url, title: site.title, type: site.type },
}),
- userEvent: "BOOKMARK_ADD"
+ userEvent: "BOOKMARK_ADD",
}),
OpenInNewWindow: site => ({
id: "newtab-menu-open-new-window",
@@ -1475,10 +1471,10 @@ const LinkMenuOptions = {
referrer: site.referrer,
typedBonus: site.typedBonus,
url: site.url,
- sponsored_tile_id: site.sponsored_tile_id
- }
+ sponsored_tile_id: site.sponsored_tile_id,
+ },
}),
- userEvent: "OPEN_NEW_WINDOW"
+ userEvent: "OPEN_NEW_WINDOW",
}),
// This blocks the url for regular stories,
// but also sends a message to DiscoveryStream with flight_id.
@@ -1499,20 +1495,20 @@ const LinkMenuOptions = {
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
- } : {}),
+ ...(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()
- } : {}),
+ ...(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"
- }))
+ ...(site.sponsored_tile_id ? { tile_id: site.sponsored_tile_id } : {}),
+ is_pocket_card: site.type === "CardGrid",
+ })),
}),
impression: actionCreators.ImpressionStats({
source: eventSource,
@@ -1520,13 +1516,12 @@ const LinkMenuOptions = {
tiles: tiles.map((site, index) => ({
id: site.guid,
pos: pos + index,
- ...(site.shim && site.shim.delete ? {
- shim: site.shim.delete
- } : {})
- }))
+ ...(site.shim && site.shim.delete ? { shim: site.shim.delete } : {}),
+ })),
}),
- userEvent: "BLOCK"
+ 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) => ({
@@ -1536,8 +1531,8 @@ const LinkMenuOptions = {
action: actionCreators.WebExtEvent(actionTypes.WEBEXT_DISMISS, {
source: eventSource,
url: site.url,
- action_position: index
- })
+ action_position: index,
+ }),
}),
DeleteUrl: (site, index, eventSource, isEnabled, siteInfo) => ({
id: "newtab-menu-delete-history",
@@ -1545,77 +1540,74 @@ const LinkMenuOptions = {
action: {
type: actionTypes.DIALOG_OPEN,
data: {
- onConfirm: [actionCreators.AlsoToMain({
- type: actionTypes.DELETE_HISTORY_URL,
- data: {
- url: site.url,
- pocket_id: site.pocket_id,
- forceBlock: site.bookmarkGuid
- }
- }), actionCreators.UserEvent(Object.assign({
- event: "DELETE",
- source: eventSource,
- action_position: index
- }, siteInfo))],
+ onConfirm: [
+ actionCreators.AlsoToMain({
+ type: actionTypes.DELETE_HISTORY_URL,
+ data: {
+ url: site.url,
+ pocket_id: site.pocket_id,
+ forceBlock: site.bookmarkGuid,
+ },
+ }),
+ actionCreators.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"],
+ 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"
- }
+ icon: "modal-delete",
+ },
},
- userEvent: "DIALOG_OPEN"
+ userEvent: "DIALOG_OPEN",
}),
ShowFile: site => ({
id: "newtab-menu-show-file",
icon: "search",
action: actionCreators.OnlyToMain({
type: actionTypes.SHOW_DOWNLOAD_FILE,
- data: {
- url: site.url
- }
- })
+ data: { url: site.url },
+ }),
}),
OpenFile: site => ({
id: "newtab-menu-open-file",
icon: "open-file",
action: actionCreators.OnlyToMain({
type: actionTypes.OPEN_DOWNLOAD_FILE,
- data: {
- url: site.url
- }
- })
+ data: { url: site.url },
+ }),
}),
CopyDownloadLink: site => ({
id: "newtab-menu-copy-download-link",
icon: "copy",
action: actionCreators.OnlyToMain({
type: actionTypes.COPY_DOWNLOAD_LINK,
- data: {
- url: site.url
- }
- })
+ data: { url: site.url },
+ }),
}),
GoToDownloadPage: site => ({
id: "newtab-menu-go-to-download-page",
icon: "download",
action: actionCreators.OnlyToMain({
type: actionTypes.OPEN_LINK,
- data: {
- url: site.referrer
- }
+ data: { url: site.referrer },
}),
- disabled: !site.referrer
+ disabled: !site.referrer,
}),
RemoveDownload: site => ({
id: "newtab-menu-remove-download",
icon: "delete",
action: actionCreators.OnlyToMain({
type: actionTypes.REMOVE_DOWNLOAD_FILE,
- data: {
- url: site.url
- }
- })
+ data: { url: site.url },
+ }),
}),
PinTopSite: (site, index) => ({
id: "newtab-menu-pin",
@@ -1624,23 +1616,19 @@ const LinkMenuOptions = {
type: actionTypes.TOP_SITES_PIN,
data: {
site,
- index
- }
+ index,
+ },
}),
- userEvent: "PIN"
+ userEvent: "PIN",
}),
UnpinTopSite: site => ({
id: "newtab-menu-unpin",
icon: "unpin",
action: actionCreators.AlsoToMain({
type: actionTypes.TOP_SITES_UNPIN,
- data: {
- site: {
- url: site.url
- }
- }
+ data: { site: { url: site.url } },
}),
- userEvent: "UNPIN"
+ userEvent: "UNPIN",
}),
SaveToPocket: (site, index, eventSource = "CARDGRID") => ({
id: "newtab-menu-save-to-pocket",
@@ -1648,65 +1636,76 @@ const LinkMenuOptions = {
action: actionCreators.AlsoToMain({
type: actionTypes.SAVE_TO_POCKET,
data: {
- site: {
- url: site.url,
- title: site.title
- }
- }
+ site: { url: site.url, title: site.title },
+ },
}),
impression: actionCreators.ImpressionStats({
source: eventSource,
pocket: 0,
- tiles: [{
- id: site.guid,
- pos: index,
- ...(site.shim && site.shim.save ? {
- shim: site.shim.save
- } : {})
- }]
+ tiles: [
+ {
+ id: site.guid,
+ pos: index,
+ ...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
+ },
+ ],
}),
- userEvent: "SAVE_TO_POCKET"
+ userEvent: "SAVE_TO_POCKET",
}),
DeleteFromPocket: site => ({
id: "newtab-menu-delete-pocket",
icon: "pocket-delete",
action: actionCreators.AlsoToMain({
type: actionTypes.DELETE_FROM_POCKET,
- data: {
- pocket_id: site.pocket_id
- }
+ data: { pocket_id: site.pocket_id },
}),
- userEvent: "DELETE_FROM_POCKET"
+ userEvent: "DELETE_FROM_POCKET",
}),
ArchiveFromPocket: site => ({
id: "newtab-menu-archive-pocket",
icon: "pocket-archive",
action: actionCreators.AlsoToMain({
type: actionTypes.ARCHIVE_FROM_POCKET,
- data: {
- pocket_id: site.pocket_id
- }
+ data: { pocket_id: site.pocket_id },
}),
- userEvent: "ARCHIVE_FROM_POCKET"
+ userEvent: "ARCHIVE_FROM_POCKET",
}),
EditTopSite: (site, index) => ({
id: "newtab-menu-edit-topsites",
icon: "edit",
action: {
type: actionTypes.TOP_SITES_EDIT,
- data: {
- index
- }
- }
+ 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()
+ 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(),
};
+
;// CONCATENATED MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -1927,21 +1926,47 @@ class DSLinkMenu extends (external_React_default()).PureComponent {
})));
}
}
-;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSitesConstants.js
+;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSitesConstants.mjs
/* 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/. */
const TOP_SITES_SOURCE = "TOP_SITES";
-const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
-const TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "ShowPrivacyInfo"];
-const TOP_SITES_SPONSORED_POSITION_CONTEXT_MENU_OPTIONS = ["OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "AboutSponsored"];
+const TOP_SITES_CONTEXT_MENU_OPTIONS = [
+ "CheckPinTopSite",
+ "EditTopSite",
+ "Separator",
+ "OpenInNewWindow",
+ "OpenInPrivateWindow",
+ "Separator",
+ "BlockUrl",
+ "DeleteUrl",
+];
+const TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS = [
+ "OpenInNewWindow",
+ "OpenInPrivateWindow",
+ "Separator",
+ "BlockUrl",
+ "ShowPrivacyInfo",
+];
+const TOP_SITES_SPONSORED_POSITION_CONTEXT_MENU_OPTIONS = [
+ "OpenInNewWindow",
+ "OpenInPrivateWindow",
+ "Separator",
+ "BlockUrl",
+ "AboutSponsored",
+];
// the special top site for search shortcut experiment can only have the option to unpin (which removes) the topsite
-const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "BlockUrl"];
+const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = [
+ "CheckPinTopSite",
+ "Separator",
+ "BlockUrl",
+];
// minimum size necessary to show a rich icon instead of a screenshot
const MIN_RICH_FAVICON_SIZE = 96;
// minimum size necessary to show any icon
const MIN_SMALL_FAVICON_SIZE = 16;
+
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -2033,8 +2058,10 @@ class ImpressionStats_ImpressionStats extends (external_React_default()).PureCom
...(link.shim ? {
shim: link.shim
} : {}),
- recommendation_id: link.recommendation_id
- }))
+ recommendation_id: link.recommendation_id,
+ fetchTimestamp: link.fetchTimestamp
+ })),
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
}));
this.impressionCardGuids = cards.map(link => link.id);
}
@@ -2146,8 +2173,8 @@ class ImpressionStats_ImpressionStats extends (external_React_default()).PureCom
}
}
ImpressionStats_ImpressionStats.defaultProps = {
- IntersectionObserver: __webpack_require__.g.IntersectionObserver,
- document: __webpack_require__.g.document,
+ IntersectionObserver: globalThis.IntersectionObserver,
+ document: globalThis.document,
rows: [],
source: ""
};
@@ -2224,7 +2251,7 @@ class SafeAnchor extends (external_React_default()).PureComponent {
}, this.props.children);
}
}
-;// CONCATENATED MODULE: ./content-src/components/Card/types.js
+;// CONCATENATED MODULE: ./content-src/components/Card/types.mjs
/* 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/. */
@@ -2232,29 +2259,30 @@ class SafeAnchor extends (external_React_default()).PureComponent {
const cardContextTypes = {
history: {
fluentID: "newtab-label-visited",
- icon: "history-item"
+ icon: "history-item",
},
removedBookmark: {
fluentID: "newtab-label-removed-bookmark",
- icon: "bookmark-removed"
+ icon: "bookmark-removed",
},
bookmark: {
fluentID: "newtab-label-bookmarked",
- icon: "bookmark-added"
+ icon: "bookmark-added",
},
trending: {
fluentID: "newtab-label-recommended",
- icon: "trending"
+ icon: "trending",
},
pocket: {
fluentID: "newtab-label-saved",
- icon: "pocket"
+ icon: "pocket",
},
download: {
fluentID: "newtab-label-download",
- icon: "download"
- }
+ icon: "download",
+ },
};
+
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -2710,7 +2738,9 @@ class _DSCard extends (external_React_default()).PureComponent {
tile_id: this.props.id,
...(this.props.shim && this.props.shim.click ? {
shim: this.props.shim.click
- } : {})
+ } : {}),
+ fetchTimestamp: this.props.fetchTimestamp,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
}
}));
this.props.dispatch(actionCreators.ImpressionStats({
@@ -2751,7 +2781,9 @@ class _DSCard extends (external_React_default()).PureComponent {
tile_id: this.props.id,
...(this.props.shim && this.props.shim.save ? {
shim: this.props.shim.save
- } : {})
+ } : {}),
+ fetchTimestamp: this.props.fetchTimestamp,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
}
}));
this.props.dispatch(actionCreators.ImpressionStats({
@@ -2913,10 +2945,12 @@ class _DSCard extends (external_React_default()).PureComponent {
...(this.props.shim && this.props.shim.impression ? {
shim: this.props.shim.impression
} : {}),
- recommendation_id: this.props.recommendation_id
+ recommendation_id: this.props.recommendation_id,
+ fetchTimestamp: this.props.fetchTimestamp
}],
dispatch: this.props.dispatch,
- source: this.props.type
+ source: this.props.type,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
})), ctaButtonVariant === "variant-b" && /*#__PURE__*/external_React_default().createElement("div", {
className: "cta-header"
}, "Shop Now"), /*#__PURE__*/external_React_default().createElement(DefaultMeta, {
@@ -3273,7 +3307,7 @@ function DSSubHeader({
}
function OnboardingExperience({
dispatch,
- windowObj = __webpack_require__.g
+ windowObj = globalThis
}) {
const [dismissed, setDismissed] = (0,external_React_namespaceObject.useState)(false);
const [maxHeight, setMaxHeight] = (0,external_React_namespaceObject.useState)(null);
@@ -3549,6 +3583,7 @@ class _CardGrid extends (external_React_default()).PureComponent {
url: rec.url,
id: rec.id,
shim: rec.shim,
+ fetchTimestamp: rec.fetchTimestamp,
type: this.props.type,
context: rec.context,
sponsor: rec.sponsor,
@@ -3564,7 +3599,8 @@ class _CardGrid extends (external_React_default()).PureComponent {
ctaButtonSponsors: ctaButtonSponsors,
ctaButtonVariant: ctaButtonVariant,
spocMessageVariant: spocMessageVariant,
- recommendation_id: rec.recommendation_id
+ recommendation_id: rec.recommendation_id,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
}));
}
if (widgets?.positions?.length && widgets?.data?.length) {
@@ -4023,7 +4059,7 @@ class _CollapsibleSection extends (external_React_default()).PureComponent {
}
}
_CollapsibleSection.defaultProps = {
- document: __webpack_require__.g.document || {
+ document: globalThis.document || {
addEventListener: () => {},
removeEventListener: () => {},
visibilityState: "hidden"
@@ -4111,7 +4147,7 @@ class ModalOverlayWrapper extends (external_React_default()).PureComponent {
}
}
ModalOverlayWrapper.defaultProps = {
- document: __webpack_require__.g.document
+ document: globalThis.document
};
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -4443,7 +4479,7 @@ class DSTextPromo extends (external_React_default()).PureComponent {
})));
}
}
-;// CONCATENATED MODULE: ./content-src/lib/screenshot-utils.js
+;// CONCATENATED MODULE: ./content-src/lib/screenshot-utils.mjs
/* 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/. */
@@ -4462,8 +4498,13 @@ class DSTextPromo extends (external_React_default()).PureComponent {
*/
const ScreenshotUtils = {
isBlob(isLocal, image) {
- return !!(image && image.path && (!isLocal && image.data || isLocal && image.url));
+ 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) {
@@ -4471,33 +4512,36 @@ const ScreenshotUtils = {
}
if (this.isBlob(false, remoteImage)) {
return {
- url: __webpack_require__.g.URL.createObjectURL(remoteImage.data),
- path: remoteImage.path
+ url: globalThis.URL.createObjectURL(remoteImage.data),
+ path: remoteImage.path,
};
}
- return {
- url: remoteImage
- };
+ 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)) {
- __webpack_require__.g.URL.revokeObjectURL(localImage.url);
+ 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;
+ 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;
- }
+ },
};
+
;// CONCATENATED MODULE: ./content-src/components/Card/Card.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -4822,14 +4866,13 @@ const PlaceholderCard = props => /*#__PURE__*/external_React_default().createEle
placeholder: true,
className: props.className
});
-;// CONCATENATED MODULE: ./content-src/lib/perf-service.js
+;// CONCATENATED MODULE: ./content-src/lib/perf-service.mjs
/* 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;
+
function _PerfService(options) {
// For testing, so that we can use a fake Window.performance object with
// known state.
@@ -4839,6 +4882,7 @@ function _PerfService(options) {
this._perf = usablePerfObj;
}
}
+
_PerfService.prototype = {
/**
* Calls the underlying mark() method on the appropriate Window.performance
@@ -4851,6 +4895,7 @@ _PerfService.prototype = {
mark: function mark(str) {
this._perf.mark(str);
},
+
/**
* Calls the underlying getEntriesByName on the appropriate Window.performance
* object.
@@ -4859,9 +4904,10 @@ _PerfService.prototype = {
* @param {String} type eg "mark"
* @return {Array} Performance* objects
*/
- getEntriesByName: function getEntriesByName(name, type) {
- return this._perf.getEntriesByName(name, type);
+ 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
@@ -4880,6 +4926,7 @@ _PerfService.prototype = {
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)
@@ -4890,6 +4937,7 @@ _PerfService.prototype = {
absNow: function absNow() {
return this.timeOrigin + this._perf.now();
},
+
/**
* This returns the absolute startTime from the most recent performance.mark()
* with the given name.
@@ -4908,16 +4956,20 @@ _PerfService.prototype = {
* See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
* for more info.
*/
- getMostRecentAbsMarkStartByName(name) {
- let entries = this.getEntriesByName(name, "mark");
+ getMostRecentAbsMarkStartByName(entryName) {
+ let entries = this.getEntriesByName(entryName, "mark");
+
if (!entries.length) {
- throw new Error(`No marks with the name ${name}`);
+ throw new Error(`No marks with the name ${entryName}`);
}
+
let mostRecentEntry = entries[entries.length - 1];
return this._perf.timeOrigin + mostRecentEntry.startTime;
- }
+ },
};
+
const perfService = new _PerfService();
+
;// CONCATENATED MODULE: ./content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -5479,6 +5531,9 @@ const INITIAL_STATE = {
// Hide the search box after handing off to AwesomeBar and user starts typing.
hide: false,
},
+ Wallpapers: {
+ wallpaperList: [],
+ },
};
function App(prevState = INITIAL_STATE.App, action) {
@@ -6219,6 +6274,15 @@ function Search(prevState = INITIAL_STATE.Search, action) {
}
}
+function Wallpapers(prevState = INITIAL_STATE.Wallpapers, action) {
+ switch (action.type) {
+ case actionTypes.WALLPAPERS_SET:
+ return { wallpaperList: action.data };
+ default:
+ return prevState;
+ }
+}
+
const reducers = {
TopSites,
App,
@@ -6230,6 +6294,7 @@ const reducers = {
Personalization: Reducers_sys_Personalization,
DiscoveryStream,
Search,
+ Wallpapers,
};
;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteFormInput.jsx
@@ -6448,8 +6513,8 @@ class TopSiteImpressionWrapper extends (external_React_default()).PureComponent
}
}
TopSiteImpressionWrapper.defaultProps = {
- IntersectionObserver: __webpack_require__.g.IntersectionObserver,
- document: __webpack_require__.g.document,
+ IntersectionObserver: globalThis.IntersectionObserver,
+ document: globalThis.document,
actionType: null,
tile: null
};
@@ -7601,7 +7666,7 @@ class _TopSites extends (external_React_default()).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 (!__webpack_require__.g.matchMedia(`(min-width: 1072px)`).matches) {
+ if (!globalThis.matchMedia(`(min-width: 1072px)`).matches) {
sitesPerRow -= 2;
}
return this.props.TopSites.rows.slice(0, this.props.TopSitesRows * sitesPerRow);
@@ -7733,7 +7798,7 @@ class Section extends (external_React_default()).PureComponent {
props
} = this;
let cardsPerRow = CARDS_PER_ROW_DEFAULT;
- if (props.compactCards && __webpack_require__.g.matchMedia(`(min-width: 1072px)`).matches) {
+ if (props.compactCards && 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.
// $break-point-widest = 1072px (from _variables.scss)
@@ -7969,7 +8034,7 @@ class Section extends (external_React_default()).PureComponent {
}
}
Section.defaultProps = {
- document: __webpack_require__.g.document,
+ document: globalThis.document,
rows: [],
emptyState: {},
pref: {},
@@ -8188,20 +8253,13 @@ class SectionTitle extends (external_React_default()).PureComponent {
}, subtitle) : null);
}
}
-;// CONCATENATED MODULE: ./content-src/lib/selectLayoutRender.js
+;// CONCATENATED MODULE: ./content-src/lib/selectLayoutRender.mjs
/* 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/. */
-const selectLayoutRender = ({
- state = {},
- prefs = {}
-}) => {
- const {
- layout,
- feeds,
- spocs
- } = state;
+const selectLayoutRender = ({ state = {}, prefs = {} }) => {
+ const { layout, feeds, spocs } = state;
let spocIndexPlacementMap = {};
/* This function fills spoc positions on a per placement basis with available spocs.
@@ -8210,8 +8268,16 @@ const selectLayoutRender = ({
* If it sees the same placement again, it remembers the previous spoc index, and continues.
* If it sees a blocked spoc, it skips that position leaving in a regular story.
*/
- function fillSpocPositionsForPlacement(data, spocsConfig, spocsData, placementName) {
- if (!spocIndexPlacementMap[placementName] && spocIndexPlacementMap[placementName] !== 0) {
+ function fillSpocPositionsForPlacement(
+ data,
+ spocsConfig,
+ spocsData,
+ placementName
+ ) {
+ if (
+ !spocIndexPlacementMap[placementName] &&
+ spocIndexPlacementMap[placementName] !== 0
+ ) {
spocIndexPlacementMap[placementName] = 0;
}
const results = [...data];
@@ -8234,107 +8300,154 @@ const selectLayoutRender = ({
results.splice(position.index, 0, spoc);
}
}
+
return results;
}
+
const positions = {};
- const DS_COMPONENTS = ["Message", "TextPromo", "SectionTitle", "Signup", "Navigation", "CardGrid", "CollectionCardGrid", "HorizontalRule", "PrivacyLink"];
+ const DS_COMPONENTS = [
+ "Message",
+ "TextPromo",
+ "SectionTitle",
+ "Signup",
+ "Navigation",
+ "CardGrid",
+ "CollectionCardGrid",
+ "HorizontalRule",
+ "PrivacyLink",
+ ];
+
const filterArray = [];
+
if (!prefs["feeds.topsites"]) {
filterArray.push("TopSites");
}
- const pocketEnabled = prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
+
+ const pocketEnabled =
+ prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
if (!pocketEnabled) {
filterArray.push(...DS_COMPONENTS);
}
+
const placeholderComponent = component => {
if (!component.feed) {
// TODO we now need a placeholder for topsites and textPromo.
return {
...component,
data: {
- spocs: []
- }
+ spocs: [],
+ },
};
}
const data = {
- recommendations: []
+ recommendations: [],
};
+
let items = 0;
if (component.properties && component.properties.items) {
items = component.properties.items;
}
for (let i = 0; i < items; i++) {
- data.recommendations.push({
- placeholder: true
- });
+ data.recommendations.push({ placeholder: true });
}
- return {
- ...component,
- data
- };
+
+ return { ...component, data };
};
// TODO update devtools to show placements
const handleSpocs = (data, component) => {
let result = [...data];
// Do we ever expect to possibly have a spoc.
- if (component.spocs && component.spocs.positions && component.spocs.positions.length) {
+ if (
+ component.spocs &&
+ component.spocs.positions &&
+ component.spocs.positions.length
+ ) {
const placement = component.placement || {};
const placementName = placement.name || "spocs";
const spocsData = spocs.data[placementName];
// We expect a spoc, spocs are loaded, and the server returned spocs.
- if (spocs.loaded && spocsData && spocsData.items && spocsData.items.length) {
- result = fillSpocPositionsForPlacement(result, component.spocs, spocsData.items, placementName);
+ if (
+ spocs.loaded &&
+ spocsData &&
+ spocsData.items &&
+ spocsData.items.length
+ ) {
+ result = fillSpocPositionsForPlacement(
+ result,
+ component.spocs,
+ spocsData.items,
+ placementName
+ );
}
}
return result;
};
+
const handleComponent = component => {
- if (component.spocs && component.spocs.positions && component.spocs.positions.length) {
+ if (
+ component.spocs &&
+ component.spocs.positions &&
+ component.spocs.positions.length
+ ) {
const placement = component.placement || {};
const placementName = placement.name || "spocs";
const spocsData = spocs.data[placementName];
- if (spocs.loaded && spocsData && spocsData.items && spocsData.items.length) {
+ if (
+ spocs.loaded &&
+ spocsData &&
+ spocsData.items &&
+ spocsData.items.length
+ ) {
return {
...component,
data: {
- spocs: spocsData.items.filter(spoc => spoc && !spocs.blocked.includes(spoc.url)).map((spoc, index) => ({
- ...spoc,
- pos: index
- }))
- }
+ spocs: spocsData.items
+ .filter(spoc => spoc && !spocs.blocked.includes(spoc.url))
+ .map((spoc, index) => ({
+ ...spoc,
+ pos: index,
+ })),
+ },
};
}
}
return {
...component,
data: {
- spocs: []
- }
+ spocs: [],
+ },
};
};
+
const handleComponentWithFeed = component => {
positions[component.type] = positions[component.type] || 0;
let data = {
- recommendations: []
+ recommendations: [],
};
+
const feed = feeds.data[component.feed.url];
if (feed && feed.data) {
data = {
...feed.data,
- recommendations: [...(feed.data.recommendations || [])]
+ recommendations: [...(feed.data.recommendations || [])],
};
}
+
if (component && component.properties && component.properties.offset) {
data = {
...data,
- recommendations: data.recommendations.slice(component.properties.offset)
+ recommendations: data.recommendations.slice(
+ component.properties.offset
+ ),
};
}
+
data = {
...data,
- recommendations: handleSpocs(data.recommendations, component)
+ recommendations: handleSpocs(data.recommendations, component),
};
+
let items = 0;
if (component.properties && component.properties.items) {
items = Math.min(component.properties.items, data.recommendations.length);
@@ -8346,27 +8459,36 @@ const selectLayoutRender = ({
for (let i = 0; i < items; i++) {
data.recommendations[i] = {
...data.recommendations[i],
- pos: positions[component.type]++
+ pos: positions[component.type]++,
};
}
- return {
- ...component,
- data
- };
+
+ return { ...component, data };
};
+
const renderLayout = () => {
const renderedLayoutArray = [];
- for (const row of layout.filter(r => r.components.filter(c => !filterArray.includes(c.type)).length)) {
+ for (const row of layout.filter(
+ r => r.components.filter(c => !filterArray.includes(c.type)).length
+ )) {
let components = [];
renderedLayoutArray.push({
...row,
- components
+ components,
});
- for (const component of row.components.filter(c => !filterArray.includes(c.type))) {
+ for (const component of row.components.filter(
+ c => !filterArray.includes(c.type)
+ )) {
const spocsConfig = component.spocs;
if (spocsConfig || component.feed) {
// TODO make sure this still works for different loading cases.
- if (component.feed && !feeds.data[component.feed.url] || spocsConfig && spocsConfig.positions && spocsConfig.positions.length && !spocs.loaded) {
+ if (
+ (component.feed && !feeds.data[component.feed.url]) ||
+ (spocsConfig &&
+ spocsConfig.positions &&
+ spocsConfig.positions.length &&
+ !spocs.loaded)
+ ) {
components.push(placeholderComponent(component));
return renderedLayoutArray;
}
@@ -8382,11 +8504,12 @@ const selectLayoutRender = ({
}
return renderedLayoutArray;
};
+
const layoutRender = renderLayout();
- return {
- layoutRender
- };
+
+ return { layoutRender };
};
+
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -8528,19 +8651,21 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
privacyNoticeURL: component.properties.privacyNoticeURL
});
case "CollectionCardGrid":
- const {
- DiscoveryStream
- } = this.props;
- return /*#__PURE__*/external_React_default().createElement(CollectionCardGrid, {
- data: component.data,
- feed: component.feed,
- spocs: DiscoveryStream.spocs,
- placement: component.placement,
- type: component.type,
- items: component.properties.items,
- dismissible: this.props.DiscoveryStream.isCollectionDismissible,
- dispatch: this.props.dispatch
- });
+ {
+ const {
+ DiscoveryStream
+ } = this.props;
+ return /*#__PURE__*/external_React_default().createElement(CollectionCardGrid, {
+ data: component.data,
+ feed: component.feed,
+ spocs: DiscoveryStream.spocs,
+ placement: component.placement,
+ type: component.type,
+ items: component.properties.items,
+ dismissible: this.props.DiscoveryStream.isCollectionDismissible,
+ dispatch: this.props.dispatch
+ });
+ }
case "CardGrid":
return /*#__PURE__*/external_React_default().createElement(CardGrid, {
title: component.header && component.header.title,
@@ -8561,7 +8686,8 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
spocMessageVariant: component.properties.spocMessageVariant,
editorsPicksHeader: component.properties.editorsPicksHeader,
recentSavesEnabled: this.props.DiscoveryStream.recentSavesEnabled,
- hideDescriptions: this.props.DiscoveryStream.hideDescriptions
+ hideDescriptions: this.props.DiscoveryStream.hideDescriptions,
+ firstVisibleTimestamp: this.props.firstVisibleTimestamp
});
case "HorizontalRule":
return /*#__PURE__*/external_React_default().createElement(HorizontalRule, null);
@@ -8718,20 +8844,87 @@ const DiscoveryStreamBase = (0,external_ReactRedux_namespaceObject.connect)(stat
DiscoveryStream: state.DiscoveryStream,
Prefs: state.Prefs,
Sections: state.Sections,
- document: __webpack_require__.g.document,
+ document: globalThis.document,
App: state.App
}))(_DiscoveryStreamBase);
-;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/BackgroundsSection/BackgroundsSection.jsx
+;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpapersSection.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-class BackgroundsSection extends (external_React_default()).PureComponent {
+
+class _WallpapersSection extends (external_React_default()).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() {
- return /*#__PURE__*/external_React_default().createElement("div", null);
+ const {
+ wallpaperList
+ } = this.props.Wallpapers;
+ const {
+ activeWallpaper
+ } = this.props;
+ return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("fieldset", {
+ className: "wallpaper-list"
+ }, wallpaperList.map(({
+ title,
+ theme,
+ fluent_id
+ }) => {
+ return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("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}`
+ }), /*#__PURE__*/external_React_default().createElement("label", {
+ htmlFor: title,
+ className: "sr-only",
+ "data-l10n-id": fluent_id
+ }, fluent_id));
+ })), /*#__PURE__*/external_React_default().createElement("button", {
+ className: "wallpapers-reset",
+ onClick: this.handleReset,
+ "data-l10n-id": "newtab-wallpaper-reset"
+ }));
}
}
+const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state => {
+ return {
+ Wallpapers: state.Wallpapers,
+ Prefs: state.Prefs
+ };
+})(_WallpapersSection);
;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -8740,6 +8933,7 @@ class BackgroundsSection extends (external_React_default()).PureComponent {
+
class ContentSection extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@@ -8818,7 +9012,10 @@ class ContentSection extends (external_React_default()).PureComponent {
mayHaveSponsoredStories,
mayHaveRecentSaves,
openPreferences,
- spocMessageVariant
+ spocMessageVariant,
+ wallpapersEnabled,
+ activeWallpaper,
+ setPref
} = this.props;
const {
topSitesEnabled,
@@ -8831,7 +9028,14 @@ class ContentSection extends (external_React_default()).PureComponent {
} = enabledSections;
return /*#__PURE__*/external_React_default().createElement("div", {
className: "home-section"
- }, /*#__PURE__*/external_React_default().createElement("div", {
+ }, wallpapersEnabled && /*#__PURE__*/external_React_default().createElement("div", {
+ className: "wallpapers-section"
+ }, /*#__PURE__*/external_React_default().createElement("h2", {
+ "data-l10n-id": "newtab-wallpaper-title"
+ }), /*#__PURE__*/external_React_default().createElement(WallpapersSection, {
+ setPref: setPref,
+ activeWallpaper: activeWallpaper
+ })), /*#__PURE__*/external_React_default().createElement("div", {
id: "shortcuts-section",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
@@ -8979,7 +9183,6 @@ class ContentSection extends (external_React_default()).PureComponent {
-
class _CustomizeMenu extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@@ -9023,10 +9226,12 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
className: "close-button",
"data-l10n-id": "newtab-custom-close-button",
ref: c => this.closeButton = c
- }), /*#__PURE__*/external_React_default().createElement(BackgroundsSection, null), /*#__PURE__*/external_React_default().createElement(ContentSection, {
+ }), /*#__PURE__*/external_React_default().createElement(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,
@@ -9039,44 +9244,46 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
const CustomizeMenu = (0,external_ReactRedux_namespaceObject.connect)(state => ({
DiscoveryStream: state.DiscoveryStream
}))(_CustomizeMenu);
-;// CONCATENATED MODULE: ./content-src/lib/constants.js
+;// CONCATENATED MODULE: ./content-src/lib/constants.mjs
/* 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/. */
-const IS_NEWTAB = __webpack_require__.g.document && __webpack_require__.g.document.documentURI === "about:newtab";
+const IS_NEWTAB =
+ globalThis.document && globalThis.document.documentURI === "about:newtab";
const NEWTAB_DARK_THEME = {
ntp_background: {
r: 42,
g: 42,
b: 46,
- a: 1
+ a: 1,
},
ntp_card_background: {
r: 66,
g: 65,
b: 77,
- a: 1
+ a: 1,
},
ntp_text: {
r: 249,
g: 249,
b: 250,
- a: 1
+ a: 1,
},
sidebar: {
r: 56,
g: 56,
b: 61,
- a: 1
+ a: 1,
},
sidebar_text: {
r: 249,
g: 249,
b: 250,
- a: 1
- }
+ a: 1,
+ },
};
+
;// CONCATENATED MODULE: ./content-src/components/Search/Search.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -9258,6 +9465,8 @@ function Base_extends() { Base_extends = Object.assign ? Object.assign.bind() :
+const Base_VISIBLE = "visible";
+const Base_VISIBILITY_CHANGE_EVENT = "visibilitychange";
const PrefsButton = ({
onClick,
icon
@@ -9306,7 +9515,7 @@ class _Base extends (external_React_default()).PureComponent {
// If we skipped the about:welcome overlay and removed the CSS classes
// we don't want to add them back to the Activity Stream view
document.body.classList.contains("inline-onboarding") ? "inline-onboarding" : ""].filter(v => v).join(" ");
- __webpack_require__.g.document.body.className = bodyClassName;
+ globalThis.document.body.className = bodyClassName;
}
render() {
const {
@@ -9337,17 +9546,55 @@ class BaseContent extends (external_React_default()).PureComponent {
this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.setPref = this.setPref.bind(this);
+ this.updateWallpaper = this.updateWallpaper.bind(this);
+ this.prefersDarkQuery = null;
+ this.handleColorModeChange = this.handleColorModeChange.bind(this);
this.state = {
- fixedSearch: false
+ fixedSearch: false,
+ firstVisibleTimestamp: null,
+ colorMode: ""
};
}
+ setFirstVisibleTimestamp() {
+ if (!this.state.firstVisibleTimestamp) {
+ this.setState({
+ firstVisibleTimestamp: Date.now()
+ });
+ }
+ }
componentDidMount() {
__webpack_require__.g.addEventListener("scroll", this.onWindowScroll);
__webpack_require__.g.addEventListener("keydown", this.handleOnKeyDown);
+ if (this.props.document.visibilityState === Base_VISIBLE) {
+ this.setFirstVisibleTimestamp();
+ } else {
+ this._onVisibilityChange = () => {
+ if (this.props.document.visibilityState === Base_VISIBLE) {
+ this.setFirstVisibleTimestamp();
+ this.props.document.removeEventListener(Base_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ this._onVisibilityChange = null;
+ }
+ };
+ this.props.document.addEventListener(Base_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);
__webpack_require__.g.removeEventListener("scroll", this.onWindowScroll);
__webpack_require__.g.removeEventListener("keydown", this.handleOnKeyDown);
+ if (this._onVisibilityChange) {
+ this.props.document.removeEventListener(Base_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ }
}
onWindowScroll() {
const prefs = this.props.Prefs.values;
@@ -9396,6 +9643,53 @@ class BaseContent extends (external_React_default()).PureComponent {
setPref(pref, value) {
this.props.dispatch(actionCreators.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 /*#__PURE__*/external_React_default().createElement("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
+ })
+ }, /*#__PURE__*/external_React_default().createElement("a", {
+ "data-l10n-name": "name-link",
+ href: name.url
+ }, name.string), /*#__PURE__*/external_React_default().createElement("a", {
+ "data-l10n-name": "webpage-link",
+ href: webpage.url
+ }, webpage.string));
+ }
+ 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"]) || "";
+ __webpack_require__.g.document?.body.style.setProperty(`--newtab-wallpaper-light`, `url(${lightWallpaper?.wallpaperUrl || ""})`);
+ __webpack_require__.g.document?.body.style.setProperty(`--newtab-wallpaper-dark`, `url(${darkWallpaper?.wallpaperUrl || ""})`);
+ }
+ }
render() {
const {
props
@@ -9408,6 +9702,8 @@ class BaseContent extends (external_React_default()).PureComponent {
customizeMenuVisible
} = App;
const prefs = props.Prefs.values;
+ const activeWallpaper = prefs[`newtabWallpapers.wallpaper-${this.state.colorMode}`];
+ const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
const {
pocketConfig
} = prefs;
@@ -9435,12 +9731,17 @@ class BaseContent extends (external_React_default()).PureComponent {
mayHaveSponsoredTopSites
} = prefs;
const outerClassName = ["outer-wrapper", isDiscoveryStream && pocketEnabled && "ds-outer-wrapper-search-alignment", isDiscoveryStream && "ds-outer-wrapper-breakpoint-override", prefs.showSearch && this.state.fixedSearch && !noSectionsEnabled && "fixed-search", prefs.showSearch && noSectionsEnabled && "only-search", prefs["logowordmark.alwaysVisible"] && "visible-logo"].filter(v => v).join(" ");
+ if (wallpapersEnabled) {
+ this.updateWallpaper();
+ }
return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement(CustomizeMenu, {
onClose: this.closeCustomizationMenu,
onOpen: this.openCustomizationMenu,
openPreferences: this.openPreferences,
setPref: this.setPref,
enabledSections: enabledSections,
+ wallpapersEnabled: wallpapersEnabled,
+ activeWallpaper: activeWallpaper,
pocketRegion: pocketRegion,
mayHaveSponsoredTopSites: mayHaveSponsoredTopSites,
mayHaveSponsoredStories: mayHaveSponsoredStories,
@@ -9460,31 +9761,38 @@ class BaseContent extends (external_React_default()).PureComponent {
className: "borderless-error"
}, /*#__PURE__*/external_React_default().createElement(DiscoveryStreamBase, {
locale: props.App.locale,
- mayHaveSponsoredStories: mayHaveSponsoredStories
- })) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null))));
+ mayHaveSponsoredStories: mayHaveSponsoredStories,
+ firstVisibleTimestamp: this.state.firstVisibleTimestamp
+ })) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null), wallpapersEnabled && this.renderWallpaperAttribution())));
}
}
+BaseContent.defaultProps = {
+ document: __webpack_require__.g.document
+};
const Base = (0,external_ReactRedux_namespaceObject.connect)(state => ({
App: state.App,
Prefs: state.Prefs,
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
- Search: state.Search
+ Search: state.Search,
+ Wallpapers: state.Wallpapers
}))(_Base);
-;// CONCATENATED MODULE: ./content-src/lib/detect-user-session-start.js
+;// CONCATENATED MODULE: ./content-src/lib/detect-user-session-start.mjs
/* 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/. */
+
const detect_user_session_start_VISIBLE = "visible";
const detect_user_session_start_VISIBILITY_CHANGE_EVENT = "visibilitychange";
+
class DetectUserSessionStart {
constructor(store, options = {}) {
this._store = store;
// Overrides for testing
- this.document = options.document || __webpack_require__.g.document;
+ this.document = options.document || globalThis.document;
this._perfService = options.perfService || perfService;
this._onVisibilityChange = this._onVisibilityChange.bind(this);
}
@@ -9502,7 +9810,10 @@ class DetectUserSessionStart {
this._sendEvent();
} else {
// If the document is not visible, listen for when it does become visible.
- this.document.addEventListener(detect_user_session_start_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ this.document.addEventListener(
+ detect_user_session_start_VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
}
}
@@ -9513,14 +9824,19 @@ class DetectUserSessionStart {
*/
_sendEvent() {
this._perfService.mark("visibility_event_rcvd_ts");
+
try {
- let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
- this._store.dispatch(actionCreators.AlsoToMain({
- type: actionTypes.SAVE_SESSION_PERF_DATA,
- data: {
- visibility_event_rcvd_ts
- }
- }));
+ let visibility_event_rcvd_ts =
+ this._perfService.getMostRecentAbsMarkStartByName(
+ "visibility_event_rcvd_ts"
+ );
+
+ this._store.dispatch(
+ actionCreators.AlsoToMain({
+ type: actionTypes.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.
@@ -9534,13 +9850,17 @@ class DetectUserSessionStart {
_onVisibilityChange() {
if (this.document.visibilityState === detect_user_session_start_VISIBLE) {
this._sendEvent();
- this.document.removeEventListener(detect_user_session_start_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
+ this.document.removeEventListener(
+ detect_user_session_start_VISIBILITY_CHANGE_EVENT,
+ this._onVisibilityChange
+ );
}
}
}
+
;// CONCATENATED MODULE: external "Redux"
const external_Redux_namespaceObject = Redux;
-;// CONCATENATED MODULE: ./content-src/lib/init-store.js
+;// CONCATENATED MODULE: ./content-src/lib/init-store.mjs
/* 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/. */
@@ -9548,6 +9868,10 @@ const external_Redux_namespaceObject = Redux;
/* eslint-env mozilla/remote-page */
+// 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
+
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
@@ -9572,11 +9896,9 @@ const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
function mergeStateReducer(mainReducer) {
return (prevState, action) => {
if (action.type === MERGE_STORE_ACTION) {
- return {
- ...prevState,
- ...action.data
- };
+ return { ...prevState, ...action.data };
}
+
return mainReducer(prevState, action);
};
}
@@ -9593,9 +9915,8 @@ const messageMiddleware = () => next => action => {
next(action);
}
};
-const rehydrationMiddleware = ({
- getState
-}) => {
+
+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;
@@ -9604,17 +9925,24 @@ const rehydrationMiddleware = ({
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) {
+ 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 === actionTypes.NEW_TAB_STATE_REQUEST;
+
if (isRehydrationRequest) {
getState.didRequestInitialState = true;
return next(action);
}
+
if (isMergeStoreAction) {
getState.didRehydrate = true;
return next(action);
@@ -9622,16 +9950,20 @@ const rehydrationMiddleware = ({
// If init happened after our request was made, we need to re-request
if (getState.didRequestInitialState && action.type === actionTypes.INIT) {
- return next(actionCreators.AlsoToMain({
- type: actionTypes.NEW_TAB_STATE_REQUEST
- }));
+ return next(actionCreators.AlsoToMain({ type: actionTypes.NEW_TAB_STATE_REQUEST }));
}
- if (actionUtils.isBroadcastToContent(action) || actionUtils.isSendToOneContent(action) || actionUtils.isSendToPreloaded(action)) {
+
+ if (
+ actionUtils.isBroadcastToContent(action) ||
+ actionUtils.isSendToOneContent(action) ||
+ actionUtils.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);
};
};
@@ -9644,19 +9976,31 @@ const rehydrationMiddleware = ({
* @return {object} A redux store
*/
function initStore(reducers, initialState) {
- const store = (0,external_Redux_namespaceObject.createStore)(mergeStateReducer((0,external_Redux_namespaceObject.combineReducers)(reducers)), initialState, __webpack_require__.g.RPMAddMessageListener && (0,external_Redux_namespaceObject.applyMiddleware)(rehydrationMiddleware, messageMiddleware));
- if (__webpack_require__.g.RPMAddMessageListener) {
- __webpack_require__.g.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
+ const store = (0,external_Redux_namespaceObject.createStore)(
+ mergeStateReducer((0,external_Redux_namespaceObject.combineReducers)(reducers)),
+ initialState,
+ globalThis.RPMAddMessageListener &&
+ (0,external_Redux_namespaceObject.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}`);
+ dump(
+ `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${
+ ex.stack
+ }`
+ );
}
});
}
+
return store;
}
+
;// CONCATENATED MODULE: external "ReactDOM"
const external_ReactDOM_namespaceObject = ReactDOM;
var external_ReactDOM_default = /*#__PURE__*/__webpack_require__.n(external_ReactDOM_namespaceObject);
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-beach.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-beach.avif
new file mode 100644
index 0000000000..5b77286079
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-beach.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-color.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-color.avif
new file mode 100644
index 0000000000..a4fc8e2341
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-color.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-landscape.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-landscape.avif
new file mode 100644
index 0000000000..ed22325f00
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-landscape.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-mountain.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-mountain.avif
new file mode 100644
index 0000000000..a704809a12
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-mountain.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-panda.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-panda.avif
new file mode 100644
index 0000000000..decfff669b
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-panda.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/dark-sky.avif b/browser/components/newtab/data/content/assets/wallpapers/dark-sky.avif
new file mode 100644
index 0000000000..51eea392ca
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/dark-sky.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-beach.avif b/browser/components/newtab/data/content/assets/wallpapers/light-beach.avif
new file mode 100644
index 0000000000..b5f7b2ae67
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-beach.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-color.avif b/browser/components/newtab/data/content/assets/wallpapers/light-color.avif
new file mode 100644
index 0000000000..3366b7aec6
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-color.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-landscape.avif b/browser/components/newtab/data/content/assets/wallpapers/light-landscape.avif
new file mode 100644
index 0000000000..1776091825
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-landscape.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-mountain.avif b/browser/components/newtab/data/content/assets/wallpapers/light-mountain.avif
new file mode 100644
index 0000000000..5983c942fc
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-mountain.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-panda.avif b/browser/components/newtab/data/content/assets/wallpapers/light-panda.avif
new file mode 100644
index 0000000000..d20f405e45
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-panda.avif
Binary files differ
diff --git a/browser/components/newtab/data/content/assets/wallpapers/light-sky.avif b/browser/components/newtab/data/content/assets/wallpapers/light-sky.avif
new file mode 100644
index 0000000000..f152f00e06
--- /dev/null
+++ b/browser/components/newtab/data/content/assets/wallpapers/light-sky.avif
Binary files differ
diff --git a/browser/components/newtab/karma.mc.config.js b/browser/components/newtab/karma.mc.config.js
index fa3ac14587..886b19df7b 100644
--- a/browser/components/newtab/karma.mc.config.js
+++ b/browser/components/newtab/karma.mc.config.js
@@ -158,6 +158,15 @@ module.exports = function (config) {
functions: 0,
branches: 0,
},
+ /**
+ * WallpaperFeed.sys.mjs is tested via an xpcshell test
+ */
+ "lib/WallpaperFeed.sys.mjs": {
+ statements: 0,
+ lines: 0,
+ functions: 0,
+ branches: 0,
+ },
"content-src/components/DiscoveryStreamComponents/**/*.jsx": {
statements: 90.48,
lines: 90.48,
@@ -170,6 +179,15 @@ module.exports = function (config) {
functions: 60,
branches: 50,
},
+ /**
+ * WallpaperSection.jsx is tested via an xpcshell test
+ */
+ "content-src/components/WallpapersSection/*.jsx": {
+ statements: 0,
+ lines: 0,
+ functions: 0,
+ branches: 0,
+ },
"content-src/components/DiscoveryStreamAdmin/*.jsx": {
statements: 0,
lines: 0,
@@ -211,7 +229,7 @@ module.exports = function (config) {
devtool: "inline-source-map",
// This resolve config allows us to import with paths relative to the root directory, e.g. "lib/ActivityStream.sys.mjs"
resolve: {
- extensions: [".js", ".jsx"],
+ extensions: [".js", ".jsx", ".mjs"],
modules: [PATHS.moduleResolveDirectory, "node_modules"],
alias: {
asrouter: path.join(__dirname, "../asrouter"),
@@ -260,7 +278,7 @@ module.exports = function (config) {
},
{
enforce: "post",
- test: /\.js[mx]?$/,
+ test: /\.js[x]?$/,
loader: "@jsdevtools/coverage-istanbul-loader",
options: { esModules: true },
include: [
diff --git a/browser/components/newtab/lib/AboutPreferences.sys.mjs b/browser/components/newtab/lib/AboutPreferences.sys.mjs
index 33f7ecdaeb..08e0ca422a 100644
--- a/browser/components/newtab/lib/AboutPreferences.sys.mjs
+++ b/browser/components/newtab/lib/AboutPreferences.sys.mjs
@@ -5,7 +5,7 @@
import {
actionTypes as at,
actionCreators as ac,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
const HTML_NS = "http://www.w3.org/1999/xhtml";
export const PREFERENCES_LOADED_EVENT = "home-pane-loaded";
diff --git a/browser/components/newtab/lib/ActivityStream.sys.mjs b/browser/components/newtab/lib/ActivityStream.sys.mjs
index f46e8aadf0..fa2d011f11 100644
--- a/browser/components/newtab/lib/ActivityStream.sys.mjs
+++ b/browser/components/newtab/lib/ActivityStream.sys.mjs
@@ -36,6 +36,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
TelemetryFeed: "resource://activity-stream/lib/TelemetryFeed.sys.mjs",
TopSitesFeed: "resource://activity-stream/lib/TopSitesFeed.sys.mjs",
TopStoriesFeed: "resource://activity-stream/lib/TopStoriesFeed.sys.mjs",
+ WallpaperFeed: "resource://activity-stream/lib/WallpaperFeed.sys.mjs",
});
// NB: Eagerly load modules that will be loaded/constructed/initialized in the
@@ -43,7 +44,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
import {
actionCreators as ac,
actionTypes as at,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
const REGION_BASIC_CONFIG =
"browser.newtabpage.activity-stream.discoverystream.region-basic-config";
@@ -233,6 +234,27 @@ export const PREFS_CONFIG = new Map([
},
],
[
+ "newtabWallpapers.enabled",
+ {
+ title: "Boolean flag to turn wallpaper functionality on and off",
+ value: true,
+ },
+ ],
+ [
+ "newtabWallpapers.wallpaper-light",
+ {
+ title: "Currently set light wallpaper",
+ value: "",
+ },
+ ],
+ [
+ "newtabWallpapers.wallpaper-dark",
+ {
+ title: "Currently set dark wallpaper",
+ value: "",
+ },
+ ],
+ [
"improvesearch.noDefaultSearchTile",
{
title: "Remove tiles that are the same as the default search",
@@ -524,6 +546,12 @@ const FEEDS_DATA = [
title: "Handles new pocket ui for the new tab page",
value: true,
},
+ {
+ name: "wallpaperfeed",
+ factory: () => new lazy.WallpaperFeed(),
+ title: "Handles fetching and managing wallpaper data from RemoteSettings",
+ value: true,
+ },
];
const FEEDS_CONFIG = new Map();
diff --git a/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs b/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
index 5392a421ca..3cb81b4793 100644
--- a/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
+++ b/browser/components/newtab/lib/ActivityStreamMessageChannel.sys.mjs
@@ -13,7 +13,7 @@ import {
actionCreators as ac,
actionTypes as at,
actionUtils as au,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
const ABOUT_NEW_TAB_URL = "about:newtab";
diff --git a/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs b/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
index ee08462503..bff9f1e04e 100644
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.sys.mjs
@@ -26,7 +26,7 @@ const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
import {
actionTypes as at,
actionCreators as ac,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
const CACHE_KEY = "discovery_stream";
const STARTUP_CACHE_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000; // 1 week
@@ -565,8 +565,8 @@ export class DiscoveryStreamFeed {
generateFeedUrl(isBff) {
if (isBff) {
- return `https://${lazy.NimbusFeatures.saveToPocket.getVariable(
- "bffApi"
+ return `https://${Services.prefs.getStringPref(
+ "extensions.pocket.bffApi"
)}/desktop/v1/recommendations?locale=$locale&region=$region&count=30`;
}
return FEED_URL;
@@ -986,8 +986,9 @@ export class DiscoveryStreamFeed {
});
if (spocsResponse) {
+ const fetchTimestamp = Date.now();
spocsState = {
- lastUpdated: Date.now(),
+ lastUpdated: fetchTimestamp,
spocs: {
...spocsResponse,
},
@@ -1050,8 +1051,13 @@ export class DiscoveryStreamFeed {
const { data: blockedResults } = this.filterBlocked(capResult);
+ const { data: spocsWithFetchTimestamp } = this.addFetchTimestamp(
+ blockedResults,
+ fetchTimestamp
+ );
+
const { data: scoredResults, personalized } =
- await this.scoreItems(blockedResults, "spocs");
+ await this.scoreItems(spocsWithFetchTimestamp, "spocs");
spocsState.spocs = {
...spocsState.spocs,
@@ -1209,6 +1215,22 @@ export class DiscoveryStreamFeed {
return { data };
}
+ // Add the fetch timestamp property to each spoc returned to communicate how
+ // old the spoc is in telemetry when it is used by the client
+ addFetchTimestamp(spocs, fetchTimestamp) {
+ if (spocs && spocs.length) {
+ return {
+ data: spocs.map(s => {
+ return {
+ ...s,
+ fetchTimestamp,
+ };
+ }),
+ };
+ }
+ return { data: spocs };
+ }
+
// For backwards compatibility, older spoc endpoint don't have flight_id,
// but instead had campaign_id we can use
//
@@ -1334,8 +1356,8 @@ export class DiscoveryStreamFeed {
let options = {};
if (this.isBff) {
const headers = new Headers();
- const oAuthConsumerKey = lazy.NimbusFeatures.saveToPocket.getVariable(
- "oAuthConsumerKeyBff"
+ const oAuthConsumerKey = Services.prefs.getStringPref(
+ "extensions.pocket.oAuthConsumerKeyBff"
);
headers.append("consumer_key", oAuthConsumerKey);
options = {
@@ -1768,7 +1790,7 @@ export class DiscoveryStreamFeed {
break;
// Check if spocs was disabled. Remove them if they were.
case PREF_SHOW_SPONSORED:
- case PREF_SHOW_SPONSORED_TOPSITES:
+ case PREF_SHOW_SPONSORED_TOPSITES: {
const dispatch = update =>
this.store.dispatch(ac.BroadcastToContent(update));
// We refresh placements data because one of the spocs were turned off.
@@ -1794,6 +1816,7 @@ export class DiscoveryStreamFeed {
await this.cache.set("spocs", {});
await this.loadSpocs(dispatch);
break;
+ }
}
}
diff --git a/browser/components/newtab/lib/DownloadsManager.sys.mjs b/browser/components/newtab/lib/DownloadsManager.sys.mjs
index a9a57222ee..f6e99e462a 100644
--- a/browser/components/newtab/lib/DownloadsManager.sys.mjs
+++ b/browser/components/newtab/lib/DownloadsManager.sys.mjs
@@ -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 { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
const lazy = {};
diff --git a/browser/components/newtab/lib/FaviconFeed.sys.mjs b/browser/components/newtab/lib/FaviconFeed.sys.mjs
index a76566d3e8..18c2231f58 100644
--- a/browser/components/newtab/lib/FaviconFeed.sys.mjs
+++ b/browser/components/newtab/lib/FaviconFeed.sys.mjs
@@ -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 { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
import { getDomain } from "resource://activity-stream/lib/TippyTopProvider.sys.mjs";
// We use importESModule here instead of static import so that
diff --git a/browser/components/newtab/lib/HighlightsFeed.sys.mjs b/browser/components/newtab/lib/HighlightsFeed.sys.mjs
index c603b886da..00eb109896 100644
--- a/browser/components/newtab/lib/HighlightsFeed.sys.mjs
+++ b/browser/components/newtab/lib/HighlightsFeed.sys.mjs
@@ -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 { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
import { shortURL } from "resource://activity-stream/lib/ShortURL.sys.mjs";
import {
diff --git a/browser/components/newtab/lib/NewTabInit.sys.mjs b/browser/components/newtab/lib/NewTabInit.sys.mjs
index db30e009ec..768cc29ea4 100644
--- a/browser/components/newtab/lib/NewTabInit.sys.mjs
+++ b/browser/components/newtab/lib/NewTabInit.sys.mjs
@@ -5,7 +5,7 @@
import {
actionCreators as ac,
actionTypes as at,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
/**
* NewTabInit - A placeholder for now. This will send a copy of the state to all
diff --git a/browser/components/newtab/lib/PlacesFeed.sys.mjs b/browser/components/newtab/lib/PlacesFeed.sys.mjs
index 70011412f8..85679153bd 100644
--- a/browser/components/newtab/lib/PlacesFeed.sys.mjs
+++ b/browser/components/newtab/lib/PlacesFeed.sys.mjs
@@ -6,7 +6,7 @@ import {
actionCreators as ac,
actionTypes as at,
actionUtils as au,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { shortURL } from "resource://activity-stream/lib/ShortURL.sys.mjs";
diff --git a/browser/components/newtab/lib/PrefsFeed.sys.mjs b/browser/components/newtab/lib/PrefsFeed.sys.mjs
index bb2502ac55..4cb41c0421 100644
--- a/browser/components/newtab/lib/PrefsFeed.sys.mjs
+++ b/browser/components/newtab/lib/PrefsFeed.sys.mjs
@@ -5,7 +5,7 @@
import {
actionCreators as ac,
actionTypes as at,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { Prefs } from "resource://activity-stream/lib/ActivityStreamPrefs.sys.mjs";
// We use importESModule here instead of static import so that
diff --git a/browser/components/newtab/lib/RecommendationProvider.sys.mjs b/browser/components/newtab/lib/RecommendationProvider.sys.mjs
index 875c90492b..9fd6b71656 100644
--- a/browser/components/newtab/lib/RecommendationProvider.sys.mjs
+++ b/browser/components/newtab/lib/RecommendationProvider.sys.mjs
@@ -12,7 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
import {
actionTypes as at,
actionCreators as ac,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
const CACHE_KEY = "personalization";
const PREF_PERSONALIZATION_MODEL_KEYS =
diff --git a/browser/components/newtab/lib/SectionsManager.sys.mjs b/browser/components/newtab/lib/SectionsManager.sys.mjs
index 069ddbb224..a1634e0d47 100644
--- a/browser/components/newtab/lib/SectionsManager.sys.mjs
+++ b/browser/components/newtab/lib/SectionsManager.sys.mjs
@@ -15,7 +15,7 @@ const { EventEmitter } = ChromeUtils.importESModule(
import {
actionCreators as ac,
actionTypes as at,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { getDefaultOptions } from "resource://activity-stream/lib/ActivityStreamStorage.sys.mjs";
const lazy = {};
@@ -389,7 +389,7 @@ export const SectionsManager = {
/**
* Sets each card in highlights' context menu options based on the card's type.
- * (See types.js for a list of types)
+ * (See types.mjs for a list of types)
*
* @param rows section rows containing a type for each card
*/
diff --git a/browser/components/newtab/lib/SystemTickFeed.sys.mjs b/browser/components/newtab/lib/SystemTickFeed.sys.mjs
index d87860fab2..fdbbda3ddd 100644
--- a/browser/components/newtab/lib/SystemTickFeed.sys.mjs
+++ b/browser/components/newtab/lib/SystemTickFeed.sys.mjs
@@ -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 { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
const lazy = {};
diff --git a/browser/components/newtab/lib/TelemetryFeed.sys.mjs b/browser/components/newtab/lib/TelemetryFeed.sys.mjs
index 1a9e9e3d34..6cf4dba4ab 100644
--- a/browser/components/newtab/lib/TelemetryFeed.sys.mjs
+++ b/browser/components/newtab/lib/TelemetryFeed.sys.mjs
@@ -18,13 +18,13 @@ const { XPCOMUtils } = ChromeUtils.importESModule(
// eslint-disable-next-line mozilla/use-static-import
const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.importESModule(
- "resource:///modules/asrouter/ActorConstants.sys.mjs"
+ "resource:///modules/asrouter/ActorConstants.mjs"
);
import {
actionTypes as at,
actionUtils as au,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { Prefs } from "resource://activity-stream/lib/ActivityStreamPrefs.sys.mjs";
import { classifySite } from "resource://activity-stream/lib/SiteClassifier.sys.mjs";
@@ -454,8 +454,7 @@ export class TelemetryFeed {
event = await this.applyCFRPolicy(event);
break;
case "badge_user_event":
- case "whats-new-panel_user_event":
- event = await this.applyWhatsNewPolicy(event);
+ event = await this.applyToolbarBadgePolicy(event);
break;
case "infobar_user_event":
event = await this.applyInfoBarPolicy(event);
@@ -509,12 +508,12 @@ export class TelemetryFeed {
* Per Bug 1482134, all the metrics for What's New panel use client_id in
* all the release channels
*/
- async applyWhatsNewPolicy(ping) {
+ async applyToolbarBadgePolicy(ping) {
ping.client_id = await this.telemetryClientId;
ping.browser_session_id = lazy.browserSessionId;
// Attach page info to `event_context` if there is a session associated with this ping
delete ping.action;
- return { ping, pingType: "whats-new-panel" };
+ return { ping, pingType: "toolbar-badge" };
}
async applyInfoBarPolicy(ping) {
@@ -715,8 +714,16 @@ export class TelemetryFeed {
const session = this.sessions.get(au.getPortIdOfSender(action));
switch (action.data?.event) {
case "CLICK": {
- const { card_type, topic, recommendation_id, tile_id, shim, feature } =
- action.data.value ?? {};
+ const {
+ card_type,
+ topic,
+ recommendation_id,
+ tile_id,
+ shim,
+ fetchTimestamp,
+ firstVisibleTimestamp,
+ feature,
+ } = action.data.value ?? {};
if (
action.data.source === "POPULAR_TOPICS" ||
card_type === "topics_widget"
@@ -740,6 +747,14 @@ export class TelemetryFeed {
});
if (shim) {
Glean.pocket.shim.set(shim);
+ if (fetchTimestamp) {
+ Glean.pocket.fetchTimestamp.set(fetchTimestamp * 1000);
+ }
+ if (firstVisibleTimestamp) {
+ Glean.pocket.newtabCreationTimestamp.set(
+ firstVisibleTimestamp * 1000
+ );
+ }
GleanPings.spoc.submit("click");
}
}
@@ -755,6 +770,16 @@ export class TelemetryFeed {
});
if (action.data.value?.shim) {
Glean.pocket.shim.set(action.data.value.shim);
+ if (action.data.value.fetchTimestamp) {
+ Glean.pocket.fetchTimestamp.set(
+ action.data.value.fetchTimestamp * 1000
+ );
+ }
+ if (action.data.value.newtabCreationTimestamp) {
+ Glean.pocket.newtabCreationTimestamp.set(
+ action.data.value.newtabCreationTimestamp * 1000
+ );
+ }
GleanPings.spoc.submit("save");
}
break;
@@ -976,6 +1001,14 @@ export class TelemetryFeed {
});
if (tile.shim) {
Glean.pocket.shim.set(tile.shim);
+ if (tile.fetchTimestamp) {
+ Glean.pocket.fetchTimestamp.set(tile.fetchTimestamp * 1000);
+ }
+ if (data.firstVisibleTimestamp) {
+ Glean.pocket.newtabCreationTimestamp.set(
+ data.firstVisibleTimestamp * 1000
+ );
+ }
GleanPings.spoc.submit("impression");
}
});
diff --git a/browser/components/newtab/lib/TopSitesFeed.sys.mjs b/browser/components/newtab/lib/TopSitesFeed.sys.mjs
index 796211085b..e259253402 100644
--- a/browser/components/newtab/lib/TopSitesFeed.sys.mjs
+++ b/browser/components/newtab/lib/TopSitesFeed.sys.mjs
@@ -5,7 +5,7 @@
import {
actionCreators as ac,
actionTypes as at,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { TippyTopProvider } from "resource://activity-stream/lib/TippyTopProvider.sys.mjs";
import {
insertPinned,
diff --git a/browser/components/newtab/lib/TopStoriesFeed.sys.mjs b/browser/components/newtab/lib/TopStoriesFeed.sys.mjs
index be030649dd..5986209a1c 100644
--- a/browser/components/newtab/lib/TopStoriesFeed.sys.mjs
+++ b/browser/components/newtab/lib/TopStoriesFeed.sys.mjs
@@ -5,7 +5,7 @@
import {
actionTypes as at,
actionCreators as ac,
-} from "resource://activity-stream/common/Actions.sys.mjs";
+} from "resource://activity-stream/common/Actions.mjs";
import { Prefs } from "resource://activity-stream/lib/ActivityStreamPrefs.sys.mjs";
import { shortURL } from "resource://activity-stream/lib/ShortURL.sys.mjs";
import { SectionsManager } from "resource://activity-stream/lib/SectionsManager.sys.mjs";
diff --git a/browser/components/newtab/lib/WallpaperFeed.sys.mjs b/browser/components/newtab/lib/WallpaperFeed.sys.mjs
new file mode 100644
index 0000000000..cb21311ddc
--- /dev/null
+++ b/browser/components/newtab/lib/WallpaperFeed.sys.mjs
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+ Utils: "resource://services-settings/Utils.sys.mjs",
+});
+
+import {
+ actionTypes as at,
+ actionCreators as ac,
+} from "resource://activity-stream/common/Actions.mjs";
+
+const PREF_WALLPAPERS_ENABLED =
+ "browser.newtabpage.activity-stream.newtabWallpapers.enabled";
+
+export class WallpaperFeed {
+ constructor() {
+ this.loaded = false;
+ this.wallpaperClient = "";
+ this.wallpaperDB = "";
+ this.baseAttachmentURL = "";
+ }
+
+ /**
+ * This thin wrapper around global.fetch makes it easier for us to write
+ * automated tests that simulate responses from this fetch.
+ */
+ fetch(...args) {
+ return fetch(...args);
+ }
+
+ /**
+ * This thin wrapper around lazy.RemoteSettings makes it easier for us to write
+ * automated tests that simulate responses from this fetch.
+ */
+ RemoteSettings(...args) {
+ return lazy.RemoteSettings(...args);
+ }
+
+ async wallpaperSetup(isStartup = false) {
+ const wallpapersEnabled = Services.prefs.getBoolPref(
+ PREF_WALLPAPERS_ENABLED
+ );
+
+ if (wallpapersEnabled) {
+ if (!this.wallpaperClient) {
+ this.wallpaperClient = this.RemoteSettings("newtab-wallpapers");
+ }
+
+ await this.getBaseAttachment();
+ this.wallpaperClient.on("sync", () => this.updateWallpapers());
+ this.updateWallpapers(isStartup);
+ }
+ }
+
+ async getBaseAttachment() {
+ if (!this.baseAttachmentURL) {
+ const SERVER = lazy.Utils.SERVER_URL;
+ const serverInfo = await (
+ await this.fetch(`${SERVER}/`, {
+ credentials: "omit",
+ })
+ ).json();
+ const { base_url } = serverInfo.capabilities.attachments;
+ this.baseAttachmentURL = base_url;
+ }
+ }
+
+ async updateWallpapers(isStartup = false) {
+ const records = await this.wallpaperClient.get();
+ if (!records?.length) {
+ return;
+ }
+
+ if (!this.baseAttachmentURL) {
+ await this.getBaseAttachment();
+ }
+ const wallpapers = records.map(record => {
+ return {
+ ...record,
+ wallpaperUrl: `${this.baseAttachmentURL}${record.attachment.location}`,
+ };
+ });
+
+ this.store.dispatch(
+ ac.BroadcastToContent({
+ type: at.WALLPAPERS_SET,
+ data: wallpapers,
+ meta: {
+ isStartup,
+ },
+ })
+ );
+ }
+
+ async onAction(action) {
+ switch (action.type) {
+ case at.INIT:
+ await this.wallpaperSetup(true /* isStartup */);
+ break;
+ case at.UNINIT:
+ break;
+ case at.SYSTEM_TICK:
+ break;
+ case at.PREF_CHANGED:
+ if (action.data.name === "newtabWallpapers.enabled") {
+ await this.wallpaperSetup(false /* isStartup */);
+ }
+ break;
+ case at.WALLPAPERS_SET:
+ break;
+ }
+ }
+}
diff --git a/browser/components/newtab/metrics.yaml b/browser/components/newtab/metrics.yaml
index bd74e609ad..c59247ceef 100644
--- a/browser/components/newtab/metrics.yaml
+++ b/browser/components/newtab/metrics.yaml
@@ -817,6 +817,35 @@ pocket:
send_in_pings:
- spoc
+ fetch_timestamp:
+ type: datetime
+ lifetime: ping
+ description: |
+ Timestamp of when the spoc was fetched by the client
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1887655
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1887655
+ notification_emails:
+ - dmueller@mozilla.com
+ expires: never
+ send_in_pings:
+ - spoc
+
+ newtab_creation_timestamp:
+ type: datetime
+ lifetime: ping
+ description: |
+ Timestamp of when this instance of the newtab was first visible to the user.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1887655
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1887655
+ notification_emails:
+ - dmueller@mozilla.com
+ expires: never
+ send_in_pings:
+ - spoc
messaging_system:
event_context_parse_error:
@@ -1031,7 +1060,7 @@ messaging_system:
type: string
description: >
Type of event the ping is capturing.
- e.g. "cfr", "whats-new-panel", "onboarding"
+ e.g. "cfr", "onboarding"
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1825863
data_reviews:
diff --git a/browser/components/newtab/test/browser/browser_as_load_location.js b/browser/components/newtab/test/browser/browser_as_load_location.js
index f11b6cf503..ce67ede0c6 100644
--- a/browser/components/newtab/test/browser/browser_as_load_location.js
+++ b/browser/components/newtab/test/browser/browser_as_load_location.js
@@ -8,7 +8,7 @@
*/
async function checkNewtabLoads(selector, message) {
// simulate a newtab open as a user would
- BrowserOpenTab();
+ BrowserCommands.openTab();
// wait until the browser loads
let browser = gBrowser.selectedBrowser;
diff --git a/browser/components/newtab/test/browser/browser_newtab_overrides.js b/browser/components/newtab/test/browser/browser_newtab_overrides.js
index 1d4a0c36e3..c876a62c4e 100644
--- a/browser/components/newtab/test/browser/browser_newtab_overrides.js
+++ b/browser/components/newtab/test/browser/browser_newtab_overrides.js
@@ -82,7 +82,7 @@ add_task(async function override_loads_in_browser() {
Assert.ok(AboutNewTab.newTabURLOverridden, "url has been overridden");
// simulate a newtab open as a user would
- BrowserOpenTab();
+ BrowserCommands.openTab();
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.browserLoaded(browser);
@@ -116,7 +116,7 @@ add_task(async function override_blank_loads_in_browser() {
Assert.ok(AboutNewTab.newTabURLOverridden, "url has been overridden");
// simulate a newtab open as a user would
- BrowserOpenTab();
+ BrowserCommands.openTab();
let browser = gBrowser.selectedBrowser;
await BrowserTestUtils.browserLoaded(browser);
diff --git a/browser/components/newtab/test/schemas/pings.js b/browser/components/newtab/test/schemas/pings.js
index fb52602bd4..2a1dd35ec6 100644
--- a/browser/components/newtab/test/schemas/pings.js
+++ b/browser/components/newtab/test/schemas/pings.js
@@ -1,7 +1,4 @@
-import {
- CONTENT_MESSAGE_TYPE,
- MAIN_MESSAGE_TYPE,
-} from "common/Actions.sys.mjs";
+import { CONTENT_MESSAGE_TYPE, MAIN_MESSAGE_TYPE } from "common/Actions.mjs";
import Joi from "joi-browser";
export const baseKeys = {
diff --git a/browser/components/newtab/test/unit/common/Actions.test.js b/browser/components/newtab/test/unit/common/Actions.test.js
index 32e417ea3f..af8d18cee8 100644
--- a/browser/components/newtab/test/unit/common/Actions.test.js
+++ b/browser/components/newtab/test/unit/common/Actions.test.js
@@ -8,7 +8,7 @@ import {
MAIN_MESSAGE_TYPE,
PRELOAD_MESSAGE_TYPE,
UI_CODE,
-} from "common/Actions.sys.mjs";
+} from "common/Actions.mjs";
describe("Actions", () => {
it("should set globalImportContext to UI_CODE", () => {
diff --git a/browser/components/newtab/test/unit/common/Reducers.test.js b/browser/components/newtab/test/unit/common/Reducers.test.js
index 7343fc6224..62f6f48353 100644
--- a/browser/components/newtab/test/unit/common/Reducers.test.js
+++ b/browser/components/newtab/test/unit/common/Reducers.test.js
@@ -11,7 +11,7 @@ const {
Search,
ASRouter,
} = reducers;
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
describe("Reducers", () => {
describe("App", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/Base.test.jsx b/browser/components/newtab/test/unit/content-src/components/Base.test.jsx
index c764348006..d8d300a3c9 100644
--- a/browser/components/newtab/test/unit/content-src/components/Base.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/Base.test.jsx
@@ -8,7 +8,7 @@ import { ErrorBoundary } from "content-src/components/ErrorBoundary/ErrorBoundar
import React from "react";
import { Search } from "content-src/components/Search/Search";
import { shallow } from "enzyme";
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
describe("<Base>", () => {
let DEFAULT_PROPS = {
@@ -21,6 +21,11 @@ describe("<Base>", () => {
adminContent: {
message: {},
},
+ document: {
+ visibilityState: "visible",
+ addEventListener: sinon.stub(),
+ removeEventListener: sinon.stub(),
+ },
};
it("should render Base component", () => {
@@ -76,6 +81,11 @@ describe("<BaseContent>", () => {
Sections: [],
DiscoveryStream: { config: { enabled: false } },
dispatch: () => {},
+ document: {
+ visibilityState: "visible",
+ addEventListener: sinon.stub(),
+ removeEventListener: sinon.stub(),
+ },
};
it("should render an ErrorBoundary with a Search child", () => {
@@ -114,6 +124,73 @@ describe("<BaseContent>", () => {
const wrapper = shallow(<BaseContent {...onlySearchProps} />);
assert.lengthOf(wrapper.find(".only-search"), 1);
});
+
+ it("should update firstVisibleTimestamp if it is visible immediately with no event listener", () => {
+ const props = Object.assign({}, DEFAULT_PROPS, {
+ document: {
+ visibilityState: "visible",
+ addEventListener: sinon.spy(),
+ removeEventListener: sinon.spy(),
+ },
+ });
+
+ const wrapper = shallow(<BaseContent {...props} />);
+ assert.notCalled(props.document.addEventListener);
+ assert.isDefined(wrapper.state("firstVisibleTimestamp"));
+ });
+ it("should attach an event listener for visibility change if it is not visible", () => {
+ const props = Object.assign({}, DEFAULT_PROPS, {
+ document: {
+ visibilityState: "hidden",
+ addEventListener: sinon.spy(),
+ removeEventListener: sinon.spy(),
+ },
+ });
+
+ const wrapper = shallow(<BaseContent {...props} />);
+ assert.calledWith(props.document.addEventListener, "visibilitychange");
+ assert.notExists(wrapper.state("firstVisibleTimestamp"));
+ });
+ it("should remove the event listener for visibility change when unmounted", () => {
+ const props = Object.assign({}, DEFAULT_PROPS, {
+ document: {
+ visibilityState: "hidden",
+ addEventListener: sinon.spy(),
+ removeEventListener: sinon.spy(),
+ },
+ });
+
+ const wrapper = shallow(<BaseContent {...props} />);
+ const [, listener] = props.document.addEventListener.firstCall.args;
+
+ wrapper.unmount();
+ assert.calledWith(
+ props.document.removeEventListener,
+ "visibilitychange",
+ listener
+ );
+ });
+ it("should remove the event listener for visibility change after becoming visible", () => {
+ const listeners = new Set();
+ const props = Object.assign({}, DEFAULT_PROPS, {
+ document: {
+ visibilityState: "hidden",
+ addEventListener: (ev, cb) => listeners.add(cb),
+ removeEventListener: (ev, cb) => listeners.delete(cb),
+ },
+ });
+
+ const wrapper = shallow(<BaseContent {...props} />);
+ assert.equal(listeners.size, 1);
+ assert.notExists(wrapper.state("firstVisibleTimestamp"));
+
+ // Simulate listeners getting called
+ props.document.visibilityState = "visible";
+ listeners.forEach(l => l());
+
+ assert.equal(listeners.size, 0);
+ assert.isDefined(wrapper.state("firstVisibleTimestamp"));
+ });
});
describe("<PrefsButton>", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/Card.test.jsx b/browser/components/newtab/test/unit/content-src/components/Card.test.jsx
index 5f07570b2e..f7f065efae 100644
--- a/browser/components/newtab/test/unit/content-src/components/Card.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/Card.test.jsx
@@ -1,7 +1,4 @@
-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 as Card,
PlaceholderCard,
diff --git a/browser/components/newtab/test/unit/content-src/components/ComponentPerfTimer.test.jsx b/browser/components/newtab/test/unit/content-src/components/ComponentPerfTimer.test.jsx
index baf203947e..fcc1dd0f45 100644
--- a/browser/components/newtab/test/unit/content-src/components/ComponentPerfTimer.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ComponentPerfTimer.test.jsx
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { ComponentPerfTimer } from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
import createMockRaf from "mock-raf";
import React from "react";
diff --git a/browser/components/newtab/test/unit/content-src/components/ConfirmDialog.test.jsx b/browser/components/newtab/test/unit/content-src/components/ConfirmDialog.test.jsx
index a471c09e66..3befa4403f 100644
--- a/browser/components/newtab/test/unit/content-src/components/ConfirmDialog.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ConfirmDialog.test.jsx
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { _ConfirmDialog as ConfirmDialog } from "content-src/components/ConfirmDialog/ConfirmDialog";
import React from "react";
import { shallow } from "enzyme";
diff --git a/browser/components/newtab/test/unit/content-src/components/CustomiseMenu.test.jsx b/browser/components/newtab/test/unit/content-src/components/CustomiseMenu.test.jsx
index e1f84f7d84..0407622cf9 100644
--- a/browser/components/newtab/test/unit/content-src/components/CustomiseMenu.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/CustomiseMenu.test.jsx
@@ -1,4 +1,4 @@
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import { ContentSection } from "content-src/components/CustomizeMenu/ContentSection/ContentSection";
import { mount } from "enzyme";
import React from "react";
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamAdmin.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamAdmin.test.jsx
index 41849fba3e..7f40b66200 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamAdmin.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamAdmin.test.jsx
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import {
DiscoveryStreamAdminInner,
CollapseToggle,
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardGrid.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardGrid.test.jsx
index 418a731ba1..ffa32bfc3e 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardGrid.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/CardGrid.test.jsx
@@ -13,10 +13,7 @@ import {
PlaceholderDSCard,
} from "content-src/components/DiscoveryStreamComponents/DSCard/DSCard";
import { TopicsWidget } from "content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget";
-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 { shallow, mount } from "enzyme";
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
index 1d572ee3ce..afb6d6dcd2 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSCard.test.jsx
@@ -10,10 +10,7 @@ import {
StatusMessage,
SponsorLabel,
} from "content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter";
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { DSLinkMenu } from "content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu";
import React from "react";
import { INITIAL_STATE } from "common/Reducers.sys.mjs";
@@ -28,6 +25,8 @@ const DEFAULT_PROPS = {
isForStartupCache: false,
},
DiscoveryStream: INITIAL_STATE.DiscoveryStream,
+ fetchTimestamp: new Date("March 20, 2024 10:30:44").getTime(),
+ firstVisibleTimestamp: new Date("March 21, 2024 10:11:12").getTime(),
};
describe("<DSCard>", () => {
@@ -174,6 +173,8 @@ describe("<DSCard>", () => {
card_type: "organic",
recommendation_id: undefined,
tile_id: "fooidx",
+ fetchTimestamp: DEFAULT_PROPS.fetchTimestamp,
+ firstVisibleTimestamp: DEFAULT_PROPS.firstVisibleTimestamp,
},
})
);
@@ -212,6 +213,8 @@ describe("<DSCard>", () => {
card_type: "spoc",
recommendation_id: undefined,
tile_id: "fooidx",
+ fetchTimestamp: DEFAULT_PROPS.fetchTimestamp,
+ firstVisibleTimestamp: DEFAULT_PROPS.firstVisibleTimestamp,
},
})
);
@@ -258,6 +261,8 @@ describe("<DSCard>", () => {
recommendation_id: undefined,
tile_id: "fooidx",
shim: "click shim",
+ fetchTimestamp: DEFAULT_PROPS.fetchTimestamp,
+ firstVisibleTimestamp: DEFAULT_PROPS.firstVisibleTimestamp,
},
})
);
@@ -370,7 +375,12 @@ describe("<DSCard>", () => {
describe("DSCard onSaveClick", () => {
it("should fire telemetry for onSaveClick", () => {
- wrapper.setProps({ id: "fooidx", pos: 1, type: "foo" });
+ wrapper.setProps({
+ id: "fooidx",
+ pos: 1,
+ type: "foo",
+ fetchTimestamp: undefined,
+ });
wrapper.instance().onSaveClick();
assert.calledThrice(dispatch);
@@ -391,6 +401,8 @@ describe("<DSCard>", () => {
card_type: "organic",
recommendation_id: undefined,
tile_id: "fooidx",
+ fetchTimestamp: undefined,
+ firstVisibleTimestamp: DEFAULT_PROPS.firstVisibleTimestamp,
},
})
);
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
index 08ac7868ce..a18e688758 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSContextFooter.test.jsx
@@ -5,7 +5,7 @@ import {
} from "content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter";
import React from "react";
import { mount } from "enzyme";
-import { cardContextTypes } from "content-src/components/Card/types.js";
+import { cardContextTypes } from "content-src/components/Card/types.mjs";
import { FluentOrText } from "content-src/components/FluentOrText/FluentOrText.jsx";
describe("<DSContextFooter>", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
index b4b743c7ff..b5acbf3b56 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
@@ -1,6 +1,6 @@
import { DSPrivacyModal } from "content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal";
import { shallow, mount } from "enzyme";
-import { actionCreators as ac } from "common/Actions.sys.mjs";
+import { actionCreators as ac } from "common/Actions.mjs";
import React from "react";
describe("Discovery Stream <DSPrivacyModal>", () => {
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx
index 4926cc6c70..c935acde1a 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx
@@ -2,7 +2,7 @@ import {
ImpressionStats,
INTERSECTION_RATIO,
} from "content-src/components/DiscoveryStreamImpressionStats/ImpressionStats";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import React from "react";
import { shallow } from "enzyme";
@@ -33,12 +33,15 @@ describe("<ImpressionStats>", () => {
};
}
+ const TEST_FETCH_TIMESTAMP = Date.now();
+ const TEST_FIRST_VISIBLE_TIMESTAMP = Date.now();
const DEFAULT_PROPS = {
rows: [
- { id: 1, pos: 0 },
- { id: 2, pos: 1 },
- { id: 3, pos: 2 },
+ { id: 1, pos: 0, fetchTimestamp: TEST_FETCH_TIMESTAMP },
+ { id: 2, pos: 1, fetchTimestamp: TEST_FETCH_TIMESTAMP },
+ { id: 3, pos: 2, fetchTimestamp: TEST_FETCH_TIMESTAMP },
],
+ firstVisibleTimestamp: TEST_FIRST_VISIBLE_TIMESTAMP,
source: SOURCE,
IntersectionObserver: buildIntersectionObserver(FullIntersectEntries),
document: {
@@ -76,7 +79,7 @@ describe("<ImpressionStats>", () => {
assert.notCalled(dispatch);
});
- it("should noly send loaded content but not impression when the wrapped item is not visbible", () => {
+ it("should only send loaded content but not impression when the wrapped item is not visbible", () => {
const dispatch = sinon.spy();
const props = {
dispatch,
@@ -128,11 +131,37 @@ describe("<ImpressionStats>", () => {
[action] = dispatch.secondCall.args;
assert.equal(action.type, at.DISCOVERY_STREAM_IMPRESSION_STATS);
assert.equal(action.data.source, SOURCE);
+ assert.equal(
+ action.data.firstVisibleTimestamp,
+ TEST_FIRST_VISIBLE_TIMESTAMP
+ );
assert.deepEqual(action.data.tiles, [
- { id: 1, pos: 0, type: "organic", recommendation_id: undefined },
- { id: 2, pos: 1, type: "organic", recommendation_id: undefined },
- { id: 3, pos: 2, type: "organic", recommendation_id: undefined },
+ {
+ id: 1,
+ pos: 0,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
+ {
+ id: 2,
+ pos: 1,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
+ {
+ id: 3,
+ pos: 2,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
]);
+ assert.equal(
+ action.data.firstVisibleTimestamp,
+ TEST_FIRST_VISIBLE_TIMESTAMP
+ );
});
it("should send a DISCOVERY_STREAM_SPOC_IMPRESSION when the wrapped item has a flightId", () => {
const dispatch = sinon.spy();
@@ -207,10 +236,32 @@ describe("<ImpressionStats>", () => {
[action] = dispatch.firstCall.args;
assert.equal(action.type, at.DISCOVERY_STREAM_IMPRESSION_STATS);
assert.deepEqual(action.data.tiles, [
- { id: 1, pos: 0, type: "organic", recommendation_id: undefined },
- { id: 2, pos: 1, type: "organic", recommendation_id: undefined },
- { id: 3, pos: 2, type: "organic", recommendation_id: undefined },
+ {
+ id: 1,
+ pos: 0,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
+ {
+ id: 2,
+ pos: 1,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
+ {
+ id: 3,
+ pos: 2,
+ type: "organic",
+ recommendation_id: undefined,
+ fetchTimestamp: TEST_FETCH_TIMESTAMP,
+ },
]);
+ assert.equal(
+ action.data.firstVisibleTimestamp,
+ TEST_FIRST_VISIBLE_TIMESTAMP
+ );
});
it("should remove visibility change listener when the wrapper is removed", () => {
const props = {
diff --git a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/TopicsWidget.test.jsx b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/TopicsWidget.test.jsx
index f879600a8f..5c9dcb4c14 100644
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/TopicsWidget.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/TopicsWidget.test.jsx
@@ -6,10 +6,7 @@ import {
TopicsWidget,
} from "content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget";
import { SafeAnchor } from "content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { mount } from "enzyme";
import React from "react";
diff --git a/browser/components/newtab/test/unit/content-src/components/Sections.test.jsx b/browser/components/newtab/test/unit/content-src/components/Sections.test.jsx
index 9f4008369a..69d023c668 100644
--- a/browser/components/newtab/test/unit/content-src/components/Sections.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/Sections.test.jsx
@@ -5,7 +5,7 @@ import {
SectionIntl,
_Sections as Sections,
} from "content-src/components/Sections/Sections";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import { mount, shallow } from "enzyme";
import { PlaceholderCard } from "content-src/components/Card/Card";
import { PocketLoggedInCta } from "content-src/components/PocketLoggedInCta/PocketLoggedInCta";
diff --git a/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx b/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
index 798bb9b8c7..9797a4863e 100644
--- a/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/TopSites.test.jsx
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
import { MIN_RICH_FAVICON_SIZE } from "content-src/components/TopSites/TopSitesConstants";
import {
diff --git a/browser/components/newtab/test/unit/content-src/components/TopSites/TopSiteImpressionWrapper.test.jsx b/browser/components/newtab/test/unit/content-src/components/TopSites/TopSiteImpressionWrapper.test.jsx
index 3f7e725de0..b1b501ca44 100644
--- a/browser/components/newtab/test/unit/content-src/components/TopSites/TopSiteImpressionWrapper.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/TopSites/TopSiteImpressionWrapper.test.jsx
@@ -2,7 +2,7 @@ import {
TopSiteImpressionWrapper,
INTERSECTION_RATIO,
} from "content-src/components/TopSites/TopSiteImpressionWrapper";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import React from "react";
import { shallow } from "enzyme";
diff --git a/browser/components/newtab/test/unit/content-src/lib/detect-user-session-start.test.js b/browser/components/newtab/test/unit/content-src/lib/detect-user-session-start.test.js
index 5a7fad7cc0..3629bb7a68 100644
--- a/browser/components/newtab/test/unit/content-src/lib/detect-user-session-start.test.js
+++ b/browser/components/newtab/test/unit/content-src/lib/detect-user-session-start.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { DetectUserSessionStart } from "content-src/lib/detect-user-session-start";
describe("detectUserSessionStart", () => {
diff --git a/browser/components/newtab/test/unit/content-src/lib/init-store.test.js b/browser/components/newtab/test/unit/content-src/lib/init-store.test.js
index 0dd510ef1a..8f998b64d0 100644
--- a/browser/components/newtab/test/unit/content-src/lib/init-store.test.js
+++ b/browser/components/newtab/test/unit/content-src/lib/init-store.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { addNumberReducer, GlobalOverrider } from "test/unit/utils";
import {
INCOMING_MESSAGE_NAME,
diff --git a/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js b/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js
index 233f31b6ca..fb28c9490b 100644
--- a/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js
+++ b/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js
@@ -1,5 +1,5 @@
import { combineReducers, createStore } from "redux";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
import { reducers } from "common/Reducers.sys.mjs";
import { selectLayoutRender } from "content-src/lib/selectLayoutRender";
diff --git a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
index 20765608fa..a19bf698d9 100644
--- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
+++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
@@ -3,10 +3,7 @@ import {
AboutPreferences,
PREFERENCES_LOADED_EVENT,
} from "lib/AboutPreferences.sys.mjs";
-import {
- actionTypes as at,
- actionCreators as ac,
-} from "common/Actions.sys.mjs";
+import { actionTypes as at, actionCreators as ac } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
describe("AboutPreferences Feed", () => {
diff --git a/browser/components/newtab/test/unit/lib/ActivityStream.test.js b/browser/components/newtab/test/unit/lib/ActivityStream.test.js
index b9deba1069..7921ae2c91 100644
--- a/browser/components/newtab/test/unit/lib/ActivityStream.test.js
+++ b/browser/components/newtab/test/unit/lib/ActivityStream.test.js
@@ -1,4 +1,4 @@
-import { CONTENT_MESSAGE_TYPE } from "common/Actions.sys.mjs";
+import { CONTENT_MESSAGE_TYPE } from "common/Actions.mjs";
import { ActivityStream, PREFS_CONFIG } from "lib/ActivityStream.sys.mjs";
import { GlobalOverrider } from "test/unit/utils";
diff --git a/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js b/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js
index 4bea86331d..8df62b2903 100644
--- a/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js
+++ b/browser/components/newtab/test/unit/lib/ActivityStreamMessageChannel.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import {
ActivityStreamMessageChannel,
DEFAULT_OPTIONS,
@@ -16,7 +13,7 @@ const OPTIONS = [
];
// Create an object containing details about a tab as expected within
-// the loaded tabs map in ActivityStreamMessageChannel.jsm.
+// the loaded tabs map in ActivityStreamMessageChannel.sys.mjs.
function getTabDetails(portID, url = "about:newtab", extraArgs = {}) {
let actor = {
portID,
diff --git a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
index 92e10facb3..e10a4cbc04 100644
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -2,7 +2,7 @@ import {
actionCreators as ac,
actionTypes as at,
actionUtils as au,
-} from "common/Actions.sys.mjs";
+} from "common/Actions.mjs";
import { combineReducers, createStore } from "redux";
import { GlobalOverrider } from "test/unit/utils";
import { DiscoveryStreamFeed } from "lib/DiscoveryStreamFeed.sys.mjs";
@@ -849,6 +849,8 @@ describe("DiscoveryStreamFeed", () => {
spocs: { items: [{ id: "data" }] },
});
sandbox.stub(feed.cache, "set").returns(Promise.resolve());
+ const loadTimestamp = 100;
+ clock.tick(loadTimestamp);
await feed.loadSpocs(feed.store.dispatch);
@@ -860,15 +862,15 @@ describe("DiscoveryStreamFeed", () => {
title: "",
sponsor: "",
sponsored_by_override: undefined,
- items: [{ id: "data", score: 1 }],
+ items: [{ id: "data", score: 1, fetchTimestamp: loadTimestamp }],
},
},
- lastUpdated: 0,
+ lastUpdated: loadTimestamp,
});
assert.deepEqual(
feed.store.getState().DiscoveryStream.spocs.data.spocs.items[0],
- { id: "data", score: 1 }
+ { id: "data", score: 1, fetchTimestamp: loadTimestamp }
);
});
it("should normalizeSpocsItems for older spoc data", async () => {
@@ -882,7 +884,7 @@ describe("DiscoveryStreamFeed", () => {
assert.deepEqual(
feed.store.getState().DiscoveryStream.spocs.data.spocs.items[0],
- { id: "data", score: 1 }
+ { id: "data", score: 1, fetchTimestamp: 0 }
);
});
it("should dispatch DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE with feature_flags", async () => {
@@ -936,7 +938,7 @@ describe("DiscoveryStreamFeed", () => {
context: "",
sponsor: "",
sponsored_by_override: undefined,
- items: [{ id: "data", score: 1 }],
+ items: [{ id: "data", score: 1, fetchTimestamp: 0 }],
},
placement2: {
title: "",
@@ -978,7 +980,7 @@ describe("DiscoveryStreamFeed", () => {
context: "context",
sponsor: "",
sponsored_by_override: undefined,
- items: [{ id: "data", score: 1 }],
+ items: [{ id: "data", score: 1, fetchTimestamp: 0 }],
},
});
});
@@ -3444,16 +3446,12 @@ describe("DiscoveryStreamFeed", () => {
},
});
sandbox.stub(global.Region, "home").get(() => "DE");
- globals.set("NimbusFeatures", {
- saveToPocket: {
- getVariable: sandbox.stub(),
- },
- });
- global.NimbusFeatures.saveToPocket.getVariable
- .withArgs("bffApi")
+ sandbox.stub(global.Services.prefs, "getStringPref");
+ global.Services.prefs.getStringPref
+ .withArgs("extensions.pocket.bffApi")
.returns("bffApi");
- global.NimbusFeatures.saveToPocket.getVariable
- .withArgs("oAuthConsumerKeyBff")
+ global.Services.prefs.getStringPref
+ .withArgs("extensions.pocket.oAuthConsumerKeyBff")
.returns("oAuthConsumerKeyBff");
});
it("should return true with isBff", async () => {
diff --git a/browser/components/newtab/test/unit/lib/DownloadsManager.test.js b/browser/components/newtab/test/unit/lib/DownloadsManager.test.js
index ac262baf90..5e2979893d 100644
--- a/browser/components/newtab/test/unit/lib/DownloadsManager.test.js
+++ b/browser/components/newtab/test/unit/lib/DownloadsManager.test.js
@@ -1,4 +1,4 @@
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import { DownloadsManager } from "lib/DownloadsManager.sys.mjs";
import { GlobalOverrider } from "test/unit/utils";
diff --git a/browser/components/newtab/test/unit/lib/FaviconFeed.test.js b/browser/components/newtab/test/unit/lib/FaviconFeed.test.js
index e9be9b86ba..8b9cf24984 100644
--- a/browser/components/newtab/test/unit/lib/FaviconFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/FaviconFeed.test.js
@@ -1,6 +1,6 @@
"use strict";
import { FaviconFeed, fetchIconFromRedirects } from "lib/FaviconFeed.sys.mjs";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
const FAKE_ENDPOINT = "https://foo.com/";
diff --git a/browser/components/newtab/test/unit/lib/NewTabInit.test.js b/browser/components/newtab/test/unit/lib/NewTabInit.test.js
index 68ab9d7821..0def9293f0 100644
--- a/browser/components/newtab/test/unit/lib/NewTabInit.test.js
+++ b/browser/components/newtab/test/unit/lib/NewTabInit.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { NewTabInit } from "lib/NewTabInit.sys.mjs";
describe("NewTabInit", () => {
diff --git a/browser/components/newtab/test/unit/lib/PrefsFeed.test.js b/browser/components/newtab/test/unit/lib/PrefsFeed.test.js
index 498c7198ab..8f33dce24f 100644
--- a/browser/components/newtab/test/unit/lib/PrefsFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/PrefsFeed.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
import { PrefsFeed } from "lib/PrefsFeed.sys.mjs";
diff --git a/browser/components/newtab/test/unit/lib/RecommendationProvider.test.js b/browser/components/newtab/test/unit/lib/RecommendationProvider.test.js
index 9e68f4869a..05999be08d 100644
--- a/browser/components/newtab/test/unit/lib/RecommendationProvider.test.js
+++ b/browser/components/newtab/test/unit/lib/RecommendationProvider.test.js
@@ -1,7 +1,4 @@
-import {
- actionCreators as ac,
- actionTypes as at,
-} from "common/Actions.sys.mjs";
+import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { RecommendationProvider } from "lib/RecommendationProvider.sys.mjs";
import { combineReducers, createStore } from "redux";
import { reducers } from "common/Reducers.sys.mjs";
diff --git a/browser/components/newtab/test/unit/lib/SectionsManager.test.js b/browser/components/newtab/test/unit/lib/SectionsManager.test.js
index b3a9abd70c..45c5b7c689 100644
--- a/browser/components/newtab/test/unit/lib/SectionsManager.test.js
+++ b/browser/components/newtab/test/unit/lib/SectionsManager.test.js
@@ -5,7 +5,7 @@ import {
CONTENT_MESSAGE_TYPE,
MAIN_MESSAGE_TYPE,
PRELOAD_MESSAGE_TYPE,
-} from "common/Actions.sys.mjs";
+} from "common/Actions.mjs";
import { EventEmitter, GlobalOverrider } from "test/unit/utils";
import { SectionsFeed, SectionsManager } from "lib/SectionsManager.sys.mjs";
diff --git a/browser/components/newtab/test/unit/lib/SystemTickFeed.test.js b/browser/components/newtab/test/unit/lib/SystemTickFeed.test.js
index a0789b182e..f5ba73d2ea 100644
--- a/browser/components/newtab/test/unit/lib/SystemTickFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/SystemTickFeed.test.js
@@ -2,7 +2,7 @@ import {
SYSTEM_TICK_INTERVAL,
SystemTickFeed,
} from "lib/SystemTickFeed.sys.mjs";
-import { actionTypes as at } from "common/Actions.sys.mjs";
+import { actionTypes as at } from "common/Actions.mjs";
import { GlobalOverrider } from "test/unit/utils";
describe("System Tick Feed", () => {
diff --git a/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js b/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
index 31a03947cd..1cb8a44631 100644
--- a/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_HighlightsFeed.js
@@ -4,7 +4,7 @@
"use strict";
const { actionTypes: at } = ChromeUtils.importESModule(
- "resource://activity-stream/common/Actions.sys.mjs"
+ "resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
diff --git a/browser/components/newtab/test/xpcshell/test_PlacesFeed.js b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
index 19f9e343f5..78dda7818e 100644
--- a/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
@@ -4,7 +4,7 @@
"use strict";
const { actionTypes: at, actionCreators: ac } = ChromeUtils.importESModule(
- "resource://activity-stream/common/Actions.sys.mjs"
+ "resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
diff --git a/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js b/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
index 59d82f5583..354eac8c2a 100644
--- a/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_TelemetryFeed.js
@@ -4,11 +4,11 @@
"use strict";
const { actionCreators: ac, actionTypes: at } = ChromeUtils.importESModule(
- "resource://activity-stream/common/Actions.sys.mjs"
+ "resource://activity-stream/common/Actions.mjs"
);
const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.importESModule(
- "resource:///modules/asrouter/ActorConstants.sys.mjs"
+ "resource:///modules/asrouter/ActorConstants.mjs"
);
const { updateAppInfo } = ChromeUtils.importESModule(
@@ -947,18 +947,18 @@ add_task(
}
);
-add_task(async function test_applyWhatsNewPolicy() {
+add_task(async function test_applyToolbarBadgePolicy() {
info(
- "TelemetryFeed.applyWhatsNewPolicy should set client_id and set pingType"
+ "TelemetryFeed.applyToolbarBadgePolicy should set client_id and set pingType"
);
let instance = new TelemetryFeed();
- let { ping, pingType } = await instance.applyWhatsNewPolicy({});
+ let { ping, pingType } = await instance.applyToolbarBadgePolicy({});
Assert.equal(
ping.client_id,
Services.prefs.getCharPref("toolkit.telemetry.cachedClientID")
);
- Assert.equal(pingType, "whats-new-panel");
+ Assert.equal(pingType, "toolbar-badge");
});
add_task(async function test_applyInfoBarPolicy() {
@@ -1288,10 +1288,10 @@ add_task(async function test_createASRouterEvent_call_correctPolicy() {
message_id: "onboarding_message_01",
});
- testCallCorrectPolicy("applyWhatsNewPolicy", {
- action: "whats-new-panel_user_event",
- event: "CLICK_BUTTON",
- message_id: "whats-new-panel_message_01",
+ testCallCorrectPolicy("applyToolbarBadgePolicy", {
+ action: "badge_user_event",
+ event: "IMPRESSION",
+ message_id: "badge_message_01",
});
testCallCorrectPolicy("applyMomentsPolicy", {
@@ -2230,6 +2230,8 @@ add_task(
const POS_1 = 1;
const POS_2 = 4;
const SHIM = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
+ const FETCH_TIMESTAMP = new Date("March 22, 2024 10:15:20");
+ const NEWTAB_CREATION_TIMESTAMP = new Date("March 23, 2024 11:10:30");
sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID });
let pingSubmitted = new Promise(resolve => {
@@ -2252,6 +2254,14 @@ add_task(
tile_id: String(2),
});
Assert.equal(Glean.pocket.shim.testGetValue(), SHIM);
+ Assert.deepEqual(
+ Glean.pocket.fetchTimestamp.testGetValue(),
+ FETCH_TIMESTAMP
+ );
+ Assert.deepEqual(
+ Glean.pocket.newtabCreationTimestamp.testGetValue(),
+ NEWTAB_CREATION_TIMESTAMP
+ );
resolve();
});
@@ -2272,10 +2282,12 @@ add_task(
type: "spoc",
recommendation_id: undefined,
shim: SHIM,
+ fetchTimestamp: FETCH_TIMESTAMP.valueOf(),
},
],
window_inner_width: 1000,
window_inner_height: 900,
+ firstVisibleTimestamp: NEWTAB_CREATION_TIMESTAMP.valueOf(),
});
await pingSubmitted;
@@ -2949,6 +2961,8 @@ add_task(
Services.fog.testResetFOG();
const ACTION_POSITION = 42;
const SHIM = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
+ const FETCH_TIMESTAMP = new Date("March 22, 2024 10:15:20");
+ const NEWTAB_CREATION_TIMESTAMP = new Date("March 23, 2024 11:10:30");
let action = ac.DiscoveryStreamUserEvent({
event: "CLICK",
action_position: ACTION_POSITION,
@@ -2957,6 +2971,8 @@ add_task(
recommendation_id: undefined,
tile_id: 448685088,
shim: SHIM,
+ fetchTimestamp: FETCH_TIMESTAMP.valueOf(),
+ firstVisibleTimestamp: NEWTAB_CREATION_TIMESTAMP.valueOf(),
},
});
@@ -2966,6 +2982,14 @@ add_task(
let pingSubmitted = new Promise(resolve => {
GleanPings.spoc.testBeforeNextSubmit(reason => {
Assert.equal(reason, "click");
+ Assert.deepEqual(
+ Glean.pocket.fetchTimestamp.testGetValue(),
+ FETCH_TIMESTAMP
+ );
+ Assert.deepEqual(
+ Glean.pocket.newtabCreationTimestamp.testGetValue(),
+ NEWTAB_CREATION_TIMESTAMP
+ );
resolve();
});
});
@@ -3043,6 +3067,8 @@ add_task(
Services.fog.testResetFOG();
const ACTION_POSITION = 42;
const SHIM = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ=";
+ const FETCH_TIMESTAMP = new Date("March 22, 2024 10:15:20");
+ const NEWTAB_CREATION_TIMESTAMP = new Date("March 23, 2024 11:10:30");
let action = ac.DiscoveryStreamUserEvent({
event: "SAVE_TO_POCKET",
action_position: ACTION_POSITION,
@@ -3051,6 +3077,8 @@ add_task(
recommendation_id: undefined,
tile_id: 448685088,
shim: SHIM,
+ fetchTimestamp: FETCH_TIMESTAMP.valueOf(),
+ newtabCreationTimestamp: NEWTAB_CREATION_TIMESTAMP.valueOf(),
},
});
@@ -3064,6 +3092,14 @@ add_task(
SHIM,
"Pocket shim was recorded"
);
+ Assert.deepEqual(
+ Glean.pocket.fetchTimestamp.testGetValue(),
+ FETCH_TIMESTAMP
+ );
+ Assert.deepEqual(
+ Glean.pocket.newtabCreationTimestamp.testGetValue(),
+ NEWTAB_CREATION_TIMESTAMP
+ );
resolve();
});
diff --git a/browser/components/newtab/test/xpcshell/test_TopSitesFeed.js b/browser/components/newtab/test/xpcshell/test_TopSitesFeed.js
index 860e8758a5..4be520fcca 100644
--- a/browser/components/newtab/test/xpcshell/test_TopSitesFeed.js
+++ b/browser/components/newtab/test/xpcshell/test_TopSitesFeed.js
@@ -8,7 +8,7 @@ const { TopSitesFeed, DEFAULT_TOP_SITES } = ChromeUtils.importESModule(
);
const { actionCreators: ac, actionTypes: at } = ChromeUtils.importESModule(
- "resource://activity-stream/common/Actions.sys.mjs"
+ "resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
diff --git a/browser/components/newtab/test/xpcshell/test_WallpaperFeed.js b/browser/components/newtab/test/xpcshell/test_WallpaperFeed.js
new file mode 100644
index 0000000000..c6c12c17bf
--- /dev/null
+++ b/browser/components/newtab/test/xpcshell/test_WallpaperFeed.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { WallpaperFeed } = ChromeUtils.importESModule(
+ "resource://activity-stream/lib/WallpaperFeed.sys.mjs"
+);
+
+const { actionCreators: ac, actionTypes: at } = ChromeUtils.importESModule(
+ "resource://activity-stream/common/Actions.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ Utils: "resource://services-settings/Utils.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+const PREF_WALLPAPERS_ENABLED =
+ "browser.newtabpage.activity-stream.newtabWallpapers.enabled";
+
+add_task(async function test_construction() {
+ let feed = new WallpaperFeed();
+
+ info("WallpaperFeed constructor should create initial values");
+
+ Assert.ok(feed, "Could construct a WallpaperFeed");
+ Assert.ok(feed.loaded === false, "WallpaperFeed is not loaded");
+ Assert.ok(
+ feed.wallpaperClient === "",
+ "wallpaperClient is initialized as an empty string"
+ );
+ Assert.ok(
+ feed.wallpaperDB === "",
+ "wallpaperDB is initialized as an empty string"
+ );
+ Assert.ok(
+ feed.baseAttachmentURL === "",
+ "baseAttachmentURL is initialized as an empty string"
+ );
+});
+
+add_task(async function test_onAction_INIT() {
+ let sandbox = sinon.createSandbox();
+ let feed = new WallpaperFeed();
+ Services.prefs.setBoolPref(PREF_WALLPAPERS_ENABLED, true);
+ const attachment = {
+ attachment: {
+ location: "attachment",
+ },
+ };
+ sandbox.stub(feed, "RemoteSettings").returns({
+ get: () => [attachment],
+ on: () => {},
+ });
+ sandbox.stub(Utils, "SERVER_URL").returns("http://localhost:8888/v1");
+ feed.store = {
+ dispatch: sinon.spy(),
+ };
+ sandbox.stub(feed, "fetch").resolves({
+ json: () => ({
+ capabilities: {
+ attachments: {
+ base_url: "http://localhost:8888/base_url/",
+ },
+ },
+ }),
+ });
+
+ info("WallpaperFeed.onAction INIT should initialize wallpapers");
+
+ await feed.onAction({
+ type: at.INIT,
+ });
+
+ Assert.ok(feed.store.dispatch.calledOnce);
+ Assert.ok(
+ feed.store.dispatch.calledWith(
+ ac.BroadcastToContent({
+ type: at.WALLPAPERS_SET,
+ data: [
+ {
+ ...attachment,
+ wallpaperUrl: "http://localhost:8888/base_url/attachment",
+ },
+ ],
+ meta: {
+ isStartup: true,
+ },
+ })
+ )
+ );
+ Services.prefs.clearUserPref(PREF_WALLPAPERS_ENABLED);
+ sandbox.restore();
+});
+
+add_task(async function test_onAction_PREF_CHANGED() {
+ let sandbox = sinon.createSandbox();
+ let feed = new WallpaperFeed();
+ Services.prefs.setBoolPref(PREF_WALLPAPERS_ENABLED, true);
+ sandbox.stub(feed, "wallpaperSetup").returns();
+
+ info("WallpaperFeed.onAction PREF_CHANGED should call wallpaperSetup");
+
+ feed.onAction({
+ type: at.PREF_CHANGED,
+ data: { name: "newtabWallpapers.enabled" },
+ });
+
+ Assert.ok(feed.wallpaperSetup.calledOnce);
+ Assert.ok(feed.wallpaperSetup.calledWith(false));
+
+ Services.prefs.clearUserPref(PREF_WALLPAPERS_ENABLED);
+ sandbox.restore();
+});
diff --git a/browser/components/newtab/test/xpcshell/xpcshell.toml b/browser/components/newtab/test/xpcshell/xpcshell.toml
index 87d73669d3..13c11b0541 100644
--- a/browser/components/newtab/test/xpcshell/xpcshell.toml
+++ b/browser/components/newtab/test/xpcshell/xpcshell.toml
@@ -26,3 +26,5 @@ support-files = ["../schemas/*.schema.json"]
["test_TopSitesFeed.js"]
["test_TopSitesFeed_glean.js"]
+
+["test_WallpaperFeed.js"]
diff --git a/browser/components/newtab/webpack.system-addon.config.js b/browser/components/newtab/webpack.system-addon.config.js
index a0400ec39e..68a384ea71 100644
--- a/browser/components/newtab/webpack.system-addon.config.js
+++ b/browser/components/newtab/webpack.system-addon.config.js
@@ -48,7 +48,7 @@ module.exports = (env = {}) => ({
},
// This resolve config allows us to import with paths relative to the root directory, e.g. "lib/ActivityStream.sys.mjs"
resolve: {
- extensions: [".js", ".jsx"],
+ extensions: [".js", ".jsx", ".mjs"],
modules: ["node_modules", "."],
},
externals: {