From 529e4e009ddbd39dd98980f91b2818d83103b953 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 5 Aug 2025 16:16:31 +0100 Subject: [PATCH] feat: add React Native-only event for pre-enabling multiple layers of Tracing --- .../core/sdk/ReactNativeApplicationModel.ts | 6 +++ .../generated/InspectorBackendCommands.js | 1 + front_end/generated/protocol-mapping.d.ts | 4 ++ front_end/generated/protocol-proxy-api.d.ts | 5 +++ front_end/models/trace/TracingManager.ts | 5 +++ .../panels/timeline/TimelineController.ts | 12 ++++++ front_end/panels/timeline/TimelinePanel.ts | 38 +++++++++++++++++++ .../devtools_protocol/browser_protocol.json | 4 ++ .../react_native_domains.pdl | 3 ++ 9 files changed, 78 insertions(+) diff --git a/front_end/core/sdk/ReactNativeApplicationModel.ts b/front_end/core/sdk/ReactNativeApplicationModel.ts index 6987a6d67260..d07637f972c2 100644 --- a/front_end/core/sdk/ReactNativeApplicationModel.ts +++ b/front_end/core/sdk/ReactNativeApplicationModel.ts @@ -51,12 +51,18 @@ export class ReactNativeApplicationModel extends SDKModel implements this.metadataCached = metadata; this.dispatchEventToListeners(Events.METADATA_UPDATED, metadata); } + + traceRequested(): void { + this.dispatchEventToListeners(Events.TRACE_REQUESTED); + } } export const enum Events { METADATA_UPDATED = 'MetadataUpdated', + TRACE_REQUESTED = 'TraceRequested', } export interface EventTypes { [Events.METADATA_UPDATED]: Protocol.ReactNativeApplication.MetadataUpdatedEvent; + [Events.TRACE_REQUESTED]: void; } diff --git a/front_end/generated/InspectorBackendCommands.js b/front_end/generated/InspectorBackendCommands.js index 393d9fb4de14..89f917d25fec 100644 --- a/front_end/generated/InspectorBackendCommands.js +++ b/front_end/generated/InspectorBackendCommands.js @@ -42,6 +42,7 @@ export function registerCommands(inspectorBackend) { // ReactNativeApplication. inspectorBackend.registerEvent("ReactNativeApplication.metadataUpdated", ["appDisplayName", "appIdentifier", "deviceName", "integrationName", "platform", "reactNativeVersion", "unstable_isProfilingBuild", "unstable_networkInspectionEnabled"]); +inspectorBackend.registerEvent("ReactNativeApplication.traceRequested", []); inspectorBackend.registerCommand("ReactNativeApplication.disable", [], [], "Disables events from backend."); inspectorBackend.registerCommand("ReactNativeApplication.enable", [], [], "Enables events from backend."); diff --git a/front_end/generated/protocol-mapping.d.ts b/front_end/generated/protocol-mapping.d.ts index bef9cc4bca9a..8381d1ffa388 100644 --- a/front_end/generated/protocol-mapping.d.ts +++ b/front_end/generated/protocol-mapping.d.ts @@ -17,6 +17,10 @@ export namespace ProtocolMapping { * device, application, and debugger integration. */ 'ReactNativeApplication.metadataUpdated': [Protocol.ReactNativeApplication.MetadataUpdatedEvent]; + /** + * Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace. + */ + 'ReactNativeApplication.traceRequested': []; /** * The loadComplete event mirrors the load complete event sent by the browser to assistive * technology when the web page has finished loading. diff --git a/front_end/generated/protocol-proxy-api.d.ts b/front_end/generated/protocol-proxy-api.d.ts index 70ac8e308fdf..5260abd52b7f 100644 --- a/front_end/generated/protocol-proxy-api.d.ts +++ b/front_end/generated/protocol-proxy-api.d.ts @@ -255,6 +255,11 @@ declare namespace ProtocolProxyApi { */ metadataUpdated(params: Protocol.ReactNativeApplication.MetadataUpdatedEvent): void; + /** + * Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace. + */ + traceRequested(): void; + } export interface AccessibilityApi { diff --git a/front_end/models/trace/TracingManager.ts b/front_end/models/trace/TracingManager.ts index 09e93f6efc57..7826185e870d 100644 --- a/front_end/models/trace/TracingManager.ts +++ b/front_end/models/trace/TracingManager.ts @@ -99,6 +99,11 @@ export class TracingManager extends SDK.SDKModel.SDKModel { this.#finishing = true; void this.#tracingAgent.invoke_end(); } + + rnPrepareForTraceCapturedInBackground(client: TracingManagerClient): void { + this.#activeClient = client; + this.#finishing = true; + } } export interface TracingManagerClient { diff --git a/front_end/panels/timeline/TimelineController.ts b/front_end/panels/timeline/TimelineController.ts index 5421fb9cf077..98140d3124d5 100644 --- a/front_end/panels/timeline/TimelineController.ts +++ b/front_end/panels/timeline/TimelineController.ts @@ -202,6 +202,18 @@ export class TimelineController implements Trace.TracingManager.TracingManagerCl await LiveMetrics.LiveMetrics.instance().enable(); } + async rnPrepareForTraceCapturedInBackground(): Promise { + await LiveMetrics.LiveMetrics.instance().disable(); + + if (this.tracingManager) { + this.tracingManager.rnPrepareForTraceCapturedInBackground(this); + } + + this.client.loadingStarted(); + await this.allSourcesFinished(); + await LiveMetrics.LiveMetrics.instance().enable(); + } + private async fetchFieldData(): Promise { const cruxManager = CrUXManager.CrUXManager.instance(); if (!cruxManager.isEnabled() || !navigator.onLine) { diff --git a/front_end/panels/timeline/TimelinePanel.ts b/front_end/panels/timeline/TimelinePanel.ts index d56c048c7409..2ee9053cda2c 100644 --- a/front_end/panels/timeline/TimelinePanel.ts +++ b/front_end/panels/timeline/TimelinePanel.ts @@ -700,6 +700,19 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod this.#showLandingPage(); this.updateTimelineControls(); + if (isReactNative) { + SDK.TargetManager.TargetManager.instance().observeModels( + SDK.ReactNativeApplicationModel.ReactNativeApplicationModel, + { + modelAdded: (model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel) => { + model.addEventListener( + SDK.ReactNativeApplicationModel.Events.TRACE_REQUESTED, () => this.rnPrepareForTraceCapturedInBackground()); + }, + modelRemoved: (_model: SDK.ReactNativeApplicationModel.ReactNativeApplicationModel) => {}, + }, + ); + } + SDK.TargetManager.TargetManager.instance().addEventListener( SDK.TargetManager.Events.SUSPEND_STATE_CHANGED, this.onSuspendStateChanged, this); const profilerModels = SDK.TargetManager.TargetManager.instance().models(SDK.CPUProfilerModel.CPUProfilerModel); @@ -731,6 +744,31 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod }); } + private async rnPrepareForTraceCapturedInBackground(): Promise { + this.setUIControlsEnabled(false); + + if (this.statusPane) { + this.statusPane.finish(); + this.statusPane.updateStatus(i18nString(UIStrings.stoppingTimeline)); + this.statusPane.updateProgressBar(i18nString(UIStrings.received), 0); + } + this.setState(State.STOP_PENDING); + + const rootTarget = SDK.TargetManager.TargetManager.instance().rootTarget(); + if (!rootTarget) { + throw new Error('Could not load root target.'); + } + const primaryPageTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); + if (!primaryPageTarget) { + throw new Error('Could not load primary page target.'); + } + + this.controller = new TimelineController(rootTarget, primaryPageTarget, this); + await this.controller.rnPrepareForTraceCapturedInBackground(); + + this.setUIControlsEnabled(true); + } + #setActiveInsight(insight: TimelineComponents.Sidebar.ActiveInsight|null): void { // When an insight is selected, ensure that the 3P checkbox is disabled // to avoid dimming interference. diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.json b/third_party/blink/public/devtools_protocol/browser_protocol.json index bdd440edab0e..a4a0675ab6c7 100644 --- a/third_party/blink/public/devtools_protocol/browser_protocol.json +++ b/third_party/blink/public/devtools_protocol/browser_protocol.json @@ -71,6 +71,10 @@ "type": "boolean" } ] + }, + { + "name": "traceRequested", + "description": "Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace." } ] }, diff --git a/third_party/blink/public/devtools_protocol/react_native_domains.pdl b/third_party/blink/public/devtools_protocol/react_native_domains.pdl index 3e9bf7f24d67..a2c5f3559dca 100644 --- a/third_party/blink/public/devtools_protocol/react_native_domains.pdl +++ b/third_party/blink/public/devtools_protocol/react_native_domains.pdl @@ -30,3 +30,6 @@ experimental domain ReactNativeApplication optional boolean unstable_isProfilingBuild # Enables the Network Panel. optional boolean unstable_networkInspectionEnabled + + # Fired when React Native requests Chrome DevTools to prepare for displaying the captured Trace. + event traceRequested