@samitouri / QOS-React / commits / 7b5b561bd2

[DevTools] Ignore new production renderers if we already use "worse" versions of React on a page (#35994)

Sebastian "Sebbie" Silbermann committed Mar 11, 2026 at 10:26 UTC 7b5b561bd2dda205ed8f397864379003b707d4ae
6 files changed +89 -15
packages/react-devtools-extensions/src/background/setExtensionIconAndPopup.js
+13 -1
@@ -1,8 +1,20 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 /* global chrome */
10
11 'use strict';
12 +import type {ReactBuildType} from 'react-devtools-shared/src/backend/types';
13
5 -function setExtensionIconAndPopup(reactBuildType, tabId) {
14 +function setExtensionIconAndPopup(
15 + reactBuildType: ReactBuildType,
16 + tabId: number,
17 +) {
18 chrome.action.setIcon({
19 tabId,
20 path: {
packages/react-devtools-extensions/src/contentScripts/installHook.js
+2 -12
@@ -10,6 +10,7 @@ import {
10 getProfilingSettings,
11 } from 'react-devtools-shared/src/utils';
12 import {postMessage} from './messages';
13 +import {createReactRendererListener} from './reactBuildType';
14
15 let resolveHookSettingsInjection: (settings: DevToolsHookSettings) => void;
16 let resolveComponentFiltersInjection: (filters: Array<ComponentFilter>) => void;
@@ -67,17 +68,6 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
68 // Detect React
69 window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on(
70 'renderer',
70 - function ({reactBuildType}) {
71 - window.postMessage(
72 - {
73 - source: 'react-devtools-hook',
74 - payload: {
75 - type: 'react-renderer-attached',
76 - reactBuildType,
77 - },
78 - },
79 - '*',
80 - );
81 - },
71 + createReactRendererListener(window),
72 );
73 }
packages/react-devtools-extensions/src/contentScripts/reactBuildType.js new
+50
@@ -0,0 +1,50 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +import type {ReactBuildType} from 'react-devtools-shared/src/backend/types';
10 +
11 +function reduceReactBuild(
12 + currentReactBuildType: null | ReactBuildType,
13 + nextReactBuildType: ReactBuildType,
14 +): ReactBuildType {
15 + if (
16 + currentReactBuildType === null ||
17 + currentReactBuildType === 'production'
18 + ) {
19 + return nextReactBuildType;
20 + }
21 +
22 + // We only display the "worst" build type, so if we've already detected a non-production build,
23 + // we ignore any future production builds. This way if a page has multiple renderers,
24 + // and at least one of them is a non-production build, we'll display that instead of "production".
25 + return nextReactBuildType === 'production'
26 + ? currentReactBuildType
27 + : nextReactBuildType;
28 +}
29 +
30 +export function createReactRendererListener(target: {
31 + postMessage: Function,
32 + ...
33 +}): ({reactBuildType: ReactBuildType}) => void {
34 + let displayedReactBuild: null | ReactBuildType = null;
35 +
36 + return function ({reactBuildType}) {
37 + displayedReactBuild = reduceReactBuild(displayedReactBuild, reactBuildType);
38 +
39 + target.postMessage(
40 + {
41 + source: 'react-devtools-hook',
42 + payload: {
43 + type: 'react-renderer-attached',
44 + reactBuildType: displayedReactBuild,
45 + },
46 + },
47 + '*',
48 + );
49 + };
50 +}
packages/react-devtools-shared/src/backend/types.js
+7
@@ -603,3 +603,10 @@ export type DevToolsHookSettings = {
603 export type DevToolsSettings = DevToolsHookSettings & {
604 componentFilters: Array<ComponentFilter>,
605 };
606 +
607 +export type ReactBuildType =
608 + | 'deadcode'
609 + | 'development'
610 + | 'outdated'
611 + | 'production'
612 + | 'unminified';
packages/react-devtools-shared/src/hook.js
+3 -2
@@ -17,6 +17,7 @@ import type {
17 DevToolsBackend,
18 DevToolsHookSettings,
19 ProfilingSettings,
20 + ReactBuildType,
21 } from './backend/types';
22 import type {ComponentFilter} from './frontend/types';
23
@@ -71,7 +72,7 @@ export function installHook(
72 return null;
73 }
74
74 - function detectReactBuildType(renderer: ReactRenderer) {
75 + function detectReactBuildType(renderer: ReactRenderer): ReactBuildType {
76 try {
77 if (typeof renderer.version === 'string') {
78 // React DOM Fiber (16+)
@@ -211,7 +212,7 @@ export function installHook(
212 const id = ++uidCounter;
213 renderers.set(id, renderer);
214
214 - const reactBuildType = hasDetectedBadDCE
215 + const reactBuildType: ReactBuildType = hasDetectedBadDCE
216 ? 'deadcode'
217 : detectReactBuildType(renderer);
218
scripts/flow/react-devtools.js
+14
@@ -17,6 +17,16 @@ declare const __IS_CHROME__: boolean;
17 declare const __IS_EDGE__: boolean;
18 declare const __IS_NATIVE__: boolean;
19
20 +interface ExtensionAction {
21 + /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action/setIcon} */
22 + setIcon(details: {
23 + tabId: number,
24 + path?: string | {[iconSize: string]: string},
25 + }): void;
26 + /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action/setPopup} */
27 + setPopup(details: {tabId: number, popup: string}): void;
28 +}
29 +
30 interface ExtensionDevtools {
31 /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/devtools/inspectedWindow} */
32 inspectedWindow: $FlowFixMe;
@@ -73,6 +83,8 @@ interface ExtensionRuntime {
83 extensionId: string,
84 connectInfo?: {name?: string, includeTlsChannelId?: boolean},
85 ): ExtensionRuntimePort;
86 + /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/getURL} */
87 + getURL(path: string): string;
88 /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage} */
89 onMessage: ExtensionEvent<
90 (
@@ -108,6 +120,8 @@ interface ExtensionTabs {
120 }
121
122 interface ExtensionAPI {
123 + /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action} */
124 + action: ExtensionAction;
125 devtools: ExtensionDevtools;
126 /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/permissions} */
127 permissions: $FlowFixMe;