Modern Event System: Add support for internal FB Primer (#18210)

This commit is contained in:
Dominic Gannaway
2020-03-04 23:41:59 +00:00
committed by GitHub
parent 45c172d948
commit 503fd82b42
12 changed files with 148 additions and 2 deletions
@@ -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
View File
@@ -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);
});
});
+3
View File
@@ -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;