mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Log the time until the Animation finishes as "Animating" (#34538)
Stacked on #34522. <img width="1025" height="200" alt="Screenshot 2025-09-19 at 6 37 28 PM" src="https://github.com/user-attachments/assets/f25900f6-6503-48b1-876d-bd6697a29c6f" /> We already cover the time between "Starting Animation" and "Remaining Effects" as "Animating". However, if the effects are forced then we can still be animating after that. This fills in that gap. This also fills in the gap if another render starts before the animation finishes on the same track. It'll mark the blank space between the previous render finishing and the next render starting as "Animating". This should correspond roughly to the native "Animations" track.
This commit is contained in:
committed by
GitHub
parent
b204edda3a
commit
b4fe1e6c7e
@@ -2100,6 +2100,7 @@ export function startViewTransition(
|
||||
passiveCallback: () => mixed,
|
||||
errorCallback: mixed => void,
|
||||
blockedCallback: string => void, // Profiling-only
|
||||
finishedAnimation: () => void, // Profiling-only
|
||||
): null | RunningViewTransition {
|
||||
const ownerDocument: Document =
|
||||
rootContainer.nodeType === DOCUMENT_NODE
|
||||
@@ -2302,6 +2303,9 @@ export function startViewTransition(
|
||||
// $FlowFixMe[prop-missing]
|
||||
ownerDocument.__reactViewTransition = null;
|
||||
}
|
||||
if (enableProfilerTimer) {
|
||||
finishedAnimation();
|
||||
}
|
||||
passiveCallback();
|
||||
});
|
||||
return transition;
|
||||
|
||||
@@ -674,6 +674,7 @@ export function startViewTransition(
|
||||
passiveCallback: () => mixed,
|
||||
errorCallback: mixed => void,
|
||||
blockedCallback: string => void, // Profiling-only
|
||||
finishedAnimation: () => void, // Profiling-only
|
||||
): null | RunningViewTransition {
|
||||
mutationCallback();
|
||||
layoutCallback();
|
||||
|
||||
@@ -860,6 +860,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
spawnedWorkCallback: () => void,
|
||||
passiveCallback: () => mixed,
|
||||
errorCallback: mixed => void,
|
||||
blockedCallback: string => void, // Profiling-only
|
||||
finishedAnimation: () => void, // Profiling-only
|
||||
): null | RunningViewTransition {
|
||||
mutationCallback();
|
||||
layoutCallback();
|
||||
|
||||
+18
@@ -73,6 +73,8 @@ const TransitionLane12: Lane = /* */ 0b0000000000010000000
|
||||
const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;
|
||||
const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;
|
||||
|
||||
export const SomeTransitionLane: Lane = TransitionLane1;
|
||||
|
||||
const TransitionUpdateLanes =
|
||||
TransitionLane1 |
|
||||
TransitionLane2 |
|
||||
@@ -633,6 +635,22 @@ export function includesTransitionLane(lanes: Lanes): boolean {
|
||||
return (lanes & TransitionLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function includesRetryLane(lanes: Lanes): boolean {
|
||||
return (lanes & RetryLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function includesIdleGroupLanes(lanes: Lanes): boolean {
|
||||
return (
|
||||
(lanes &
|
||||
(SelectiveHydrationLane |
|
||||
IdleHydrationLane |
|
||||
IdleLane |
|
||||
OffscreenLane |
|
||||
DeferredLane)) !==
|
||||
NoLanes
|
||||
);
|
||||
}
|
||||
|
||||
export function includesOnlyHydrationLanes(lanes: Lanes): boolean {
|
||||
return (lanes & HydrationLanes) === lanes;
|
||||
}
|
||||
|
||||
@@ -1458,7 +1458,7 @@ export function logAnimatingPhase(
|
||||
endTime,
|
||||
currentTrack,
|
||||
LANES_TRACK_GROUP,
|
||||
'secondary',
|
||||
'secondary-dark',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -1468,7 +1468,7 @@ export function logAnimatingPhase(
|
||||
endTime,
|
||||
currentTrack,
|
||||
LANES_TRACK_GROUP,
|
||||
'secondary',
|
||||
'secondary-dark',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+137
-17
@@ -179,6 +179,8 @@ import {
|
||||
includesOnlyTransitions,
|
||||
includesBlockingLane,
|
||||
includesTransitionLane,
|
||||
includesRetryLane,
|
||||
includesIdleGroupLanes,
|
||||
includesExpiredLane,
|
||||
getNextLanes,
|
||||
getEntangledLanes,
|
||||
@@ -201,6 +203,9 @@ import {
|
||||
includesOnlyViewTransitionEligibleLanes,
|
||||
isGestureRender,
|
||||
GestureLane,
|
||||
SomeTransitionLane,
|
||||
SomeRetryLane,
|
||||
IdleLane,
|
||||
} from './ReactFiberLane';
|
||||
import {
|
||||
DiscreteEventPriority,
|
||||
@@ -292,6 +297,8 @@ import {
|
||||
clearTransitionTimers,
|
||||
clampBlockingTimers,
|
||||
clampTransitionTimers,
|
||||
clampRetryTimers,
|
||||
clampIdleTimers,
|
||||
markNestedUpdateScheduled,
|
||||
renderStartTime,
|
||||
commitStartTime,
|
||||
@@ -312,6 +319,11 @@ import {
|
||||
resetCommitErrors,
|
||||
PINGED_UPDATE,
|
||||
SPAWNED_UPDATE,
|
||||
startAnimating,
|
||||
stopAnimating,
|
||||
animatingLanes,
|
||||
retryClampTime,
|
||||
idleClampTime,
|
||||
} from './ReactProfilerTimer';
|
||||
|
||||
// DEV stuff
|
||||
@@ -1426,6 +1438,7 @@ function finishConcurrentRender(
|
||||
// immediately, wait for more data to arrive.
|
||||
// TODO: Combine retry throttling with Suspensey commits. Right now they
|
||||
// run one after the other.
|
||||
pendingEffectsLanes = lanes;
|
||||
root.timeoutHandle = scheduleTimeout(
|
||||
commitRootWhenReady.bind(
|
||||
null,
|
||||
@@ -1539,6 +1552,7 @@ function commitRootWhenReady(
|
||||
// Not yet ready to commit. Delay the commit until the renderer notifies
|
||||
// us that it's ready. This will be canceled if we start work on the
|
||||
// root again.
|
||||
pendingEffectsLanes = lanes;
|
||||
root.cancelPendingCommit = schedulePendingCommit(
|
||||
commitRoot.bind(
|
||||
null,
|
||||
@@ -1889,6 +1903,12 @@ function finalizeRender(lanes: Lanes, finalizationTime: number): void {
|
||||
if (includesTransitionLane(lanes)) {
|
||||
clampTransitionTimers(finalizationTime);
|
||||
}
|
||||
if (includesRetryLane(lanes)) {
|
||||
clampRetryTimers(finalizationTime);
|
||||
}
|
||||
if (includesIdleGroupLanes(lanes)) {
|
||||
clampIdleTimers(finalizationTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1939,6 +1959,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
}
|
||||
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
|
||||
}
|
||||
const previousUpdateTask = workInProgressUpdateTask;
|
||||
|
||||
workInProgressUpdateTask = null;
|
||||
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
|
||||
@@ -1951,18 +1972,30 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
|
||||
? blockingClampTime
|
||||
: blockingEventTime;
|
||||
const clampedRenderStartTime = // Clamp the suspended time to the first event/update.
|
||||
clampedEventTime >= 0
|
||||
? clampedEventTime
|
||||
: clampedUpdateTime >= 0
|
||||
? clampedUpdateTime
|
||||
: renderStartTime;
|
||||
if (blockingSuspendedTime >= 0) {
|
||||
setCurrentTrackFromLanes(lanes);
|
||||
setCurrentTrackFromLanes(SyncLane);
|
||||
logSuspendedWithDelayPhase(
|
||||
blockingSuspendedTime,
|
||||
// Clamp the suspended time to the first event/update.
|
||||
clampedEventTime >= 0
|
||||
? clampedEventTime
|
||||
: clampedUpdateTime >= 0
|
||||
? clampedUpdateTime
|
||||
: renderStartTime,
|
||||
clampedRenderStartTime,
|
||||
lanes,
|
||||
workInProgressUpdateTask,
|
||||
previousUpdateTask,
|
||||
);
|
||||
} else if (
|
||||
includesSyncLane(animatingLanes) ||
|
||||
includesBlockingLane(animatingLanes)
|
||||
) {
|
||||
// If this lane is still animating, log the time from previous render finishing to now as animating.
|
||||
setCurrentTrackFromLanes(SyncLane);
|
||||
logAnimatingPhase(
|
||||
blockingClampTime,
|
||||
clampedRenderStartTime,
|
||||
previousUpdateTask,
|
||||
);
|
||||
}
|
||||
logBlockingStart(
|
||||
@@ -1994,19 +2027,29 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
|
||||
? transitionClampTime
|
||||
: transitionEventTime;
|
||||
const clampedRenderStartTime =
|
||||
// Clamp the suspended time to the first event/update.
|
||||
clampedEventTime >= 0
|
||||
? clampedEventTime
|
||||
: clampedUpdateTime >= 0
|
||||
? clampedUpdateTime
|
||||
: renderStartTime;
|
||||
if (transitionSuspendedTime >= 0) {
|
||||
setCurrentTrackFromLanes(lanes);
|
||||
setCurrentTrackFromLanes(SomeTransitionLane);
|
||||
logSuspendedWithDelayPhase(
|
||||
transitionSuspendedTime,
|
||||
// Clamp the suspended time to the first event/update.
|
||||
clampedEventTime >= 0
|
||||
? clampedEventTime
|
||||
: clampedUpdateTime >= 0
|
||||
? clampedUpdateTime
|
||||
: renderStartTime,
|
||||
clampedRenderStartTime,
|
||||
lanes,
|
||||
workInProgressUpdateTask,
|
||||
);
|
||||
} else if (includesTransitionLane(animatingLanes)) {
|
||||
// If this lane is still animating, log the time from previous render finishing to now as animating.
|
||||
setCurrentTrackFromLanes(SomeTransitionLane);
|
||||
logAnimatingPhase(
|
||||
transitionClampTime,
|
||||
clampedRenderStartTime,
|
||||
previousUpdateTask,
|
||||
);
|
||||
}
|
||||
logTransitionStart(
|
||||
clampedStartTime,
|
||||
@@ -2022,6 +2065,20 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
);
|
||||
clearTransitionTimers();
|
||||
}
|
||||
if (includesRetryLane(lanes)) {
|
||||
if (includesRetryLane(animatingLanes)) {
|
||||
// If this lane is still animating, log the time from previous render finishing to now as animating.
|
||||
setCurrentTrackFromLanes(SomeRetryLane);
|
||||
logAnimatingPhase(retryClampTime, renderStartTime, previousUpdateTask);
|
||||
}
|
||||
}
|
||||
if (includesIdleGroupLanes(lanes)) {
|
||||
if (includesIdleGroupLanes(animatingLanes)) {
|
||||
// If this lane is still animating, log the time from previous render finishing to now as animating.
|
||||
setCurrentTrackFromLanes(IdleLane);
|
||||
logAnimatingPhase(idleClampTime, renderStartTime, previousUpdateTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const timeoutHandle = root.timeoutHandle;
|
||||
@@ -2038,6 +2095,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
cancelPendingCommit();
|
||||
}
|
||||
|
||||
pendingEffectsLanes = NoLanes;
|
||||
|
||||
resetWorkInProgressStack();
|
||||
workInProgressRoot = root;
|
||||
const rootWorkInProgress = createWorkInProgress(root.current, null);
|
||||
@@ -3592,6 +3651,9 @@ function commitRoot(
|
||||
|
||||
pendingEffectsStatus = PENDING_MUTATION_PHASE;
|
||||
if (enableViewTransition && willStartViewTransition) {
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
startAnimating(lanes);
|
||||
}
|
||||
pendingViewTransition = startViewTransition(
|
||||
suspendedState,
|
||||
root.containerInfo,
|
||||
@@ -3603,6 +3665,15 @@ function commitRoot(
|
||||
flushPassiveEffects,
|
||||
reportViewTransitionError,
|
||||
enableProfilerTimer ? suspendedViewTransition : (null: any),
|
||||
enableProfilerTimer
|
||||
? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
|
||||
finishedViewTransition.bind(
|
||||
null,
|
||||
lanes,
|
||||
// TODO: Use a ViewTransition Task
|
||||
__DEV__ ? workInProgressUpdateTask : null,
|
||||
)
|
||||
: (null: any),
|
||||
);
|
||||
} else {
|
||||
// Flush synchronously.
|
||||
@@ -3634,13 +3705,62 @@ function suspendedViewTransition(reason: string): void {
|
||||
commitEndTime,
|
||||
commitErrors,
|
||||
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
|
||||
workInProgressUpdateTask,
|
||||
workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase.
|
||||
);
|
||||
pendingSuspendedViewTransitionReason = reason;
|
||||
pendingSuspendedCommitReason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
function finishedViewTransition(
|
||||
lanes: Lanes,
|
||||
task: null | ConsoleTask, // DEV-only
|
||||
): void {
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
if ((animatingLanes & lanes) === NoLanes) {
|
||||
// Was already stopped by some other action or maybe other root.
|
||||
return;
|
||||
}
|
||||
stopAnimating(lanes);
|
||||
// If an affected track isn't in the middle of rendering or committing, log from the previous
|
||||
// finished render until the end of the animation.
|
||||
if (
|
||||
(includesSyncLane(lanes) || includesBlockingLane(lanes)) &&
|
||||
!includesSyncLane(workInProgressRootRenderLanes) &&
|
||||
!includesBlockingLane(workInProgressRootRenderLanes) &&
|
||||
!includesSyncLane(pendingEffectsLanes) &&
|
||||
!includesBlockingLane(pendingEffectsLanes)
|
||||
) {
|
||||
setCurrentTrackFromLanes(SyncLane);
|
||||
logAnimatingPhase(blockingClampTime, now(), task);
|
||||
}
|
||||
if (
|
||||
includesTransitionLane(lanes) &&
|
||||
!includesTransitionLane(workInProgressRootRenderLanes) &&
|
||||
!includesTransitionLane(pendingEffectsLanes)
|
||||
) {
|
||||
setCurrentTrackFromLanes(SomeTransitionLane);
|
||||
logAnimatingPhase(transitionClampTime, now(), task);
|
||||
}
|
||||
if (
|
||||
includesRetryLane(lanes) &&
|
||||
!includesRetryLane(workInProgressRootRenderLanes) &&
|
||||
!includesRetryLane(pendingEffectsLanes)
|
||||
) {
|
||||
setCurrentTrackFromLanes(SomeRetryLane);
|
||||
logAnimatingPhase(retryClampTime, now(), task);
|
||||
}
|
||||
if (
|
||||
includesIdleGroupLanes(lanes) &&
|
||||
!includesIdleGroupLanes(workInProgressRootRenderLanes) &&
|
||||
!includesIdleGroupLanes(pendingEffectsLanes)
|
||||
) {
|
||||
setCurrentTrackFromLanes(IdleLane);
|
||||
logAnimatingPhase(idleClampTime, now(), task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushAfterMutationEffects(): void {
|
||||
if (pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE) {
|
||||
return;
|
||||
@@ -3715,7 +3835,7 @@ function flushLayoutEffects(): void {
|
||||
commitEndTime, // The start is the end of the first commit part.
|
||||
commitStartTime, // The end is the start of the second commit part.
|
||||
suspendedViewTransitionReason,
|
||||
workInProgressUpdateTask,
|
||||
workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
includesTransitionLane,
|
||||
includesBlockingLane,
|
||||
includesSyncLane,
|
||||
NoLanes,
|
||||
} from './ReactFiberLane';
|
||||
|
||||
import {resolveEventType, resolveEventTimeStamp} from './ReactFiberConfig';
|
||||
@@ -88,6 +89,11 @@ export let transitionEventType: null | string = null; // Event type of the first
|
||||
export let transitionEventIsRepeat: boolean = false;
|
||||
export let transitionSuspendedTime: number = -1.1;
|
||||
|
||||
export let retryClampTime: number = -0;
|
||||
export let idleClampTime: number = -0;
|
||||
|
||||
export let animatingLanes: Lanes = NoLanes;
|
||||
|
||||
export let yieldReason: SuspendedReason = (0: any);
|
||||
export let yieldStartTime: number = -1.1; // The time when we yielded to the event loop
|
||||
|
||||
@@ -306,6 +312,20 @@ export function clampTransitionTimers(finalTime: number): void {
|
||||
transitionClampTime = finalTime;
|
||||
}
|
||||
|
||||
export function clampRetryTimers(finalTime: number): void {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
retryClampTime = finalTime;
|
||||
}
|
||||
|
||||
export function clampIdleTimers(finalTime: number): void {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
idleClampTime = finalTime;
|
||||
}
|
||||
|
||||
export function pushNestedEffectDurations(): number {
|
||||
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
|
||||
return 0;
|
||||
@@ -578,3 +598,11 @@ export function transferActualDuration(fiber: Fiber): void {
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
export function startAnimating(lanes: Lanes): void {
|
||||
animatingLanes |= lanes;
|
||||
}
|
||||
|
||||
export function stopAnimating(lanes: Lanes): void {
|
||||
animatingLanes &= ~lanes;
|
||||
}
|
||||
|
||||
@@ -424,6 +424,7 @@ export function startViewTransition(
|
||||
passiveCallback: () => mixed,
|
||||
errorCallback: mixed => void,
|
||||
blockedCallback: string => void, // Profiling-only
|
||||
finishedAnimation: () => void, // Profiling-only
|
||||
): null | RunningViewTransition {
|
||||
mutationCallback();
|
||||
layoutCallback();
|
||||
|
||||
Reference in New Issue
Block a user