1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {Fiber} from './ReactInternalTypes';
    
  11. import type {Container, SuspenseInstance} from './ReactFiberConfig';
    
  12. import type {SuspenseState} from './ReactFiberSuspenseComponent';
    
  13. 
    
  14. import {get as getInstance} from 'shared/ReactInstanceMap';
    
  15. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  16. import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
    
  17. import {
    
  18.   ClassComponent,
    
  19.   HostComponent,
    
  20.   HostHoistable,
    
  21.   HostSingleton,
    
  22.   HostRoot,
    
  23.   HostPortal,
    
  24.   HostText,
    
  25.   SuspenseComponent,
    
  26. } from './ReactWorkTags';
    
  27. import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
    
  28. import {enableFloat, enableHostSingletons} from 'shared/ReactFeatureFlags';
    
  29. 
    
  30. const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
    
  31. 
    
  32. export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
    
  33.   let node = fiber;
    
  34.   let nearestMounted: null | Fiber = fiber;
    
  35.   if (!fiber.alternate) {
    
  36.     // If there is no alternate, this might be a new tree that isn't inserted
    
  37.     // yet. If it is, then it will have a pending insertion effect on it.
    
  38.     let nextNode: Fiber = node;
    
  39.     do {
    
  40.       node = nextNode;
    
  41.       if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
    
  42.         // This is an insertion or in-progress hydration. The nearest possible
    
  43.         // mounted fiber is the parent but we need to continue to figure out
    
  44.         // if that one is still mounted.
    
  45.         nearestMounted = node.return;
    
  46.       }
    
  47.       // $FlowFixMe[incompatible-type] we bail out when we get a null
    
  48.       nextNode = node.return;
    
  49.     } while (nextNode);
    
  50.   } else {
    
  51.     while (node.return) {
    
  52.       node = node.return;
    
  53.     }
    
  54.   }
    
  55.   if (node.tag === HostRoot) {
    
  56.     // TODO: Check if this was a nested HostRoot when used with
    
  57.     // renderContainerIntoSubtree.
    
  58.     return nearestMounted;
    
  59.   }
    
  60.   // If we didn't hit the root, that means that we're in an disconnected tree
    
  61.   // that has been unmounted.
    
  62.   return null;
    
  63. }
    
  64. 
    
  65. export function getSuspenseInstanceFromFiber(
    
  66.   fiber: Fiber,
    
  67. ): null | SuspenseInstance {
    
  68.   if (fiber.tag === SuspenseComponent) {
    
  69.     let suspenseState: SuspenseState | null = fiber.memoizedState;
    
  70.     if (suspenseState === null) {
    
  71.       const current = fiber.alternate;
    
  72.       if (current !== null) {
    
  73.         suspenseState = current.memoizedState;
    
  74.       }
    
  75.     }
    
  76.     if (suspenseState !== null) {
    
  77.       return suspenseState.dehydrated;
    
  78.     }
    
  79.   }
    
  80.   return null;
    
  81. }
    
  82. 
    
  83. export function getContainerFromFiber(fiber: Fiber): null | Container {
    
  84.   return fiber.tag === HostRoot
    
  85.     ? (fiber.stateNode.containerInfo: Container)
    
  86.     : null;
    
  87. }
    
  88. 
    
  89. export function isFiberMounted(fiber: Fiber): boolean {
    
  90.   return getNearestMountedFiber(fiber) === fiber;
    
  91. }
    
  92. 
    
  93. export function isMounted(component: React$Component<any, any>): boolean {
    
  94.   if (__DEV__) {
    
  95.     const owner = (ReactCurrentOwner.current: any);
    
  96.     if (owner !== null && owner.tag === ClassComponent) {
    
  97.       const ownerFiber: Fiber = owner;
    
  98.       const instance = ownerFiber.stateNode;
    
  99.       if (!instance._warnedAboutRefsInRender) {
    
  100.         console.error(
    
  101.           '%s is accessing isMounted inside its render() function. ' +
    
  102.             'render() should be a pure function of props and state. It should ' +
    
  103.             'never access something that requires stale data from the previous ' +
    
  104.             'render, such as refs. Move this logic to componentDidMount and ' +
    
  105.             'componentDidUpdate instead.',
    
  106.           getComponentNameFromFiber(ownerFiber) || 'A component',
    
  107.         );
    
  108.       }
    
  109.       instance._warnedAboutRefsInRender = true;
    
  110.     }
    
  111.   }
    
  112. 
    
  113.   const fiber: ?Fiber = getInstance(component);
    
  114.   if (!fiber) {
    
  115.     return false;
    
  116.   }
    
  117.   return getNearestMountedFiber(fiber) === fiber;
    
  118. }
    
  119. 
    
  120. function assertIsMounted(fiber: Fiber) {
    
  121.   if (getNearestMountedFiber(fiber) !== fiber) {
    
  122.     throw new Error('Unable to find node on an unmounted component.');
    
  123.   }
    
  124. }
    
  125. 
    
  126. export function findCurrentFiberUsingSlowPath(fiber: Fiber): Fiber | null {
    
  127.   const alternate = fiber.alternate;
    
  128.   if (!alternate) {
    
  129.     // If there is no alternate, then we only need to check if it is mounted.
    
  130.     const nearestMounted = getNearestMountedFiber(fiber);
    
  131. 
    
  132.     if (nearestMounted === null) {
    
  133.       throw new Error('Unable to find node on an unmounted component.');
    
  134.     }
    
  135. 
    
  136.     if (nearestMounted !== fiber) {
    
  137.       return null;
    
  138.     }
    
  139.     return fiber;
    
  140.   }
    
  141.   // If we have two possible branches, we'll walk backwards up to the root
    
  142.   // to see what path the root points to. On the way we may hit one of the
    
  143.   // special cases and we'll deal with them.
    
  144.   let a: Fiber = fiber;
    
  145.   let b: Fiber = alternate;
    
  146.   while (true) {
    
  147.     const parentA = a.return;
    
  148.     if (parentA === null) {
    
  149.       // We're at the root.
    
  150.       break;
    
  151.     }
    
  152.     const parentB = parentA.alternate;
    
  153.     if (parentB === null) {
    
  154.       // There is no alternate. This is an unusual case. Currently, it only
    
  155.       // happens when a Suspense component is hidden. An extra fragment fiber
    
  156.       // is inserted in between the Suspense fiber and its children. Skip
    
  157.       // over this extra fragment fiber and proceed to the next parent.
    
  158.       const nextParent = parentA.return;
    
  159.       if (nextParent !== null) {
    
  160.         a = b = nextParent;
    
  161.         continue;
    
  162.       }
    
  163.       // If there's no parent, we're at the root.
    
  164.       break;
    
  165.     }
    
  166. 
    
  167.     // If both copies of the parent fiber point to the same child, we can
    
  168.     // assume that the child is current. This happens when we bailout on low
    
  169.     // priority: the bailed out fiber's child reuses the current child.
    
  170.     if (parentA.child === parentB.child) {
    
  171.       let child = parentA.child;
    
  172.       while (child) {
    
  173.         if (child === a) {
    
  174.           // We've determined that A is the current branch.
    
  175.           assertIsMounted(parentA);
    
  176.           return fiber;
    
  177.         }
    
  178.         if (child === b) {
    
  179.           // We've determined that B is the current branch.
    
  180.           assertIsMounted(parentA);
    
  181.           return alternate;
    
  182.         }
    
  183.         child = child.sibling;
    
  184.       }
    
  185. 
    
  186.       // We should never have an alternate for any mounting node. So the only
    
  187.       // way this could possibly happen is if this was unmounted, if at all.
    
  188.       throw new Error('Unable to find node on an unmounted component.');
    
  189.     }
    
  190. 
    
  191.     if (a.return !== b.return) {
    
  192.       // The return pointer of A and the return pointer of B point to different
    
  193.       // fibers. We assume that return pointers never criss-cross, so A must
    
  194.       // belong to the child set of A.return, and B must belong to the child
    
  195.       // set of B.return.
    
  196.       a = parentA;
    
  197.       b = parentB;
    
  198.     } else {
    
  199.       // The return pointers point to the same fiber. We'll have to use the
    
  200.       // default, slow path: scan the child sets of each parent alternate to see
    
  201.       // which child belongs to which set.
    
  202.       //
    
  203.       // Search parent A's child set
    
  204.       let didFindChild = false;
    
  205.       let child = parentA.child;
    
  206.       while (child) {
    
  207.         if (child === a) {
    
  208.           didFindChild = true;
    
  209.           a = parentA;
    
  210.           b = parentB;
    
  211.           break;
    
  212.         }
    
  213.         if (child === b) {
    
  214.           didFindChild = true;
    
  215.           b = parentA;
    
  216.           a = parentB;
    
  217.           break;
    
  218.         }
    
  219.         child = child.sibling;
    
  220.       }
    
  221.       if (!didFindChild) {
    
  222.         // Search parent B's child set
    
  223.         child = parentB.child;
    
  224.         while (child) {
    
  225.           if (child === a) {
    
  226.             didFindChild = true;
    
  227.             a = parentB;
    
  228.             b = parentA;
    
  229.             break;
    
  230.           }
    
  231.           if (child === b) {
    
  232.             didFindChild = true;
    
  233.             b = parentB;
    
  234.             a = parentA;
    
  235.             break;
    
  236.           }
    
  237.           child = child.sibling;
    
  238.         }
    
  239. 
    
  240.         if (!didFindChild) {
    
  241.           throw new Error(
    
  242.             'Child was not found in either parent set. This indicates a bug ' +
    
  243.               'in React related to the return pointer. Please file an issue.',
    
  244.           );
    
  245.         }
    
  246.       }
    
  247.     }
    
  248. 
    
  249.     if (a.alternate !== b) {
    
  250.       throw new Error(
    
  251.         "Return fibers should always be each others' alternates. " +
    
  252.           'This error is likely caused by a bug in React. Please file an issue.',
    
  253.       );
    
  254.     }
    
  255.   }
    
  256. 
    
  257.   // If the root is not a host container, we're in a disconnected tree. I.e.
    
  258.   // unmounted.
    
  259.   if (a.tag !== HostRoot) {
    
  260.     throw new Error('Unable to find node on an unmounted component.');
    
  261.   }
    
  262. 
    
  263.   if (a.stateNode.current === a) {
    
  264.     // We've determined that A is the current branch.
    
  265.     return fiber;
    
  266.   }
    
  267.   // Otherwise B has to be current branch.
    
  268.   return alternate;
    
  269. }
    
  270. 
    
  271. export function findCurrentHostFiber(parent: Fiber): Fiber | null {
    
  272.   const currentParent = findCurrentFiberUsingSlowPath(parent);
    
  273.   return currentParent !== null
    
  274.     ? findCurrentHostFiberImpl(currentParent)
    
  275.     : null;
    
  276. }
    
  277. 
    
  278. function findCurrentHostFiberImpl(node: Fiber): Fiber | null {
    
  279.   // Next we'll drill down this component to find the first HostComponent/Text.
    
  280.   const tag = node.tag;
    
  281.   if (
    
  282.     tag === HostComponent ||
    
  283.     (enableFloat ? tag === HostHoistable : false) ||
    
  284.     (enableHostSingletons ? tag === HostSingleton : false) ||
    
  285.     tag === HostText
    
  286.   ) {
    
  287.     return node;
    
  288.   }
    
  289. 
    
  290.   let child = node.child;
    
  291.   while (child !== null) {
    
  292.     const match = findCurrentHostFiberImpl(child);
    
  293.     if (match !== null) {
    
  294.       return match;
    
  295.     }
    
  296.     child = child.sibling;
    
  297.   }
    
  298. 
    
  299.   return null;
    
  300. }
    
  301. 
    
  302. export function findCurrentHostFiberWithNoPortals(parent: Fiber): Fiber | null {
    
  303.   const currentParent = findCurrentFiberUsingSlowPath(parent);
    
  304.   return currentParent !== null
    
  305.     ? findCurrentHostFiberWithNoPortalsImpl(currentParent)
    
  306.     : null;
    
  307. }
    
  308. 
    
  309. function findCurrentHostFiberWithNoPortalsImpl(node: Fiber): Fiber | null {
    
  310.   // Next we'll drill down this component to find the first HostComponent/Text.
    
  311.   const tag = node.tag;
    
  312.   if (
    
  313.     tag === HostComponent ||
    
  314.     (enableFloat ? tag === HostHoistable : false) ||
    
  315.     (enableHostSingletons ? tag === HostSingleton : false) ||
    
  316.     tag === HostText
    
  317.   ) {
    
  318.     return node;
    
  319.   }
    
  320. 
    
  321.   let child = node.child;
    
  322.   while (child !== null) {
    
  323.     if (child.tag !== HostPortal) {
    
  324.       const match = findCurrentHostFiberWithNoPortalsImpl(child);
    
  325.       if (match !== null) {
    
  326.         return match;
    
  327.       }
    
  328.     }
    
  329.     child = child.sibling;
    
  330.   }
    
  331. 
    
  332.   return null;
    
  333. }
    
  334. 
    
  335. export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
    
  336.   const memoizedState = fiber.memoizedState;
    
  337.   return (
    
  338.     fiber.tag === SuspenseComponent &&
    
  339.     memoizedState !== null &&
    
  340.     memoizedState.dehydrated === null
    
  341.   );
    
  342. }
    
  343. 
    
  344. export function doesFiberContain(
    
  345.   parentFiber: Fiber,
    
  346.   childFiber: Fiber,
    
  347. ): boolean {
    
  348.   let node: null | Fiber = childFiber;
    
  349.   const parentFiberAlternate = parentFiber.alternate;
    
  350.   while (node !== null) {
    
  351.     if (node === parentFiber || node === parentFiberAlternate) {
    
  352.       return true;
    
  353.     }
    
  354.     node = node.return;
    
  355.   }
    
  356.   return false;
    
  357. }