Skip to content

Commit 6f345cb

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
add a test for view culling where during reparenting a view is culled (#50885)
Summary: Pull Request resolved: #50885 changelog: [internal] a special case inside of Differentiator handling parent-child switching from unflattened-flattened to flattened-unflattened. If a child has view that is culled, this needs to be handled. This diff also simplifies the implementation inside of calculateShadowViewMutationsFlattener by passing only one cullingContext. Reviewed By: mdvacca Differential Revision: D73523523 fbshipit-source-id: d6f314da6b9ff40bcf3362243b03de1b39e7aabb
1 parent f51d6ec commit 6f345cb

File tree

2 files changed

+112
-32
lines changed

2 files changed

+112
-32
lines changed

packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,3 +1468,95 @@ test('flattening and deleting a subtree that is partially culled', () => {
14681468
'Delete {type: "View", nativeID: "child"}',
14691469
]);
14701470
});
1471+
1472+
test('parent-child switching from unflattened-flattened to flattened-unflattened and grandchild is culled', () => {
1473+
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
1474+
1475+
// First render unflattened view container with flattened child that has a culled grandchild.
1476+
Fantom.runTask(() => {
1477+
root.render(
1478+
<ScrollView
1479+
style={{height: 100, width: 100}}
1480+
contentOffset={{x: 0, y: 60}}>
1481+
<View
1482+
style={{
1483+
marginTop: 100,
1484+
opacity: 0,
1485+
}}>
1486+
<View
1487+
style={{
1488+
marginTop: 50,
1489+
}}>
1490+
<View
1491+
nativeID={'grandchild'}
1492+
style={{height: 10, width: 10, marginTop: 11}}
1493+
/>
1494+
</View>
1495+
</View>
1496+
</ScrollView>,
1497+
);
1498+
});
1499+
1500+
// Note that `grandchild` is not mounted.
1501+
expect(root.takeMountingManagerLogs()).toEqual([
1502+
'Update {type: "RootView", nativeID: (root)}',
1503+
'Create {type: "ScrollView", nativeID: (N/A)}',
1504+
'Create {type: "View", nativeID: (N/A)}',
1505+
'Create {type: "View", nativeID: (N/A)}',
1506+
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
1507+
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
1508+
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
1509+
]);
1510+
1511+
let maybeNode = null;
1512+
1513+
// Now change unflattened view container to flattened and change its child to be unflattened.
1514+
Fantom.runTask(() => {
1515+
root.render(
1516+
<ScrollView
1517+
style={{height: 100, width: 100}}
1518+
ref={node => {
1519+
maybeNode = node;
1520+
}}
1521+
contentOffset={{x: 0, y: 60}}>
1522+
<View
1523+
style={{
1524+
marginTop: 100,
1525+
}}>
1526+
<View
1527+
style={{
1528+
marginTop: 50,
1529+
opacity: 0,
1530+
}}>
1531+
<View
1532+
nativeID={'grandchild'}
1533+
style={{height: 10, width: 10, marginTop: 11}}
1534+
/>
1535+
</View>
1536+
</View>
1537+
</ScrollView>,
1538+
);
1539+
});
1540+
1541+
// Note that `grandchild` is not mounted.
1542+
expect(root.takeMountingManagerLogs()).toEqual([
1543+
'Remove {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
1544+
'Delete {type: "View", nativeID: (N/A)}',
1545+
'Create {type: "View", nativeID: (N/A)}',
1546+
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
1547+
]);
1548+
1549+
const element = ensureInstance(maybeNode, ReactNativeElement);
1550+
1551+
// Scroll to reveal grandchild.
1552+
Fantom.scrollTo(element, {
1553+
x: 0,
1554+
y: 70,
1555+
});
1556+
1557+
expect(root.takeMountingManagerLogs()).toEqual([
1558+
'Update {type: "ScrollView", nativeID: (N/A)}',
1559+
'Create {type: "View", nativeID: "grandchild"}',
1560+
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "grandchild"}',
1561+
]);
1562+
});

packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ static void calculateShadowViewMutationsFlattener(
165165
Tag parentTagForUpdate,
166166
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherNewNodes,
167167
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherOldNodes,
168-
const CullingContext& oldCullingContext,
169-
const CullingContext& newCullingContext);
168+
const CullingContext& cullingContext);
170169

171170
/**
172171
* Updates the subtrees of any matched ShadowViewNodePair. This handles
@@ -222,8 +221,7 @@ static void updateMatchedPairSubtrees(
222221
oldPair.shadowView.tag,
223222
nullptr,
224223
nullptr,
225-
oldCullingContextCopy,
226-
newCullingContextCopy);
224+
oldCullingContextCopy);
227225
}
228226
// Unflattening
229227
else {
@@ -257,7 +255,6 @@ static void updateMatchedPairSubtrees(
257255
parentTag,
258256
nullptr,
259257
nullptr,
260-
oldCullingContextCopy,
261258
newCullingContextCopy);
262259

263260
// If old nodes were not visited, we know that we can delete
@@ -423,12 +420,11 @@ static void calculateShadowViewMutationsFlattener(
423420
Tag parentTagForUpdate,
424421
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherNewNodes,
425422
TinyMap<Tag, ShadowViewNodePair*>* parentSubVisitedOtherOldNodes,
426-
const CullingContext& oldCullingContext,
427-
const CullingContext& newCullingContext) {
423+
const CullingContext& cullingContext) {
428424
// Step 1: iterate through entire tree
429425
std::vector<ShadowViewNodePair*> treeChildren =
430426
sliceChildShadowNodeViewPairsFromViewNodePair(
431-
node, scope, false, newCullingContext);
427+
node, scope, false, cullingContext);
432428

433429
DEBUG_LOGS({
434430
LOG(ERROR) << "Differ Flattener: "
@@ -620,16 +616,13 @@ static void calculateShadowViewMutationsFlattener(
620616
}
621617

622618
auto adjustedOldCullingContext =
623-
oldCullingContext.adjustCullingContextIfNeeded(oldTreeNodePair);
619+
cullingContext.adjustCullingContextIfNeeded(oldTreeNodePair);
624620
auto adjustedNewCullingContext =
625-
newCullingContext.adjustCullingContextIfNeeded(newTreeNodePair);
621+
cullingContext.adjustCullingContextIfNeeded(newTreeNodePair);
626622

627623
// Update children if appropriate.
628624
if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) {
629-
if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode ||
630-
oldCullingContext != newCullingContext) {
631-
// TODO(T217775046): Find a test case for oldCullingContext !=
632-
// newCullingContext condition above.
625+
if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) {
633626
ViewNodePairScope innerScope{};
634627
auto oldGrandChildPairs =
635628
sliceChildShadowNodeViewPairsFromViewNodePair(
@@ -677,12 +670,9 @@ static void calculateShadowViewMutationsFlattener(
677670
: parentTag),
678671
subVisitedNewMap,
679672
subVisitedOldMap,
680-
oldCullingContext,
681-
newCullingContext);
673+
cullingContext.adjustCullingContextIfNeeded(treeChildPair));
682674
} else {
683675
// Get flattened nodes from either new or old tree
684-
// TODO(T217775046): Find a test case for this branch of view
685-
// flattening + culling.
686676
auto flattenedNodes = sliceChildShadowNodeViewPairsFromViewNodePair(
687677
(childReparentMode == ReparentMode::Flatten ? newTreeNodePair
688678
: oldTreeNodePair),
@@ -735,8 +725,7 @@ static void calculateShadowViewMutationsFlattener(
735725
: parentTagForUpdateWhenUnflattened),
736726
subVisitedNewMap,
737727
subVisitedOldMap,
738-
adjustedOldCullingContext,
739-
adjustedNewCullingContext);
728+
cullingContext.adjustCullingContextIfNeeded(oldTreeNodePair));
740729
}
741730
// Flatten parent, unflatten child
742731
else {
@@ -762,8 +751,9 @@ static void calculateShadowViewMutationsFlattener(
762751
: parentTag),
763752
/* parentSubVisitedOtherNewNodes */ subVisitedNewMap,
764753
/* parentSubVisitedOtherOldNodes */ subVisitedOldMap,
765-
oldCullingContext,
766-
newCullingContext);
754+
reparentMode == ReparentMode::Flatten
755+
? adjustedOldCullingContext
756+
: adjustedNewCullingContext);
767757

768758
// If old nodes were not visited, we know that we can delete them
769759
// now. They will be removed from the hierarchy by the outermost
@@ -848,10 +838,8 @@ static void calculateShadowViewMutationsFlattener(
848838
continue;
849839
}
850840

851-
auto adjustedOldCullingContext =
852-
oldCullingContext.adjustCullingContextIfNeeded(treeChildPair);
853-
auto adjustedNewCullingContext =
854-
newCullingContext.adjustCullingContextIfNeeded(treeChildPair);
841+
auto adjustedCullingContext =
842+
cullingContext.adjustCullingContextIfNeeded(treeChildPair);
855843

856844
if (reparentMode == ReparentMode::Flatten) {
857845
mutationContainer.deleteMutations.push_back(
@@ -864,10 +852,10 @@ static void calculateShadowViewMutationsFlattener(
864852
mutationContainer.destructiveDownwardMutations,
865853
treeChildPair.shadowView.tag,
866854
sliceChildShadowNodeViewPairsFromViewNodePair(
867-
treeChildPair, innerScope, false, adjustedOldCullingContext),
855+
treeChildPair, innerScope, false, adjustedCullingContext),
868856
{},
869-
adjustedOldCullingContext,
870-
newCullingContext);
857+
adjustedCullingContext,
858+
{});
871859
}
872860
} else {
873861
mutationContainer.createMutations.push_back(
@@ -881,9 +869,9 @@ static void calculateShadowViewMutationsFlattener(
881869
treeChildPair.shadowView.tag,
882870
{},
883871
sliceChildShadowNodeViewPairsFromViewNodePair(
884-
treeChildPair, innerScope, false, adjustedNewCullingContext),
885-
oldCullingContext,
886-
adjustedNewCullingContext);
872+
treeChildPair, innerScope, false, adjustedCullingContext),
873+
{},
874+
adjustedCullingContext);
887875
}
888876
}
889877
}

0 commit comments

Comments
 (0)