/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import { convertInspectedElementBackendToFrontend, hydrateHelper, inspectElement as inspectElementAPI, } from 'react-devtools-shared/src/backendAPI'; import {fillInPath} from 'react-devtools-shared/src/hydration'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; import type { InspectElementFullData, InspectElementHydratedPath, } from 'react-devtools-shared/src/backend/types'; import type { Element, InspectedElement as InspectedElementFrontend, InspectedElementResponseType, } from 'react-devtools-shared/src/devtools/views/Components/types'; // Map an Element in the Store to the most recent copy of its inspected data. // As updates comes from the backend, inspected data is updated. // Both this map and the inspected objects in it are mutable. // They should never be read from directly during render; // Use a Suspense cache to ensure that transitions work correctly and there is no tearing. const inspectedElementMap: WeakMap< Element, InspectedElementFrontend, > = new WeakMap(); type Path = Array; type InspectElementReturnType = [ InspectedElementFrontend, InspectedElementResponseType, ]; export function inspectElement({ bridge, element, path, rendererID, }: {| bridge: FrontendBridge, element: Element, path: Path | null, rendererID: number, |}): Promise { const {id} = element; return inspectElementAPI({ bridge, id, path, rendererID, }).then((data: any) => { const {type} = data; let inspectedElement; switch (type) { case 'no-change': // This is a no-op for the purposes of our cache. inspectedElement = inspectedElementMap.get(element); if (inspectedElement != null) { return [inspectedElement, type]; } break; case 'not-found': // This is effectively a no-op. // If the Element is still in the Store, we can eagerly remove it from the Map. inspectedElementMap.delete(element); throw Error(`Element "${id}" not found`); case 'full-data': const fullData = ((data: any): InspectElementFullData); // New data has come in. // We should replace the data in our local mutable copy. inspectedElement = convertInspectedElementBackendToFrontend( fullData.value, ); inspectedElementMap.set(element, inspectedElement); return [inspectedElement, type]; case 'hydrated-path': const hydratedPathData = ((data: any): InspectElementHydratedPath); const {value} = hydratedPathData; // A path has been hydrated. // Merge it with the latest copy we have locally and resolve with the merged value. inspectedElement = inspectedElementMap.get(element) || null; if (inspectedElement !== null) { // Clone element inspectedElement = {...inspectedElement}; // Merge hydrated data fillInPath( inspectedElement, value, ((path: any): Path), hydrateHelper(value, ((path: any): Path)), ); inspectedElementMap.set(element, inspectedElement); return [inspectedElement, type]; } break; default: // Should never happen. if (__DEV__) { console.error( `Unexpected inspected element response data: "${type}"`, ); } break; } throw Error(`Unable to inspect element with id "${id}"`); }); }