From 9e7f334c7159b10900a8d197c3df226d32efdc96 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 19 Mar 2020 20:44:38 +0000 Subject: [PATCH] ModernEventSystem: fix event replaying (#18346) * ModernEventSystem: fix event replaying --- .../src/events/DOMModernPluginEventSystem.js | 102 +++++++++--------- .../src/events/ReactDOMEventReplaying.js | 75 ++++++++----- 2 files changed, 100 insertions(+), 77 deletions(-) diff --git a/packages/react-dom/src/events/DOMModernPluginEventSystem.js b/packages/react-dom/src/events/DOMModernPluginEventSystem.js index 2ed642efb4..f9f7fe0c12 100644 --- a/packages/react-dom/src/events/DOMModernPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMModernPluginEventSystem.js @@ -302,60 +302,62 @@ export function dispatchEventForPluginEventSystem( ) { return; } - // The below logic attempts to work out if we need to change - // the target fiber to a different ancestor. We had similar logic - // in the legacy event system, except the big difference between - // systems is that the modern event system now has an event listener - // attached to each React Root and React Portal Root. Together, - // the DOM nodes representing these roots are the "rootContainer". - // To figure out which ancestor instance we should use, we traverse - // up the fiber tree from the target instance and attempt to find - // root boundaries that match that of our current "rootContainer". - // If we find that "rootContainer", we find the parent fiber - // sub-tree for that root and make that our ancestor instance. - let node = targetInst; + if (targetInst !== null) { + // The below logic attempts to work out if we need to change + // the target fiber to a different ancestor. We had similar logic + // in the legacy event system, except the big difference between + // systems is that the modern event system now has an event listener + // attached to each React Root and React Portal Root. Together, + // the DOM nodes representing these roots are the "rootContainer". + // To figure out which ancestor instance we should use, we traverse + // up the fiber tree from the target instance and attempt to find + // root boundaries that match that of our current "rootContainer". + // If we find that "rootContainer", we find the parent fiber + // sub-tree for that root and make that our ancestor instance. + let node = targetInst; - while (true) { - if (node === null) { - return; - } - if (node.tag === HostRoot || node.tag === HostPortal) { - const container = node.stateNode.containerInfo; - if (isMatchingRootContainer(container, possibleTargetContainerNode)) { - break; - } - if (node.tag === HostPortal) { - // The target is a portal, but it's not the rootContainer we're looking for. - // Normally portals handle their own events all the way down to the root. - // So we should be able to stop now. However, we don't know if this portal - // was part of *our* root. - let grandNode = node.return; - while (grandNode !== null) { - if (grandNode.tag === HostRoot || grandNode.tag === HostPortal) { - const grandContainer = grandNode.stateNode.containerInfo; - if ( - isMatchingRootContainer( - grandContainer, - possibleTargetContainerNode, - ) - ) { - // This is the rootContainer we're looking for and we found it as - // a parent of the Portal. That means we can ignore it because the - // Portal will bubble through to us. - return; - } - } - grandNode = grandNode.return; - } - } - const parentSubtreeInst = getClosestInstanceFromNode(container); - if (parentSubtreeInst === null) { + while (true) { + if (node === null) { return; } - node = ancestorInst = parentSubtreeInst; - continue; + if (node.tag === HostRoot || node.tag === HostPortal) { + const container = node.stateNode.containerInfo; + if (isMatchingRootContainer(container, possibleTargetContainerNode)) { + break; + } + if (node.tag === HostPortal) { + // The target is a portal, but it's not the rootContainer we're looking for. + // Normally portals handle their own events all the way down to the root. + // So we should be able to stop now. However, we don't know if this portal + // was part of *our* root. + let grandNode = node.return; + while (grandNode !== null) { + if (grandNode.tag === HostRoot || grandNode.tag === HostPortal) { + const grandContainer = grandNode.stateNode.containerInfo; + if ( + isMatchingRootContainer( + grandContainer, + possibleTargetContainerNode, + ) + ) { + // This is the rootContainer we're looking for and we found it as + // a parent of the Portal. That means we can ignore it because the + // Portal will bubble through to us. + return; + } + } + grandNode = grandNode.return; + } + } + const parentSubtreeInst = getClosestInstanceFromNode(container); + if (parentSubtreeInst === null) { + return; + } + node = ancestorInst = parentSubtreeInst; + continue; + } + node = node.return; } - node = node.return; } } diff --git a/packages/react-dom/src/events/ReactDOMEventReplaying.js b/packages/react-dom/src/events/ReactDOMEventReplaying.js index 83348eb640..9f34d3a898 100644 --- a/packages/react-dom/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom/src/events/ReactDOMEventReplaying.js @@ -127,7 +127,7 @@ type QueuedReplayableEvent = {| topLevelType: DOMTopLevelEventType, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, - targetContainer: EventTarget | null, + targetContainers: Array, |}; let hasScheduledReplayAttempt = false; @@ -294,7 +294,7 @@ function createQueuedReplayableEvent( topLevelType, eventSystemFlags: eventSystemFlags | IS_REPLAYED, nativeEvent, - targetContainer, + targetContainers: targetContainer !== null ? [targetContainer] : [], }; } @@ -402,9 +402,16 @@ function accumulateOrCreateContinuousQueuedReplayableEvent( } // If we have already queued this exact event, then it's because // the different event systems have different DOM event listeners. - // We can accumulate the flags and store a single event to be - // replayed. + // We can accumulate the flags, and the targetContainers, and + // store a single event to be replayed. existingQueuedEvent.eventSystemFlags |= eventSystemFlags; + const targetContainers = existingQueuedEvent.targetContainers; + if ( + targetContainer !== null && + targetContainers.indexOf(targetContainer) === -1 + ) { + targetContainers.push(targetContainer); + } return existingQueuedEvent; } @@ -555,20 +562,26 @@ function attemptReplayContinuousQueuedEvent( if (queuedEvent.blockedOn !== null) { return false; } - let nextBlockedOn = attemptToDispatchEvent( - queuedEvent.topLevelType, - queuedEvent.eventSystemFlags, - queuedEvent.targetContainer, - queuedEvent.nativeEvent, - ); - if (nextBlockedOn !== null) { - // We're still blocked. Try again later. - let fiber = getInstanceFromNode(nextBlockedOn); - if (fiber !== null) { - attemptContinuousHydration(fiber); + let targetContainers = queuedEvent.targetContainers; + while (targetContainers.length > 0) { + let targetContainer = targetContainers[0]; + let nextBlockedOn = attemptToDispatchEvent( + queuedEvent.topLevelType, + queuedEvent.eventSystemFlags, + targetContainer, + queuedEvent.nativeEvent, + ); + if (nextBlockedOn !== null) { + // We're still blocked. Try again later. + let fiber = getInstanceFromNode(nextBlockedOn); + if (fiber !== null) { + attemptContinuousHydration(fiber); + } + queuedEvent.blockedOn = nextBlockedOn; + return false; } - queuedEvent.blockedOn = nextBlockedOn; - return false; + // This target container was successfully dispatched. Try the next. + targetContainers.shift(); } return true; } @@ -598,16 +611,24 @@ function replayUnblockedEvents() { } break; } - let nextBlockedOn = attemptToDispatchEvent( - nextDiscreteEvent.topLevelType, - nextDiscreteEvent.eventSystemFlags, - nextDiscreteEvent.targetContainer, - nextDiscreteEvent.nativeEvent, - ); - if (nextBlockedOn !== null) { - // We're still blocked. Try again later. - nextDiscreteEvent.blockedOn = nextBlockedOn; - } else { + let targetContainers = nextDiscreteEvent.targetContainers; + while (targetContainers.length > 0) { + let targetContainer = targetContainers[0]; + let nextBlockedOn = attemptToDispatchEvent( + nextDiscreteEvent.topLevelType, + nextDiscreteEvent.eventSystemFlags, + targetContainer, + nextDiscreteEvent.nativeEvent, + ); + if (nextBlockedOn !== null) { + // We're still blocked. Try again later. + nextDiscreteEvent.blockedOn = nextBlockedOn; + break; + } + // This target container was successfully dispatched. Try the next. + targetContainers.shift(); + } + if (nextDiscreteEvent.blockedOn === null) { // We've successfully replayed the first event. Let's try the next one. queuedDiscreteEvents.shift(); }