Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ACTION_BURN } from '../burning/BurnProvider.js';
* @property {Record<string, boolean>} favorites
* @property {string[]} urls
* @property {number} totalTrackers
* @property {Record<string, boolean|null|undefined>} cookiePopUpBlocked
*/

/**
Expand All @@ -52,6 +53,7 @@ export function normalizeData(prev, incoming) {
trackingStatus: {},
urls: [],
totalTrackers: incoming.totalTrackers,
cookiePopUpBlocked: {},
};

if (shallowDiffers(prev.urls, incoming.urls)) {
Expand All @@ -64,6 +66,7 @@ export function normalizeData(prev, incoming) {
const id = item.url;

output.favorites[id] = item.favorite;
output.cookiePopUpBlocked[id] = item.cookiePopUpBlocked;

/** @type {Item} */
const next = {
Expand All @@ -73,6 +76,7 @@ export function normalizeData(prev, incoming) {
faviconMax: item.favicon?.maxAvailableSize ?? DDG_DEFAULT_ICON_SIZE,
favoriteSrc: item.favicon?.src,
trackersFound: item.trackersFound,
// cookiePopUpBlocked: item.cookiePopUpBlocked,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this left here on purpose?

};
const differs = shallowDiffers(next, prev.items[id] || {});
output.items[id] = differs ? next : prev.items[id] || {};
Expand Down Expand Up @@ -187,6 +191,7 @@ export function SignalStateProvider({ children }) {
favorites: {},
urls: [],
totalTrackers: 0,
cookiePopUpBlocked: {},
},
{ activity: state.data.activity, urls: state.data.urls, totalTrackers: state.data.totalTrackers },
),
Expand Down
5 changes: 3 additions & 2 deletions special-pages/pages/new-tab/app/activity/activity.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ title: Activity
"url": "https://youtube.com/watch?v=abc",
"relativeTime": "Just now"
}
]
],
"cookiePopUpBlocked": true,
}
]
}
Expand Down Expand Up @@ -178,4 +179,4 @@ example payload without id (for example, on history items)
```

### `activity_burnAnimationComplete`
- Sent when the burn animation completes
- Sent when the burn animation completes
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const activityExamples = {
factory: () => (
<Activity itemCount={10} batched={false}>
<Mock size={3}>
<ActivityBody canBurn={false} visibility={'visible'} />
<ActivityBody canBurn={false} visibility={'visible'} shouldDisplayLegacyActivity={false} />
</Mock>
</Activity>
),
Expand All @@ -25,7 +25,7 @@ export const activityExamples = {
factory: () => (
<Activity itemCount={20} batched={false}>
<Mock size={1}>
<ActivityBody canBurn={false} visibility={'visible'} />
<ActivityBody canBurn={false} visibility={'visible'} shouldDisplayLegacyActivity={false} />
</Mock>
</Activity>
),
Expand All @@ -34,7 +34,7 @@ export const activityExamples = {
factory: () => (
<Activity itemCount={0} batched={false}>
<Mock size={0}>
<ActivityBody canBurn={false} visibility={'visible'} />
<ActivityBody canBurn={false} visibility={'visible'} shouldDisplayLegacyActivity={false} />
</Mock>
</Activity>
),
Expand All @@ -58,6 +58,7 @@ function Mock({ children, size }) {
favorites: {},
urls: [],
totalTrackers: 0,
cookiePopUpBlocked: null,
},
{ activity: mocks, urls: mocks.map((x) => x.url), totalTrackers: 0 },
);
Expand Down
134 changes: 113 additions & 21 deletions special-pages/pages/new-tab/app/activity/components/Activity.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { h } from 'preact';
import styles from './Activity.module.css';
// @todo legacyProtections: `stylesLegacy` can be removed once all platforms
// are ready for the new Protections Report
import stylesLegacy from './ActivityLegacy.module.css';
import { useContext, useEffect, useRef } from 'preact/hooks';
import { memo } from 'preact/compat';
import { ActivityContext, ActivityServiceContext } from '../ActivityProvider.js';
Expand All @@ -8,7 +11,7 @@ import { useOnMiddleClick } from '../../utils.js';
import { useAdBlocking, useBatchedActivityApi, usePlatformName } from '../../settings.provider.js';
import { CompanyIcon } from '../../components/CompanyIcon.js';
import { Trans } from '../../../../../shared/components/TranslationsProvider.js';
import { ActivityItem } from './ActivityItem.js';
import { ActivityItem, ActivityItemLegacy } from './ActivityItem.js';
import { ActivityBurningSignalContext, BurnProvider } from '../../burning/BurnProvider.js';
import { useEnv } from '../../../../../shared/components/EnvironmentProvider.js';
import { useComputed } from '@preact/signals';
Expand All @@ -18,6 +21,7 @@ import { HistoryItems } from './HistoryItems.js';
import { NormalizedDataContext, SignalStateProvider } from '../NormalizeDataProvider.js';
import { ActivityInteractionsContext } from '../../burning/ActivityInteractionsContext.js';
import { ProtectionsEmpty } from '../../protections/components/Protections.js';
import { TickPill } from '../../components/TickPill/TickPill';

/**
* @import enStrings from "../strings.json"
Expand Down Expand Up @@ -55,8 +59,9 @@ export function ActivityEmptyState() {
* @param {object} props
* @param {boolean} props.canBurn
* @param {DocumentVisibilityState} props.visibility
* @param {boolean} props.shouldDisplayLegacyActivity
*/
export function ActivityBody({ canBurn, visibility }) {
export function ActivityBody({ canBurn, visibility, shouldDisplayLegacyActivity }) {
const { isReducedMotion } = useEnv();
const { keys } = useContext(NormalizedDataContext);
const { burning, exiting } = useContext(ActivityBurningSignalContext);
Expand All @@ -71,8 +76,33 @@ export function ActivityBody({ canBurn, visibility }) {
return (
<ul class={styles.activity} data-busy={busy} ref={ref} onClick={didClick}>
{keys.value.map((id, _index) => {
if (canBurn && !isReducedMotion) return <BurnableItem id={id} key={id} documentVisibility={visibility} />;
return <RemovableItem id={id} key={id} canBurn={canBurn} documentVisibility={visibility} />;
if (canBurn && !isReducedMotion) {
return (
<BurnableItem
id={id}
key={id}
documentVisibility={visibility}
// @todo legacyProtections:
// `shouldDisplayLegacyActivity` can be removed once
// all platforms are ready for the new protections
// report
shouldDisplayLegacyActivity={shouldDisplayLegacyActivity}
/>
);
}

return (
<RemovableItem
id={id}
key={id}
canBurn={canBurn}
documentVisibility={visibility}
// @todo legacyProtections:
// `shouldDisplayLegacyActivity` can be removed once all
// platforms are ready for the new protections report
shouldDisplayLegacyActivity={shouldDisplayLegacyActivity}
/>
);
})}
</ul>
);
Expand Down Expand Up @@ -111,16 +141,23 @@ const BurnableItem = memo(
* @param {object} props
* @param {string} props.id
* @param {'visible' | 'hidden'} props.documentVisibility
* @param {boolean} props.shouldDisplayLegacyActivity
*/
function BurnableItem({ id, documentVisibility }) {
function BurnableItem({ id, documentVisibility, shouldDisplayLegacyActivity }) {
const { activity } = useContext(NormalizedDataContext);
const item = useComputed(() => activity.value.items[id]);

if (!item.value) {
return null;
}

// @todo legacyProtections: Once all platforms are ready for the new
// protections report we can use `ActivityItem`
const ActivityItemComponent = shouldDisplayLegacyActivity ? ActivityItemLegacy : ActivityItem;

return (
<ActivityItemAnimationWrapper url={id}>
<ActivityItem
<ActivityItemComponent
title={item.value.title}
url={id}
favoriteSrc={item.value.favoriteSrc}
Expand All @@ -129,9 +166,16 @@ const BurnableItem = memo(
canBurn={true}
documentVisibility={documentVisibility}
>
<TrackerStatus id={id} trackersFound={item.value.trackersFound} />
{shouldDisplayLegacyActivity ? (
// @todo legacyProtections: `TrackerStatusLegacy` and
// supporting prop can be removed once all platforms are
// ready for the new protections report
<TrackerStatusLegacy id={id} trackersFound={item.value.trackersFound} />
) : (
<TrackerStatus id={id} trackersFound={item.value.trackersFound} />
)}
<HistoryItems id={id} />
</ActivityItem>
</ActivityItemComponent>
</ActivityItemAnimationWrapper>
);
},
Expand All @@ -143,8 +187,9 @@ const RemovableItem = memo(
* @param {string} props.id
* @param {boolean} props.canBurn
* @param {"visible" | "hidden"} props.documentVisibility
* @param {boolean} props.shouldDisplayLegacyActivity
*/
function RemovableItem({ id, canBurn, documentVisibility }) {
function RemovableItem({ id, canBurn, documentVisibility, shouldDisplayLegacyActivity }) {
const { activity } = useContext(NormalizedDataContext);
const item = useComputed(() => activity.value.items[id]);
if (!item.value) {
Expand All @@ -154,8 +199,13 @@ const RemovableItem = memo(
</p>
);
}

// @todo legacyProtections: Once all platforms are ready for the new
// protections report we can use `ActivityItem`
const ActivityItemComponent = shouldDisplayLegacyActivity ? ActivityItemLegacy : ActivityItem;

return (
<ActivityItem
<ActivityItemComponent
title={item.value.title}
url={id}
favoriteSrc={item.value.favoriteSrc}
Expand All @@ -164,9 +214,16 @@ const RemovableItem = memo(
canBurn={canBurn}
documentVisibility={documentVisibility}
>
<TrackerStatus id={id} trackersFound={item.value.trackersFound} />
{shouldDisplayLegacyActivity ? (
// @todo legacyProtections: `TrackerStatusLegacy` and
// supporting prop can be removed once all platforms are
// ready for the new protections report
<TrackerStatusLegacy id={id} trackersFound={item.value.trackersFound} />
) : (
<TrackerStatus id={id} trackersFound={item.value.trackersFound} />
)}
<HistoryItems id={id} />
</ActivityItem>
</ActivityItemComponent>
);
},
);
Expand All @@ -178,6 +235,40 @@ const DDG_MAX_TRACKER_ICONS = 3;
* @param {boolean} props.trackersFound
*/
function TrackerStatus({ id, trackersFound }) {
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
const { activity } = useContext(NormalizedDataContext);
const status = useComputed(() => activity.value.trackingStatus[id]);
const cookiePopUpBlocked = useComputed(() => activity.value.cookiePopUpBlocked?.[id]).value;
const { totalCount: totalTrackersBlocked } = status.value;

const totalTrackersPillText =
totalTrackersBlocked === 0
? trackersFound
? t('activity_no_trackers_blocked')
: t('activity_no_trackers')
: t(totalTrackersBlocked === 1 ? 'activity_countBlockedSingular' : 'activity_countBlockedPlural', {
count: String(totalTrackersBlocked),
});

return (
<div class={styles.companiesIconRow} data-testid="TrackerStatus">
<div class={styles.companiesText}>
{totalTrackersBlocked > 0 && <TickPill text={totalTrackersPillText} />}
{cookiePopUpBlocked && <TickPill text={t('activity_cookiePopUpBlocked')} />}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Global Overwrite Causes Incorrect Cookie Popup Status

The normalizeData function in NormalizeDataProvider.js incorrectly stores cookiePopUpBlocked globally, overwriting it in a loop so only the last item's value persists. The TrackerStatus component then reads this global value, causing all activity items to display the same cookie popup status instead of their individual ones. The per-item status is correctly available in trackingStatus[id].cookiePopUpBlocked.

Additional Locations (1)

Fix in Cursor Fix in Web

</div>
</div>
);
}

// @todo legacyProtections: `TrackerStatusLegacy` can be removed once all
// platforms are ready for the new protections report

/**
* @param {object} props
* @param {string} props.id
* @param {boolean} props.trackersFound
*/
function TrackerStatusLegacy({ id, trackersFound }) {
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
const { activity } = useContext(NormalizedDataContext);
const status = useComputed(() => activity.value.trackingStatus[id]);
Expand All @@ -193,7 +284,7 @@ function TrackerStatus({ id, trackersFound }) {
if (other.length > 0) {
const title = other.map((item) => item.displayName).join('\n');
otherIcon = (
<span title={title} class={styles.otherIcon}>
<span title={title} class={stylesLegacy.otherIcon}>
+{other.length}
</span>
);
Expand All @@ -207,23 +298,23 @@ function TrackerStatus({ id, trackersFound }) {
text = adBlocking ? t('activity_no_adsAndTrackers') : t('activity_no_trackers');
}
return (
<p class={styles.companiesIconRow} data-testid="TrackerStatus">
<p class={stylesLegacy.companiesIconRow} data-testid="TrackerStatus">
{text}
</p>
);
}

return (
<div class={styles.companiesIconRow} data-testid="TrackerStatus">
<div class={styles.companiesIcons}>
<div class={stylesLegacy.companiesIconRow} data-testid="TrackerStatus">
<div class={stylesLegacy.companiesIcons}>
{icons}
{otherIcon}
</div>
<div class={styles.companiesText}>
<div class={stylesLegacy.companiesText}>
{adBlocking ? (
<Trans str={t('activity_countBlockedAdsAndTrackersPlural', { count: String(status.value.totalCount) })} values={{}} />
) : (
<Trans str={t('activity_countBlockedPlural', { count: String(status.value.totalCount) })} values={{}} />
<Trans str={t('activity_countBlockedPluralLegacy', { count: String(status.value.totalCount) })} values={{}} />
)}
</div>
</div>
Expand Down Expand Up @@ -263,8 +354,9 @@ export function ActivityConfigured({ children }) {
* ```
* @param {object} props
* @param {boolean} props.showBurnAnimation
* @param {boolean} props.shouldDisplayLegacyActivity
*/
export function ActivityConsumer({ showBurnAnimation }) {
export function ActivityConsumer({ showBurnAnimation, shouldDisplayLegacyActivity }) {
const { state } = useContext(ActivityContext);
const service = useContext(ActivityServiceContext);
const platformName = usePlatformName();
Expand All @@ -274,7 +366,7 @@ export function ActivityConsumer({ showBurnAnimation }) {
return (
<SignalStateProvider>
<ActivityConfigured>
<ActivityBody canBurn={false} visibility={visibility} />
<ActivityBody canBurn={false} visibility={visibility} shouldDisplayLegacyActivity={shouldDisplayLegacyActivity} />
</ActivityConfigured>
</SignalStateProvider>
);
Expand All @@ -283,7 +375,7 @@ export function ActivityConsumer({ showBurnAnimation }) {
<SignalStateProvider>
<BurnProvider service={service} showBurnAnimation={showBurnAnimation}>
<ActivityConfigured>
<ActivityBody canBurn={true} visibility={visibility} />
<ActivityBody canBurn={true} visibility={visibility} shouldDisplayLegacyActivity={shouldDisplayLegacyActivity} />
</ActivityConfigured>
</BurnProvider>
</SignalStateProvider>
Expand Down
Loading
Loading