From 1c755ec40078eba33fb2a85d02e4daf5b7235140 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Wed, 14 Aug 2024 18:29:57 +0100 Subject: [PATCH] Split React DevTools into individual panels --- config/gni/devtools_grd_files.gni | 7 +- front_end/entrypoints/rn_fusebox/BUILD.gn | 3 +- .../entrypoints/rn_fusebox/rn_fusebox.ts | 3 +- front_end/panels/react_devtools/BUILD.gn | 19 +- .../ReactDevToolsComponentsView.ts | 23 +++ .../react_devtools/ReactDevToolsModel.ts | 143 +++++++++----- .../ReactDevToolsProfilerView.ts | 23 +++ ...vToolsView.ts => ReactDevToolsViewBase.ts} | 174 ++++++++---------- .../panels/react_devtools/react_devtools.ts | 5 +- .../react_devtools_components-meta.ts | 46 +++++ ...eta.ts => react_devtools_profiler-meta.ts} | 10 +- .../react-devtools/react-devtools.ts | 4 +- 12 files changed, 304 insertions(+), 156 deletions(-) create mode 100644 front_end/panels/react_devtools/ReactDevToolsComponentsView.ts create mode 100644 front_end/panels/react_devtools/ReactDevToolsProfilerView.ts rename front_end/panels/react_devtools/{ReactDevToolsView.ts => ReactDevToolsViewBase.ts} (60%) create mode 100644 front_end/panels/react_devtools/react_devtools_components-meta.ts rename front_end/panels/react_devtools/{react_devtools-meta.ts => react_devtools_profiler-meta.ts} (84%) diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index 416b1cf6ea0..c55ecce0857 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -538,7 +538,8 @@ grd_files_release_sources = [ "front_end/panels/protocol_monitor/components/components.js", "front_end/panels/protocol_monitor/protocol_monitor-meta.js", "front_end/panels/protocol_monitor/protocol_monitor.js", - "front_end/panels/react_devtools/react_devtools-meta.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.js", "front_end/panels/recorder/components/components.js", "front_end/panels/recorder/controllers/controllers.js", @@ -1464,8 +1465,10 @@ grd_files_debug_sources = [ "front_end/panels/protocol_monitor/components/Toolbar.js", "front_end/panels/protocol_monitor/components/toolbar.css.js", "front_end/panels/protocol_monitor/protocolMonitor.css.js", + "front_end/panels/react_devtools/ReactDevToolsComponentsView.js", "front_end/panels/react_devtools/ReactDevToolsModel.js", - "front_end/panels/react_devtools/ReactDevToolsView.js", + "front_end/panels/react_devtools/ReactDevToolsProfilerView.js", + "front_end/panels/react_devtools/ReactDevToolsViewBase.js", "front_end/panels/recorder/RecorderController.js", "front_end/panels/recorder/RecorderEvents.js", "front_end/panels/recorder/RecorderPanel.js", diff --git a/front_end/entrypoints/rn_fusebox/BUILD.gn b/front_end/entrypoints/rn_fusebox/BUILD.gn index b8202e5ff4b..bd59ad615d8 100644 --- a/front_end/entrypoints/rn_fusebox/BUILD.gn +++ b/front_end/entrypoints/rn_fusebox/BUILD.gn @@ -29,7 +29,8 @@ devtools_entrypoint("entrypoint") { "../../panels/network:meta", "../../panels/performance_monitor:meta", "../../panels/recorder:meta", - "../../panels/react_devtools:meta", + "../../panels/react_devtools:components_meta", + "../../panels/react_devtools:profiler_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 3a07b1b7634..a09654b7f86 100644 --- a/front_end/entrypoints/rn_fusebox/rn_fusebox.ts +++ b/front_end/entrypoints/rn_fusebox/rn_fusebox.ts @@ -11,7 +11,8 @@ import '../inspector_main/inspector_main-meta.js'; import '../../panels/issues/issues-meta.js'; import '../../panels/mobile_throttling/mobile_throttling-meta.js'; import '../../panels/network/network-meta.js'; -import '../../panels/react_devtools/react_devtools-meta.js'; +import '../../panels/react_devtools/react_devtools_components-meta.js'; +import '../../panels/react_devtools/react_devtools_profiler-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 37f08cf3eb6..07d928f8403 100644 --- a/front_end/panels/react_devtools/BUILD.gn +++ b/front_end/panels/react_devtools/BUILD.gn @@ -9,7 +9,12 @@ import("../../../scripts/build/ninja/generate_css.gni") import("../visibility.gni") devtools_module("react_devtools") { - sources = [ "ReactDevToolsView.ts", "ReactDevToolsModel.ts" ] + sources = [ + "ReactDevToolsComponentsView.ts", + "ReactDevToolsProfilerView.ts", + "ReactDevToolsModel.ts", + "ReactDevToolsViewBase.ts", + ] deps = [ "../../core/host:bundle", @@ -38,8 +43,16 @@ devtools_entrypoint("bundle") { visibility += devtools_panels_visibility } -devtools_entrypoint("meta") { - entrypoint = "react_devtools-meta.ts" +devtools_entrypoint("components_meta") { + entrypoint = "react_devtools_components-meta.ts" + + deps = [ ":bundle" ] + + visibility = [ "../../entrypoints/*" ] +} + +devtools_entrypoint("profiler_meta") { + entrypoint = "react_devtools_profiler-meta.ts" deps = [ ":bundle" ] diff --git a/front_end/panels/react_devtools/ReactDevToolsComponentsView.ts b/front_end/panels/react_devtools/ReactDevToolsComponentsView.ts new file mode 100644 index 00000000000..bd2513d701b --- /dev/null +++ b/front_end/panels/react_devtools/ReactDevToolsComponentsView.ts @@ -0,0 +1,23 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2024 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: '⚛️ Components (React DevTools)', +}; +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsComponentsView.ts', UIStrings); +const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); + +export class ReactDevToolsComponentsViewImpl extends ReactDevToolsViewBase { + constructor() { + super('components', i18nString(UIStrings.title)); + } +} diff --git a/front_end/panels/react_devtools/ReactDevToolsModel.ts b/front_end/panels/react_devtools/ReactDevToolsModel.ts index 06b6d3dc7e1..73ae2a5c320 100644 --- a/front_end/panels/react_devtools/ReactDevToolsModel.ts +++ b/front_end/panels/react_devtools/ReactDevToolsModel.ts @@ -5,6 +5,7 @@ import * as SDK from '../../core/sdk/sdk.js'; import * as ReactNativeModels from '../../models/react_native/react_native.js'; +import * as ReactDevTools from '../../third_party/react-devtools/react-devtools.js'; import type * as ReactDevToolsTypes from '../../third_party/react-devtools/react-devtools.js'; import type * as Common from '../../core/common/common.js'; @@ -13,14 +14,12 @@ export const enum Events { InitializationCompleted = 'InitializationCompleted', InitializationFailed = 'InitializationFailed', Destroyed = 'Destroyed', - MessageReceived = 'MessageReceived', } export type EventTypes = { [Events.InitializationCompleted]: void, [Events.InitializationFailed]: string, [Events.Destroyed]: void, - [Events.MessageReceived]: ReactDevToolsTypes.Message, }; type ReactDevToolsBindingsBackendExecutionContextUnavailableEvent = Common.EventTarget.EventTargetEvent< @@ -31,65 +30,115 @@ type ReactDevToolsBindingsBackendExecutionContextUnavailableEvent = Common.Event export class ReactDevToolsModel extends SDK.SDKModel.SDKModel { private static readonly FUSEBOX_BINDING_NAMESPACE = 'react-devtools'; - private readonly rdtBindingsModel: ReactNativeModels.ReactDevToolsBindingsModel.ReactDevToolsBindingsModel | null; + + readonly #wall: ReactDevToolsTypes.Wall; + readonly #bindingsModel: ReactNativeModels.ReactDevToolsBindingsModel.ReactDevToolsBindingsModel; + readonly #listeners: Set = new Set(); + #initializeCalled: boolean = false; + #initialized: boolean = false; + #bridge: ReactDevToolsTypes.Bridge | null; + #store: ReactDevToolsTypes.Store | null; constructor(target: SDK.Target.Target) { super(target); - const rdtBindingsModel = target.model(ReactNativeModels.ReactDevToolsBindingsModel.ReactDevToolsBindingsModel); - if (!rdtBindingsModel) { + this.#wall = { + listen: (listener): Function => { + this.#listeners.add(listener); + + return (): void => { + this.#listeners.delete(listener); + }; + }, + send: (event, payload): void => void this.#sendMessage({event, payload}), + }; + this.#bridge = ReactDevTools.createBridge(this.#wall); + this.#store = ReactDevTools.createStore(this.#bridge); + + const bindingsModel = target.model(ReactNativeModels.ReactDevToolsBindingsModel.ReactDevToolsBindingsModel); + if (bindingsModel == null) { throw new Error('Failed to construct ReactDevToolsModel: ReactDevToolsBindingsModel was null'); } - this.rdtBindingsModel = rdtBindingsModel; - - rdtBindingsModel.addEventListener(ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextCreated, this.onBackendExecutionContextCreated, this); - rdtBindingsModel.addEventListener(ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextUnavailable, this.onBackendExecutionContextUnavailable, this); - rdtBindingsModel.addEventListener(ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextDestroyed, this.onBackendExecutionContextDestroyed, this); + this.#bindingsModel = bindingsModel; - void this.initialize(rdtBindingsModel); - } + bindingsModel.addEventListener( + ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextCreated, + this.#handleBackendExecutionContextCreated, + this, + ); + bindingsModel.addEventListener( + ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextUnavailable, + this.#handleBackendExecutionContextUnavailable, + this, + ); + bindingsModel.addEventListener( + ReactNativeModels.ReactDevToolsBindingsModel.Events.BackendExecutionContextDestroyed, + this.#handleBackendExecutionContextDestroyed, + this, + ); - private async initialize(rdtBindingsModel: ReactNativeModels.ReactDevToolsBindingsModel.ReactDevToolsBindingsModel): Promise { - return rdtBindingsModel.enable() - .then(() => this.onBindingsModelInitializationCompleted()) - .catch((error: Error) => this.onBindingsModelInitializationFailed(error)); + // Notify backend if Chrome DevTools was closed, marking frontend as disconnected + window.addEventListener('beforeunload', () => this.#bridge?.shutdown()); } - private onBindingsModelInitializationCompleted(): void { - const rdtBindingsModel = this.rdtBindingsModel; - if (!rdtBindingsModel) { - throw new Error('Failed to initialize ReactDevToolsModel: ReactDevToolsBindingsModel was null'); + async ensureInitialized(): Promise { + if (this.#initializeCalled) { + return; } - rdtBindingsModel.subscribeToDomainMessages( - ReactDevToolsModel.FUSEBOX_BINDING_NAMESPACE, - message => this.onMessage(message as ReactDevToolsTypes.Message), - ); + this.#initializeCalled = true; + + try { + const bindingsModel = this.#bindingsModel; + await bindingsModel.enable(); + + bindingsModel.subscribeToDomainMessages( + ReactDevToolsModel.FUSEBOX_BINDING_NAMESPACE, + message => this.#handleMessage(message as ReactDevToolsTypes.Message), + ); + + await bindingsModel.initializeDomain(ReactDevToolsModel.FUSEBOX_BINDING_NAMESPACE); - void rdtBindingsModel.initializeDomain(ReactDevToolsModel.FUSEBOX_BINDING_NAMESPACE) - .then(() => this.onDomainInitializationCompleted()) - .catch((error: Error) => this.onDomainInitializationFailed(error)); + this.#initialized = true; + this.dispatchEventToListeners(Events.InitializationCompleted); + } catch (e) { + this.dispatchEventToListeners(Events.InitializationFailed, e.message); + } } - private onBindingsModelInitializationFailed(error: Error): void { - this.dispatchEventToListeners(Events.InitializationFailed, error.message); + isInitialized(): boolean { + return this.#initialized; } - private onDomainInitializationCompleted(): void { - this.dispatchEventToListeners(Events.InitializationCompleted); + getBridgeOrThrow(): ReactDevToolsTypes.Bridge { + if (this.#bridge == null) { + throw new Error('Failed to get bridge from ReactDevToolsModel: bridge was null'); + } + + return this.#bridge; } - private onDomainInitializationFailed(error: Error): void { - this.dispatchEventToListeners(Events.InitializationFailed, error.message); + getStoreOrThrow(): ReactDevToolsTypes.Store { + if (this.#store == null) { + throw new Error('Failed to get store from ReactDevToolsModel: store was null'); + } + + return this.#store; } - private onMessage(message: ReactDevToolsTypes.Message): void { - this.dispatchEventToListeners(Events.MessageReceived, message); + #handleMessage(message: ReactDevToolsTypes.Message): void { + if (!message) { + return; + } + + for (const listener of this.#listeners) { + listener(message); + } } - async sendMessage(message: ReactDevToolsTypes.Message): Promise { - const rdtBindingsModel = this.rdtBindingsModel; + async #sendMessage(message: ReactDevToolsTypes.Message): Promise { + const rdtBindingsModel = this.#bindingsModel; if (!rdtBindingsModel) { throw new Error('Failed to send message from ReactDevToolsModel: ReactDevToolsBindingsModel was null'); } @@ -97,25 +146,33 @@ export class ReactDevToolsModel extends SDK.SDKModel.SDKModel { return rdtBindingsModel.sendMessage(ReactDevToolsModel.FUSEBOX_BINDING_NAMESPACE, message); } - private onBackendExecutionContextCreated(): void { - const rdtBindingsModel = this.rdtBindingsModel; + #handleBackendExecutionContextCreated(): void { + const rdtBindingsModel = this.#bindingsModel; if (!rdtBindingsModel) { throw new Error('ReactDevToolsModel failed to handle BackendExecutionContextCreated event: ReactDevToolsBindingsModel was null'); } - // This could happen if the app was reloaded while ReactDevToolsBindingsModel was initialing + this.#bridge = ReactDevTools.createBridge(this.#wall); + this.#store = ReactDevTools.createStore(this.#bridge); + + // This could happen if the app was reloaded while ReactDevToolsBindingsModel was initializing if (!rdtBindingsModel.isEnabled()) { - void this.initialize(rdtBindingsModel); + void this.ensureInitialized(); } else { this.dispatchEventToListeners(Events.InitializationCompleted); } } - private onBackendExecutionContextUnavailable({data: errorMessage}: ReactDevToolsBindingsBackendExecutionContextUnavailableEvent): void { + #handleBackendExecutionContextUnavailable({data: errorMessage}: ReactDevToolsBindingsBackendExecutionContextUnavailableEvent): void { this.dispatchEventToListeners(Events.InitializationFailed, errorMessage); } - private onBackendExecutionContextDestroyed(): void { + #handleBackendExecutionContextDestroyed(): void { + this.#bridge?.shutdown(); + this.#bridge = null; + this.#store = null; + this.#listeners.clear(); + this.dispatchEventToListeners(Events.Destroyed); } } diff --git a/front_end/panels/react_devtools/ReactDevToolsProfilerView.ts b/front_end/panels/react_devtools/ReactDevToolsProfilerView.ts new file mode 100644 index 00000000000..d15eff533bb --- /dev/null +++ b/front_end/panels/react_devtools/ReactDevToolsProfilerView.ts @@ -0,0 +1,23 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2024 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: '⚛️ Profiler (React DevTools)', +}; +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsProfilerView.ts', UIStrings); +const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); + +export class ReactDevToolsProfilerViewImpl extends ReactDevToolsViewBase { + constructor() { + super('profiler', i18nString(UIStrings.title)); + } +} diff --git a/front_end/panels/react_devtools/ReactDevToolsView.ts b/front_end/panels/react_devtools/ReactDevToolsViewBase.ts similarity index 60% rename from front_end/panels/react_devtools/ReactDevToolsView.ts rename to front_end/panels/react_devtools/ReactDevToolsViewBase.ts index d549028d36c..8b41ba3fac8 100644 --- a/front_end/panels/react_devtools/ReactDevToolsView.ts +++ b/front_end/panels/react_devtools/ReactDevToolsViewBase.ts @@ -17,22 +17,18 @@ import {Events as ReactDevToolsModelEvents, ReactDevToolsModel, type EventTypes import type * as ReactDevToolsTypes from '../../third_party/react-devtools/react-devtools.js'; import type * as Platform from '../../core/platform/platform.js'; +import { LocalizedString } from '../../core/platform/UIString.js'; const UIStrings = { - /** - *@description Title of the React DevTools view - */ - title: 'React DevTools', /** * @description Label of the FB-only 'send feedback' button. */ sendFeedback: '[FB-only] Send feedback', }; -const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsView.ts', UIStrings); +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/ReactDevToolsViewBase.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); type ReactDevToolsInitializationFailedEvent = Common.EventTarget.EventTargetEvent; -type ReactDevToolsMessageReceivedEvent = Common.EventTarget.EventTargetEvent; // Based on ExtensionServer.onOpenResource async function openResource( @@ -73,96 +69,106 @@ function viewElementSourceFunction(source: ReactDevToolsTypes.Source, symbolicat void openResource(sourceURL as Platform.DevToolsPath.UrlString, line - 1, column - 1); } -export class ReactDevToolsViewImpl extends UI.View.SimpleView { - private readonly wall: ReactDevToolsTypes.Wall; - private backendIsConnected: boolean = false; - private bridge: ReactDevToolsTypes.Bridge | null = null; - private store: ReactDevToolsTypes.Store | null = null; - private readonly listeners: Set = new Set(); +export class ReactDevToolsViewBase extends UI.View.SimpleView implements + SDK.TargetManager.SDKModelObserver { + readonly #tab: string; + #model: ReactDevToolsModel | null = null; + + constructor( + tab: 'components' | 'profiler', + title: LocalizedString, + ) { + super(title); - constructor() { - super(i18nString(UIStrings.title)); + this.#tab = tab; + this.#renderLoader(); + } + + override wasShown(): void { + super.wasShown(); + this.registerCSSFiles([ReactDevTools.CSS]); - this.wall = { - listen: (listener): Function => { - this.listeners.add(listener); + if (this.#model == null) { + SDK.TargetManager.TargetManager.instance().observeModels(ReactDevToolsModel, this); + } + } - return (): void => { - this.listeners.delete(listener); - }; - }, - send: (event, payload): void => this.sendMessage(event, payload), - }; + modelAdded(model: ReactDevToolsModel): void { + this.#model = model; - // Notify backend if Chrome DevTools was closed, marking frontend as disconnected - window.addEventListener('beforeunload', () => this.bridge?.shutdown()); + if (model.isInitialized()) { + // Already initialized from another rendered React DevTools view - render + // from initialized state + this.#renderDevToolsView(); + } - SDK.TargetManager.TargetManager.instance().addModelListener( - ReactDevToolsModel, + model.addEventListener( ReactDevToolsModelEvents.InitializationCompleted, - this.onInitializationCompleted, + this.#handleInitializationCompleted, this, ); - SDK.TargetManager.TargetManager.instance().addModelListener( - ReactDevToolsModel, + model.addEventListener( ReactDevToolsModelEvents.InitializationFailed, - this.onInitializationFailed, + this.#handleInitializationFailed, this, ); - SDK.TargetManager.TargetManager.instance().addModelListener( - ReactDevToolsModel, + model.addEventListener( ReactDevToolsModelEvents.Destroyed, - this.onDestroyed, + this.#handleBackendDestroyed, + this, + ); + void model.ensureInitialized(); + } + + modelRemoved(model: ReactDevToolsModel): void { + model.removeEventListener( + ReactDevToolsModelEvents.InitializationCompleted, + this.#handleInitializationCompleted, + this, + ); + model.removeEventListener( + ReactDevToolsModelEvents.InitializationFailed, + this.#handleInitializationFailed, this, ); - SDK.TargetManager.TargetManager.instance().addModelListener( - ReactDevToolsModel, - ReactDevToolsModelEvents.MessageReceived, - this.onMessage, + model.removeEventListener( + ReactDevToolsModelEvents.Destroyed, + this.#handleBackendDestroyed, this, ); + } - this.renderLoader(); + #handleInitializationCompleted(): void { + this.#renderDevToolsView(); } - private onInitializationCompleted(): void { - // Clear loader or error views - this.clearView(); + #handleInitializationFailed({data: errorMessage}: ReactDevToolsInitializationFailedEvent): void { + this.#renderErrorView(errorMessage); + } + + #handleBackendDestroyed(): void { + this.#renderLoader(); + } - this.backendIsConnected = true; - this.bridge = ReactDevTools.createBridge(this.wall); - this.store = ReactDevTools.createStore(this.bridge); + #renderDevToolsView(): void { + this.#clearView(); + const model = this.#model!; const usingDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; - ReactDevTools.initialize(this.contentElement, { - bridge: this.bridge, - store: this.store, + const initializeFn = this.#tab === 'components' ? ReactDevTools.initializeComponents : ReactDevTools.initializeProfiler; + + initializeFn(this.contentElement, { + bridge: model.getBridgeOrThrow(), + store: model.getStoreOrThrow(), theme: usingDarkTheme ? 'dark' : 'light', canViewElementSourceFunction: () => true, viewElementSourceFunction, }); } - private onInitializationFailed({data: errorMessage}: ReactDevToolsInitializationFailedEvent): void { - this.backendIsConnected = false; - this.clearView(); - this.renderErrorView(errorMessage); - } - - private onDestroyed(): void { - // Unmount React DevTools view - this.clearView(); - - this.backendIsConnected = false; - this.bridge?.shutdown(); - this.bridge = null; - this.store = null; - this.listeners.clear(); - - this.renderLoader(); - } + #renderLoader(): void { + this.#clearView(); - private renderLoader(): void { const loaderContainer = document.createElement('div'); loaderContainer.setAttribute('style', 'display: flex; flex: 1; justify-content: center; align-items: center'); @@ -173,7 +179,9 @@ export class ReactDevToolsViewImpl extends UI.View.SimpleView { this.contentElement.appendChild(loaderContainer); } - private renderErrorView(errorMessage: string): void { + #renderErrorView(errorMessage: string): void { + this.#clearView(); + const errorContainer = document.createElement('div'); errorContainer.setAttribute('style', 'display: flex; flex: 1; flex-direction: column; justify-content: center; align-items: center'); @@ -198,35 +206,7 @@ export class ReactDevToolsViewImpl extends UI.View.SimpleView { } } - private clearView(): void { + #clearView(): void { this.contentElement.removeChildren(); } - - override wasShown(): void { - super.wasShown(); - - // This has to be here, because initialize() can be called when user is on the other panel and view is unmounted - this.registerCSSFiles([ReactDevTools.CSS]); - } - - private onMessage({data: message}: ReactDevToolsMessageReceivedEvent): void { - if (!message) { - return; - } - - for (const listener of this.listeners) { - listener(message); - } - } - - private sendMessage(event: string, payload?: ReactDevToolsTypes.MessagePayload): void { - // If the execution context has been destroyed, do not attempt to send a message - if (!this.backendIsConnected) { - return; - } - - for (const model of SDK.TargetManager.TargetManager.instance().models(ReactDevToolsModel, {scoped: true})) { - void model.sendMessage({event, payload}); - } - } } diff --git a/front_end/panels/react_devtools/react_devtools.ts b/front_end/panels/react_devtools/react_devtools.ts index b024f5b623e..fcb39ab104b 100644 --- a/front_end/panels/react_devtools/react_devtools.ts +++ b/front_end/panels/react_devtools/react_devtools.ts @@ -3,7 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import * as ReactDevToolsView from './ReactDevToolsView.js'; +import * as ReactDevToolsComponentsView from './ReactDevToolsComponentsView.js'; import * as ReactDevToolsModel from './ReactDevToolsModel.js'; +import * as ReactDevToolsProfilerView from './ReactDevToolsProfilerView.js'; -export {ReactDevToolsView, ReactDevToolsModel}; +export {ReactDevToolsComponentsView, ReactDevToolsModel, ReactDevToolsProfilerView}; diff --git a/front_end/panels/react_devtools/react_devtools_components-meta.ts b/front_end/panels/react_devtools/react_devtools_components-meta.ts new file mode 100644 index 00000000000..80dab3e1553 --- /dev/null +++ b/front_end/panels/react_devtools/react_devtools_components-meta.ts @@ -0,0 +1,46 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2024 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: '⚛️ Components', + + /** + * @description Command for showing the React DevTools panel + */ + command: 'Show React DevTools Components panel', +}; +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/react_devtools_components-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-components', + title: i18nLazyString(UIStrings.title), + commandPrompt: i18nLazyString(UIStrings.command), + persistence: UI.ViewManager.ViewPersistence.PERMANENT, + order: 1000, + async loadView() { + const Module = await loadModule(); + return new Module.ReactDevToolsComponentsView.ReactDevToolsComponentsViewImpl(); + }, +}); + diff --git a/front_end/panels/react_devtools/react_devtools-meta.ts b/front_end/panels/react_devtools/react_devtools_profiler-meta.ts similarity index 84% rename from front_end/panels/react_devtools/react_devtools-meta.ts rename to front_end/panels/react_devtools/react_devtools_profiler-meta.ts index b34d6847c89..e2c67d06fe4 100644 --- a/front_end/panels/react_devtools/react_devtools-meta.ts +++ b/front_end/panels/react_devtools/react_devtools_profiler-meta.ts @@ -12,14 +12,14 @@ const UIStrings = { /** * @description React DevTools panel title */ - title: '⚛️ React DevTools', + title: '⚛️ Profiler', /** * @description Command for showing the React DevTools panel */ - command: 'Show React DevTools panel', + command: 'Show React DevTools Profiler panel', }; -const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/react_devtools-meta.ts', UIStrings); +const str_ = i18n.i18n.registerUIStrings('panels/react_devtools/react_devtools_profiler-meta.ts', UIStrings); const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); let loadedModule: (typeof ReactDevToolsPanelModule|undefined); @@ -33,14 +33,14 @@ async function loadModule(): Promise { UI.ViewManager.registerViewExtension({ location: UI.ViewManager.ViewLocationValues.PANEL, - id: 'react-devtools', + id: 'react-devtools-profiler', title: i18nLazyString(UIStrings.title), commandPrompt: i18nLazyString(UIStrings.command), persistence: UI.ViewManager.ViewPersistence.PERMANENT, order: 1000, async loadView() { const Module = await loadModule(); - return new Module.ReactDevToolsView.ReactDevToolsViewImpl(); + return new Module.ReactDevToolsProfilerView.ReactDevToolsProfilerViewImpl(); }, }); diff --git a/front_end/third_party/react-devtools/react-devtools.ts b/front_end/third_party/react-devtools/react-devtools.ts index 7c9659d7326..9ec1f351b5a 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, initialize} from './package/frontend.js'; +import {createBridge, createStore, initializeComponents, initializeProfiler} from './package/frontend.js'; import CSS from './package/frontend.css.js'; export type * from './package/frontend'; -export {createBridge, createStore, initialize, CSS}; +export {createBridge, createStore, initializeComponents, initializeProfiler, CSS};