ModernEventSystem: fix event replaying (#18346)
* ModernEventSystem: fix event replaying
Dominic Gannaway committed
Mar 19, 2020 at 20:44 UTC
9e7f334c7159b10900a8d197c3df226d32efdc96
2 files changed
+97
-74
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+49
-47
@@ -302,60 +302,62 @@ export function dispatchEventForPluginEventSystem(
302
) {
303
return;
304
}
305
- // The below logic attempts to work out if we need to change
306
- // the target fiber to a different ancestor. We had similar logic
307
- // in the legacy event system, except the big difference between
308
- // systems is that the modern event system now has an event listener
309
- // attached to each React Root and React Portal Root. Together,
310
- // the DOM nodes representing these roots are the "rootContainer".
311
- // To figure out which ancestor instance we should use, we traverse
312
- // up the fiber tree from the target instance and attempt to find
313
- // root boundaries that match that of our current "rootContainer".
314
- // If we find that "rootContainer", we find the parent fiber
315
- // sub-tree for that root and make that our ancestor instance.
316
- let node = targetInst;
305
+ if (targetInst !== null) {
306
+ // The below logic attempts to work out if we need to change
307
+ // the target fiber to a different ancestor. We had similar logic
308
+ // in the legacy event system, except the big difference between
309
+ // systems is that the modern event system now has an event listener
310
+ // attached to each React Root and React Portal Root. Together,
311
+ // the DOM nodes representing these roots are the "rootContainer".
312
+ // To figure out which ancestor instance we should use, we traverse
313
+ // up the fiber tree from the target instance and attempt to find
314
+ // root boundaries that match that of our current "rootContainer".
315
+ // If we find that "rootContainer", we find the parent fiber
316
+ // sub-tree for that root and make that our ancestor instance.
317
+ let node = targetInst;
318
318
- while (true) {
319
- if (node === null) {
320
- return;
321
- }
322
- if (node.tag === HostRoot || node.tag === HostPortal) {
323
- const container = node.stateNode.containerInfo;
324
- if (isMatchingRootContainer(container, possibleTargetContainerNode)) {
325
- break;
319
+ while (true) {
320
+ if (node === null) {
321
+ return;
322
}
327
- if (node.tag === HostPortal) {
328
- // The target is a portal, but it's not the rootContainer we're looking for.
329
- // Normally portals handle their own events all the way down to the root.
330
- // So we should be able to stop now. However, we don't know if this portal
331
- // was part of *our* root.
332
- let grandNode = node.return;
333
- while (grandNode !== null) {
334
- if (grandNode.tag === HostRoot || grandNode.tag === HostPortal) {
335
- const grandContainer = grandNode.stateNode.containerInfo;
336
- if (
337
- isMatchingRootContainer(
338
- grandContainer,
339
- possibleTargetContainerNode,
340
- )
341
- ) {
342
- // This is the rootContainer we're looking for and we found it as
343
- // a parent of the Portal. That means we can ignore it because the
344
- // Portal will bubble through to us.
345
- return;
323
+ if (node.tag === HostRoot || node.tag === HostPortal) {
324
+ const container = node.stateNode.containerInfo;
325
+ if (isMatchingRootContainer(container, possibleTargetContainerNode)) {
326
+ break;
327
+ }
328
+ if (node.tag === HostPortal) {
329
+ // The target is a portal, but it's not the rootContainer we're looking for.
330
+ // Normally portals handle their own events all the way down to the root.
331
+ // So we should be able to stop now. However, we don't know if this portal
332
+ // was part of *our* root.
333
+ let grandNode = node.return;
334
+ while (grandNode !== null) {
335
+ if (grandNode.tag === HostRoot || grandNode.tag === HostPortal) {
336
+ const grandContainer = grandNode.stateNode.containerInfo;
337
+ if (
338
+ isMatchingRootContainer(
339
+ grandContainer,
340
+ possibleTargetContainerNode,
341
+ )
342
+ ) {
343
+ // This is the rootContainer we're looking for and we found it as
344
+ // a parent of the Portal. That means we can ignore it because the
345
+ // Portal will bubble through to us.
346
+ return;
347
+ }
348
}
349
+ grandNode = grandNode.return;
350
}
348
- grandNode = grandNode.return;
351
}
352
+ const parentSubtreeInst = getClosestInstanceFromNode(container);
353
+ if (parentSubtreeInst === null) {
354
+ return;
355
+ }
356
+ node = ancestorInst = parentSubtreeInst;
357
+ continue;
358
}
351
- const parentSubtreeInst = getClosestInstanceFromNode(container);
352
- if (parentSubtreeInst === null) {
353
- return;
354
- }
355
- node = ancestorInst = parentSubtreeInst;
356
- continue;
359
+ node = node.return;
360
}
358
- node = node.return;
361
}
362
}
363
packages/react-dom/src/events/ReactDOMEventReplaying.js
+48
-27
@@ -127,7 +127,7 @@ type QueuedReplayableEvent = {|
127
topLevelType: DOMTopLevelEventType,
128
eventSystemFlags: EventSystemFlags,
129
nativeEvent: AnyNativeEvent,
130
- targetContainer: EventTarget | null,
130
+ targetContainers: Array<EventTarget>,
131
|};
132
133
let hasScheduledReplayAttempt = false;
@@ -294,7 +294,7 @@ function createQueuedReplayableEvent(
294
topLevelType,
295
eventSystemFlags: eventSystemFlags | IS_REPLAYED,
296
nativeEvent,
297
- targetContainer,
297
+ targetContainers: targetContainer !== null ? [targetContainer] : [],
298
};
299
}
300
@@ -402,9 +402,16 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
402
}
403
// If we have already queued this exact event, then it's because
404
// the different event systems have different DOM event listeners.
405
- // We can accumulate the flags and store a single event to be
406
- // replayed.
405
+ // We can accumulate the flags, and the targetContainers, and
406
+ // store a single event to be replayed.
407
existingQueuedEvent.eventSystemFlags |= eventSystemFlags;
408
+ const targetContainers = existingQueuedEvent.targetContainers;
409
+ if (
410
+ targetContainer !== null &&
411
+ targetContainers.indexOf(targetContainer) === -1
412
+ ) {
413
+ targetContainers.push(targetContainer);
414
+ }
415
return existingQueuedEvent;
416
}
417
@@ -555,20 +562,26 @@ function attemptReplayContinuousQueuedEvent(
562
if (queuedEvent.blockedOn !== null) {
563
return false;
564
}
558
- let nextBlockedOn = attemptToDispatchEvent(
559
- queuedEvent.topLevelType,
560
- queuedEvent.eventSystemFlags,
561
- queuedEvent.targetContainer,
562
- queuedEvent.nativeEvent,
563
- );
564
- if (nextBlockedOn !== null) {
565
- // We're still blocked. Try again later.
566
- let fiber = getInstanceFromNode(nextBlockedOn);
567
- if (fiber !== null) {
568
- attemptContinuousHydration(fiber);
565
+ let targetContainers = queuedEvent.targetContainers;
566
+ while (targetContainers.length > 0) {
567
+ let targetContainer = targetContainers[0];
568
+ let nextBlockedOn = attemptToDispatchEvent(
569
+ queuedEvent.topLevelType,
570
+ queuedEvent.eventSystemFlags,
571
+ targetContainer,
572
+ queuedEvent.nativeEvent,
573
+ );
574
+ if (nextBlockedOn !== null) {
575
+ // We're still blocked. Try again later.
576
+ let fiber = getInstanceFromNode(nextBlockedOn);
577
+ if (fiber !== null) {
578
+ attemptContinuousHydration(fiber);
579
+ }
580
+ queuedEvent.blockedOn = nextBlockedOn;
581
+ return false;
582
}
570
- queuedEvent.blockedOn = nextBlockedOn;
571
- return false;
583
+ // This target container was successfully dispatched. Try the next.
584
+ targetContainers.shift();
585
}
586
return true;
587
}
@@ -598,16 +611,24 @@ function replayUnblockedEvents() {
611
}
612
break;
613
}
601
- let nextBlockedOn = attemptToDispatchEvent(
602
- nextDiscreteEvent.topLevelType,
603
- nextDiscreteEvent.eventSystemFlags,
604
- nextDiscreteEvent.targetContainer,
605
- nextDiscreteEvent.nativeEvent,
606
- );
607
- if (nextBlockedOn !== null) {
608
- // We're still blocked. Try again later.
609
- nextDiscreteEvent.blockedOn = nextBlockedOn;
610
- } else {
614
+ let targetContainers = nextDiscreteEvent.targetContainers;
615
+ while (targetContainers.length > 0) {
616
+ let targetContainer = targetContainers[0];
617
+ let nextBlockedOn = attemptToDispatchEvent(
618
+ nextDiscreteEvent.topLevelType,
619
+ nextDiscreteEvent.eventSystemFlags,
620
+ targetContainer,
621
+ nextDiscreteEvent.nativeEvent,
622
+ );
623
+ if (nextBlockedOn !== null) {
624
+ // We're still blocked. Try again later.
625
+ nextDiscreteEvent.blockedOn = nextBlockedOn;
626
+ break;
627
+ }
628
+ // This target container was successfully dispatched. Try the next.
629
+ targetContainers.shift();
630
+ }
631
+ if (nextDiscreteEvent.blockedOn === null) {
632
// We've successfully replayed the first event. Let's try the next one.
633
queuedDiscreteEvents.shift();
634
}