Skip to content

Commit 8311cb5

Browse files
authored
Modern Event System: refactor legacy FB support logic (#18336)
1 parent 1cf4a17 commit 8311cb5

13 files changed

+1721
-1686
lines changed

packages/legacy-events/EventSystemFlags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export const IS_ACTIVE = 1 << 3;
1616
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
1717
export const IS_REPLAYED = 1 << 5;
1818
export const IS_FIRST_ANCESTOR = 1 << 6;
19+
export const LEGACY_FB_SUPPORT = 1 << 7;

packages/react-dom/src/client/ReactDOMComponent.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1365,7 +1365,6 @@ export function listenToEventResponderEventTypes(
13651365
(targetEventType: any),
13661366
true,
13671367
passiveItem.listener,
1368-
true,
13691368
);
13701369
listenerMap.delete(passiveKey);
13711370
}

packages/react-dom/src/events/DOMModernPluginEventSystem.js

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
2424
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
2525
import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils';
2626
import {plugins} from 'legacy-events/EventPluginRegistry';
27+
import {LEGACY_FB_SUPPORT, IS_REPLAYED} from 'legacy-events/EventSystemFlags';
2728

2829
import {HostRoot, HostPortal} from 'shared/ReactWorkTags';
2930

@@ -66,6 +67,7 @@ import {
6667
TOP_RATE_CHANGE,
6768
TOP_PROGRESS,
6869
TOP_PLAYING,
70+
TOP_CLICK,
6971
} from './DOMTopLevelEventTypes';
7072
import {
7173
getClosestInstanceFromNode,
@@ -78,7 +80,7 @@ import {
7880
ELEMENT_NODE,
7981
} from '../shared/HTMLNodeType';
8082

81-
import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags';
83+
import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
8284

8385
const capturePhaseEvents = new Set([
8486
TOP_FOCUS,
@@ -197,7 +199,6 @@ export function listenToTopLevelEvent(
197199
topLevelType,
198200
isCapturePhase,
199201
((listenerEntry: any): ElementListenerMapEntry).listener,
200-
((listenerEntry: any): ElementListenerMapEntry).passive,
201202
);
202203
}
203204
const listener = addTrappedEventListener(
@@ -225,42 +226,24 @@ export function listenToEvent(
225226
}
226227
}
227228

228-
const validFBLegacyPrimerRels = new Set([
229-
'dialog',
230-
'dialog-post',
231-
'async',
232-
'async-post',
233-
'theater',
234-
'toggle',
235-
]);
236-
237-
function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
238-
let node = nativeEvent.target;
239-
const type = nativeEvent.type;
240-
if (type !== 'click') {
229+
function willDeferLaterForLegacyFBSupport(
230+
topLevelType: DOMTopLevelEventType,
231+
targetContainer: EventTarget,
232+
): boolean {
233+
if (topLevelType !== TOP_CLICK) {
241234
return false;
242235
}
243-
while (node !== null) {
244-
// Primer works by intercepting a click event on an <a> element
245-
// that has a "rel" attribute that matches one of the valid ones
246-
// in the Set above. If we intercept this before Primer does, we
247-
// will need to defer the current event till later and discontinue
248-
// execution of the current event. To do this we can add a document
249-
// event listener and continue again later after propagation.
250-
if (node.tagName === 'A' && validFBLegacyPrimerRels.has(node.rel)) {
251-
const legacyFBSupport = true;
252-
const isCapture = nativeEvent.eventPhase === 1;
253-
addTrappedEventListener(
254-
null,
255-
((type: any): DOMTopLevelEventType),
256-
isCapture,
257-
legacyFBSupport,
258-
);
259-
return true;
260-
}
261-
node = node.parentNode;
262-
}
263-
return false;
236+
// We defer all click events with legacy FB support mode on.
237+
// This means we add a one time event listener to trigger
238+
// after the FB delegated listeners fire.
239+
const isDeferredListenerForLegacyFBSupport = true;
240+
addTrappedEventListener(
241+
targetContainer,
242+
topLevelType,
243+
false,
244+
isDeferredListenerForLegacyFBSupport,
245+
);
246+
return true;
264247
}
265248

266249
function isMatchingRootContainer(
@@ -303,12 +286,19 @@ export function dispatchEventForPluginEventSystem(
303286
// TODO: useEvent for document and window
304287
return;
305288
}
306-
// If we detect the FB legacy primer system, we
289+
// If we are using the legacy FB support flag, we
307290
// defer the event to the null with a one
308291
// time event listener so we can defer the event.
309292
if (
310-
enableLegacyFBPrimerSupport &&
311-
willDeferLaterForFBLegacyPrimer(nativeEvent)
293+
enableLegacyFBSupport &&
294+
// We do not want to defer if the event system has already been
295+
// set to LEGACY_FB_SUPPORT. LEGACY_FB_SUPPORT only gets set when
296+
// we call willDeferLaterForLegacyFBSupport, thus not bailing out
297+
// will result in endless cycles like an infinite loop.
298+
(eventSystemFlags & LEGACY_FB_SUPPORT) === 0 &&
299+
// We also don't want to defer during event replaying.
300+
(eventSystemFlags & IS_REPLAYED) === 0 &&
301+
willDeferLaterForLegacyFBSupport(topLevelType, targetContainer)
312302
) {
313303
return;
314304
}

packages/react-dom/src/events/ReactDOMEventListener.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
IS_PASSIVE,
4343
IS_ACTIVE,
4444
PASSIVE_NOT_SUPPORTED,
45+
LEGACY_FB_SUPPORT,
4546
} from 'legacy-events/EventSystemFlags';
4647

4748
import {
@@ -59,7 +60,7 @@ import {passiveBrowserEventsSupported} from './checkPassiveEvents';
5960
import {
6061
enableDeprecatedFlareAPI,
6162
enableModernEventSystem,
62-
enableLegacyFBPrimerSupport,
63+
enableLegacyFBSupport,
6364
enableUseEventAPI,
6465
} from 'shared/ReactFeatureFlags';
6566
import {
@@ -129,10 +130,10 @@ export function addResponderEventSystemEvent(
129130
}
130131

131132
export function addTrappedEventListener(
132-
targetContainer: null | EventTarget,
133+
targetContainer: EventTarget,
133134
topLevelType: DOMTopLevelEventType,
134135
capture: boolean,
135-
legacyFBSupport?: boolean,
136+
isDeferredListenerForLegacyFBSupport?: boolean,
136137
passive?: boolean,
137138
priority?: EventPriority,
138139
): any => void {
@@ -159,11 +160,15 @@ export function addTrappedEventListener(
159160
if (passive === true && !passiveBrowserEventsSupported) {
160161
passive = false;
161162
}
163+
const eventSystemFlags =
164+
enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
165+
? PLUGIN_EVENT_SYSTEM | LEGACY_FB_SUPPORT
166+
: PLUGIN_EVENT_SYSTEM;
162167

163168
listener = listenerWrapper.bind(
164169
null,
165170
topLevelType,
166-
PLUGIN_EVENT_SYSTEM,
171+
eventSystemFlags,
167172
targetContainer,
168173
);
169174

@@ -177,14 +182,17 @@ export function addTrappedEventListener(
177182
targetContainer = document;
178183
}
179184

180-
const validTargetContainer = ((targetContainer: any): EventTarget);
185+
targetContainer =
186+
enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
187+
? (targetContainer: any).ownerDocument
188+
: targetContainer;
181189

182190
const rawEventName = getRawEventName(topLevelType);
183191

184192
let unsubscribeListener;
185193
// When legacyFBSupport is enabled, it's for when we
186194
// want to add a one time event listener to a container.
187-
// This should only be used with enableLegacyFBPrimerSupport
195+
// This should only be used with enableLegacyFBSupport
188196
// due to requirement to provide compatibility with
189197
// internal FB www event tooling. This works by removing
190198
// the event listener as soon as it is invoked. We could
@@ -193,14 +201,14 @@ export function addTrappedEventListener(
193201
// browsers do not support this today, and given this is
194202
// to support legacy code patterns, it's likely they'll
195203
// need support for such browsers.
196-
if (enableLegacyFBPrimerSupport && legacyFBSupport) {
204+
if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
197205
const originalListener = listener;
198206
listener = function(...p) {
199207
try {
200208
return originalListener.apply(this, p);
201209
} finally {
202210
removeEventListener(
203-
validTargetContainer,
211+
targetContainer,
204212
rawEventName,
205213
unsubscribeListener,
206214
capture,
@@ -212,14 +220,14 @@ export function addTrappedEventListener(
212220
if (enableUseEventAPI && passive !== undefined) {
213221
// This is only used with passive is either true or false.
214222
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
215-
validTargetContainer,
223+
targetContainer,
216224
rawEventName,
217225
listener,
218226
passive,
219227
);
220228
} else {
221229
unsubscribeListener = addEventCaptureListener(
222-
validTargetContainer,
230+
targetContainer,
223231
rawEventName,
224232
listener,
225233
);
@@ -228,14 +236,14 @@ export function addTrappedEventListener(
228236
if (enableUseEventAPI && passive !== undefined) {
229237
// This is only used with passive is either true or false.
230238
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
231-
validTargetContainer,
239+
targetContainer,
232240
rawEventName,
233241
listener,
234242
passive,
235243
);
236244
} else {
237245
unsubscribeListener = addEventBubbleListener(
238-
validTargetContainer,
246+
targetContainer,
239247
rawEventName,
240248
listener,
241249
);
@@ -249,7 +257,6 @@ export function removeTrappedEventListener(
249257
topLevelType: DOMTopLevelEventType,
250258
capture: boolean,
251259
listener: any => void,
252-
passive: void | boolean,
253260
): void {
254261
const rawEventName = getRawEventName(topLevelType);
255262
removeEventListener(targetContainer, rawEventName, listener, capture);

0 commit comments

Comments
 (0)