fix a crash in Fabric View Culling when wrapping view is unflattened (#49845)

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

changelog: [internal]

Fixes a crash that occurs when culled view's ancestor is flattened/unflattened.

Reviewed By: lenaic

Differential Revision: D70620103

fbshipit-source-id: 20b7f9acd59c5e74e768d31ff1f96bdd06747389
This commit is contained in:
Samuel Susla
2025-03-05 11:18:46 -08:00
committed by Facebook GitHub Bot
parent cc761e42c8
commit f5682d64eb
2 changed files with 119 additions and 5 deletions
@@ -840,3 +840,112 @@ test('culling when ScrollView parent has transform', () => {
'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
});
test('view flattening with culling', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
let maybeNode;
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
style={{
marginTop: 150,
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, backgroundColor: 'red'}}
/>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
const element = ensureInstance(maybeNode, ReactNativeElement);
Fantom.runOnUIThread(() => {
Fantom.scrollTo(element, {
x: 0,
y: 60,
});
});
Fantom.runWorkLoop();
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "child"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
]);
// force view to be unflattened.
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
style={{
marginTop: 150,
opacity: 0, // force view to be unflattened
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, backgroundColor: 'red'}}
/>
</View>
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "View", nativeID: "child"}',
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
'Create {type: "View", nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
]);
// force view to be flattened.
Fantom.runTask(() => {
root.render(
<ScrollView
style={{height: 100, width: 100}}
ref={node => {
maybeNode = node;
}}>
<View
style={{
marginTop: 150,
}}>
<View
nativeID={'child'}
style={{height: 10, width: 10, backgroundColor: 'red'}}
/>
</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)}',
'Delete {type: "View", nativeID: (N/A)}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "child"}',
]);
});
@@ -201,6 +201,11 @@ static void updateMatchedPairSubtrees(
<< newPair << " with parent [" << parentTag << "]";
});
auto oldCullingContextCopy =
oldCullingContext.adjustCullingContextIfNeeded(oldPair);
auto newCullingContextCopy =
newCullingContext.adjustCullingContextIfNeeded(newPair);
// Flattening
if (!oldPair.flattened) {
// Flatten old tree into new list
@@ -217,8 +222,8 @@ static void updateMatchedPairSubtrees(
oldPair.shadowView.tag,
nullptr,
nullptr,
oldCullingContext,
newCullingContext);
oldCullingContextCopy,
newCullingContextCopy);
}
// Unflattening
else {
@@ -230,7 +235,7 @@ static void updateMatchedPairSubtrees(
// + zIndex: the children could be listed before the parent,
// interwoven with children from other nodes, etc.
auto oldFlattenedNodes = sliceChildShadowNodeViewPairsFromViewNodePair(
oldPair, scope, true, oldCullingContext);
oldPair, scope, true, oldCullingContextCopy);
for (size_t i = 0, j = 0;
i < oldChildPairs.size() && j < oldFlattenedNodes.size();
i++) {
@@ -252,8 +257,8 @@ static void updateMatchedPairSubtrees(
parentTag,
nullptr,
nullptr,
oldCullingContext,
newCullingContext);
oldCullingContextCopy,
newCullingContextCopy);
// If old nodes were not visited, we know that we can delete
// them now. They will be removed from the hierarchy by the