Use the JSX of the ViewTransition as the Stack Trace of "Animating" Traces (#34539)

Stacked on #34538.

Track the Task of the first ViewTransition that we detected as
animating. Use this as the Task as "Starting Animation", "Animating"
etc. That way you can see which ViewTransition spawned the Animation.
Although it's likely to be multiple.

<img width="757" height="393" alt="Screenshot 2025-09-19 at 10 19 18 PM"
src="https://github.com/user-attachments/assets/a6cdcb89-bd02-40ec-b3c3-11121c29e892"
/>
This commit is contained in:
Sebastian Markbåge 2025-09-20 11:11:27 -04:00 committed by GitHub
parent b4fe1e6c7e
commit d91d28c8ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 31 deletions

View File

@ -39,6 +39,11 @@ import {
getViewTransitionName, getViewTransitionName,
getViewTransitionClassName, getViewTransitionClassName,
} from './ReactFiberViewTransitionComponent'; } from './ReactFiberViewTransitionComponent';
import {trackAnimatingTask} from './ReactProfilerTimer';
import {
enableComponentPerformanceTrack,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
export let shouldStartViewTransition: boolean = false; export let shouldStartViewTransition: boolean = false;
@ -101,21 +106,27 @@ export function popViewTransitionCancelableScope(
let viewTransitionHostInstanceIdx = 0; let viewTransitionHostInstanceIdx = 0;
export function applyViewTransitionToHostInstances( function applyViewTransitionToHostInstances(
child: null | Fiber, fiber: Fiber,
name: string, name: string,
className: ?string, className: ?string,
collectMeasurements: null | Array<InstanceMeasurement>, collectMeasurements: null | Array<InstanceMeasurement>,
stopAtNestedViewTransitions: boolean, stopAtNestedViewTransitions: boolean,
): boolean { ): boolean {
viewTransitionHostInstanceIdx = 0; viewTransitionHostInstanceIdx = 0;
return applyViewTransitionToHostInstancesRecursive( const inViewport = applyViewTransitionToHostInstancesRecursive(
child, fiber.child,
name, name,
className, className,
collectMeasurements, collectMeasurements,
stopAtNestedViewTransitions, stopAtNestedViewTransitions,
); );
if (enableProfilerTimer && enableComponentPerformanceTrack && inViewport) {
if (fiber._debugTask != null) {
trackAnimatingTask(fiber._debugTask);
}
}
return inViewport;
} }
function applyViewTransitionToHostInstancesRecursive( function applyViewTransitionToHostInstancesRecursive(
@ -247,7 +258,7 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
// We found a new appearing view transition with the same name as this deletion. // We found a new appearing view transition with the same name as this deletion.
// We'll transition between them. // We'll transition between them.
const inViewport = applyViewTransitionToHostInstances( const inViewport = applyViewTransitionToHostInstances(
child.child, child,
name, name,
className, className,
null, null,
@ -284,7 +295,7 @@ export function commitEnterViewTransitions(
); );
if (className !== 'none') { if (className !== 'none') {
const inViewport = applyViewTransitionToHostInstances( const inViewport = applyViewTransitionToHostInstances(
placement.child, placement,
name, name,
className, className,
null, null,
@ -355,7 +366,7 @@ function commitDeletedPairViewTransitions(deletion: Fiber): void {
if (className !== 'none') { if (className !== 'none') {
// We found a new appearing view transition with the same name as this deletion. // We found a new appearing view transition with the same name as this deletion.
const inViewport = applyViewTransitionToHostInstances( const inViewport = applyViewTransitionToHostInstances(
child.child, child,
name, name,
className, className,
null, null,
@ -406,7 +417,7 @@ export function commitExitViewTransitions(deletion: Fiber): void {
); );
if (className !== 'none') { if (className !== 'none') {
const inViewport = applyViewTransitionToHostInstances( const inViewport = applyViewTransitionToHostInstances(
deletion.child, deletion,
name, name,
className, className,
null, null,
@ -490,7 +501,7 @@ export function commitBeforeUpdateViewTransition(
return; return;
} }
applyViewTransitionToHostInstances( applyViewTransitionToHostInstances(
current.child, current,
oldName, oldName,
className, className,
(current.memoizedState = []), (current.memoizedState = []),
@ -518,7 +529,7 @@ export function commitNestedViewTransitions(changedParent: Fiber): void {
child.flags &= ~Update; child.flags &= ~Update;
if (className !== 'none') { if (className !== 'none') {
applyViewTransitionToHostInstances( applyViewTransitionToHostInstances(
child.child, child,
name, name,
className, className,
(child.memoizedState = []), (child.memoizedState = []),

View File

@ -324,6 +324,7 @@ import {
animatingLanes, animatingLanes,
retryClampTime, retryClampTime,
idleClampTime, idleClampTime,
animatingTask,
} from './ReactProfilerTimer'; } from './ReactProfilerTimer';
// DEV stuff // DEV stuff
@ -1995,7 +1996,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
logAnimatingPhase( logAnimatingPhase(
blockingClampTime, blockingClampTime,
clampedRenderStartTime, clampedRenderStartTime,
previousUpdateTask, animatingTask,
); );
} }
logBlockingStart( logBlockingStart(
@ -2048,7 +2049,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
logAnimatingPhase( logAnimatingPhase(
transitionClampTime, transitionClampTime,
clampedRenderStartTime, clampedRenderStartTime,
previousUpdateTask, animatingTask,
); );
} }
logTransitionStart( logTransitionStart(
@ -2069,14 +2070,14 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
if (includesRetryLane(animatingLanes)) { if (includesRetryLane(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating. // If this lane is still animating, log the time from previous render finishing to now as animating.
setCurrentTrackFromLanes(SomeRetryLane); setCurrentTrackFromLanes(SomeRetryLane);
logAnimatingPhase(retryClampTime, renderStartTime, previousUpdateTask); logAnimatingPhase(retryClampTime, renderStartTime, animatingTask);
} }
} }
if (includesIdleGroupLanes(lanes)) { if (includesIdleGroupLanes(lanes)) {
if (includesIdleGroupLanes(animatingLanes)) { if (includesIdleGroupLanes(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating. // If this lane is still animating, log the time from previous render finishing to now as animating.
setCurrentTrackFromLanes(IdleLane); setCurrentTrackFromLanes(IdleLane);
logAnimatingPhase(idleClampTime, renderStartTime, previousUpdateTask); logAnimatingPhase(idleClampTime, renderStartTime, animatingTask);
} }
} }
} }
@ -3667,12 +3668,7 @@ function commitRoot(
enableProfilerTimer ? suspendedViewTransition : (null: any), enableProfilerTimer ? suspendedViewTransition : (null: any),
enableProfilerTimer enableProfilerTimer
? // This callback fires after "pendingEffects" so we need to snapshot the arguments. ? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
finishedViewTransition.bind( finishedViewTransition.bind(null, lanes)
null,
lanes,
// TODO: Use a ViewTransition Task
__DEV__ ? workInProgressUpdateTask : null,
)
: (null: any), : (null: any),
); );
} else { } else {
@ -3712,15 +3708,13 @@ function suspendedViewTransition(reason: string): void {
} }
} }
function finishedViewTransition( function finishedViewTransition(lanes: Lanes): void {
lanes: Lanes,
task: null | ConsoleTask, // DEV-only
): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) { if (enableProfilerTimer && enableComponentPerformanceTrack) {
if ((animatingLanes & lanes) === NoLanes) { if ((animatingLanes & lanes) === NoLanes) {
// Was already stopped by some other action or maybe other root. // Was already stopped by some other action or maybe other root.
return; return;
} }
const task = animatingTask;
stopAnimating(lanes); stopAnimating(lanes);
// If an affected track isn't in the middle of rendering or committing, log from the previous // 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. // finished render until the end of the animation.
@ -3835,7 +3829,7 @@ function flushLayoutEffects(): void {
commitEndTime, // The start is the end of the first commit part. commitEndTime, // The start is the end of the first commit part.
commitStartTime, // The end is the start of the second commit part. commitStartTime, // The end is the start of the second commit part.
suspendedViewTransitionReason, suspendedViewTransitionReason,
workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase. animatingTask,
); );
} }
} }
@ -3938,7 +3932,7 @@ function flushSpawnedWork(): void {
startViewTransitionStartTime, startViewTransitionStartTime,
commitEndTime, commitEndTime,
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT, pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
workInProgressUpdateTask, // TODO: Use a ViewTransition Task. animatingTask,
); );
if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) { if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) {
pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT; pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT;
@ -4440,11 +4434,7 @@ function flushPassiveEffectsImpl() {
passiveEffectStartTime = now(); passiveEffectStartTime = now();
if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) { if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) {
// The animation was started, so we've been animating since that happened. // The animation was started, so we've been animating since that happened.
logAnimatingPhase( logAnimatingPhase(commitEndTime, passiveEffectStartTime, animatingTask);
commitEndTime,
passiveEffectStartTime,
workInProgressUpdateTask, // TODO: Use a ViewTransition Task
);
} else { } else {
logPaintYieldPhase( logPaintYieldPhase(
commitEndTime, commitEndTime,

View File

@ -93,6 +93,7 @@ export let retryClampTime: number = -0;
export let idleClampTime: number = -0; export let idleClampTime: number = -0;
export let animatingLanes: Lanes = NoLanes; export let animatingLanes: Lanes = NoLanes;
export let animatingTask: null | ConsoleTask = null; // First ViewTransition applying an Animation.
export let yieldReason: SuspendedReason = (0: any); export let yieldReason: SuspendedReason = (0: any);
export let yieldStartTime: number = -1.1; // The time when we yielded to the event loop export let yieldStartTime: number = -1.1; // The time when we yielded to the event loop
@ -601,8 +602,16 @@ export function transferActualDuration(fiber: Fiber): void {
export function startAnimating(lanes: Lanes): void { export function startAnimating(lanes: Lanes): void {
animatingLanes |= lanes; animatingLanes |= lanes;
animatingTask = null;
} }
export function stopAnimating(lanes: Lanes): void { export function stopAnimating(lanes: Lanes): void {
animatingLanes &= ~lanes; animatingLanes &= ~lanes;
animatingTask = null;
}
export function trackAnimatingTask(task: ConsoleTask): void {
if (animatingTask === null) {
animatingTask = task;
}
} }