Skip to content

Commit 503fd82

Browse files
authored
Modern Event System: Add support for internal FB Primer (#18210)
1 parent 45c172d commit 503fd82

12 files changed

+148
-2
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ import {
5656
TOP_PROGRESS,
5757
TOP_PLAYING,
5858
} from './DOMTopLevelEventTypes';
59+
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
60+
61+
import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags';
5962

6063
const capturePhaseEvents = new Set([
6164
TOP_FOCUS,
@@ -165,6 +168,44 @@ export function listenToEvent(
165168
}
166169
}
167170

171+
const validFBLegacyPrimerRels = new Set([
172+
'dialog',
173+
'dialog-post',
174+
'async',
175+
'async-post',
176+
'theater',
177+
'toggle',
178+
]);
179+
180+
function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
181+
let node = nativeEvent.target;
182+
const type = nativeEvent.type;
183+
if (type !== 'click') {
184+
return false;
185+
}
186+
while (node !== null) {
187+
// Primer works by intercepting a click event on an <a> element
188+
// that has a "rel" attribute that matches one of the valid ones
189+
// in the Set above. If we intercept this before Primer does, we
190+
// will need to defer the current event till later and discontinue
191+
// execution of the current event. To do this we can add a document
192+
// event listener and continue again later after propagation.
193+
if (node.tagName === 'A' && validFBLegacyPrimerRels.has(node.rel)) {
194+
const legacyFBSupport = true;
195+
const isCapture = nativeEvent.eventPhase === 1;
196+
trapEventForPluginEventSystem(
197+
document,
198+
((type: any): DOMTopLevelEventType),
199+
isCapture,
200+
legacyFBSupport,
201+
);
202+
return true;
203+
}
204+
node = node.parentNode;
205+
}
206+
return false;
207+
}
208+
168209
export function dispatchEventForPluginEventSystem(
169210
topLevelType: DOMTopLevelEventType,
170211
eventSystemFlags: EventSystemFlags,
@@ -173,6 +214,17 @@ export function dispatchEventForPluginEventSystem(
173214
rootContainer: Document | Element,
174215
): void {
175216
let ancestorInst = targetInst;
217+
if (rootContainer.nodeType !== DOCUMENT_NODE) {
218+
// If we detect the FB legacy primer system, we
219+
// defer the event to the "document" with a one
220+
// time event listener so we can defer the event.
221+
if (
222+
enableLegacyFBPrimerSupport &&
223+
willDeferLaterForFBLegacyPrimer(nativeEvent)
224+
) {
225+
return;
226+
}
227+
}
176228

177229
batchedEventUpdates(() =>
178230
dispatchEventsForPlugins(

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {passiveBrowserEventsSupported} from './checkPassiveEvents';
5656
import {
5757
enableDeprecatedFlareAPI,
5858
enableModernEventSystem,
59+
enableLegacyFBPrimerSupport,
5960
} from 'shared/ReactFeatureFlags';
6061
import {
6162
UserBlockingEvent,
@@ -143,6 +144,7 @@ export function trapEventForPluginEventSystem(
143144
container: Document | Element,
144145
topLevelType: DOMTopLevelEventType,
145146
capture: boolean,
147+
legacyFBSupport?: boolean,
146148
): void {
147149
let listener;
148150
let listenerWrapper;
@@ -166,10 +168,40 @@ export function trapEventForPluginEventSystem(
166168
);
167169

168170
const rawEventName = getRawEventName(topLevelType);
171+
let fbListener;
172+
173+
// When legacyFBSupport is enabled, it's for when we
174+
// want to add a one time event listener to a container.
175+
// This should only be used with enableLegacyFBPrimerSupport
176+
// due to requirement to provide compatibility with
177+
// internal FB www event tooling. This works by removing
178+
// the event listener as soon as it is invoked. We could
179+
// also attempt to use the {once: true} param on
180+
// addEventListener, but that requires support and some
181+
// browsers do not support this today, and given this is
182+
// to support legacy code patterns, it's likely they'll
183+
// need support for such browsers.
184+
if (enableLegacyFBPrimerSupport && legacyFBSupport) {
185+
const originalListener = listener;
186+
listener = function(...p) {
187+
try {
188+
return originalListener.apply(this, p);
189+
} finally {
190+
if (fbListener) {
191+
fbListener.remove();
192+
} else {
193+
container.removeEventListener(
194+
((rawEventName: any): string),
195+
(listener: any),
196+
);
197+
}
198+
}
199+
};
200+
}
169201
if (capture) {
170-
addEventCaptureListener(container, rawEventName, listener);
202+
fbListener = addEventCaptureListener(container, rawEventName, listener);
171203
} else {
172-
addEventBubbleListener(container, rawEventName, listener);
204+
fbListener = addEventBubbleListener(container, rawEventName, listener);
173205
}
174206
}
175207

packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,54 @@ describe('DOMModernPluginEventSystem', () => {
119119
expect(log[4]).toEqual(['bubble', divElement]);
120120
expect(log[5]).toEqual(['bubble', buttonElement]);
121121
});
122+
123+
it('handle propagation of click events correctly with FB primer', () => {
124+
ReactFeatureFlags.enableLegacyFBPrimerSupport = true;
125+
const aRef = React.createRef();
126+
127+
const log = [];
128+
// Stop propagation throught the React system
129+
const onClick = jest.fn(e => e.stopPropagation());
130+
const onDivClick = jest.fn();
131+
132+
function Test() {
133+
return (
134+
<div onClick={onDivClick}>
135+
<a ref={aRef} href="#" onClick={onClick} rel="dialog">
136+
Click me
137+
</a>
138+
</div>
139+
);
140+
}
141+
ReactDOM.render(<Test />, container);
142+
143+
// Fake primer
144+
document.addEventListener('click', e => {
145+
if (e.target.rel === 'dialog') {
146+
log.push('primer');
147+
}
148+
});
149+
let aElement = aRef.current;
150+
dispatchClickEvent(aElement);
151+
expect(onClick).toHaveBeenCalledTimes(1);
152+
expect(log).toEqual(['primer']);
153+
expect(onDivClick).toHaveBeenCalledTimes(0);
154+
155+
log.length = 0;
156+
// This isn't something that should be picked up by Primer
157+
function Test2() {
158+
return (
159+
<div onClick={onDivClick}>
160+
<a ref={aRef} href="#" onClick={onClick} rel="dialog-foo">
161+
Click me
162+
</a>
163+
</div>
164+
);
165+
}
166+
ReactDOM.render(<Test2 />, container);
167+
dispatchClickEvent(aElement);
168+
expect(onClick).toHaveBeenCalledTimes(1);
169+
expect(log).toEqual([]);
170+
expect(onDivClick).toHaveBeenCalledTimes(0);
171+
});
122172
});

packages/shared/ReactFeatureFlags.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
128128

129129
// Modern event system where events get registered at roots
130130
export const enableModernEventSystem = false;
131+
132+
// Support legacy Primer support on internal FB www
133+
export const enableLegacyFBPrimerSupport = false;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4444
export const runAllPassiveEffectDestroysBeforeCreates = false;
4545
export const enableModernEventSystem = false;
4646
export const warnAboutSpreadingKeyToJSX = false;
47+
export const enableLegacyFBPrimerSupport = false;
4748

4849
// Internal-only attempt to debug a React Native issue. See D20130868.
4950
export const throwEarlyForMysteriousError = true;

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4343
export const runAllPassiveEffectDestroysBeforeCreates = false;
4444
export const enableModernEventSystem = false;
4545
export const warnAboutSpreadingKeyToJSX = false;
46+
export const enableLegacyFBPrimerSupport = false;
4647

4748
// Internal-only attempt to debug a React Native issue. See D20130868.
4849
export const throwEarlyForMysteriousError = false;

packages/shared/forks/ReactFeatureFlags.persistent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4343
export const runAllPassiveEffectDestroysBeforeCreates = false;
4444
export const enableModernEventSystem = false;
4545
export const warnAboutSpreadingKeyToJSX = false;
46+
export const enableLegacyFBPrimerSupport = false;
4647

4748
// Internal-only attempt to debug a React Native issue. See D20130868.
4849
export const throwEarlyForMysteriousError = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4343
export const runAllPassiveEffectDestroysBeforeCreates = false;
4444
export const enableModernEventSystem = false;
4545
export const warnAboutSpreadingKeyToJSX = false;
46+
export const enableLegacyFBPrimerSupport = false;
4647

4748
// Internal-only attempt to debug a React Native issue. See D20130868.
4849
export const throwEarlyForMysteriousError = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4343
export const runAllPassiveEffectDestroysBeforeCreates = false;
4444
export const enableModernEventSystem = false;
4545
export const warnAboutSpreadingKeyToJSX = false;
46+
export const enableLegacyFBPrimerSupport = false;
4647

4748
// Internal-only attempt to debug a React Native issue. See D20130868.
4849
export const throwEarlyForMysteriousError = false;

packages/shared/forks/ReactFeatureFlags.testing.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
4343
export const runAllPassiveEffectDestroysBeforeCreates = false;
4444
export const enableModernEventSystem = false;
4545
export const warnAboutSpreadingKeyToJSX = false;
46+
export const enableLegacyFBPrimerSupport = false;
4647

4748
// Internal-only attempt to debug a React Native issue. See D20130868.
4849
export const throwEarlyForMysteriousError = false;

0 commit comments

Comments
 (0)