diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 9153c03049b9f..4ccd2ea67e8b5 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -22,6 +22,7 @@ import { replayFailedUnitOfWorkWithInvokeGuardedCallback, enableProfilerTimer, enableProfilerNestedUpdatePhase, + enableProfilerNestedUpdateScheduledHook, enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, @@ -110,6 +111,7 @@ import { ForwardRef, MemoComponent, SimpleMemoComponent, + Profiler, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; import { @@ -258,6 +260,10 @@ let workInProgress: Fiber | null = null; // The lanes we're rendering let workInProgressRootRenderLanes: Lanes = NoLanes; +// Only used when enableProfilerNestedUpdateScheduledHook is true; +// to track which root is currently committing layout effects. +let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null; + // Stack that allows components to change the render lanes for its subtree // This is a superset of the lanes we started working on at the root. The only // case where it's different from `workInProgressRootRenderLanes` is when we @@ -509,6 +515,30 @@ export function scheduleUpdateOnFiber( // Mark that the root has a pending update. markRootUpdated(root, lane, eventTime); + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + if ( + executionContext === CommitContext && + root === rootCommittingMutationOrLayoutEffects + ) { + if (fiber.mode & ProfileMode) { + let current = fiber; + while (current !== null) { + if (current.tag === Profiler) { + const {onNestedUpdateScheduled} = current.memoizedProps; + if (typeof onNestedUpdateScheduled === 'function') { + if (enableSchedulerTracing) { + onNestedUpdateScheduled(root.memoizedInteractions); + } else { + onNestedUpdateScheduled(); + } + } + } + current = current.return; + } + } + } + } + if (root === workInProgressRoot) { // Received an update to a tree that's in the middle of rendering. Mark // that there was an interleaved update work on this root. Unless the @@ -1898,6 +1928,10 @@ function commitRootImpl(root, renderPriorityLevel) { recordCommitTime(); } + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + rootCommittingMutationOrLayoutEffects = root; + } + // The next phase is the mutation phase, where we mutate the host tree. commitMutationEffects(finishedWork, root, renderPriorityLevel); @@ -1936,6 +1970,10 @@ function commitRootImpl(root, renderPriorityLevel) { markLayoutEffectsStopped(); } + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + rootCommittingMutationOrLayoutEffects = null; + } + // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. requestPaint(); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index b2476dad5b615..609bd8016b9cb 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -23,6 +23,7 @@ import { enableProfilerTimer, enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, + enableProfilerNestedUpdateScheduledHook, enableSchedulerTracing, warnAboutUnmockedScheduler, deferRenderPhaseUpdateToNextBatch, @@ -112,6 +113,7 @@ import { OffscreenComponent, LegacyHiddenComponent, ScopeComponent, + Profiler, } from './ReactWorkTags'; import {LegacyRoot} from './ReactRootTags'; import { @@ -329,6 +331,10 @@ let hasUncaughtError = false; let firstUncaughtError = null; let legacyErrorBoundariesThatAlreadyFailed: Set | null = null; +// Only used when enableProfilerNestedUpdateScheduledHook is true; +// to track which root is currently committing layout effects. +let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null; + let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority; @@ -533,6 +539,30 @@ export function scheduleUpdateOnFiber( // Mark that the root has a pending update. markRootUpdated(root, lane, eventTime); + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + if ( + executionContext === CommitContext && + root === rootCommittingMutationOrLayoutEffects + ) { + if (fiber.mode & ProfileMode) { + let current = fiber; + while (current !== null) { + if (current.tag === Profiler) { + const {onNestedUpdateScheduled} = current.memoizedProps; + if (typeof onNestedUpdateScheduled === 'function') { + if (enableSchedulerTracing) { + onNestedUpdateScheduled(root.memoizedInteractions); + } else { + onNestedUpdateScheduled(); + } + } + } + current = current.return; + } + } + } + } + if (root === workInProgressRoot) { // Received an update to a tree that's in the middle of rendering. Mark // that there was an interleaved update work on this root. Unless the @@ -2047,6 +2077,12 @@ function commitRootImpl(root, renderPriorityLevel) { recordCommitTime(); } + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + // Track the root here, rather than in commitLayoutEffects(), because of ref setters. + // Updates scheduled during ref detachment should also be flagged. + rootCommittingMutationOrLayoutEffects = root; + } + // The next phase is the mutation phase, where we mutate the host tree. nextEffect = firstEffect; do { @@ -2112,6 +2148,10 @@ function commitRootImpl(root, renderPriorityLevel) { nextEffect = null; + if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { + rootCommittingMutationOrLayoutEffects = null; + } + // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. requestPaint(); diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index d9c70ae73b75c..92b30d9280fab 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -29,6 +29,7 @@ function loadModules({ enableProfilerTimer = true, enableProfilerCommitHooks = true, enableProfilerNestedUpdatePhase = true, + enableProfilerNestedUpdateScheduledHook = false, enableSchedulerTracing = true, replayFailedUnitOfWorkWithInvokeGuardedCallback = false, useNoopRenderer = false, @@ -38,6 +39,7 @@ function loadModules({ ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer; ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks; ReactFeatureFlags.enableProfilerNestedUpdatePhase = enableProfilerNestedUpdatePhase; + ReactFeatureFlags.enableProfilerNestedUpdateScheduledHook = enableProfilerNestedUpdateScheduledHook; ReactFeatureFlags.enableSchedulerTracing = enableSchedulerTracing; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = replayFailedUnitOfWorkWithInvokeGuardedCallback; @@ -2438,6 +2440,528 @@ describe('Profiler', () => { }); } }); + + describe(`onNestedUpdateScheduled enableSchedulerTracing:${ + enableSchedulerTracing ? 'enabled' : 'disabled' + }`, () => { + beforeEach(() => { + jest.resetModules(); + + loadModules({ + enableProfilerNestedUpdateScheduledHook: true, + enableSchedulerTracing, + useNoopRenderer: true, + }); + }); + + it('is not called when the legacy render API is used to schedule an update', () => { + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.renderLegacySyncRoot( + +
initial
+
, + ); + + ReactNoop.renderLegacySyncRoot( + +
update
+
, + ); + + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('is not called when the root API is used to schedule an update', () => { + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.render( + +
initial
+
, + ); + + ReactNoop.render( + +
update
+
, + ); + + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('is called when a function component schedules an update during a layout effect', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useLayoutEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } + + const interactionCreation = { + id: 0, + name: 'creation event', + timestamp: Scheduler.unstable_now(), + }; + + const onNestedUpdateScheduled = jest.fn(); + + SchedulerTracing.unstable_trace( + interactionCreation.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(onNestedUpdateScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionCreation, + ]); + } + }); + + it('bubbles up and calls all ancestor Profilers', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useLayoutEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } + const onNestedUpdateScheduledOne = jest.fn(); + const onNestedUpdateScheduledTwo = jest.fn(); + const onNestedUpdateScheduledThree = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + <> + + + + + , + ); + }); + + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduledOne).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduledTwo).toHaveBeenCalledTimes(1); + expect(onNestedUpdateScheduledThree).not.toHaveBeenCalled(); + }); + + it('is not called when an update is scheduled for another doort during a layout effect', () => { + const setStateRef = React.createRef(null); + + function ComponentRootOne() { + const [state, setState] = React.useState(false); + setStateRef.current = setState; + Scheduler.unstable_yieldValue(`ComponentRootOne:${state}`); + return state; + } + + function ComponentRootTwo() { + React.useLayoutEffect(() => { + setStateRef.current(true); + }, []); + Scheduler.unstable_yieldValue('ComponentRootTwo'); + return null; + } + + const interactionCreation = { + id: 0, + name: 'creation event', + timestamp: Scheduler.unstable_now(), + }; + + const onNestedUpdateScheduled = jest.fn(); + + SchedulerTracing.unstable_trace( + interactionCreation.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.renderToRootWithID( + + + , + 1, + ); + + ReactNoop.renderToRootWithID( + + + , + 2, + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded([ + 'ComponentRootOne:false', + 'ComponentRootTwo', + 'ComponentRootOne:true', + ]); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('is not called when a function component schedules an update during a passive effect', () => { + function Component() { + const [didMount, setDidMount] = React.useState(false); + React.useEffect(() => { + setDidMount(true); + }, []); + Scheduler.unstable_yieldValue(`Component:${didMount}`); + return didMount; + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('is not called when a function component schedules an update outside of render', () => { + const updateFnRef = React.createRef(null); + + function Component() { + const [state, setState] = React.useState(false); + updateFnRef.current = () => setState(true); + Scheduler.unstable_yieldValue(`Component:${state}`); + return state; + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false']); + + ReactNoop.act(() => { + updateFnRef.current(); + }); + expect(Scheduler).toHaveYielded(['Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('it is not called when a component schedules an update during render', () => { + function Component() { + const [state, setState] = React.useState(false); + if (state === false) { + setState(true); + } + Scheduler.unstable_yieldValue(`Component:${state}`); + return state; + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + + it('it is called when a component schedules an update from a ref callback', () => { + function Component({mountChild}) { + const [refAttached, setRefAttached] = React.useState(false); + const [refDetached, setRefDetached] = React.useState(false); + const refSetter = React.useCallback(ref => { + if (ref !== null) { + setRefAttached(true); + } else { + setRefDetached(true); + } + }, []); + Scheduler.unstable_yieldValue( + `Component:${refAttached}:${refDetached}`, + ); + return mountChild ?
: null; + } + + const onNestedUpdateScheduled = jest.fn(); + + const interactionCreation = { + id: 0, + name: 'creation event', + timestamp: Scheduler.unstable_now(), + }; + + SchedulerTracing.unstable_trace( + interactionCreation.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded([ + 'Component:false:false', + 'Component:true:false', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(onNestedUpdateScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionCreation, + ]); + } + + const interactionUpdate = { + id: 1, + name: 'update event', + timestamp: Scheduler.unstable_now(), + }; + + SchedulerTracing.unstable_trace( + interactionUpdate.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded([ + 'Component:true:false', + 'Component:true:true', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(2); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(onNestedUpdateScheduled.mock.calls[1][0]).toMatchInteractions([ + interactionUpdate, + ]); + } + }); + + it('is called when a class component schedules an update from the componentDidMount lifecycles', () => { + class Component extends React.Component { + state = { + value: false, + }; + componentDidMount() { + this.setState({value: true}); + } + render() { + const {value} = this.state; + Scheduler.unstable_yieldValue(`Component:${value}`); + return value; + } + } + + const interactionCreation = { + id: 0, + name: 'creation event', + timestamp: Scheduler.unstable_now(), + }; + + const onNestedUpdateScheduled = jest.fn(); + + SchedulerTracing.unstable_trace( + interactionCreation.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(onNestedUpdateScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionCreation, + ]); + } + }); + + it('is called when a class component schedules an update from the componentDidUpdate lifecycles', () => { + class Component extends React.Component { + state = { + nestedUpdateSheduled: false, + }; + componentDidUpdate(prevProps, prevState) { + if ( + this.props.scheduleNestedUpdate && + !this.state.nestedUpdateSheduled + ) { + this.setState({nestedUpdateSheduled: true}); + } + } + render() { + const {scheduleNestedUpdate} = this.props; + const {nestedUpdateSheduled} = this.state; + Scheduler.unstable_yieldValue( + `Component:${scheduleNestedUpdate}:${nestedUpdateSheduled}`, + ); + return nestedUpdateSheduled; + } + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false:false']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + + const interactionCreation = { + id: 0, + name: 'creation event', + timestamp: Scheduler.unstable_now(), + }; + + SchedulerTracing.unstable_trace( + interactionCreation.name, + Scheduler.unstable_now(), + () => { + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + }, + ); + + expect(Scheduler).toHaveYielded([ + 'Component:true:false', + 'Component:true:true', + ]); + expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1); + if (ReactFeatureFlags.enableSchedulerTracing) { + expect(onNestedUpdateScheduled.mock.calls[0][0]).toMatchInteractions([ + interactionCreation, + ]); + } + }); + + it('is not called when a class component schedules an update outside of render', () => { + const updateFnRef = React.createRef(null); + + class Component extends React.Component { + state = { + value: false, + }; + render() { + const {value} = this.state; + updateFnRef.current = () => this.setState({value: true}); + Scheduler.unstable_yieldValue(`Component:${value}`); + return value; + } + } + + const onNestedUpdateScheduled = jest.fn(); + + ReactNoop.act(() => { + ReactNoop.render( + + + , + ); + }); + expect(Scheduler).toHaveYielded(['Component:false']); + + ReactNoop.act(() => { + updateFnRef.current(); + }); + expect(Scheduler).toHaveYielded(['Component:true']); + expect(onNestedUpdateScheduled).not.toHaveBeenCalled(); + }); + }); }); describe('interaction tracing', () => { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index b796e5b1ffacc..c2c4ada6260de 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -39,6 +39,10 @@ export const enableProfilerCommitHooks = false; // Phase param passed to onRender callback differentiates between an "update" and a "cascading-update". export const enableProfilerNestedUpdatePhase = false; +// Profiler API accepts a function to be called when a nested update is scheduled. +// This callback accepts the component type (class instance or function) the update is scheduled for. +export const enableProfilerNestedUpdateScheduledHook = false; + // Trace which interactions trigger each commit. export const enableSchedulerTracing = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 5a5bc9ea85f80..461267619d823 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -16,6 +16,7 @@ export const enableSchedulingProfiler = false; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 82c57055f1c0c..d3b55536eaef7 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -18,6 +18,7 @@ export const warnAboutDeprecatedLifecycles = true; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 6a572127e5ea2..b20b1b1da03e0 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -18,6 +18,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 2fae5f7e2bdaa..b0da73bc65fb8 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -18,6 +18,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index d254b9274ad3a..cfa4816166c0c 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -18,6 +18,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index 7b470ba5bf674..757e99c6e1dc9 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -18,6 +18,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = __PROFILE__; export const enableSuspenseServerRenderer = false; export const enableSelectiveHydration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index a0bc67a833bda..1de76e52c3bb1 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -18,6 +18,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false; export const enableProfilerTimer = false; export const enableProfilerCommitHooks = false; export const enableProfilerNestedUpdatePhase = false; +export const enableProfilerNestedUpdateScheduledHook = false; export const enableSchedulerTracing = false; export const enableSuspenseServerRenderer = true; export const enableSelectiveHydration = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index fee39cd71aa41..95ba1859e1507 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -48,3 +48,5 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; export const enableDoubleInvokingEffects = false; export const enableUseRefAccessWarning = __VARIANT__; + +export const enableProfilerNestedUpdateScheduledHook = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 2e8d9d39cfa43..947b05ab85302 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -36,6 +36,8 @@ export const { export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__; +export const enableProfilerNestedUpdateScheduledHook = + __PROFILE__ && dynamicFeatureFlags.enableProfilerNestedUpdateScheduledHook; // Logs additional User Timing API marks for use with an experimental profiling tool. export const enableSchedulingProfiler = __PROFILE__;