diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index fc80826678e23..8f67e12db1e1f 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -125,6 +125,7 @@ import { enableViewTransition, enableHydrationChangeEvent, enableFragmentRefsScrollIntoView, + enableProfilerTimer, } from 'shared/ReactFeatureFlags'; import { HostComponent, @@ -2098,6 +2099,7 @@ export function startViewTransition( spawnedWorkCallback: () => void, passiveCallback: () => mixed, errorCallback: mixed => void, + blockedCallback: string => void, // Profiling-only ): null | RunningViewTransition { const ownerDocument: Document = rootContainer.nodeType === DOCUMENT_NODE @@ -2131,10 +2133,10 @@ export function startViewTransition( blockingPromises.push(ownerDocument.fonts.ready); } } + const blockingIndexSnapshot = blockingPromises.length; if (suspendedState !== null) { // Suspend on any images that still haven't loaded and are in the viewport. const suspenseyImages = suspendedState.suspenseyImages; - const blockingIndexSnapshot = blockingPromises.length; let imgBytes = 0; for (let i = 0; i < suspenseyImages.length; i++) { const suspenseyImage = suspenseyImages[i]; @@ -2162,6 +2164,15 @@ export function startViewTransition( } } if (blockingPromises.length > 0) { + if (enableProfilerTimer) { + const blockedReason = + blockingIndexSnapshot > 0 + ? blockingPromises.length > blockingIndexSnapshot + ? 'Waiting on Fonts and Images' + : 'Waiting on Fonts' + : 'Waiting on Images'; + blockedCallback(blockedReason); + } const blockingReady = Promise.race([ Promise.all(blockingPromises), new Promise(resolve => diff --git a/packages/react-native-renderer/src/ReactFiberConfigNative.js b/packages/react-native-renderer/src/ReactFiberConfigNative.js index c9a5fb591bfd8..8271a62327aea 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigNative.js +++ b/packages/react-native-renderer/src/ReactFiberConfigNative.js @@ -673,6 +673,7 @@ export function startViewTransition( spawnedWorkCallback: () => void, passiveCallback: () => mixed, errorCallback: mixed => void, + blockedCallback: string => void, // Profiling-only ): null | RunningViewTransition { mutationCallback(); layoutCallback(); diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 92ca7e00e2696..67438b7f817e8 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -1254,6 +1254,45 @@ export function logSuspendedCommitPhase( } } +export function logSuspendedViewTransitionPhase( + startTime: number, + endTime: number, + reason: string, + debugTask: null | ConsoleTask, +): void { + // This means the commit was suspended on CSS or images. + if (supportsUserTiming) { + if (endTime <= startTime) { + return; + } + // TODO: Include the exact reason and URLs of what resources suspended. + // TODO: This might also be Suspended while waiting on a View Transition. + if (__DEV__ && debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + console.timeStamp.bind( + console, + reason, + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + 'secondary-light', + ), + ); + } else { + console.timeStamp( + reason, + startTime, + endTime, + currentTrack, + LANES_TRACK_GROUP, + 'secondary-light', + ); + } + } +} + export function logCommitErrored( startTime: number, endTime: number, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index d141c2855f66e..b2b53281b9234 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -81,6 +81,7 @@ import { logSuspendedWithDelayPhase, logSuspenseThrottlePhase, logSuspendedCommitPhase, + logSuspendedViewTransitionPhase, logCommitPhase, logPaintYieldPhase, logStartViewTransitionYieldPhase, @@ -704,6 +705,7 @@ let pendingTransitionTypes: null | TransitionTypes = null; let pendingDidIncludeRenderPhaseUpdate: boolean = false; let pendingSuspendedCommitReason: SuspendedCommitReason = IMMEDIATE_COMMIT; // Profiling-only let pendingDelayedCommitReason: DelayedCommitReason = IMMEDIATE_COMMIT; // Profiling-only +let pendingSuspendedViewTransitionReason: null | string = null; // Profiling-only // Use these to prevent an infinite loop of nested updates const NESTED_UPDATE_LIMIT = 50; @@ -3445,6 +3447,7 @@ function commitRoot( pendingEffectsRenderEndTime = completedRenderEndTime; pendingSuspendedCommitReason = suspendedCommitReason; pendingDelayedCommitReason = IMMEDIATE_COMMIT; + pendingSuspendedViewTransitionReason = null; } if (enableGestureTransition && isGestureRender(lanes)) { @@ -3604,6 +3607,7 @@ function commitRoot( flushSpawnedWork, flushPassiveEffects, reportViewTransitionError, + enableProfilerTimer ? suspendedViewTransition : (null: any), ); } else { // Flush synchronously. @@ -3624,6 +3628,24 @@ function reportViewTransitionError(error: mixed) { onRecoverableError(error, makeErrorInfo(null)); } +function suspendedViewTransition(reason: string): void { + if (enableProfilerTimer && enableComponentPerformanceTrack) { + // We'll split the commit into two phases, because we're suspended in the middle. + recordCommitEndTime(); + logCommitPhase( + pendingSuspendedCommitReason === IMMEDIATE_COMMIT + ? pendingEffectsRenderEndTime + : commitStartTime, + commitEndTime, + commitErrors, + pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT, + workInProgressUpdateTask, + ); + pendingSuspendedViewTransitionReason = reason; + pendingSuspendedCommitReason = SUSPENDED_COMMIT; + } +} + function flushAfterMutationEffects(): void { if (pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE) { return; @@ -3688,6 +3710,21 @@ function flushLayoutEffects(): void { } pendingEffectsStatus = NO_PENDING_EFFECTS; + if (enableProfilerTimer && enableComponentPerformanceTrack) { + const suspendedViewTransitionReason = pendingSuspendedViewTransitionReason; + if (suspendedViewTransitionReason !== null) { + // We suspended in the middle of the commit for the view transition. + // We'll start a new commit track now. + recordCommitTime(); + logSuspendedViewTransitionPhase( + commitEndTime, // The start is the end of the first commit part. + commitStartTime, // The end is the start of the second commit part. + suspendedViewTransitionReason, + workInProgressUpdateTask, + ); + } + } + const root = pendingEffectsRoot; const finishedWork = pendingFinishedWork; const lanes = pendingEffectsLanes; diff --git a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js index 86621f68480b8..7b1477fa25602 100644 --- a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js +++ b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js @@ -423,6 +423,7 @@ export function startViewTransition( spawnedWorkCallback: () => void, passiveCallback: () => mixed, errorCallback: mixed => void, + blockedCallback: string => void, // Profiling-only ): null | RunningViewTransition { mutationCallback(); layoutCallback();