Skip to content

Commit

Permalink
Modern Event System: Support nested portal/root boundaries (#18201)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Mar 5, 2020
1 parent fa03206 commit 60b11f6
Show file tree
Hide file tree
Showing 2 changed files with 936 additions and 2 deletions.
66 changes: 65 additions & 1 deletion packages/react-dom/src/events/DOMModernPluginEventSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
import {executeDispatchesInOrder} from 'legacy-events/EventPluginUtils';
import {plugins} from 'legacy-events/EventPluginRegistry';

import {HostRoot, HostPortal} from 'shared/ReactWorkTags';

import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
import getEventTarget from './getEventTarget';
import {getListenerMapForElement} from './DOMEventListenerMap';
Expand Down Expand Up @@ -56,7 +58,8 @@ import {
TOP_PROGRESS,
TOP_PLAYING,
} from './DOMTopLevelEventTypes';
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import {DOCUMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType';

import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags';

Expand Down Expand Up @@ -206,6 +209,17 @@ function willDeferLaterForFBLegacyPrimer(nativeEvent: any): boolean {
return false;
}

function isMatchingRootContainer(
grandContainer: Element,
rootContainer: Document | Element,
): boolean {
return (
grandContainer === rootContainer ||
(grandContainer.nodeType === COMMENT_NODE &&
grandContainer.parentNode === rootContainer)
);
}

export function dispatchEventForPluginEventSystem(
topLevelType: DOMTopLevelEventType,
eventSystemFlags: EventSystemFlags,
Expand All @@ -224,6 +238,56 @@ 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;

while (true) {
if (node === null) {
return;
}
if (node.tag === HostRoot || node.tag === HostPortal) {
const container = node.stateNode.containerInfo;
if (isMatchingRootContainer(container, rootContainer)) {
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, rootContainer)) {
// 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;
}
}

batchedEventUpdates(() =>
Expand Down
Loading

0 comments on commit 60b11f6

Please sign in to comment.