Skip to content

Commit 17d274d

Browse files
authored
Remove Mutation Check Around commit/measureUpdateViewTransition (facebook#32617)
There's two ways to find updated View Transitions. One is the "commit/measureNestedViewTransitions" pass which is used to find things in unchanged subtrees. This can only lead to the relayout case since there's can't possibly be any mutations in the subtree. This is only triggered when none of the direct siblings have any mutations at all. The other case is "commit/measureUpdateViewTransition" which is for a ViewTransition that itself has mutations scheduled inside of it which leads to the "update" case. However, there's a case between these two cases. When a direct sibling has a mutation but there's also a ViewTransition exactly at the same level. In that case we can't bail out on the whole set of children so we won't trigger the "nested" case. Previously we also didn't trigger the "commit/measureUpdateViewTransition" case because we first checked if that had any mutations inside of it at all. This leads to neither case picking up this boundary. We could check if the ViewTransition itself has any mutations inside and if not trigger the nested path. There's a simpler way though. Because `commit/measureUpdateViewTransition` is actually just optimistic. The flags are pessimistic and we don't know for sure if there will actually be a mutation until we've traversed the tree. It can sometimes lead to the "relayout" case. So we can just use that same path, knowing that it'll just lead to the layout pass. Therefore it's safe to just remove this check.
1 parent 6b5d9fd commit 17d274d

File tree

2 files changed

+49
-63
lines changed

2 files changed

+49
-63
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ import {
100100
Hydrating,
101101
Passive,
102102
BeforeMutationMask,
103-
BeforeMutationTransitionMask,
103+
BeforeAndAfterMutationTransitionMask,
104104
MutationMask,
105105
LayoutMask,
106106
PassiveMask,
@@ -311,7 +311,7 @@ function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {
311311
// If this commit is eligible for a View Transition we look into all mutated subtrees.
312312
// TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.
313313
const subtreeMask = isViewTransitionEligible
314-
? BeforeMutationTransitionMask
314+
? BeforeAndAfterMutationTransitionMask
315315
: BeforeMutationMask;
316316
while (nextEffect !== null) {
317317
const fiber = nextEffect;
@@ -487,16 +487,8 @@ function commitBeforeMutationEffectsOnFiber(
487487
if (current === null) {
488488
// This is a new mount. We should have handled this as part of the
489489
// Placement effect or it is deeper inside a entering transition.
490-
} else if (
491-
(finishedWork.subtreeFlags &
492-
(Placement |
493-
Update |
494-
ChildDeletion |
495-
ContentReset |
496-
Visibility)) !==
497-
NoFlags
498-
) {
499-
// Something mutated within this subtree. This might need to cause
490+
} else {
491+
// Something may have mutated within this subtree. This might need to cause
500492
// a cross-fade of this parent. We first assign old names to the
501493
// previous tree in the before mutation phase in case we need to.
502494
// TODO: This walks the tree that we might continue walking anyway.
@@ -2440,7 +2432,7 @@ function recursivelyTraverseAfterMutationEffects(
24402432
lanes: Lanes,
24412433
) {
24422434
// We need to visit the same nodes that we visited in the before mutation phase.
2443-
if (parentFiber.subtreeFlags & BeforeMutationTransitionMask) {
2435+
if (parentFiber.subtreeFlags & BeforeAndAfterMutationTransitionMask) {
24442436
let child = parentFiber.child;
24452437
while (child !== null) {
24462438
commitAfterMutationEffectsOnFiber(child, root, lanes);
@@ -2525,63 +2517,57 @@ function commitAfterMutationEffectsOnFiber(
25252517
break;
25262518
}
25272519
case ViewTransitionComponent: {
2528-
if (
2529-
(finishedWork.subtreeFlags &
2530-
(Placement | Update | ChildDeletion | ContentReset | Visibility)) !==
2531-
NoFlags
2532-
) {
2533-
const wasMutated = (finishedWork.flags & Update) !== NoFlags;
2520+
const wasMutated = (finishedWork.flags & Update) !== NoFlags;
25342521

2535-
const prevContextChanged = viewTransitionContextChanged;
2536-
const prevCancelableChildren = pushViewTransitionCancelableScope();
2537-
viewTransitionContextChanged = false;
2538-
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2522+
const prevContextChanged = viewTransitionContextChanged;
2523+
const prevCancelableChildren = pushViewTransitionCancelableScope();
2524+
viewTransitionContextChanged = false;
2525+
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
25392526

2540-
if (viewTransitionContextChanged) {
2541-
finishedWork.flags |= Update;
2542-
}
2527+
if (viewTransitionContextChanged) {
2528+
finishedWork.flags |= Update;
2529+
}
25432530

2544-
const inViewport = measureUpdateViewTransition(current, finishedWork);
2531+
const inViewport = measureUpdateViewTransition(current, finishedWork);
25452532

2546-
if ((finishedWork.flags & Update) === NoFlags || !inViewport) {
2547-
// If this boundary didn't update, then we may be able to cancel its children.
2548-
// We bubble them up to the parent set to be determined later if we can cancel.
2549-
// Similarly, if old and new state was outside the viewport, we can skip it
2550-
// even if it did update.
2551-
if (prevCancelableChildren === null) {
2552-
// Bubbling up this whole set to the parent.
2553-
} else {
2554-
// Merge with parent set.
2555-
// $FlowFixMe[method-unbinding]
2556-
prevCancelableChildren.push.apply(
2557-
prevCancelableChildren,
2558-
viewTransitionCancelableChildren,
2559-
);
2560-
popViewTransitionCancelableScope(prevCancelableChildren);
2561-
}
2562-
// TODO: If this doesn't end up canceled, because a parent animates,
2563-
// then we should probably issue an event since this instance is part of it.
2533+
if ((finishedWork.flags & Update) === NoFlags || !inViewport) {
2534+
// If this boundary didn't update, then we may be able to cancel its children.
2535+
// We bubble them up to the parent set to be determined later if we can cancel.
2536+
// Similarly, if old and new state was outside the viewport, we can skip it
2537+
// even if it did update.
2538+
if (prevCancelableChildren === null) {
2539+
// Bubbling up this whole set to the parent.
25642540
} else {
2565-
const props: ViewTransitionProps = finishedWork.memoizedProps;
2566-
scheduleViewTransitionEvent(
2567-
finishedWork,
2568-
wasMutated || viewTransitionContextChanged
2569-
? props.onUpdate
2570-
: props.onLayout,
2541+
// Merge with parent set.
2542+
// $FlowFixMe[method-unbinding]
2543+
prevCancelableChildren.push.apply(
2544+
prevCancelableChildren,
2545+
viewTransitionCancelableChildren,
25712546
);
2572-
2573-
// If this boundary did update, we cannot cancel its children so those are dropped.
25742547
popViewTransitionCancelableScope(prevCancelableChildren);
25752548
}
2549+
// TODO: If this doesn't end up canceled, because a parent animates,
2550+
// then we should probably issue an event since this instance is part of it.
2551+
} else {
2552+
const props: ViewTransitionProps = finishedWork.memoizedProps;
2553+
scheduleViewTransitionEvent(
2554+
finishedWork,
2555+
wasMutated || viewTransitionContextChanged
2556+
? props.onUpdate
2557+
: props.onLayout,
2558+
);
25762559

2577-
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {
2578-
// This boundary changed size in a way that may have caused its parent to
2579-
// relayout. We need to bubble this information up to the parent.
2580-
viewTransitionContextChanged = true;
2581-
} else {
2582-
// Otherwise, we restore it to whatever the parent had found so far.
2583-
viewTransitionContextChanged = prevContextChanged;
2584-
}
2560+
// If this boundary did update, we cannot cancel its children so those are dropped.
2561+
popViewTransitionCancelableScope(prevCancelableChildren);
2562+
}
2563+
2564+
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {
2565+
// This boundary changed size in a way that may have caused its parent to
2566+
// relayout. We need to bubble this information up to the parent.
2567+
viewTransitionContextChanged = true;
2568+
} else {
2569+
// Otherwise, we restore it to whatever the parent had found so far.
2570+
viewTransitionContextChanged = prevContextChanged;
25852571
}
25862572
break;
25872573
}

packages/react-reconciler/src/ReactFiberFlags.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ export const BeforeMutationMask: number =
108108

109109
// For View Transition support we use the snapshot phase to scan the tree for potentially
110110
// affected ViewTransition components.
111-
export const BeforeMutationTransitionMask: number =
112-
Snapshot | Update | Placement | ChildDeletion | Visibility;
111+
export const BeforeAndAfterMutationTransitionMask: number =
112+
Snapshot | Update | Placement | ChildDeletion | Visibility | ContentReset;
113113

114114
export const MutationMask =
115115
Placement |

0 commit comments

Comments
 (0)