import type {ReactContext} from 'shared/ReactTypes';
import {isPrimaryRenderer} from './ReactFizzConfig';
let rendererSigil;
if (__DEV__) {
rendererSigil = {};
}
type ContextNode<T> = {
parent: null | ContextNode<any>,
depth: number,
context: ReactContext<T>,
parentValue: T,
value: T,
};
export opaque type ContextSnapshot = null | ContextNode<any>;
export const rootContextSnapshot: ContextSnapshot = null;
let currentActiveSnapshot: ContextSnapshot = null;
function popNode(prev: ContextNode<any>): void {
if (isPrimaryRenderer) {
prev.context._currentValue = prev.parentValue;
} else {
prev.context._currentValue2 = prev.parentValue;
}
}
function pushNode(next: ContextNode<any>): void {
if (isPrimaryRenderer) {
next.context._currentValue = next.value;
} else {
next.context._currentValue2 = next.value;
}
}
function popToNearestCommonAncestor(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
if (prev === next) {
} else {
popNode(prev);
const parentPrev = prev.parent;
const parentNext = next.parent;
if (parentPrev === null) {
if (parentNext !== null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
} else {
if (parentNext === null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
popToNearestCommonAncestor(parentPrev, parentNext);
}
pushNode(next);
}
}
function popAllPrevious(prev: ContextNode<any>): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev !== null) {
popAllPrevious(parentPrev);
}
}
function pushAllNext(next: ContextNode<any>): void {
const parentNext = next.parent;
if (parentNext !== null) {
pushAllNext(parentNext);
}
pushNode(next);
}
function popPreviousToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (parentPrev.depth === next.depth) {
popToNearestCommonAncestor(parentPrev, next);
} else {
popPreviousToCommonLevel(parentPrev, next);
}
}
function popNextToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
const parentNext = next.parent;
if (parentNext === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (prev.depth === parentNext.depth) {
popToNearestCommonAncestor(prev, parentNext);
} else {
popNextToCommonLevel(prev, parentNext);
}
pushNode(next);
}
export function switchContext(newSnapshot: ContextSnapshot): void {
const prev = currentActiveSnapshot;
const next = newSnapshot;
if (prev !== next) {
if (prev === null) {
pushAllNext(next);
} else if (next === null) {
popAllPrevious(prev);
} else if (prev.depth === next.depth) {
popToNearestCommonAncestor(prev, next);
} else if (prev.depth > next.depth) {
popPreviousToCommonLevel(prev, next);
} else {
popNextToCommonLevel(prev, next);
}
currentActiveSnapshot = next;
}
}
export function pushProvider<T>(
context: ReactContext<T>,
nextValue: T,
): ContextSnapshot {
let prevValue;
if (isPrimaryRenderer) {
prevValue = context._currentValue;
context._currentValue = nextValue;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer = rendererSigil;
}
} else {
prevValue = context._currentValue2;
context._currentValue2 = nextValue;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
const prevNode = currentActiveSnapshot;
const newNode: ContextNode<T> = {
parent: prevNode,
depth: prevNode === null ? 0 : prevNode.depth + 1,
context: context,
parentValue: prevValue,
value: nextValue,
};
currentActiveSnapshot = newNode;
return newNode;
}
export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
const prevSnapshot = currentActiveSnapshot;
if (prevSnapshot === null) {
throw new Error(
'Tried to pop a Context at the root of the app. This is a bug in React.',
);
}
if (__DEV__) {
if (prevSnapshot.context !== context) {
console.error(
'The parent context is not the expected context. This is probably a bug in React.',
);
}
}
if (isPrimaryRenderer) {
const value = prevSnapshot.parentValue;
prevSnapshot.context._currentValue = value;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer = rendererSigil;
}
} else {
const value = prevSnapshot.parentValue;
prevSnapshot.context._currentValue2 = value;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
return (currentActiveSnapshot = prevSnapshot.parent);
}
export function getActiveContext(): ContextSnapshot {
return currentActiveSnapshot;
}
export function readContext<T>(context: ReactContext<T>): T {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
return value;
}