ModernEventSystem: fix event replaying (#18346)

* ModernEventSystem: fix event replaying
This commit is contained in:
Dominic Gannaway
2020-03-19 20:44:38 +00:00
committed by GitHub
parent ffefb4e77f
commit 9e7f334c71
2 changed files with 100 additions and 77 deletions
+52 -50
View File
@@ -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;
}
}
+48 -27
View File
@@ -127,7 +127,7 @@ type QueuedReplayableEvent = {|
topLevelType: DOMTopLevelEventType,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetContainer: EventTarget | null,
targetContainers: Array<EventTarget>,
|};
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();
}