From fe138e77fd67b595bd6ac02703d34964f2ea742b Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Mon, 9 Nov 2026 14:06:24 +0000 Subject: [PATCH] Add React DevTools Suspense panel --- config/gni/devtools_grd_files.gni | 2 + front_end/entrypoints/rn_fusebox/BUILD.gn | 1 + .../entrypoints/rn_fusebox/rn_fusebox.ts | 1 + front_end/panels/react_devtools/BUILD.gn | 9 ++++ .../ReactDevToolsSuspenseView.ts | 23 ++++++++++ .../react_devtools/ReactDevToolsViewBase.ts | 10 ++-- .../panels/react_devtools/react_devtools.ts | 3 +- .../react_devtools_suspense-meta.ts | 46 +++++++++++++++++++ .../react-devtools/react-devtools.ts | 4 +- .../ui/visual_logging/KnownContextValues.ts | 1 + 10 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 front_end/panels/react_devtools/ReactDevToolsSuspenseView.ts create mode 100644 front_end/panels/react_devtools/react_devtools_suspense-meta.ts diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index b01eefc5671d..81a811b348fd 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -669,6 +669,7 @@ grd_files_release_sources = [ "front_end/panels/react_devtools/react_devtools.js", "front_end/panels/react_devtools/react_devtools_components-meta.js", "front_end/panels/react_devtools/react_devtools_profiler-meta.js", + "front_end/panels/react_devtools/react_devtools_suspense-meta.js", "front_end/panels/recorder/components/components.js", "front_end/panels/recorder/controllers/controllers.js", "front_end/panels/recorder/converters/converters.js", @@ -1719,6 +1720,7 @@ grd_files_debug_sources = [ "front_end/panels/react_devtools/ReactDevToolsComponentsView.js", "front_end/panels/react_devtools/ReactDevToolsModel.js", "front_end/panels/react_devtools/ReactDevToolsProfilerView.js", + "front_end/panels/react_devtools/ReactDevToolsSuspenseView.js", "front_end/panels/react_devtools/ReactDevToolsViewBase.js", "front_end/panels/recorder/RecorderController.js", "front_end/panels/recorder/RecorderEvents.js", diff --git a/front_end/entrypoints/rn_fusebox/BUILD.gn b/front_end/entrypoints/rn_fusebox/BUILD.gn index 02c7dab7442f..130837eacbc2 100644 --- a/front_end/entrypoints/rn_fusebox/BUILD.gn +++ b/front_end/entrypoints/rn_fusebox/BUILD.gn @@ -49,6 +49,7 @@ devtools_entrypoint("entrypoint") { "../../panels/recorder:meta", "../../panels/react_devtools:components_meta", "../../panels/react_devtools:profiler_meta", + "../../panels/react_devtools:suspense_meta", "../../panels/rn_welcome:meta", "../../panels/security:meta", "../../panels/sensors:meta", diff --git a/front_end/entrypoints/rn_fusebox/rn_fusebox.ts b/front_end/entrypoints/rn_fusebox/rn_fusebox.ts index 154368f7340c..c4565e984898 100644 --- a/front_end/entrypoints/rn_fusebox/rn_fusebox.ts +++ b/front_end/entrypoints/rn_fusebox/rn_fusebox.ts @@ -13,6 +13,7 @@ import '../../panels/mobile_throttling/mobile_throttling-meta.js'; import '../../panels/network/network-meta.js'; import '../../panels/react_devtools/react_devtools_components-meta.js'; import '../../panels/react_devtools/react_devtools_profiler-meta.js'; +import '../../panels/react_devtools/react_devtools_suspense-meta.js'; import '../../panels/rn_welcome/rn_welcome-meta.js'; import '../../panels/timeline/timeline-meta.js'; diff --git a/front_end/panels/react_devtools/BUILD.gn b/front_end/panels/react_devtools/BUILD.gn index 07d928f84036..7daf58547304 100644 --- a/front_end/panels/react_devtools/BUILD.gn +++ b/front_end/panels/react_devtools/BUILD.gn @@ -12,6 +12,7 @@ devtools_module("react_devtools") { sources = [ "ReactDevToolsComponentsView.ts", "ReactDevToolsProfilerView.ts", + "ReactDevToolsSuspenseView.ts", "ReactDevToolsModel.ts", "ReactDevToolsViewBase.ts", ] @@ -58,3 +59,11 @@ devtools_entrypoint("profiler_meta") { visibility = [ "../../entrypoints/*" ] } + +devtools_entrypoint("suspense_meta") { + entrypoint = "react_devtools_suspense-meta.ts" + + deps = [ ":bundle" ] + + visibility = [ "../../entrypoints/*" ] +} diff --git a/front_end/panels/react_devtools/ReactDevToolsSuspenseView.ts b/front_end/panels/react_devtools/ReactDevToolsSuspenseView.ts new file mode 100644 index 000000000000..cdf5ed1a3c85 --- /dev/null +++ b/front_end/panels/react_devtools/ReactDevToolsSuspenseView.ts @@ -0,0 +1,23 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as i18n from '../../core/i18n/i18n.js'; + +import { ReactDevToolsViewBase } from './ReactDevToolsViewBase.js'; + +const UIStrings = { + /** + *@description Title of the React DevTools view + */ + title: '⚛️ Suspense (React DevTools)', +} as const; +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsSuspenseView.ts', UIStrings); +const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); + +export class ReactDevToolsSuspenseViewImpl extends ReactDevToolsViewBase { + constructor() { + super('suspense', i18nString(UIStrings.title)); + } +} diff --git a/front_end/panels/react_devtools/ReactDevToolsViewBase.ts b/front_end/panels/react_devtools/ReactDevToolsViewBase.ts index 3084f6863a31..00ba4a0928db 100644 --- a/front_end/panels/react_devtools/ReactDevToolsViewBase.ts +++ b/front_end/panels/react_devtools/ReactDevToolsViewBase.ts @@ -76,11 +76,11 @@ function viewElementSourceFunction(source: ReactDevToolsSource, symbolicatedSour export class ReactDevToolsViewBase extends UI.View.SimpleView implements SDK.TargetManager.SDKModelObserver { - readonly #tab: string; + readonly #tab: 'components' | 'profiler' | 'suspense'; #model: ReactDevToolsModel | null = null; constructor( - tab: 'components' | 'profiler', + tab: 'components' | 'profiler' | 'suspense', title: Platform.UIString.LocalizedString, ) { super(title, true); @@ -161,7 +161,11 @@ export class ReactDevToolsViewBase extends UI.View.SimpleView implements } const usingDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; - const initializeFn = this.#tab === 'components' ? ReactDevTools.initializeComponents : ReactDevTools.initializeProfiler; + const initializeFn = { + components: ReactDevTools.initializeComponents, + profiler: ReactDevTools.initializeProfiler, + suspense: ReactDevTools.initializeSuspense, + }[this.#tab]; initializeFn(this.contentElement, { bridge: model.getBridgeOrThrow(), diff --git a/front_end/panels/react_devtools/react_devtools.ts b/front_end/panels/react_devtools/react_devtools.ts index fcb39ab104b5..c521abbe9222 100644 --- a/front_end/panels/react_devtools/react_devtools.ts +++ b/front_end/panels/react_devtools/react_devtools.ts @@ -6,5 +6,6 @@ import * as ReactDevToolsComponentsView from './ReactDevToolsComponentsView.js'; import * as ReactDevToolsModel from './ReactDevToolsModel.js'; import * as ReactDevToolsProfilerView from './ReactDevToolsProfilerView.js'; +import * as ReactDevToolsSuspenseView from './ReactDevToolsSuspenseView.js'; -export {ReactDevToolsComponentsView, ReactDevToolsModel, ReactDevToolsProfilerView}; +export {ReactDevToolsComponentsView, ReactDevToolsModel, ReactDevToolsProfilerView, ReactDevToolsSuspenseView}; diff --git a/front_end/panels/react_devtools/react_devtools_suspense-meta.ts b/front_end/panels/react_devtools/react_devtools_suspense-meta.ts new file mode 100644 index 000000000000..4125ded33e1f --- /dev/null +++ b/front_end/panels/react_devtools/react_devtools_suspense-meta.ts @@ -0,0 +1,46 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as i18n from '../../core/i18n/i18n.js'; +import * as UI from '../../ui/legacy/legacy.js'; + +import type * as ReactDevToolsPanelModule from './react_devtools.js'; + +const UIStrings = { + /** + * @description React DevTools panel title + */ + title: 'Suspense ⚛', + + /** + * @description Command for showing the React DevTools panel + */ + command: 'Show React DevTools Suspense panel', +} as const; +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/react_devtools_suspense-meta.ts', UIStrings); +const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); + +let loadedModule: (typeof ReactDevToolsPanelModule|undefined); + +async function loadModule(): Promise { + if (!loadedModule) { + loadedModule = await import('./react_devtools.js'); + } + return loadedModule; +} + +UI.ViewManager.registerViewExtension({ + location: UI.ViewManager.ViewLocationValues.PANEL, + id: 'react-devtools-suspense', + title: i18nLazyString(UIStrings.title), + commandPrompt: i18nLazyString(UIStrings.command), + persistence: UI.ViewManager.ViewPersistence.PERMANENT, + order: 1000, + async loadView() { + const Module = await loadModule(); + return new Module.ReactDevToolsSuspenseView.ReactDevToolsSuspenseViewImpl(); + }, +}); + diff --git a/front_end/third_party/react-devtools/react-devtools.ts b/front_end/third_party/react-devtools/react-devtools.ts index 9ec1f351b5aa..670e211ec5d8 100644 --- a/front_end/third_party/react-devtools/react-devtools.ts +++ b/front_end/third_party/react-devtools/react-devtools.ts @@ -1,5 +1,5 @@ -import {createBridge, createStore, initializeComponents, initializeProfiler} from './package/frontend.js'; +import {createBridge, createStore, initializeComponents, initializeProfiler, initializeSuspense} from './package/frontend.js'; import CSS from './package/frontend.css.js'; export type * from './package/frontend'; -export {createBridge, createStore, initializeComponents, initializeProfiler, CSS}; +export {createBridge, createStore, initializeComponents, initializeProfiler, initializeSuspense, CSS}; diff --git a/front_end/ui/visual_logging/KnownContextValues.ts b/front_end/ui/visual_logging/KnownContextValues.ts index 92cfb7fb450f..c1efedb13443 100644 --- a/front_end/ui/visual_logging/KnownContextValues.ts +++ b/front_end/ui/visual_logging/KnownContextValues.ts @@ -2798,6 +2798,7 @@ export const knownContextValues = new Set([ 'raw-headers-show-more', 'react-devtools-components', 'react-devtools-profiler', + 'react-devtools-suspense', 'read-only', 'read-write', 'readiness-list-link',