Skip to content

Commit b2357ec

Browse files
authored
[RN] Add support for document instance in React Native (facebook#32260)
## Summary We're adding support for `Document` instances in React Native (as `ReactNativeDocument` instances) in facebook/react-native#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 facebook/react-native#49012 manually syncing these changes.
1 parent c492f97 commit b2357ec

File tree

9 files changed

+87
-14
lines changed

9 files changed

+87
-14
lines changed

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ import {
4141
import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFabric';
4242

4343
// 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';
4549
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
4650

4751
if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
@@ -126,10 +130,16 @@ function render(
126130
onRecoverableError = options.onRecoverableError;
127131
}
128132

133+
const publicRootInstance = createPublicRootInstance(containerTag);
134+
const rootInstance = {
135+
publicInstance: publicRootInstance,
136+
containerTag,
137+
};
138+
129139
// TODO (bvaughn): If we decide to keep the wrapper component,
130140
// We could create a wrapper for containerTag as well to reduce special casing.
131141
root = createContainer(
132-
containerTag,
142+
rootInstance,
133143
concurrentRoot ? ConcurrentRoot : LegacyRoot,
134144
null,
135145
false,
@@ -140,6 +150,7 @@ function render(
140150
onRecoverableError,
141151
null,
142152
);
153+
143154
roots.set(containerTag, root);
144155
}
145156
updateContainer(element, root, null, callback);
@@ -157,6 +168,9 @@ function stopSurface(containerTag: number) {
157168
if (root) {
158169
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
159170
updateContainer(null, root, null, () => {
171+
// Remove the reference to the public instance to prevent memory leaks.
172+
root.containerInfo.publicInstance = null;
173+
160174
roots.delete(containerTag);
161175
});
162176
}
@@ -170,6 +184,16 @@ function createPortal(
170184
return createPortalImpl(children, containerTag, null, key);
171185
}
172186

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+
173197
setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
174198

175199
const roots = new Map<number, FiberRoot>();
@@ -195,6 +219,8 @@ export {
195219
// instance handles we use to dispatch events. This provides a way to access
196220
// the public instances we created from them (potentially created lazily).
197221
getPublicInstanceFromInternalInstanceHandle,
222+
// Returns the document instance for that root tag.
223+
getPublicInstanceFromRootTag,
198224
// DEV-only:
199225
isChildPublicInstance,
200226
};

packages/react-native-renderer/src/ReactFiberConfigFabric.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
createPublicTextInstance,
3232
type PublicInstance as ReactNativePublicInstance,
3333
type PublicTextInstance,
34+
type PublicRootInstance,
3435
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
3536

3637
const {
@@ -108,7 +109,10 @@ export type TextInstance = {
108109
};
109110
export type HydratableInstance = Instance | TextInstance;
110111
export type PublicInstance = ReactNativePublicInstance;
111-
export type Container = number;
112+
export type Container = {
113+
containerTag: number,
114+
publicInstance: PublicRootInstance | null,
115+
};
112116
export type ChildSet = Object | Array<Node>;
113117
export type HostContext = $ReadOnly<{
114118
isInAParentText: boolean,
@@ -180,7 +184,7 @@ export function createInstance(
180184
const node = createNode(
181185
tag, // reactTag
182186
viewConfig.uiViewClassName, // viewName
183-
rootContainerInstance, // rootTag
187+
rootContainerInstance.containerTag, // rootTag
184188
updatePayload, // props
185189
internalInstanceHandle, // internalInstanceHandle
186190
);
@@ -189,6 +193,7 @@ export function createInstance(
189193
tag,
190194
viewConfig,
191195
internalInstanceHandle,
196+
rootContainerInstance.publicInstance,
192197
);
193198

194199
return {
@@ -221,7 +226,7 @@ export function createTextInstance(
221226
const node = createNode(
222227
tag, // reactTag
223228
'RCTRawText', // viewName
224-
rootContainerInstance, // rootTag
229+
rootContainerInstance.containerTag, // rootTag
225230
{text: text}, // props
226231
internalInstanceHandle, // instance handle
227232
);
@@ -501,7 +506,7 @@ export function finalizeContainerChildren(
501506
newChildren: ChildSet,
502507
): void {
503508
if (!enableFabricCompleteRootInCommitPhase) {
504-
completeRoot(container, newChildren);
509+
completeRoot(container.containerTag, newChildren);
505510
}
506511
}
507512

@@ -511,7 +516,7 @@ export function replaceContainerChildren(
511516
): void {
512517
// Noop - children will be replaced in finalizeContainerChildren
513518
if (enableFabricCompleteRootInCommitPhase) {
514-
completeRoot(container, newChildren);
519+
completeRoot(container.containerTag, newChildren);
515520
}
516521
}
517522

packages/react-native-renderer/src/ReactFiberConfigNative.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ReactNativeViewConfigRegistry,
1616
UIManager,
1717
deepFreezeAndThrowOnMutationInDev,
18+
type PublicRootInstance,
1819
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
1920

2021
import {create, diff} from './ReactNativeAttributePayload';
@@ -54,7 +55,10 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
5455

5556
export type Type = string;
5657
export type Props = Object;
57-
export type Container = number;
58+
export type Container = {
59+
containerTag: number,
60+
publicInstance: PublicRootInstance | null,
61+
};
5862
export type Instance = ReactNativeFiberHostComponent;
5963
export type TextInstance = number;
6064
export type HydratableInstance = Instance | TextInstance;
@@ -143,7 +147,7 @@ export function createInstance(
143147
UIManager.createView(
144148
tag, // reactTag
145149
viewConfig.uiViewClassName, // viewName
146-
rootContainerInstance, // rootTag
150+
rootContainerInstance.containerTag, // rootTag
147151
updatePayload, // props
148152
);
149153

@@ -176,7 +180,7 @@ export function createTextInstance(
176180
UIManager.createView(
177181
tag, // reactTag
178182
'RCTRawText', // viewName
179-
rootContainerInstance, // rootTag
183+
rootContainerInstance.containerTag, // rootTag
180184
{text: text}, // props
181185
);
182186

@@ -349,7 +353,7 @@ export function appendChildToContainer(
349353
): void {
350354
const childTag = typeof child === 'number' ? child : child._nativeTag;
351355
UIManager.setChildren(
352-
parentInstance, // containerTag
356+
parentInstance.containerTag, // containerTag
353357
[childTag], // reactTags
354358
);
355359
}
@@ -479,7 +483,7 @@ export function removeChildFromContainer(
479483
): void {
480484
recursivelyUncacheFiberNode(child);
481485
UIManager.manageChildren(
482-
parentInstance, // containerID
486+
parentInstance.containerTag, // containerID
483487
[], // moveFromIndices
484488
[], // moveToIndices
485489
[], // addChildReactTags

packages/react-native-renderer/src/ReactNativeRenderer.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {ReactPortal, ReactNodeList} from 'shared/ReactTypes';
1111
import type {ElementRef, ElementType, MixedElement} from 'react';
1212
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
1313
import type {RenderRootOptions} from './ReactNativeTypes';
14+
import type {Container} from 'react-reconciler/src/ReactFiberConfig';
1415

1516
import './ReactNativeInjection';
1617

@@ -143,10 +144,16 @@ function render(
143144
onRecoverableError = options.onRecoverableError;
144145
}
145146

147+
const rootInstance: Container = {
148+
containerTag,
149+
// $FlowExpectedError[incompatible-type] the legacy renderer does not use public root instances
150+
publicInstance: null,
151+
};
152+
146153
// TODO (bvaughn): If we decide to keep the wrapper component,
147154
// We could create a wrapper for containerTag as well to reduce special casing.
148155
root = createContainer(
149-
containerTag,
156+
rootInstance,
150157
LegacyRoot,
151158
null,
152159
false,

packages/react-native-renderer/src/ReactNativeTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export opaque type Node = mixed;
231231
export opaque type InternalInstanceHandle = mixed;
232232
type PublicInstance = mixed;
233233
type PublicTextInstance = mixed;
234+
export opaque type PublicRootInstance = mixed;
234235

235236
export type ReactFabricType = {
236237
findHostInstance_DEPRECATED<TElementType: ElementType>(

packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativePrivateInterface.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
export opaque type PublicInstance = mixed;
1111
export opaque type PublicTextInstance = mixed;
12+
export opaque type PublicRootInstance = mixed;
1213

1314
module.exports = {
1415
get BatchedBridge() {
@@ -59,4 +60,7 @@ module.exports = {
5960
get createPublicTextInstance() {
6061
return require('./createPublicTextInstance').default;
6162
},
63+
get createPublicRootInstance() {
64+
return require('./createPublicRootInstance').default;
65+
},
6266
};

packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/createPublicInstance.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@
77
* @flow strict
88
*/
99

10-
import type {PublicInstance} from './ReactNativePrivateInterface';
10+
import type {
11+
PublicInstance,
12+
PublicRootInstance,
13+
} from './ReactNativePrivateInterface';
1114

1215
export default function createPublicInstance(
1316
tag: number,
1417
viewConfig: mixed,
1518
internalInstanceHandle: mixed,
19+
rootPublicInstance: PublicRootInstance | null,
1620
): PublicInstance {
1721
return {
1822
__nativeTag: tag,
1923
__internalInstanceHandle: internalInstanceHandle,
24+
__rootPublicInstance: rootPublicInstance,
2025
};
2126
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
143143
};
144144
declare export opaque type PublicInstance;
145145
declare export opaque type PublicTextInstance;
146+
declare export opaque type PublicRootInstance;
146147
declare export function getNodeFromPublicInstance(
147148
publicInstance: PublicInstance,
148149
): Object;
@@ -153,7 +154,11 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
153154
tag: number,
154155
viewConfig: __ViewConfig,
155156
internalInstanceHandle: mixed,
157+
publicRootInstance: PublicRootInstance | null,
156158
): PublicInstance;
159+
declare export function createPublicRootInstance(
160+
rootTag: number,
161+
): PublicRootInstance;
157162
declare export function createPublicTextInstance(
158163
internalInstanceHandle: mixed,
159164
): PublicTextInstance;

0 commit comments

Comments
 (0)