mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
Commit the Gesture lane if a gesture ends closer to the target state (#35486)
Stacked on #35485. Before this PR, the `startGestureTransition` API would itself never commit its state. After the gesture releases it stops the animation in the next commit which just leaves the DOM tree in the original state. If there's an actual state change from the Action then that's committed as the new DOM tree. To avoid animating from the original state to the new state again, this is DOM without an animation. However, this means that you can't have the actual action committing be in a slightly different state and animate between the final gesture state and into the new action. Instead, we now actually keep the render tree around and commit it in the end. Basically we assume that if the Timeline was closer to the end then visually you're already there and we can commit into that state. Most of the time this will be at the actual end state when you release but if you have something else cancelling the gesture (e.g. `touchcancel`) it can still commit this state even though your gesture recognizer might not consider this an Action. I think this is ok and keeps it simple. When the gesture lane commits, it'll leave a Transition behind as work from the revert lanes on the Optimistic updates. This means that if you don't do anything in the Action this will cause another commit right after which reverts. This revert can animate the snap back. There's a few fixes needed in follow up PRs: - Fixed in #35487. ~To support unentangled Transitions we need to explicitly entangle the revert lane with the Action to avoid committing a revert followed by a forward instead of committing the forward entangled with the revert. This just works now since everything is entangled but won't work with #35392.~ - Fixed in #35510. ~This currently rerenders the gesture lane once before committing if it was already completed but blocked. We should be able to commit the already completed tree as is.~
This commit is contained in:
committed by
GitHub
parent
f0fbb0d199
commit
4028aaa50c
@@ -171,17 +171,20 @@ export default function Page({url, navigate}) {
|
||||
}}>
|
||||
<h1>{!show ? 'A' + counter : 'B'}</h1>
|
||||
</ViewTransition>
|
||||
{show ? (
|
||||
<div>
|
||||
{a}
|
||||
{b}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{b}
|
||||
{a}
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
// Using url instead of renderedUrl here lets us only update this on commit.
|
||||
url === '/?b' ? (
|
||||
<div>
|
||||
{a}
|
||||
{b}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{b}
|
||||
{a}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<ViewTransition>
|
||||
{show ? (
|
||||
<div>hello{exclamation}</div>
|
||||
|
||||
@@ -12,13 +12,10 @@ import type {GestureOptions} from 'shared/ReactTypes';
|
||||
import type {GestureTimeline, RunningViewTransition} from './ReactFiberConfig';
|
||||
import type {TransitionTypes} from 'react/src/ReactTransitionType';
|
||||
|
||||
import {
|
||||
GestureLane,
|
||||
includesBlockingLane,
|
||||
includesTransitionLane,
|
||||
} from './ReactFiberLane';
|
||||
import {GestureLane, markRootFinished, NoLane, NoLanes} from './ReactFiberLane';
|
||||
import {ensureRootIsScheduled} from './ReactFiberRootScheduler';
|
||||
import {getCurrentGestureOffset, stopViewTransition} from './ReactFiberConfig';
|
||||
import {pingGestureRoot, restartGestureRoot} from './ReactFiberWorkLoop';
|
||||
|
||||
// This type keeps track of any scheduled or active gestures.
|
||||
export type ScheduledGesture = {
|
||||
@@ -28,6 +25,7 @@ export type ScheduledGesture = {
|
||||
rangeEnd: number, // The percentage along the timeline where the "destination" state is reached.
|
||||
types: null | TransitionTypes, // Any addTransitionType call made during startGestureTransition.
|
||||
running: null | RunningViewTransition, // Used to cancel the running transition after we're done.
|
||||
committing: boolean, // If the gesture was released in a committed state and should actually commit.
|
||||
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
|
||||
next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root.
|
||||
};
|
||||
@@ -55,6 +53,7 @@ export function scheduleGesture(
|
||||
rangeEnd: 100, // Uninitialized
|
||||
types: null,
|
||||
running: null,
|
||||
committing: false,
|
||||
prev: prev,
|
||||
next: null,
|
||||
};
|
||||
@@ -122,77 +121,98 @@ export function cancelScheduledGesture(
|
||||
): void {
|
||||
gesture.count--;
|
||||
if (gesture.count === 0) {
|
||||
// Delete the scheduled gesture from the pending queue.
|
||||
deleteScheduledGesture(root, gesture);
|
||||
// If the end state is closer to the end than the beginning then we commit into the
|
||||
// end state before reverting back (or applying a new Transition).
|
||||
// Otherwise we just revert back and don't commit.
|
||||
let shouldCommit: boolean;
|
||||
const finalOffset = getCurrentGestureOffset(gesture.provider);
|
||||
const rangeStart = gesture.rangeStart;
|
||||
const rangeEnd = gesture.rangeEnd;
|
||||
if (rangeStart < rangeEnd) {
|
||||
shouldCommit = finalOffset > rangeStart + (rangeEnd - rangeStart) / 2;
|
||||
} else {
|
||||
shouldCommit = finalOffset < rangeEnd + (rangeStart - rangeEnd) / 2;
|
||||
}
|
||||
// TODO: If we're currently rendering this gesture, we need to restart the render
|
||||
// on a different gesture or cancel the render..
|
||||
// TODO: We might want to pause the View Transition at this point since you should
|
||||
// no longer be able to update the position of anything but it might be better to
|
||||
// just commit the gesture state.
|
||||
const runningTransition = gesture.running;
|
||||
if (runningTransition !== null) {
|
||||
const pendingLanesExcludingGestureLane = root.pendingLanes & ~GestureLane;
|
||||
if (
|
||||
includesBlockingLane(pendingLanesExcludingGestureLane) ||
|
||||
includesTransitionLane(pendingLanesExcludingGestureLane)
|
||||
) {
|
||||
// If we have pending work we schedule the gesture to be stopped at the next commit.
|
||||
// This ensures that we don't snap back to the previous state until we have
|
||||
// had a chance to commit any resulting updates.
|
||||
const existing = root.stoppingGestures;
|
||||
if (existing !== null) {
|
||||
gesture.next = existing;
|
||||
existing.prev = gesture;
|
||||
if (runningTransition !== null && shouldCommit) {
|
||||
// If we are going to commit this gesture in its to state, we need to wait to
|
||||
// stop it until it commits. We should now schedule a render at the gesture
|
||||
// lane to actually commit it.
|
||||
gesture.committing = true;
|
||||
if (root.pendingGestures === gesture) {
|
||||
// Ping the root given the new state. This is similar to pingSuspendedRoot.
|
||||
// This will either schedule the gesture lane to be committed possibly from its current state.
|
||||
pingGestureRoot(root);
|
||||
}
|
||||
} else {
|
||||
// If we're not going to commit this gesture we can stop the View Transition
|
||||
// right away and delete the scheduled gesture from the pending queue.
|
||||
if (gesture.prev === null) {
|
||||
if (root.pendingGestures === gesture) {
|
||||
// This was the currently rendering gesture.
|
||||
root.pendingGestures = gesture.next;
|
||||
let remainingLanes = root.pendingLanes;
|
||||
if (root.pendingGestures === null) {
|
||||
// Gestures don't clear their lanes while the gesture is still active but it
|
||||
// might not be scheduled to do any more renders and so we shouldn't schedule
|
||||
// any more gesture lane work until a new gesture is scheduled.
|
||||
remainingLanes &= ~GestureLane;
|
||||
}
|
||||
markRootFinished(
|
||||
root,
|
||||
GestureLane,
|
||||
remainingLanes,
|
||||
NoLane,
|
||||
NoLane,
|
||||
NoLanes,
|
||||
);
|
||||
// If we had a currently rendering gesture we need to now reset the gesture lane to
|
||||
// now render the next gesture or cancel if there's no more gestures in the queue.
|
||||
restartGestureRoot(root);
|
||||
}
|
||||
root.stoppingGestures = gesture;
|
||||
} else {
|
||||
gesture.running = null;
|
||||
// If there's no work scheduled so we can stop the View Transition right away.
|
||||
stopViewTransition(runningTransition);
|
||||
if (runningTransition !== null) {
|
||||
stopViewTransition(runningTransition);
|
||||
}
|
||||
} else {
|
||||
// This was not the current gesture so it doesn't affect the current render.
|
||||
gesture.prev.next = gesture.next;
|
||||
if (gesture.next !== null) {
|
||||
gesture.next.prev = gesture.prev;
|
||||
}
|
||||
gesture.prev = null;
|
||||
gesture.next = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteScheduledGesture(
|
||||
root: FiberRoot,
|
||||
gesture: ScheduledGesture,
|
||||
): void {
|
||||
if (gesture.prev === null) {
|
||||
if (root.pendingGestures === gesture) {
|
||||
root.pendingGestures = gesture.next;
|
||||
if (root.pendingGestures === null) {
|
||||
// Gestures don't clear their lanes while the gesture is still active but it
|
||||
// might not be scheduled to do any more renders and so we shouldn't schedule
|
||||
// any more gesture lane work until a new gesture is scheduled.
|
||||
root.pendingLanes &= ~GestureLane;
|
||||
}
|
||||
export function stopCommittedGesture(root: FiberRoot) {
|
||||
// The top was just committed. We can delete it from the queue
|
||||
// and stop its View Transition now.
|
||||
const committedGesture = root.pendingGestures;
|
||||
if (committedGesture !== null) {
|
||||
// Mark it as no longer committing and should no longer be included in rerenders.
|
||||
committedGesture.committing = false;
|
||||
const nextGesture = committedGesture.next;
|
||||
if (nextGesture === null) {
|
||||
// Gestures don't clear their lanes while the gesture is still active but it
|
||||
// might not be scheduled to do any more renders and so we shouldn't schedule
|
||||
// any more gesture lane work until a new gesture is scheduled.
|
||||
root.pendingLanes &= ~GestureLane;
|
||||
} else {
|
||||
nextGesture.prev = null;
|
||||
}
|
||||
if (root.stoppingGestures === gesture) {
|
||||
// This should not really happen the way we use it now but just in case we start.
|
||||
root.stoppingGestures = gesture.next;
|
||||
root.pendingGestures = nextGesture;
|
||||
const runningTransition = committedGesture.running;
|
||||
if (runningTransition !== null) {
|
||||
committedGesture.running = null;
|
||||
stopViewTransition(runningTransition);
|
||||
}
|
||||
} else {
|
||||
gesture.prev.next = gesture.next;
|
||||
if (gesture.next !== null) {
|
||||
gesture.next.prev = gesture.prev;
|
||||
}
|
||||
gesture.prev = null;
|
||||
gesture.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
export function stopCompletedGestures(root: FiberRoot) {
|
||||
let gesture = root.stoppingGestures;
|
||||
root.stoppingGestures = null;
|
||||
while (gesture !== null) {
|
||||
if (gesture.running !== null) {
|
||||
stopViewTransition(gesture.running);
|
||||
gesture.running = null;
|
||||
}
|
||||
const nextGesture = gesture.next;
|
||||
gesture.next = null;
|
||||
gesture.prev = null;
|
||||
gesture = nextGesture;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1384,7 +1384,7 @@ function updateReducerImpl<S, A>(
|
||||
// ScheduledGesture.
|
||||
const scheduledGesture = update.gesture;
|
||||
if (scheduledGesture !== null) {
|
||||
if (scheduledGesture.count === 0) {
|
||||
if (scheduledGesture.count === 0 && !scheduledGesture.committing) {
|
||||
// This gesture has already been cancelled. We can clean up this update.
|
||||
update = update.next;
|
||||
continue;
|
||||
|
||||
@@ -115,7 +115,6 @@ function FiberRootNode(
|
||||
|
||||
if (enableGestureTransition) {
|
||||
this.pendingGestures = null;
|
||||
this.stoppingGestures = null;
|
||||
this.gestureClone = null;
|
||||
}
|
||||
|
||||
|
||||
254
packages/react-reconciler/src/ReactFiberWorkLoop.js
vendored
254
packages/react-reconciler/src/ReactFiberWorkLoop.js
vendored
@@ -398,10 +398,7 @@ import {
|
||||
} from './ReactFiberRootScheduler';
|
||||
import {getMaskedContext, getUnmaskedContext} from './ReactFiberLegacyContext';
|
||||
import {logUncaughtError} from './ReactFiberErrorLogger';
|
||||
import {
|
||||
deleteScheduledGesture,
|
||||
stopCompletedGestures,
|
||||
} from './ReactFiberGestureScheduler';
|
||||
import {stopCommittedGesture} from './ReactFiberGestureScheduler';
|
||||
import {claimQueuedTransitionTypes} from './ReactFiberTransitionTypes';
|
||||
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
@@ -1407,7 +1404,7 @@ function finishConcurrentRender(
|
||||
|
||||
if (shouldForceFlushFallbacksInDEV()) {
|
||||
// We're inside an `act` scope. Commit immediately.
|
||||
commitRoot(
|
||||
completeRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
lanes,
|
||||
@@ -1417,6 +1414,7 @@ function finishConcurrentRender(
|
||||
workInProgressDeferredLane,
|
||||
workInProgressRootInterleavedUpdatedLanes,
|
||||
workInProgressSuspendedRetryLanes,
|
||||
workInProgressRootDidSkipSuspendedSiblings,
|
||||
exitStatus,
|
||||
null,
|
||||
null,
|
||||
@@ -1458,7 +1456,7 @@ function finishConcurrentRender(
|
||||
// run one after the other.
|
||||
pendingEffectsLanes = lanes;
|
||||
root.timeoutHandle = scheduleTimeout(
|
||||
commitRootWhenReady.bind(
|
||||
completeRootWhenReady.bind(
|
||||
null,
|
||||
root,
|
||||
finishedWork,
|
||||
@@ -1480,7 +1478,7 @@ function finishConcurrentRender(
|
||||
return;
|
||||
}
|
||||
}
|
||||
commitRootWhenReady(
|
||||
completeRootWhenReady(
|
||||
root,
|
||||
finishedWork,
|
||||
workInProgressRootRecoverableErrors,
|
||||
@@ -1499,7 +1497,7 @@ function finishConcurrentRender(
|
||||
}
|
||||
}
|
||||
|
||||
function commitRootWhenReady(
|
||||
function completeRootWhenReady(
|
||||
root: FiberRoot,
|
||||
finishedWork: Fiber,
|
||||
recoverableErrors: Array<CapturedValue<mixed>> | null,
|
||||
@@ -1540,12 +1538,18 @@ function commitRootWhenReady(
|
||||
// This will also track any newly added or appearing ViewTransition
|
||||
// components for the purposes of forming pairs.
|
||||
accumulateSuspenseyCommit(finishedWork, lanes, suspendedState);
|
||||
if (isViewTransitionEligible || isGestureTransition) {
|
||||
// If we're stopping gestures we don't have to wait for any pending
|
||||
// view transition. We'll stop it when we commit.
|
||||
if (!enableGestureTransition || root.stoppingGestures === null) {
|
||||
suspendOnActiveViewTransition(suspendedState, root.containerInfo);
|
||||
}
|
||||
if (
|
||||
isViewTransitionEligible ||
|
||||
(isGestureTransition &&
|
||||
root.pendingGestures !== null &&
|
||||
// If we're committing this gesture and it already has a View Transition
|
||||
// running, then we don't have to wait for that gesture. We'll stop it
|
||||
// when we commit.
|
||||
(root.pendingGestures.running === null ||
|
||||
!root.pendingGestures.committing))
|
||||
) {
|
||||
// Wait for any pending View Transition (including gestures) to finish.
|
||||
suspendOnActiveViewTransition(suspendedState, root.containerInfo);
|
||||
}
|
||||
// For timeouts we use the previous fallback commit for retries and
|
||||
// the start time of the transition for transitions. This offset
|
||||
@@ -1572,7 +1576,7 @@ function commitRootWhenReady(
|
||||
// root again.
|
||||
pendingEffectsLanes = lanes;
|
||||
root.cancelPendingCommit = schedulePendingCommit(
|
||||
commitRoot.bind(
|
||||
completeRoot.bind(
|
||||
null,
|
||||
root,
|
||||
finishedWork,
|
||||
@@ -1583,6 +1587,7 @@ function commitRootWhenReady(
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
didSkipSuspendedSiblings,
|
||||
exitStatus,
|
||||
suspendedState,
|
||||
enableProfilerTimer
|
||||
@@ -1599,7 +1604,7 @@ function commitRootWhenReady(
|
||||
}
|
||||
|
||||
// Otherwise, commit immediately.;
|
||||
commitRoot(
|
||||
completeRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
lanes,
|
||||
@@ -1609,6 +1614,7 @@ function commitRootWhenReady(
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
didSkipSuspendedSiblings,
|
||||
exitStatus,
|
||||
suspendedState,
|
||||
suspendedCommitReason,
|
||||
@@ -3420,7 +3426,7 @@ function unwindUnitOfWork(unitOfWork: Fiber, skipSiblings: boolean): void {
|
||||
workInProgress = null;
|
||||
}
|
||||
|
||||
function commitRoot(
|
||||
function completeRoot(
|
||||
root: FiberRoot,
|
||||
finishedWork: null | Fiber,
|
||||
lanes: Lanes,
|
||||
@@ -3430,6 +3436,7 @@ function commitRoot(
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
didSkipSuspendedSiblings: boolean,
|
||||
exitStatus: RootExitStatus,
|
||||
suspendedState: null | SuspendedState,
|
||||
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
|
||||
@@ -3496,9 +3503,9 @@ function commitRoot(
|
||||
markCommitStopped();
|
||||
}
|
||||
if (enableGestureTransition) {
|
||||
// Stop any gestures that were completed and is now being reverted.
|
||||
if (root.stoppingGestures !== null) {
|
||||
stopCompletedGestures(root);
|
||||
// Stop any gestures that were committed.
|
||||
if (isGestureRender(lanes)) {
|
||||
stopCommittedGesture(root);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -3520,10 +3527,95 @@ function commitRoot(
|
||||
);
|
||||
}
|
||||
|
||||
if (root === workInProgressRoot) {
|
||||
// We can reset these now that they are finished.
|
||||
workInProgressRoot = null;
|
||||
workInProgress = null;
|
||||
workInProgressRootRenderLanes = NoLanes;
|
||||
} else {
|
||||
// This indicates that the last root we worked on is not the same one that
|
||||
// we're committing now. This most commonly happens when a suspended root
|
||||
// times out.
|
||||
}
|
||||
|
||||
// workInProgressX might be overwritten, so we want
|
||||
// to store it in pendingPassiveX until they get processed
|
||||
// We need to pass this through as an argument to completeRoot
|
||||
// because workInProgressX might have changed between
|
||||
// the previous render and commit if we throttle the commit
|
||||
// with setTimeout
|
||||
pendingFinishedWork = finishedWork;
|
||||
pendingEffectsRoot = root;
|
||||
pendingEffectsLanes = lanes;
|
||||
pendingPassiveTransitions = transitions;
|
||||
pendingRecoverableErrors = recoverableErrors;
|
||||
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
|
||||
if (enableProfilerTimer) {
|
||||
pendingEffectsRenderEndTime = completedRenderEndTime;
|
||||
pendingSuspendedCommitReason = suspendedCommitReason;
|
||||
pendingDelayedCommitReason = IMMEDIATE_COMMIT;
|
||||
pendingSuspendedViewTransitionReason = null;
|
||||
}
|
||||
|
||||
if (enableGestureTransition && isGestureRender(lanes)) {
|
||||
const committingGesture = root.pendingGestures;
|
||||
if (committingGesture !== null && !committingGesture.committing) {
|
||||
// This gesture is not ready to commit yet. We'll mark it as suspended and
|
||||
// start a gesture transition which isn't really a side-effect. Then later
|
||||
// we might come back around to actually committing the root.
|
||||
const didAttemptEntireTree = !didSkipSuspendedSiblings;
|
||||
markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
|
||||
if (committingGesture.running === null) {
|
||||
applyGestureOnRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
recoverableErrors,
|
||||
suspendedState,
|
||||
enableProfilerTimer
|
||||
? suspendedCommitReason === null
|
||||
? completedRenderEndTime
|
||||
: commitStartTime
|
||||
: 0,
|
||||
);
|
||||
} else {
|
||||
// If we already have a gesture running, we don't update it in place
|
||||
// even if we have a new tree. Instead we wait until we can commit.
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not starting a gesture we now actually commit the root.
|
||||
commitRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
lanes,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
suspendedState,
|
||||
suspendedCommitReason,
|
||||
completedRenderEndTime,
|
||||
);
|
||||
}
|
||||
|
||||
function commitRoot(
|
||||
root: FiberRoot,
|
||||
finishedWork: Fiber,
|
||||
lanes: Lanes,
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
suspendedState: null | SuspendedState,
|
||||
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
|
||||
completedRenderEndTime: number, // Profiling-only
|
||||
) {
|
||||
// Check which lanes no longer have any work scheduled on them, and mark
|
||||
// those as finished.
|
||||
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
|
||||
|
||||
pendingEffectsRemainingLanes = remainingLanes;
|
||||
|
||||
// Make sure to account for lanes that were updated by a concurrent event
|
||||
// during the render phase; don't mark them as finished.
|
||||
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
|
||||
@@ -3553,53 +3645,6 @@ function commitRoot(
|
||||
// Reset this before firing side effects so we can detect recursive updates.
|
||||
didIncludeCommitPhaseUpdate = false;
|
||||
|
||||
if (root === workInProgressRoot) {
|
||||
// We can reset these now that they are finished.
|
||||
workInProgressRoot = null;
|
||||
workInProgress = null;
|
||||
workInProgressRootRenderLanes = NoLanes;
|
||||
} else {
|
||||
// This indicates that the last root we worked on is not the same one that
|
||||
// we're committing now. This most commonly happens when a suspended root
|
||||
// times out.
|
||||
}
|
||||
|
||||
// workInProgressX might be overwritten, so we want
|
||||
// to store it in pendingPassiveX until they get processed
|
||||
// We need to pass this through as an argument to commitRoot
|
||||
// because workInProgressX might have changed between
|
||||
// the previous render and commit if we throttle the commit
|
||||
// with setTimeout
|
||||
pendingFinishedWork = finishedWork;
|
||||
pendingEffectsRoot = root;
|
||||
pendingEffectsLanes = lanes;
|
||||
pendingEffectsRemainingLanes = remainingLanes;
|
||||
pendingPassiveTransitions = transitions;
|
||||
pendingRecoverableErrors = recoverableErrors;
|
||||
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
|
||||
if (enableProfilerTimer) {
|
||||
pendingEffectsRenderEndTime = completedRenderEndTime;
|
||||
pendingSuspendedCommitReason = suspendedCommitReason;
|
||||
pendingDelayedCommitReason = IMMEDIATE_COMMIT;
|
||||
pendingSuspendedViewTransitionReason = null;
|
||||
}
|
||||
|
||||
if (enableGestureTransition && isGestureRender(lanes)) {
|
||||
// This is a special kind of render that doesn't commit regular effects.
|
||||
commitGestureOnRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
recoverableErrors,
|
||||
suspendedState,
|
||||
enableProfilerTimer
|
||||
? suspendedCommitReason === null
|
||||
? completedRenderEndTime
|
||||
: commitStartTime
|
||||
: 0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are pending passive effects, schedule a callback to process them.
|
||||
// Do this as early as possible, so it is queued before anything else that
|
||||
// might get scheduled in the commit phase. (See #16714.)
|
||||
@@ -3712,20 +3757,19 @@ function commitRoot(
|
||||
}
|
||||
}
|
||||
|
||||
let willStartViewTransition = shouldStartViewTransition;
|
||||
if (enableGestureTransition) {
|
||||
// Stop any gestures that were completed and is now being committed.
|
||||
if (root.stoppingGestures !== null) {
|
||||
stopCompletedGestures(root);
|
||||
// If we are in the process of stopping some gesture we shouldn't start
|
||||
// a View Transition because that would start from the previous state to
|
||||
// the next state.
|
||||
willStartViewTransition = false;
|
||||
// Stop any gestures that were committed.
|
||||
if (isGestureRender(lanes)) {
|
||||
stopCommittedGesture(root);
|
||||
// Note that shouldStartViewTransition should always be false here because
|
||||
// committing a gesture never starts a new View Transition itself since it's
|
||||
// not a View Transition eligible lane. Only follow up Transition commits can
|
||||
// cause animate.
|
||||
}
|
||||
}
|
||||
|
||||
pendingEffectsStatus = PENDING_MUTATION_PHASE;
|
||||
if (enableViewTransition && willStartViewTransition) {
|
||||
if (enableViewTransition && shouldStartViewTransition) {
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
startAnimating(lanes);
|
||||
}
|
||||
@@ -4155,7 +4199,7 @@ function flushSpawnedWork(): void {
|
||||
flushPendingEffects();
|
||||
}
|
||||
|
||||
// Always call this before exiting `commitRoot`, to ensure that any
|
||||
// Always call this before exiting `completeRoot`, to ensure that any
|
||||
// additional work on this root is scheduled.
|
||||
ensureRootIsScheduled(root);
|
||||
|
||||
@@ -4244,7 +4288,7 @@ function flushSpawnedWork(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function commitGestureOnRoot(
|
||||
function applyGestureOnRoot(
|
||||
root: FiberRoot,
|
||||
finishedWork: Fiber,
|
||||
recoverableErrors: null | Array<CapturedValue<mixed>>,
|
||||
@@ -4259,7 +4303,6 @@ function commitGestureOnRoot(
|
||||
ensureRootIsScheduled(root);
|
||||
return;
|
||||
}
|
||||
deleteScheduledGesture(root, finishedGesture);
|
||||
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
startAnimating(pendingEffectsLanes);
|
||||
@@ -4468,7 +4511,7 @@ function flushPassiveEffects(): boolean {
|
||||
// flushPassiveEffectsImpl
|
||||
const root = pendingEffectsRoot;
|
||||
// Cache and clear the remaining lanes flag; it must be reset since this
|
||||
// method can be called from various places, not always from commitRoot
|
||||
// method can be called from various places, not always from completeRoot
|
||||
// where the remaining lanes are known
|
||||
const remainingLanes = pendingEffectsRemainingLanes;
|
||||
pendingEffectsRemainingLanes = NoLanes;
|
||||
@@ -4856,6 +4899,57 @@ function pingSuspendedRoot(
|
||||
ensureRootIsScheduled(root);
|
||||
}
|
||||
|
||||
export function pingGestureRoot(root: FiberRoot): void {
|
||||
const gesture = root.pendingGestures;
|
||||
if (gesture === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
root.cancelPendingCommit !== null &&
|
||||
isGestureRender(pendingEffectsLanes)
|
||||
) {
|
||||
// We have a suspended commit which we'll discard and rerender.
|
||||
// TODO: Just use this commit since it's ready to go.
|
||||
const cancelPendingCommit = root.cancelPendingCommit;
|
||||
if (cancelPendingCommit !== null) {
|
||||
root.cancelPendingCommit = null;
|
||||
cancelPendingCommit();
|
||||
}
|
||||
}
|
||||
// Ping it for rerender and commit.
|
||||
markRootPinged(root, GestureLane);
|
||||
ensureRootIsScheduled(root);
|
||||
}
|
||||
|
||||
export function restartGestureRoot(root: FiberRoot): void {
|
||||
if (
|
||||
workInProgressRoot === root &&
|
||||
isGestureRender(workInProgressRootRenderLanes)
|
||||
) {
|
||||
// The current render was a gesture but it's now defunct. We need to restart the render.
|
||||
if ((executionContext & RenderContext) === NoContext) {
|
||||
prepareFreshStack(root, NoLanes);
|
||||
} else {
|
||||
// TODO: Throw interruption when supported again.
|
||||
}
|
||||
} else if (
|
||||
root.cancelPendingCommit !== null &&
|
||||
isGestureRender(pendingEffectsLanes)
|
||||
) {
|
||||
// We have a suspended commit which we'll discard.
|
||||
const cancelPendingCommit = root.cancelPendingCommit;
|
||||
if (cancelPendingCommit !== null) {
|
||||
root.cancelPendingCommit = null;
|
||||
cancelPendingCommit();
|
||||
}
|
||||
}
|
||||
if (root.pendingGestures !== null) {
|
||||
// We still have gestures to work on. Let's schedule a restart.
|
||||
markRootPinged(root, GestureLane);
|
||||
}
|
||||
ensureRootIsScheduled(root);
|
||||
}
|
||||
|
||||
function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
|
||||
// The boundary fiber (a Suspense component or SuspenseList component)
|
||||
// previously was rendered in its fallback state. One of the promises that
|
||||
|
||||
@@ -292,7 +292,6 @@ type BaseFiberRootProperties = {
|
||||
transitionTypes: null | TransitionTypes, // TODO: Make this a LaneMap.
|
||||
// enableGestureTransition only
|
||||
pendingGestures: null | ScheduledGesture,
|
||||
stoppingGestures: null | ScheduledGesture,
|
||||
gestureClone: null | Instance,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user