@samitouri / QOS-React / commits / b2357ecd82

[RN] Add support for document instance in React Native (#32260)

## Summary We're adding support for `Document` instances in React Native (as `ReactNativeDocument` instances) in https://github.com/facebook/react-native/pull/49012 , which requires the React Fabric renderer to handle its lifecycle. This modifies the renderer to create those document instances and associate them with the React root, and provides a new method for React Native to access them given its containerTag / rootTag. ## How did you test this change? Tested e2e in https://github.com/facebook/react-native/pull/49012 manually syncing these changes.

Rubén Norte committed Jan 29, 2025 at 17:07 UTC b2357ecd8203341a3668a96d32d68dd519e5430d
9 files changed +87 -14
packages/react-native-renderer/src/ReactFabric.js
+28 -2
@@ -41,7 +41,11 @@ import {
41 import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFabric';
42
43 // Module provided by RN:
44 -import {ReactFiberErrorDialog} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
44 +import {
45 + ReactFiberErrorDialog,
46 + createPublicRootInstance,
47 + type PublicRootInstance,
48 +} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
49 import {disableLegacyMode} from 'shared/ReactFeatureFlags';
50
51 if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
@@ -126,10 +130,16 @@ function render(
130 onRecoverableError = options.onRecoverableError;
131 }
132
133 + const publicRootInstance = createPublicRootInstance(containerTag);
134 + const rootInstance = {
135 + publicInstance: publicRootInstance,
136 + containerTag,
137 + };
138 +
139 // TODO (bvaughn): If we decide to keep the wrapper component,
140 // We could create a wrapper for containerTag as well to reduce special casing.
141 root = createContainer(
132 - containerTag,
142 + rootInstance,
143 concurrentRoot ? ConcurrentRoot : LegacyRoot,
144 null,
145 false,
@@ -140,6 +150,7 @@ function render(
150 onRecoverableError,
151 null,
152 );
153 +
154 roots.set(containerTag, root);
155 }
156 updateContainer(element, root, null, callback);
@@ -157,6 +168,9 @@ function stopSurface(containerTag: number) {
168 if (root) {
169 // TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
170 updateContainer(null, root, null, () => {
171 + // Remove the reference to the public instance to prevent memory leaks.
172 + root.containerInfo.publicInstance = null;
173 +
174 roots.delete(containerTag);
175 });
176 }
@@ -170,6 +184,16 @@ function createPortal(
184 return createPortalImpl(children, containerTag, null, key);
185 }
186
187 +function getPublicInstanceFromRootTag(
188 + rootTag: number,
189 +): PublicRootInstance | null {
190 + const root = roots.get(rootTag);
191 + if (root) {
192 + return root.containerInfo.publicInstance;
193 + }
194 + return null;
195 +}
196 +
197 setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
198
199 const roots = new Map<number, FiberRoot>();
@@ -195,6 +219,8 @@ export {
219 // instance handles we use to dispatch events. This provides a way to access
220 // the public instances we created from them (potentially created lazily).
221 getPublicInstanceFromInternalInstanceHandle,
222 + // Returns the document instance for that root tag.
223 + getPublicInstanceFromRootTag,
224 // DEV-only:
225 isChildPublicInstance,
226 };
packages/react-native-renderer/src/ReactFiberConfigFabric.js
+10 -5
@@ -31,6 +31,7 @@ import {
31 createPublicTextInstance,
32 type PublicInstance as ReactNativePublicInstance,
33 type PublicTextInstance,
34 + type PublicRootInstance,
35 } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
36
37 const {
@@ -108,7 +109,10 @@ export type TextInstance = {
109 };
110 export type HydratableInstance = Instance | TextInstance;
111 export type PublicInstance = ReactNativePublicInstance;
111 -export type Container = number;
112 +export type Container = {
113 + containerTag: number,
114 + publicInstance: PublicRootInstance | null,
115 +};
116 export type ChildSet = Object | Array<Node>;
117 export type HostContext = $ReadOnly<{
118 isInAParentText: boolean,
@@ -180,7 +184,7 @@ export function createInstance(
184 const node = createNode(
185 tag, // reactTag
186 viewConfig.uiViewClassName, // viewName
183 - rootContainerInstance, // rootTag
187 + rootContainerInstance.containerTag, // rootTag
188 updatePayload, // props
189 internalInstanceHandle, // internalInstanceHandle
190 );
@@ -189,6 +193,7 @@ export function createInstance(
193 tag,
194 viewConfig,
195 internalInstanceHandle,
196 + rootContainerInstance.publicInstance,
197 );
198
199 return {
@@ -221,7 +226,7 @@ export function createTextInstance(
226 const node = createNode(
227 tag, // reactTag
228 'RCTRawText', // viewName
224 - rootContainerInstance, // rootTag
229 + rootContainerInstance.containerTag, // rootTag
230 {text: text}, // props
231 internalInstanceHandle, // instance handle
232 );
@@ -501,7 +506,7 @@ export function finalizeContainerChildren(
506 newChildren: ChildSet,
507 ): void {
508 if (!enableFabricCompleteRootInCommitPhase) {
504 - completeRoot(container, newChildren);
509 + completeRoot(container.containerTag, newChildren);
510 }
511 }
512
@@ -511,7 +516,7 @@ export function replaceContainerChildren(
516 ): void {
517 // Noop - children will be replaced in finalizeContainerChildren
518 if (enableFabricCompleteRootInCommitPhase) {
514 - completeRoot(container, newChildren);
519 + completeRoot(container.containerTag, newChildren);
520 }
521 }
522
packages/react-native-renderer/src/ReactFiberConfigNative.js
+9 -5
@@ -15,6 +15,7 @@ import {
15 ReactNativeViewConfigRegistry,
16 UIManager,
17 deepFreezeAndThrowOnMutationInDev,
18 + type PublicRootInstance,
19 } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
20
21 import {create, diff} from './ReactNativeAttributePayload';
@@ -54,7 +55,10 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
55
56 export type Type = string;
57 export type Props = Object;
57 -export type Container = number;
58 +export type Container = {
59 + containerTag: number,
60 + publicInstance: PublicRootInstance | null,
61 +};
62 export type Instance = ReactNativeFiberHostComponent;
63 export type TextInstance = number;
64 export type HydratableInstance = Instance | TextInstance;
@@ -143,7 +147,7 @@ export function createInstance(
147 UIManager.createView(
148 tag, // reactTag
149 viewConfig.uiViewClassName, // viewName
146 - rootContainerInstance, // rootTag
150 + rootContainerInstance.containerTag, // rootTag
151 updatePayload, // props
152 );
153
@@ -176,7 +180,7 @@ export function createTextInstance(
180 UIManager.createView(
181 tag, // reactTag
182 'RCTRawText', // viewName
179 - rootContainerInstance, // rootTag
183 + rootContainerInstance.containerTag, // rootTag
184 {text: text}, // props
185 );
186
@@ -349,7 +353,7 @@ export function appendChildToContainer(
353 ): void {
354 const childTag = typeof child === 'number' ? child : child._nativeTag;
355 UIManager.setChildren(
352 - parentInstance, // containerTag
356 + parentInstance.containerTag, // containerTag
357 [childTag], // reactTags
358 );
359 }
@@ -479,7 +483,7 @@ export function removeChildFromContainer(
483 ): void {
484 recursivelyUncacheFiberNode(child);
485 UIManager.manageChildren(
482 - parentInstance, // containerID
486 + parentInstance.containerTag, // containerID
487 [], // moveFromIndices
488 [], // moveToIndices
489 [], // addChildReactTags
packages/react-native-renderer/src/ReactNativeRenderer.js
+8 -1
@@ -11,6 +11,7 @@ import type {ReactPortal, ReactNodeList} from 'shared/ReactTypes';
11 import type {ElementRef, ElementType, MixedElement} from 'react';
12 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
13 import type {RenderRootOptions} from './ReactNativeTypes';
14 +import type {Container} from 'react-reconciler/src/ReactFiberConfig';
15
16 import './ReactNativeInjection';
17
@@ -143,10 +144,16 @@ function render(
144 onRecoverableError = options.onRecoverableError;
145 }
146
147 + const rootInstance: Container = {
148 + containerTag,
149 + // $FlowExpectedError[incompatible-type] the legacy renderer does not use public root instances
150 + publicInstance: null,
151 + };
152 +
153 // TODO (bvaughn): If we decide to keep the wrapper component,
154 // We could create a wrapper for containerTag as well to reduce special casing.
155 root = createContainer(
149 - containerTag,
156 + rootInstance,
157 LegacyRoot,
158 null,
159 false,
packages/react-native-renderer/src/ReactNativeTypes.js
+1
@@ -231,6 +231,7 @@ export opaque type Node = mixed;
231 export opaque type InternalInstanceHandle = mixed;
232 type PublicInstance = mixed;
233 type PublicTextInstance = mixed;
234 +export opaque type PublicRootInstance = mixed;
235
236 export type ReactFabricType = {
237 findHostInstance_DEPRECATED<TElementType: ElementType>(
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js
+4
@@ -9,6 +9,7 @@
9
10 export opaque type PublicInstance = mixed;
11 export opaque type PublicTextInstance = mixed;
12 +export opaque type PublicRootInstance = mixed;
13
14 module.exports = {
15 get BatchedBridge() {
@@ -59,4 +60,7 @@ module.exports = {
60 get createPublicTextInstance() {
61 return require('./createPublicTextInstance').default;
62 },
63 + get createPublicRootInstance() {
64 + return require('./createPublicRootInstance').default;
65 + },
66 };
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/createPublicInstance.js
+6 -1
@@ -7,15 +7,20 @@
7 * @flow strict
8 */
9
10 -import type {PublicInstance} from './ReactNativePrivateInterface';
10 +import type {
11 + PublicInstance,
12 + PublicRootInstance,
13 +} from './ReactNativePrivateInterface';
14
15 export default function createPublicInstance(
16 tag: number,
17 viewConfig: mixed,
18 internalInstanceHandle: mixed,
19 + rootPublicInstance: PublicRootInstance | null,
20 ): PublicInstance {
21 return {
22 __nativeTag: tag,
23 __internalInstanceHandle: internalInstanceHandle,
24 + __rootPublicInstance: rootPublicInstance,
25 };
26 }
packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/createPublicRootInstance.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow strict
8 + */
9 +
10 +import type {PublicRootInstance} from './ReactNativePrivateInterface';
11 +
12 +export default function createPublicRootInstance(
13 + rootTag: number,
14 +): PublicRootInstance {
15 + return null;
16 +}
scripts/flow/react-native-host-hooks.js
+5
@@ -143,6 +143,7 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
143 };
144 declare export opaque type PublicInstance;
145 declare export opaque type PublicTextInstance;
146 + declare export opaque type PublicRootInstance;
147 declare export function getNodeFromPublicInstance(
148 publicInstance: PublicInstance,
149 ): Object;
@@ -153,7 +154,11 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
154 tag: number,
155 viewConfig: __ViewConfig,
156 internalInstanceHandle: mixed,
157 + publicRootInstance: PublicRootInstance | null,
158 ): PublicInstance;
159 + declare export function createPublicRootInstance(
160 + rootTag: number,
161 + ): PublicRootInstance;
162 declare export function createPublicTextInstance(
163 internalInstanceHandle: mixed,
164 ): PublicTextInstance;