diff --git a/packages/react-dom/src/events/CurrentReplayingEvent.js b/packages/react-dom/src/events/CurrentReplayingEvent.js new file mode 100644 index 000000000000..c7091360de3b --- /dev/null +++ b/packages/react-dom/src/events/CurrentReplayingEvent.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ +import type {AnyNativeEvent} from '../events/PluginModuleType'; + +// This exists to avoid circular dependency between ReactDOMEventReplaying +// and DOMPluginEventSystem. + +let currentReplayingEvent = null; + +export function setReplayingEvent(event: AnyNativeEvent): void { + if (__DEV__) { + if (currentReplayingEvent !== null) { + console.error( + 'Expected currently replaying event to be null. This error ' + + 'is likely caused by a bug in React. Please file an issue.', + ); + } + } + currentReplayingEvent = event; +} + +export function resetReplayingEvent(): void { + if (__DEV__) { + if (currentReplayingEvent === null) { + console.error( + 'Expected currently replaying event to not be null. This error ' + + 'is likely caused by a bug in React. Please file an issue.', + ); + } + } + currentReplayingEvent = null; +} + +export function isReplayingEvent(event: AnyNativeEvent): boolean { + return event === currentReplayingEvent; +} diff --git a/packages/react-dom/src/events/DOMPluginEventSystem.js b/packages/react-dom/src/events/DOMPluginEventSystem.js index 7f7f77183726..e52b128c11da 100644 --- a/packages/react-dom/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMPluginEventSystem.js @@ -27,6 +27,7 @@ import { IS_EVENT_HANDLE_NON_MANAGED_NODE, IS_NON_DELEGATED, } from './EventSystemFlags'; +import {isReplayingEvent} from './CurrentReplayingEvent'; import { HostRoot, @@ -557,7 +558,8 @@ export function dispatchEventForPluginEventSystem( // for legacy FB support, where the expected behavior was to // match React < 16 behavior of delegated clicks to the doc. domEventName === 'click' && - (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 + (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 && + !isReplayingEvent(nativeEvent) ) { deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer); return; diff --git a/packages/react-dom/src/events/EventSystemFlags.js b/packages/react-dom/src/events/EventSystemFlags.js index 150a73d35ded..2e2600dfb506 100644 --- a/packages/react-dom/src/events/EventSystemFlags.js +++ b/packages/react-dom/src/events/EventSystemFlags.js @@ -13,11 +13,10 @@ export const IS_EVENT_HANDLE_NON_MANAGED_NODE = 1; export const IS_NON_DELEGATED = 1 << 1; export const IS_CAPTURE_PHASE = 1 << 2; export const IS_PASSIVE = 1 << 3; -export const IS_REPLAYED = 1 << 4; -export const IS_LEGACY_FB_SUPPORT_MODE = 1 << 5; +export const IS_LEGACY_FB_SUPPORT_MODE = 1 << 4; export const SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE = - IS_LEGACY_FB_SUPPORT_MODE | IS_REPLAYED | IS_CAPTURE_PHASE; + IS_LEGACY_FB_SUPPORT_MODE | IS_CAPTURE_PHASE; // We do not want to defer if the event system has already been // set to LEGACY_FB_SUPPORT. LEGACY_FB_SUPPORT only gets set when diff --git a/packages/react-dom/src/events/ReactDOMEventReplaying.js b/packages/react-dom/src/events/ReactDOMEventReplaying.js index e277b98a9ff8..bfcc7a4596f4 100644 --- a/packages/react-dom/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom/src/events/ReactDOMEventReplaying.js @@ -31,6 +31,7 @@ import { findInstanceBlockingEvent, return_targetInst, } from './ReactDOMEventListener'; +import {setReplayingEvent, resetReplayingEvent} from './CurrentReplayingEvent'; import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem'; import { getInstanceFromNode, @@ -91,8 +92,6 @@ type PointerEvent = Event & { ... }; -import {IS_REPLAYED} from './EventSystemFlags'; - type QueuedReplayableEvent = {| blockedOn: null | Container | SuspenseInstance, domEventName: DOMEventName, @@ -180,7 +179,7 @@ function createQueuedReplayableEvent( return { blockedOn, domEventName, - eventSystemFlags: eventSystemFlags | IS_REPLAYED, + eventSystemFlags, nativeEvent, targetContainers: [targetContainer], }; @@ -473,6 +472,7 @@ function attemptReplayContinuousQueuedEvent( queuedEvent.nativeEvent, ); if (nextBlockedOn === null) { + setReplayingEvent(queuedEvent.nativeEvent); dispatchEventForPluginEventSystem( queuedEvent.domEventName, queuedEvent.eventSystemFlags, @@ -480,6 +480,7 @@ function attemptReplayContinuousQueuedEvent( return_targetInst, targetContainer, ); + resetReplayingEvent(); } else { // We're still blocked. Try again later. const fiber = getInstanceFromNode(nextBlockedOn); @@ -531,6 +532,7 @@ function replayUnblockedEvents() { nextDiscreteEvent.nativeEvent, ); if (nextBlockedOn === null) { + setReplayingEvent(nextDiscreteEvent.nativeEvent); dispatchEventForPluginEventSystem( nextDiscreteEvent.domEventName, nextDiscreteEvent.eventSystemFlags, @@ -538,6 +540,7 @@ function replayUnblockedEvents() { return_targetInst, targetContainer, ); + resetReplayingEvent(); } else { // We're still blocked. Try again later. nextDiscreteEvent.blockedOn = nextBlockedOn; diff --git a/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js index be5d94da806c..ea7e3fac1cd5 100644 --- a/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js @@ -13,7 +13,7 @@ import type {DispatchQueue} from '../DOMPluginEventSystem'; import type {EventSystemFlags} from '../EventSystemFlags'; import {registerDirectEvent} from '../EventRegistry'; -import {IS_REPLAYED} from 'react-dom/src/events/EventSystemFlags'; +import {isReplayingEvent} from '../CurrentReplayingEvent'; import {SyntheticMouseEvent, SyntheticPointerEvent} from '../SyntheticEvent'; import { getClosestInstanceFromNode, @@ -54,7 +54,7 @@ function extractEvents( const isOutEvent = domEventName === 'mouseout' || domEventName === 'pointerout'; - if (isOverEvent && (eventSystemFlags & IS_REPLAYED) === 0) { + if (isOverEvent && !isReplayingEvent(nativeEvent)) { // If this is an over event with a target, we might have already dispatched // the event in the out event of the other target. If this is replayed, // then it's because we couldn't dispatch against this target previously