Modern Event System: revise ancestor logic (#18886)

This commit is contained in:
Dominic Gannaway 2020-05-11 22:09:52 +01:00 committed by GitHub
parent 2b9d7cf65f
commit e16703e6c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 13 deletions

View File

@ -417,13 +417,13 @@ export function dispatchEventForPluginEventSystem(
// sub-tree for that root and make that our ancestor instance.
let node = targetInst;
while (true) {
mainLoop: while (true) {
if (node === null) {
return;
}
const nodeTag = node.tag;
if (nodeTag === HostRoot || nodeTag === HostPortal) {
const container = node.stateNode.containerInfo;
let container = node.stateNode.containerInfo;
if (isMatchingRootContainer(container, targetContainerNode)) {
break;
}
@ -449,18 +449,23 @@ export function dispatchEventForPluginEventSystem(
grandNode = grandNode.return;
}
}
const parentSubtreeInst = getClosestInstanceFromNode(container);
if (parentSubtreeInst === null) {
return;
// Now we need to find it's corresponding host fiber in the other
// tree. To do this we can use getClosestInstanceFromNode, but we
// need to validate that the fiber is a host instance, otherwise
// we need to traverse up through the DOM till we find the correct
// node that is from the other tree.
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container);
if (parentNode === null) {
return;
}
const parentTag = parentNode.tag;
if (parentTag === HostComponent || parentTag === HostText) {
node = ancestorInst = parentNode;
continue mainLoop;
}
container = container.parentNode;
}
const parentTag = parentSubtreeInst.tag;
// getClosestInstanceFromNode can return a HostRoot or SuspenseComponent.
// So we need to ensure we only set the ancestor to a HostComponent or HostText.
if (parentTag === HostComponent || parentTag === HostText) {
ancestorInst = parentSubtreeInst;
}
node = parentSubtreeInst;
continue;
}
node = node.return;
}

View File

@ -224,6 +224,95 @@ describe('DOMModernPluginEventSystem', () => {
expect(log[5]).toEqual(['bubble', buttonElement]);
});
it('handle propagation of click events between disjointed roots #2', () => {
const buttonRef = React.createRef();
const button2Ref = React.createRef();
const divRef = React.createRef();
const spanRef = React.createRef();
const log = [];
const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
const onClickCapture = jest.fn(e =>
log.push(['capture', e.currentTarget]),
);
function Child() {
return (
<div
ref={divRef}
onClick={onClick}
onClickCapture={onClickCapture}>
Click me!
</div>
);
}
function Parent() {
return (
<button
ref={button2Ref}
onClick={onClick}
onClickCapture={onClickCapture}
/>
);
}
function GrandParent() {
return (
<button
ref={buttonRef}
onClick={onClick}
onClickCapture={onClickCapture}>
<span ref={spanRef} />
</button>
);
}
// We make a wrapper with an inner container that we
// render to. So it looks like <div><span></span></div>
// We then render to all three:
// - container
// - parentContainer
// - childContainer
const parentContainer = document.createElement('div');
const childContainer = document.createElement('div');
ReactDOM.render(<GrandParent />, container);
ReactDOM.render(<Parent />, parentContainer);
ReactDOM.render(<Child />, childContainer);
parentContainer.appendChild(childContainer);
spanRef.current.appendChild(parentContainer);
// Inside <GrandParent />
const buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(onClick).toHaveBeenCalledTimes(1);
expect(onClickCapture).toHaveBeenCalledTimes(1);
expect(log[0]).toEqual(['capture', buttonElement]);
expect(log[1]).toEqual(['bubble', buttonElement]);
// Inside <Child />
const divElement = divRef.current;
dispatchClickEvent(divElement);
expect(onClick).toHaveBeenCalledTimes(3);
expect(onClickCapture).toHaveBeenCalledTimes(3);
expect(log[2]).toEqual(['capture', divElement]);
expect(log[3]).toEqual(['bubble', divElement]);
expect(log[4]).toEqual(['capture', buttonElement]);
expect(log[5]).toEqual(['bubble', buttonElement]);
// Inside <Parent />
const buttonElement2 = button2Ref.current;
dispatchClickEvent(buttonElement2);
expect(onClick).toHaveBeenCalledTimes(5);
expect(onClickCapture).toHaveBeenCalledTimes(5);
expect(log[6]).toEqual(['capture', buttonElement2]);
expect(log[7]).toEqual(['bubble', buttonElement2]);
expect(log[8]).toEqual(['capture', buttonElement]);
expect(log[9]).toEqual(['bubble', buttonElement]);
});
it('handle propagation of click events between disjointed comment roots', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();