Skip to content

Commit b4fe1e6

Browse files
authored
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.
1 parent b204edd commit b4fe1e6

File tree

8 files changed

+193
-19
lines changed

8 files changed

+193
-19
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,6 +2100,7 @@ export function startViewTransition(
21002100
passiveCallback: () => mixed,
21012101
errorCallback: mixed => void,
21022102
blockedCallback: string => void, // Profiling-only
2103+
finishedAnimation: () => void, // Profiling-only
21032104
): null | RunningViewTransition {
21042105
const ownerDocument: Document =
21052106
rootContainer.nodeType === DOCUMENT_NODE
@@ -2302,6 +2303,9 @@ export function startViewTransition(
23022303
// $FlowFixMe[prop-missing]
23032304
ownerDocument.__reactViewTransition = null;
23042305
}
2306+
if (enableProfilerTimer) {
2307+
finishedAnimation();
2308+
}
23052309
passiveCallback();
23062310
});
23072311
return transition;

packages/react-native-renderer/src/ReactFiberConfigNative.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ export function startViewTransition(
674674
passiveCallback: () => mixed,
675675
errorCallback: mixed => void,
676676
blockedCallback: string => void, // Profiling-only
677+
finishedAnimation: () => void, // Profiling-only
677678
): null | RunningViewTransition {
678679
mutationCallback();
679680
layoutCallback();

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
860860
spawnedWorkCallback: () => void,
861861
passiveCallback: () => mixed,
862862
errorCallback: mixed => void,
863+
blockedCallback: string => void, // Profiling-only
864+
finishedAnimation: () => void, // Profiling-only
863865
): null | RunningViewTransition {
864866
mutationCallback();
865867
layoutCallback();

packages/react-reconciler/src/ReactFiberLane.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ const TransitionLane12: Lane = /* */ 0b0000000000010000000
7373
const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;
7474
const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;
7575

76+
export const SomeTransitionLane: Lane = TransitionLane1;
77+
7678
const TransitionUpdateLanes =
7779
TransitionLane1 |
7880
TransitionLane2 |
@@ -633,6 +635,22 @@ export function includesTransitionLane(lanes: Lanes): boolean {
633635
return (lanes & TransitionLanes) !== NoLanes;
634636
}
635637

638+
export function includesRetryLane(lanes: Lanes): boolean {
639+
return (lanes & RetryLanes) !== NoLanes;
640+
}
641+
642+
export function includesIdleGroupLanes(lanes: Lanes): boolean {
643+
return (
644+
(lanes &
645+
(SelectiveHydrationLane |
646+
IdleHydrationLane |
647+
IdleLane |
648+
OffscreenLane |
649+
DeferredLane)) !==
650+
NoLanes
651+
);
652+
}
653+
636654
export function includesOnlyHydrationLanes(lanes: Lanes): boolean {
637655
return (lanes & HydrationLanes) === lanes;
638656
}

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,7 +1458,7 @@ export function logAnimatingPhase(
14581458
endTime,
14591459
currentTrack,
14601460
LANES_TRACK_GROUP,
1461-
'secondary',
1461+
'secondary-dark',
14621462
),
14631463
);
14641464
} else {
@@ -1468,7 +1468,7 @@ export function logAnimatingPhase(
14681468
endTime,
14691469
currentTrack,
14701470
LANES_TRACK_GROUP,
1471-
'secondary',
1471+
'secondary-dark',
14721472
);
14731473
}
14741474
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 137 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ import {
179179
includesOnlyTransitions,
180180
includesBlockingLane,
181181
includesTransitionLane,
182+
includesRetryLane,
183+
includesIdleGroupLanes,
182184
includesExpiredLane,
183185
getNextLanes,
184186
getEntangledLanes,
@@ -201,6 +203,9 @@ import {
201203
includesOnlyViewTransitionEligibleLanes,
202204
isGestureRender,
203205
GestureLane,
206+
SomeTransitionLane,
207+
SomeRetryLane,
208+
IdleLane,
204209
} from './ReactFiberLane';
205210
import {
206211
DiscreteEventPriority,
@@ -292,6 +297,8 @@ import {
292297
clearTransitionTimers,
293298
clampBlockingTimers,
294299
clampTransitionTimers,
300+
clampRetryTimers,
301+
clampIdleTimers,
295302
markNestedUpdateScheduled,
296303
renderStartTime,
297304
commitStartTime,
@@ -312,6 +319,11 @@ import {
312319
resetCommitErrors,
313320
PINGED_UPDATE,
314321
SPAWNED_UPDATE,
322+
startAnimating,
323+
stopAnimating,
324+
animatingLanes,
325+
retryClampTime,
326+
idleClampTime,
315327
} from './ReactProfilerTimer';
316328

317329
// DEV stuff
@@ -1426,6 +1438,7 @@ function finishConcurrentRender(
14261438
// immediately, wait for more data to arrive.
14271439
// TODO: Combine retry throttling with Suspensey commits. Right now they
14281440
// run one after the other.
1441+
pendingEffectsLanes = lanes;
14291442
root.timeoutHandle = scheduleTimeout(
14301443
commitRootWhenReady.bind(
14311444
null,
@@ -1539,6 +1552,7 @@ function commitRootWhenReady(
15391552
// Not yet ready to commit. Delay the commit until the renderer notifies
15401553
// us that it's ready. This will be canceled if we start work on the
15411554
// root again.
1555+
pendingEffectsLanes = lanes;
15421556
root.cancelPendingCommit = schedulePendingCommit(
15431557
commitRoot.bind(
15441558
null,
@@ -1889,6 +1903,12 @@ function finalizeRender(lanes: Lanes, finalizationTime: number): void {
18891903
if (includesTransitionLane(lanes)) {
18901904
clampTransitionTimers(finalizationTime);
18911905
}
1906+
if (includesRetryLane(lanes)) {
1907+
clampRetryTimers(finalizationTime);
1908+
}
1909+
if (includesIdleGroupLanes(lanes)) {
1910+
clampIdleTimers(finalizationTime);
1911+
}
18921912
}
18931913
}
18941914

@@ -1939,6 +1959,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19391959
}
19401960
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
19411961
}
1962+
const previousUpdateTask = workInProgressUpdateTask;
19421963

19431964
workInProgressUpdateTask = null;
19441965
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
@@ -1951,18 +1972,30 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19511972
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
19521973
? blockingClampTime
19531974
: blockingEventTime;
1975+
const clampedRenderStartTime = // Clamp the suspended time to the first event/update.
1976+
clampedEventTime >= 0
1977+
? clampedEventTime
1978+
: clampedUpdateTime >= 0
1979+
? clampedUpdateTime
1980+
: renderStartTime;
19541981
if (blockingSuspendedTime >= 0) {
1955-
setCurrentTrackFromLanes(lanes);
1982+
setCurrentTrackFromLanes(SyncLane);
19561983
logSuspendedWithDelayPhase(
19571984
blockingSuspendedTime,
1958-
// Clamp the suspended time to the first event/update.
1959-
clampedEventTime >= 0
1960-
? clampedEventTime
1961-
: clampedUpdateTime >= 0
1962-
? clampedUpdateTime
1963-
: renderStartTime,
1985+
clampedRenderStartTime,
19641986
lanes,
1965-
workInProgressUpdateTask,
1987+
previousUpdateTask,
1988+
);
1989+
} else if (
1990+
includesSyncLane(animatingLanes) ||
1991+
includesBlockingLane(animatingLanes)
1992+
) {
1993+
// If this lane is still animating, log the time from previous render finishing to now as animating.
1994+
setCurrentTrackFromLanes(SyncLane);
1995+
logAnimatingPhase(
1996+
blockingClampTime,
1997+
clampedRenderStartTime,
1998+
previousUpdateTask,
19661999
);
19672000
}
19682001
logBlockingStart(
@@ -1994,19 +2027,29 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19942027
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
19952028
? transitionClampTime
19962029
: transitionEventTime;
2030+
const clampedRenderStartTime =
2031+
// Clamp the suspended time to the first event/update.
2032+
clampedEventTime >= 0
2033+
? clampedEventTime
2034+
: clampedUpdateTime >= 0
2035+
? clampedUpdateTime
2036+
: renderStartTime;
19972037
if (transitionSuspendedTime >= 0) {
1998-
setCurrentTrackFromLanes(lanes);
2038+
setCurrentTrackFromLanes(SomeTransitionLane);
19992039
logSuspendedWithDelayPhase(
20002040
transitionSuspendedTime,
2001-
// Clamp the suspended time to the first event/update.
2002-
clampedEventTime >= 0
2003-
? clampedEventTime
2004-
: clampedUpdateTime >= 0
2005-
? clampedUpdateTime
2006-
: renderStartTime,
2041+
clampedRenderStartTime,
20072042
lanes,
20082043
workInProgressUpdateTask,
20092044
);
2045+
} else if (includesTransitionLane(animatingLanes)) {
2046+
// If this lane is still animating, log the time from previous render finishing to now as animating.
2047+
setCurrentTrackFromLanes(SomeTransitionLane);
2048+
logAnimatingPhase(
2049+
transitionClampTime,
2050+
clampedRenderStartTime,
2051+
previousUpdateTask,
2052+
);
20102053
}
20112054
logTransitionStart(
20122055
clampedStartTime,
@@ -2022,6 +2065,20 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
20222065
);
20232066
clearTransitionTimers();
20242067
}
2068+
if (includesRetryLane(lanes)) {
2069+
if (includesRetryLane(animatingLanes)) {
2070+
// If this lane is still animating, log the time from previous render finishing to now as animating.
2071+
setCurrentTrackFromLanes(SomeRetryLane);
2072+
logAnimatingPhase(retryClampTime, renderStartTime, previousUpdateTask);
2073+
}
2074+
}
2075+
if (includesIdleGroupLanes(lanes)) {
2076+
if (includesIdleGroupLanes(animatingLanes)) {
2077+
// If this lane is still animating, log the time from previous render finishing to now as animating.
2078+
setCurrentTrackFromLanes(IdleLane);
2079+
logAnimatingPhase(idleClampTime, renderStartTime, previousUpdateTask);
2080+
}
2081+
}
20252082
}
20262083

20272084
const timeoutHandle = root.timeoutHandle;
@@ -2038,6 +2095,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
20382095
cancelPendingCommit();
20392096
}
20402097

2098+
pendingEffectsLanes = NoLanes;
2099+
20412100
resetWorkInProgressStack();
20422101
workInProgressRoot = root;
20432102
const rootWorkInProgress = createWorkInProgress(root.current, null);
@@ -3592,6 +3651,9 @@ function commitRoot(
35923651

35933652
pendingEffectsStatus = PENDING_MUTATION_PHASE;
35943653
if (enableViewTransition && willStartViewTransition) {
3654+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3655+
startAnimating(lanes);
3656+
}
35953657
pendingViewTransition = startViewTransition(
35963658
suspendedState,
35973659
root.containerInfo,
@@ -3603,6 +3665,15 @@ function commitRoot(
36033665
flushPassiveEffects,
36043666
reportViewTransitionError,
36053667
enableProfilerTimer ? suspendedViewTransition : (null: any),
3668+
enableProfilerTimer
3669+
? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
3670+
finishedViewTransition.bind(
3671+
null,
3672+
lanes,
3673+
// TODO: Use a ViewTransition Task
3674+
__DEV__ ? workInProgressUpdateTask : null,
3675+
)
3676+
: (null: any),
36063677
);
36073678
} else {
36083679
// Flush synchronously.
@@ -3634,13 +3705,62 @@ function suspendedViewTransition(reason: string): void {
36343705
commitEndTime,
36353706
commitErrors,
36363707
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
3637-
workInProgressUpdateTask,
3708+
workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase.
36383709
);
36393710
pendingSuspendedViewTransitionReason = reason;
36403711
pendingSuspendedCommitReason = reason;
36413712
}
36423713
}
36433714

3715+
function finishedViewTransition(
3716+
lanes: Lanes,
3717+
task: null | ConsoleTask, // DEV-only
3718+
): void {
3719+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3720+
if ((animatingLanes & lanes) === NoLanes) {
3721+
// Was already stopped by some other action or maybe other root.
3722+
return;
3723+
}
3724+
stopAnimating(lanes);
3725+
// If an affected track isn't in the middle of rendering or committing, log from the previous
3726+
// finished render until the end of the animation.
3727+
if (
3728+
(includesSyncLane(lanes) || includesBlockingLane(lanes)) &&
3729+
!includesSyncLane(workInProgressRootRenderLanes) &&
3730+
!includesBlockingLane(workInProgressRootRenderLanes) &&
3731+
!includesSyncLane(pendingEffectsLanes) &&
3732+
!includesBlockingLane(pendingEffectsLanes)
3733+
) {
3734+
setCurrentTrackFromLanes(SyncLane);
3735+
logAnimatingPhase(blockingClampTime, now(), task);
3736+
}
3737+
if (
3738+
includesTransitionLane(lanes) &&
3739+
!includesTransitionLane(workInProgressRootRenderLanes) &&
3740+
!includesTransitionLane(pendingEffectsLanes)
3741+
) {
3742+
setCurrentTrackFromLanes(SomeTransitionLane);
3743+
logAnimatingPhase(transitionClampTime, now(), task);
3744+
}
3745+
if (
3746+
includesRetryLane(lanes) &&
3747+
!includesRetryLane(workInProgressRootRenderLanes) &&
3748+
!includesRetryLane(pendingEffectsLanes)
3749+
) {
3750+
setCurrentTrackFromLanes(SomeRetryLane);
3751+
logAnimatingPhase(retryClampTime, now(), task);
3752+
}
3753+
if (
3754+
includesIdleGroupLanes(lanes) &&
3755+
!includesIdleGroupLanes(workInProgressRootRenderLanes) &&
3756+
!includesIdleGroupLanes(pendingEffectsLanes)
3757+
) {
3758+
setCurrentTrackFromLanes(IdleLane);
3759+
logAnimatingPhase(idleClampTime, now(), task);
3760+
}
3761+
}
3762+
}
3763+
36443764
function flushAfterMutationEffects(): void {
36453765
if (pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE) {
36463766
return;
@@ -3715,7 +3835,7 @@ function flushLayoutEffects(): void {
37153835
commitEndTime, // The start is the end of the first commit part.
37163836
commitStartTime, // The end is the start of the second commit part.
37173837
suspendedViewTransitionReason,
3718-
workInProgressUpdateTask,
3838+
workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase.
37193839
);
37203840
}
37213841
}

0 commit comments

Comments
 (0)