import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack';
import type {SuspenseProps, SuspenseState} from './ReactFiberSuspenseComponent';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
import {createCursor, push, pop} from './ReactFiberStack';
import {isCurrentTreeHidden} from './ReactFiberHiddenContext';
import {OffscreenComponent} from './ReactWorkTags';
const suspenseHandlerStackCursor: StackCursor<Fiber | null> =
createCursor(null);
let shellBoundary: Fiber | null = null;
export function getShellBoundary(): Fiber | null {
return shellBoundary;
}
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
const current = handler.alternate;
const props: SuspenseProps = handler.pendingProps;
if (
enableSuspenseAvoidThisFallback &&
props.unstable_avoidThisFallback === true &&
(current === null || isCurrentTreeHidden())
) {
if (shellBoundary === null) {
push(suspenseHandlerStackCursor, handler, handler);
} else {
const handlerOnStack = suspenseHandlerStackCursor.current;
push(suspenseHandlerStackCursor, handlerOnStack, handler);
}
return;
}
push(suspenseHandlerStackCursor, handler, handler);
if (shellBoundary === null) {
if (current === null || isCurrentTreeHidden()) {
shellBoundary = handler;
} else {
const prevState: SuspenseState = current.memoizedState;
if (prevState !== null) {
shellBoundary = handler;
}
}
}
}
export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
reuseSuspenseHandlerOnStack(fiber);
}
export function pushOffscreenSuspenseHandler(fiber: Fiber): void {
if (fiber.tag === OffscreenComponent) {
push(suspenseHandlerStackCursor, fiber, fiber);
if (shellBoundary !== null) {
} else {
const current = fiber.alternate;
if (current !== null) {
const prevState: OffscreenState = current.memoizedState;
if (prevState !== null) {
shellBoundary = fiber;
}
}
}
} else {
reuseSuspenseHandlerOnStack(fiber);
}
}
export function reuseSuspenseHandlerOnStack(fiber: Fiber) {
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
}
export function getSuspenseHandler(): Fiber | null {
return suspenseHandlerStackCursor.current;
}
export function popSuspenseHandler(fiber: Fiber): void {
pop(suspenseHandlerStackCursor, fiber);
if (shellBoundary === fiber) {
shellBoundary = null;
}
}
export opaque type SuspenseContext = number;
export opaque type SubtreeSuspenseContext: SuspenseContext = number;
export opaque type ShallowSuspenseContext: SuspenseContext = number;
const DefaultSuspenseContext: SuspenseContext = 0b00;
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
DefaultSuspenseContext,
);
export function hasSuspenseListContext(
parentContext: SuspenseContext,
flag: SuspenseContext,
): boolean {
return (parentContext & flag) !== 0;
}
export function setDefaultShallowSuspenseListContext(
parentContext: SuspenseContext,
): SuspenseContext {
return parentContext & SubtreeSuspenseContextMask;
}
export function setShallowSuspenseListContext(
parentContext: SuspenseContext,
shallowContext: ShallowSuspenseContext,
): SuspenseContext {
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
}
export function pushSuspenseListContext(
fiber: Fiber,
newContext: SuspenseContext,
): void {
push(suspenseStackCursor, newContext, fiber);
}
export function popSuspenseListContext(fiber: Fiber): void {
pop(suspenseStackCursor, fiber);
}