Skip to content

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Feb 22, 2025

This adds a ReactFiberApplyGesture which is basically intended to be a fork of the phases in ReactFiberCommitWork except for the fake commit that useSwipeTransition does. So far none of the phases are actually implemented yet. This is just the scaffolding around them so I can fill them in later.

The important bit is that we call startViewTransition (via the startGestureTransition Config) when a gesture starts. We add a paused animation to prevent the transition from committing (even if the ScrollTimeline goes to 100%). This also locks the documents so that we can't commit any other Transitions until it completes.

When the gesture completes (scroll end) then we stop the gesture View Transition. If there's no new work scheduled we do that immediately but if there was any new work already scheduled, then we assume that this will potentially commit the new state. So we wait for that to finish. This lets us lock the animation in its state instead of snapping back and then applying the real update.

Using this technique we can't actually run a View Transition from the current state to the actual committed state because it would snap back to the beginning and then run the View Transition from there. Therefore any new commit needs to skip View Transitions even if it should've technically animated to that state. We assume that the new state is the same as the optimistic state you already swiped to. An alternative to this technique could be to commit the optimistic state when we cancel and then apply any new updates o top of that. I might explore that in the future.

Regardless it's important that the action associated with the swipe schedules some work before we cancel. Otherwise it risks reverting first. So I had to update this in the fixture.

@react-sizebot
Copy link

react-sizebot commented Feb 22, 2025

Comparing: 5eb20b3...efca3e9

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 +0.05% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.49% 515.73 kB 518.24 kB +0.36% 92.10 kB 92.43 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.87% 565.65 kB 570.57 kB +0.69% 100.87 kB 101.56 kB
facebook-www/ReactDOM-prod.classic.js +0.21% 636.71 kB 638.06 kB +0.18% 112.07 kB 112.28 kB
facebook-www/ReactDOM-prod.modern.js +0.22% 627.03 kB 628.38 kB +0.19% 110.48 kB 110.70 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 +0.95% 434.22 kB 438.36 kB +0.72% 70.26 kB 70.77 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer.production.js +0.88% 36.67 kB 36.99 kB +0.82% 6.86 kB 6.92 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer.production.js +0.88% 36.69 kB 37.02 kB +0.84% 6.89 kB 6.95 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer.production.js +0.88% 36.70 kB 37.02 kB +0.83% 6.90 kB 6.95 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.88% 36.80 kB 37.12 kB +0.81% 6.88 kB 6.94 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.88% 36.82 kB 37.14 kB +0.82% 6.91 kB 6.97 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.88% 36.83 kB 37.15 kB +0.82% 6.91 kB 6.97 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.87% 565.65 kB 570.57 kB +0.69% 100.87 kB 101.56 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +0.85% 487.18 kB 491.32 kB +0.66% 78.13 kB 78.64 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +0.85% 580.38 kB 585.30 kB +0.65% 104.45 kB 105.12 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer.development.js +0.84% 40.87 kB 41.22 kB +0.78% 7.46 kB 7.51 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer.development.js +0.84% 40.90 kB 41.24 kB +0.80% 7.48 kB 7.54 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer.development.js +0.84% 40.90 kB 41.25 kB +0.80% 7.49 kB 7.55 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.84% 41.01 kB 41.36 kB +0.78% 7.47 kB 7.53 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.84% 41.04 kB 41.38 kB +0.80% 7.50 kB 7.56 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.84% 41.04 kB 41.39 kB +0.80% 7.51 kB 7.57 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +0.78% 621.19 kB 626.05 kB +0.65% 109.59 kB 110.30 kB
oss-experimental/react-art/cjs/react-art.production.js +0.69% 324.18 kB 326.42 kB +0.53% 55.36 kB 55.65 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.61% 724.01 kB 728.43 kB +0.46% 114.45 kB 114.98 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +0.53% 1,040.47 kB 1,045.95 kB +0.41% 174.46 kB 175.17 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +0.52% 1,056.87 kB 1,062.35 kB +0.42% 177.30 kB 178.03 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.52% 1,057.39 kB 1,062.87 kB +0.40% 178.19 kB 178.90 kB
oss-stable-semver/react-dom/cjs/react-dom-client.production.js +0.49% 515.60 kB 518.12 kB +0.36% 92.07 kB 92.41 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.49% 515.73 kB 518.24 kB +0.36% 92.10 kB 92.43 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js +0.47% 540.76 kB 543.28 kB +0.34% 96.09 kB 96.42 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js +0.46% 546.01 kB 548.52 kB +0.36% 96.77 kB 97.11 kB
oss-stable/react-dom/cjs/react-dom-profiling.profiling.js +0.46% 546.13 kB 548.65 kB +0.35% 96.80 kB 97.14 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js +0.46% 546.27 kB 548.78 kB +0.34% 97.17 kB 97.50 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js +0.44% 565.62 kB 568.14 kB +0.35% 99.83 kB 100.18 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js +0.44% 571.56 kB 574.08 kB +0.34% 100.99 kB 101.34 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.js +0.43% 390.96 kB 392.65 kB +0.20% 63.47 kB 63.60 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.js +0.43% 390.99 kB 392.67 kB +0.20% 63.49 kB 63.62 kB
oss-experimental/react-art/cjs/react-art.development.js +0.43% 617.81 kB 620.46 kB +0.23% 98.47 kB 98.70 kB
react-native/implementations/ReactFabric-prod.js +0.42% 358.69 kB 360.20 kB +0.15% 62.42 kB 62.52 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.js +0.40% 417.15 kB 418.84 kB +0.24% 67.12 kB 67.28 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.js +0.40% 417.17 kB 418.86 kB +0.24% 67.14 kB 67.31 kB
react-native/implementations/ReactFabric-prod.fb.js +0.40% 380.40 kB 381.91 kB +0.20% 66.13 kB 66.26 kB
react-native/implementations/ReactFabric-profiling.js +0.39% 383.92 kB 385.44 kB +0.16% 66.13 kB 66.24 kB
oss-stable-semver/react-art/cjs/react-art.production.js +0.38% 301.10 kB 302.23 kB +0.16% 51.33 kB 51.41 kB
oss-stable/react-art/cjs/react-art.production.js +0.38% 301.17 kB 302.31 kB +0.16% 51.36 kB 51.44 kB
react-native/implementations/ReactFabric-profiling.fb.js +0.37% 405.91 kB 407.42 kB +0.16% 69.98 kB 70.09 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.js +0.37% 312.65 kB 313.80 kB +0.22% 54.83 kB 54.95 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.js +0.37% 312.72 kB 313.88 kB +0.22% 54.85 kB 54.98 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.js +0.37% 312.90 kB 314.05 kB +0.22% 54.89 kB 55.01 kB
facebook-www/ReactReconciler-prod.modern.js +0.34% 485.47 kB 487.13 kB +0.22% 77.67 kB 77.84 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js +0.34% 333.40 kB 334.54 kB +0.14% 58.14 kB 58.22 kB
facebook-www/ReactReconciler-prod.classic.js +0.33% 495.74 kB 497.39 kB +0.23% 79.28 kB 79.46 kB
oss-stable-semver/react-dom/cjs/react-dom-client.development.js +0.33% 949.48 kB 952.60 kB +0.24% 160.42 kB 160.80 kB
oss-stable/react-dom/cjs/react-dom-client.development.js +0.33% 949.61 kB 952.73 kB +0.24% 160.44 kB 160.83 kB
oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js +0.32% 965.92 kB 969.04 kB +0.23% 163.25 kB 163.63 kB
oss-stable/react-dom/cjs/react-dom-profiling.development.js +0.32% 966.05 kB 969.17 kB +0.23% 163.28 kB 163.66 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js +0.32% 355.58 kB 356.71 kB +0.14% 61.31 kB 61.39 kB
facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js +0.32% 990.42 kB 993.54 kB +0.21% 166.49 kB 166.85 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.31% 644.66 kB 646.66 kB +0.18% 102.94 kB 103.13 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.31% 644.68 kB 646.69 kB +0.18% 102.97 kB 103.15 kB
facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js +0.31% 1,006.75 kB 1,009.87 kB +0.21% 169.31 kB 169.68 kB
react-native/implementations/ReactNativeRenderer-prod.js +0.31% 366.13 kB 367.26 kB +0.13% 63.59 kB 63.67 kB
facebook-www/ReactART-prod.modern.js +0.30% 373.50 kB 374.64 kB +0.13% 62.89 kB 62.98 kB
react-native/implementations/ReactFabric-dev.js +0.30% 639.97 kB 641.90 kB +0.15% 104.41 kB 104.56 kB
facebook-www/ReactART-prod.classic.js +0.30% 383.45 kB 384.59 kB +0.15% 64.49 kB 64.58 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js +0.30% 384.30 kB 385.44 kB +0.16% 66.81 kB 66.92 kB
react-native/implementations/ReactNativeRenderer-profiling.js +0.29% 391.48 kB 392.61 kB +0.13% 67.39 kB 67.48 kB
react-native/implementations/ReactFabric-dev.fb.js +0.29% 670.06 kB 671.99 kB +0.16% 108.98 kB 109.15 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +0.28% 556.51 kB 558.06 kB +0.17% 90.50 kB 90.65 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +0.28% 556.54 kB 558.09 kB +0.17% 90.51 kB 90.67 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +0.28% 556.59 kB 558.14 kB +0.17% 90.52 kB 90.68 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js +0.28% 409.78 kB 410.92 kB +0.12% 70.64 kB 70.73 kB
oss-stable-semver/react-art/cjs/react-art.development.js +0.27% 559.10 kB 560.63 kB +0.16% 90.10 kB 90.24 kB
oss-stable/react-art/cjs/react-art.development.js +0.27% 559.17 kB 560.70 kB +0.16% 90.12 kB 90.26 kB
facebook-www/ReactTestRenderer-dev.modern.js +0.27% 574.47 kB 576.00 kB +0.12% 93.60 kB 93.72 kB
facebook-www/ReactTestRenderer-dev.classic.js +0.27% 574.47 kB 576.00 kB +0.12% 93.60 kB 93.72 kB
facebook-www/ReactReconciler-dev.modern.js +0.26% 760.27 kB 762.24 kB +0.16% 119.21 kB 119.40 kB
facebook-www/ReactReconciler-dev.classic.js +0.26% 769.47 kB 771.45 kB +0.15% 120.88 kB 121.06 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +0.26% 597.46 kB 599.00 kB +0.16% 96.40 kB 96.55 kB
react-native/implementations/ReactNativeRenderer-dev.js +0.24% 648.22 kB 649.75 kB +0.15% 105.79 kB 105.94 kB
facebook-www/ReactART-dev.modern.js +0.23% 655.82 kB 657.35 kB +0.13% 103.75 kB 103.89 kB
facebook-www/ReactART-dev.classic.js +0.23% 665.31 kB 666.84 kB +0.13% 105.59 kB 105.73 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +0.23% 674.92 kB 676.45 kB +0.14% 109.85 kB 110.00 kB
facebook-www/ReactDOM-prod.modern.js +0.22% 627.03 kB 628.38 kB +0.19% 110.48 kB 110.70 kB
facebook-www/ReactDOM-prod.classic.js +0.21% 636.71 kB 638.06 kB +0.18% 112.07 kB 112.28 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.21% 641.75 kB 643.10 kB +0.18% 114.20 kB 114.41 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.21% 651.43 kB 652.78 kB +0.17% 115.79 kB 115.98 kB
facebook-www/ReactDOM-profiling.modern.js +0.21% 654.42 kB 655.77 kB +0.16% 114.40 kB 114.58 kB
facebook-www/ReactDOM-profiling.classic.js +0.20% 664.15 kB 665.50 kB +0.16% 115.99 kB 116.18 kB

Generated by 🚫 dangerJS against 4c5d858

);
// We must have already cancelled this gesture before we had a chance to
// render it. Let's schedule work on the next set of lanes.
ensureRootIsScheduled(root);
Copy link
Member

Choose a reason for hiding this comment

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

What work is scheduled here if the gesture was never rendered?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There can be lower priority work like Idle or Transition that are now unblocked and can start rendering. Those may not have a callback yet.

@@ -3879,6 +3998,8 @@ function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {

export function flushPendingEffects(wasDelayedCommit?: boolean): boolean {
// Returns whether passive effects were flushed.
flushGestureMutations();
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to call this separately if its also called at the start of flushGestureAnimations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not really today but this may not keep being the case that flushGestureAnimations calls it. I think these will change a bit so that flushGestureAnimations might be able to be skipped or flushed out of band from other flushes. This just ensures that each step is called regardless.

This phase also collects the appearing side of pairs.
Essentially ReactFiberApplyGesture works as a fork of ReactFiberCommitWork
which has its own phase that don't really commit but emulate what a commit
into the destination state would look like before getting reverted.

This is all done within a startViewTransition.

To handle any sync flushing that happens in the gaps of startViewTransition
callbacks, we use the same pendingEffectsStatus strategy except with a
different sequence. If flushPendingEffects is called early it just goes
through it as normal. However it probably should just abort the transition
instead.
Instead of relying on the protocol working for both in every environment.

This lets us specialize this for gestures. Notably we don't wait for pending
navigations or fonts in this mode. Any added fonts would be added before
we have a chance to take a snapshot so we don't really have that option
here. It's probably fine because most cases for swipe are backwards but
it can potentially be an issue swiping into a new page that has new fonts.
I'm about to add another queue.
If we have any scheduled work up until that point, we wait for it to commit
first so that if the new commit is the same as the target we don't snap
back first.
We need this for the scroll to keep responding to events.
This ensures that we have already scheduled some work before we cancel.
That way we know that this work will be the one to commit the new state.
Otherwise we might cancel too soon.
If we cancel a gesture before it has a chance to commit we still leave the lane In this case we need to just skip it.
It won't block this transition from completing but it'll stick around to
future transitions.
@sebmarkbage sebmarkbage merged commit 3607f48 into facebook:main Feb 27, 2025
194 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 27, 2025
This adds a `ReactFiberApplyGesture` which is basically intended to be a
fork of the phases in `ReactFiberCommitWork` except for the fake commit
that `useSwipeTransition` does. So far none of the phases are actually
implemented yet. This is just the scaffolding around them so I can fill
them in later.

The important bit is that we call `startViewTransition` (via the
`startGestureTransition` Config) when a gesture starts. We add a paused
animation to prevent the transition from committing (even if the
ScrollTimeline goes to 100%). This also locks the documents so that we
can't commit any other Transitions until it completes.

When the gesture completes (scroll end) then we stop the gesture View
Transition. If there's no new work scheduled we do that immediately but
if there was any new work already scheduled, then we assume that this
will potentially commit the new state. So we wait for that to finish.
This lets us lock the animation in its state instead of snapping back
and then applying the real update.

Using this technique we can't actually run a View Transition from the
current state to the actual committed state because it would snap back
to the beginning and then run the View Transition from there. Therefore
any new commit needs to skip View Transitions even if it should've
technically animated to that state. We assume that the new state is the
same as the optimistic state you already swiped to. An alternative to
this technique could be to commit the optimistic state when we cancel
and then apply any new updates o top of that. I might explore that in
the future.

Regardless it's important that the `action` associated with the swipe
schedules some work before we cancel. Otherwise it risks reverting
first. So I had to update this in the fixture.

DiffTrain build for [3607f48](3607f48)
github-actions bot pushed a commit that referenced this pull request Feb 27, 2025
This adds a `ReactFiberApplyGesture` which is basically intended to be a
fork of the phases in `ReactFiberCommitWork` except for the fake commit
that `useSwipeTransition` does. So far none of the phases are actually
implemented yet. This is just the scaffolding around them so I can fill
them in later.

The important bit is that we call `startViewTransition` (via the
`startGestureTransition` Config) when a gesture starts. We add a paused
animation to prevent the transition from committing (even if the
ScrollTimeline goes to 100%). This also locks the documents so that we
can't commit any other Transitions until it completes.

When the gesture completes (scroll end) then we stop the gesture View
Transition. If there's no new work scheduled we do that immediately but
if there was any new work already scheduled, then we assume that this
will potentially commit the new state. So we wait for that to finish.
This lets us lock the animation in its state instead of snapping back
and then applying the real update.

Using this technique we can't actually run a View Transition from the
current state to the actual committed state because it would snap back
to the beginning and then run the View Transition from there. Therefore
any new commit needs to skip View Transitions even if it should've
technically animated to that state. We assume that the new state is the
same as the optimistic state you already swiped to. An alternative to
this technique could be to commit the optimistic state when we cancel
and then apply any new updates o top of that. I might explore that in
the future.

Regardless it's important that the `action` associated with the swipe
schedules some work before we cancel. Otherwise it risks reverting
first. So I had to update this in the fixture.

DiffTrain build for [3607f48](3607f48)
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