import type {Fiber} from './ReactInternalTypes';
import type {
Container,
ActivityInstance,
SuspenseInstance,
} from './ReactFiberConfig';
import type {ActivityState} from './ReactFiberActivityComponent';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import {
HostComponent,
HostHoistable,
HostSingleton,
HostRoot,
HostPortal,
HostText,
ActivityComponent,
SuspenseComponent,
OffscreenComponent,
} from './ReactWorkTags';
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
let node = fiber;
let nearestMounted: null | Fiber = fiber;
if (!fiber.alternate) {
let nextNode: Fiber = node;
do {
node = nextNode;
if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
nearestMounted = node.return;
}
nextNode = node.return;
} while (nextNode);
} else {
while (node.return) {
node = node.return;
}
}
if (node.tag === HostRoot) {
return nearestMounted;
}
return null;
}
export function getSuspenseInstanceFromFiber(
fiber: Fiber,
): null | SuspenseInstance {
if (fiber.tag === SuspenseComponent) {
let suspenseState: SuspenseState | null = fiber.memoizedState;
if (suspenseState === null) {
const current = fiber.alternate;
if (current !== null) {
suspenseState = current.memoizedState;
}
}
if (suspenseState !== null) {
return suspenseState.dehydrated;
}
}
return null;
}
export function getActivityInstanceFromFiber(
fiber: Fiber,
): null | ActivityInstance {
if (fiber.tag === ActivityComponent) {
let activityState: ActivityState | null = fiber.memoizedState;
if (activityState === null) {
const current = fiber.alternate;
if (current !== null) {
activityState = current.memoizedState;
}
}
if (activityState !== null) {
return activityState.dehydrated;
}
}
return null;
}
export function getContainerFromFiber(fiber: Fiber): null | Container {
return fiber.tag === HostRoot
? (fiber.stateNode.containerInfo: Container)
: null;
}
function assertIsMounted(fiber: Fiber) {
if (getNearestMountedFiber(fiber) !== fiber) {
throw new Error('Unable to find node on an unmounted component.');
}
}
export function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
const alternate = fiber.alternate;
if (!alternate) {
const nearestMounted = getNearestMountedFiber(fiber);
if (nearestMounted === null) {
throw new Error('Unable to find node on an unmounted component.');
}
if (nearestMounted !== fiber) {
return null;
}
return fiber;
}
let a: Fiber = fiber;
let b: Fiber = alternate;
while (true) {
const parentA = a.return;
if (parentA === null) {
break;
}
const parentB = parentA.alternate;
if (parentB === null) {
const nextParent = parentA.return;
if (nextParent !== null) {
a = b = nextParent;
continue;
}
break;
}
if (parentA.child === parentB.child) {
let child = parentA.child;
while (child) {
if (child === a) {
assertIsMounted(parentA);
return fiber;
}
if (child === b) {
assertIsMounted(parentA);
return alternate;
}
child = child.sibling;
}
throw new Error('Unable to find node on an unmounted component.');
}
if (a.return !== b.return) {
a = parentA;
b = parentB;
} else {
let didFindChild = false;
let child = parentA.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentA;
b = parentB;
break;
}
if (child === b) {
didFindChild = true;
b = parentA;
a = parentB;
break;
}
child = child.sibling;
}
if (!didFindChild) {
child = parentB.child;
while (child) {
if (child === a) {
didFindChild = true;
a = parentB;
b = parentA;
break;
}
if (child === b) {
didFindChild = true;
b = parentB;
a = parentA;
break;
}
child = child.sibling;
}
if (!didFindChild) {
throw new Error(
'Child was not found in either parent set. This indicates a bug ' +
'in React related to the return pointer. Please file an issue.',
);
}
}
}
if (a.alternate !== b) {
throw new Error(
"Return fibers should always be each others' alternates. " +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
}
if (a.tag !== HostRoot) {
throw new Error('Unable to find node on an unmounted component.');
}
if (a.stateNode.current === a) {
return fiber;
}
return alternate;
}
export function findCurrentHostFiber(parent: Fiber): Fiber | null {
const currentParent = findCurrentFiberUsingSlowPath(parent);
return currentParent !== null
? findCurrentHostFiberImpl(currentParent)
: null;
}
function findCurrentHostFiberImpl(node: Fiber): Fiber | null {
const tag = node.tag;
if (
tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostText
) {
return node;
}
let child = node.child;
while (child !== null) {
const match = findCurrentHostFiberImpl(child);
if (match !== null) {
return match;
}
child = child.sibling;
}
return null;
}
export function findCurrentHostFiberWithNoPortals(parent: Fiber): Fiber | null {
const currentParent = findCurrentFiberUsingSlowPath(parent);
return currentParent !== null
? findCurrentHostFiberWithNoPortalsImpl(currentParent)
: null;
}
function findCurrentHostFiberWithNoPortalsImpl(node: Fiber): Fiber | null {
const tag = node.tag;
if (
tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostText
) {
return node;
}
let child = node.child;
while (child !== null) {
if (child.tag !== HostPortal) {
const match = findCurrentHostFiberWithNoPortalsImpl(child);
if (match !== null) {
return match;
}
}
child = child.sibling;
}
return null;
}
export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
const memoizedState = fiber.memoizedState;
return (
fiber.tag === SuspenseComponent &&
memoizedState !== null &&
memoizedState.dehydrated === null
);
}
export function doesFiberContain(
parentFiber: Fiber,
childFiber: Fiber,
): boolean {
let node: null | Fiber = childFiber;
const parentFiberAlternate = parentFiber.alternate;
while (node !== null) {
if (node === parentFiber || node === parentFiberAlternate) {
return true;
}
node = node.return;
}
return false;
}
export function traverseFragmentInstance<A, B, C>(
fragmentFiber: Fiber,
fn: (Fiber, A, B, C) => boolean,
a: A,
b: B,
c: C,
): void {
traverseVisibleHostChildren(fragmentFiber.child, false, fn, a, b, c);
}
export function traverseFragmentInstanceDeeply<A, B, C>(
fragmentFiber: Fiber,
fn: (Fiber, A, B, C) => boolean,
a: A,
b: B,
c: C,
): void {
traverseVisibleHostChildren(fragmentFiber.child, true, fn, a, b, c);
}
function traverseVisibleHostChildren<A, B, C>(
child: Fiber | null,
searchWithinHosts: boolean,
fn: (Fiber, A, B, C) => boolean,
a: A,
b: B,
c: C,
): boolean {
while (child !== null) {
if (child.tag === HostComponent && fn(child, a, b, c)) {
return true;
} else if (
child.tag === OffscreenComponent &&
child.memoizedState !== null
) {
} else {
if (
(searchWithinHosts || child.tag !== HostComponent) &&
traverseVisibleHostChildren(child.child, searchWithinHosts, fn, a, b, c)
) {
return true;
}
}
child = child.sibling;
}
return false;
}
export function getFragmentParentHostFiber(fiber: Fiber): null | Fiber {
let parent = fiber.return;
while (parent !== null) {
if (parent.tag === HostRoot || parent.tag === HostComponent) {
return parent;
}
parent = parent.return;
}
return null;
}
export function getInstanceFromHostFiber<I>(fiber: Fiber): I {
switch (fiber.tag) {
case HostComponent:
return fiber.stateNode;
case HostRoot:
return fiber.stateNode.containerInfo;
default:
throw new Error('Expected to find a host node. This is a bug in React.');
}
}
let searchTarget = null;
let searchBoundary = null;
function pushSearchTarget(target: null | Fiber): void {
searchTarget = target;
}
function popSearchTarget(): null | Fiber {
return searchTarget;
}
function pushSearchBoundary(value: null | Fiber): void {
searchBoundary = value;
}
function popSearchBoundary(): null | Fiber {
return searchBoundary;
}
export function getNextSiblingHostFiber(fiber: Fiber): null | Fiber {
traverseVisibleHostChildren(fiber.sibling, false, findNextSibling);
const sibling = popSearchTarget();
pushSearchTarget(null);
return sibling;
}
function findNextSibling(child: Fiber): boolean {
pushSearchTarget(child);
return true;
}
export function isFiberContainedBy(
maybeChild: Fiber,
maybeParent: Fiber,
): boolean {
let parent = maybeParent.return;
if (parent === maybeChild || parent === maybeChild.alternate) {
return true;
}
while (parent !== null && parent !== maybeChild) {
if (
(parent.tag === HostComponent || parent.tag === HostRoot) &&
(parent.return === maybeChild || parent.return === maybeChild.alternate)
) {
return true;
}
parent = parent.return;
}
return false;
}
export function isFiberPreceding(fiber: Fiber, otherFiber: Fiber): boolean {
const commonAncestor = getLowestCommonAncestor(
fiber,
otherFiber,
getParentForFragmentAncestors,
);
if (commonAncestor === null) {
return false;
}
traverseVisibleHostChildren(
commonAncestor,
true,
isFiberPrecedingCheck,
otherFiber,
fiber,
);
const target = popSearchTarget();
pushSearchTarget(null);
return target !== null;
}
function isFiberPrecedingCheck(
child: Fiber,
target: Fiber,
boundary: Fiber,
): boolean {
if (child === boundary) {
return true;
}
if (child === target) {
pushSearchTarget(child);
return true;
}
return false;
}
export function isFiberFollowing(fiber: Fiber, otherFiber: Fiber): boolean {
const commonAncestor = getLowestCommonAncestor(
fiber,
otherFiber,
getParentForFragmentAncestors,
);
if (commonAncestor === null) {
return false;
}
traverseVisibleHostChildren(
commonAncestor,
true,
isFiberFollowingCheck,
otherFiber,
fiber,
);
const target = popSearchTarget();
pushSearchTarget(null);
pushSearchBoundary(null);
return target !== null;
}
function isFiberFollowingCheck(
child: Fiber,
target: Fiber,
boundary: Fiber,
): boolean {
if (child === boundary) {
pushSearchBoundary(child);
return false;
}
if (child === target) {
if (popSearchBoundary() !== null) {
pushSearchTarget(child);
}
return true;
}
return false;
}
function getParentForFragmentAncestors(inst: Fiber | null): Fiber | null {
if (inst === null) {
return null;
}
do {
inst = inst === null ? null : inst.return;
} while (
inst &&
inst.tag !== HostComponent &&
inst.tag !== HostSingleton &&
inst.tag !== HostRoot
);
if (inst) {
return inst;
}
return null;
}
export function getLowestCommonAncestor(
instA: Fiber,
instB: Fiber,
getParent: (inst: Fiber | null) => Fiber | null,
): Fiber | null {
let nodeA: null | Fiber = instA;
let nodeB: null | Fiber = instB;
let depthA = 0;
for (let tempA: null | Fiber = nodeA; tempA; tempA = getParent(tempA)) {
depthA++;
}
let depthB = 0;
for (let tempB: null | Fiber = nodeB; tempB; tempB = getParent(tempB)) {
depthB++;
}
while (depthA - depthB > 0) {
nodeA = getParent(nodeA);
depthA--;
}
while (depthB - depthA > 0) {
nodeB = getParent(nodeB);
depthB--;
}
let depth = depthA;
while (depth--) {
if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
return nodeA;
}
nodeA = getParent(nodeA);
nodeB = getParent(nodeB);
}
return null;
}