/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack';
import {isFiberMounted} from './ReactFiberTreeReflection';
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {ClassComponent, HostRoot} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import checkPropTypes from 'shared/checkPropTypes';
import {createCursor, push, pop} from './ReactFiberStack';
let warnedAboutMissingGetChildContext;
if (__DEV__) {
warnedAboutMissingGetChildContext = ({}: {[string]: boolean});
}export const emptyContextObject: {} = {};
if (__DEV__) {
Object.freeze(emptyContextObject);
}// A cursor to the current merged context object on the stack.const contextStackCursor: StackCursor<Object> =
createCursor(emptyContextObject);
// A cursor to a boolean indicating whether the context has changed.const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.// We use this to get access to the parent context after we have already// pushed the next context provider, and now need to merge their contexts.let previousContext: Object = emptyContextObject;
function getUnmaskedContext(
workInProgress: Fiber,Component: Function,didPushOwnContextIfProvider: boolean,): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}return contextStackCursor.current;
}}function cacheContext(
workInProgress: Fiber,unmaskedContext: Object,maskedContext: Object,): void {if (disableLegacyContext) {
return;
} else {const instance = workInProgress.stateNode;instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;instance.__reactInternalMemoizedMaskedChildContext = maskedContext;}}function getMaskedContext(
workInProgress: Fiber,unmaskedContext: Object,): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {return instance.__reactInternalMemoizedMaskedChildContext;
}const context: {[string]: $FlowFixMe} = {};
for (const key in contextTypes) {
context[key] = unmaskedContext[key];
}if (__DEV__) {
const name = getComponentNameFromFiber(workInProgress) || 'Unknown';
checkPropTypes(contextTypes, context, 'context', name);
}// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}return context;
}}function hasContextChanged(): boolean {
if (disableLegacyContext) {
return false;
} else {
return didPerformWorkStackCursor.current;
}}function isContextProvider(type: Function): boolean {
if (disableLegacyContext) {
return false;
} else {
const childContextTypes = type.childContextTypes;
return childContextTypes !== null && childContextTypes !== undefined;
}}function popContext(fiber: Fiber): void {
if (disableLegacyContext) {
return;
} else {pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}}function popTopLevelContextObject(fiber: Fiber): void {
if (disableLegacyContext) {
return;
} else {pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}}function pushTopLevelContextObject(
fiber: Fiber,context: Object,didChange: boolean,): void {if (disableLegacyContext) {
return;
} else {if (contextStackCursor.current !== emptyContextObject) {
throw new Error(
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);}push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}}function processChildContext(
fiber: Fiber,type: any,parentContext: Object,): Object {
if (disableLegacyContext) {
return parentContext;
} else {
const instance = fiber.stateNode;
const childContextTypes = type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
console.error(
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);}}return parentContext;
}const childContext = instance.getChildContext();
for (const contextKey in childContext) {
if (!(contextKey in childContextTypes)) {
throw new Error(
`${getComponentNameFromFiber(fiber) || 'Unknown'
}.getChildContext(): key "${contextKey}" is not defined in childContextTypes.`,
);}}if (__DEV__) {
const name = getComponentNameFromFiber(fiber) || 'Unknown';
checkPropTypes(childContextTypes, childContext, 'child context', name);
}return {...parentContext, ...childContext};
}}function pushContextProvider(workInProgress: Fiber): boolean {
if (disableLegacyContext) {
return false;
} else {
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyContextObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);return true;
}}function invalidateContextProvider(
workInProgress: Fiber,type: any,didChange: boolean,): void {if (disableLegacyContext) {
return;
} else {const instance = workInProgress.stateNode;if (!instance) {
throw new Error(
'Expected to have an instance by this point. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);}if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
const mergedContext = processChildContext(
workInProgress,
type,
previousContext,
);instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {pop(didPerformWorkStackCursor, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
}}}function findCurrentUnmaskedContext(fiber: Fiber): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
throw new Error(
'Expected subtree parent to be a mounted class component. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);}let node: Fiber = fiber;
do {
switch (node.tag) {
case HostRoot:
return node.stateNode.context;
case ClassComponent: {
const Component = node.type;
if (isContextProvider(Component)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}break;
}}// $FlowFixMe[incompatible-type] we bail out when we get a null
node = node.return;
} while (node !== null);
throw new Error(
'Found unexpected detached subtree parent. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);}}export {
getUnmaskedContext,
cacheContext,
getMaskedContext,
hasContextChanged,
popContext,
popTopLevelContextObject,
pushTopLevelContextObject,
processChildContext,
isContextProvider,
pushContextProvider,
invalidateContextProvider,
findCurrentUnmaskedContext,
};