@samitouri / QOS-React-2 / commits / e16703e6c7

Modern Event System: revise ancestor logic (#18886)

Dominic Gannaway committed May 11, 2020 at 22:09 UTC e16703e6c7776ff39e30c448df075f48bdbe08ea
2 files changed +107 -13
packages/react-dom/src/events/DOMModernPluginEventSystem.js
+18 -13
@@ -417,13 +417,13 @@ export function dispatchEventForPluginEventSystem(
417 // sub-tree for that root and make that our ancestor instance.
418 let node = targetInst;
419
420 - while (true) {
420 + mainLoop: while (true) {
421 if (node === null) {
422 return;
423 }
424 const nodeTag = node.tag;
425 if (nodeTag === HostRoot || nodeTag === HostPortal) {
426 - const container = node.stateNode.containerInfo;
426 + let container = node.stateNode.containerInfo;
427 if (isMatchingRootContainer(container, targetContainerNode)) {
428 break;
429 }
@@ -449,18 +449,23 @@ export function dispatchEventForPluginEventSystem(
449 grandNode = grandNode.return;
450 }
451 }
452 - const parentSubtreeInst = getClosestInstanceFromNode(container);
453 - if (parentSubtreeInst === null) {
454 - return;
455 - }
456 - const parentTag = parentSubtreeInst.tag;
457 - // getClosestInstanceFromNode can return a HostRoot or SuspenseComponent.
458 - // So we need to ensure we only set the ancestor to a HostComponent or HostText.
459 - if (parentTag === HostComponent || parentTag === HostText) {
460 - ancestorInst = parentSubtreeInst;
452 + // Now we need to find it's corresponding host fiber in the other
453 + // tree. To do this we can use getClosestInstanceFromNode, but we
454 + // need to validate that the fiber is a host instance, otherwise
455 + // we need to traverse up through the DOM till we find the correct
456 + // node that is from the other tree.
457 + while (container !== null) {
458 + const parentNode = getClosestInstanceFromNode(container);
459 + if (parentNode === null) {
460 + return;
461 + }
462 + const parentTag = parentNode.tag;
463 + if (parentTag === HostComponent || parentTag === HostText) {
464 + node = ancestorInst = parentNode;
465 + continue mainLoop;
466 + }
467 + container = container.parentNode;
468 }
462 - node = parentSubtreeInst;
463 - continue;
469 }
470 node = node.return;
471 }
packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js
+89
@@ -224,6 +224,95 @@ describe('DOMModernPluginEventSystem', () => {
224 expect(log[5]).toEqual(['bubble', buttonElement]);
225 });
226
227 + it('handle propagation of click events between disjointed roots #2', () => {
228 + const buttonRef = React.createRef();
229 + const button2Ref = React.createRef();
230 + const divRef = React.createRef();
231 + const spanRef = React.createRef();
232 + const log = [];
233 + const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
234 + const onClickCapture = jest.fn(e =>
235 + log.push(['capture', e.currentTarget]),
236 + );
237 +
238 + function Child() {
239 + return (
240 + <div
241 + ref={divRef}
242 + onClick={onClick}
243 + onClickCapture={onClickCapture}>
244 + Click me!
245 + </div>
246 + );
247 + }
248 +
249 + function Parent() {
250 + return (
251 + <button
252 + ref={button2Ref}
253 + onClick={onClick}
254 + onClickCapture={onClickCapture}
255 + />
256 + );
257 + }
258 +
259 + function GrandParent() {
260 + return (
261 + <button
262 + ref={buttonRef}
263 + onClick={onClick}
264 + onClickCapture={onClickCapture}>
265 + <span ref={spanRef} />
266 + </button>
267 + );
268 + }
269 +
270 + // We make a wrapper with an inner container that we
271 + // render to. So it looks like <div><span></span></div>
272 + // We then render to all three:
273 + // - container
274 + // - parentContainer
275 + // - childContainer
276 +
277 + const parentContainer = document.createElement('div');
278 + const childContainer = document.createElement('div');
279 +
280 + ReactDOM.render(<GrandParent />, container);
281 + ReactDOM.render(<Parent />, parentContainer);
282 + ReactDOM.render(<Child />, childContainer);
283 +
284 + parentContainer.appendChild(childContainer);
285 + spanRef.current.appendChild(parentContainer);
286 +
287 + // Inside <GrandParent />
288 + const buttonElement = buttonRef.current;
289 + dispatchClickEvent(buttonElement);
290 + expect(onClick).toHaveBeenCalledTimes(1);
291 + expect(onClickCapture).toHaveBeenCalledTimes(1);
292 + expect(log[0]).toEqual(['capture', buttonElement]);
293 + expect(log[1]).toEqual(['bubble', buttonElement]);
294 +
295 + // Inside <Child />
296 + const divElement = divRef.current;
297 + dispatchClickEvent(divElement);
298 + expect(onClick).toHaveBeenCalledTimes(3);
299 + expect(onClickCapture).toHaveBeenCalledTimes(3);
300 + expect(log[2]).toEqual(['capture', divElement]);
301 + expect(log[3]).toEqual(['bubble', divElement]);
302 + expect(log[4]).toEqual(['capture', buttonElement]);
303 + expect(log[5]).toEqual(['bubble', buttonElement]);
304 +
305 + // Inside <Parent />
306 + const buttonElement2 = button2Ref.current;
307 + dispatchClickEvent(buttonElement2);
308 + expect(onClick).toHaveBeenCalledTimes(5);
309 + expect(onClickCapture).toHaveBeenCalledTimes(5);
310 + expect(log[6]).toEqual(['capture', buttonElement2]);
311 + expect(log[7]).toEqual(['bubble', buttonElement2]);
312 + expect(log[8]).toEqual(['capture', buttonElement]);
313 + expect(log[9]).toEqual(['bubble', buttonElement]);
314 + });
315 +
316 it('handle propagation of click events between disjointed comment roots', () => {
317 const buttonRef = React.createRef();
318 const divRef = React.createRef();