fix[devtools/inspectElement]: dont pause initial inspectElement call when user switches tabs (#27488)

There are not so many changes, most of them are changing imports,
because I've moved types for UI in a single file.

In https://github.com/facebook/react/pull/27357 I've added support for
pausing polling events: when user inspects an element, we start polling
React DevTools backend for updates in props / state. If user switches
tabs, extension's service worker can be killed by browser and this
polling will start spamming errors.

What I've missed is that we also have a separate call for this API, but
which is executed only once when user selects an element. We don't
handle promise rejection here and this can lead to some errors when user
selects an element and switches tabs right after it.

The only change here is that this API now has
`shouldListenToPauseEvents` param, which is `true` for polling, so we
will pause polling once user switches tabs. It is `false` by default, so
we won't pause initial call by accident.


https://github.com/hoxyq/react/blob/af8beeebf63b5824497fcd0bb35b7c0ac8fe60a0/packages/react-devtools-shared/src/backendAPI.js#L96
This commit is contained in:
Ruslan Lesiutin
2023-10-10 18:10:17 +01:00
committed by GitHub
parent 151e75a128
commit 77ec61885f
58 changed files with 305 additions and 282 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ import {
} from './cachedSettings';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {ComponentFilter} from 'react-devtools-shared/src/types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
import type {DevToolsHook} from 'react-devtools-shared/src/backend/types';
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
+1 -1
View File
@@ -33,7 +33,7 @@ import {
import {localStorageSetItem} from 'react-devtools-shared/src/storage';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
installHook(window);
+1 -1
View File
@@ -8,7 +8,7 @@ import {installHook} from 'react-devtools-shared/src/hook';
import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {Wall} from 'react-devtools-shared/src/types';
import type {Wall} from 'react-devtools-shared/src/frontend/types';
function startActivation(contentWindow: any, bridge: BackendBridge) {
const onSavedPreferences = (data: $FlowFixMe) => {
+1 -1
View File
@@ -13,7 +13,7 @@ import {
getHideConsoleLogsInStrictMode,
} from 'react-devtools-shared/src/utils';
import type {Wall} from 'react-devtools-shared/src/types';
import type {Wall} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Props} from 'react-devtools-shared/src/devtools/views/DevTools';
@@ -30,12 +30,7 @@ describe('InspectedElementContext', () => {
): Promise<Object> {
const rendererID = ((store.getRendererIDForElement(id): any): number);
const promise = backendAPI
.inspectElement({
bridge,
id,
path,
rendererID,
})
.inspectElement(bridge, false, id, path, rendererID)
.then(data =>
backendAPI.convertInspectedElementBackendToFrontend(data.value),
);
@@ -8,7 +8,7 @@
*/
import typeof ReactTestRenderer from 'react-test-renderer';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type Store from 'react-devtools-shared/src/devtools/store';
@@ -187,7 +187,7 @@ describe('ProfilerStore', () => {
utils.act(() => {
const {
ElementTypeHostComponent,
} = require('react-devtools-shared/src/types');
} = require('react-devtools-shared/src/frontend/types');
store.componentFilters = [
utils.createElementTypeFilter(ElementTypeHostComponent),
];
@@ -36,7 +36,7 @@ describe('Store component filters', () => {
store.recordChangeDescriptions = true;
React = require('react');
Types = require('react-devtools-shared/src/types');
Types = require('react-devtools-shared/src/frontend/types');
utils = require('./utils');
legacyRender = utils.legacyRender;
+5 -5
View File
@@ -12,7 +12,7 @@ import typeof ReactTestRenderer from 'react-test-renderer';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type Store from 'react-devtools-shared/src/devtools/store';
import type {ProfilingDataFrontend} from 'react-devtools-shared/src/devtools/views/Profiler/types';
import type {ElementType} from 'react-devtools-shared/src/types';
import type {ElementType} from 'react-devtools-shared/src/frontend/types';
export function act(
callback: Function,
@@ -86,7 +86,7 @@ export function createDisplayNameFilter(
source: string,
isEnabled: boolean = true,
) {
const Types = require('react-devtools-shared/src/types');
const Types = require('react-devtools-shared/src/frontend/types');
let isValid = true;
try {
new RegExp(source); // eslint-disable-line no-new
@@ -102,7 +102,7 @@ export function createDisplayNameFilter(
}
export function createHOCFilter(isEnabled: boolean = true) {
const Types = require('react-devtools-shared/src/types');
const Types = require('react-devtools-shared/src/frontend/types');
return {
type: Types.ComponentFilterHOC,
isEnabled,
@@ -114,7 +114,7 @@ export function createElementTypeFilter(
elementType: ElementType,
isEnabled: boolean = true,
) {
const Types = require('react-devtools-shared/src/types');
const Types = require('react-devtools-shared/src/frontend/types');
return {
type: Types.ComponentFilterElementType,
isEnabled,
@@ -126,7 +126,7 @@ export function createLocationFilter(
source: string,
isEnabled: boolean = true,
) {
const Types = require('react-devtools-shared/src/types');
const Types = require('react-devtools-shared/src/frontend/types');
let isValid = true;
try {
new RegExp(source); // eslint-disable-line no-new
+1 -1
View File
@@ -7,7 +7,7 @@
* @flow
*/
import type {StyleXPlugin} from 'react-devtools-shared/src/types';
import type {StyleXPlugin} from 'react-devtools-shared/src/frontend/types';
import isArray from 'react-devtools-shared/src/isArray';
const cachedStyleNameToValueMap: Map<string, string> = new Map();
+1 -1
View File
@@ -42,7 +42,7 @@ import type {
import type {
ComponentFilter,
BrowserTheme,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {isSynchronousXHRSupported} from './utils';
const debug = (methodName: string, ...args: Array<string>) => {
@@ -13,7 +13,7 @@ import {
ElementTypeRoot,
ElementTypeHostComponent,
ElementTypeOtherOrUnknown,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {getUID, utfEncodeString, printOperationsArray} from '../../utils';
import {
cleanForBridge,
@@ -50,7 +50,7 @@ import type {
import type {
ComponentFilter,
ElementType,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import type {InspectedElement, SerializedElement} from '../types';
export type InternalInstance = Object;
+2 -2
View File
@@ -25,7 +25,7 @@ import {
ElementTypeSuspenseList,
ElementTypeTracingMarker,
StrictMode,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {
deletePathInObject,
getDisplayName,
@@ -121,7 +121,7 @@ import type {
ComponentFilter,
ElementType,
Plugins,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
type getTypeSymbolType = (type: any) => symbol | number;
+2 -2
View File
@@ -21,14 +21,14 @@ import type {
ComponentFilter,
ElementType,
Plugins,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import type {
ResolveNativeStyle,
SetupNativeStyleEditor,
} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
import type {InitBackend} from 'react-devtools-shared/src/backend';
import type {TimelineDataExport} from 'react-devtools-timeline/src/types';
import type {BrowserTheme} from 'react-devtools-shared/src/types';
import type {BrowserTheme} from 'react-devtools-shared/src/frontend/types';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type Agent from './agent';
+1 -1
View File
@@ -12,7 +12,7 @@ import {compareVersions} from 'compare-versions';
import {dehydrate} from '../hydration';
import isArray from 'shared/isArray';
import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {DehydratedData} from 'react-devtools-shared/src/frontend/types';
// TODO: update this to the first React version that has a corresponding DevTools backend
const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9';
+28 -15
View File
@@ -24,7 +24,8 @@ import type {
import type {
DehydratedData,
InspectedElement as InspectedElementFrontend,
} from 'react-devtools-shared/src/devtools/views/Components/types';
} from 'react-devtools-shared/src/frontend/types';
import type {InspectedElementPath} from 'react-devtools-shared/src/frontend/types';
export function clearErrorsAndWarnings({
bridge,
@@ -86,25 +87,21 @@ export function copyInspectedElementPath({
});
}
export function inspectElement({
bridge,
forceFullData,
id,
path,
rendererID,
}: {
export function inspectElement(
bridge: FrontendBridge,
forceFullData: boolean,
id: number,
path: Array<string | number> | null,
path: InspectedElementPath | null,
rendererID: number,
}): Promise<InspectedElementPayload> {
shouldListenToPauseEvents: boolean = false,
): Promise<InspectedElementPayload> {
const requestID = requestCounter++;
const promise = getPromiseForRequestID<InspectedElementPayload>(
requestID,
'inspectedElement',
bridge,
`Timed out while inspecting element ${id}.`,
shouldListenToPauseEvents,
);
bridge.send('inspectElement', {
@@ -148,16 +145,29 @@ function getPromiseForRequestID<T>(
eventType: $Keys<BackendEvents>,
bridge: FrontendBridge,
timeoutMessage: string,
shouldListenToPauseEvents: boolean = false,
): Promise<T> {
return new Promise((resolve, reject) => {
const cleanup = () => {
bridge.removeListener(eventType, onInspectedElement);
bridge.removeListener('shutdown', onDisconnect);
bridge.removeListener('pauseElementPolling', onDisconnect);
bridge.removeListener('shutdown', onShutdown);
if (shouldListenToPauseEvents) {
bridge.removeListener('pauseElementPolling', onDisconnect);
}
clearTimeout(timeoutID);
};
const onShutdown = () => {
cleanup();
reject(
new Error(
'Failed to inspect element. Try again or restart React DevTools.',
),
);
};
const onDisconnect = () => {
cleanup();
reject(new ElementPollingCancellationError());
@@ -176,8 +186,11 @@ function getPromiseForRequestID<T>(
};
bridge.addListener(eventType, onInspectedElement);
bridge.addListener('shutdown', onDisconnect);
bridge.addListener('pauseElementPolling', onDisconnect);
bridge.addListener('shutdown', onShutdown);
if (shouldListenToPauseEvents) {
bridge.addListener('pauseElementPolling', onDisconnect);
}
const timeoutID = setTimeout(onTimeout, TIMEOUT_DELAY);
});
@@ -277,7 +290,7 @@ export function convertInspectedElementBackendToFrontend(
export function hydrateHelper(
dehydratedData: DehydratedData | null,
path?: Array<string | number>,
path: ?InspectedElementPath,
): Object | null {
if (dehydratedData !== null) {
const {cleaned, data, unserializable} = dehydratedData;
+1 -1
View File
@@ -9,7 +9,7 @@
import EventEmitter from './events';
import type {ComponentFilter, Wall} from './types';
import type {ComponentFilter, Wall} from './frontend/types';
import type {
InspectedElementPayload,
OwnersList,
+7 -4
View File
@@ -21,7 +21,7 @@ import {
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
} from '../constants';
import {ElementTypeRoot} from '../types';
import {ElementTypeRoot} from '../frontend/types';
import {
getSavedComponentFilters,
setSavedComponentFilters,
@@ -37,10 +37,13 @@ import {
BRIDGE_PROTOCOL,
currentBridgeProtocol,
} from 'react-devtools-shared/src/bridge';
import {StrictMode} from 'react-devtools-shared/src/types';
import {StrictMode} from 'react-devtools-shared/src/frontend/types';
import type {Element} from './views/Components/types';
import type {ComponentFilter, ElementType} from '../types';
import type {
Element,
ComponentFilter,
ElementType,
} from 'react-devtools-shared/src/frontend/types';
import type {
FrontendBridge,
BridgeProtocol,
+1 -1
View File
@@ -9,7 +9,7 @@
import JSON5 from 'json5';
import type {Element} from './views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {StateContext} from './views/Components/TreeContext';
import type Store from './store';
@@ -11,7 +11,7 @@ import * as React from 'react';
import {Fragment} from 'react';
import styles from './Badge.css';
import type {ElementType} from 'react-devtools-shared/src/types';
import type {ElementType} from 'react-devtools-shared/src/frontend/types';
type Props = {
className?: string,
@@ -13,7 +13,7 @@ import {StoreContext} from '../context';
import {
ComponentFilterElementType,
ElementTypeSuspense,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
export default function CannotSuspendWarningMessage(): React.Node {
const store = useContext(StoreContext);
@@ -20,7 +20,7 @@ import {useSubscription} from '../hooks';
import {logEvent} from 'react-devtools-shared/src/Logger';
import type {ItemData} from './Tree';
import type {Element as ElementType} from './types';
import type {Element as ElementType} from 'react-devtools-shared/src/frontend/types';
import styles from './Element.css';
import Icon from '../Icon';
@@ -10,7 +10,7 @@
import * as React from 'react';
import styles from './HocBadges.css';
import type {Element} from './types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
type Props = {
element: Element,
@@ -17,7 +17,7 @@ import Icon from '../Icon';
import {ModalDialogContext} from '../ModalDialog';
import ViewElementSourceContext from './ViewElementSourceContext';
import Toggle from '../Toggle';
import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
import CannotSuspendWarningMessage from './CannotSuspendWarningMessage';
import InspectedElementView from './InspectedElementView';
import {InspectedElementContext} from './InspectedElementContext';
@@ -26,7 +26,7 @@ import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants';
import styles from './InspectedElement.css';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
export type Props = {};
@@ -37,12 +37,12 @@ import FetchFileWithCachingContext from 'react-devtools-shared/src/devtools/view
import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
import {SettingsContext} from '../Settings/SettingsContext';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
Element,
InspectedElement,
} from 'react-devtools-shared/src/devtools/views/Components/types';
} from 'react-devtools-shared/src/frontend/types';
type Path = Array<string | number>;
type InspectPathFunction = (path: Path) => void;
@@ -18,11 +18,11 @@ import styles from './InspectedElementSharedStyles.css';
import {
ElementTypeClass,
ElementTypeFunction,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
type Props = {
bridge: FrontendBridge,
@@ -24,7 +24,7 @@ import {
clearWarningsForElement as clearWarningsForElementAPI,
} from 'react-devtools-shared/src/backendAPI';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
type Props = {
@@ -25,11 +25,11 @@ import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache
import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
import isArray from 'react-devtools-shared/src/isArray';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {ToggleParseHookNames} from './InspectedElementContext';
type HooksTreeViewProps = {
@@ -17,11 +17,11 @@ import NewKeyValue from './NewKeyValue';
import {alphaSortEntries, serializeDataForCopy} from '../utils';
import Store from '../../store';
import styles from './InspectedElementSharedStyles.css';
import {ElementTypeClass} from 'react-devtools-shared/src/types';
import {ElementTypeClass} from 'react-devtools-shared/src/frontend/types';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
type Props = {
bridge: FrontendBridge,
@@ -9,7 +9,7 @@
import {copy} from 'clipboard-js';
import * as React from 'react';
import {ElementTypeHostComponent} from 'react-devtools-shared/src/types';
import {ElementTypeHostComponent} from 'react-devtools-shared/src/frontend/types';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import KeyValue from './KeyValue';
@@ -17,9 +17,9 @@ import {alphaSortEntries, serializeDataForCopy} from '../utils';
import Store from '../../store';
import styles from './InspectedElementSharedStyles.css';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
type Props = {
bridge: FrontendBridge,
@@ -14,9 +14,9 @@ import sharedStyles from './InspectedElementSharedStyles.css';
import styles from './InspectedElementStyleXPlugin.css';
import {enableStyleXFeatures} from 'react-devtools-feature-flags';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
type Props = {
bridge: FrontendBridge,
@@ -11,10 +11,10 @@ import * as React from 'react';
import {OptionsContext} from '../context';
import EditableValue from './EditableValue';
import Store from '../../store';
import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
import styles from './InspectedElementSharedStyles.css';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
type Props = {
@@ -38,8 +38,15 @@ import {logEvent} from 'react-devtools-shared/src/Logger';
import styles from './InspectedElementView.css';
import type {ContextMenuContextType} from '../context';
import type {Element, InspectedElement, SerializedElement} from './types';
import type {ElementType, HookNames} from 'react-devtools-shared/src/types';
import type {
Element,
InspectedElement,
SerializedElement,
} from 'react-devtools-shared/src/frontend/types';
import type {
ElementType,
HookNames,
} from 'react-devtools-shared/src/frontend/types';
import type {ToggleParseHookNames} from './InspectedElementContext';
export type CopyPath = (path: Array<string | number>) => void;
@@ -28,8 +28,8 @@ import isArray from 'react-devtools-shared/src/isArray';
import {InspectedElementContext} from './InspectedElementContext';
import {PROTOCOLS_SUPPORTED_AS_LINKS_IN_KEY_VALUE} from './constants';
import type {InspectedElement} from './types';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {Element as ReactElement} from 'react';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
@@ -31,7 +31,7 @@ import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type Store from 'react-devtools-shared/src/devtools/store';
import type {StyleAndLayout as StyleAndLayoutBackend} from 'react-devtools-shared/src/backend/NativeStyleEditor/types';
import type {StyleAndLayout as StyleAndLayoutFrontend} from './types';
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {
Resource,
Thenable,
@@ -15,7 +15,7 @@ import {smartParse} from '../../utils';
import {parseHookPathForEdit} from './utils';
import styles from './NewArrayValue.css';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
type Props = {
@@ -15,7 +15,7 @@ import EditableValue from './EditableValue';
import {parseHookPathForEdit} from './utils';
import styles from './NewKeyValue.css';
import type {InspectedElement} from './types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
type Props = {
@@ -20,7 +20,7 @@ import type {OwnersList} from 'react-devtools-shared/src/backend/types';
import type {
Element,
SerializedElement,
} from 'react-devtools-shared/src/devtools/views/Components/types';
} from 'react-devtools-shared/src/frontend/types';
import type {Resource, Thenable} from '../../cache';
type Context = (id: number) => Array<SerializedElement> | null;
@@ -32,7 +32,7 @@ import {
MenuItem,
} from '../Components/reach-ui/menu-button';
import type {SerializedElement} from './types';
import type {SerializedElement} from 'react-devtools-shared/src/frontend/types';
import styles from './OwnersStack.css';
@@ -7,7 +7,8 @@
* @flow
*/
import type {Element} from './types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import * as React from 'react';
import {useContext, useMemo} from 'react';
import {TreeStateContext} from './TreeContext';
@@ -42,7 +42,7 @@ import {createRegExp} from '../utils';
import {BridgeContext, StoreContext} from '../context';
import Store from '../../store';
import type {Element} from './types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
export type StateContext = {
// Tree
@@ -1,140 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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 type {Source} from 'shared/ReactElementType';
import type {
Dehydrated,
Unserializable,
} from 'react-devtools-shared/src/hydration';
import type {ElementType, Plugins} from 'react-devtools-shared/src/types';
// Each element on the frontend corresponds to a Fiber on the backend.
// Some of its information (e.g. id, type, displayName) come from the backend.
// Other bits (e.g. weight and depth) are computed on the frontend for windowing and display purposes.
// Elements are updated on a push basis meaning the backend pushes updates to the frontend when needed.
export type Element = {
id: number,
parentID: number,
children: Array<number>,
type: ElementType,
displayName: string | null,
key: number | string | null,
hocDisplayNames: null | Array<string>,
// Should the elements children be visible in the tree?
isCollapsed: boolean,
// Owner (if available)
ownerID: number,
// How many levels deep within the tree is this element?
// This determines how much indentation (left padding) should be used in the Elements tree.
depth: number,
// How many nodes (including itself) are below this Element within the tree.
// This property is used to quickly determine the total number of Elements,
// and the Element at any given index (for windowing purposes).
weight: number,
// This element is not in a StrictMode compliant subtree.
// Only true for React versions supporting StrictMode.
isStrictModeNonCompliant: boolean,
};
export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
hocDisplayNames: Array<string> | null,
type: ElementType,
};
export type OwnersList = {
id: number,
owners: Array<SerializedElement> | null,
};
export type InspectedElementResponseType =
| 'error'
| 'full-data'
| 'hydrated-path'
| 'no-change'
| 'not-found';
export type InspectedElement = {
id: number,
// Does the current renderer support editable hooks and function props?
canEditHooks: boolean,
canEditFunctionProps: boolean,
// Does the current renderer support advanced editing interface?
canEditHooksAndDeletePaths: boolean,
canEditHooksAndRenamePaths: boolean,
canEditFunctionPropsDeletePaths: boolean,
canEditFunctionPropsRenamePaths: boolean,
// Is this Error, and can its value be overridden now?
isErrored: boolean,
canToggleError: boolean,
targetErrorBoundaryID: ?number,
// Is this Suspense, and can its value be overridden now?
canToggleSuspense: boolean,
// Can view component source location.
canViewSource: boolean,
// Does the component have legacy context attached to it.
hasLegacyContext: boolean,
// Inspectable properties.
context: Object | null,
hooks: Object | null,
props: Object | null,
state: Object | null,
key: number | string | null,
errors: Array<[string, number]>,
warnings: Array<[string, number]>,
// List of owners
owners: Array<SerializedElement> | null,
// Location of component in source code.
source: Source | null,
type: ElementType,
// Meta information about the root this element belongs to.
rootType: string | null,
// Meta information about the renderer that created this element.
rendererPackageName: string | null,
rendererVersion: string | null,
// UI plugins/visualizations for the inspected element.
plugins: Plugins,
};
// TODO: Add profiling type
type Data =
| string
| Dehydrated
| Unserializable
| Array<Dehydrated>
| Array<Unserializable>
| {[string]: Data};
export type DehydratedData = {
cleaned: Array<Array<string | number>>,
data: Data,
unserializable: Array<Array<string | number>>,
};
@@ -46,11 +46,11 @@ import styles from './DevTools.css';
import './root.css';
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {FetchFileWithCaching} from './Components/FetchFileWithCachingContext';
import type {HookNamesModuleLoaderFunction} from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {BrowserTheme} from 'react-devtools-shared/src/types';
import type {BrowserTheme} from 'react-devtools-shared/src/frontend/types';
export type TabID = 'components' | 'profiler';
@@ -18,10 +18,10 @@ import {
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
} from 'react-devtools-shared/src/constants';
import {utfDecodeString} from 'react-devtools-shared/src/utils';
import {ElementTypeRoot} from 'react-devtools-shared/src/types';
import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types';
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
import type {ElementType} from 'react-devtools-shared/src/types';
import type {ElementType} from 'react-devtools-shared/src/frontend/types';
import type {
CommitTree,
CommitTreeNode,
@@ -10,7 +10,7 @@
import {
ElementTypeForwardRef,
ElementTypeMemo,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {formatDuration} from './utils';
import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
@@ -8,13 +8,13 @@
*/
import type {CommitTree} from './types';
import type {SerializedElement} from '../Components/types';
import type {SerializedElement} from 'react-devtools-shared/src/frontend/types';
import * as React from 'react';
import {useContext} from 'react';
import {ProfilerContext} from './ProfilerContext';
import styles from './Updaters.css';
import {ElementTypeRoot} from '../../../types';
import {ElementTypeRoot} from '../../../frontend/types';
export type Props = {
commitTree: CommitTree,
@@ -7,8 +7,10 @@
* @flow
*/
import type {ElementType} from 'react-devtools-shared/src/types';
import type {SerializedElement} from '../Components/types';
import type {
ElementType,
SerializedElement,
} from 'react-devtools-shared/src/frontend/types';
import type {
TimelineData,
TimelineDataExport,
@@ -40,7 +40,7 @@ import {
ElementTypeOtherOrUnknown,
ElementTypeProfiler,
ElementTypeSuspense,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {getDefaultOpenInEditorURL} from 'react-devtools-shared/src/utils';
import styles from './SettingsShared.css';
@@ -52,7 +52,7 @@ import type {
ElementType,
ElementTypeComponentFilter,
RegExpComponentFilter,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
const vscodeFilepath = 'vscode://file/{path}:{line}';
@@ -34,7 +34,7 @@ import {useLocalStorage} from '../hooks';
import {BridgeContext} from '../context';
import {logEvent} from 'react-devtools-shared/src/Logger';
import type {BrowserTheme} from 'react-devtools-shared/src/types';
import type {BrowserTheme} from 'react-devtools-shared/src/frontend/types';
export type DisplayDensity = 'comfortable' | 'compact';
export type Theme = 'auto' | 'light' | 'dark';
@@ -14,6 +14,12 @@
* Be mindful of backwards compatibility when making changes.
*/
import type {Source} from 'shared/ReactElementType';
import type {
Dehydrated,
Unserializable,
} from 'react-devtools-shared/src/hydration';
export type BrowserTheme = 'dark' | 'light';
export type Wall = {
@@ -112,3 +118,130 @@ export type Plugins = {
};
export const StrictMode = 1;
// Each element on the frontend corresponds to a Fiber on the backend.
// Some of its information (e.g. id, type, displayName) come from the backend.
// Other bits (e.g. weight and depth) are computed on the frontend for windowing and display purposes.
// Elements are updated on a push basis meaning the backend pushes updates to the frontend when needed.
export type Element = {
id: number,
parentID: number,
children: Array<number>,
type: ElementType,
displayName: string | null,
key: number | string | null,
hocDisplayNames: null | Array<string>,
// Should the elements children be visible in the tree?
isCollapsed: boolean,
// Owner (if available)
ownerID: number,
// How many levels deep within the tree is this element?
// This determines how much indentation (left padding) should be used in the Elements tree.
depth: number,
// How many nodes (including itself) are below this Element within the tree.
// This property is used to quickly determine the total number of Elements,
// and the Element at any given index (for windowing purposes).
weight: number,
// This element is not in a StrictMode compliant subtree.
// Only true for React versions supporting StrictMode.
isStrictModeNonCompliant: boolean,
};
export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
hocDisplayNames: Array<string> | null,
type: ElementType,
};
export type OwnersList = {
id: number,
owners: Array<SerializedElement> | null,
};
export type InspectedElementResponseType =
| 'error'
| 'full-data'
| 'hydrated-path'
| 'no-change'
| 'not-found';
export type InspectedElementPath = Array<string | number>;
export type InspectedElement = {
id: number,
// Does the current renderer support editable hooks and function props?
canEditHooks: boolean,
canEditFunctionProps: boolean,
// Does the current renderer support advanced editing interface?
canEditHooksAndDeletePaths: boolean,
canEditHooksAndRenamePaths: boolean,
canEditFunctionPropsDeletePaths: boolean,
canEditFunctionPropsRenamePaths: boolean,
// Is this Error, and can its value be overridden now?
isErrored: boolean,
canToggleError: boolean,
targetErrorBoundaryID: ?number,
// Is this Suspense, and can its value be overridden now?
canToggleSuspense: boolean,
// Can view component source location.
canViewSource: boolean,
// Does the component have legacy context attached to it.
hasLegacyContext: boolean,
// Inspectable properties.
context: Object | null,
hooks: Object | null,
props: Object | null,
state: Object | null,
key: number | string | null,
errors: Array<[string, number]>,
warnings: Array<[string, number]>,
// List of owners
owners: Array<SerializedElement> | null,
// Location of component in source code.
source: Source | null,
type: ElementType,
// Meta information about the root this element belongs to.
rootType: string | null,
// Meta information about the renderer that created this element.
rendererPackageName: string | null,
rendererVersion: string | null,
// UI plugins/visualizations for the inspected element.
plugins: Plugins,
};
// TODO: Add profiling type
type Data =
| string
| Dehydrated
| Unserializable
| Array<Dehydrated>
| Array<Unserializable>
| {[string]: Data};
export type DehydratedData = {
cleaned: Array<Array<string | number>>,
data: Data,
unserializable: Array<Array<string | number>>,
};
+1 -1
View File
@@ -8,7 +8,7 @@
* @flow
*/
import type {BrowserTheme} from './types';
import type {BrowserTheme} from './frontend/types';
import type {
DevToolsHook,
Handler,
+2 -2
View File
@@ -11,11 +11,11 @@ import {__DEBUG__} from 'react-devtools-shared/src/constants';
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Element} from './devtools/views/Components/types';
import type {
Element,
HookNames,
HookSourceLocationKey,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
import {withCallbackPerfMeasurements} from './PerformanceLoggingUtils';
@@ -9,7 +9,7 @@
import type {HookSourceAndMetadata} from './loadSourceAndMetadata';
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
import type {HookNames} from 'react-devtools-shared/src/types';
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
import {withAsyncPerfMeasurements} from 'react-devtools-shared/src/PerformanceLoggingUtils';
@@ -29,7 +29,10 @@ import type {
LocationKeyToHookSourceAndMetadata,
} from './loadSourceAndMetadata';
import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
import type {
HookNames,
LRUCache,
} from 'react-devtools-shared/src/frontend/types';
type AST = mixed;
+7 -4
View File
@@ -14,9 +14,12 @@ import {
getInObject,
formatDataForPreview,
setInObject,
} from './utils';
} from 'react-devtools-shared/src/utils';
import type {DehydratedData} from './devtools/views/Components/types';
import type {
DehydratedData,
InspectedElementPath,
} from 'react-devtools-shared/src/frontend/types';
export const meta = {
inspectable: (Symbol('inspectable'): symbol),
@@ -123,7 +126,7 @@ export function dehydrate(
unserializable: Array<Array<string | number>>,
path: Array<string | number>,
isPathAllowed: (path: Array<string | number>) => boolean,
level?: number = 0,
level: number = 0,
): $PropertyType<DehydratedData, 'data'> {
const type = getDataType(data);
@@ -365,7 +368,7 @@ export function dehydrate(
export function fillInPath(
object: Object,
data: DehydratedData,
path: Array<string | number>,
path: InspectedElementPath,
value: any,
) {
const target = getInObject(object, path);
+9 -11
View File
@@ -21,7 +21,8 @@ import type {
Element,
InspectedElement as InspectedElementFrontend,
InspectedElementResponseType,
} from 'react-devtools-shared/src/devtools/views/Components/types';
InspectedElementPath,
} from 'react-devtools-shared/src/frontend/types';
const Pending = 0;
const Resolved = 1;
@@ -83,7 +84,7 @@ function createCacheSeed(
*/
export function inspectElement(
element: Element,
path: Array<string | number> | null,
path: InspectedElementPath | null,
store: Store,
bridge: FrontendBridge,
): InspectedElementFrontend | null {
@@ -123,12 +124,7 @@ export function inspectElement(
return null;
}
inspectElementMutableSource({
bridge,
element,
path,
rendererID: ((rendererID: any): number),
}).then(
inspectElementMutableSource(bridge, element, path, rendererID).then(
([inspectedElement]: [
InspectedElementFrontend,
InspectedElementResponseType,
@@ -151,6 +147,7 @@ export function inspectElement(
wake();
},
);
map.set(element, record);
}
@@ -186,12 +183,13 @@ export function checkForUpdate({
return;
}
return inspectElementMutableSource({
return inspectElementMutableSource(
bridge,
element,
path: null,
null,
rendererID,
}).then(
true,
).then(
([inspectedElement, responseType]: [
InspectedElementFrontend,
InspectedElementResponseType,
@@ -15,20 +15,21 @@ import {
} from 'react-devtools-shared/src/backendAPI';
import {fillInPath} from 'react-devtools-shared/src/hydration';
import type {LRUCache} from 'react-devtools-shared/src/types';
import type {LRUCache} from 'react-devtools-shared/src/frontend/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {
InspectElementError,
InspectElementFullData,
InspectElementHydratedPath,
} from 'react-devtools-shared/src/backend/types';
import UserError from 'react-devtools-shared/src/errors/UserError';
import UnknownHookError from 'react-devtools-shared/src/errors/UnknownHookError';
import type {
Element,
InspectedElement as InspectedElementFrontend,
InspectedElementResponseType,
} from 'react-devtools-shared/src/devtools/views/Components/types';
import UserError from 'react-devtools-shared/src/errors/UserError';
import UnknownHookError from 'react-devtools-shared/src/errors/UnknownHookError';
InspectedElementPath,
} from 'react-devtools-shared/src/frontend/types';
// Maps element ID to inspected data.
// We use an LRU for this rather than a WeakMap because of how the "no-change" optimization works.
@@ -44,24 +45,18 @@ const inspectedElementCache: LRUCache<number, InspectedElementFrontend> =
max: 25,
});
type Path = Array<string | number>;
type InspectElementReturnType = [
InspectedElementFrontend,
InspectedElementResponseType,
];
export function inspectElement({
bridge,
element,
path,
rendererID,
}: {
export function inspectElement(
bridge: FrontendBridge,
element: Element,
path: Path | null,
path: InspectedElementPath | null,
rendererID: number,
}): Promise<InspectElementReturnType> {
shouldListenToPauseEvents: boolean = false,
): Promise<InspectElementReturnType> {
const {id} = element;
// This could indicate that the DevTools UI has been closed and reopened.
@@ -69,13 +64,14 @@ export function inspectElement({
// In this case, we need to tell it to resend the full data.
const forceFullData = !inspectedElementCache.has(id);
return inspectElementAPI({
return inspectElementAPI(
bridge,
forceFullData,
id,
path,
rendererID,
}).then((data: any) => {
shouldListenToPauseEvents,
).then((data: any) => {
const {type} = data;
let inspectedElement;
@@ -141,12 +137,14 @@ export function inspectElement({
inspectedElement = {...inspectedElement};
// Merge hydrated data
fillInPath(
inspectedElement,
value,
((path: any): Path),
hydrateHelper(value, ((path: any): Path)),
);
if (path != null) {
fillInPath(
inspectedElement,
value,
path,
hydrateHelper(value, path),
);
}
inspectedElementCache.set(id, inspectedElement);
+11 -4
View File
@@ -41,20 +41,27 @@ import {
LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
LOCAL_STORAGE_HIDE_CONSOLE_LOGS_IN_STRICT_MODE,
} from './constants';
import {ComponentFilterElementType, ElementTypeHostComponent} from './types';
import {
ComponentFilterElementType,
ElementTypeHostComponent,
} from './frontend/types';
import {
ElementTypeRoot,
ElementTypeClass,
ElementTypeForwardRef,
ElementTypeFunction,
ElementTypeMemo,
} from 'react-devtools-shared/src/types';
} from 'react-devtools-shared/src/frontend/types';
import {localStorageGetItem, localStorageSetItem} from './storage';
import {meta} from './hydration';
import isArray from './isArray';
import type {ComponentFilter, ElementType, BrowserTheme} from './types';
import type {LRUCache} from 'react-devtools-shared/src/types';
import type {
ComponentFilter,
ElementType,
BrowserTheme,
} from './frontend/types';
import type {LRUCache} from 'react-devtools-shared/src/frontend/types';
// $FlowFixMe[method-unbinding]
const hasOwnProperty = Object.prototype.hasOwnProperty;