Skip to content

Commit 842e2e4

Browse files
committed
Modern Event System: Add support for internal FB Primer
1 parent 355970a commit 842e2e4

12 files changed

+125
-2
lines changed

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

Lines changed: 48 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,40 @@ 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+
183+
while (node !== null) {
184+
// Primer works by intercepting a click event on an <a> element
185+
// that has a "rel" attribute that matches one of the valid ones
186+
// in the Set above. If we intercept this before Primer does, we
187+
// will need to defer the current event till later and discontinue
188+
// execution of the current event. To do this we can add a document
189+
// event listener and continue again later after propagation.
190+
if (node.tagName === 'A' && validFBLegacyPrimerRels.has(node.rel)) {
191+
const legacyFBSupport = true;
192+
trapEventForPluginEventSystem(
193+
document,
194+
nativeEvent.type,
195+
false,
196+
legacyFBSupport,
197+
);
198+
return true;
199+
}
200+
node = node.parentNode;
201+
}
202+
return false;
203+
}
204+
168205
export function dispatchEventForPluginEventSystem(
169206
topLevelType: DOMTopLevelEventType,
170207
eventSystemFlags: EventSystemFlags,
@@ -173,6 +210,17 @@ export function dispatchEventForPluginEventSystem(
173210
rootContainer: Document | Element,
174211
): void {
175212
let ancestorInst = targetInst;
213+
if (rootContainer.nodeType !== DOCUMENT_NODE) {
214+
// If we detect the FB legacy primer system, we
215+
// defer the event to the "document" with a one
216+
// time event listener so we can defer the event.
217+
if (
218+
enableLegacyFBPrimerSupport &&
219+
willDeferLaterForFBLegacyPrimer(nativeEvent)
220+
) {
221+
return;
222+
}
223+
}
176224

177225
batchedEventUpdates(() =>
178226
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,35 @@ 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 primer', () => {
124+
const aRef = React.createRef();
125+
const log = [];
126+
// Stop propagation throught the React system
127+
const onClick = jest.fn(e => e.stopPropagation());
128+
129+
function Test() {
130+
return (
131+
<a ref={aRef} href="#" onClick={onClick} rel="dialog">
132+
Click me
133+
</a>
134+
);
135+
}
136+
137+
ReactDOM.render(<Test />, container);
138+
139+
// Fake primer
140+
document.addEventListener('click', e => {
141+
if (e.target.rel === 'dialog') {
142+
log.push('primer');
143+
}
144+
});
145+
146+
ReactFeatureFlags.enableLegacyFBPrimerSupport = true;
147+
let aElement = aRef.current;
148+
dispatchClickEvent(aElement);
149+
expect(onClick).toHaveBeenCalledTimes(1);
150+
expect(log).toEqual(['primer']);
151+
ReactFeatureFlags.enableLegacyFBPrimerSupport = false;
152+
});
122153
});

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)