/* 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-disable-next-line no-unused-vars import React, { useCallback, useRef } from "react"; import { useSelector, batch } from "react-redux"; import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; import { useIntersectionObserver, useSizeSubmenu } from "../../../lib/utils"; import { WIDGET_REGISTRY, resolveWidgetSize } from "common/WidgetsRegistry.mjs"; import { MoveSubmenu } from "../MoveSubmenu"; const USER_ACTION_TYPES = { CHANGE_SIZE: "change_size", }; const PRIVACY_ENTRY = WIDGET_REGISTRY.find(w => w.id === "privacy"); const ICON_BASE_URL = "chrome://newtab/content/data/content/assets/"; // Renders a widget icon by asset filename. The wrapper div is the alignment // hook. TEMP (Bug 2049390): callers pass a static filename for now; the // per-message icon mapping (shield/planet/star/bolt/kit) is a follow-up commit. const privacyImage = filename => (
); const PREF_PRIVACY_MAX_COUNT = "widgets.privacy.maxCount"; const DEFAULT_PRIVACY_MAX_COUNT = 100; // Resolves the count at which the readout caps to "N+". trainhopConfig wins so // an experiment can override the pref's default; then the pref // (widgets.privacy.maxCount, default 100); then a defensive fallback. Routed // through a helper (never the raw pref) per the trainhop-gate convention. function resolvePrivacyMaxCount(prefs) { return ( prefs.trainhopConfig?.widgets?.privacyMaxCount || prefs[PREF_PRIVACY_MAX_COUNT] || DEFAULT_PRIVACY_MAX_COUNT ); } function Privacy({ dispatch, widgetsMayBeMaximized, widgetEnabledMap }) { const prefs = useSelector(state => state.Prefs.values); const privacyData = useSelector(state => state.PrivacyWidget); // Size comes from the registry helper: user-set pref > trainhop suggestion // > registry defaultSize. Never read the size pref directly. const widgetSize = resolveWidgetSize(PRIVACY_ENTRY, prefs); const impressionFired = useRef(false); const trackersToday = privacyData?.trackersToday ?? 0; const sitesToday = privacyData?.sitesToday ?? 0; // Gate the metric UI on a real feed update. Before the first broadcast — or // when it's skipped (e.g. the backward-compat guard in PrivacyFeed on older // platforms) — show no metric state rather than a misleading empty/zero one. const initialized = privacyData?.initialized ?? false; // Ceiling the readout at "{maxCount}+" so the number stays a tidy single line. const maxCount = resolvePrivacyMaxCount(prefs); const displayCount = trackersToday > maxCount ? `${maxCount}+` : `${trackersToday}`; const isEmptyState = trackersToday === 0; const showTip = !isEmptyState; const isLarge = widgetSize === "large"; const handleIntersection = useCallback(() => { if (impressionFired.current) { return; } impressionFired.current = true; dispatch( ac.AlsoToMain({ type: at.WIDGETS_IMPRESSION, data: { widget_name: "privacy", widget_size: widgetSize, }, }) ); }, [dispatch, widgetSize]); const widgetRef = useIntersectionObserver(handleIntersection); function handlePrivacyHide() { batch(() => { dispatch( ac.OnlyToMain({ type: at.SET_PREF, data: { name: PRIVACY_ENTRY.enabledPref, value: false }, }) ); dispatch( ac.OnlyToMain({ type: at.WIDGETS_ENABLED, data: { widget_name: "privacy", widget_source: "context_menu", enabled: false, widget_size: widgetSize, }, }) ); }); } const handleChangeSize = useCallback( size => { batch(() => { dispatch( ac.OnlyToMain({ type: at.SET_PREF, data: { name: PRIVACY_ENTRY.sizePref, value: size }, }) ); dispatch( ac.OnlyToMain({ type: at.WIDGETS_USER_EVENT, data: { widget_name: "privacy", widget_source: "context_menu", user_action: USER_ACTION_TYPES.CHANGE_SIZE, action_value: size, widget_size: size, }, }) ); }); }, [dispatch] ); const sizeSubmenuRef = useSizeSubmenu(handleChangeSize); function handleLearnMore() { batch(() => { dispatch( ac.OnlyToMain({ type: at.OPEN_LINK, data: { url: "https://support.mozilla.org/kb/firefox-new-tab-widgets", }, }) ); dispatch( ac.OnlyToMain({ type: at.WIDGETS_USER_EVENT, data: { widget_name: "privacy", widget_source: "context_menu", user_action: "learn_more", widget_size: widgetSize, }, }) ); }); } return (
{ widgetRef.current = [el]; }} >
{widgetsMayBeMaximized && ( {["medium", "large"].map(size => ( ))} )}
{initialized && (isEmptyState ? (
{privacyImage("widget-privacy-shield.svg")}

) : ( <>
{/* Compact sizes (small, medium): icon beside the count. Keyed off !isLarge so a future "small" needs no change. */} {!isLarge && privacyImage("widget-privacy-shield-check.svg")} {displayCount}
{showTip && ( <>
{/* Large only: icon sits inside the tip. */} {isLarge && privacyImage("widget-privacy-shield-check.svg")}

)} ))}
); } export { Privacy };