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 LRU from 'lru-cache';
    
  11. import {
    
  12.   isElement,
    
  13.   typeOf,
    
  14.   ContextConsumer,
    
  15.   ContextProvider,
    
  16.   ForwardRef,
    
  17.   Fragment,
    
  18.   Lazy,
    
  19.   Memo,
    
  20.   Portal,
    
  21.   Profiler,
    
  22.   StrictMode,
    
  23.   Suspense,
    
  24. } from 'react-is';
    
  25. import {
    
  26.   REACT_SUSPENSE_LIST_TYPE as SuspenseList,
    
  27.   REACT_TRACING_MARKER_TYPE as TracingMarker,
    
  28. } from 'shared/ReactSymbols';
    
  29. import {
    
  30.   TREE_OPERATION_ADD,
    
  31.   TREE_OPERATION_REMOVE,
    
  32.   TREE_OPERATION_REMOVE_ROOT,
    
  33.   TREE_OPERATION_REORDER_CHILDREN,
    
  34.   TREE_OPERATION_SET_SUBTREE_MODE,
    
  35.   TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
    
  36.   TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
    
  37.   LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY,
    
  38.   LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
    
  39.   LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
    
  40.   LOCAL_STORAGE_SHOULD_APPEND_COMPONENT_STACK_KEY,
    
  41.   LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
    
  42.   LOCAL_STORAGE_HIDE_CONSOLE_LOGS_IN_STRICT_MODE,
    
  43. } from './constants';
    
  44. import {
    
  45.   ComponentFilterElementType,
    
  46.   ElementTypeHostComponent,
    
  47. } from './frontend/types';
    
  48. import {
    
  49.   ElementTypeRoot,
    
  50.   ElementTypeClass,
    
  51.   ElementTypeForwardRef,
    
  52.   ElementTypeFunction,
    
  53.   ElementTypeMemo,
    
  54. } from 'react-devtools-shared/src/frontend/types';
    
  55. import {localStorageGetItem, localStorageSetItem} from './storage';
    
  56. import {meta} from './hydration';
    
  57. import isArray from './isArray';
    
  58. 
    
  59. import type {
    
  60.   ComponentFilter,
    
  61.   ElementType,
    
  62.   BrowserTheme,
    
  63. } from './frontend/types';
    
  64. import type {LRUCache} from 'react-devtools-shared/src/frontend/types';
    
  65. 
    
  66. // $FlowFixMe[method-unbinding]
    
  67. const hasOwnProperty = Object.prototype.hasOwnProperty;
    
  68. 
    
  69. const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
    
  70. 
    
  71. // On large trees, encoding takes significant time.
    
  72. // Try to reuse the already encoded strings.
    
  73. const encodedStringCache: LRUCache<string, Array<number>> = new LRU({
    
  74.   max: 1000,
    
  75. });
    
  76. 
    
  77. export function alphaSortKeys(
    
  78.   a: string | number | symbol,
    
  79.   b: string | number | symbol,
    
  80. ): number {
    
  81.   if (a.toString() > b.toString()) {
    
  82.     return 1;
    
  83.   } else if (b.toString() > a.toString()) {
    
  84.     return -1;
    
  85.   } else {
    
  86.     return 0;
    
  87.   }
    
  88. }
    
  89. 
    
  90. export function getAllEnumerableKeys(
    
  91.   obj: Object,
    
  92. ): Set<string | number | symbol> {
    
  93.   const keys = new Set<string | number | symbol>();
    
  94.   let current = obj;
    
  95.   while (current != null) {
    
  96.     const currentKeys = [
    
  97.       ...Object.keys(current),
    
  98.       ...Object.getOwnPropertySymbols(current),
    
  99.     ];
    
  100.     const descriptors = Object.getOwnPropertyDescriptors(current);
    
  101.     currentKeys.forEach(key => {
    
  102.       // $FlowFixMe[incompatible-type]: key can be a Symbol https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
    
  103.       if (descriptors[key].enumerable) {
    
  104.         keys.add(key);
    
  105.       }
    
  106.     });
    
  107.     current = Object.getPrototypeOf(current);
    
  108.   }
    
  109.   return keys;
    
  110. }
    
  111. 
    
  112. // Mirror https://github.com/facebook/react/blob/7c21bf72ace77094fd1910cc350a548287ef8350/packages/shared/getComponentName.js#L27-L37
    
  113. export function getWrappedDisplayName(
    
  114.   outerType: mixed,
    
  115.   innerType: any,
    
  116.   wrapperName: string,
    
  117.   fallbackName?: string,
    
  118. ): string {
    
  119.   const displayName = (outerType: any).displayName;
    
  120.   return (
    
  121.     displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})`
    
  122.   );
    
  123. }
    
  124. 
    
  125. export function getDisplayName(
    
  126.   type: Function,
    
  127.   fallbackName: string = 'Anonymous',
    
  128. ): string {
    
  129.   const nameFromCache = cachedDisplayNames.get(type);
    
  130.   if (nameFromCache != null) {
    
  131.     return nameFromCache;
    
  132.   }
    
  133. 
    
  134.   let displayName = fallbackName;
    
  135. 
    
  136.   // The displayName property is not guaranteed to be a string.
    
  137.   // It's only safe to use for our purposes if it's a string.
    
  138.   // github.com/facebook/react-devtools/issues/803
    
  139.   if (typeof type.displayName === 'string') {
    
  140.     displayName = type.displayName;
    
  141.   } else if (typeof type.name === 'string' && type.name !== '') {
    
  142.     displayName = type.name;
    
  143.   }
    
  144. 
    
  145.   cachedDisplayNames.set(type, displayName);
    
  146.   return displayName;
    
  147. }
    
  148. 
    
  149. let uidCounter: number = 0;
    
  150. 
    
  151. export function getUID(): number {
    
  152.   return ++uidCounter;
    
  153. }
    
  154. 
    
  155. export function utfDecodeString(array: Array<number>): string {
    
  156.   // Avoid spreading the array (e.g. String.fromCodePoint(...array))
    
  157.   // Functions arguments are first placed on the stack before the function is called
    
  158.   // which throws a RangeError for large arrays.
    
  159.   // See github.com/facebook/react/issues/22293
    
  160.   let string = '';
    
  161.   for (let i = 0; i < array.length; i++) {
    
  162.     const char = array[i];
    
  163.     string += String.fromCodePoint(char);
    
  164.   }
    
  165.   return string;
    
  166. }
    
  167. 
    
  168. function surrogatePairToCodePoint(
    
  169.   charCode1: number,
    
  170.   charCode2: number,
    
  171. ): number {
    
  172.   return ((charCode1 & 0x3ff) << 10) + (charCode2 & 0x3ff) + 0x10000;
    
  173. }
    
  174. 
    
  175. // Credit for this encoding approach goes to Tim Down:
    
  176. // https://stackoverflow.com/questions/4877326/how-can-i-tell-if-a-string-contains-multibyte-characters-in-javascript
    
  177. export function utfEncodeString(string: string): Array<number> {
    
  178.   const cached = encodedStringCache.get(string);
    
  179.   if (cached !== undefined) {
    
  180.     return cached;
    
  181.   }
    
  182. 
    
  183.   const encoded = [];
    
  184.   let i = 0;
    
  185.   let charCode;
    
  186.   while (i < string.length) {
    
  187.     charCode = string.charCodeAt(i);
    
  188.     // Handle multibyte unicode characters (like emoji).
    
  189.     if ((charCode & 0xf800) === 0xd800) {
    
  190.       encoded.push(surrogatePairToCodePoint(charCode, string.charCodeAt(++i)));
    
  191.     } else {
    
  192.       encoded.push(charCode);
    
  193.     }
    
  194.     ++i;
    
  195.   }
    
  196. 
    
  197.   encodedStringCache.set(string, encoded);
    
  198. 
    
  199.   return encoded;
    
  200. }
    
  201. 
    
  202. export function printOperationsArray(operations: Array<number>) {
    
  203.   // The first two values are always rendererID and rootID
    
  204.   const rendererID = operations[0];
    
  205.   const rootID = operations[1];
    
  206. 
    
  207.   const logs = [`operations for renderer:${rendererID} and root:${rootID}`];
    
  208. 
    
  209.   let i = 2;
    
  210. 
    
  211.   // Reassemble the string table.
    
  212.   const stringTable: Array<null | string> = [
    
  213.     null, // ID = 0 corresponds to the null string.
    
  214.   ];
    
  215.   const stringTableSize = operations[i++];
    
  216.   const stringTableEnd = i + stringTableSize;
    
  217.   while (i < stringTableEnd) {
    
  218.     const nextLength = operations[i++];
    
  219.     const nextString = utfDecodeString(
    
  220.       (operations.slice(i, i + nextLength): any),
    
  221.     );
    
  222.     stringTable.push(nextString);
    
  223.     i += nextLength;
    
  224.   }
    
  225. 
    
  226.   while (i < operations.length) {
    
  227.     const operation = operations[i];
    
  228. 
    
  229.     switch (operation) {
    
  230.       case TREE_OPERATION_ADD: {
    
  231.         const id = ((operations[i + 1]: any): number);
    
  232.         const type = ((operations[i + 2]: any): ElementType);
    
  233. 
    
  234.         i += 3;
    
  235. 
    
  236.         if (type === ElementTypeRoot) {
    
  237.           logs.push(`Add new root node ${id}`);
    
  238. 
    
  239.           i++; // isStrictModeCompliant
    
  240.           i++; // supportsProfiling
    
  241.           i++; // supportsStrictMode
    
  242.           i++; // hasOwnerMetadata
    
  243.         } else {
    
  244.           const parentID = ((operations[i]: any): number);
    
  245.           i++;
    
  246. 
    
  247.           i++; // ownerID
    
  248. 
    
  249.           const displayNameStringID = operations[i];
    
  250.           const displayName = stringTable[displayNameStringID];
    
  251.           i++;
    
  252. 
    
  253.           i++; // key
    
  254. 
    
  255.           logs.push(
    
  256.             `Add node ${id} (${displayName || 'null'}) as child of ${parentID}`,
    
  257.           );
    
  258.         }
    
  259.         break;
    
  260.       }
    
  261.       case TREE_OPERATION_REMOVE: {
    
  262.         const removeLength = ((operations[i + 1]: any): number);
    
  263.         i += 2;
    
  264. 
    
  265.         for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) {
    
  266.           const id = ((operations[i]: any): number);
    
  267.           i += 1;
    
  268. 
    
  269.           logs.push(`Remove node ${id}`);
    
  270.         }
    
  271.         break;
    
  272.       }
    
  273.       case TREE_OPERATION_REMOVE_ROOT: {
    
  274.         i += 1;
    
  275. 
    
  276.         logs.push(`Remove root ${rootID}`);
    
  277.         break;
    
  278.       }
    
  279.       case TREE_OPERATION_SET_SUBTREE_MODE: {
    
  280.         const id = operations[i + 1];
    
  281.         const mode = operations[i + 1];
    
  282. 
    
  283.         i += 3;
    
  284. 
    
  285.         logs.push(`Mode ${mode} set for subtree with root ${id}`);
    
  286.         break;
    
  287.       }
    
  288.       case TREE_OPERATION_REORDER_CHILDREN: {
    
  289.         const id = ((operations[i + 1]: any): number);
    
  290.         const numChildren = ((operations[i + 2]: any): number);
    
  291.         i += 3;
    
  292.         const children = operations.slice(i, i + numChildren);
    
  293.         i += numChildren;
    
  294. 
    
  295.         logs.push(`Re-order node ${id} children ${children.join(',')}`);
    
  296.         break;
    
  297.       }
    
  298.       case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
    
  299.         // Base duration updates are only sent while profiling is in progress.
    
  300.         // We can ignore them at this point.
    
  301.         // The profiler UI uses them lazily in order to generate the tree.
    
  302.         i += 3;
    
  303.         break;
    
  304.       case TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS:
    
  305.         const id = operations[i + 1];
    
  306.         const numErrors = operations[i + 2];
    
  307.         const numWarnings = operations[i + 3];
    
  308. 
    
  309.         i += 4;
    
  310. 
    
  311.         logs.push(
    
  312.           `Node ${id} has ${numErrors} errors and ${numWarnings} warnings`,
    
  313.         );
    
  314.         break;
    
  315.       default:
    
  316.         throw Error(`Unsupported Bridge operation "${operation}"`);
    
  317.     }
    
  318.   }
    
  319. 
    
  320.   console.log(logs.join('\n  '));
    
  321. }
    
  322. 
    
  323. export function getDefaultComponentFilters(): Array<ComponentFilter> {
    
  324.   return [
    
  325.     {
    
  326.       type: ComponentFilterElementType,
    
  327.       value: ElementTypeHostComponent,
    
  328.       isEnabled: true,
    
  329.     },
    
  330.   ];
    
  331. }
    
  332. 
    
  333. export function getSavedComponentFilters(): Array<ComponentFilter> {
    
  334.   try {
    
  335.     const raw = localStorageGetItem(
    
  336.       LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY,
    
  337.     );
    
  338.     if (raw != null) {
    
  339.       return JSON.parse(raw);
    
  340.     }
    
  341.   } catch (error) {}
    
  342.   return getDefaultComponentFilters();
    
  343. }
    
  344. 
    
  345. export function setSavedComponentFilters(
    
  346.   componentFilters: Array<ComponentFilter>,
    
  347. ): void {
    
  348.   localStorageSetItem(
    
  349.     LOCAL_STORAGE_COMPONENT_FILTER_PREFERENCES_KEY,
    
  350.     JSON.stringify(componentFilters),
    
  351.   );
    
  352. }
    
  353. 
    
  354. function parseBool(s: ?string): ?boolean {
    
  355.   if (s === 'true') {
    
  356.     return true;
    
  357.   }
    
  358.   if (s === 'false') {
    
  359.     return false;
    
  360.   }
    
  361. }
    
  362. 
    
  363. export function castBool(v: any): ?boolean {
    
  364.   if (v === true || v === false) {
    
  365.     return v;
    
  366.   }
    
  367. }
    
  368. 
    
  369. export function castBrowserTheme(v: any): ?BrowserTheme {
    
  370.   if (v === 'light' || v === 'dark' || v === 'auto') {
    
  371.     return v;
    
  372.   }
    
  373. }
    
  374. 
    
  375. export function getAppendComponentStack(): boolean {
    
  376.   const raw = localStorageGetItem(
    
  377.     LOCAL_STORAGE_SHOULD_APPEND_COMPONENT_STACK_KEY,
    
  378.   );
    
  379.   return parseBool(raw) ?? true;
    
  380. }
    
  381. 
    
  382. export function getBreakOnConsoleErrors(): boolean {
    
  383.   const raw = localStorageGetItem(LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS);
    
  384.   return parseBool(raw) ?? false;
    
  385. }
    
  386. 
    
  387. export function getHideConsoleLogsInStrictMode(): boolean {
    
  388.   const raw = localStorageGetItem(
    
  389.     LOCAL_STORAGE_HIDE_CONSOLE_LOGS_IN_STRICT_MODE,
    
  390.   );
    
  391.   return parseBool(raw) ?? false;
    
  392. }
    
  393. 
    
  394. export function getShowInlineWarningsAndErrors(): boolean {
    
  395.   const raw = localStorageGetItem(
    
  396.     LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
    
  397.   );
    
  398.   return parseBool(raw) ?? true;
    
  399. }
    
  400. 
    
  401. export function getDefaultOpenInEditorURL(): string {
    
  402.   return typeof process.env.EDITOR_URL === 'string'
    
  403.     ? process.env.EDITOR_URL
    
  404.     : '';
    
  405. }
    
  406. 
    
  407. export function getOpenInEditorURL(): string {
    
  408.   try {
    
  409.     const raw = localStorageGetItem(LOCAL_STORAGE_OPEN_IN_EDITOR_URL);
    
  410.     if (raw != null) {
    
  411.       return JSON.parse(raw);
    
  412.     }
    
  413.   } catch (error) {}
    
  414.   return getDefaultOpenInEditorURL();
    
  415. }
    
  416. 
    
  417. export function separateDisplayNameAndHOCs(
    
  418.   displayName: string | null,
    
  419.   type: ElementType,
    
  420. ): [string | null, Array<string> | null] {
    
  421.   if (displayName === null) {
    
  422.     return [null, null];
    
  423.   }
    
  424. 
    
  425.   let hocDisplayNames = null;
    
  426. 
    
  427.   switch (type) {
    
  428.     case ElementTypeClass:
    
  429.     case ElementTypeForwardRef:
    
  430.     case ElementTypeFunction:
    
  431.     case ElementTypeMemo:
    
  432.       if (displayName.indexOf('(') >= 0) {
    
  433.         const matches = displayName.match(/[^()]+/g);
    
  434.         if (matches != null) {
    
  435.           displayName = matches.pop();
    
  436.           hocDisplayNames = matches;
    
  437.         }
    
  438.       }
    
  439.       break;
    
  440.     default:
    
  441.       break;
    
  442.   }
    
  443. 
    
  444.   return [displayName, hocDisplayNames];
    
  445. }
    
  446. 
    
  447. // Pulled from react-compat
    
  448. // https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349
    
  449. export function shallowDiffers(prev: Object, next: Object): boolean {
    
  450.   for (const attribute in prev) {
    
  451.     if (!(attribute in next)) {
    
  452.       return true;
    
  453.     }
    
  454.   }
    
  455.   for (const attribute in next) {
    
  456.     if (prev[attribute] !== next[attribute]) {
    
  457.       return true;
    
  458.     }
    
  459.   }
    
  460.   return false;
    
  461. }
    
  462. 
    
  463. export function getInObject(object: Object, path: Array<string | number>): any {
    
  464.   return path.reduce((reduced: Object, attr: any): any => {
    
  465.     if (reduced) {
    
  466.       if (hasOwnProperty.call(reduced, attr)) {
    
  467.         return reduced[attr];
    
  468.       }
    
  469.       if (typeof reduced[Symbol.iterator] === 'function') {
    
  470.         // Convert iterable to array and return array[index]
    
  471.         //
    
  472.         // TRICKY
    
  473.         // Don't use [...spread] syntax for this purpose.
    
  474.         // This project uses @babel/plugin-transform-spread in "loose" mode which only works with Array values.
    
  475.         // Other types (e.g. typed arrays, Sets) will not spread correctly.
    
  476.         return Array.from(reduced)[attr];
    
  477.       }
    
  478.     }
    
  479. 
    
  480.     return null;
    
  481.   }, object);
    
  482. }
    
  483. 
    
  484. export function deletePathInObject(
    
  485.   object: Object,
    
  486.   path: Array<string | number>,
    
  487. ) {
    
  488.   const length = path.length;
    
  489.   const last = path[length - 1];
    
  490.   if (object != null) {
    
  491.     const parent = getInObject(object, path.slice(0, length - 1));
    
  492.     if (parent) {
    
  493.       if (isArray(parent)) {
    
  494.         parent.splice(((last: any): number), 1);
    
  495.       } else {
    
  496.         delete parent[last];
    
  497.       }
    
  498.     }
    
  499.   }
    
  500. }
    
  501. 
    
  502. export function renamePathInObject(
    
  503.   object: Object,
    
  504.   oldPath: Array<string | number>,
    
  505.   newPath: Array<string | number>,
    
  506. ) {
    
  507.   const length = oldPath.length;
    
  508.   if (object != null) {
    
  509.     const parent = getInObject(object, oldPath.slice(0, length - 1));
    
  510.     if (parent) {
    
  511.       const lastOld = oldPath[length - 1];
    
  512.       const lastNew = newPath[length - 1];
    
  513.       parent[lastNew] = parent[lastOld];
    
  514.       if (isArray(parent)) {
    
  515.         parent.splice(((lastOld: any): number), 1);
    
  516.       } else {
    
  517.         delete parent[lastOld];
    
  518.       }
    
  519.     }
    
  520.   }
    
  521. }
    
  522. 
    
  523. export function setInObject(
    
  524.   object: Object,
    
  525.   path: Array<string | number>,
    
  526.   value: any,
    
  527. ) {
    
  528.   const length = path.length;
    
  529.   const last = path[length - 1];
    
  530.   if (object != null) {
    
  531.     const parent = getInObject(object, path.slice(0, length - 1));
    
  532.     if (parent) {
    
  533.       parent[last] = value;
    
  534.     }
    
  535.   }
    
  536. }
    
  537. 
    
  538. export type DataType =
    
  539.   | 'array'
    
  540.   | 'array_buffer'
    
  541.   | 'bigint'
    
  542.   | 'boolean'
    
  543.   | 'class_instance'
    
  544.   | 'data_view'
    
  545.   | 'date'
    
  546.   | 'function'
    
  547.   | 'html_all_collection'
    
  548.   | 'html_element'
    
  549.   | 'infinity'
    
  550.   | 'iterator'
    
  551.   | 'opaque_iterator'
    
  552.   | 'nan'
    
  553.   | 'null'
    
  554.   | 'number'
    
  555.   | 'object'
    
  556.   | 'react_element'
    
  557.   | 'regexp'
    
  558.   | 'string'
    
  559.   | 'symbol'
    
  560.   | 'typed_array'
    
  561.   | 'undefined'
    
  562.   | 'unknown';
    
  563. 
    
  564. /**
    
  565.  * Get a enhanced/artificial type string based on the object instance
    
  566.  */
    
  567. export function getDataType(data: Object): DataType {
    
  568.   if (data === null) {
    
  569.     return 'null';
    
  570.   } else if (data === undefined) {
    
  571.     return 'undefined';
    
  572.   }
    
  573. 
    
  574.   if (isElement(data)) {
    
  575.     return 'react_element';
    
  576.   }
    
  577. 
    
  578.   if (typeof HTMLElement !== 'undefined' && data instanceof HTMLElement) {
    
  579.     return 'html_element';
    
  580.   }
    
  581. 
    
  582.   const type = typeof data;
    
  583.   switch (type) {
    
  584.     case 'bigint':
    
  585.       return 'bigint';
    
  586.     case 'boolean':
    
  587.       return 'boolean';
    
  588.     case 'function':
    
  589.       return 'function';
    
  590.     case 'number':
    
  591.       if (Number.isNaN(data)) {
    
  592.         return 'nan';
    
  593.       } else if (!Number.isFinite(data)) {
    
  594.         return 'infinity';
    
  595.       } else {
    
  596.         return 'number';
    
  597.       }
    
  598.     case 'object':
    
  599.       if (isArray(data)) {
    
  600.         return 'array';
    
  601.       } else if (ArrayBuffer.isView(data)) {
    
  602.         return hasOwnProperty.call(data.constructor, 'BYTES_PER_ELEMENT')
    
  603.           ? 'typed_array'
    
  604.           : 'data_view';
    
  605.       } else if (data.constructor && data.constructor.name === 'ArrayBuffer') {
    
  606.         // HACK This ArrayBuffer check is gross; is there a better way?
    
  607.         // We could try to create a new DataView with the value.
    
  608.         // If it doesn't error, we know it's an ArrayBuffer,
    
  609.         // but this seems kind of awkward and expensive.
    
  610.         return 'array_buffer';
    
  611.       } else if (typeof data[Symbol.iterator] === 'function') {
    
  612.         const iterator = data[Symbol.iterator]();
    
  613.         if (!iterator) {
    
  614.           // Proxies might break assumptoins about iterators.
    
  615.           // See github.com/facebook/react/issues/21654
    
  616.         } else {
    
  617.           return iterator === data ? 'opaque_iterator' : 'iterator';
    
  618.         }
    
  619.       } else if (data.constructor && data.constructor.name === 'RegExp') {
    
  620.         return 'regexp';
    
  621.       } else {
    
  622.         // $FlowFixMe[method-unbinding]
    
  623.         const toStringValue = Object.prototype.toString.call(data);
    
  624.         if (toStringValue === '[object Date]') {
    
  625.           return 'date';
    
  626.         } else if (toStringValue === '[object HTMLAllCollection]') {
    
  627.           return 'html_all_collection';
    
  628.         }
    
  629.       }
    
  630. 
    
  631.       if (!isPlainObject(data)) {
    
  632.         return 'class_instance';
    
  633.       }
    
  634. 
    
  635.       return 'object';
    
  636.     case 'string':
    
  637.       return 'string';
    
  638.     case 'symbol':
    
  639.       return 'symbol';
    
  640.     case 'undefined':
    
  641.       if (
    
  642.         // $FlowFixMe[method-unbinding]
    
  643.         Object.prototype.toString.call(data) === '[object HTMLAllCollection]'
    
  644.       ) {
    
  645.         return 'html_all_collection';
    
  646.       }
    
  647.       return 'undefined';
    
  648.     default:
    
  649.       return 'unknown';
    
  650.   }
    
  651. }
    
  652. 
    
  653. export function getDisplayNameForReactElement(
    
  654.   element: React$Element<any>,
    
  655. ): string | null {
    
  656.   const elementType = typeOf(element);
    
  657.   switch (elementType) {
    
  658.     case ContextConsumer:
    
  659.       return 'ContextConsumer';
    
  660.     case ContextProvider:
    
  661.       return 'ContextProvider';
    
  662.     case ForwardRef:
    
  663.       return 'ForwardRef';
    
  664.     case Fragment:
    
  665.       return 'Fragment';
    
  666.     case Lazy:
    
  667.       return 'Lazy';
    
  668.     case Memo:
    
  669.       return 'Memo';
    
  670.     case Portal:
    
  671.       return 'Portal';
    
  672.     case Profiler:
    
  673.       return 'Profiler';
    
  674.     case StrictMode:
    
  675.       return 'StrictMode';
    
  676.     case Suspense:
    
  677.       return 'Suspense';
    
  678.     case SuspenseList:
    
  679.       return 'SuspenseList';
    
  680.     case TracingMarker:
    
  681.       return 'TracingMarker';
    
  682.     default:
    
  683.       const {type} = element;
    
  684.       if (typeof type === 'string') {
    
  685.         return type;
    
  686.       } else if (typeof type === 'function') {
    
  687.         return getDisplayName(type, 'Anonymous');
    
  688.       } else if (type != null) {
    
  689.         return 'NotImplementedInDevtools';
    
  690.       } else {
    
  691.         return 'Element';
    
  692.       }
    
  693.   }
    
  694. }
    
  695. 
    
  696. const MAX_PREVIEW_STRING_LENGTH = 50;
    
  697. 
    
  698. function truncateForDisplay(
    
  699.   string: string,
    
  700.   length: number = MAX_PREVIEW_STRING_LENGTH,
    
  701. ) {
    
  702.   if (string.length > length) {
    
  703.     return string.slice(0, length) + '';
    
  704.   } else {
    
  705.     return string;
    
  706.   }
    
  707. }
    
  708. 
    
  709. // Attempts to mimic Chrome's inline preview for values.
    
  710. // For example, the following value...
    
  711. //   {
    
  712. //      foo: 123,
    
  713. //      bar: "abc",
    
  714. //      baz: [true, false],
    
  715. //      qux: { ab: 1, cd: 2 }
    
  716. //   };
    
  717. //
    
  718. // Would show a preview of...
    
  719. //   {foo: 123, bar: "abc", baz: Array(2), qux: {…}}
    
  720. //
    
  721. // And the following value...
    
  722. //   [
    
  723. //     123,
    
  724. //     "abc",
    
  725. //     [true, false],
    
  726. //     { foo: 123, bar: "abc" }
    
  727. //   ];
    
  728. //
    
  729. // Would show a preview of...
    
  730. //   [123, "abc", Array(2), {…}]
    
  731. export function formatDataForPreview(
    
  732.   data: any,
    
  733.   showFormattedValue: boolean,
    
  734. ): string {
    
  735.   if (data != null && hasOwnProperty.call(data, meta.type)) {
    
  736.     return showFormattedValue
    
  737.       ? data[meta.preview_long]
    
  738.       : data[meta.preview_short];
    
  739.   }
    
  740. 
    
  741.   const type = getDataType(data);
    
  742. 
    
  743.   switch (type) {
    
  744.     case 'html_element':
    
  745.       return `<${truncateForDisplay(data.tagName.toLowerCase())} />`;
    
  746.     case 'function':
    
  747.       return truncateForDisplay(
    
  748.         `ƒ ${typeof data.name === 'function' ? '' : data.name}() {}`,
    
  749.       );
    
  750.     case 'string':
    
  751.       return `"${data}"`;
    
  752.     case 'bigint':
    
  753.       return truncateForDisplay(data.toString() + 'n');
    
  754.     case 'regexp':
    
  755.       return truncateForDisplay(data.toString());
    
  756.     case 'symbol':
    
  757.       return truncateForDisplay(data.toString());
    
  758.     case 'react_element':
    
  759.       return `<${truncateForDisplay(
    
  760.         getDisplayNameForReactElement(data) || 'Unknown',
    
  761.       )} />`;
    
  762.     case 'array_buffer':
    
  763.       return `ArrayBuffer(${data.byteLength})`;
    
  764.     case 'data_view':
    
  765.       return `DataView(${data.buffer.byteLength})`;
    
  766.     case 'array':
    
  767.       if (showFormattedValue) {
    
  768.         let formatted = '';
    
  769.         for (let i = 0; i < data.length; i++) {
    
  770.           if (i > 0) {
    
  771.             formatted += ', ';
    
  772.           }
    
  773.           formatted += formatDataForPreview(data[i], false);
    
  774.           if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
    
  775.             // Prevent doing a lot of unnecessary iteration...
    
  776.             break;
    
  777.           }
    
  778.         }
    
  779.         return `[${truncateForDisplay(formatted)}]`;
    
  780.       } else {
    
  781.         const length = hasOwnProperty.call(data, meta.size)
    
  782.           ? data[meta.size]
    
  783.           : data.length;
    
  784.         return `Array(${length})`;
    
  785.       }
    
  786.     case 'typed_array':
    
  787.       const shortName = `${data.constructor.name}(${data.length})`;
    
  788.       if (showFormattedValue) {
    
  789.         let formatted = '';
    
  790.         for (let i = 0; i < data.length; i++) {
    
  791.           if (i > 0) {
    
  792.             formatted += ', ';
    
  793.           }
    
  794.           formatted += data[i];
    
  795.           if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
    
  796.             // Prevent doing a lot of unnecessary iteration...
    
  797.             break;
    
  798.           }
    
  799.         }
    
  800.         return `${shortName} [${truncateForDisplay(formatted)}]`;
    
  801.       } else {
    
  802.         return shortName;
    
  803.       }
    
  804.     case 'iterator':
    
  805.       const name = data.constructor.name;
    
  806. 
    
  807.       if (showFormattedValue) {
    
  808.         // TRICKY
    
  809.         // Don't use [...spread] syntax for this purpose.
    
  810.         // This project uses @babel/plugin-transform-spread in "loose" mode which only works with Array values.
    
  811.         // Other types (e.g. typed arrays, Sets) will not spread correctly.
    
  812.         const array = Array.from(data);
    
  813. 
    
  814.         let formatted = '';
    
  815.         for (let i = 0; i < array.length; i++) {
    
  816.           const entryOrEntries = array[i];
    
  817. 
    
  818.           if (i > 0) {
    
  819.             formatted += ', ';
    
  820.           }
    
  821. 
    
  822.           // TRICKY
    
  823.           // Browsers display Maps and Sets differently.
    
  824.           // To mimic their behavior, detect if we've been given an entries tuple.
    
  825.           //   Map(2) {"abc" => 123, "def" => 123}
    
  826.           //   Set(2) {"abc", 123}
    
  827.           if (isArray(entryOrEntries)) {
    
  828.             const key = formatDataForPreview(entryOrEntries[0], true);
    
  829.             const value = formatDataForPreview(entryOrEntries[1], false);
    
  830.             formatted += `${key} => ${value}`;
    
  831.           } else {
    
  832.             formatted += formatDataForPreview(entryOrEntries, false);
    
  833.           }
    
  834. 
    
  835.           if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
    
  836.             // Prevent doing a lot of unnecessary iteration...
    
  837.             break;
    
  838.           }
    
  839.         }
    
  840. 
    
  841.         return `${name}(${data.size}) {${truncateForDisplay(formatted)}}`;
    
  842.       } else {
    
  843.         return `${name}(${data.size})`;
    
  844.       }
    
  845.     case 'opaque_iterator': {
    
  846.       return data[Symbol.toStringTag];
    
  847.     }
    
  848.     case 'date':
    
  849.       return data.toString();
    
  850.     case 'class_instance':
    
  851.       return data.constructor.name;
    
  852.     case 'object':
    
  853.       if (showFormattedValue) {
    
  854.         const keys = Array.from(getAllEnumerableKeys(data)).sort(alphaSortKeys);
    
  855. 
    
  856.         let formatted = '';
    
  857.         for (let i = 0; i < keys.length; i++) {
    
  858.           const key = keys[i];
    
  859.           if (i > 0) {
    
  860.             formatted += ', ';
    
  861.           }
    
  862.           formatted += `${key.toString()}: ${formatDataForPreview(
    
  863.             data[key],
    
  864.             false,
    
  865.           )}`;
    
  866.           if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
    
  867.             // Prevent doing a lot of unnecessary iteration...
    
  868.             break;
    
  869.           }
    
  870.         }
    
  871.         return `{${truncateForDisplay(formatted)}}`;
    
  872.       } else {
    
  873.         return '{…}';
    
  874.       }
    
  875.     case 'boolean':
    
  876.     case 'number':
    
  877.     case 'infinity':
    
  878.     case 'nan':
    
  879.     case 'null':
    
  880.     case 'undefined':
    
  881.       return data;
    
  882.     default:
    
  883.       try {
    
  884.         return truncateForDisplay(String(data));
    
  885.       } catch (error) {
    
  886.         return 'unserializable';
    
  887.       }
    
  888.   }
    
  889. }
    
  890. 
    
  891. // Basically checking that the object only has Object in its prototype chain
    
  892. export const isPlainObject = (object: Object): boolean => {
    
  893.   const objectPrototype = Object.getPrototypeOf(object);
    
  894.   if (!objectPrototype) return true;
    
  895. 
    
  896.   const objectParentPrototype = Object.getPrototypeOf(objectPrototype);
    
  897.   return !objectParentPrototype;
    
  898. };