Fix a crash in View Culling coming from unflattening/flattening algorith (#51907)

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

changelog: [internal]

fixes a crash that is described by the two tests. The two tests cover both changes made to Differentiator.cpp. If you revert either of the change, a test will crash.

Reviewed By: christophpurrer

Differential Revision: D76231984

fbshipit-source-id: c32ab1851844121095f1dec953befb2e77c83fd4
This commit is contained in:
Samuel Susla
2025-06-09 14:26:42 -07:00
committed by Facebook GitHub Bot
parent 38a4b62211
commit 84f9e48331
2 changed files with 123 additions and 2 deletions
@@ -2327,6 +2327,126 @@ describe('reparenting', () => {
'Insert {type: "View", parentNativeID: "grandchild", index: 0, nativeID: "grandgrandchild"}',
]);
});
test('parent-child flattening with deep hierarchy', () => {
function renderTree(root: Fantom.Root, isFinal: boolean) {
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
contentOffset={{x: 0, y: 52}}>
<View
style={{
marginTop: isFinal ? 92 : 100,
opacity: isFinal ? 0 : undefined,
}}>
<View
style={{
marginTop: 50,
opacity: isFinal ? 0 : undefined,
}}>
<View collapsable={false} style={{height: 10, width: 10}}>
<View
collapsable={false}
style={{height: 5, width: 5, marginTop: 5}}>
<View
nativeID="child"
style={{height: 2.5, width: 2.5, marginTop: 2.5}}
/>
</View>
</View>
</View>
</View>
</ScrollView>,
);
});
}
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
renderTree(root, false);
expect(root.takeMountingManagerLogs()).not.toContain(
'Create {type: "View", nativeID: "child"}',
);
renderTree(root, true);
expect(root.takeMountingManagerLogs()).toContain(
'Create {type: "View", nativeID: "child"}',
);
const finalRoot = Fantom.createRoot({
viewportWidth: 100,
viewportHeight: 100,
});
renderTree(finalRoot, true);
expect(root.getRenderedOutput().toJSON).toEqual(
finalRoot.getRenderedOutput().toJSON,
);
});
test('parent-child unflattening with deep hierarchy', () => {
function renderTree(root: Fantom.Root, isFinal: boolean) {
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
contentOffset={{x: 0, y: 52}}>
<View
style={{
marginTop: isFinal ? 92 : 100,
opacity: isFinal ? undefined : 0,
}}>
<View
style={{
marginTop: 50,
opacity: isFinal ? undefined : 0,
}}>
<View collapsable={false} style={{height: 10, width: 10}}>
<View
collapsable={false}
style={{height: 5, width: 5, marginTop: 5}}>
<View
nativeID="child"
style={{height: 2.5, width: 2.5, marginTop: 2.5}}
/>
</View>
</View>
</View>
</View>
</ScrollView>,
);
});
}
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
renderTree(root, false);
expect(root.takeMountingManagerLogs()).not.toContain(
'Create {type: "View", nativeID: "child"}',
);
renderTree(root, true);
expect(root.takeMountingManagerLogs()).toContain(
'Create {type: "View", nativeID: "child"}',
);
const finalRoot = Fantom.createRoot({
viewportWidth: 100,
viewportHeight: 100,
});
renderTree(finalRoot, true);
expect(root.getRenderedOutput().toJSON).toEqual(
finalRoot.getRenderedOutput().toJSON,
);
});
});
describe('opt out mechanism - Unstable_uncullableView & Unstable_uncullableTrace', () => {
@@ -630,7 +630,8 @@ static void calculateShadowViewMutationsFlattener(
// Update children if appropriate.
if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) {
if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) {
if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode ||
adjustedOldCullingContext != adjustedNewCullingContext) {
ViewNodePairScope innerScope{};
auto oldGrandChildPairs =
sliceChildShadowNodeViewPairsFromViewNodePair(
@@ -678,7 +679,7 @@ static void calculateShadowViewMutationsFlattener(
: parentTag),
subVisitedNewMap,
subVisitedOldMap,
cullingContext,
cullingContextForUnvisitedOtherNodes,
cullingContext.adjustCullingContextIfNeeded(treeChildPair));
} else {
// Get flattened nodes from either new or old tree