Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/react-dom/src/events/DOMModernPluginEventSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down
36 changes: 34 additions & 2 deletions packages/react-dom/src/events/ReactDOMEventListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {passiveBrowserEventsSupported} from './checkPassiveEvents';
import {
enableDeprecatedFlareAPI,
enableModernEventSystem,
enableLegacyFBPrimerSupport,
} from 'shared/ReactFeatureFlags';
import {
UserBlockingEvent,
Expand Down Expand Up @@ -143,6 +144,7 @@ export function trapEventForPluginEventSystem(
container: Document | Element,
topLevelType: DOMTopLevelEventType,
capture: boolean,
legacyFBSupport?: boolean,
): void {
let listener;
let listenerWrapper;
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.persistent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.testing.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down