diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index e714004eb6c..19d4084b012 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -701,6 +701,7 @@ grd_files_debug_sources = [ "front_end/core/host/InspectorFrontendHost.js", "front_end/core/host/InspectorFrontendHostAPI.js", "front_end/core/host/Platform.js", + "front_end/core/host/RNPerfMetrics.js", "front_end/core/host/ResourceLoader.js", "front_end/core/host/UserMetrics.js", "front_end/core/i18n/DevToolsLocale.js", diff --git a/front_end/core/host/BUILD.gn b/front_end/core/host/BUILD.gn index 0d9f614f4ab..1ac8bda8149 100644 --- a/front_end/core/host/BUILD.gn +++ b/front_end/core/host/BUILD.gn @@ -10,6 +10,7 @@ devtools_module("host") { "InspectorFrontendHost.ts", "InspectorFrontendHostAPI.ts", "Platform.ts", + "RNPerfMetrics.ts", "ResourceLoader.ts", "UserMetrics.ts", ] diff --git a/front_end/core/host/RNPerfMetrics.ts b/front_end/core/host/RNPerfMetrics.ts new file mode 100644 index 00000000000..104997934ab --- /dev/null +++ b/front_end/core/host/RNPerfMetrics.ts @@ -0,0 +1,73 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2020 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 type RNReliabilityEventListener = (event: ReactNativeChromeDevToolsEvent) => void; + +type UnsunscribeFn = () => void; +export type RNPerfMetrics = { + addEventListener: (listener: RNReliabilityEventListener) => UnsunscribeFn, + removeAllEventListeners: () => void, + sendEvent: (event: ReactNativeChromeDevToolsEvent) => void, +}; + +let instance: RNPerfMetrics|null = null; + +export function getInstance(): RNPerfMetrics { + if (instance === null) { + instance = new RNPerfMetricsImpl(); + } + return instance; +} + +class RNPerfMetricsImpl implements RNPerfMetrics { + #listeners: Set = new Set(); + + addEventListener(listener: RNReliabilityEventListener): () => void { + this.#listeners.add(listener); + + const unsubscribe = (): void => { + this.#listeners.delete(listener); + }; + + return unsubscribe; + } + + removeAllEventListeners(): void { + this.#listeners.clear(); + } + + sendEvent(event: ReactNativeChromeDevToolsEvent): void { + if (globalThis.enableReactNativePerfMetrics !== true) { + return; + } + + const errors = []; + for (const listener of this.#listeners) { + try { + listener(event); + } catch (e) { + errors.push(e); + } + } + + if (errors.length > 0) { + const error = new AggregateError(errors); + console.error('Error occurred when calling event listeners', error); + } + } +} + +export function registerPerfMetricsGlobalPostMessageHandler(): void { + if (globalThis.enableReactNativePerfMetrics !== true || + globalThis.enableReactNativePerfMetricsGlobalPostMessage !== true) { + return; + } + + getInstance().addEventListener(event => { + window.postMessage({event, tag: 'react-native-chrome-devtools-perf-metrics'}, window.location.origin); + }); +} + +export type ReactNativeChromeDevToolsEvent = {}; diff --git a/front_end/core/host/host.ts b/front_end/core/host/host.ts index d7823b710d2..b1e6c207e7e 100644 --- a/front_end/core/host/host.ts +++ b/front_end/core/host/host.ts @@ -6,6 +6,7 @@ import * as InspectorFrontendHost from './InspectorFrontendHost.js'; import * as InspectorFrontendHostAPI from './InspectorFrontendHostAPI.js'; import * as Platform from './Platform.js'; import * as ResourceLoader from './ResourceLoader.js'; +import * as RNPerfMetrics from './RNPerfMetrics.js'; import * as UserMetrics from './UserMetrics.js'; export { @@ -13,7 +14,9 @@ export { InspectorFrontendHostAPI, Platform, ResourceLoader, + RNPerfMetrics, UserMetrics, }; export const userMetrics = new UserMetrics.UserMetrics(); +export const rnPerfMetrics = RNPerfMetrics.getInstance(); diff --git a/front_end/entrypoint_template.html b/front_end/entrypoint_template.html index 986932cdf4f..a82ef64f563 100644 --- a/front_end/entrypoint_template.html +++ b/front_end/entrypoint_template.html @@ -17,6 +17,6 @@ - + diff --git a/front_end/entrypoints/rn_inspector/BUILD.gn b/front_end/entrypoints/rn_inspector/BUILD.gn index 9572f54bd77..8b2ec72c46b 100644 --- a/front_end/entrypoints/rn_inspector/BUILD.gn +++ b/front_end/entrypoints/rn_inspector/BUILD.gn @@ -1,3 +1,4 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. # Copyright 2021 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. diff --git a/front_end/entrypoints/rn_inspector/rn_inspector.ts b/front_end/entrypoints/rn_inspector/rn_inspector.ts index 10589b39375..8a2b40cf60c 100644 --- a/front_end/entrypoints/rn_inspector/rn_inspector.ts +++ b/front_end/entrypoints/rn_inspector/rn_inspector.ts @@ -14,9 +14,12 @@ import '../../panels/network/network-meta.js'; import '../../panels/js_profiler/js_profiler-meta.js'; import '../../panels/rn_welcome/rn_welcome-meta.js'; +import * as Host from '../../core/host/host.js'; import * as Root from '../../core/root/root.js'; import * as Main from '../main/main.js'; +Host.RNPerfMetrics.registerPerfMetricsGlobalPostMessageHandler(); + // Legacy JavaScript Profiler - we support this until Hermes can support the // modern Performance panel. Root.Runtime.experiments.register( diff --git a/front_end/global_typings/global_defs.d.ts b/front_end/global_typings/global_defs.d.ts index a6a85857eb3..add6d352822 100644 --- a/front_end/global_typings/global_defs.d.ts +++ b/front_end/global_typings/global_defs.d.ts @@ -3,6 +3,7 @@ // found in the LICENSE file. /// +/// interface CSSStyleSheet { replaceSync(content: string): void; diff --git a/front_end/global_typings/react_native.d.ts b/front_end/global_typings/react_native.d.ts new file mode 100644 index 00000000000..6734f5be5f9 --- /dev/null +++ b/front_end/global_typings/react_native.d.ts @@ -0,0 +1,13 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// Copyright 2020 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 {}; + +declare global { + namespace globalThis { + var enableReactNativePerfMetrics: boolean|undefined; + var enableReactNativePerfMetricsGlobalPostMessage: boolean|undefined; + } +} diff --git a/scripts/eslint_rules/lib/check_license_header.js b/scripts/eslint_rules/lib/check_license_header.js index 16517112aa3..ff028969730 100644 --- a/scripts/eslint_rules/lib/check_license_header.js +++ b/scripts/eslint_rules/lib/check_license_header.js @@ -69,6 +69,7 @@ const EXCLUDED_FILES = [ const META_CODE_PATHS = [ 'entrypoints/rn_inspector', 'panels/rn_welcome', + 'core/host/RNPerfMetrics.ts', ]; const OTHER_LICENSE_HEADERS = [ diff --git a/scripts/eslint_rules/lib/no_bound_component_methods.js b/scripts/eslint_rules/lib/no_bound_component_methods.js index ce8dd7ef46b..90d65f9f4c5 100644 --- a/scripts/eslint_rules/lib/no_bound_component_methods.js +++ b/scripts/eslint_rules/lib/no_bound_component_methods.js @@ -95,7 +95,7 @@ module.exports = { callExpressionNode) { const methodArg = callExpressionNode.arguments[1]; // Confirm that the argument is this.X, otherwise skip it - if (methodArg.type !== 'MemberExpression') { + if (methodArg?.type !== 'MemberExpression') { return; }