diff --git a/packages/react-dom/src/events/DOMModernPluginEventSystem.js b/packages/react-dom/src/events/DOMModernPluginEventSystem.js index b8c07cc4e91..09fc54e7f5d 100644 --- a/packages/react-dom/src/events/DOMModernPluginEventSystem.js +++ b/packages/react-dom/src/events/DOMModernPluginEventSystem.js @@ -56,6 +56,11 @@ import { TOP_PROGRESS, TOP_PLAYING, } from './DOMTopLevelEventTypes'; +import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; +import {DOCUMENT_NODE, COMMENT_NODE} from '../shared/HTMLNodeType'; + +import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags'; +import {HostRoot, HostPortal} from 'shared/ReactWorkTags'; const capturePhaseEvents = new Set([ TOP_FOCUS, @@ -165,6 +170,25 @@ export function listenToEvent( } } +function willDelegateLaterForFBLegacyPrimer(nativeEvent: any): boolean { + let node = nativeEvent.target; + + while (node !== null) { + if (node.tagName === 'A' && node.rel) { + const legacyFBSupport = true; + trapEventForPluginEventSystem( + document, + nativeEvent.type, + false, + legacyFBSupport, + ); + return true; + } + node = node.parentNode; + } + return false; +} + export function dispatchEventForPluginEventSystem( topLevelType: DOMTopLevelEventType, eventSystemFlags: EventSystemFlags, @@ -173,6 +197,38 @@ export function dispatchEventForPluginEventSystem( rootContainer: Document | Element, ): void { let ancestorInst = targetInst; + if (rootContainer.nodeType !== DOCUMENT_NODE) { + // FB only + if ( + enableLegacyFBPrimerSupport && + willDelegateLaterForFBLegacyPrimer(nativeEvent) + ) { + return; + } + let node = targetInst; + + while (true) { + if (node === null) { + return; + } else if (node.tag === HostRoot || node.tag === HostPortal) { + const container = node.stateNode.containerInfo; + if ( + container === rootContainer || + (container.nodeType === COMMENT_NODE && + container.parentNode === rootContainer) + ) { + break; + } + const parentSubtreeInst = getClosestInstanceFromNode(container); + if (parentSubtreeInst === null) { + return; + } + node = ancestorInst = parentSubtreeInst; + continue; + } + node = node.return; + } + } batchedEventUpdates(() => dispatchEventsForPlugins( diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index e6913dbeed5..e4feb40b806 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -65,6 +65,7 @@ import { import {getEventPriorityForPluginSystem} from './DOMEventProperties'; import {dispatchEventForLegacyPluginEventSystem} from './DOMLegacyEventPluginSystem'; import {dispatchEventForPluginEventSystem} from './DOMModernPluginEventSystem'; +import {enableLegacyFBPrimerSupport} from 'shared/ReactFeatureFlags'; const { unstable_UserBlockingPriority: UserBlockingPriority, @@ -143,6 +144,7 @@ export function trapEventForPluginEventSystem( container: Document | Element, topLevelType: DOMTopLevelEventType, capture: boolean, + legacyFBSupport?: boolean, ): void { let listener; let listenerWrapper; @@ -164,12 +166,29 @@ export function trapEventForPluginEventSystem( PLUGIN_EVENT_SYSTEM, container, ); - const rawEventName = getRawEventName(topLevelType); + let fbListener; + if (enableLegacyFBPrimerSupport && legacyFBSupport) { + const originalListener = listener; + listener = function(...p) { + try { + return originalListener.apply(this, p); + } finally { + if (fbListener) { + fbListener.remove(); + } else { + container.removeEventListener( + ((rawEventName: any): string), + (listener: any), + ); + } + } + }; + } if (capture) { - addEventCaptureListener(container, rawEventName, listener); + fbListener = addEventCaptureListener(container, rawEventName, listener); } else { - addEventBubbleListener(container, rawEventName, listener); + fbListener = addEventBubbleListener(container, rawEventName, listener); } } diff --git a/packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js index 66c670b7ca3..10711989303 100644 --- a/packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js @@ -12,6 +12,8 @@ let React; let ReactFeatureFlags; let ReactDOM; +let ReactDOMServer; +let Scheduler; function dispatchClickEvent(element) { const event = document.createEvent('Event'); @@ -29,6 +31,8 @@ describe('DOMModernPluginEventSystem', () => { React = require('react'); ReactDOM = require('react-dom'); + Scheduler = require('scheduler'); + ReactDOMServer = require('react-dom/server'); container = document.createElement('div'); document.body.appendChild(container); }); @@ -77,6 +81,500 @@ describe('DOMModernPluginEventSystem', () => { expect(log[5]).toEqual(['bubble', buttonElement]); }); + it('handle propagation of click events between roots', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const childRef = 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 ( +
+ Click me! +
+ ); + } + + function Parent() { + return ( + + ); + } + + ReactDOM.render(, container); + ReactDOM.render(, childRef.current); + + let 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]); + + let 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]); + }); + + it('handle propagation of click events between disjointed roots', () => { + const buttonRef = React.createRef(); + const divRef = 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 ( +
+ Click me! +
+ ); + } + + function Parent() { + return ( + + ); + } + + // We use a comment node here, then mount to it + const disjointedNode = document.createComment( + ' react-mount-point-unstable ', + ); + ReactDOM.render(, container); + spanRef.current.appendChild(disjointedNode); + ReactDOM.render(, disjointedNode); + + let 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]); + + let 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]); + }); + + it('handle propagation of click events between portals', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const log = []; + const onClick = jest.fn(e => log.push(['bubble', e.currentTarget])); + const onClickCapture = jest.fn(e => log.push(['capture', e.currentTarget])); + + const portalElement = document.createElement('div'); + document.body.appendChild(portalElement); + + function Child() { + return ( +
+ Click me! +
+ ); + } + + function Parent() { + return ( + + ); + } + + ReactDOM.render(, container); + + let 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]); + + let divElement = divRef.current; + dispatchClickEvent(divElement); + expect(onClick).toHaveBeenCalledTimes(3); + expect(onClickCapture).toHaveBeenCalledTimes(3); + expect(log[2]).toEqual(['capture', buttonElement]); + expect(log[3]).toEqual(['capture', divElement]); + expect(log[4]).toEqual(['bubble', divElement]); + expect(log[5]).toEqual(['bubble', buttonElement]); + + document.body.removeChild(portalElement); + }); + + it('handle click events on document.body portals', () => { + const log = []; + + function Child({label}) { + return
log.push(label)}>{label}
; + } + + function Parent() { + return ( + <> + {ReactDOM.createPortal(, document.body)} + {ReactDOM.createPortal(, document.body)} + + ); + } + + ReactDOM.render(, container); + + const second = document.body.lastChild; + expect(second.textContent).toEqual('second'); + dispatchClickEvent(second); + + expect(log).toEqual(['second']); + + const first = second.previousSibling; + expect(first.textContent).toEqual('first'); + dispatchClickEvent(first); + + expect(log).toEqual(['second', 'first']); + }); + + it.experimental( + 'does not invoke an event on a parent tree when a subtree is dehydrated', + async () => { + let suspend = false; + let resolve; + let promise = new Promise(resolvePromise => (resolve = resolvePromise)); + + let clicks = 0; + let childSlotRef = React.createRef(); + + function Parent() { + return
clicks++} ref={childSlotRef} />; + } + + function Child({text}) { + if (suspend) { + throw promise; + } else { + return Click me; + } + } + + function App() { + // The root is a Suspense boundary. + return ( + + + + ); + } + + suspend = false; + let finalHTML = ReactDOMServer.renderToString(); + + let parentContainer = document.createElement('div'); + let childContainer = document.createElement('div'); + + // We need this to be in the document since we'll dispatch events on it. + document.body.appendChild(parentContainer); + + // We're going to use a different root as a parent. + // This lets us detect whether an event goes through React's event system. + let parentRoot = ReactDOM.createRoot(parentContainer); + parentRoot.render(); + Scheduler.unstable_flushAll(); + + childSlotRef.current.appendChild(childContainer); + + childContainer.innerHTML = finalHTML; + + let a = childContainer.getElementsByTagName('a')[0]; + + suspend = true; + + // Hydrate asynchronously. + let root = ReactDOM.createRoot(childContainer, {hydrate: true}); + root.render(); + jest.runAllTimers(); + Scheduler.unstable_flushAll(); + + // The Suspense boundary is not yet hydrated. + a.click(); + expect(clicks).toBe(0); + + // Resolving the promise so that rendering can complete. + suspend = false; + resolve(); + await promise; + + Scheduler.unstable_flushAll(); + jest.runAllTimers(); + + // We're now full hydrated. + + expect(clicks).toBe(1); + + document.body.removeChild(parentContainer); + }, + ); + + it('handle click events on dynamic portals', () => { + const log = []; + + function Parent() { + const ref = React.useRef(null); + const [portal, setPortal] = React.useState(null); + + React.useEffect(() => { + setPortal( + ReactDOM.createPortal( + log.push('child')} id="child" />, + ref.current, + ), + ); + }); + + return ( +
log.push('parent')} id="parent"> + {portal} +
+ ); + } + + ReactDOM.render(, container); + + const parent = container.lastChild; + expect(parent.id).toEqual('parent'); + dispatchClickEvent(parent); + + expect(log).toEqual(['parent']); + + const child = parent.lastChild; + expect(child.id).toEqual('child'); + dispatchClickEvent(child); + + // we add both 'child' and 'parent' due to bubbling + expect(log).toEqual(['parent', 'child', 'parent']); + }); + + // Slight alteration to the last test, to catch + // a subtle difference in traversal. + it('handle click events on dynamic portals #2', () => { + const log = []; + + function Parent() { + const ref = React.useRef(null); + const [portal, setPortal] = React.useState(null); + + React.useEffect(() => { + setPortal( + ReactDOM.createPortal( + log.push('child')} id="child" />, + ref.current, + ), + ); + }); + + return ( +
log.push('parent')} id="parent"> +
{portal}
+
+ ); + } + + ReactDOM.render(, container); + + const parent = container.lastChild; + expect(parent.id).toEqual('parent'); + dispatchClickEvent(parent); + + expect(log).toEqual(['parent']); + + const child = parent.lastChild; + expect(child.id).toEqual('child'); + dispatchClickEvent(child); + + // we add both 'child' and 'parent' due to bubbling + expect(log).toEqual(['parent', 'child', 'parent']); + }); + + it('native stopPropagation on click events between portals', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const middelDivRef = React.createRef(); + const log = []; + const onClick = jest.fn(e => log.push(['bubble', e.currentTarget])); + const onClickCapture = jest.fn(e => log.push(['capture', e.currentTarget])); + + const portalElement = document.createElement('div'); + document.body.appendChild(portalElement); + + function Child() { + return ( +
+
+ Click me! +
+
+ ); + } + + function Parent() { + React.useLayoutEffect(() => { + // This should prevent the portalElement listeners from + // capturing the events in the bubble phase. + middelDivRef.current.addEventListener('click', e => { + e.stopPropagation(); + }); + }); + + return ( + + ); + } + + ReactDOM.render(, container); + + let 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]); + + let divElement = divRef.current; + dispatchClickEvent(divElement); + expect(onClick).toHaveBeenCalledTimes(1); + expect(onClickCapture).toHaveBeenCalledTimes(1); + + document.body.removeChild(portalElement); + }); + it('handle propagation of focus events', () => { const buttonRef = React.createRef(); const divRef = React.createRef(); @@ -119,4 +617,306 @@ describe('DOMModernPluginEventSystem', () => { expect(log[4]).toEqual(['bubble', divElement]); expect(log[5]).toEqual(['bubble', buttonElement]); }); + + it('handle propagation of focus events between roots', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const childRef = React.createRef(); + const log = []; + const onFocus = jest.fn(e => log.push(['bubble', e.currentTarget])); + const onFocusCapture = jest.fn(e => log.push(['capture', e.currentTarget])); + + function Child() { + return ( +
+ Click me! +
+ ); + } + + function Parent() { + return ( + + ); + } + + ReactDOM.render(, container); + ReactDOM.render(, childRef.current); + + let buttonElement = buttonRef.current; + buttonElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(1); + expect(onFocusCapture).toHaveBeenCalledTimes(1); + expect(log[0]).toEqual(['capture', buttonElement]); + expect(log[1]).toEqual(['bubble', buttonElement]); + + let divElement = divRef.current; + divElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(3); + expect(onFocusCapture).toHaveBeenCalledTimes(3); + expect(log[2]).toEqual(['capture', buttonElement]); + expect(log[3]).toEqual(['bubble', buttonElement]); + expect(log[4]).toEqual(['capture', divElement]); + expect(log[5]).toEqual(['bubble', divElement]); + }); + + it('handle propagation of focus events between portals', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const log = []; + const onFocus = jest.fn(e => log.push(['bubble', e.currentTarget])); + const onFocusCapture = jest.fn(e => log.push(['capture', e.currentTarget])); + + const portalElement = document.createElement('div'); + document.body.appendChild(portalElement); + + function Child() { + return ( +
+ Click me! +
+ ); + } + + function Parent() { + return ( + + ); + } + + ReactDOM.render(, container); + + let buttonElement = buttonRef.current; + buttonElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(1); + expect(onFocusCapture).toHaveBeenCalledTimes(1); + expect(log[0]).toEqual(['capture', buttonElement]); + expect(log[1]).toEqual(['bubble', buttonElement]); + + let divElement = divRef.current; + divElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(3); + expect(onFocusCapture).toHaveBeenCalledTimes(3); + expect(log[2]).toEqual(['capture', buttonElement]); + expect(log[3]).toEqual(['capture', divElement]); + expect(log[4]).toEqual(['bubble', divElement]); + expect(log[5]).toEqual(['bubble', buttonElement]); + + document.body.removeChild(portalElement); + }); + + it('native stopPropagation on focus events between portals', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const middelDivRef = React.createRef(); + const log = []; + const onFocus = jest.fn(e => log.push(['bubble', e.currentTarget])); + const onFocusCapture = jest.fn(e => log.push(['capture', e.currentTarget])); + + const portalElement = document.createElement('div'); + document.body.appendChild(portalElement); + + function Child() { + return ( +
+
+ Click me! +
+
+ ); + } + + function Parent() { + React.useLayoutEffect(() => { + // This should prevent the portalElement listeners from + // capturing the events in the bubble phase. + middelDivRef.current.addEventListener('click', e => { + e.stopPropagation(); + }); + }); + + return ( + + ); + } + + ReactDOM.render(, container); + + let buttonElement = buttonRef.current; + buttonElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(1); + expect(onFocusCapture).toHaveBeenCalledTimes(1); + expect(log[0]).toEqual(['capture', buttonElement]); + expect(log[1]).toEqual(['bubble', buttonElement]); + + let divElement = divRef.current; + divElement.focus(); + expect(onFocus).toHaveBeenCalledTimes(1); + expect(onFocusCapture).toHaveBeenCalledTimes(1); + + document.body.removeChild(portalElement); + }); + + it('handle propagation of enter and leave events between portals', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const log = []; + const onMouseEnter = jest.fn(e => log.push(e.currentTarget)); + const onMouseLeave = jest.fn(e => log.push(e.currentTarget)); + + const portalElement = document.createElement('div'); + document.body.appendChild(portalElement); + + function Child() { + return ( +
+ ); + } + + function Parent() { + return ( + + ); + } + + ReactDOM.render(, container); + + let buttonElement = buttonRef.current; + buttonElement.dispatchEvent( + new MouseEvent('mouseover', { + bubbles: true, + cancelable: true, + relatedTarget: null, + }), + ); + expect(onMouseEnter).toHaveBeenCalledTimes(1); + expect(onMouseLeave).toHaveBeenCalledTimes(0); + expect(log[0]).toEqual(buttonElement); + + let divElement = divRef.current; + buttonElement.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: divElement, + }), + ); + divElement.dispatchEvent( + new MouseEvent('mouseover', { + bubbles: true, + cancelable: true, + relatedTarget: buttonElement, + }), + ); + expect(onMouseEnter).toHaveBeenCalledTimes(2); + expect(onMouseLeave).toHaveBeenCalledTimes(0); + expect(log[1]).toEqual(divElement); + + document.body.removeChild(portalElement); + }); + + it('handle propagation of enter and leave events between portals #2', () => { + const buttonRef = React.createRef(); + const divRef = React.createRef(); + const portalRef = React.createRef(); + const log = []; + const onMouseEnter = jest.fn(e => log.push(e.currentTarget)); + const onMouseLeave = jest.fn(e => log.push(e.currentTarget)); + + function Child() { + return ( +
+ ); + } + + function Parent() { + const [portal, setPortal] = React.useState(null); + + React.useLayoutEffect(() => { + setPortal(ReactDOM.createPortal(, portalRef.current)); + }, []); + + return ( + + ); + } + + ReactDOM.render(, container); + + let buttonElement = buttonRef.current; + buttonElement.dispatchEvent( + new MouseEvent('mouseover', { + bubbles: true, + cancelable: true, + relatedTarget: null, + }), + ); + expect(onMouseEnter).toHaveBeenCalledTimes(1); + expect(onMouseLeave).toHaveBeenCalledTimes(0); + expect(log[0]).toEqual(buttonElement); + + let divElement = divRef.current; + buttonElement.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: divElement, + }), + ); + divElement.dispatchEvent( + new MouseEvent('mouseover', { + bubbles: true, + cancelable: true, + relatedTarget: buttonElement, + }), + ); + expect(onMouseEnter).toHaveBeenCalledTimes(2); + expect(onMouseLeave).toHaveBeenCalledTimes(0); + expect(log[1]).toEqual(divElement); + }); }); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 412ec5f9eef..f4e95b44d27 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -128,3 +128,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false; // Modern event system where events get registered at roots export const enableModernEventSystem = false; + +// Support legacy Primer support on internal FB www +export const enableLegacyFBPrimerSupport = false; diff --git a/packages/shared/ReactTreeTraversal.js b/packages/shared/ReactTreeTraversal.js index 08a1a3d87c2..5c26ac213bf 100644 --- a/packages/shared/ReactTreeTraversal.js +++ b/packages/shared/ReactTreeTraversal.js @@ -5,21 +5,54 @@ * LICENSE file in the root directory of this source tree. */ -import {HostComponent} from './ReactWorkTags'; +import type {Fiber} from 'react-reconciler/src/ReactFiber'; -function getParent(inst) { - do { - inst = inst.return; - // TODO: If this is a HostRoot we might want to bail out. - // That is depending on if we want nested subtrees (layers) to bubble - // events to their parent. We could also go through parentNode on the - // host node but that wouldn't work for React Native and doesn't let us - // do the portal feature. - } while (inst && inst.tag !== HostComponent); - if (inst) { - return inst; +import {HostComponent, HostPortal, HostRoot} from './ReactWorkTags'; +import {enableModernEventSystem} from './ReactFeatureFlags'; + +export function getParent( + inst: Fiber, + alwaysTraversePortals?: boolean, +): null | Fiber { + if (enableModernEventSystem) { + let node = inst.return; + + while (node !== null) { + if (node.tag === HostPortal && !alwaysTraversePortals) { + let grandNode = node; + const portalNode = node.stateNode.containerInfo; + while (grandNode !== null) { + // If we find a root that is actually a parent in the DOM tree + // then we don't continue with getting the parent, as that root + // will have its own event listener. + if ( + grandNode.tag === HostRoot && + grandNode.stateNode.containerInfo.contains(portalNode) + ) { + return null; + } + grandNode = grandNode.return; + } + } else if (node.tag === HostComponent) { + return node; + } + node = node.return; + } + return null; + } else { + do { + inst = inst.return; + // TODO: If this is a HostRoot we might want to bail out. + // That is depending on if we want nested subtrees (layers) to bubble + // events to their parent. We could also go through parentNode on the + // host node but that wouldn't work for React Native and doesn't let us + // do the portal feature. + } while (inst && inst.tag !== HostComponent); + if (inst) { + return inst; + } + return null; } - return null; } /** @@ -28,23 +61,23 @@ function getParent(inst) { */ export function getLowestCommonAncestor(instA, instB) { let depthA = 0; - for (let tempA = instA; tempA; tempA = getParent(tempA)) { + for (let tempA = instA; tempA; tempA = getParent(tempA, true)) { depthA++; } let depthB = 0; - for (let tempB = instB; tempB; tempB = getParent(tempB)) { + for (let tempB = instB; tempB; tempB = getParent(tempB, true)) { depthB++; } // If A is deeper, crawl up. while (depthA - depthB > 0) { - instA = getParent(instA); + instA = getParent(instA, true); depthA--; } // If B is deeper, crawl up. while (depthB - depthA > 0) { - instB = getParent(instB); + instB = getParent(instB, true); depthB--; } @@ -54,8 +87,8 @@ export function getLowestCommonAncestor(instA, instB) { if (instA === instB || instA === instB.alternate) { return instA; } - instA = getParent(instA); - instB = getParent(instB); + instA = getParent(instA, true); + instB = getParent(instB, true); } return null; } @@ -68,7 +101,7 @@ export function isAncestor(instA, instB) { if (instA === instB || instA === instB.alternate) { return true; } - instB = getParent(instB); + instB = getParent(instB, true); } return false; } @@ -120,7 +153,7 @@ export function traverseEnterLeave(from, to, fn, argFrom, argTo) { break; } pathFrom.push(from); - from = getParent(from); + from = getParent(from, true); } const pathTo = []; while (true) { @@ -135,7 +168,7 @@ export function traverseEnterLeave(from, to, fn, argFrom, argTo) { break; } pathTo.push(to); - to = getParent(to); + to = getParent(to, true); } for (let i = 0; i < pathFrom.length; i++) { fn(pathFrom[i], 'bubbled', argFrom); diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index bce5af20b23..922074b1454 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -44,6 +44,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = false; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index c30e273b75e..e37ca75167d 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = false; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 5bd1551a9ce..e0a9d877597 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = false; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index a785da9ec12..e303ea72d6d 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = false; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index c00f54a5e89..e72b97e23d8 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = true; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index c8e7e724841..c4b82f4d2b4 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = false; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 4b4db206546..2fcb0aaf01c 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -43,6 +43,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false; export const runAllPassiveEffectDestroysBeforeCreates = false; export const enableModernEventSystem = false; export const warnAboutSpreadingKeyToJSX = false; +export const enableLegacyFBPrimerSupport = true; // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index aea6f295961..33f62e8b4f1 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -97,6 +97,8 @@ export const warnUnstableRenderSubtreeIntoContainer = false; export const enableModernEventSystem = false; +export const enableLegacyFBPrimerSupport = false; + // Internal-only attempt to debug a React Native issue. See D20130868. export const throwEarlyForMysteriousError = false;