mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Modern Event System: Add support for internal FB Primer (#18210)
This commit is contained in:
@@ -56,6 +56,9 @@ import {
|
||||
TOP_PROGRESS,
|
||||
TOP_PLAYING,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
|
||||
|
||||
import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const capturePhaseEvents = new Set([
|
||||
TOP_FOCUS,
|
||||
@@ -165,6 +168,44 @@ export function listenToEvent(
|
||||
}
|
||||
}
|
||||
|
||||
const validFBLegacyPrimerRels = new Set([
|
||||
'dialog',
|
||||
'dialog-post',
|
||||
'async',
|
||||
'async-post',
|
||||
'theater',
|
||||
'toggle',
|
||||
]);
|
||||
|
||||
function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
|
||||
let node = nativeEvent.target;
|
||||
const type = nativeEvent.type;
|
||||
if (type !== 'click') {
|
||||
return false;
|
||||
}
|
||||
while (node !== null) {
|
||||
// Primer works by intercepting a click event on an <a> element
|
||||
// that has a "rel" attribute that matches one of the valid ones
|
||||
// in the Set above. If we intercept this before Primer does, we
|
||||
// will need to defer the current event till later and discontinue
|
||||
// execution of the current event. To do this we can add a document
|
||||
// event listener and continue again later after propagation.
|
||||
if (node.tagName === 'A' && validFBLegacyPrimerRels.has(node.rel)) {
|
||||
const legacyFBSupport = true;
|
||||
const isCapture = nativeEvent.eventPhase === 1;
|
||||
trapEventForPluginEventSystem(
|
||||
document,
|
||||
((type: any): DOMTopLevelEventType),
|
||||
isCapture,
|
||||
legacyFBSupport,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function dispatchEventForPluginEventSystem(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
@@ -173,6 +214,17 @@ export function dispatchEventForPluginEventSystem(
|
||||
rootContainer: Document | Element,
|
||||
): void {
|
||||
let ancestorInst = targetInst;
|
||||
if (rootContainer.nodeType !== DOCUMENT_NODE) {
|
||||
// If we detect the FB legacy primer system, we
|
||||
// defer the event to the "document" with a one
|
||||
// time event listener so we can defer the event.
|
||||
if (
|
||||
enableLegacyFBPrimerSupport &&
|
||||
willDeferLaterForFBLegacyPrimer(nativeEvent)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
batchedEventUpdates(() =>
|
||||
dispatchEventsForPlugins(
|
||||
|
||||
+34
-2
@@ -56,6 +56,7 @@ import {passiveBrowserEventsSupported} from './checkPassiveEvents';
|
||||
import {
|
||||
enableDeprecatedFlareAPI,
|
||||
enableModernEventSystem,
|
||||
enableLegacyFBPrimerSupport,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
UserBlockingEvent,
|
||||
@@ -143,6 +144,7 @@ export function trapEventForPluginEventSystem(
|
||||
container: Document | Element,
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
capture: boolean,
|
||||
legacyFBSupport?: boolean,
|
||||
): void {
|
||||
let listener;
|
||||
let listenerWrapper;
|
||||
@@ -166,10 +168,40 @@ export function trapEventForPluginEventSystem(
|
||||
);
|
||||
|
||||
const rawEventName = getRawEventName(topLevelType);
|
||||
let fbListener;
|
||||
|
||||
// When legacyFBSupport is enabled, it's for when we
|
||||
// want to add a one time event listener to a container.
|
||||
// This should only be used with enableLegacyFBPrimerSupport
|
||||
// due to requirement to provide compatibility with
|
||||
// internal FB www event tooling. This works by removing
|
||||
// the event listener as soon as it is invoked. We could
|
||||
// also attempt to use the {once: true} param on
|
||||
// addEventListener, but that requires support and some
|
||||
// browsers do not support this today, and given this is
|
||||
// to support legacy code patterns, it's likely they'll
|
||||
// need support for such browsers.
|
||||
if (enableLegacyFBPrimerSupport && legacyFBSupport) {
|
||||
const originalListener = listener;
|
||||
listener = function(...p) {
|
||||
try {
|
||||
return originalListener.apply(this, p);
|
||||
} finally {
|
||||
if (fbListener) {
|
||||
fbListener.remove();
|
||||
} else {
|
||||
container.removeEventListener(
|
||||
((rawEventName: any): string),
|
||||
(listener: any),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
if (capture) {
|
||||
addEventCaptureListener(container, rawEventName, listener);
|
||||
fbListener = addEventCaptureListener(container, rawEventName, listener);
|
||||
} else {
|
||||
addEventBubbleListener(container, rawEventName, listener);
|
||||
fbListener = addEventBubbleListener(container, rawEventName, listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,4 +119,54 @@ describe('DOMModernPluginEventSystem', () => {
|
||||
expect(log[4]).toEqual(['bubble', divElement]);
|
||||
expect(log[5]).toEqual(['bubble', buttonElement]);
|
||||
});
|
||||
|
||||
it('handle propagation of click events correctly with FB primer', () => {
|
||||
ReactFeatureFlags.enableLegacyFBPrimerSupport = true;
|
||||
const aRef = React.createRef();
|
||||
|
||||
const log = [];
|
||||
// Stop propagation throught the React system
|
||||
const onClick = jest.fn(e => e.stopPropagation());
|
||||
const onDivClick = jest.fn();
|
||||
|
||||
function Test() {
|
||||
return (
|
||||
<div onClick={onDivClick}>
|
||||
<a ref={aRef} href="#" onClick={onClick} rel="dialog">
|
||||
Click me
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<Test />, container);
|
||||
|
||||
// Fake primer
|
||||
document.addEventListener('click', e => {
|
||||
if (e.target.rel === 'dialog') {
|
||||
log.push('primer');
|
||||
}
|
||||
});
|
||||
let aElement = aRef.current;
|
||||
dispatchClickEvent(aElement);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(log).toEqual(['primer']);
|
||||
expect(onDivClick).toHaveBeenCalledTimes(0);
|
||||
|
||||
log.length = 0;
|
||||
// This isn't something that should be picked up by Primer
|
||||
function Test2() {
|
||||
return (
|
||||
<div onClick={onDivClick}>
|
||||
<a ref={aRef} href="#" onClick={onClick} rel="dialog-foo">
|
||||
Click me
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<Test2 />, container);
|
||||
dispatchClickEvent(aElement);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(log).toEqual([]);
|
||||
expect(onDivClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,3 +128,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
|
||||
|
||||
// Modern event system where events get registered at roots
|
||||
export const enableModernEventSystem = false;
|
||||
|
||||
// Support legacy Primer support on internal FB www
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
@@ -44,6 +44,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = true;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = false;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
|
||||
export const runAllPassiveEffectDestroysBeforeCreates = false;
|
||||
export const enableModernEventSystem = false;
|
||||
export const warnAboutSpreadingKeyToJSX = false;
|
||||
export const enableLegacyFBPrimerSupport = !__EXPERIMENTAL__;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
@@ -97,6 +97,8 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
|
||||
|
||||
export const enableModernEventSystem = false;
|
||||
|
||||
export const enableLegacyFBPrimerSupport = !__EXPERIMENTAL__;
|
||||
|
||||
// Internal-only attempt to debug a React Native issue. See D20130868.
|
||||
export const throwEarlyForMysteriousError = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user