fix a crash in view culling when the differentiator produces create instruction for existing tag (#50843)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/50843

changelog: [internal]

Fix a crash where view culling produces a mutation to create a view that was already created.

Without the change in Differentiator.cpp, the test fails assert on [StubViewTree.cpp:69](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/renderer/mounting/stubs/StubViewTree.cpp#L69).

Reviewed By: rubennorte

Differential Revision: D72818343

fbshipit-source-id: 8aec3ccf967f453c619a9495dcd32b43b21afea3
This commit is contained in:
Samuel Susla
2025-04-22 10:32:55 -07:00
committed by Facebook GitHub Bot
parent 90a93aa48c
commit 4ab7d531fb
2 changed files with 79 additions and 4 deletions
@@ -1162,3 +1162,80 @@ test('scroll view parent is unflattened and view becomes culled', () => {
'Insert {type: "ScrollView", parentNativeID: "unflattened", index: 0, nativeID: (N/A)}',
]);
});
test('parent-child flattening with culling', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
contentOffset={{x: 0, y: 60}}>
<View
style={{
marginTop: 100,
opacity: 0,
}}>
<View
style={{
marginTop: 50,
opacity: 0,
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, backgroundColor: 'red'}}
/>
</View>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
// force parent-child to be flattened.
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
contentOffset={{x: 0, y: 60}}>
<View
style={{
marginTop: 100,
}}>
<View
style={{
marginTop: 50,
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, backgroundColor: 'red'}}
/>
</View>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "View", nativeID: "child"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Delete {type: "View", nativeID: (N/A)}',
'Delete {type: "View", nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
]);
});
@@ -663,8 +663,6 @@ static void calculateShadowViewMutationsFlattener(
// Case 1: child mode is the same as parent.
// This is a flatten-flatten, or unflatten-unflatten.
if (childReparentMode == reparentMode) {
// TODO(T217775046): Find a test case for this branch of view
// flattening + culling.
calculateShadowViewMutationsFlattener(
scope,
childReparentMode,
@@ -679,8 +677,8 @@ static void calculateShadowViewMutationsFlattener(
: parentTag),
subVisitedNewMap,
subVisitedOldMap,
adjustedOldCullingContext,
adjustedNewCullingContext);
oldCullingContext,
newCullingContext);
} else {
// Get flattened nodes from either new or old tree
// TODO(T217775046): Find a test case for this branch of view