Skip to content

Commit 1445095

Browse files
committed
Modern Event System: Add support for internal FB Primer
Address feedback Ensure click onl Fix
1 parent 355970a commit 1445095

12 files changed

+126
-2
lines changed

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

Lines changed: 51 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,43 @@ 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+
trapEventForPluginEventSystem(
196+
document,
197+
((type: any): DOMTopLevelEventType),
198+
false,
199+
legacyFBSupport,
200+
);
201+
return true;
202+
}
203+
node = node.parentNode;
204+
}
205+
return false;
206+
}
207+
168208
export function dispatchEventForPluginEventSystem(
169209
topLevelType: DOMTopLevelEventType,
170210
eventSystemFlags: EventSystemFlags,
@@ -173,6 +213,17 @@ export function dispatchEventForPluginEventSystem(
173213
rootContainer: Document | Element,
174214
): void {
175215
let ancestorInst = targetInst;
216+
if (rootContainer.nodeType !== DOCUMENT_NODE) {
217+
// If we detect the FB legacy primer system, we
218+
// defer the event to the "document" with a one
219+
// time event listener so we can defer the event.
220+
if (
221+
enableLegacyFBPrimerSupport &&
222+
willDeferLaterForFBLegacyPrimer(nativeEvent)
223+
) {
224+
return;
225+
}
226+
}
176227

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

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)