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 'react-reconciler/src/ReactInternalTypes';
    
  11. import type {ReactScopeInstance} from 'shared/ReactTypes';
    
  12. import type {
    
  13.   ReactDOMEventHandle,
    
  14.   ReactDOMEventHandleListener,
    
  15. } from './ReactDOMEventHandleTypes';
    
  16. import type {
    
  17.   Container,
    
  18.   TextInstance,
    
  19.   Instance,
    
  20.   SuspenseInstance,
    
  21.   Props,
    
  22.   HoistableRoot,
    
  23.   RootResources,
    
  24. } from './ReactFiberConfigDOM';
    
  25. 
    
  26. import {
    
  27.   HostComponent,
    
  28.   HostHoistable,
    
  29.   HostSingleton,
    
  30.   HostText,
    
  31.   HostRoot,
    
  32.   SuspenseComponent,
    
  33. } from 'react-reconciler/src/ReactWorkTags';
    
  34. 
    
  35. import {getParentSuspenseInstance} from './ReactFiberConfigDOM';
    
  36. 
    
  37. import {
    
  38.   enableScopeAPI,
    
  39.   enableFloat,
    
  40.   enableHostSingletons,
    
  41. } from 'shared/ReactFeatureFlags';
    
  42. 
    
  43. const randomKey = Math.random().toString(36).slice(2);
    
  44. const internalInstanceKey = '__reactFiber$' + randomKey;
    
  45. const internalPropsKey = '__reactProps$' + randomKey;
    
  46. const internalContainerInstanceKey = '__reactContainer$' + randomKey;
    
  47. const internalEventHandlersKey = '__reactEvents$' + randomKey;
    
  48. const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
    
  49. const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
    
  50. const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
    
  51. const internalHoistableMarker = '__reactMarker$' + randomKey;
    
  52. 
    
  53. export function detachDeletedInstance(node: Instance): void {
    
  54.   // TODO: This function is only called on host components. I don't think all of
    
  55.   // these fields are relevant.
    
  56.   delete (node: any)[internalInstanceKey];
    
  57.   delete (node: any)[internalPropsKey];
    
  58.   delete (node: any)[internalEventHandlersKey];
    
  59.   delete (node: any)[internalEventHandlerListenersKey];
    
  60.   delete (node: any)[internalEventHandlesSetKey];
    
  61. }
    
  62. 
    
  63. export function precacheFiberNode(
    
  64.   hostInst: Fiber,
    
  65.   node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
    
  66. ): void {
    
  67.   (node: any)[internalInstanceKey] = hostInst;
    
  68. }
    
  69. 
    
  70. export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
    
  71.   // $FlowFixMe[prop-missing]
    
  72.   node[internalContainerInstanceKey] = hostRoot;
    
  73. }
    
  74. 
    
  75. export function unmarkContainerAsRoot(node: Container): void {
    
  76.   // $FlowFixMe[prop-missing]
    
  77.   node[internalContainerInstanceKey] = null;
    
  78. }
    
  79. 
    
  80. export function isContainerMarkedAsRoot(node: Container): boolean {
    
  81.   // $FlowFixMe[prop-missing]
    
  82.   return !!node[internalContainerInstanceKey];
    
  83. }
    
  84. 
    
  85. // Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
    
  86. // If the target node is part of a hydrated or not yet rendered subtree, then
    
  87. // this may also return a SuspenseComponent or HostRoot to indicate that.
    
  88. // Conceptually the HostRoot fiber is a child of the Container node. So if you
    
  89. // pass the Container node as the targetNode, you will not actually get the
    
  90. // HostRoot back. To get to the HostRoot, you need to pass a child of it.
    
  91. // The same thing applies to Suspense boundaries.
    
  92. export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
    
  93.   let targetInst = (targetNode: any)[internalInstanceKey];
    
  94.   if (targetInst) {
    
  95.     // Don't return HostRoot or SuspenseComponent here.
    
  96.     return targetInst;
    
  97.   }
    
  98.   // If the direct event target isn't a React owned DOM node, we need to look
    
  99.   // to see if one of its parents is a React owned DOM node.
    
  100.   let parentNode = targetNode.parentNode;
    
  101.   while (parentNode) {
    
  102.     // We'll check if this is a container root that could include
    
  103.     // React nodes in the future. We need to check this first because
    
  104.     // if we're a child of a dehydrated container, we need to first
    
  105.     // find that inner container before moving on to finding the parent
    
  106.     // instance. Note that we don't check this field on  the targetNode
    
  107.     // itself because the fibers are conceptually between the container
    
  108.     // node and the first child. It isn't surrounding the container node.
    
  109.     // If it's not a container, we check if it's an instance.
    
  110.     targetInst =
    
  111.       (parentNode: any)[internalContainerInstanceKey] ||
    
  112.       (parentNode: any)[internalInstanceKey];
    
  113.     if (targetInst) {
    
  114.       // Since this wasn't the direct target of the event, we might have
    
  115.       // stepped past dehydrated DOM nodes to get here. However they could
    
  116.       // also have been non-React nodes. We need to answer which one.
    
  117. 
    
  118.       // If we the instance doesn't have any children, then there can't be
    
  119.       // a nested suspense boundary within it. So we can use this as a fast
    
  120.       // bailout. Most of the time, when people add non-React children to
    
  121.       // the tree, it is using a ref to a child-less DOM node.
    
  122.       // Normally we'd only need to check one of the fibers because if it
    
  123.       // has ever gone from having children to deleting them or vice versa
    
  124.       // it would have deleted the dehydrated boundary nested inside already.
    
  125.       // However, since the HostRoot starts out with an alternate it might
    
  126.       // have one on the alternate so we need to check in case this was a
    
  127.       // root.
    
  128.       const alternate = targetInst.alternate;
    
  129.       if (
    
  130.         targetInst.child !== null ||
    
  131.         (alternate !== null && alternate.child !== null)
    
  132.       ) {
    
  133.         // Next we need to figure out if the node that skipped past is
    
  134.         // nested within a dehydrated boundary and if so, which one.
    
  135.         let suspenseInstance = getParentSuspenseInstance(targetNode);
    
  136.         while (suspenseInstance !== null) {
    
  137.           // We found a suspense instance. That means that we haven't
    
  138.           // hydrated it yet. Even though we leave the comments in the
    
  139.           // DOM after hydrating, and there are boundaries in the DOM
    
  140.           // that could already be hydrated, we wouldn't have found them
    
  141.           // through this pass since if the target is hydrated it would
    
  142.           // have had an internalInstanceKey on it.
    
  143.           // Let's get the fiber associated with the SuspenseComponent
    
  144.           // as the deepest instance.
    
  145.           // $FlowFixMe[prop-missing]
    
  146.           const targetSuspenseInst = suspenseInstance[internalInstanceKey];
    
  147.           if (targetSuspenseInst) {
    
  148.             return targetSuspenseInst;
    
  149.           }
    
  150.           // If we don't find a Fiber on the comment, it might be because
    
  151.           // we haven't gotten to hydrate it yet. There might still be a
    
  152.           // parent boundary that hasn't above this one so we need to find
    
  153.           // the outer most that is known.
    
  154.           suspenseInstance = getParentSuspenseInstance(suspenseInstance);
    
  155.           // If we don't find one, then that should mean that the parent
    
  156.           // host component also hasn't hydrated yet. We can return it
    
  157.           // below since it will bail out on the isMounted check later.
    
  158.         }
    
  159.       }
    
  160.       return targetInst;
    
  161.     }
    
  162.     targetNode = parentNode;
    
  163.     parentNode = targetNode.parentNode;
    
  164.   }
    
  165.   return null;
    
  166. }
    
  167. 
    
  168. /**
    
  169.  * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
    
  170.  * instance, or null if the node was not rendered by this React.
    
  171.  */
    
  172. export function getInstanceFromNode(node: Node): Fiber | null {
    
  173.   const inst =
    
  174.     (node: any)[internalInstanceKey] ||
    
  175.     (node: any)[internalContainerInstanceKey];
    
  176.   if (inst) {
    
  177.     const tag = inst.tag;
    
  178.     if (
    
  179.       tag === HostComponent ||
    
  180.       tag === HostText ||
    
  181.       tag === SuspenseComponent ||
    
  182.       (enableFloat ? tag === HostHoistable : false) ||
    
  183.       (enableHostSingletons ? tag === HostSingleton : false) ||
    
  184.       tag === HostRoot
    
  185.     ) {
    
  186.       return inst;
    
  187.     } else {
    
  188.       return null;
    
  189.     }
    
  190.   }
    
  191.   return null;
    
  192. }
    
  193. 
    
  194. /**
    
  195.  * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
    
  196.  * DOM node.
    
  197.  */
    
  198. export function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
    
  199.   const tag = inst.tag;
    
  200.   if (
    
  201.     tag === HostComponent ||
    
  202.     (enableFloat ? tag === HostHoistable : false) ||
    
  203.     (enableHostSingletons ? tag === HostSingleton : false) ||
    
  204.     tag === HostText
    
  205.   ) {
    
  206.     // In Fiber this, is just the state node right now. We assume it will be
    
  207.     // a host component or host text.
    
  208.     return inst.stateNode;
    
  209.   }
    
  210. 
    
  211.   // Without this first invariant, passing a non-DOM-component triggers the next
    
  212.   // invariant for a missing parent, which is super confusing.
    
  213.   throw new Error('getNodeFromInstance: Invalid argument.');
    
  214. }
    
  215. 
    
  216. export function getFiberCurrentPropsFromNode(
    
  217.   node: Instance | TextInstance | SuspenseInstance,
    
  218. ): Props {
    
  219.   return (node: any)[internalPropsKey] || null;
    
  220. }
    
  221. 
    
  222. export function updateFiberProps(
    
  223.   node: Instance | TextInstance | SuspenseInstance,
    
  224.   props: Props,
    
  225. ): void {
    
  226.   (node: any)[internalPropsKey] = props;
    
  227. }
    
  228. 
    
  229. export function getEventListenerSet(node: EventTarget): Set<string> {
    
  230.   let elementListenerSet = (node: any)[internalEventHandlersKey];
    
  231.   if (elementListenerSet === undefined) {
    
  232.     elementListenerSet = (node: any)[internalEventHandlersKey] = new Set();
    
  233.   }
    
  234.   return elementListenerSet;
    
  235. }
    
  236. 
    
  237. export function getFiberFromScopeInstance(
    
  238.   scope: ReactScopeInstance,
    
  239. ): null | Fiber {
    
  240.   if (enableScopeAPI) {
    
  241.     return (scope: any)[internalInstanceKey] || null;
    
  242.   }
    
  243.   return null;
    
  244. }
    
  245. 
    
  246. export function setEventHandlerListeners(
    
  247.   scope: EventTarget | ReactScopeInstance,
    
  248.   listeners: Set<ReactDOMEventHandleListener>,
    
  249. ): void {
    
  250.   (scope: any)[internalEventHandlerListenersKey] = listeners;
    
  251. }
    
  252. 
    
  253. export function getEventHandlerListeners(
    
  254.   scope: EventTarget | ReactScopeInstance,
    
  255. ): null | Set<ReactDOMEventHandleListener> {
    
  256.   return (scope: any)[internalEventHandlerListenersKey] || null;
    
  257. }
    
  258. 
    
  259. export function addEventHandleToTarget(
    
  260.   target: EventTarget | ReactScopeInstance,
    
  261.   eventHandle: ReactDOMEventHandle,
    
  262. ): void {
    
  263.   let eventHandles = (target: any)[internalEventHandlesSetKey];
    
  264.   if (eventHandles === undefined) {
    
  265.     eventHandles = (target: any)[internalEventHandlesSetKey] = new Set();
    
  266.   }
    
  267.   eventHandles.add(eventHandle);
    
  268. }
    
  269. 
    
  270. export function doesTargetHaveEventHandle(
    
  271.   target: EventTarget | ReactScopeInstance,
    
  272.   eventHandle: ReactDOMEventHandle,
    
  273. ): boolean {
    
  274.   const eventHandles = (target: any)[internalEventHandlesSetKey];
    
  275.   if (eventHandles === undefined) {
    
  276.     return false;
    
  277.   }
    
  278.   return eventHandles.has(eventHandle);
    
  279. }
    
  280. 
    
  281. export function getResourcesFromRoot(root: HoistableRoot): RootResources {
    
  282.   let resources = (root: any)[internalRootNodeResourcesKey];
    
  283.   if (!resources) {
    
  284.     resources = (root: any)[internalRootNodeResourcesKey] = {
    
  285.       hoistableStyles: new Map(),
    
  286.       hoistableScripts: new Map(),
    
  287.     };
    
  288.   }
    
  289.   return resources;
    
  290. }
    
  291. 
    
  292. export function isMarkedHoistable(node: Node): boolean {
    
  293.   return !!(node: any)[internalHoistableMarker];
    
  294. }
    
  295. 
    
  296. export function markNodeAsHoistable(node: Node) {
    
  297.   (node: any)[internalHoistableMarker] = true;
    
  298. }
    
  299. 
    
  300. export function isOwnedInstance(node: Node): boolean {
    
  301.   return !!(
    
  302.     (node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
    
  303.   );
    
  304. }