Skip to content

Commit 3d227aa

Browse files
committed
Add more phases to the ReactFiberApplyGesture (#32578)
Stacked on #32585 and #32605. This adds more loops for the phases of "Apply Gesture". It doesn't implement the interesting bit yet like adding view-transition-names and measurements. I'll do that in a separate PR to keep reviewing easier. The three phases of this approach is roughly: - Clone and apply names to the "old" state. - Inside startViewTransition: Apply names to the "new" state. Measure both the "old" and "new" state to know whether to cancel some of them. Delete the clones which will include all the "old" names. - After startViewTransition: Restore "new" names back to no view-transition-name. Since we don't have any other Effects in these phases we have a bit more flexibility and we can avoid extra phases that traverse the tree. I've tried to avoid any additional passes. An interesting consequence of this approach is that we could measure both the "old" and "new" state before `startViewTransition`. This would be more efficient because we wouldn't need to take View Transition snapshots of parts of the tree that won't actually animate. However, that would require an extra pass and force layout earlier. It would also have different semantics from the fire-and-forget View Transitions because we could optimize better which can be visible. It would also not account for any late mutations. So I decided to instead let the layout be computed by painting as usual and then measure both "old" and "new" inside the startViewTransition instead. Then canceling anything that doesn't animate to keep it consistent. Unfortunately, though there's not a lot of code sharing possible in these phases because the strategy is so different with the cloning and because the animation is performed in reverse. The "finishedWork" Fiber represents the "old" state and the "current" Fiber represents the "new" state. The most complicated phase is the cloning. I actually ended up having to make a very different pattern from the other phases and CommitWork in general. Because we have to clone as we go and also do other things like apply names and finding pairs, it has more phases. I ended up with an approach that uses three different loops. The outer one for updated trees, one for inserted trees that don't need cloning (doesn't include reappearing offscreen) and one for not updated trees that still need cloning. Inside each loop it can also be in different phases which I track with the `visitPhase` enum - this pattern is kind of new. Additionally, we need to measure the cloned nodes after we've applied mutations to them and we have to wait until the whole tree is inserted. We don't have a reference to these DOM elements in the Fiber tree since that still refers to the original ones. We need to store the cloned elements somewhere. So I added a temporary field on the ViewTransitionState to keep track of any clones owned by that ViewTransition. When we deep clone an unchanged subtree we don't have DOM element instances. It wouldn't be quite safe to try to find them from the tree structure. So we need to avoid the deep clones if we might need DOM elements. Therefore we keep traversing in the case where we need to find nested ViewTransition boundaries that are either potentially affected by layout or a "pair". For the other two phases the pattern there's a lot of code duplication since it's slightly different from the commit ones but they at least follow the same pattern. For the restore phase I was actually able to reuse most of the code. I don't love how much code this is. DiffTrain build for [c4a3b92](c4a3b92)
1 parent 1809610 commit 3d227aa

32 files changed

+2510
-2304
lines changed

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ __DEV__ &&
15321532
exports.useTransition = function () {
15331533
return resolveDispatcher().useTransition();
15341534
};
1535-
exports.version = "19.1.0-www-classic-3e956805-20250314";
1535+
exports.version = "19.1.0-www-classic-c4a3b92e-20250314";
15361536
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15371537
"function" ===
15381538
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ __DEV__ &&
15321532
exports.useTransition = function () {
15331533
return resolveDispatcher().useTransition();
15341534
};
1535-
exports.version = "19.1.0-www-modern-3e956805-20250314";
1535+
exports.version = "19.1.0-www-modern-c4a3b92e-20250314";
15361536
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15371537
"function" ===
15381538
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,4 +641,4 @@ exports.useSyncExternalStore = function (
641641
exports.useTransition = function () {
642642
return ReactSharedInternals.H.useTransition();
643643
};
644-
exports.version = "19.1.0-www-classic-3e956805-20250314";
644+
exports.version = "19.1.0-www-classic-c4a3b92e-20250314";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,4 +641,4 @@ exports.useSyncExternalStore = function (
641641
exports.useTransition = function () {
642642
return ReactSharedInternals.H.useTransition();
643643
};
644-
exports.version = "19.1.0-www-modern-3e956805-20250314";
644+
exports.version = "19.1.0-www-modern-c4a3b92e-20250314";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ exports.useSyncExternalStore = function (
645645
exports.useTransition = function () {
646646
return ReactSharedInternals.H.useTransition();
647647
};
648-
exports.version = "19.1.0-www-classic-3e956805-20250314";
648+
exports.version = "19.1.0-www-classic-c4a3b92e-20250314";
649649
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
650650
"function" ===
651651
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ exports.useSyncExternalStore = function (
645645
exports.useTransition = function () {
646646
return ReactSharedInternals.H.useTransition();
647647
};
648-
exports.version = "19.1.0-www-modern-3e956805-20250314";
648+
exports.version = "19.1.0-www-modern-c4a3b92e-20250314";
649649
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
650650
"function" ===
651651
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10620,7 +10620,7 @@ __DEV__ &&
1062010620
);
1062110621
}
1062210622
}
10623-
function applyViewTransitionToHostInstances(
10623+
function applyViewTransitionToHostInstancesRecursive(
1062410624
child,
1062510625
name,
1062610626
className,
@@ -10634,7 +10634,7 @@ __DEV__ &&
1063410634
: inViewport || (inViewport = !0);
1063510635
else if (22 !== child.tag || null === child.memoizedState)
1063610636
(30 === child.tag && stopAtNestedViewTransitions) ||
10637-
(applyViewTransitionToHostInstances(
10637+
(applyViewTransitionToHostInstancesRecursive(
1063810638
child.child,
1063910639
name,
1064010640
className,
@@ -10666,7 +10666,7 @@ __DEV__ &&
1066610666
props.share
1066710667
);
1066810668
"none" !== className &&
10669-
applyViewTransitionToHostInstances(
10669+
applyViewTransitionToHostInstancesRecursive(
1067010670
deletion.child,
1067110671
name,
1067210672
className,
@@ -10699,7 +10699,7 @@ __DEV__ &&
1069910699
void 0 !== pair ? props.share : props.exit
1070010700
);
1070110701
"none" !== className &&
10702-
applyViewTransitionToHostInstances(
10702+
applyViewTransitionToHostInstancesRecursive(
1070310703
deletion.child,
1070410704
name,
1070510705
className,
@@ -10726,14 +10726,16 @@ __DEV__ &&
1072610726
var props = changedParent.memoizedProps,
1072710727
name = getViewTransitionName(props, changedParent.stateNode);
1072810728
props = getViewTransitionClassName(props.className, props.layout);
10729-
"none" !== props &&
10730-
applyViewTransitionToHostInstances(
10729+
if ("none" !== props) {
10730+
var collectMeasurements = (changedParent.memoizedState = []);
10731+
applyViewTransitionToHostInstancesRecursive(
1073110732
changedParent.child,
1073210733
name,
1073310734
props,
10734-
(changedParent.memoizedState = []),
10735+
collectMeasurements,
1073510736
!1
1073610737
);
10738+
}
1073710739
} else
1073810740
0 !== (changedParent.subtreeFlags & 33554432) &&
1073910741
commitNestedViewTransitions(changedParent);
@@ -10898,25 +10900,26 @@ __DEV__ &&
1089810900
isViewTransitionEligible.memoizedProps,
1089910901
isViewTransitionEligible.stateNode
1090010902
);
10901-
current = current.memoizedProps;
10902-
flags = getViewTransitionClassName(
10903-
current.className,
10904-
current.update
10903+
flags = current.memoizedProps;
10904+
current = getViewTransitionClassName(
10905+
flags.className,
10906+
flags.update
1090510907
);
1090610908
if (
10907-
"none" === flags &&
10908-
((flags = getViewTransitionClassName(
10909-
current.className,
10910-
current.layout
10909+
"none" === current &&
10910+
((current = getViewTransitionClassName(
10911+
flags.className,
10912+
flags.layout
1091110913
)),
10912-
"none" === flags)
10914+
"none" === current)
1091310915
)
1091410916
break a;
10915-
applyViewTransitionToHostInstances(
10917+
flags = isViewTransitionEligible.memoizedState = [];
10918+
applyViewTransitionToHostInstancesRecursive(
1091610919
isViewTransitionEligible.child,
1091710920
finishedWork,
10921+
current,
1091810922
flags,
10919-
(isViewTransitionEligible.memoizedState = []),
1092010923
!0
1092110924
);
1092210925
}
@@ -15818,7 +15821,12 @@ __DEV__ &&
1581815821
(key = createFiber(30, pendingProps, key, mode)),
1581915822
(key.elementType = REACT_VIEW_TRANSITION_TYPE),
1582015823
(key.lanes = lanes),
15821-
(key.stateNode = { autoName: null, paired: null, ref: null }),
15824+
(key.stateNode = {
15825+
autoName: null,
15826+
paired: null,
15827+
clones: null,
15828+
ref: null
15829+
}),
1582215830
key
1582315831
);
1582415832
case REACT_SCOPE_TYPE:
@@ -18495,10 +18503,10 @@ __DEV__ &&
1849518503
(function () {
1849618504
var internals = {
1849718505
bundleType: 1,
18498-
version: "19.1.0-www-classic-3e956805-20250314",
18506+
version: "19.1.0-www-classic-c4a3b92e-20250314",
1849918507
rendererPackageName: "react-art",
1850018508
currentDispatcherRef: ReactSharedInternals,
18501-
reconcilerVersion: "19.1.0-www-classic-3e956805-20250314"
18509+
reconcilerVersion: "19.1.0-www-classic-c4a3b92e-20250314"
1850218510
};
1850318511
internals.overrideHookState = overrideHookState;
1850418512
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -18532,7 +18540,7 @@ __DEV__ &&
1853218540
exports.Shape = Shape;
1853318541
exports.Surface = Surface;
1853418542
exports.Text = Text;
18535-
exports.version = "19.1.0-www-classic-3e956805-20250314";
18543+
exports.version = "19.1.0-www-classic-c4a3b92e-20250314";
1853618544
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1853718545
"function" ===
1853818546
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10438,7 +10438,7 @@ __DEV__ &&
1043810438
);
1043910439
}
1044010440
}
10441-
function applyViewTransitionToHostInstances(
10441+
function applyViewTransitionToHostInstancesRecursive(
1044210442
child,
1044310443
name,
1044410444
className,
@@ -10452,7 +10452,7 @@ __DEV__ &&
1045210452
: inViewport || (inViewport = !0);
1045310453
else if (22 !== child.tag || null === child.memoizedState)
1045410454
(30 === child.tag && stopAtNestedViewTransitions) ||
10455-
(applyViewTransitionToHostInstances(
10455+
(applyViewTransitionToHostInstancesRecursive(
1045610456
child.child,
1045710457
name,
1045810458
className,
@@ -10484,7 +10484,7 @@ __DEV__ &&
1048410484
props.share
1048510485
);
1048610486
"none" !== className &&
10487-
applyViewTransitionToHostInstances(
10487+
applyViewTransitionToHostInstancesRecursive(
1048810488
deletion.child,
1048910489
name,
1049010490
className,
@@ -10517,7 +10517,7 @@ __DEV__ &&
1051710517
void 0 !== pair ? props.share : props.exit
1051810518
);
1051910519
"none" !== className &&
10520-
applyViewTransitionToHostInstances(
10520+
applyViewTransitionToHostInstancesRecursive(
1052110521
deletion.child,
1052210522
name,
1052310523
className,
@@ -10544,14 +10544,16 @@ __DEV__ &&
1054410544
var props = changedParent.memoizedProps,
1054510545
name = getViewTransitionName(props, changedParent.stateNode);
1054610546
props = getViewTransitionClassName(props.className, props.layout);
10547-
"none" !== props &&
10548-
applyViewTransitionToHostInstances(
10547+
if ("none" !== props) {
10548+
var collectMeasurements = (changedParent.memoizedState = []);
10549+
applyViewTransitionToHostInstancesRecursive(
1054910550
changedParent.child,
1055010551
name,
1055110552
props,
10552-
(changedParent.memoizedState = []),
10553+
collectMeasurements,
1055310554
!1
1055410555
);
10556+
}
1055510557
} else
1055610558
0 !== (changedParent.subtreeFlags & 33554432) &&
1055710559
commitNestedViewTransitions(changedParent);
@@ -10716,25 +10718,26 @@ __DEV__ &&
1071610718
isViewTransitionEligible.memoizedProps,
1071710719
isViewTransitionEligible.stateNode
1071810720
);
10719-
current = current.memoizedProps;
10720-
flags = getViewTransitionClassName(
10721-
current.className,
10722-
current.update
10721+
flags = current.memoizedProps;
10722+
current = getViewTransitionClassName(
10723+
flags.className,
10724+
flags.update
1072310725
);
1072410726
if (
10725-
"none" === flags &&
10726-
((flags = getViewTransitionClassName(
10727-
current.className,
10728-
current.layout
10727+
"none" === current &&
10728+
((current = getViewTransitionClassName(
10729+
flags.className,
10730+
flags.layout
1072910731
)),
10730-
"none" === flags)
10732+
"none" === current)
1073110733
)
1073210734
break a;
10733-
applyViewTransitionToHostInstances(
10735+
flags = isViewTransitionEligible.memoizedState = [];
10736+
applyViewTransitionToHostInstancesRecursive(
1073410737
isViewTransitionEligible.child,
1073510738
finishedWork,
10739+
current,
1073610740
flags,
10737-
(isViewTransitionEligible.memoizedState = []),
1073810741
!0
1073910742
);
1074010743
}
@@ -15632,7 +15635,12 @@ __DEV__ &&
1563215635
(key = createFiber(30, pendingProps, key, mode)),
1563315636
(key.elementType = REACT_VIEW_TRANSITION_TYPE),
1563415637
(key.lanes = lanes),
15635-
(key.stateNode = { autoName: null, paired: null, ref: null }),
15638+
(key.stateNode = {
15639+
autoName: null,
15640+
paired: null,
15641+
clones: null,
15642+
ref: null
15643+
}),
1563615644
key
1563715645
);
1563815646
case REACT_SCOPE_TYPE:
@@ -18267,10 +18275,10 @@ __DEV__ &&
1826718275
(function () {
1826818276
var internals = {
1826918277
bundleType: 1,
18270-
version: "19.1.0-www-modern-3e956805-20250314",
18278+
version: "19.1.0-www-modern-c4a3b92e-20250314",
1827118279
rendererPackageName: "react-art",
1827218280
currentDispatcherRef: ReactSharedInternals,
18273-
reconcilerVersion: "19.1.0-www-modern-3e956805-20250314"
18281+
reconcilerVersion: "19.1.0-www-modern-c4a3b92e-20250314"
1827418282
};
1827518283
internals.overrideHookState = overrideHookState;
1827618284
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -18304,7 +18312,7 @@ __DEV__ &&
1830418312
exports.Shape = Shape;
1830518313
exports.Surface = Surface;
1830618314
exports.Text = Text;
18307-
exports.version = "19.1.0-www-modern-3e956805-20250314";
18315+
exports.version = "19.1.0-www-modern-c4a3b92e-20250314";
1830818316
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1830918317
"function" ===
1831018318
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)