diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index 0e034586bdc..74f22c1a068 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -842,8 +842,10 @@ grd_files_debug_sources = [ "front_end/entrypoints/node_app/NodeConnectionsPanel.js", "front_end/entrypoints/node_app/NodeMain.js", "front_end/entrypoints/node_app/nodeConnectionsPanel.css.js", + "front_end/entrypoints/rn_fusebox/FuseboxAppMetadataObserver.js", "front_end/entrypoints/rn_fusebox/FuseboxProfilingBuildObserver.js", "front_end/entrypoints/rn_fusebox/FuseboxReconnectDeviceButton.js", + "front_end/entrypoints/rn_fusebox/FuseboxWindowTitleManager.js", "front_end/entrypoints/shell/browser_compatibility_guard.js", "front_end/entrypoints/wasmparser_worker/WasmParserWorker.js", "front_end/entrypoints/worker_app/WorkerMain.js", diff --git a/front_end/entrypoints/rn_fusebox/BUILD.gn b/front_end/entrypoints/rn_fusebox/BUILD.gn index 038049032cf..a1e58ab3a6b 100644 --- a/front_end/entrypoints/rn_fusebox/BUILD.gn +++ b/front_end/entrypoints/rn_fusebox/BUILD.gn @@ -9,8 +9,10 @@ import("../visibility.gni") devtools_module("rn_fusebox") { sources = [ + "FuseboxAppMetadataObserver.ts", "FuseboxReconnectDeviceButton.ts", "FuseboxProfilingBuildObserver.ts", + "FuseboxWindowTitleManager.ts", ] deps = [ diff --git a/front_end/entrypoints/rn_fusebox/FuseboxAppMetadataObserver.ts b/front_end/entrypoints/rn_fusebox/FuseboxAppMetadataObserver.ts new file mode 100644 index 00000000000..61077f48d0d --- /dev/null +++ b/front_end/entrypoints/rn_fusebox/FuseboxAppMetadataObserver.ts @@ -0,0 +1,38 @@ +// 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 Common from '../../core/common/common.js'; +import * as Protocol from '../../generated/protocol.js'; +import * as SDK from '../../core/sdk/sdk.js'; +import FuseboxWindowTitleManager from './FuseboxWindowTitleManager.js'; + +/** + * Model observer which updates the DevTools window title based on the connected + * React Native app metadata. + */ +export default class FuseboxAppMetadataObserver implements + SDK.TargetManager.SDKModelObserver { + constructor(targetManager: SDK.TargetManager.TargetManager) { + targetManager.observeModels(SDK.ReactNativeApplicationModel.ReactNativeApplicationModel, this); + } + + modelAdded(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void { + model.ensureEnabled(); + model.addEventListener(SDK.ReactNativeApplicationModel.Events.MetadataUpdated, this.#handleMetadataUpdated, this); + } + + modelRemoved(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void { + model.removeEventListener( + SDK.ReactNativeApplicationModel.Events.MetadataUpdated, this.#handleMetadataUpdated, this); + } + + #handleMetadataUpdated( + event: Common.EventTarget.EventTargetEvent): void { + const {appDisplayName, deviceName} = event.data; + + // Update window title + FuseboxWindowTitleManager.instance().setAppInfo(appDisplayName, deviceName); + } +} diff --git a/front_end/entrypoints/rn_fusebox/FuseboxProfilingBuildObserver.ts b/front_end/entrypoints/rn_fusebox/FuseboxProfilingBuildObserver.ts index 670b1a8c888..7648f0508f6 100644 --- a/front_end/entrypoints/rn_fusebox/FuseboxProfilingBuildObserver.ts +++ b/front_end/entrypoints/rn_fusebox/FuseboxProfilingBuildObserver.ts @@ -9,6 +9,7 @@ import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as i18n from '../../core/i18n/i18n.js'; +import FuseboxWindowTitleManager from './FuseboxWindowTitleManager.js'; const UIStrings = { /** @@ -45,6 +46,7 @@ export default class FuseboxProfilingBuildObserver implements const {unstable_isProfilingBuild} = event.data; if (unstable_isProfilingBuild) { + FuseboxWindowTitleManager.instance().setSuffix('[PROFILING]'); this.#hideUnsupportedFeatures(); this.#ensurePerformancePanelEnabled(); } diff --git a/front_end/entrypoints/rn_fusebox/FuseboxWindowTitleManager.ts b/front_end/entrypoints/rn_fusebox/FuseboxWindowTitleManager.ts new file mode 100644 index 00000000000..36d0be917cf --- /dev/null +++ b/front_end/entrypoints/rn_fusebox/FuseboxWindowTitleManager.ts @@ -0,0 +1,48 @@ +// 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. + +export default class FuseboxWindowTitleManager { + static #instance: FuseboxWindowTitleManager; + #appDisplayName?: string; + #deviceName?: string; + #suffix?: string; + + private constructor() {} + + static instance(): FuseboxWindowTitleManager { + if (!this.#instance) { + this.#instance = new FuseboxWindowTitleManager(); + } + return this.#instance; + } + + setAppInfo(appDisplayName: string | undefined, deviceName: string | undefined): void { + this.#appDisplayName = appDisplayName; + this.#deviceName = deviceName; + this.#updateTitle(); + } + + setSuffix(suffix: string): void { + this.#suffix = suffix; + this.#updateTitle(); + } + + #updateTitle(): void { + const parts: string[] = []; + + if (this.#appDisplayName) { + parts.push(this.#appDisplayName); + } + if (this.#deviceName) { + parts.push(`(${this.#deviceName})`); + } + if (this.#suffix) { + parts.push(this.#suffix); + } + parts.push('- React Native DevTools'); + + document.title = parts.join(' '); + } +} diff --git a/front_end/entrypoints/rn_fusebox/rn_fusebox.ts b/front_end/entrypoints/rn_fusebox/rn_fusebox.ts index 8fcaceac274..21d6c266ba1 100644 --- a/front_end/entrypoints/rn_fusebox/rn_fusebox.ts +++ b/front_end/entrypoints/rn_fusebox/rn_fusebox.ts @@ -23,8 +23,7 @@ import * as RNExperiments from '../../core/rn_experiments/rn_experiments.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as Main from '../main/main.js'; -import * as Common from '../../core/common/common.js'; -import * as Protocol from '../../generated/protocol.js'; +import FuseboxAppMetadataObserver from './FuseboxAppMetadataObserver.js'; import FuseboxReconnectDeviceButton from './FuseboxReconnectDeviceButton.js'; import FuseboxProfilingBuildObserver from './FuseboxProfilingBuildObserver.js'; @@ -164,34 +163,7 @@ UI.Toolbar.registerToolbarItem({ }, }); -class FuseboxReactNativeApplicationObserver implements - SDK.TargetManager.SDKModelObserver { - constructor(targetManager: SDK.TargetManager.TargetManager) { - targetManager.observeModels(SDK.ReactNativeApplicationModel.ReactNativeApplicationModel, this); - } - - modelAdded(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void { - model.ensureEnabled(); - model.addEventListener(SDK.ReactNativeApplicationModel.Events.MetadataUpdated, this.#handleMetadataUpdated, this); - } - - modelRemoved(model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel): void { - model.removeEventListener( - SDK.ReactNativeApplicationModel.Events.MetadataUpdated, this.#handleMetadataUpdated, this); - } - - #handleMetadataUpdated( - event: Common.EventTarget.EventTargetEvent): void { - const {appDisplayName, deviceName} = event.data; - - // Update window title - if (appDisplayName !== null && appDisplayName !== undefined) { - document.title = `${appDisplayName}${deviceName !== null && deviceName !== undefined ? ` (${deviceName})` : ''} - React Native DevTools`; - } - } -} - -new FuseboxReactNativeApplicationObserver(SDK.TargetManager.TargetManager.instance()); +new FuseboxAppMetadataObserver(SDK.TargetManager.TargetManager.instance()); new FuseboxProfilingBuildObserver(SDK.TargetManager.TargetManager.instance()); Host.rnPerfMetrics.entryPointLoadingFinished('rn_fusebox');