From 26d378473edb2055558996ea83d1a11ae5c9fb49 Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Wed, 10 Jul 2024 17:18:01 -0400 Subject: [PATCH] Add unstable context access API for internal profiling --- .../react-debug-tools/src/ReactDebugHooks.js | 2 +- .../react-reconciler/src/ReactFiberHooks.js | 89 +++++++- .../src/ReactFiberNewContext.js | 80 ++++++- .../src/ReactInternalTypes.js | 16 +- .../__tests__/ReactContextWithBailout-test.js | 211 ++++++++++++++++++ packages/react/index.fb.js | 1 + packages/react/src/ReactClient.js | 2 + packages/react/src/ReactHooks.js | 29 +++ packages/shared/ReactFeatureFlags.js | 3 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 2 + 15 files changed, 426 insertions(+), 14 deletions(-) create mode 100644 packages/react-reconciler/src/__tests__/ReactContextWithBailout-test.js diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index d7ffc6626c69da..c39b456bb9857b 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -155,7 +155,7 @@ function getPrimitiveStackCache(): Map> { let currentFiber: null | Fiber = null; let currentHook: null | Hook = null; -let currentContextDependency: null | ContextDependency = null; +let currentContextDependency: null | ContextDependency = null; function nextHook(): null | Hook { const hook = currentHook; diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 5e9d9085457c16..a2567bd64118e1 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -47,6 +47,7 @@ import { enableUseDeferredValueInitialArg, disableLegacyMode, enableNoCloningMemoCache, + enableContextProfiling, } from 'shared/ReactFeatureFlags'; import { REACT_CONTEXT_TYPE, @@ -81,7 +82,11 @@ import { ContinuousEventPriority, higherEventPriority, } from './ReactEventPriorities'; -import {readContext, checkIfContextChanged} from './ReactFiberNewContext'; +import { + readContext, + readContextAndCompare, + checkIfContextChanged, +} from './ReactFiberNewContext'; import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags'; import { LayoutStatic as LayoutStaticEffect, @@ -1053,6 +1058,13 @@ function updateWorkInProgressHook(): Hook { return workInProgressHook; } +function unstable_useContextWithBailout( + context: ReactContext, + compare: void | (T => mixed), +): T { + return readContextAndCompare(context, compare); +} + // NOTE: defining two versions of this function to avoid size impact when this feature is disabled. // Previously this function was inlined, the additional `memoCache` property makes it not inlined. let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue; @@ -3689,6 +3701,10 @@ if (enableAsyncActions) { if (enableAsyncActions) { (ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError; } +if (enableContextProfiling) { + (ContextOnlyDispatcher: Dispatcher).unstable_useContextWithBailout = + throwInvalidHookError; +} const HooksDispatcherOnMount: Dispatcher = { readContext, @@ -3728,6 +3744,10 @@ if (enableAsyncActions) { if (enableAsyncActions) { (HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic; } +if (enableContextProfiling) { + (HooksDispatcherOnMount: Dispatcher).unstable_useContextWithBailout = + unstable_useContextWithBailout; +} const HooksDispatcherOnUpdate: Dispatcher = { readContext, @@ -3767,6 +3787,10 @@ if (enableAsyncActions) { if (enableAsyncActions) { (HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic; } +if (enableContextProfiling) { + (HooksDispatcherOnUpdate: Dispatcher).unstable_useContextWithBailout = + unstable_useContextWithBailout; +} const HooksDispatcherOnRerender: Dispatcher = { readContext, @@ -3806,6 +3830,10 @@ if (enableAsyncActions) { if (enableAsyncActions) { (HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic; } +if (enableContextProfiling) { + (HooksDispatcherOnRerender: Dispatcher).unstable_useContextWithBailout = + unstable_useContextWithBailout; +} let HooksDispatcherOnMountInDEV: Dispatcher | null = null; let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null; @@ -4019,6 +4047,14 @@ if (__DEV__) { return mountOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (HooksDispatcherOnMountInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + mountHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } HooksDispatcherOnMountWithHookTypesInDEV = { readContext(context: ReactContext): T { @@ -4200,6 +4236,14 @@ if (__DEV__) { return mountOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + updateHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } HooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -4380,6 +4424,14 @@ if (__DEV__) { return updateOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + updateHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } HooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -4560,6 +4612,14 @@ if (__DEV__) { return rerenderOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + updateHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } InvalidNestedHooksDispatcherOnMountInDEV = { readContext(context: ReactContext): T { @@ -4766,6 +4826,15 @@ if (__DEV__) { return mountOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (HooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + warnInvalidHookAccess(); + mountHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } InvalidNestedHooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -4972,6 +5041,15 @@ if (__DEV__) { return updateOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } InvalidNestedHooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -5178,4 +5256,13 @@ if (__DEV__) { return rerenderOptimistic(passthrough, reducer); }; } + if (enableContextProfiling) { + (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).unstable_useContextWithBailout = + function (context: ReactContext, compare: void | (T => mixed)): T { + currentHookNameInDev = 'useContext'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return unstable_useContextWithBailout(context, compare); + }; + } } diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index c19e056b0a3cd1..bec21a08686fe2 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -51,6 +51,8 @@ import { getHostTransitionProvider, HostTransitionContext, } from './ReactFiberHostContext'; +import isArray from '../../shared/isArray'; +import {enableContextProfiling} from '../../shared/ReactFeatureFlags'; const valueCursor: StackCursor = createCursor(null); @@ -70,7 +72,7 @@ if (__DEV__) { } let currentlyRenderingFiber: Fiber | null = null; -let lastContextDependency: ContextDependency | null = null; +let lastContextDependency: ContextDependency | null = null; let lastFullyObservedContext: ReactContext | null = null; let isDisallowedContextReadInDEV: boolean = false; @@ -400,8 +402,22 @@ function propagateContextChanges( findContext: for (let i = 0; i < contexts.length; i++) { const context: ReactContext = contexts[i]; // Check if the context matches. - // TODO: Compare selected values to bail out early. if (dependency.context === context) { + const compare = dependency.compare; + if (enableContextProfiling && compare != null) { + const newValue = isPrimaryRenderer + ? dependency.context._currentValue + : dependency.context._currentValue2; + if ( + !checkIfComparedContextValuesChanged( + dependency.lastComparedValue, + compare(newValue), + ) + ) { + // Compared value hasn't changed. Bail out early. + continue findContext; + } + } // Match! Schedule an update on this fiber. // In the lazy implementation, don't mark a dirty flag on the @@ -641,6 +657,28 @@ function propagateParentContextChanges( workInProgress.flags |= DidPropagateContext; } +function checkIfComparedContextValuesChanged( + oldComparedValue: mixed, + newComparedValue: mixed, +): boolean { + if (isArray(oldComparedValue) && isArray(newComparedValue)) { + for ( + let i = 0; + i < oldComparedValue.length && i < newComparedValue.length; + i++ + ) { + if (!is(newComparedValue[i], oldComparedValue[i])) { + return true; + } + } + } else { + if (!is(newComparedValue, oldComparedValue)) { + return true; + } + } + return false; +} + export function checkIfContextChanged( currentDependencies: Dependencies, ): boolean { @@ -659,8 +697,20 @@ export function checkIfContextChanged( ? context._currentValue : context._currentValue2; const oldValue = dependency.memoizedValue; - if (!is(newValue, oldValue)) { - return true; + const compare = dependency.compare; + if (enableContextProfiling && compare != null) { + if ( + checkIfComparedContextValuesChanged( + dependency.lastComparedValue, + compare(newValue), + ) + ) { + return true; + } + } else { + if (!is(newValue, oldValue)) { + return true; + } } dependency = dependency.next; } @@ -694,6 +744,17 @@ export function prepareToReadContext( } } +export function readContextAndCompare( + context: ReactContext, + compare: void | (C => mixed), +): C { + if (!enableLazyContextPropagation) { + return readContext(context); + } + + return readContextForConsumer(currentlyRenderingFiber, context, compare); +} + export function readContext(context: ReactContext): T { if (__DEV__) { // This warning would fire if you read context inside a Hook like useMemo. @@ -721,10 +782,13 @@ export function readContextDuringReconciliation( return readContextForConsumer(consumer, context); } -function readContextForConsumer( +type ContextCompare = C => S; + +function readContextForConsumer( consumer: Fiber | null, - context: ReactContext, -): T { + context: ReactContext, + compare?: void | (C => S), +): C { const value = isPrimaryRenderer ? context._currentValue : context._currentValue2; @@ -736,6 +800,8 @@ function readContextForConsumer( context: ((context: any): ReactContext), memoizedValue: value, next: null, + compare: ((compare: any): ContextCompare | null), + lastComparedValue: compare != null ? compare(value) : null, }; if (lastContextDependency === null) { diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index beee88de2549bd..67c260d78a0e9e 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -61,16 +61,18 @@ export type HookType = | 'useFormState' | 'useActionState'; -export type ContextDependency = { - context: ReactContext, - next: ContextDependency | null, - memoizedValue: T, +export type ContextDependency = { + context: ReactContext, + next: ContextDependency | null, + memoizedValue: C, + compare: (C => S) | null, + lastComparedValue: S | null, ... }; export type Dependencies = { lanes: Lanes, - firstContext: ContextDependency | null, + firstContext: ContextDependency | null, ... }; @@ -384,6 +386,10 @@ export type Dispatcher = { initialArg: I, init?: (I) => S, ): [S, Dispatch], + unstable_useContextWithBailout?: ( + context: ReactContext, + compare: void | (T => mixed), + ) => T, useContext(context: ReactContext): T, useRef(initialValue: T): {current: T}, useEffect( diff --git a/packages/react-reconciler/src/__tests__/ReactContextWithBailout-test.js b/packages/react-reconciler/src/__tests__/ReactContextWithBailout-test.js new file mode 100644 index 00000000000000..eaaa24396673fb --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactContextWithBailout-test.js @@ -0,0 +1,211 @@ +let React; +let ReactNoop; +let Scheduler; +let act; +let assertLog; +let useState; +let useContext; +let unstable_useContextWithBailout; + +describe('ReactContextWithBailout', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); + const testUtils = require('internal-test-utils'); + act = testUtils.act; + assertLog = testUtils.assertLog; + useState = React.useState; + useContext = React.useContext; + React.unstable_useContextWithBailout; + unstable_useContextWithBailout = React.unstable_useContextWithBailout; + }); + + function Text({text}) { + Scheduler.log(text); + return text; + } + + // @gate enableLazyContextPropagation && enableContextProfiling + test('unstable_useContextWithBailout basic usage', async () => { + const Context = React.createContext(); + + let setContext; + function App() { + const [context, _setContext] = useState({a: 'A0', b: 'B0', c: 'C0'}); + setContext = _setContext; + return ( + + + + ); + } + + // Intermediate parent that bails out. Children will only re-render when the + // context changes. + const Indirection = React.memo(() => { + return ( + <> + A: , B: , C: , AB: + + ); + }); + + function A() { + const {a} = unstable_useContextWithBailout(Context, context => context.a); + return ; + } + + function B() { + const {b} = unstable_useContextWithBailout(Context, context => context.b); + return ; + } + + function C() { + const {c} = unstable_useContextWithBailout(Context, context => context.c); + return ; + } + + function AB() { + const {a, b} = unstable_useContextWithBailout(Context, context => [ + context.a, + context.b, + ]); + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + assertLog(['A0', 'B0', 'C0', 'A0B0']); + expect(root).toMatchRenderedOutput('A: A0, B: B0, C: C0, AB: A0B0'); + + // Update a. Only the A and AB consumer should re-render. + await act(async () => { + setContext({a: 'A1', c: 'C0', b: 'B0'}); + }); + assertLog(['A1', 'A1B0']); + expect(root).toMatchRenderedOutput('A: A1, B: B0, C: C0, AB: A1B0'); + + // Update b. Only the B and AB consumer should re-render. + await act(async () => { + setContext({a: 'A1', b: 'B1', c: 'C0'}); + }); + assertLog(['B1', 'A1B1']); + expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C0, AB: A1B1'); + + // Update c. Only the C consumer should re-render. + await act(async () => { + setContext({a: 'A1', b: 'B1', c: 'C1'}); + }); + assertLog(['C1']); + expect(root).toMatchRenderedOutput('A: A1, B: B1, C: C1, AB: A1B1'); + }); + + // @gate enableLazyContextPropagation && enableContextProfiling + test('unstable_useContextWithBailout and useContext subscribing to same context in same component', async () => { + const Context = React.createContext(); + + let setContext; + function App() { + const [context, _setContext] = useState({a: 0, b: 0, unrelated: 0}); + setContext = _setContext; + return ( + + + + ); + } + + // Intermediate parent that bails out. Children will only re-render when the + // context changes. + const Indirection = React.memo(() => { + return ; + }); + + function Child() { + const {a} = unstable_useContextWithBailout(Context, context => context.a); + const context = useContext(Context); + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + assertLog(['A: 0, B: 0']); + expect(root).toMatchRenderedOutput('A: 0, B: 0'); + + // Update an unrelated field that isn't used by the component. The context + // attempts to bail out, but the normal context forces an update. + await act(async () => { + setContext({a: 0, b: 0, unrelated: 1}); + }); + assertLog(['A: 0, B: 0']); + expect(root).toMatchRenderedOutput('A: 0, B: 0'); + }); + + // @gate enableLazyContextPropagation && enableContextProfiling + test('unstable_useContextWithBailout and useContext subscribing to different contexts in same component', async () => { + const ContextA = React.createContext(); + const ContextB = React.createContext(); + + let setContextA; + let setContextB; + function App() { + const [a, _setContextA] = useState({a: 0, unrelated: 0}); + const [b, _setContextB] = useState(0); + setContextA = _setContextA; + setContextB = _setContextB; + return ( + + + + + + ); + } + + // Intermediate parent that bails out. Children will only re-render when the + // context changes. + const Indirection = React.memo(() => { + return ; + }); + + function Child() { + const {a} = unstable_useContextWithBailout( + ContextA, + context => context.a, + ); + const b = useContext(ContextB); + return ; + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + assertLog(['A: 0, B: 0']); + expect(root).toMatchRenderedOutput('A: 0, B: 0'); + + // Update a field in A that isn't part of the compared context. It should + // bail out. + await act(async () => { + setContextA({a: 0, unrelated: 1}); + }); + assertLog([]); + expect(root).toMatchRenderedOutput('A: 0, B: 0'); + + // Now update the same a field again, but this time, also update a different + // context in the same batch. The other context prevents a bail out. + await act(async () => { + setContextA({a: 0, unrelated: 1}); + setContextB(1); + }); + assertLog(['A: 0, B: 1']); + expect(root).toMatchRenderedOutput('A: 0, B: 1'); + }); +}); diff --git a/packages/react/index.fb.js b/packages/react/index.fb.js index 49259d9eaf50fb..1b87e4b2e582fc 100644 --- a/packages/react/index.fb.js +++ b/packages/react/index.fb.js @@ -39,6 +39,7 @@ export { use, useActionState, useCallback, + unstable_useContextWithBailout, useContext, useDebugValue, useDeferredValue, diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index fc668246a988b0..318d8e648d9d5c 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -38,6 +38,7 @@ import {postpone} from './ReactPostpone'; import { getCacheForType, useCallback, + unstable_useContextWithBailout, useContext, useEffect, useEffectEvent, @@ -83,6 +84,7 @@ export { cache, postpone as unstable_postpone, useCallback, + unstable_useContextWithBailout, useContext, useEffect, useEffectEvent as experimental_useEffectEvent, diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 93d9fa28f07f9a..959e4863bc4d17 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -19,6 +19,10 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {enableAsyncActions} from 'shared/ReactFeatureFlags'; +import { + enableContextProfiling, + enableLazyContextPropagation, +} from '../../shared/ReactFeatureFlags'; type BasicStateAction = (S => S) | S; type Dispatch = A => void; @@ -65,6 +69,31 @@ export function useContext(Context: ReactContext): T { return dispatcher.useContext(Context); } +export function unstable_useContextWithBailout( + context: ReactContext, + compare: void | (T => mixed), +): T { + if (!enableContextProfiling) { + throw new Error('Not implemented.'); + } + if (!enableLazyContextPropagation) { + throw new Error( + 'unstable_useContextWithBailout requires enableLazyContextPropagation', + ); + } + const dispatcher = resolveDispatcher(); + if (__DEV__) { + if (context.$$typeof === REACT_CONSUMER_TYPE) { + console.error( + 'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' + + 'Did you mean to call useContext(Context) instead?', + ); + } + } + // $FlowFixMe[not-a-function] This is unstable, thus optional + return dispatcher.unstable_useContextWithBailout(context, compare); +} + export function useState( initialState: (() => S) | S, ): [S, Dispatch>] { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 3c2089cb19c130..ad21915dc0a9f6 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -97,6 +97,9 @@ export const enableTransitionTracing = false; // No known bugs, but needs performance testing export const enableLazyContextPropagation = false; +// Expose unstable useContext for performance testing +export const enableContextProfiling = false; + // FB-only usage. The new API has different semantics. export const enableLegacyHidden = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index b37f2f4fa0c14e..f3687e9af4238f 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -59,6 +59,7 @@ export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = true; export const enableInfiniteRenderLoopDetection = true; export const enableLazyContextPropagation = false; +export const enableContextProfiling = false; export const enableLegacyCache = false; export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 3ef19e4b127e7b..5bc7fee25ca6ba 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -52,6 +52,7 @@ export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = false; export const enableInfiniteRenderLoopDetection = true; export const enableLazyContextPropagation = false; +export const enableContextProfiling = false; export const enableLegacyCache = false; export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 5b01abdb496bf6..cdaf826ad32038 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -52,6 +52,7 @@ export const transitionLaneExpirationMs = 5000; export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; +export const enableContextProfiling = false; export const enableLegacyHidden = false; export const forceConcurrentByDefaultForTesting = false; export const allowConcurrentByDefault = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index ebb8964a5b7411..01dcb98a552a83 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -44,6 +44,7 @@ export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = false; export const enableInfiniteRenderLoopDetection = true; export const enableLazyContextPropagation = false; +export const enableContextProfiling = false; export const enableLegacyCache = false; export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 026c2be4f8c670..1718be98c59972 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -55,6 +55,7 @@ export const transitionLaneExpirationMs = 5000; export const disableSchedulerTimeoutInWorkLoop = false; export const enableLazyContextPropagation = false; +export const enableContextProfiling = false; export const enableLegacyHidden = false; export const forceConcurrentByDefaultForTesting = false; export const allowConcurrentByDefault = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 3671f40ad8d31c..17d00685c27c6c 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -79,6 +79,8 @@ export const enableTaint = false; export const enablePostpone = false; +export const enableContextProfiling = true; + // TODO: www currently relies on this feature. It's disabled in open source. // Need to remove it. export const disableCommentsAsDOMContainers = false;