Skip to content

Commit e0a63b6

Browse files
committed
Remove duplicate subscription
Issue fixed: duplicate subscription • Before: Two subscriptions to activity_onBurnComplete (one in BatchedActivityService, one in ProtectionsProvider) • After: Only one subscription exists (in BatchedActivityService). We listen to the custom event it dispatches instead of creating a duplicate. Implementation details 1. No duplicate subscription: Only BatchedActivityService subscribes to activity_onBurnComplete (line 93 in batched-activity.service.js). 2. Event listener setup: useBlockedCount listens to the custom event from BatchedActivityService.burns EventTarget (lines 122-137). 3. Conditional setup: The listener is set up only when ActivityProvider is mounted (when feed is 'activity'). This is correct because: • Burns only occur when viewing the activity feed • The listener is set up when activityService becomes available • The effect dependency array includes activityService, so it re-runs when the service becomes available 4. Cleanup: The cleanup function captures the burns reference directly (line 130), ensuring proper cleanup even if activityService is destroyed/unmounted. 5. Shared state: burnCompleteTimeRef is shared via BurnCompleteTimeContext, so all useBlockedCount hooks share the same burn complete time. Edge cases handled 1. Feed switching: When switching from 'activity' to 'privacy-stats', ActivityProvider unmounts, the service is destroyed, and the listener is cleaned up. 2. Initial render: If feed is not 'activity', activityService is {} (default), the effect returns early, and no listener is set up (correct). 3. Late mount: If ActivityProvider mounts after useBlockedCount runs, the effect re-runs when activityService becomes available and sets up the listener. 4. Service destruction: When BatchedActivityService is destroyed, burns is set to null, and the cleanup uses optional chaining to handle this safely.
1 parent a3f1450 commit e0a63b6

File tree

1 file changed

+24
-11
lines changed

1 file changed

+24
-11
lines changed

special-pages/pages/new-tab/app/protections/components/ProtectionsProvider.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useMessaging } from '../../types.js';
44
import { reducer, useConfigSubscription, useInitialDataAndConfig } from '../../service.hooks.js';
55
import { ProtectionsService } from '../protections.service.js';
66
import { useSignal, useSignalEffect } from '@preact/signals';
7+
import { ActivityServiceContext } from '../../activity/ActivityProvider.js';
78

89
/**
910
* @typedef {import('../../../types/new-tab.js').ProtectionsData} ProtectionsData
@@ -60,18 +61,10 @@ export function ProtectionsProvider(props) {
6061
// subscribe to config updates
6162
useConfigSubscription({ dispatch, service });
6263

63-
// Set up a single subscription to activity_onBurnComplete at the Provider level
64-
// This prevents duplicate subscription errors when multiple useBlockedCount hooks are used
65-
const ntp = useMessaging();
64+
// Create a shared ref for burn complete time
65+
// This will be updated by listening to the custom event from BatchedActivityService
66+
// instead of creating a duplicate subscription
6667
const burnCompleteTimeRef = useRef(/** @type {number | null} */ (null));
67-
68-
useEffect(() => {
69-
if (!ntp) return;
70-
return ntp.messaging.subscribe('activity_onBurnComplete', () => {
71-
// Mark that we should skip animation if next update goes to 0
72-
burnCompleteTimeRef.current = Date.now();
73-
});
74-
}, [ntp]);
7568

7669
// expose a fn for sync toggling
7770
const toggle = useCallback(() => {
@@ -120,9 +113,29 @@ export function useService() {
120113
export function useBlockedCount(initial) {
121114
const service = useContext(ProtectionsServiceContext);
122115
const burnCompleteTimeRef = useContext(BurnCompleteTimeContext);
116+
const activityService = useContext(ActivityServiceContext);
123117
const signal = useSignal(initial);
124118
const skipAnimationSignal = useSignal(false);
125119

120+
// Listen to the custom event from BatchedActivityService instead of creating a duplicate subscription
121+
// BatchedActivityService already subscribes to activity_onBurnComplete and dispatches this event
122+
useEffect(() => {
123+
if (!activityService?.burns) return;
124+
125+
const handleBurnComplete = () => {
126+
// Mark that we should skip animation if next update goes to 0
127+
burnCompleteTimeRef.current = Date.now();
128+
};
129+
130+
const burns = activityService.burns;
131+
burns.addEventListener('activity_onBurnComplete', handleBurnComplete);
132+
return () => {
133+
// Use the captured burns reference to ensure we remove from the correct EventTarget
134+
// even if activityService is destroyed/unmounted
135+
burns?.removeEventListener('activity_onBurnComplete', handleBurnComplete);
136+
};
137+
}, [activityService, burnCompleteTimeRef]);
138+
126139
// @todo jingram possibly refactor to include full object
127140
useSignalEffect(() => {
128141
return service?.onData((evt) => {

0 commit comments

Comments
 (0)