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 {
    
  11.   ElementTypeClass,
    
  12.   ElementTypeFunction,
    
  13.   ElementTypeRoot,
    
  14.   ElementTypeHostComponent,
    
  15.   ElementTypeOtherOrUnknown,
    
  16. } from 'react-devtools-shared/src/frontend/types';
    
  17. import {getUID, utfEncodeString, printOperationsArray} from '../../utils';
    
  18. import {
    
  19.   cleanForBridge,
    
  20.   copyWithDelete,
    
  21.   copyWithRename,
    
  22.   copyWithSet,
    
  23.   serializeToString,
    
  24. } from '../utils';
    
  25. import {
    
  26.   deletePathInObject,
    
  27.   getDisplayName,
    
  28.   getInObject,
    
  29.   renamePathInObject,
    
  30.   setInObject,
    
  31. } from 'react-devtools-shared/src/utils';
    
  32. import {
    
  33.   __DEBUG__,
    
  34.   TREE_OPERATION_ADD,
    
  35.   TREE_OPERATION_REMOVE,
    
  36.   TREE_OPERATION_REORDER_CHILDREN,
    
  37. } from '../../constants';
    
  38. import {decorateMany, forceUpdate, restoreMany} from './utils';
    
  39. 
    
  40. import type {
    
  41.   DevToolsHook,
    
  42.   GetFiberIDForNative,
    
  43.   InspectedElementPayload,
    
  44.   InstanceAndStyle,
    
  45.   NativeType,
    
  46.   PathFrame,
    
  47.   PathMatch,
    
  48.   RendererInterface,
    
  49. } from '../types';
    
  50. import type {
    
  51.   ComponentFilter,
    
  52.   ElementType,
    
  53. } from 'react-devtools-shared/src/frontend/types';
    
  54. import type {InspectedElement, SerializedElement} from '../types';
    
  55. 
    
  56. export type InternalInstance = Object;
    
  57. type LegacyRenderer = Object;
    
  58. 
    
  59. function getData(internalInstance: InternalInstance) {
    
  60.   let displayName = null;
    
  61.   let key = null;
    
  62. 
    
  63.   // != used deliberately here to catch undefined and null
    
  64.   if (internalInstance._currentElement != null) {
    
  65.     if (internalInstance._currentElement.key) {
    
  66.       key = String(internalInstance._currentElement.key);
    
  67.     }
    
  68. 
    
  69.     const elementType = internalInstance._currentElement.type;
    
  70.     if (typeof elementType === 'string') {
    
  71.       displayName = elementType;
    
  72.     } else if (typeof elementType === 'function') {
    
  73.       displayName = getDisplayName(elementType);
    
  74.     }
    
  75.   }
    
  76. 
    
  77.   return {
    
  78.     displayName,
    
  79.     key,
    
  80.   };
    
  81. }
    
  82. 
    
  83. function getElementType(internalInstance: InternalInstance): ElementType {
    
  84.   // != used deliberately here to catch undefined and null
    
  85.   if (internalInstance._currentElement != null) {
    
  86.     const elementType = internalInstance._currentElement.type;
    
  87.     if (typeof elementType === 'function') {
    
  88.       const publicInstance = internalInstance.getPublicInstance();
    
  89.       if (publicInstance !== null) {
    
  90.         return ElementTypeClass;
    
  91.       } else {
    
  92.         return ElementTypeFunction;
    
  93.       }
    
  94.     } else if (typeof elementType === 'string') {
    
  95.       return ElementTypeHostComponent;
    
  96.     }
    
  97.   }
    
  98.   return ElementTypeOtherOrUnknown;
    
  99. }
    
  100. 
    
  101. function getChildren(internalInstance: Object): Array<any> {
    
  102.   const children = [];
    
  103. 
    
  104.   // If the parent is a native node without rendered children, but with
    
  105.   // multiple string children, then the `element` that gets passed in here is
    
  106.   // a plain value -- a string or number.
    
  107.   if (typeof internalInstance !== 'object') {
    
  108.     // No children
    
  109.   } else if (
    
  110.     internalInstance._currentElement === null ||
    
  111.     internalInstance._currentElement === false
    
  112.   ) {
    
  113.     // No children
    
  114.   } else if (internalInstance._renderedComponent) {
    
  115.     const child = internalInstance._renderedComponent;
    
  116.     if (getElementType(child) !== ElementTypeOtherOrUnknown) {
    
  117.       children.push(child);
    
  118.     }
    
  119.   } else if (internalInstance._renderedChildren) {
    
  120.     const renderedChildren = internalInstance._renderedChildren;
    
  121.     for (const name in renderedChildren) {
    
  122.       const child = renderedChildren[name];
    
  123.       if (getElementType(child) !== ElementTypeOtherOrUnknown) {
    
  124.         children.push(child);
    
  125.       }
    
  126.     }
    
  127.   }
    
  128.   // Note: we skip the case where children are just strings or numbers
    
  129.   // because the new DevTools skips over host text nodes anyway.
    
  130.   return children;
    
  131. }
    
  132. 
    
  133. export function attach(
    
  134.   hook: DevToolsHook,
    
  135.   rendererID: number,
    
  136.   renderer: LegacyRenderer,
    
  137.   global: Object,
    
  138. ): RendererInterface {
    
  139.   const idToInternalInstanceMap: Map<number, InternalInstance> = new Map();
    
  140.   const internalInstanceToIDMap: WeakMap<InternalInstance, number> =
    
  141.     new WeakMap();
    
  142.   const internalInstanceToRootIDMap: WeakMap<InternalInstance, number> =
    
  143.     new WeakMap();
    
  144. 
    
  145.   let getInternalIDForNative: GetFiberIDForNative =
    
  146.     ((null: any): GetFiberIDForNative);
    
  147.   let findNativeNodeForInternalID: (id: number) => ?NativeType;
    
  148.   let getFiberForNative = (node: NativeType) => {
    
  149.     // Not implemented.
    
  150.     return null;
    
  151.   };
    
  152. 
    
  153.   if (renderer.ComponentTree) {
    
  154.     getInternalIDForNative = (node, findNearestUnfilteredAncestor) => {
    
  155.       const internalInstance =
    
  156.         renderer.ComponentTree.getClosestInstanceFromNode(node);
    
  157.       return internalInstanceToIDMap.get(internalInstance) || null;
    
  158.     };
    
  159.     findNativeNodeForInternalID = (id: number) => {
    
  160.       const internalInstance = idToInternalInstanceMap.get(id);
    
  161.       return renderer.ComponentTree.getNodeFromInstance(internalInstance);
    
  162.     };
    
  163.     getFiberForNative = (node: NativeType) => {
    
  164.       return renderer.ComponentTree.getClosestInstanceFromNode(node);
    
  165.     };
    
  166.   } else if (renderer.Mount.getID && renderer.Mount.getNode) {
    
  167.     getInternalIDForNative = (node, findNearestUnfilteredAncestor) => {
    
  168.       // Not implemented.
    
  169.       return null;
    
  170.     };
    
  171.     findNativeNodeForInternalID = (id: number) => {
    
  172.       // Not implemented.
    
  173.       return null;
    
  174.     };
    
  175.   }
    
  176. 
    
  177.   function getDisplayNameForFiberID(id: number): string | null {
    
  178.     const internalInstance = idToInternalInstanceMap.get(id);
    
  179.     return internalInstance ? getData(internalInstance).displayName : null;
    
  180.   }
    
  181. 
    
  182.   function getID(internalInstance: InternalInstance): number {
    
  183.     if (typeof internalInstance !== 'object' || internalInstance === null) {
    
  184.       throw new Error('Invalid internal instance: ' + internalInstance);
    
  185.     }
    
  186.     if (!internalInstanceToIDMap.has(internalInstance)) {
    
  187.       const id = getUID();
    
  188.       internalInstanceToIDMap.set(internalInstance, id);
    
  189.       idToInternalInstanceMap.set(id, internalInstance);
    
  190.     }
    
  191.     return ((internalInstanceToIDMap.get(internalInstance): any): number);
    
  192.   }
    
  193. 
    
  194.   function areEqualArrays(a: Array<any>, b: Array<any>) {
    
  195.     if (a.length !== b.length) {
    
  196.       return false;
    
  197.     }
    
  198.     for (let i = 0; i < a.length; i++) {
    
  199.       if (a[i] !== b[i]) {
    
  200.         return false;
    
  201.       }
    
  202.     }
    
  203.     return true;
    
  204.   }
    
  205. 
    
  206.   // This is shared mutable state that lets us keep track of where we are.
    
  207.   let parentIDStack = [];
    
  208. 
    
  209.   let oldReconcilerMethods = null;
    
  210.   if (renderer.Reconciler) {
    
  211.     // React 15
    
  212.     oldReconcilerMethods = decorateMany(renderer.Reconciler, {
    
  213.       mountComponent(fn, args) {
    
  214.         const internalInstance = args[0];
    
  215.         const hostContainerInfo = args[3];
    
  216.         if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
    
  217.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  218.           return fn.apply(this, args);
    
  219.         }
    
  220.         if (hostContainerInfo._topLevelWrapper === undefined) {
    
  221.           // SSR
    
  222.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  223.           return fn.apply(this, args);
    
  224.         }
    
  225. 
    
  226.         const id = getID(internalInstance);
    
  227.         // Push the operation.
    
  228.         const parentID =
    
  229.           parentIDStack.length > 0
    
  230.             ? parentIDStack[parentIDStack.length - 1]
    
  231.             : 0;
    
  232.         recordMount(internalInstance, id, parentID);
    
  233.         parentIDStack.push(id);
    
  234. 
    
  235.         // Remember the root.
    
  236.         internalInstanceToRootIDMap.set(
    
  237.           internalInstance,
    
  238.           getID(hostContainerInfo._topLevelWrapper),
    
  239.         );
    
  240. 
    
  241.         try {
    
  242.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  243.           const result = fn.apply(this, args);
    
  244.           parentIDStack.pop();
    
  245.           return result;
    
  246.         } catch (err) {
    
  247.           parentIDStack = [];
    
  248.           throw err;
    
  249.         } finally {
    
  250.           if (parentIDStack.length === 0) {
    
  251.             const rootID = internalInstanceToRootIDMap.get(internalInstance);
    
  252.             if (rootID === undefined) {
    
  253.               throw new Error('Expected to find root ID.');
    
  254.             }
    
  255.             flushPendingEvents(rootID);
    
  256.           }
    
  257.         }
    
  258.       },
    
  259.       performUpdateIfNecessary(fn, args) {
    
  260.         const internalInstance = args[0];
    
  261.         if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
    
  262.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  263.           return fn.apply(this, args);
    
  264.         }
    
  265. 
    
  266.         const id = getID(internalInstance);
    
  267.         parentIDStack.push(id);
    
  268. 
    
  269.         const prevChildren = getChildren(internalInstance);
    
  270.         try {
    
  271.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  272.           const result = fn.apply(this, args);
    
  273. 
    
  274.           const nextChildren = getChildren(internalInstance);
    
  275.           if (!areEqualArrays(prevChildren, nextChildren)) {
    
  276.             // Push the operation
    
  277.             recordReorder(internalInstance, id, nextChildren);
    
  278.           }
    
  279. 
    
  280.           parentIDStack.pop();
    
  281.           return result;
    
  282.         } catch (err) {
    
  283.           parentIDStack = [];
    
  284.           throw err;
    
  285.         } finally {
    
  286.           if (parentIDStack.length === 0) {
    
  287.             const rootID = internalInstanceToRootIDMap.get(internalInstance);
    
  288.             if (rootID === undefined) {
    
  289.               throw new Error('Expected to find root ID.');
    
  290.             }
    
  291.             flushPendingEvents(rootID);
    
  292.           }
    
  293.         }
    
  294.       },
    
  295.       receiveComponent(fn, args) {
    
  296.         const internalInstance = args[0];
    
  297.         if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
    
  298.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  299.           return fn.apply(this, args);
    
  300.         }
    
  301. 
    
  302.         const id = getID(internalInstance);
    
  303.         parentIDStack.push(id);
    
  304. 
    
  305.         const prevChildren = getChildren(internalInstance);
    
  306.         try {
    
  307.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  308.           const result = fn.apply(this, args);
    
  309. 
    
  310.           const nextChildren = getChildren(internalInstance);
    
  311.           if (!areEqualArrays(prevChildren, nextChildren)) {
    
  312.             // Push the operation
    
  313.             recordReorder(internalInstance, id, nextChildren);
    
  314.           }
    
  315. 
    
  316.           parentIDStack.pop();
    
  317.           return result;
    
  318.         } catch (err) {
    
  319.           parentIDStack = [];
    
  320.           throw err;
    
  321.         } finally {
    
  322.           if (parentIDStack.length === 0) {
    
  323.             const rootID = internalInstanceToRootIDMap.get(internalInstance);
    
  324.             if (rootID === undefined) {
    
  325.               throw new Error('Expected to find root ID.');
    
  326.             }
    
  327.             flushPendingEvents(rootID);
    
  328.           }
    
  329.         }
    
  330.       },
    
  331.       unmountComponent(fn, args) {
    
  332.         const internalInstance = args[0];
    
  333.         if (getElementType(internalInstance) === ElementTypeOtherOrUnknown) {
    
  334.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  335.           return fn.apply(this, args);
    
  336.         }
    
  337. 
    
  338.         const id = getID(internalInstance);
    
  339.         parentIDStack.push(id);
    
  340.         try {
    
  341.           // $FlowFixMe[object-this-reference] found when upgrading Flow
    
  342.           const result = fn.apply(this, args);
    
  343.           parentIDStack.pop();
    
  344. 
    
  345.           // Push the operation.
    
  346.           recordUnmount(internalInstance, id);
    
  347. 
    
  348.           return result;
    
  349.         } catch (err) {
    
  350.           parentIDStack = [];
    
  351.           throw err;
    
  352.         } finally {
    
  353.           if (parentIDStack.length === 0) {
    
  354.             const rootID = internalInstanceToRootIDMap.get(internalInstance);
    
  355.             if (rootID === undefined) {
    
  356.               throw new Error('Expected to find root ID.');
    
  357.             }
    
  358.             flushPendingEvents(rootID);
    
  359.           }
    
  360.         }
    
  361.       },
    
  362.     });
    
  363.   }
    
  364. 
    
  365.   function cleanup() {
    
  366.     if (oldReconcilerMethods !== null) {
    
  367.       if (renderer.Component) {
    
  368.         restoreMany(renderer.Component.Mixin, oldReconcilerMethods);
    
  369.       } else {
    
  370.         restoreMany(renderer.Reconciler, oldReconcilerMethods);
    
  371.       }
    
  372.     }
    
  373.     oldReconcilerMethods = null;
    
  374.   }
    
  375. 
    
  376.   function recordMount(
    
  377.     internalInstance: InternalInstance,
    
  378.     id: number,
    
  379.     parentID: number,
    
  380.   ) {
    
  381.     const isRoot = parentID === 0;
    
  382. 
    
  383.     if (__DEBUG__) {
    
  384.       console.log(
    
  385.         '%crecordMount()',
    
  386.         'color: green; font-weight: bold;',
    
  387.         id,
    
  388.         getData(internalInstance).displayName,
    
  389.       );
    
  390.     }
    
  391. 
    
  392.     if (isRoot) {
    
  393.       // TODO Is this right? For all versions?
    
  394.       const hasOwnerMetadata =
    
  395.         internalInstance._currentElement != null &&
    
  396.         internalInstance._currentElement._owner != null;
    
  397. 
    
  398.       pushOperation(TREE_OPERATION_ADD);
    
  399.       pushOperation(id);
    
  400.       pushOperation(ElementTypeRoot);
    
  401.       pushOperation(0); // StrictMode compliant?
    
  402.       pushOperation(0); // Profiling flag
    
  403.       pushOperation(0); // StrictMode supported?
    
  404.       pushOperation(hasOwnerMetadata ? 1 : 0);
    
  405.     } else {
    
  406.       const type = getElementType(internalInstance);
    
  407.       const {displayName, key} = getData(internalInstance);
    
  408. 
    
  409.       const ownerID =
    
  410.         internalInstance._currentElement != null &&
    
  411.         internalInstance._currentElement._owner != null
    
  412.           ? getID(internalInstance._currentElement._owner)
    
  413.           : 0;
    
  414. 
    
  415.       const displayNameStringID = getStringID(displayName);
    
  416.       const keyStringID = getStringID(key);
    
  417.       pushOperation(TREE_OPERATION_ADD);
    
  418.       pushOperation(id);
    
  419.       pushOperation(type);
    
  420.       pushOperation(parentID);
    
  421.       pushOperation(ownerID);
    
  422.       pushOperation(displayNameStringID);
    
  423.       pushOperation(keyStringID);
    
  424.     }
    
  425.   }
    
  426. 
    
  427.   function recordReorder(
    
  428.     internalInstance: InternalInstance,
    
  429.     id: number,
    
  430.     nextChildren: Array<InternalInstance>,
    
  431.   ) {
    
  432.     pushOperation(TREE_OPERATION_REORDER_CHILDREN);
    
  433.     pushOperation(id);
    
  434.     const nextChildIDs = nextChildren.map(getID);
    
  435.     pushOperation(nextChildIDs.length);
    
  436.     for (let i = 0; i < nextChildIDs.length; i++) {
    
  437.       pushOperation(nextChildIDs[i]);
    
  438.     }
    
  439.   }
    
  440. 
    
  441.   function recordUnmount(internalInstance: InternalInstance, id: number) {
    
  442.     pendingUnmountedIDs.push(id);
    
  443.     idToInternalInstanceMap.delete(id);
    
  444.   }
    
  445. 
    
  446.   function crawlAndRecordInitialMounts(
    
  447.     id: number,
    
  448.     parentID: number,
    
  449.     rootID: number,
    
  450.   ) {
    
  451.     if (__DEBUG__) {
    
  452.       console.group('crawlAndRecordInitialMounts() id:', id);
    
  453.     }
    
  454. 
    
  455.     const internalInstance = idToInternalInstanceMap.get(id);
    
  456.     if (internalInstance != null) {
    
  457.       internalInstanceToRootIDMap.set(internalInstance, rootID);
    
  458.       recordMount(internalInstance, id, parentID);
    
  459.       getChildren(internalInstance).forEach(child =>
    
  460.         crawlAndRecordInitialMounts(getID(child), id, rootID),
    
  461.       );
    
  462.     }
    
  463. 
    
  464.     if (__DEBUG__) {
    
  465.       console.groupEnd();
    
  466.     }
    
  467.   }
    
  468. 
    
  469.   function flushInitialOperations() {
    
  470.     // Crawl roots though and register any nodes that mounted before we were injected.
    
  471. 
    
  472.     const roots =
    
  473.       renderer.Mount._instancesByReactRootID ||
    
  474.       renderer.Mount._instancesByContainerID;
    
  475. 
    
  476.     for (const key in roots) {
    
  477.       const internalInstance = roots[key];
    
  478.       const id = getID(internalInstance);
    
  479.       crawlAndRecordInitialMounts(id, 0, id);
    
  480.       flushPendingEvents(id);
    
  481.     }
    
  482.   }
    
  483. 
    
  484.   const pendingOperations: Array<number> = [];
    
  485.   const pendingStringTable: Map<string, number> = new Map();
    
  486.   let pendingUnmountedIDs: Array<number> = [];
    
  487.   let pendingStringTableLength: number = 0;
    
  488.   let pendingUnmountedRootID: number | null = null;
    
  489. 
    
  490.   function flushPendingEvents(rootID: number) {
    
  491.     if (
    
  492.       pendingOperations.length === 0 &&
    
  493.       pendingUnmountedIDs.length === 0 &&
    
  494.       pendingUnmountedRootID === null
    
  495.     ) {
    
  496.       return;
    
  497.     }
    
  498. 
    
  499.     const numUnmountIDs =
    
  500.       pendingUnmountedIDs.length + (pendingUnmountedRootID === null ? 0 : 1);
    
  501. 
    
  502.     const operations = new Array<number>(
    
  503.       // Identify which renderer this update is coming from.
    
  504.       2 + // [rendererID, rootFiberID]
    
  505.         // How big is the string table?
    
  506.         1 + // [stringTableLength]
    
  507.         // Then goes the actual string table.
    
  508.         pendingStringTableLength +
    
  509.         // All unmounts are batched in a single message.
    
  510.         // [TREE_OPERATION_REMOVE, removedIDLength, ...ids]
    
  511.         (numUnmountIDs > 0 ? 2 + numUnmountIDs : 0) +
    
  512.         // Mount operations
    
  513.         pendingOperations.length,
    
  514.     );
    
  515. 
    
  516.     // Identify which renderer this update is coming from.
    
  517.     // This enables roots to be mapped to renderers,
    
  518.     // Which in turn enables fiber properations, states, and hooks to be inspected.
    
  519.     let i = 0;
    
  520.     operations[i++] = rendererID;
    
  521.     operations[i++] = rootID;
    
  522. 
    
  523.     // Now fill in the string table.
    
  524.     // [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]
    
  525.     operations[i++] = pendingStringTableLength;
    
  526.     pendingStringTable.forEach((value, key) => {
    
  527.       operations[i++] = key.length;
    
  528.       const encodedKey = utfEncodeString(key);
    
  529.       for (let j = 0; j < encodedKey.length; j++) {
    
  530.         operations[i + j] = encodedKey[j];
    
  531.       }
    
  532.       i += key.length;
    
  533.     });
    
  534. 
    
  535.     if (numUnmountIDs > 0) {
    
  536.       // All unmounts except roots are batched in a single message.
    
  537.       operations[i++] = TREE_OPERATION_REMOVE;
    
  538.       // The first number is how many unmounted IDs we're gonna send.
    
  539.       operations[i++] = numUnmountIDs;
    
  540.       // Fill in the unmounts
    
  541.       for (let j = 0; j < pendingUnmountedIDs.length; j++) {
    
  542.         operations[i++] = pendingUnmountedIDs[j];
    
  543.       }
    
  544.       // The root ID should always be unmounted last.
    
  545.       if (pendingUnmountedRootID !== null) {
    
  546.         operations[i] = pendingUnmountedRootID;
    
  547.         i++;
    
  548.       }
    
  549.     }
    
  550. 
    
  551.     // Fill in the rest of the operations.
    
  552.     for (let j = 0; j < pendingOperations.length; j++) {
    
  553.       operations[i + j] = pendingOperations[j];
    
  554.     }
    
  555.     i += pendingOperations.length;
    
  556. 
    
  557.     if (__DEBUG__) {
    
  558.       printOperationsArray(operations);
    
  559.     }
    
  560. 
    
  561.     // If we've already connected to the frontend, just pass the operations through.
    
  562.     hook.emit('operations', operations);
    
  563. 
    
  564.     pendingOperations.length = 0;
    
  565.     pendingUnmountedIDs = [];
    
  566.     pendingUnmountedRootID = null;
    
  567.     pendingStringTable.clear();
    
  568.     pendingStringTableLength = 0;
    
  569.   }
    
  570. 
    
  571.   function pushOperation(op: number): void {
    
  572.     if (__DEV__) {
    
  573.       if (!Number.isInteger(op)) {
    
  574.         console.error(
    
  575.           'pushOperation() was called but the value is not an integer.',
    
  576.           op,
    
  577.         );
    
  578.       }
    
  579.     }
    
  580.     pendingOperations.push(op);
    
  581.   }
    
  582. 
    
  583.   function getStringID(str: string | null): number {
    
  584.     if (str === null) {
    
  585.       return 0;
    
  586.     }
    
  587.     const existingID = pendingStringTable.get(str);
    
  588.     if (existingID !== undefined) {
    
  589.       return existingID;
    
  590.     }
    
  591.     const stringID = pendingStringTable.size + 1;
    
  592.     pendingStringTable.set(str, stringID);
    
  593.     // The string table total length needs to account
    
  594.     // both for the string length, and for the array item
    
  595.     // that contains the length itself. Hence + 1.
    
  596.     pendingStringTableLength += str.length + 1;
    
  597.     return stringID;
    
  598.   }
    
  599. 
    
  600.   let currentlyInspectedElementID: number | null = null;
    
  601.   let currentlyInspectedPaths: Object = {};
    
  602. 
    
  603.   // Track the intersection of currently inspected paths,
    
  604.   // so that we can send their data along if the element is re-rendered.
    
  605.   function mergeInspectedPaths(path: Array<string | number>) {
    
  606.     let current = currentlyInspectedPaths;
    
  607.     path.forEach(key => {
    
  608.       if (!current[key]) {
    
  609.         current[key] = {};
    
  610.       }
    
  611.       current = current[key];
    
  612.     });
    
  613.   }
    
  614. 
    
  615.   function createIsPathAllowed(key: string) {
    
  616.     // This function helps prevent previously-inspected paths from being dehydrated in updates.
    
  617.     // This is important to avoid a bad user experience where expanded toggles collapse on update.
    
  618.     return function isPathAllowed(path: Array<string | number>): boolean {
    
  619.       let current = currentlyInspectedPaths[key];
    
  620.       if (!current) {
    
  621.         return false;
    
  622.       }
    
  623.       for (let i = 0; i < path.length; i++) {
    
  624.         current = current[path[i]];
    
  625.         if (!current) {
    
  626.           return false;
    
  627.         }
    
  628.       }
    
  629.       return true;
    
  630.     };
    
  631.   }
    
  632. 
    
  633.   // Fast path props lookup for React Native style editor.
    
  634.   function getInstanceAndStyle(id: number): InstanceAndStyle {
    
  635.     let instance = null;
    
  636.     let style = null;
    
  637. 
    
  638.     const internalInstance = idToInternalInstanceMap.get(id);
    
  639.     if (internalInstance != null) {
    
  640.       instance = internalInstance._instance || null;
    
  641. 
    
  642.       const element = internalInstance._currentElement;
    
  643.       if (element != null && element.props != null) {
    
  644.         style = element.props.style || null;
    
  645.       }
    
  646.     }
    
  647. 
    
  648.     return {
    
  649.       instance,
    
  650.       style,
    
  651.     };
    
  652.   }
    
  653. 
    
  654.   function updateSelectedElement(id: number): void {
    
  655.     const internalInstance = idToInternalInstanceMap.get(id);
    
  656.     if (internalInstance == null) {
    
  657.       console.warn(`Could not find instance with id "${id}"`);
    
  658.       return;
    
  659.     }
    
  660. 
    
  661.     switch (getElementType(internalInstance)) {
    
  662.       case ElementTypeClass:
    
  663.         global.$r = internalInstance._instance;
    
  664.         break;
    
  665.       case ElementTypeFunction:
    
  666.         const element = internalInstance._currentElement;
    
  667.         if (element == null) {
    
  668.           console.warn(`Could not find element with id "${id}"`);
    
  669.           return;
    
  670.         }
    
  671. 
    
  672.         global.$r = {
    
  673.           props: element.props,
    
  674.           type: element.type,
    
  675.         };
    
  676.         break;
    
  677.       default:
    
  678.         global.$r = null;
    
  679.         break;
    
  680.     }
    
  681.   }
    
  682. 
    
  683.   function storeAsGlobal(
    
  684.     id: number,
    
  685.     path: Array<string | number>,
    
  686.     count: number,
    
  687.   ): void {
    
  688.     const inspectedElement = inspectElementRaw(id);
    
  689.     if (inspectedElement !== null) {
    
  690.       const value = getInObject(inspectedElement, path);
    
  691.       const key = `$reactTemp${count}`;
    
  692. 
    
  693.       window[key] = value;
    
  694. 
    
  695.       console.log(key);
    
  696.       console.log(value);
    
  697.     }
    
  698.   }
    
  699. 
    
  700.   function getSerializedElementValueByPath(
    
  701.     id: number,
    
  702.     path: Array<string | number>,
    
  703.   ): ?string {
    
  704.     const inspectedElement = inspectElementRaw(id);
    
  705.     if (inspectedElement !== null) {
    
  706.       const valueToCopy = getInObject(inspectedElement, path);
    
  707. 
    
  708.       return serializeToString(valueToCopy);
    
  709.     }
    
  710.   }
    
  711. 
    
  712.   function inspectElement(
    
  713.     requestID: number,
    
  714.     id: number,
    
  715.     path: Array<string | number> | null,
    
  716.     forceFullData: boolean,
    
  717.   ): InspectedElementPayload {
    
  718.     if (forceFullData || currentlyInspectedElementID !== id) {
    
  719.       currentlyInspectedElementID = id;
    
  720.       currentlyInspectedPaths = {};
    
  721.     }
    
  722. 
    
  723.     const inspectedElement = inspectElementRaw(id);
    
  724.     if (inspectedElement === null) {
    
  725.       return {
    
  726.         id,
    
  727.         responseID: requestID,
    
  728.         type: 'not-found',
    
  729.       };
    
  730.     }
    
  731. 
    
  732.     if (path !== null) {
    
  733.       mergeInspectedPaths(path);
    
  734.     }
    
  735. 
    
  736.     // Any time an inspected element has an update,
    
  737.     // we should update the selected $r value as wel.
    
  738.     // Do this before dehydration (cleanForBridge).
    
  739.     updateSelectedElement(id);
    
  740. 
    
  741.     inspectedElement.context = cleanForBridge(
    
  742.       inspectedElement.context,
    
  743.       createIsPathAllowed('context'),
    
  744.     );
    
  745.     inspectedElement.props = cleanForBridge(
    
  746.       inspectedElement.props,
    
  747.       createIsPathAllowed('props'),
    
  748.     );
    
  749.     inspectedElement.state = cleanForBridge(
    
  750.       inspectedElement.state,
    
  751.       createIsPathAllowed('state'),
    
  752.     );
    
  753. 
    
  754.     return {
    
  755.       id,
    
  756.       responseID: requestID,
    
  757.       type: 'full-data',
    
  758.       value: inspectedElement,
    
  759.     };
    
  760.   }
    
  761. 
    
  762.   function inspectElementRaw(id: number): InspectedElement | null {
    
  763.     const internalInstance = idToInternalInstanceMap.get(id);
    
  764. 
    
  765.     if (internalInstance == null) {
    
  766.       return null;
    
  767.     }
    
  768. 
    
  769.     const {displayName, key} = getData(internalInstance);
    
  770.     const type = getElementType(internalInstance);
    
  771. 
    
  772.     let context = null;
    
  773.     let owners = null;
    
  774.     let props = null;
    
  775.     let state = null;
    
  776.     let source = null;
    
  777. 
    
  778.     const element = internalInstance._currentElement;
    
  779.     if (element !== null) {
    
  780.       props = element.props;
    
  781.       source = element._source != null ? element._source : null;
    
  782. 
    
  783.       let owner = element._owner;
    
  784.       if (owner) {
    
  785.         owners = ([]: Array<SerializedElement>);
    
  786.         while (owner != null) {
    
  787.           owners.push({
    
  788.             displayName: getData(owner).displayName || 'Unknown',
    
  789.             id: getID(owner),
    
  790.             key: element.key,
    
  791.             type: getElementType(owner),
    
  792.           });
    
  793.           if (owner._currentElement) {
    
  794.             owner = owner._currentElement._owner;
    
  795.           }
    
  796.         }
    
  797.       }
    
  798.     }
    
  799. 
    
  800.     const publicInstance = internalInstance._instance;
    
  801.     if (publicInstance != null) {
    
  802.       context = publicInstance.context || null;
    
  803.       state = publicInstance.state || null;
    
  804.     }
    
  805. 
    
  806.     // Not implemented
    
  807.     const errors: Array<[string, number]> = [];
    
  808.     const warnings: Array<[string, number]> = [];
    
  809. 
    
  810.     return {
    
  811.       id,
    
  812. 
    
  813.       // Does the current renderer support editable hooks and function props?
    
  814.       canEditHooks: false,
    
  815.       canEditFunctionProps: false,
    
  816. 
    
  817.       // Does the current renderer support advanced editing interface?
    
  818.       canEditHooksAndDeletePaths: false,
    
  819.       canEditHooksAndRenamePaths: false,
    
  820.       canEditFunctionPropsDeletePaths: false,
    
  821.       canEditFunctionPropsRenamePaths: false,
    
  822. 
    
  823.       // Toggle error boundary did not exist in legacy versions
    
  824.       canToggleError: false,
    
  825.       isErrored: false,
    
  826.       targetErrorBoundaryID: null,
    
  827. 
    
  828.       // Suspense did not exist in legacy versions
    
  829.       canToggleSuspense: false,
    
  830. 
    
  831.       // Can view component source location.
    
  832.       canViewSource: type === ElementTypeClass || type === ElementTypeFunction,
    
  833. 
    
  834.       // Only legacy context exists in legacy versions.
    
  835.       hasLegacyContext: true,
    
  836. 
    
  837.       displayName: displayName,
    
  838. 
    
  839.       type: type,
    
  840. 
    
  841.       key: key != null ? key : null,
    
  842. 
    
  843.       // Inspectable properties.
    
  844.       context,
    
  845.       hooks: null,
    
  846.       props,
    
  847.       state,
    
  848.       errors,
    
  849.       warnings,
    
  850. 
    
  851.       // List of owners
    
  852.       owners,
    
  853. 
    
  854.       // Location of component in source code.
    
  855.       source,
    
  856. 
    
  857.       rootType: null,
    
  858.       rendererPackageName: null,
    
  859.       rendererVersion: null,
    
  860. 
    
  861.       plugins: {
    
  862.         stylex: null,
    
  863.       },
    
  864.     };
    
  865.   }
    
  866. 
    
  867.   function logElementToConsole(id: number): void {
    
  868.     const result = inspectElementRaw(id);
    
  869.     if (result === null) {
    
  870.       console.warn(`Could not find element with id "${id}"`);
    
  871.       return;
    
  872.     }
    
  873. 
    
  874.     const supportsGroup = typeof console.groupCollapsed === 'function';
    
  875.     if (supportsGroup) {
    
  876.       console.groupCollapsed(
    
  877.         `[Click to expand] %c<${result.displayName || 'Component'} />`,
    
  878.         // --dom-tag-name-color is the CSS variable Chrome styles HTML elements with in the console.
    
  879.         'color: var(--dom-tag-name-color); font-weight: normal;',
    
  880.       );
    
  881.     }
    
  882.     if (result.props !== null) {
    
  883.       console.log('Props:', result.props);
    
  884.     }
    
  885.     if (result.state !== null) {
    
  886.       console.log('State:', result.state);
    
  887.     }
    
  888.     if (result.context !== null) {
    
  889.       console.log('Context:', result.context);
    
  890.     }
    
  891.     const nativeNode = findNativeNodeForInternalID(id);
    
  892.     if (nativeNode !== null) {
    
  893.       console.log('Node:', nativeNode);
    
  894.     }
    
  895.     if (window.chrome || /firefox/i.test(navigator.userAgent)) {
    
  896.       console.log(
    
  897.         'Right-click any value to save it as a global variable for further inspection.',
    
  898.       );
    
  899.     }
    
  900.     if (supportsGroup) {
    
  901.       console.groupEnd();
    
  902.     }
    
  903.   }
    
  904. 
    
  905.   function prepareViewAttributeSource(
    
  906.     id: number,
    
  907.     path: Array<string | number>,
    
  908.   ): void {
    
  909.     const inspectedElement = inspectElementRaw(id);
    
  910.     if (inspectedElement !== null) {
    
  911.       window.$attribute = getInObject(inspectedElement, path);
    
  912.     }
    
  913.   }
    
  914. 
    
  915.   function prepareViewElementSource(id: number): void {
    
  916.     const internalInstance = idToInternalInstanceMap.get(id);
    
  917.     if (internalInstance == null) {
    
  918.       console.warn(`Could not find instance with id "${id}"`);
    
  919.       return;
    
  920.     }
    
  921. 
    
  922.     const element = internalInstance._currentElement;
    
  923.     if (element == null) {
    
  924.       console.warn(`Could not find element with id "${id}"`);
    
  925.       return;
    
  926.     }
    
  927. 
    
  928.     global.$type = element.type;
    
  929.   }
    
  930. 
    
  931.   function deletePath(
    
  932.     type: 'context' | 'hooks' | 'props' | 'state',
    
  933.     id: number,
    
  934.     hookID: ?number,
    
  935.     path: Array<string | number>,
    
  936.   ): void {
    
  937.     const internalInstance = idToInternalInstanceMap.get(id);
    
  938.     if (internalInstance != null) {
    
  939.       const publicInstance = internalInstance._instance;
    
  940.       if (publicInstance != null) {
    
  941.         switch (type) {
    
  942.           case 'context':
    
  943.             deletePathInObject(publicInstance.context, path);
    
  944.             forceUpdate(publicInstance);
    
  945.             break;
    
  946.           case 'hooks':
    
  947.             throw new Error('Hooks not supported by this renderer');
    
  948.           case 'props':
    
  949.             const element = internalInstance._currentElement;
    
  950.             internalInstance._currentElement = {
    
  951.               ...element,
    
  952.               props: copyWithDelete(element.props, path),
    
  953.             };
    
  954.             forceUpdate(publicInstance);
    
  955.             break;
    
  956.           case 'state':
    
  957.             deletePathInObject(publicInstance.state, path);
    
  958.             forceUpdate(publicInstance);
    
  959.             break;
    
  960.         }
    
  961.       }
    
  962.     }
    
  963.   }
    
  964. 
    
  965.   function renamePath(
    
  966.     type: 'context' | 'hooks' | 'props' | 'state',
    
  967.     id: number,
    
  968.     hookID: ?number,
    
  969.     oldPath: Array<string | number>,
    
  970.     newPath: Array<string | number>,
    
  971.   ): void {
    
  972.     const internalInstance = idToInternalInstanceMap.get(id);
    
  973.     if (internalInstance != null) {
    
  974.       const publicInstance = internalInstance._instance;
    
  975.       if (publicInstance != null) {
    
  976.         switch (type) {
    
  977.           case 'context':
    
  978.             renamePathInObject(publicInstance.context, oldPath, newPath);
    
  979.             forceUpdate(publicInstance);
    
  980.             break;
    
  981.           case 'hooks':
    
  982.             throw new Error('Hooks not supported by this renderer');
    
  983.           case 'props':
    
  984.             const element = internalInstance._currentElement;
    
  985.             internalInstance._currentElement = {
    
  986.               ...element,
    
  987.               props: copyWithRename(element.props, oldPath, newPath),
    
  988.             };
    
  989.             forceUpdate(publicInstance);
    
  990.             break;
    
  991.           case 'state':
    
  992.             renamePathInObject(publicInstance.state, oldPath, newPath);
    
  993.             forceUpdate(publicInstance);
    
  994.             break;
    
  995.         }
    
  996.       }
    
  997.     }
    
  998.   }
    
  999. 
    
  1000.   function overrideValueAtPath(
    
  1001.     type: 'context' | 'hooks' | 'props' | 'state',
    
  1002.     id: number,
    
  1003.     hookID: ?number,
    
  1004.     path: Array<string | number>,
    
  1005.     value: any,
    
  1006.   ): void {
    
  1007.     const internalInstance = idToInternalInstanceMap.get(id);
    
  1008.     if (internalInstance != null) {
    
  1009.       const publicInstance = internalInstance._instance;
    
  1010.       if (publicInstance != null) {
    
  1011.         switch (type) {
    
  1012.           case 'context':
    
  1013.             setInObject(publicInstance.context, path, value);
    
  1014.             forceUpdate(publicInstance);
    
  1015.             break;
    
  1016.           case 'hooks':
    
  1017.             throw new Error('Hooks not supported by this renderer');
    
  1018.           case 'props':
    
  1019.             const element = internalInstance._currentElement;
    
  1020.             internalInstance._currentElement = {
    
  1021.               ...element,
    
  1022.               props: copyWithSet(element.props, path, value),
    
  1023.             };
    
  1024.             forceUpdate(publicInstance);
    
  1025.             break;
    
  1026.           case 'state':
    
  1027.             setInObject(publicInstance.state, path, value);
    
  1028.             forceUpdate(publicInstance);
    
  1029.             break;
    
  1030.         }
    
  1031.       }
    
  1032.     }
    
  1033.   }
    
  1034. 
    
  1035.   // v16+ only features
    
  1036.   const getProfilingData = () => {
    
  1037.     throw new Error('getProfilingData not supported by this renderer');
    
  1038.   };
    
  1039.   const handleCommitFiberRoot = () => {
    
  1040.     throw new Error('handleCommitFiberRoot not supported by this renderer');
    
  1041.   };
    
  1042.   const handleCommitFiberUnmount = () => {
    
  1043.     throw new Error('handleCommitFiberUnmount not supported by this renderer');
    
  1044.   };
    
  1045.   const handlePostCommitFiberRoot = () => {
    
  1046.     throw new Error('handlePostCommitFiberRoot not supported by this renderer');
    
  1047.   };
    
  1048.   const overrideError = () => {
    
  1049.     throw new Error('overrideError not supported by this renderer');
    
  1050.   };
    
  1051.   const overrideSuspense = () => {
    
  1052.     throw new Error('overrideSuspense not supported by this renderer');
    
  1053.   };
    
  1054.   const startProfiling = () => {
    
  1055.     // Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
    
  1056.   };
    
  1057.   const stopProfiling = () => {
    
  1058.     // Do not throw, since this would break a multi-root scenario where v15 and v16 were both present.
    
  1059.   };
    
  1060. 
    
  1061.   function getBestMatchForTrackedPath(): PathMatch | null {
    
  1062.     // Not implemented.
    
  1063.     return null;
    
  1064.   }
    
  1065. 
    
  1066.   function getPathForElement(id: number): Array<PathFrame> | null {
    
  1067.     // Not implemented.
    
  1068.     return null;
    
  1069.   }
    
  1070. 
    
  1071.   function updateComponentFilters(componentFilters: Array<ComponentFilter>) {
    
  1072.     // Not implemented.
    
  1073.   }
    
  1074. 
    
  1075.   function setTraceUpdatesEnabled(enabled: boolean) {
    
  1076.     // Not implemented.
    
  1077.   }
    
  1078. 
    
  1079.   function setTrackedPath(path: Array<PathFrame> | null) {
    
  1080.     // Not implemented.
    
  1081.   }
    
  1082. 
    
  1083.   function getOwnersList(id: number): Array<SerializedElement> | null {
    
  1084.     // Not implemented.
    
  1085.     return null;
    
  1086.   }
    
  1087. 
    
  1088.   function clearErrorsAndWarnings() {
    
  1089.     // Not implemented
    
  1090.   }
    
  1091. 
    
  1092.   function clearErrorsForFiberID(id: number) {
    
  1093.     // Not implemented
    
  1094.   }
    
  1095. 
    
  1096.   function clearWarningsForFiberID(id: number) {
    
  1097.     // Not implemented
    
  1098.   }
    
  1099. 
    
  1100.   function patchConsoleForStrictMode() {}
    
  1101. 
    
  1102.   function unpatchConsoleForStrictMode() {}
    
  1103. 
    
  1104.   function hasFiberWithId(id: number): boolean {
    
  1105.     return idToInternalInstanceMap.has(id);
    
  1106.   }
    
  1107. 
    
  1108.   return {
    
  1109.     clearErrorsAndWarnings,
    
  1110.     clearErrorsForFiberID,
    
  1111.     clearWarningsForFiberID,
    
  1112.     cleanup,
    
  1113.     getSerializedElementValueByPath,
    
  1114.     deletePath,
    
  1115.     flushInitialOperations,
    
  1116.     getBestMatchForTrackedPath,
    
  1117.     getDisplayNameForFiberID,
    
  1118.     getFiberForNative,
    
  1119.     getFiberIDForNative: getInternalIDForNative,
    
  1120.     getInstanceAndStyle,
    
  1121.     findNativeNodesForFiberID: (id: number) => {
    
  1122.       const nativeNode = findNativeNodeForInternalID(id);
    
  1123.       return nativeNode == null ? null : [nativeNode];
    
  1124.     },
    
  1125.     getOwnersList,
    
  1126.     getPathForElement,
    
  1127.     getProfilingData,
    
  1128.     handleCommitFiberRoot,
    
  1129.     handleCommitFiberUnmount,
    
  1130.     handlePostCommitFiberRoot,
    
  1131.     hasFiberWithId,
    
  1132.     inspectElement,
    
  1133.     logElementToConsole,
    
  1134.     overrideError,
    
  1135.     overrideSuspense,
    
  1136.     overrideValueAtPath,
    
  1137.     renamePath,
    
  1138.     patchConsoleForStrictMode,
    
  1139.     prepareViewAttributeSource,
    
  1140.     prepareViewElementSource,
    
  1141.     renderer,
    
  1142.     setTraceUpdatesEnabled,
    
  1143.     setTrackedPath,
    
  1144.     startProfiling,
    
  1145.     stopProfiling,
    
  1146.     storeAsGlobal,
    
  1147.     unpatchConsoleForStrictMode,
    
  1148.     updateComponentFilters,
    
  1149.   };
    
  1150. }