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.


af8beeebf6/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

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;