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. /* eslint-disable react-internal/prod-error-codes */
    
  11. 
    
  12. import type {ReactElement} from 'shared/ReactElementType';
    
  13. import type {Fiber, FiberRoot} from './ReactInternalTypes';
    
  14. import type {Instance} from './ReactFiberConfig';
    
  15. import type {ReactNodeList} from 'shared/ReactTypes';
    
  16. 
    
  17. import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
    
  18. import {
    
  19.   flushSync,
    
  20.   scheduleUpdateOnFiber,
    
  21.   flushPassiveEffects,
    
  22. } from './ReactFiberWorkLoop';
    
  23. import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
    
  24. import {updateContainer} from './ReactFiberReconciler';
    
  25. import {emptyContextObject} from './ReactFiberContext';
    
  26. import {SyncLane} from './ReactFiberLane';
    
  27. import {
    
  28.   ClassComponent,
    
  29.   FunctionComponent,
    
  30.   ForwardRef,
    
  31.   HostComponent,
    
  32.   HostHoistable,
    
  33.   HostSingleton,
    
  34.   HostPortal,
    
  35.   HostRoot,
    
  36.   MemoComponent,
    
  37.   SimpleMemoComponent,
    
  38. } from './ReactWorkTags';
    
  39. import {
    
  40.   REACT_FORWARD_REF_TYPE,
    
  41.   REACT_MEMO_TYPE,
    
  42.   REACT_LAZY_TYPE,
    
  43. } from 'shared/ReactSymbols';
    
  44. import {supportsSingletons} from './ReactFiberConfig';
    
  45. 
    
  46. export type Family = {
    
  47.   current: any,
    
  48. };
    
  49. 
    
  50. export type RefreshUpdate = {
    
  51.   staleFamilies: Set<Family>,
    
  52.   updatedFamilies: Set<Family>,
    
  53. };
    
  54. 
    
  55. // Resolves type to a family.
    
  56. type RefreshHandler = any => Family | void;
    
  57. 
    
  58. // Used by React Refresh runtime through DevTools Global Hook.
    
  59. export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
    
  60. export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
    
  61. export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
    
  62. export type FindHostInstancesForRefresh = (
    
  63.   root: FiberRoot,
    
  64.   families: Array<Family>,
    
  65. ) => Set<Instance>;
    
  66. 
    
  67. let resolveFamily: RefreshHandler | null = null;
    
  68. let failedBoundaries: WeakSet<Fiber> | null = null;
    
  69. 
    
  70. export const setRefreshHandler = (handler: RefreshHandler | null): void => {
    
  71.   if (__DEV__) {
    
  72.     resolveFamily = handler;
    
  73.   }
    
  74. };
    
  75. 
    
  76. export function resolveFunctionForHotReloading(type: any): any {
    
  77.   if (__DEV__) {
    
  78.     if (resolveFamily === null) {
    
  79.       // Hot reloading is disabled.
    
  80.       return type;
    
  81.     }
    
  82.     const family = resolveFamily(type);
    
  83.     if (family === undefined) {
    
  84.       return type;
    
  85.     }
    
  86.     // Use the latest known implementation.
    
  87.     return family.current;
    
  88.   } else {
    
  89.     return type;
    
  90.   }
    
  91. }
    
  92. 
    
  93. export function resolveClassForHotReloading(type: any): any {
    
  94.   // No implementation differences.
    
  95.   return resolveFunctionForHotReloading(type);
    
  96. }
    
  97. 
    
  98. export function resolveForwardRefForHotReloading(type: any): any {
    
  99.   if (__DEV__) {
    
  100.     if (resolveFamily === null) {
    
  101.       // Hot reloading is disabled.
    
  102.       return type;
    
  103.     }
    
  104.     const family = resolveFamily(type);
    
  105.     if (family === undefined) {
    
  106.       // Check if we're dealing with a real forwardRef. Don't want to crash early.
    
  107.       if (
    
  108.         type !== null &&
    
  109.         type !== undefined &&
    
  110.         typeof type.render === 'function'
    
  111.       ) {
    
  112.         // ForwardRef is special because its resolved .type is an object,
    
  113.         // but it's possible that we only have its inner render function in the map.
    
  114.         // If that inner render function is different, we'll build a new forwardRef type.
    
  115.         const currentRender = resolveFunctionForHotReloading(type.render);
    
  116.         if (type.render !== currentRender) {
    
  117.           const syntheticType = {
    
  118.             $$typeof: REACT_FORWARD_REF_TYPE,
    
  119.             render: currentRender,
    
  120.           };
    
  121.           if (type.displayName !== undefined) {
    
  122.             (syntheticType: any).displayName = type.displayName;
    
  123.           }
    
  124.           return syntheticType;
    
  125.         }
    
  126.       }
    
  127.       return type;
    
  128.     }
    
  129.     // Use the latest known implementation.
    
  130.     return family.current;
    
  131.   } else {
    
  132.     return type;
    
  133.   }
    
  134. }
    
  135. 
    
  136. export function isCompatibleFamilyForHotReloading(
    
  137.   fiber: Fiber,
    
  138.   element: ReactElement,
    
  139. ): boolean {
    
  140.   if (__DEV__) {
    
  141.     if (resolveFamily === null) {
    
  142.       // Hot reloading is disabled.
    
  143.       return false;
    
  144.     }
    
  145. 
    
  146.     const prevType = fiber.elementType;
    
  147.     const nextType = element.type;
    
  148. 
    
  149.     // If we got here, we know types aren't === equal.
    
  150.     let needsCompareFamilies = false;
    
  151. 
    
  152.     const $$typeofNextType =
    
  153.       typeof nextType === 'object' && nextType !== null
    
  154.         ? nextType.$$typeof
    
  155.         : null;
    
  156. 
    
  157.     switch (fiber.tag) {
    
  158.       case ClassComponent: {
    
  159.         if (typeof nextType === 'function') {
    
  160.           needsCompareFamilies = true;
    
  161.         }
    
  162.         break;
    
  163.       }
    
  164.       case FunctionComponent: {
    
  165.         if (typeof nextType === 'function') {
    
  166.           needsCompareFamilies = true;
    
  167.         } else if ($$typeofNextType === REACT_LAZY_TYPE) {
    
  168.           // We don't know the inner type yet.
    
  169.           // We're going to assume that the lazy inner type is stable,
    
  170.           // and so it is sufficient to avoid reconciling it away.
    
  171.           // We're not going to unwrap or actually use the new lazy type.
    
  172.           needsCompareFamilies = true;
    
  173.         }
    
  174.         break;
    
  175.       }
    
  176.       case ForwardRef: {
    
  177.         if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
    
  178.           needsCompareFamilies = true;
    
  179.         } else if ($$typeofNextType === REACT_LAZY_TYPE) {
    
  180.           needsCompareFamilies = true;
    
  181.         }
    
  182.         break;
    
  183.       }
    
  184.       case MemoComponent:
    
  185.       case SimpleMemoComponent: {
    
  186.         if ($$typeofNextType === REACT_MEMO_TYPE) {
    
  187.           // TODO: if it was but can no longer be simple,
    
  188.           // we shouldn't set this.
    
  189.           needsCompareFamilies = true;
    
  190.         } else if ($$typeofNextType === REACT_LAZY_TYPE) {
    
  191.           needsCompareFamilies = true;
    
  192.         }
    
  193.         break;
    
  194.       }
    
  195.       default:
    
  196.         return false;
    
  197.     }
    
  198. 
    
  199.     // Check if both types have a family and it's the same one.
    
  200.     if (needsCompareFamilies) {
    
  201.       // Note: memo() and forwardRef() we'll compare outer rather than inner type.
    
  202.       // This means both of them need to be registered to preserve state.
    
  203.       // If we unwrapped and compared the inner types for wrappers instead,
    
  204.       // then we would risk falsely saying two separate memo(Foo)
    
  205.       // calls are equivalent because they wrap the same Foo function.
    
  206.       const prevFamily = resolveFamily(prevType);
    
  207.       // $FlowFixMe[not-a-function] found when upgrading Flow
    
  208.       if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
    
  209.         return true;
    
  210.       }
    
  211.     }
    
  212.     return false;
    
  213.   } else {
    
  214.     return false;
    
  215.   }
    
  216. }
    
  217. 
    
  218. export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
    
  219.   if (__DEV__) {
    
  220.     if (resolveFamily === null) {
    
  221.       // Hot reloading is disabled.
    
  222.       return;
    
  223.     }
    
  224.     if (typeof WeakSet !== 'function') {
    
  225.       return;
    
  226.     }
    
  227.     if (failedBoundaries === null) {
    
  228.       failedBoundaries = new WeakSet();
    
  229.     }
    
  230.     failedBoundaries.add(fiber);
    
  231.   }
    
  232. }
    
  233. 
    
  234. export const scheduleRefresh: ScheduleRefresh = (
    
  235.   root: FiberRoot,
    
  236.   update: RefreshUpdate,
    
  237. ): void => {
    
  238.   if (__DEV__) {
    
  239.     if (resolveFamily === null) {
    
  240.       // Hot reloading is disabled.
    
  241.       return;
    
  242.     }
    
  243.     const {staleFamilies, updatedFamilies} = update;
    
  244.     flushPassiveEffects();
    
  245.     flushSync(() => {
    
  246.       scheduleFibersWithFamiliesRecursively(
    
  247.         root.current,
    
  248.         updatedFamilies,
    
  249.         staleFamilies,
    
  250.       );
    
  251.     });
    
  252.   }
    
  253. };
    
  254. 
    
  255. export const scheduleRoot: ScheduleRoot = (
    
  256.   root: FiberRoot,
    
  257.   element: ReactNodeList,
    
  258. ): void => {
    
  259.   if (__DEV__) {
    
  260.     if (root.context !== emptyContextObject) {
    
  261.       // Super edge case: root has a legacy _renderSubtree context
    
  262.       // but we don't know the parentComponent so we can't pass it.
    
  263.       // Just ignore. We'll delete this with _renderSubtree code path later.
    
  264.       return;
    
  265.     }
    
  266.     flushPassiveEffects();
    
  267.     flushSync(() => {
    
  268.       updateContainer(element, root, null, null);
    
  269.     });
    
  270.   }
    
  271. };
    
  272. 
    
  273. function scheduleFibersWithFamiliesRecursively(
    
  274.   fiber: Fiber,
    
  275.   updatedFamilies: Set<Family>,
    
  276.   staleFamilies: Set<Family>,
    
  277. ): void {
    
  278.   if (__DEV__) {
    
  279.     const {alternate, child, sibling, tag, type} = fiber;
    
  280. 
    
  281.     let candidateType = null;
    
  282.     switch (tag) {
    
  283.       case FunctionComponent:
    
  284.       case SimpleMemoComponent:
    
  285.       case ClassComponent:
    
  286.         candidateType = type;
    
  287.         break;
    
  288.       case ForwardRef:
    
  289.         candidateType = type.render;
    
  290.         break;
    
  291.       default:
    
  292.         break;
    
  293.     }
    
  294. 
    
  295.     if (resolveFamily === null) {
    
  296.       throw new Error('Expected resolveFamily to be set during hot reload.');
    
  297.     }
    
  298. 
    
  299.     let needsRender = false;
    
  300.     let needsRemount = false;
    
  301.     if (candidateType !== null) {
    
  302.       const family = resolveFamily(candidateType);
    
  303.       if (family !== undefined) {
    
  304.         if (staleFamilies.has(family)) {
    
  305.           needsRemount = true;
    
  306.         } else if (updatedFamilies.has(family)) {
    
  307.           if (tag === ClassComponent) {
    
  308.             needsRemount = true;
    
  309.           } else {
    
  310.             needsRender = true;
    
  311.           }
    
  312.         }
    
  313.       }
    
  314.     }
    
  315.     if (failedBoundaries !== null) {
    
  316.       if (
    
  317.         failedBoundaries.has(fiber) ||
    
  318.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  319.         (alternate !== null && failedBoundaries.has(alternate))
    
  320.       ) {
    
  321.         needsRemount = true;
    
  322.       }
    
  323.     }
    
  324. 
    
  325.     if (needsRemount) {
    
  326.       fiber._debugNeedsRemount = true;
    
  327.     }
    
  328.     if (needsRemount || needsRender) {
    
  329.       const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
    
  330.       if (root !== null) {
    
  331.         scheduleUpdateOnFiber(root, fiber, SyncLane);
    
  332.       }
    
  333.     }
    
  334.     if (child !== null && !needsRemount) {
    
  335.       scheduleFibersWithFamiliesRecursively(
    
  336.         child,
    
  337.         updatedFamilies,
    
  338.         staleFamilies,
    
  339.       );
    
  340.     }
    
  341.     if (sibling !== null) {
    
  342.       scheduleFibersWithFamiliesRecursively(
    
  343.         sibling,
    
  344.         updatedFamilies,
    
  345.         staleFamilies,
    
  346.       );
    
  347.     }
    
  348.   }
    
  349. }
    
  350. 
    
  351. export const findHostInstancesForRefresh: FindHostInstancesForRefresh = (
    
  352.   root: FiberRoot,
    
  353.   families: Array<Family>,
    
  354. ): Set<Instance> => {
    
  355.   if (__DEV__) {
    
  356.     const hostInstances = new Set<Instance>();
    
  357.     const types = new Set(families.map(family => family.current));
    
  358.     findHostInstancesForMatchingFibersRecursively(
    
  359.       root.current,
    
  360.       types,
    
  361.       hostInstances,
    
  362.     );
    
  363.     return hostInstances;
    
  364.   } else {
    
  365.     throw new Error(
    
  366.       'Did not expect findHostInstancesForRefresh to be called in production.',
    
  367.     );
    
  368.   }
    
  369. };
    
  370. 
    
  371. function findHostInstancesForMatchingFibersRecursively(
    
  372.   fiber: Fiber,
    
  373.   types: Set<any>,
    
  374.   hostInstances: Set<Instance>,
    
  375. ) {
    
  376.   if (__DEV__) {
    
  377.     const {child, sibling, tag, type} = fiber;
    
  378. 
    
  379.     let candidateType = null;
    
  380.     switch (tag) {
    
  381.       case FunctionComponent:
    
  382.       case SimpleMemoComponent:
    
  383.       case ClassComponent:
    
  384.         candidateType = type;
    
  385.         break;
    
  386.       case ForwardRef:
    
  387.         candidateType = type.render;
    
  388.         break;
    
  389.       default:
    
  390.         break;
    
  391.     }
    
  392. 
    
  393.     let didMatch = false;
    
  394.     if (candidateType !== null) {
    
  395.       if (types.has(candidateType)) {
    
  396.         didMatch = true;
    
  397.       }
    
  398.     }
    
  399. 
    
  400.     if (didMatch) {
    
  401.       // We have a match. This only drills down to the closest host components.
    
  402.       // There's no need to search deeper because for the purpose of giving
    
  403.       // visual feedback, "flashing" outermost parent rectangles is sufficient.
    
  404.       findHostInstancesForFiberShallowly(fiber, hostInstances);
    
  405.     } else {
    
  406.       // If there's no match, maybe there will be one further down in the child tree.
    
  407.       if (child !== null) {
    
  408.         findHostInstancesForMatchingFibersRecursively(
    
  409.           child,
    
  410.           types,
    
  411.           hostInstances,
    
  412.         );
    
  413.       }
    
  414.     }
    
  415. 
    
  416.     if (sibling !== null) {
    
  417.       findHostInstancesForMatchingFibersRecursively(
    
  418.         sibling,
    
  419.         types,
    
  420.         hostInstances,
    
  421.       );
    
  422.     }
    
  423.   }
    
  424. }
    
  425. 
    
  426. function findHostInstancesForFiberShallowly(
    
  427.   fiber: Fiber,
    
  428.   hostInstances: Set<Instance>,
    
  429. ): void {
    
  430.   if (__DEV__) {
    
  431.     const foundHostInstances = findChildHostInstancesForFiberShallowly(
    
  432.       fiber,
    
  433.       hostInstances,
    
  434.     );
    
  435.     if (foundHostInstances) {
    
  436.       return;
    
  437.     }
    
  438.     // If we didn't find any host children, fallback to closest host parent.
    
  439.     let node = fiber;
    
  440.     while (true) {
    
  441.       switch (node.tag) {
    
  442.         case HostSingleton:
    
  443.         case HostComponent:
    
  444.           hostInstances.add(node.stateNode);
    
  445.           return;
    
  446.         case HostPortal:
    
  447.           hostInstances.add(node.stateNode.containerInfo);
    
  448.           return;
    
  449.         case HostRoot:
    
  450.           hostInstances.add(node.stateNode.containerInfo);
    
  451.           return;
    
  452.       }
    
  453.       if (node.return === null) {
    
  454.         throw new Error('Expected to reach root first.');
    
  455.       }
    
  456.       node = node.return;
    
  457.     }
    
  458.   }
    
  459. }
    
  460. 
    
  461. function findChildHostInstancesForFiberShallowly(
    
  462.   fiber: Fiber,
    
  463.   hostInstances: Set<Instance>,
    
  464. ): boolean {
    
  465.   if (__DEV__) {
    
  466.     let node: Fiber = fiber;
    
  467.     let foundHostInstances = false;
    
  468.     while (true) {
    
  469.       if (
    
  470.         node.tag === HostComponent ||
    
  471.         (enableFloat ? node.tag === HostHoistable : false) ||
    
  472.         (enableHostSingletons && supportsSingletons
    
  473.           ? node.tag === HostSingleton
    
  474.           : false)
    
  475.       ) {
    
  476.         // We got a match.
    
  477.         foundHostInstances = true;
    
  478.         hostInstances.add(node.stateNode);
    
  479.         // There may still be more, so keep searching.
    
  480.       } else if (node.child !== null) {
    
  481.         node.child.return = node;
    
  482.         node = node.child;
    
  483.         continue;
    
  484.       }
    
  485.       if (node === fiber) {
    
  486.         return foundHostInstances;
    
  487.       }
    
  488.       while (node.sibling === null) {
    
  489.         if (node.return === null || node.return === fiber) {
    
  490.           return foundHostInstances;
    
  491.         }
    
  492.         node = node.return;
    
  493.       }
    
  494.       node.sibling.return = node.return;
    
  495.       node = node.sibling;
    
  496.     }
    
  497.   }
    
  498.   return false;
    
  499. }