Typecheck React DevTools extension main script (#35519)

This commit is contained in:
Sebastian "Sebbie" Silbermann
2026-01-16 13:08:28 +01:00
committed by GitHub
parent db71391c5c
commit cbc4d40663
3 changed files with 63 additions and 35 deletions

View File

@@ -1,6 +1,14 @@
/* global chrome */
/** @flow */
import type {RootType} from 'react-dom/src/client/ReactDOMRoot';
import type {FrontendBridge, Message} from 'react-devtools-shared/src/bridge';
import type {
TabID,
ViewElementSource,
} from 'react-devtools-shared/src/devtools/views/DevTools';
import type {SourceSelection} from 'react-devtools-shared/src/devtools/views/Editor/EditorPane';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import {createElement} from 'react';
import {flushSync} from 'react-dom';
@@ -51,9 +59,9 @@ const hookNamesModuleLoaderFunction = () => resolvedParseHookNames;
function createBridge() {
bridge = new Bridge({
listen(fn) {
const bridgeListener = message => fn(message);
const bridgeListener = (message: Message) => fn(message);
// Store the reference so that we unsubscribe from the same object.
const portOnMessage = port.onMessage;
const portOnMessage = ((port: any): ExtensionPort).onMessage;
portOnMessage.addListener(bridgeListener);
lastSubscribedBridgeListener = bridgeListener;
@@ -71,7 +79,7 @@ function createBridge() {
bridge.addListener('reloadAppForProfiling', () => {
localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
evalInInspectedWindow('reload', []);
evalInInspectedWindow('reload', [], () => {});
});
bridge.addListener(
@@ -176,14 +184,20 @@ function createBridgeAndStore() {
// Otherwise, the Store may miss important initial tree op codes.
injectBackendManager(chrome.devtools.inspectedWindow.tabId);
const viewAttributeSourceFunction = (id, path) => {
const viewAttributeSourceFunction = (
id: Element['id'],
path: Array<string | number>,
) => {
const rendererID = store.getRendererIDForElement(id);
if (rendererID != null) {
viewAttributeSource(rendererID, id, path);
}
};
const viewElementSourceFunction = (source, symbolicatedSource) => {
const viewElementSourceFunction: ViewElementSource = (
source,
symbolicatedSource,
) => {
const [, sourceURL, line, column] = symbolicatedSource
? symbolicatedSource
: source;
@@ -198,7 +212,7 @@ function createBridgeAndStore() {
root = createRoot(document.createElement('div'));
render = (overrideTab = mostRecentOverrideTab) => {
render = (overrideTab: TabID | null = mostRecentOverrideTab) => {
mostRecentOverrideTab = overrideTab;
root.render(
@@ -227,7 +241,9 @@ function createBridgeAndStore() {
};
}
function ensureInitialHTMLIsCleared(container) {
function ensureInitialHTMLIsCleared(
container: HTMLElement & {_hasInitialHTMLBeenCleared?: boolean},
) {
if (container._hasInitialHTMLBeenCleared) {
return;
}
@@ -397,13 +413,6 @@ function createSourcesEditorPanel() {
logEvent({event_name: 'selected-editor-pane'});
}
});
createdPane.onShown.addListener(() => {
bridge.emit('extensionEditorPaneShown');
});
createdPane.onHidden.addListener(() => {
bridge.emit('extensionEditorPaneHidden');
});
});
}
@@ -479,10 +488,10 @@ function performInTabNavigationCleanup() {
// Do not clean mostRecentOverrideTab on purpose, so we remember last opened
// React DevTools tab, when user does in-tab navigation
store = null;
bridge = null;
render = null;
root = null;
store = (null: $FlowFixMe);
bridge = (null: $FlowFixMe);
render = (null: $FlowFixMe);
root = (null: $FlowFixMe);
}
function performFullCleanup() {
@@ -504,18 +513,18 @@ function performFullCleanup() {
componentsPortalContainer = null;
profilerPortalContainer = null;
suspensePortalContainer = null;
root = null;
root = (null: $FlowFixMe);
mostRecentOverrideTab = null;
store = null;
bridge = null;
render = null;
store = (null: $FlowFixMe);
bridge = (null: $FlowFixMe);
render = (null: $FlowFixMe);
port?.disconnect();
port = null;
port = (null: $FlowFixMe);
}
function connectExtensionPort() {
function connectExtensionPort(): void {
if (port) {
throw new Error('DevTools port was already connected');
}
@@ -539,7 +548,7 @@ function connectExtensionPort() {
// so, when we call `port.disconnect()` from this script,
// this should not trigger this callback and port reconnection
port.onDisconnect.addListener(() => {
port = null;
port = (null: $FlowFixMe);
connectExtensionPort();
});
}
@@ -593,9 +602,9 @@ function mountReactDevToolsWhenReactHasLoaded() {
);
}
let bridge = null;
let bridge: FrontendBridge = (null: $FlowFixMe);
let lastSubscribedBridgeListener = null;
let store = null;
let store: Store = (null: $FlowFixMe);
let profilingData = null;
@@ -610,13 +619,28 @@ let suspensePortalContainer = null;
let editorPortalContainer = null;
let inspectedElementPortalContainer = null;
let mostRecentOverrideTab = null;
let render = null;
let root = null;
let mostRecentOverrideTab: null | TabID = null;
let render: (overrideTab?: TabID) => void = (null: $FlowFixMe);
let root: RootType = (null: $FlowFixMe);
let currentSelectedSource: null | SourceSelection = null;
let port = null;
type ExtensionEvent = {
addListener(callback: (message: Message, port: ExtensionPort) => void): void,
removeListener(
callback: (message: Message, port: ExtensionPort) => void,
): void,
};
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-Port */
type ExtensionPort = {
onDisconnect: ExtensionEvent,
onMessage: ExtensionEvent,
postMessage(message: mixed, transferable?: Array<mixed>): void,
disconnect(): void,
};
let port: ExtensionPort = (null: $FlowFixMe);
// In case when multiple navigation events emitted in a short period of time
// This debounced callback primarily used to avoid mounting React DevTools multiple times, which results
@@ -649,7 +673,7 @@ connectExtensionPort();
mountReactDevToolsWhenReactHasLoaded();
function onThemeChanged(themeName) {
function onThemeChanged() {
// Rerender with the new theme
render();
}

View File

@@ -63,7 +63,11 @@ export type LoggerEvent =
+value: any,
...
},
};
}
| {
+event_name: 'selected-editor-pane',
}
| {+event_name: 'selected-inspected-element-pane'};
export type LogFunction = LoggerEvent => void | Promise<void>;

View File

@@ -74,7 +74,7 @@ export const currentBridgeProtocol: BridgeProtocol =
type ElementAndRendererID = {id: number, rendererID: RendererID};
type Message = {
export type Message = {
event: string,
payload: any,
};
@@ -239,7 +239,7 @@ export type BackendEvents = {
type StartProfilingParams = ProfilingSettings;
type ReloadAndProfilingParams = ProfilingSettings;
type FrontendEvents = {
export type FrontendEvents = {
clearErrorsAndWarnings: [{rendererID: RendererID}],
clearErrorsForElementID: [ElementAndRendererID],
clearHostInstanceHighlight: [],