From 9011a5ba84f0c677644404defcc403caa30c2d03 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Wed, 21 Jan 2026 13:26:38 +0100 Subject: [PATCH] Type `react-devtools-hook-installer` and `react-devtools-hook-settings-injector` messages --- .eslintrc.js | 1 + flow-typed/environments/bom.js | 16 +++--- flow-typed/environments/dom.js | 6 +- flow-typed/environments/html.js | 4 +- packages/react-devtools-core/src/backend.js | 2 +- .../contentScripts/hookSettingsInjector.js | 56 +++++++++++-------- .../src/contentScripts/installHook.js | 23 +++++--- .../src/contentScripts/messages.js | 42 ++++++++++++++ 8 files changed, 106 insertions(+), 44 deletions(-) create mode 100644 packages/react-devtools-extensions/src/contentScripts/messages.js diff --git a/.eslintrc.js b/.eslintrc.js index dec7cd8f304..952149f30ca 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -593,6 +593,7 @@ module.exports = { mixin$Animatable: 'readonly', MouseEventHandler: 'readonly', NavigateEvent: 'readonly', + Partial: 'readonly', PerformanceMeasureOptions: 'readonly', PropagationPhases: 'readonly', PropertyDescriptor: 'readonly', diff --git a/flow-typed/environments/bom.js b/flow-typed/environments/bom.js index 06412856009..7c082ea5dae 100644 --- a/flow-typed/environments/bom.js +++ b/flow-typed/environments/bom.js @@ -826,7 +826,7 @@ declare class WebSocket extends EventTarget { bufferedAmount: number; extensions: string; onopen: (ev: any) => mixed; - onmessage: (ev: MessageEvent) => mixed; + onmessage: (ev: MessageEvent<>) => mixed; onclose: (ev: CloseEvent) => mixed; onerror: (ev: any) => mixed; binaryType: 'blob' | 'arraybuffer'; @@ -855,8 +855,8 @@ declare class Worker extends EventTarget { workerOptions?: WorkerOptions ): void; onerror: null | ((ev: any) => mixed); - onmessage: null | ((ev: MessageEvent) => mixed); - onmessageerror: null | ((ev: MessageEvent) => mixed); + onmessage: null | ((ev: MessageEvent<>) => mixed); + onmessageerror: null | ((ev: MessageEvent<>) => mixed); postMessage(message: any, ports?: any): void; terminate(): void; } @@ -888,14 +888,14 @@ declare class WorkerGlobalScope extends EventTarget { } declare class DedicatedWorkerGlobalScope extends WorkerGlobalScope { - onmessage: (ev: MessageEvent) => mixed; - onmessageerror: (ev: MessageEvent) => mixed; + onmessage: (ev: MessageEvent<>) => mixed; + onmessageerror: (ev: MessageEvent<>) => mixed; postMessage(message: any, transfer?: Iterable): void; } declare class SharedWorkerGlobalScope extends WorkerGlobalScope { name: string; - onconnect: (ev: MessageEvent) => mixed; + onconnect: (ev: MessageEvent<>) => mixed; } declare class WorkerLocation { @@ -2056,8 +2056,8 @@ declare class MessagePort extends EventTarget { start(): void; close(): void; - onmessage: null | ((ev: MessageEvent) => mixed); - onmessageerror: null | ((ev: MessageEvent) => mixed); + onmessage: null | ((ev: MessageEvent<>) => mixed); + onmessageerror: null | ((ev: MessageEvent<>) => mixed); } declare class MessageChannel { diff --git a/flow-typed/environments/dom.js b/flow-typed/environments/dom.js index 8e3e7570731..331e73f8914 100644 --- a/flow-typed/environments/dom.js +++ b/flow-typed/environments/dom.js @@ -151,7 +151,7 @@ type TransitionEventHandler = (event: TransitionEvent) => mixed; type TransitionEventListener = | {handleEvent: TransitionEventHandler, ...} | TransitionEventHandler; -type MessageEventHandler = (event: MessageEvent) => mixed; +type MessageEventHandler = (event: MessageEvent<>) => mixed; type MessageEventListener = | {handleEvent: MessageEventHandler, ...} | MessageEventHandler; @@ -845,8 +845,8 @@ declare class PageTransitionEvent extends Event { // https://www.w3.org/TR/2008/WD-html5-20080610/comms.html // and // https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces -declare class MessageEvent extends Event { - data: mixed; +declare class MessageEvent extends Event { + data: Data; origin: string; lastEventId: string; source: WindowProxy; diff --git a/flow-typed/environments/html.js b/flow-typed/environments/html.js index 54e1e48f739..ffbf9ac9405 100644 --- a/flow-typed/environments/html.js +++ b/flow-typed/environments/html.js @@ -109,8 +109,8 @@ declare class ErrorEvent extends Event { // https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts declare class BroadcastChannel extends EventTarget { name: string; - onmessage: ?(event: MessageEvent) => void; - onmessageerror: ?(event: MessageEvent) => void; + onmessage: ?(event: MessageEvent<>) => void; + onmessageerror: ?(event: MessageEvent<>) => void; constructor(name: string): void; postMessage(msg: mixed): void; diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 9305155ab0e..4cca0887c57 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -293,7 +293,7 @@ export function connectToDevTools(options: ?ConnectOptions) { scheduleRetry(); } - function handleMessage(event: MessageEvent) { + function handleMessage(event: MessageEvent<>) { let data; try { if (typeof event.data === 'string') { diff --git a/packages/react-devtools-extensions/src/contentScripts/hookSettingsInjector.js b/packages/react-devtools-extensions/src/contentScripts/hookSettingsInjector.js index da809f65cac..196369e71d9 100644 --- a/packages/react-devtools-extensions/src/contentScripts/hookSettingsInjector.js +++ b/packages/react-devtools-extensions/src/contentScripts/hookSettingsInjector.js @@ -1,38 +1,50 @@ /* global chrome */ +/** @flow */ // We can't use chrome.storage domain from scripts which are injected in ExecutionWorld.MAIN // This is the only purpose of this script - to send persisted settings to installHook.js content script -async function messageListener(event: MessageEvent) { +import type {UnknownMessageEvent} from './messages'; +import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types'; +import {postMessage} from './messages'; + +async function messageListener(event: UnknownMessageEvent) { if (event.source !== window) { return; } if (event.data.source === 'react-devtools-hook-installer') { if (event.data.payload.handshake) { - const settings = await chrome.storage.local.get(); + const settings: Partial = + await chrome.storage.local.get(); // If storage was empty (first installation), define default settings - if (typeof settings.appendComponentStack !== 'boolean') { - settings.appendComponentStack = true; - } - if (typeof settings.breakOnConsoleErrors !== 'boolean') { - settings.breakOnConsoleErrors = false; - } - if (typeof settings.showInlineWarningsAndErrors !== 'boolean') { - settings.showInlineWarningsAndErrors = true; - } - if (typeof settings.hideConsoleLogsInStrictMode !== 'boolean') { - settings.hideConsoleLogsInStrictMode = false; - } - if ( - typeof settings.disableSecondConsoleLogDimmingInStrictMode !== 'boolean' - ) { - settings.disableSecondConsoleLogDimmingInStrictMode = false; - } + const hookSettings: DevToolsHookSettings = { + appendComponentStack: + typeof settings.appendComponentStack === 'boolean' + ? settings.appendComponentStack + : true, + breakOnConsoleErrors: + typeof settings.breakOnConsoleErrors === 'boolean' + ? settings.breakOnConsoleErrors + : false, + showInlineWarningsAndErrors: + typeof settings.showInlineWarningsAndErrors === 'boolean' + ? settings.showInlineWarningsAndErrors + : true, + hideConsoleLogsInStrictMode: + typeof settings.hideConsoleLogsInStrictMode === 'boolean' + ? settings.hideConsoleLogsInStrictMode + : false, + disableSecondConsoleLogDimmingInStrictMode: + typeof settings.disableSecondConsoleLogDimmingInStrictMode === + 'boolean' + ? settings.disableSecondConsoleLogDimmingInStrictMode + : false, + }; - window.postMessage({ + postMessage({ source: 'react-devtools-hook-settings-injector', - payload: {settings}, + payload: {settings: hookSettings}, }); window.removeEventListener('message', messageListener); @@ -41,7 +53,7 @@ async function messageListener(event: MessageEvent) { } window.addEventListener('message', messageListener); -window.postMessage({ +postMessage({ source: 'react-devtools-hook-settings-injector', payload: {handshake: true}, }); diff --git a/packages/react-devtools-extensions/src/contentScripts/installHook.js b/packages/react-devtools-extensions/src/contentScripts/installHook.js index e70e97b2857..8f30d6f0892 100644 --- a/packages/react-devtools-extensions/src/contentScripts/installHook.js +++ b/packages/react-devtools-extensions/src/contentScripts/installHook.js @@ -1,39 +1,46 @@ +/** @flow */ + +import type {UnknownMessageEvent} from './messages'; +import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types'; + import {installHook} from 'react-devtools-shared/src/hook'; import { getIfReloadedAndProfiling, getProfilingSettings, } from 'react-devtools-shared/src/utils'; +import {postMessage} from './messages'; -let resolveHookSettingsInjection; +let resolveHookSettingsInjection: (settings: DevToolsHookSettings) => void; -function messageListener(event: MessageEvent) { +function messageListener(event: UnknownMessageEvent) { if (event.source !== window) { return; } if (event.data.source === 'react-devtools-hook-settings-injector') { + const payload = event.data.payload; // In case handshake message was sent prior to hookSettingsInjector execution // We can't guarantee order - if (event.data.payload.handshake) { - window.postMessage({ + if (payload.handshake) { + postMessage({ source: 'react-devtools-hook-installer', payload: {handshake: true}, }); - } else if (event.data.payload.settings) { + } else if (payload.settings) { window.removeEventListener('message', messageListener); - resolveHookSettingsInjection(event.data.payload.settings); + resolveHookSettingsInjection(payload.settings); } } } // Avoid double execution if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) { - const hookSettingsPromise = new Promise(resolve => { + const hookSettingsPromise = new Promise(resolve => { resolveHookSettingsInjection = resolve; }); window.addEventListener('message', messageListener); - window.postMessage({ + postMessage({ source: 'react-devtools-hook-installer', payload: {handshake: true}, }); diff --git a/packages/react-devtools-extensions/src/contentScripts/messages.js b/packages/react-devtools-extensions/src/contentScripts/messages.js new file mode 100644 index 00000000000..e65d46b4b26 --- /dev/null +++ b/packages/react-devtools-extensions/src/contentScripts/messages.js @@ -0,0 +1,42 @@ +/** @flow */ + +import type {DevToolsHookSettings} from 'react-devtools-shared/src/backend/types'; + +export function postMessage(event: UnknownMessageEventData): void { + window.postMessage(event); +} + +export interface UnknownMessageEvent + extends MessageEvent {} + +export type UnknownMessageEventData = + | HookSettingsInjectorEventData + | HookInstallerEventData; + +export type HookInstallerEventData = { + source: 'react-devtools-hook-installer', + payload: HookInstallerEventPayload, +}; + +export type HookInstallerEventPayload = HookInstallerEventPayloadHandshake; + +export type HookInstallerEventPayloadHandshake = { + handshake: true, +}; + +export type HookSettingsInjectorEventData = { + source: 'react-devtools-hook-settings-injector', + payload: HookSettingsInjectorEventPayload, +}; + +export type HookSettingsInjectorEventPayload = + | HookSettingsInjectorEventPayloadHandshake + | HookSettingsInjectorEventPayloadSettings; + +export type HookSettingsInjectorEventPayloadHandshake = { + handshake: true, +}; + +export type HookSettingsInjectorEventPayloadSettings = { + settings: DevToolsHookSettings, +};