Skip to content

Add more phases to the ReactFiberApplyGesture #32578

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 14, 2025

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Mar 12, 2025

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.

@react-sizebot
Copy link

react-sizebot commented Mar 12, 2025

Comparing: 6733870...0c12bc0

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 518.75 kB 516.17 kB = 92.47 kB 92.15 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +1.19% 591.76 kB 598.80 kB +0.81% 105.36 kB 106.21 kB
facebook-www/ReactDOM-prod.classic.js = 652.39 kB 650.06 kB = 114.72 kB 114.44 kB
facebook-www/ReactDOM-prod.modern.js = 642.71 kB 640.38 kB = 113.15 kB 112.87 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-reconciler/cjs/react-reconciler.production.js +1.71% 444.93 kB 452.56 kB +1.09% 71.69 kB 72.47 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +1.53% 497.89 kB 505.52 kB +1.02% 79.60 kB 80.41 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +1.22% 738.47 kB 747.49 kB +0.83% 116.45 kB 117.41 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +1.19% 591.76 kB 598.80 kB +0.81% 105.36 kB 106.21 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +1.16% 606.49 kB 613.53 kB +0.79% 108.96 kB 109.81 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +1.09% 647.17 kB 654.21 kB +0.74% 114.08 kB 114.93 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +0.82% 1,073.21 kB 1,082.03 kB +0.55% 180.02 kB 181.01 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +0.81% 1,089.61 kB 1,098.43 kB +0.55% 182.86 kB 183.86 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.81% 1,090.13 kB 1,098.95 kB +0.56% 183.76 kB 184.79 kB
oss-experimental/react-art/cjs/react-art.production.js +0.68% 326.92 kB 329.14 kB +0.32% 55.68 kB 55.86 kB
oss-experimental/react-art/cjs/react-art.development.js +0.40% 620.86 kB 623.38 kB +0.23% 98.86 kB 99.09 kB
facebook-www/ReactDOMTesting-dev.classic.js = 1,194.21 kB 1,191.26 kB = 198.99 kB 198.63 kB
facebook-www/ReactDOMTesting-dev.modern.js = 1,185.07 kB 1,182.12 kB = 197.20 kB 196.85 kB
facebook-www/ReactDOM-dev.classic.js = 1,177.67 kB 1,174.73 kB = 195.13 kB 194.79 kB
facebook-www/ReactDOM-dev.modern.js = 1,168.53 kB 1,165.59 kB = 193.41 kB 193.08 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js = 677.47 kB 675.72 kB = 110.21 kB 109.99 kB
react-native/implementations/ReactNativeRenderer-dev.js = 650.62 kB 648.88 kB = 106.09 kB 105.88 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js = 600.52 kB 598.82 kB = 96.71 kB 96.51 kB
oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,267.61 kB 2,261.18 kB = 358.95 kB 358.11 kB
facebook-www/ReactTestRenderer-dev.classic.js = 577.52 kB 575.83 kB = 93.87 kB 93.72 kB
facebook-www/ReactTestRenderer-dev.modern.js = 577.52 kB 575.83 kB = 93.87 kB 93.72 kB
facebook-www/ReactReconciler-dev.classic.js = 820.07 kB 817.65 kB = 127.37 kB 127.17 kB
facebook-www/ReactART-dev.classic.js = 710.89 kB 708.78 kB = 111.38 kB 111.17 kB
react-native/implementations/ReactFabric-dev.fb.js = 672.95 kB 670.95 kB = 109.33 kB 109.14 kB
facebook-www/ReactReconciler-dev.modern.js = 810.86 kB 808.45 kB = 125.69 kB 125.49 kB
facebook-www/ReactART-dev.modern.js = 701.40 kB 699.29 kB = 109.54 kB 109.33 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js = 559.66 kB 557.95 kB = 90.84 kB 90.65 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js = 559.61 kB 557.90 kB = 90.83 kB 90.64 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js = 559.58 kB 557.87 kB = 90.82 kB 90.63 kB
oss-stable/react-art/cjs/react-art.development.js = 560.65 kB 558.91 kB = 90.16 kB 89.96 kB
oss-stable-semver/react-art/cjs/react-art.development.js = 560.58 kB 558.83 kB = 90.13 kB 89.93 kB
react-native/implementations/ReactFabric-dev.js = 642.51 kB 640.51 kB = 104.73 kB 104.54 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js = 648.14 kB 646.11 kB = 103.30 kB 103.08 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js = 648.11 kB 646.09 kB = 103.27 kB 103.05 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js = 1,014.23 kB 1,011.04 kB = 170.44 kB 170.07 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js = 411.53 kB 410.21 kB = 70.85 kB 70.72 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js = 997.91 kB 994.71 kB = 167.57 kB 167.21 kB
oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,268.31 kB 2,260.98 kB = 358.98 kB 358.08 kB
oss-stable/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.production.js = 2,268.31 kB 2,260.98 kB = 358.98 kB 358.08 kB
oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.77 kB 2,587.37 kB = 370.62 kB 369.65 kB
facebook-www/ReactDOM-profiling.classic.js = 715.32 kB 712.99 kB = 123.87 kB 123.60 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js = 970.19 kB 966.99 kB = 163.81 kB 163.41 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js = 970.06 kB 966.86 kB = 163.78 kB 163.38 kB
facebook-www/ReactDOM-profiling.modern.js = 707.28 kB 704.94 kB = 122.54 kB 122.28 kB
react-native/implementations/ReactNativeRenderer-profiling.js = 393.06 kB 391.75 kB = 67.55 kB 67.41 kB
oss-stable/react-dom/cjs/react-dom-client.development.js = 953.75 kB 950.55 kB = 160.98 kB 160.58 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js = 953.62 kB 950.42 kB = 160.95 kB 160.55 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js = 386.05 kB 384.73 kB = 67.03 kB 66.87 kB
facebook-www/ReactDOMTesting-prod.classic.js = 666.80 kB 664.46 kB = 118.38 kB 118.10 kB
facebook-www/ReactDOMTesting-prod.modern.js = 657.11 kB 654.78 kB = 116.77 kB 116.49 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js = 357.74 kB 356.47 kB = 61.64 kB 61.52 kB
facebook-www/ReactDOM-prod.classic.js = 652.39 kB 650.06 kB = 114.72 kB 114.44 kB
react-native/implementations/ReactNativeRenderer-prod.js = 367.71 kB 366.40 kB = 63.74 kB 63.62 kB
oss-stable-semver/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.58 kB 2,586.23 kB = 370.60 kB 369.56 kB
oss-stable/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js = 2,595.58 kB 2,586.23 kB = 370.60 kB 369.56 kB
facebook-www/ReactDOM-prod.modern.js = 642.71 kB 640.38 kB = 113.15 kB 112.87 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js = 335.56 kB 334.29 kB = 58.45 kB 58.34 kB
react-native/implementations/ReactFabric-profiling.fb.js = 408.29 kB 406.73 kB = 70.26 kB 70.13 kB
react-native/implementations/ReactFabric-profiling.js = 386.00 kB 384.44 kB = 66.35 kB 66.22 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js = 419.72 kB 418.02 kB = 67.47 kB 67.29 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js = 419.70 kB 418.00 kB = 67.44 kB 67.27 kB
react-native/implementations/ReactFabric-prod.fb.js = 382.78 kB 381.22 kB = 66.40 kB 66.26 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js = 315.08 kB 313.79 kB = 55.24 kB 55.09 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js = 314.90 kB 313.61 kB = 55.21 kB 55.06 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js = 314.83 kB 313.54 kB = 55.19 kB 55.04 kB
facebook-www/ReactReconciler-prod.classic.js = 502.84 kB 500.76 kB = 80.27 kB 80.05 kB
facebook-www/ReactReconciler-prod.modern.js = 492.57 kB 490.50 kB = 78.67 kB 78.47 kB
facebook-www/ReactART-prod.classic.js = 386.51 kB 384.86 kB = 64.83 kB 64.67 kB
react-native/implementations/ReactFabric-prod.js = 360.76 kB 359.20 kB = 62.63 kB 62.51 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js = 393.54 kB 391.83 kB = 63.80 kB 63.64 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js = 393.51 kB 391.81 kB = 63.77 kB 63.62 kB
oss-stable/react-art/cjs/react-art.production.js = 302.82 kB 301.49 kB = 51.46 kB 51.34 kB
oss-stable-semver/react-art/cjs/react-art.production.js = 302.75 kB 301.42 kB = 51.44 kB 51.32 kB
facebook-www/ReactART-prod.modern.js = 376.57 kB 374.91 kB = 63.21 kB 63.06 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js = 577.46 kB 574.88 kB = 101.90 kB 101.57 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js = 571.52 kB 568.94 kB = 100.74 kB 100.41 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js = 552.17 kB 549.58 kB = 98.07 kB 97.75 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js = 549.16 kB 546.57 kB = 97.17 kB 96.85 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js = 549.03 kB 546.45 kB = 97.14 kB 96.82 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js = 546.66 kB 544.08 kB = 96.99 kB 96.68 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 518.75 kB 516.17 kB = 92.47 kB 92.15 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js = 518.63 kB 516.04 kB = 92.44 kB 92.12 kB

Generated by 🚫 dangerJS against 59a9547

}
}

function trackExitViewTransitions(deletion: Fiber): void {
Copy link
Collaborator Author

@sebmarkbage sebmarkbage Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming becomes interesting for these because they're in reverse. This will actually trigger the enter className since deletions are treated as "enter" in reverse. So I'm inclined to rename this to "trackEnterViewTransitions" and vice versa.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swapped it.

We need to be able to refer to these to measure and apply names to them
in future passes.
We do this so that we can collect any exiting pairs that will have
corresponding insertions.

I don't love that we do that here instead of a previous phase but it would
require another pass.
This makes it a bit easier not to mess it up at the cost of some more stack
allocation (which may cause overflow).
This is a bit simpler because it doesn't do cloning so it matches the
similar phases of ReactFiberCommitViewTransitions.

The "old" phase doesn't need resetting because all the clones get deleted
at the end.

We don't need to visit the insertions other than just the top level "enter"
because any pair will be visited by the deletion recursion.
This can reuse more from ReactFiberCommitViewTransitions because the
restore code is more broad than technically necessary.
Since we're applying transitions in reverse, all "insertions" in the newly
finished trees are actually "exits". All "deletions" are "enters".
Once we reach a HostComponent this will turn null and we can bail.
Since this is reversed.

Also adding a stub for the missing measureUpdateViewTransition.
Copy link
Member

@rickhanlonii rickhanlonii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized we'll probably need real browser tests to actually test view transitions

}
let child = deletion.child;
while (child !== null) {
if (child.tag === OffscreenComponent && child.memoizedState === null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most other places we use a switch, which can be nice because you can grep case OffscreenComponent to find all the special component handling cases

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One annoying thing about closure is that it doesn't change from switch to if, when the switch is unnecessary. The switch can have special meaning (e.g. most VMs turn it into a jump table for integers and sometimes even strings) but for simple cases it's just more bytes.

But yea, I think it's probably also worth while adding some more cases here like Portals even if it's just a comment that we've considered it.

I have a to do to make a pass over some of the enter/exit passes which doesn't check the OffscreenComponent but maybe they do need to.

@sebmarkbage
Copy link
Collaborator Author

I think a lot of the actually recursion of flags and stuff could be covered by unit tests. It just needs a ton of them to cover all the edge cases. It's just not worth writing them all until we have the rough structure working and getting closer to the final API.

Browser tests aren't actually that useful for most of the browser specific things anyway because they end up being things like flashes that don't show up in the assertions anyway, or you have to write it very specifically to assert a specific browser bug then you've already covered it anyway ideally with a comment for why.

@sebmarkbage sebmarkbage merged commit c4a3b92 into facebook:main Mar 14, 2025
194 checks passed
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
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)
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
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)
sebmarkbage added a commit that referenced this pull request Mar 14, 2025
… Phase (#32599)

Stacked on #32578.

We need to apply view-transition-names to the clones that we create in
the "old" phase for the ViewTransition boundaries that should activate.

Finding pairs is a little trickier than in
ReactFiberCommitViewTransitions. Normally we collect all name
"insertions" in the `accumulateSuspenseyCommit` phase before we even
commit. Then in the snapshot do we visit all "deletions" and since we
already collected all the insertions we know immediately if the deletion
had a pair and should therefore get a "name" assigned to activate the
boundary. For ReactFiberApplyGesture we need to assign names to
"insertions" since it's in reverse but we don't already have a map of
deletions. Therefore we need to first visit all deletions.

Instead of doing that in a completely separate pass, we instead visit
deletions in the same pass to find pairs. Since this is in the same pass
we might visit insertions before deletions or vice versa depending on
document order. However, we can deal with this by applying the name to
the insertion when we find the deletion if we've already made the clones
at that point.

Applying names to pure exits, updates or nested (relayout) is a bit more
straight-forward.
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
… Phase (#32599)

Stacked on #32578.

We need to apply view-transition-names to the clones that we create in
the "old" phase for the ViewTransition boundaries that should activate.

Finding pairs is a little trickier than in
ReactFiberCommitViewTransitions. Normally we collect all name
"insertions" in the `accumulateSuspenseyCommit` phase before we even
commit. Then in the snapshot do we visit all "deletions" and since we
already collected all the insertions we know immediately if the deletion
had a pair and should therefore get a "name" assigned to activate the
boundary. For ReactFiberApplyGesture we need to assign names to
"insertions" since it's in reverse but we don't already have a map of
deletions. Therefore we need to first visit all deletions.

Instead of doing that in a completely separate pass, we instead visit
deletions in the same pass to find pairs. Since this is in the same pass
we might visit insertions before deletions or vice versa depending on
document order. However, we can deal with this by applying the name to
the insertion when we find the deletion if we've already made the clones
at that point.

Applying names to pure exits, updates or nested (relayout) is a bit more
straight-forward.

DiffTrain build for [2e38573](2e38573)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants