From 6b0753eb3eac3ca4eb8b1ba94b0971932ce5c47c Mon Sep 17 00:00:00 2001 From: Luna Ruan Date: Fri, 11 Feb 2022 13:15:10 -0500 Subject: [PATCH] [Devtools][Transition Tracing] Add Transition callbacks to createRoot (#23276) - Add the type of transition tracing callbacks - Add transition tracing callbacks as an option to `createRoot` - Add transition tracing callbacks on the root - Add option to pass transition tracing callbacks to createReactNoop --- .../react-dom/src/client/ReactDOMLegacy.js | 3 +- packages/react-dom/src/client/ReactDOMRoot.js | 14 ++++- .../react-native-renderer/src/ReactFabric.js | 1 + .../src/ReactNativeRenderer.js | 1 + .../src/createReactNoop.js | 14 ++++- .../src/ReactFiberReconciler.new.js | 8 ++- .../src/ReactFiberReconciler.old.js | 8 ++- .../src/ReactFiberRoot.new.js | 16 +++++- .../src/ReactFiberRoot.old.js | 16 +++++- .../src/ReactInternalTypes.js | 55 +++++++++++++++++++ .../src/ReactTestRenderer.js | 1 + 11 files changed, 129 insertions(+), 8 deletions(-) diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index b95be0bd3f22d0..340e151bf893c6 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -127,7 +127,8 @@ function legacyCreateRootFromDOMContainer( false, // isStrictMode false, // concurrentUpdatesByDefaultOverride, '', // identifierPrefix - noopOnRecoverableError, + noopOnRecoverableError, // onRecoverableError + null, // transitionCallbacks ); markContainerAsRoot(root.current, container); diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 9867a1b45e5dae..1fe4557cb8bc1e 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -9,7 +9,10 @@ import type {Container} from './ReactDOMHostConfig'; import type {MutableSource, ReactNodeList} from 'shared/ReactTypes'; -import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; +import type { + FiberRoot, + TransitionTracingCallbacks, +} from 'react-reconciler/src/ReactInternalTypes'; import {queueExplicitHydrationTarget} from '../events/ReactDOMEventReplaying'; @@ -25,6 +28,7 @@ export type CreateRootOptions = { unstable_concurrentUpdatesByDefault?: boolean, identifierPrefix?: string, onRecoverableError?: (error: mixed) => void, + transitionCallbacks?: TransitionTracingCallbacks, ... }; @@ -158,6 +162,8 @@ export function createRoot( let concurrentUpdatesByDefaultOverride = false; let identifierPrefix = ''; let onRecoverableError = defaultOnRecoverableError; + let transitionCallbacks = null; + if (options !== null && options !== undefined) { if (__DEV__) { if ((options: any).hydrate) { @@ -181,6 +187,9 @@ export function createRoot( if (options.onRecoverableError !== undefined) { onRecoverableError = options.onRecoverableError; } + if (options.transitionCallbacks !== undefined) { + transitionCallbacks = options.transitionCallbacks; + } } const root = createContainer( @@ -192,6 +201,7 @@ export function createRoot( concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, + transitionCallbacks, ); markContainerAsRoot(root.current, container); @@ -260,6 +270,8 @@ export function hydrateRoot( concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, + // TODO(luna) Support hydration later + null, ); markContainerAsRoot(root.current, container); // This can't be a comment node since hydration doesn't work on comment nodes anyway. diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index 2f64c5a591994b..127b20fd5dacfe 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -221,6 +221,7 @@ function render( null, '', onRecoverableError, + null, ); roots.set(containerTag, root); } diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index 2ea04acdcbe6c4..1dca2cd7a1d93f 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -217,6 +217,7 @@ function render( null, '', onRecoverableError, + null, ); roots.set(containerTag, root); } diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index c314af59cb59ab..e0ba72076a1af9 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -14,7 +14,10 @@ * environment. */ -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type { + Fiber, + TransitionTracingCallbacks, +} from 'react-reconciler/src/ReactInternalTypes'; import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue'; import type {ReactNodeList, OffscreenMode} from 'shared/ReactTypes'; import type {RootTag} from 'react-reconciler/src/ReactRootTags'; @@ -64,6 +67,10 @@ type TextInstance = {| context: HostContext, |}; type HostContext = Object; +type CreateRootOptions = { + transitionCallbacks?: TransitionTracingCallbacks, + ... +}; const NO_CONTEXT = {}; const UPPERCASE_CONTEXT = {}; @@ -980,7 +987,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { }, // TODO: Replace ReactNoop.render with createRoot + root.render - createRoot() { + createRoot(options?: CreateRootOptions) { const container = { rootID: '' + idCounter++, pendingChildren: [], @@ -995,6 +1002,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { false, '', onRecoverableError, + options && options.transitionCallbacks + ? options.transitionCallbacks + : null, ); return { _Scheduler: Scheduler, diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index d3130581d2ff8b..6db63352bfc25d 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -7,7 +7,11 @@ * @flow */ -import type {Fiber, SuspenseHydrationCallbacks} from './ReactInternalTypes'; +import type { + Fiber, + SuspenseHydrationCallbacks, + TransitionTracingCallbacks, +} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import type { @@ -246,6 +250,7 @@ export function createContainer( concurrentUpdatesByDefaultOverride: null | boolean, identifierPrefix: string, onRecoverableError: (error: mixed) => void, + transitionCallbacks: null | TransitionTracingCallbacks, ): OpaqueRoot { return createFiberRoot( containerInfo, @@ -256,6 +261,7 @@ export function createContainer( concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, + transitionCallbacks, ); } diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index 19136883c778cd..d8be40ba026735 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -7,7 +7,11 @@ * @flow */ -import type {Fiber, SuspenseHydrationCallbacks} from './ReactInternalTypes'; +import type { + Fiber, + SuspenseHydrationCallbacks, + TransitionTracingCallbacks, +} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import type { @@ -246,6 +250,7 @@ export function createContainer( concurrentUpdatesByDefaultOverride: null | boolean, identifierPrefix: string, onRecoverableError: (error: mixed) => void, + transitionCallbacks: null | TransitionTracingCallbacks, ): OpaqueRoot { return createFiberRoot( containerInfo, @@ -256,6 +261,7 @@ export function createContainer( concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, + transitionCallbacks, ); } diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 85820dc7a3cdf6..3a4546008ad501 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -7,7 +7,11 @@ * @flow */ -import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes'; +import type { + FiberRoot, + SuspenseHydrationCallbacks, + TransitionTracingCallbacks, +} from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import {noTimeout, supportsHydration} from './ReactFiberHostConfig'; @@ -25,6 +29,7 @@ import { enableProfilerCommitHooks, enableProfilerTimer, enableUpdaterTracking, + enableTransitionTracing, } from 'shared/ReactFeatureFlags'; import {initializeUpdateQueue} from './ReactUpdateQueue.new'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; @@ -78,6 +83,10 @@ function FiberRootNode( this.hydrationCallbacks = null; } + if (enableTransitionTracing) { + this.transitionCallbacks = null; + } + if (enableProfilerTimer && enableProfilerCommitHooks) { this.effectDuration = 0; this.passiveEffectDuration = 0; @@ -116,6 +125,7 @@ export function createFiberRoot( // single type, like a DynamicHostConfig that is defined by the renderer. identifierPrefix: string, onRecoverableError: null | ((error: mixed) => void), + transitionCallbacks: null | TransitionTracingCallbacks, ): FiberRoot { const root: FiberRoot = (new FiberRootNode( containerInfo, @@ -128,6 +138,10 @@ export function createFiberRoot( root.hydrationCallbacks = hydrationCallbacks; } + if (enableTransitionTracing) { + root.transitionCallbacks = transitionCallbacks; + } + // Cyclic construction. This cheats the type system right now because // stateNode is any. const uninitializedFiber = createHostRootFiber( diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index e1eaee798bfb20..56ebcd5cccac47 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -7,7 +7,11 @@ * @flow */ -import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes'; +import type { + FiberRoot, + SuspenseHydrationCallbacks, + TransitionTracingCallbacks, +} from './ReactInternalTypes'; import type {RootTag} from './ReactRootTags'; import {noTimeout, supportsHydration} from './ReactFiberHostConfig'; @@ -25,6 +29,7 @@ import { enableProfilerCommitHooks, enableProfilerTimer, enableUpdaterTracking, + enableTransitionTracing, } from 'shared/ReactFeatureFlags'; import {initializeUpdateQueue} from './ReactUpdateQueue.old'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; @@ -78,6 +83,10 @@ function FiberRootNode( this.hydrationCallbacks = null; } + if (enableTransitionTracing) { + this.transitionCallbacks = null; + } + if (enableProfilerTimer && enableProfilerCommitHooks) { this.effectDuration = 0; this.passiveEffectDuration = 0; @@ -116,6 +125,7 @@ export function createFiberRoot( // single type, like a DynamicHostConfig that is defined by the renderer. identifierPrefix: string, onRecoverableError: null | ((error: mixed) => void), + transitionCallbacks: null | TransitionTracingCallbacks, ): FiberRoot { const root: FiberRoot = (new FiberRootNode( containerInfo, @@ -128,6 +138,10 @@ export function createFiberRoot( root.hydrationCallbacks = hydrationCallbacks; } + if (enableTransitionTracing) { + root.transitionCallbacks = transitionCallbacks; + } + // Cyclic construction. This cheats the type system right now because // stateNode is any. const uninitializedFiber = createHostRootFiber( diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 352498be61f307..ed668d9126b6f9 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -268,6 +268,60 @@ type SuspenseCallbackOnlyFiberRootProperties = {| hydrationCallbacks: null | SuspenseHydrationCallbacks, |}; +export type TransitionTracingCallbacks = { + onTransitionStart?: (transitionName: string, startTime: number) => void, + onTransitionProgress?: ( + transitionName: string, + startTime: number, + currentTime: number, + pending: Array<{name: null | string}>, + ) => void, + onTransitionIncomplete?: ( + transitionName: string, + startTime: number, + deletions: Array<{ + type: string, + name?: string, + newName?: string, + endTime: number, + }>, + ) => void, + onTransitionComplete?: ( + transitionName: string, + startTime: number, + endTime: number, + ) => void, + onMarkerProgress?: ( + transitionName: string, + marker: string, + startTime: number, + currentTime: number, + pending: Array<{name: null | string}>, + ) => void, + onMarkerIncomplete?: ( + transitionName: string, + marker: string, + startTime: number, + deletions: Array<{ + type: string, + name?: string, + newName?: string, + endTime: number, + }>, + ) => void, + onMarkerComplete?: ( + transitionName: string, + marker: string, + startTime: number, + endTime: number, + ) => void, +}; + +// The following fields are only used in transition tracing in Profile builds +type TransitionTracingOnlyFiberRootProperties = {| + transitionCallbacks: null | TransitionTracingCallbacks, +|}; + // Exported FiberRoot type includes all properties, // To avoid requiring potentially error-prone :any casts throughout the project. // The types are defined separately within this file to ensure they stay in sync. @@ -275,6 +329,7 @@ export type FiberRoot = { ...BaseFiberRootProperties, ...SuspenseCallbackOnlyFiberRootProperties, ...UpdaterTrackingOnlyFiberRootProperties, + ...TransitionTracingOnlyFiberRootProperties, ... }; diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 75d4de071bfd18..e850086439a67c 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -479,6 +479,7 @@ function create(element: React$Element, options: TestRendererOptions) { concurrentUpdatesByDefault, '', onRecoverableError, + null, ); if (root == null) {