From d91d28c8ba6fe7c96e651f82fc47c9d5481bf5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Sat, 20 Sep 2025 11:11:27 -0400 Subject: [PATCH] Use the JSX of the ViewTransition as the Stack Trace of "Animating" Traces (#34539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Screenshot 2025-09-19 at 10 19 18 PM --- .../src/ReactFiberCommitViewTransitions.js | 31 ++++++++++++------ .../src/ReactFiberWorkLoop.js | 32 +++++++------------ .../src/ReactProfilerTimer.js | 9 ++++++ 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js index 9a75195915..c8e59afb88 100644 --- a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js +++ b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js @@ -39,6 +39,11 @@ import { getViewTransitionName, getViewTransitionClassName, } from './ReactFiberViewTransitionComponent'; +import {trackAnimatingTask} from './ReactProfilerTimer'; +import { + enableComponentPerformanceTrack, + enableProfilerTimer, +} from 'shared/ReactFeatureFlags'; export let shouldStartViewTransition: boolean = false; @@ -101,21 +106,27 @@ export function popViewTransitionCancelableScope( let viewTransitionHostInstanceIdx = 0; -export function applyViewTransitionToHostInstances( - child: null | Fiber, +function applyViewTransitionToHostInstances( + fiber: Fiber, name: string, className: ?string, collectMeasurements: null | Array, stopAtNestedViewTransitions: boolean, ): boolean { viewTransitionHostInstanceIdx = 0; - return applyViewTransitionToHostInstancesRecursive( - child, + const inViewport = applyViewTransitionToHostInstancesRecursive( + fiber.child, name, className, collectMeasurements, stopAtNestedViewTransitions, ); + if (enableProfilerTimer && enableComponentPerformanceTrack && inViewport) { + if (fiber._debugTask != null) { + trackAnimatingTask(fiber._debugTask); + } + } + return inViewport; } 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'll transition between them. const inViewport = applyViewTransitionToHostInstances( - child.child, + child, name, className, null, @@ -284,7 +295,7 @@ export function commitEnterViewTransitions( ); if (className !== 'none') { const inViewport = applyViewTransitionToHostInstances( - placement.child, + placement, name, className, null, @@ -355,7 +366,7 @@ function commitDeletedPairViewTransitions(deletion: Fiber): void { if (className !== 'none') { // We found a new appearing view transition with the same name as this deletion. const inViewport = applyViewTransitionToHostInstances( - child.child, + child, name, className, null, @@ -406,7 +417,7 @@ export function commitExitViewTransitions(deletion: Fiber): void { ); if (className !== 'none') { const inViewport = applyViewTransitionToHostInstances( - deletion.child, + deletion, name, className, null, @@ -490,7 +501,7 @@ export function commitBeforeUpdateViewTransition( return; } applyViewTransitionToHostInstances( - current.child, + current, oldName, className, (current.memoizedState = []), @@ -518,7 +529,7 @@ export function commitNestedViewTransitions(changedParent: Fiber): void { child.flags &= ~Update; if (className !== 'none') { applyViewTransitionToHostInstances( - child.child, + child, name, className, (child.memoizedState = []), diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 7003d25aff..7c899f6ff8 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -324,6 +324,7 @@ import { animatingLanes, retryClampTime, idleClampTime, + animatingTask, } from './ReactProfilerTimer'; // DEV stuff @@ -1995,7 +1996,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { logAnimatingPhase( blockingClampTime, clampedRenderStartTime, - previousUpdateTask, + animatingTask, ); } logBlockingStart( @@ -2048,7 +2049,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { logAnimatingPhase( transitionClampTime, clampedRenderStartTime, - previousUpdateTask, + animatingTask, ); } logTransitionStart( @@ -2069,14 +2070,14 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { 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); + logAnimatingPhase(retryClampTime, renderStartTime, animatingTask); } } 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); + logAnimatingPhase(idleClampTime, renderStartTime, animatingTask); } } } @@ -3667,12 +3668,7 @@ function commitRoot( 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, - ) + finishedViewTransition.bind(null, lanes) : (null: any), ); } else { @@ -3712,15 +3708,13 @@ function suspendedViewTransition(reason: string): void { } } -function finishedViewTransition( - lanes: Lanes, - task: null | ConsoleTask, // DEV-only -): void { +function finishedViewTransition(lanes: Lanes): void { if (enableProfilerTimer && enableComponentPerformanceTrack) { if ((animatingLanes & lanes) === NoLanes) { // Was already stopped by some other action or maybe other root. return; } + const task = animatingTask; 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. @@ -3835,7 +3829,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, // TODO: Use a ViewTransition Task and this is not safe to read in this phase. + animatingTask, ); } } @@ -3938,7 +3932,7 @@ function flushSpawnedWork(): void { startViewTransitionStartTime, commitEndTime, pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT, - workInProgressUpdateTask, // TODO: Use a ViewTransition Task. + animatingTask, ); if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) { pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT; @@ -4440,11 +4434,7 @@ function flushPassiveEffectsImpl() { passiveEffectStartTime = now(); if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) { // The animation was started, so we've been animating since that happened. - logAnimatingPhase( - commitEndTime, - passiveEffectStartTime, - workInProgressUpdateTask, // TODO: Use a ViewTransition Task - ); + logAnimatingPhase(commitEndTime, passiveEffectStartTime, animatingTask); } else { logPaintYieldPhase( commitEndTime, diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 0891a4f5f5..f2cf8efdce 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -93,6 +93,7 @@ export let retryClampTime: number = -0; export let idleClampTime: number = -0; export let animatingLanes: Lanes = NoLanes; +export let animatingTask: null | ConsoleTask = null; // First ViewTransition applying an Animation. export let yieldReason: SuspendedReason = (0: any); 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 { animatingLanes |= lanes; + animatingTask = null; } export function stopAnimating(lanes: Lanes): void { animatingLanes &= ~lanes; + animatingTask = null; +} + +export function trackAnimatingTask(task: ConsoleTask): void { + if (animatingTask === null) { + animatingTask = task; + } }