Files
react/packages/react-devtools-shared/src/inspectedElementMutableSource.js
T

133 lines
3.7 KiB
JavaScript

/**
* 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<string | number>;
type InspectElementReturnType = [
InspectedElementFrontend,
InspectedElementResponseType,
];
export function inspectElement({
bridge,
element,
path,
rendererID,
}: {|
bridge: FrontendBridge,
element: Element,
path: Path | null,
rendererID: number,
|}): Promise<InspectElementReturnType> {
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}"`);
});
}