diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js index 7fef11051116..8a07dfefb0fe 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js @@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM'; import {enableScopeAPI} from 'shared/ReactFeatureFlags'; +import {enableInternalInstanceMap} from 'shared/ReactFeatureFlags'; + const randomKey = Math.random().toString(36).slice(2); const internalInstanceKey = '__reactFiber$' + randomKey; const internalPropsKey = '__reactProps$' + randomKey; @@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey; const internalHoistableMarker = '__reactMarker$' + randomKey; const internalScrollTimer = '__reactScroll$' + randomKey; +type InstanceUnion = + | Instance + | TextInstance + | SuspenseInstance + | ActivityInstance + | ReactScopeInstance + | Container; + +const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; +const internalInstanceMap: + | WeakMap + | Map = new PossiblyWeakMap(); +const internalPropsMap: + | WeakMap + | Map = new PossiblyWeakMap(); + export function detachDeletedInstance(node: Instance): void { + if (enableInternalInstanceMap) { + internalInstanceMap.delete(node); + internalPropsMap.delete(node); + delete (node: any)[internalEventHandlersKey]; + delete (node: any)[internalEventHandlerListenersKey]; + delete (node: any)[internalEventHandlesSetKey]; + delete (node: any)[internalRootNodeResourcesKey]; + return; + } // TODO: This function is only called on host components. I don't think all of // these fields are relevant. delete (node: any)[internalInstanceKey]; @@ -68,6 +95,10 @@ export function precacheFiberNode( | ActivityInstance | ReactScopeInstance, ): void { + if (enableInternalInstanceMap) { + internalInstanceMap.set(node, hostInst); + return; + } (node: any)[internalInstanceKey] = hostInst; } @@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean { // HostRoot back. To get to the HostRoot, you need to pass a child of it. // The same thing applies to Suspense and Activity boundaries. export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { - let targetInst = (targetNode: any)[internalInstanceKey]; + let targetInst: void | Fiber; + if (enableInternalInstanceMap) { + targetInst = internalInstanceMap.get(((targetNode: any): InstanceUnion)); + } else { + targetInst = (targetNode: any)[internalInstanceKey]; + } if (targetInst) { // Don't return HostRoot, SuspenseComponent or ActivityComponent here. return targetInst; @@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { // itself because the fibers are conceptually between the container // node and the first child. It isn't surrounding the container node. // If it's not a container, we check if it's an instance. - targetInst = - (parentNode: any)[internalContainerInstanceKey] || - (parentNode: any)[internalInstanceKey]; + if (enableInternalInstanceMap) { + targetInst = + (parentNode: any)[internalContainerInstanceKey] || + internalInstanceMap.get(((parentNode: any): InstanceUnion)); + } else { + targetInst = + (parentNode: any)[internalContainerInstanceKey] || + (parentNode: any)[internalInstanceKey]; + } if (targetInst) { // Since this wasn't the direct target of the event, we might have // stepped past dehydrated DOM nodes to get here. However they could @@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { // have had an internalInstanceKey on it. // Let's get the fiber associated with the SuspenseComponent // as the deepest instance. - // $FlowFixMe[prop-missing] - const targetFiber = hydrationInstance[internalInstanceKey]; + const targetFiber = enableInternalInstanceMap + ? internalInstanceMap.get(hydrationInstance) + : // $FlowFixMe[prop-missing] + hydrationInstance[internalInstanceKey]; if (targetFiber) { return targetFiber; } @@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber { * instance, or null if the node was not rendered by this React. */ export function getInstanceFromNode(node: Node): Fiber | null { - const inst = - (node: any)[internalInstanceKey] || - (node: any)[internalContainerInstanceKey]; + let inst: void | null | Fiber; + if (enableInternalInstanceMap) { + inst = + internalInstanceMap.get(((node: any): InstanceUnion)) || + (node: any)[internalContainerInstanceKey]; + } else { + inst = + (node: any)[internalInstanceKey] || + (node: any)[internalContainerInstanceKey]; + } if (inst) { const tag = inst.tag; if ( @@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode( | TextInstance | SuspenseInstance | ActivityInstance, -): Props { +): Props | null { + if (enableInternalInstanceMap) { + return internalPropsMap.get(node) || null; + } return (node: any)[internalPropsKey] || null; } export function updateFiberProps(node: Instance, props: Props): void { + if (enableInternalInstanceMap) { + internalPropsMap.set(node, props); + return; + } (node: any)[internalPropsKey] = props; } export function getEventListenerSet(node: EventTarget): Set { - let elementListenerSet = (node: any)[internalEventHandlersKey]; + let elementListenerSet: Set | void; + elementListenerSet = (node: any)[internalEventHandlersKey]; if (elementListenerSet === undefined) { elementListenerSet = (node: any)[internalEventHandlersKey] = new Set(); } @@ -246,6 +305,9 @@ export function getFiberFromScopeInstance( scope: ReactScopeInstance, ): null | Fiber { if (enableScopeAPI) { + if (enableInternalInstanceMap) { + return internalInstanceMap.get(((scope: any): InstanceUnion)) || null; + } return (scope: any)[internalInstanceKey] || null; } return null; @@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void { } export function isOwnedInstance(node: Node): boolean { + if (enableInternalInstanceMap) { + return !!( + (node: any)[internalHoistableMarker] || + internalInstanceMap.has((node: any)) + ); + } return !!( (node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey] ); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index c31809276bdb..a5befa11a0d5 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = true; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableInternalInstanceMap: boolean = false; + // ----------------------------------------------------------------------------- // Ready for next major. // diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index e1b26cdb83b3..71c64b9db03d 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean = __PROFILE__ && dynamicFlags.enableComponentPerformanceTrack; export const enablePerformanceIssueReporting: boolean = enableComponentPerformanceTrack; +export const enableInternalInstanceMap: boolean = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 3880556d247f..cf765d7d6899 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = false; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableInternalInstanceMap: boolean = false; + // Profiling Only export const enableProfilerTimer: boolean = __PROFILE__; export const enableProfilerCommitHooks: boolean = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 88c46d26e2eb..0747f0e8be43 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true; export const enableFragmentRefsScrollIntoView: boolean = true; export const enableFragmentRefsInstanceHandles: boolean = false; +export const enableInternalInstanceMap: boolean = false; + // TODO: This must be in sync with the main ReactFeatureFlags file because // the Test Renderer's value must be the same as the one used by the // react package. diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index d815fd7ddc8e..2b40c1b01c6c 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false; export const enableFragmentRefsInstanceHandles: boolean = false; export const ownerStackLimit = 1e4; +export const enableInternalInstanceMap: boolean = false; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 674aa5d32018..1d174609cb9c 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__; export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__; export const enableAsyncDebugInfo: boolean = __VARIANT__; +export const enableInternalInstanceMap: boolean = __VARIANT__; + // TODO: These flags are hard-coded to the default values used in open source. // Update the tests so that they pass in either mode, then set these // to __VARIANT__. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 4ace06ed4c49..029cd0d196e1 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -35,6 +35,7 @@ export const { enableFragmentRefs, enableFragmentRefsScrollIntoView, enableAsyncDebugInfo, + enableInternalInstanceMap, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build.