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 {createCursor, push, pop} from './ReactFiberStack';
let warnedAboutMissingGetChildContext;
if (__DEV__) {
warnedAboutMissingGetChildContext = ({}: {[string]: boolean});
}
export const emptyContextObject: {} = {};
if (__DEV__) {
Object.freeze(emptyContextObject);
}
const contextStackCursor: StackCursor<Object> =
createCursor(emptyContextObject);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
let previousContext: Object = emptyContextObject;
function getUnmaskedContext(
workInProgress: Fiber,
Component: Function,
didPushOwnContextIfProvider: boolean,
): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
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;
}
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 (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;
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.`,
);
}
}
return {...parentContext, ...childContext};
}
}
function pushContextProvider(workInProgress: Fiber): boolean {
if (disableLegacyContext) {
return false;
} else {
const instance = workInProgress.stateNode;
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyContextObject;
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) {
const mergedContext = processChildContext(
workInProgress,
type,
previousContext,
);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
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 {
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;
}
}
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,
};