From ff7f376e95065dbc7bcbd48593d2b1be75cc72aa Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:05:33 +0000 Subject: [PATCH 1/2] feat(timeline): display newly supported options for Timeline (#224) --- front_end/panels/timeline/TimelinePanel.ts | 34 ++++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/front_end/panels/timeline/TimelinePanel.ts b/front_end/panels/timeline/TimelinePanel.ts index d0b05d2b8999..f49975849de5 100644 --- a/front_end/panels/timeline/TimelinePanel.ts +++ b/front_end/panels/timeline/TimelinePanel.ts @@ -573,7 +573,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod this.panelToolbar.wrappable = true; this.panelRightToolbar = timelineToolbarContainer.createChild('devtools-toolbar'); this.panelRightToolbar.role = 'presentation'; - if (!isNode && !isReactNative) { + if (!isNode) { this.createSettingsPane(); this.updateShowSettingsToolbarButton(); } @@ -1225,7 +1225,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod } // Settings - if (!isNode && !isReactNative) { + if (!isNode) { this.panelRightToolbar.appendSeparator(); this.panelRightToolbar.appendToolbarItem(this.showSettingsPaneButton); } @@ -1334,22 +1334,24 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod this.disableCaptureJSProfileSetting.title(), this.disableCaptureJSProfileSetting, i18nString(UIStrings.disablesJavascriptSampling))); - const cpuThrottlingPane = this.settingsPane.createChild('div'); - cpuThrottlingPane.append(i18nString(UIStrings.cpu)); - this.cpuThrottlingSelect = MobileThrottling.ThrottlingManager.throttlingManager().createCPUThrottlingSelector(); - cpuThrottlingPane.append(this.cpuThrottlingSelect.control.element); + if (!isReactNative) { + const cpuThrottlingPane = this.settingsPane.createChild('div'); + cpuThrottlingPane.append(i18nString(UIStrings.cpu)); + this.cpuThrottlingSelect = MobileThrottling.ThrottlingManager.throttlingManager().createCPUThrottlingSelector(); + cpuThrottlingPane.append(this.cpuThrottlingSelect.control.element); - this.settingsPane.append(UI.SettingsUI.createSettingCheckbox( - this.captureLayersAndPicturesSetting.title(), this.captureLayersAndPicturesSetting, - i18nString(UIStrings.capturesAdvancedPaint))); + this.settingsPane.append(UI.SettingsUI.createSettingCheckbox( + this.captureLayersAndPicturesSetting.title(), this.captureLayersAndPicturesSetting, + i18nString(UIStrings.capturesAdvancedPaint))); - const networkThrottlingPane = this.settingsPane.createChild('div'); - networkThrottlingPane.append(i18nString(UIStrings.network)); - networkThrottlingPane.append(this.createNetworkConditionsSelectToolbarItem().element); + const networkThrottlingPane = this.settingsPane.createChild('div'); + networkThrottlingPane.append(i18nString(UIStrings.network)); + networkThrottlingPane.append(this.createNetworkConditionsSelectToolbarItem().element); - this.settingsPane.append(UI.SettingsUI.createSettingCheckbox( - this.captureSelectorStatsSetting.title(), this.captureSelectorStatsSetting, - i18nString(UIStrings.capturesSelectorStats))); + this.settingsPane.append(UI.SettingsUI.createSettingCheckbox( + this.captureSelectorStatsSetting.title(), this.captureSelectorStatsSetting, + i18nString(UIStrings.capturesSelectorStats))); + } const thirdPartyCheckbox = this.createSettingCheckbox(this.#thirdPartyTracksSetting, i18nString(UIStrings.showDataAddedByExtensions)); @@ -1618,7 +1620,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod } private updateSettingsPaneVisibility(): void { - if (isNode || isReactNative) { + if (isNode) { return; } if (this.showSettingsPaneSetting.get()) { From 3f189fe12562226c57e450c158cbd03429e766b1 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 5 Mar 2026 18:27:35 +0000 Subject: [PATCH 2/2] Formalise ENABLE_TIMELINE_FRAMES experiment (#242) --- .../core/rn_experiments/experimentsImpl.ts | 6 ++++++ front_end/core/root/Runtime.ts | 2 ++ .../rn_fusebox/FuseboxFeatureObserver.ts | 20 ++++++++++++++++++- front_end/generated/protocol.ts | 5 +++++ .../TimelineFlameChartDataProvider.ts | 2 +- front_end/panels/timeline/TimelinePanel.ts | 2 +- front_end/testing/EnvironmentHelpers.ts | 1 + .../devtools_protocol/browser_protocol.json | 6 ++++++ .../react_native_domains.pdl | 2 ++ 9 files changed, 43 insertions(+), 3 deletions(-) diff --git a/front_end/core/rn_experiments/experimentsImpl.ts b/front_end/core/rn_experiments/experimentsImpl.ts index b0c216ba1691..0c7c2d415503 100644 --- a/front_end/core/rn_experiments/experimentsImpl.ts +++ b/front_end/core/rn_experiments/experimentsImpl.ts @@ -184,3 +184,9 @@ Instance.register({ unstable: false, enabledByDefault: ({ isReactNativeEntryPoint }) => isReactNativeEntryPoint, }); + +Instance.register({ + name: RNExperimentName.ENABLE_TIMELINE_FRAMES, + title: 'Enable performance frames track', + unstable: true, +}); diff --git a/front_end/core/root/Runtime.ts b/front_end/core/root/Runtime.ts index 2f72e40419bd..6c59a91c84c8 100644 --- a/front_end/core/root/Runtime.ts +++ b/front_end/core/root/Runtime.ts @@ -305,6 +305,7 @@ export const experiments = new ExperimentsSupport(); export enum RNExperimentName { REACT_NATIVE_SPECIFIC_UI = 'react-native-specific-ui', JS_HEAP_PROFILER_ENABLE = 'js-heap-profiler-enable', + ENABLE_TIMELINE_FRAMES = 'enable-timeline-frames', } export enum ConditionName { @@ -339,6 +340,7 @@ export const enum ExperimentName { JS_HEAP_PROFILER_ENABLE = RNExperimentName.JS_HEAP_PROFILER_ENABLE, REACT_NATIVE_SPECIFIC_UI = RNExperimentName.REACT_NATIVE_SPECIFIC_UI, NOT_REACT_NATIVE_SPECIFIC_UI = '!' + RNExperimentName.REACT_NATIVE_SPECIFIC_UI, + ENABLE_TIMELINE_FRAMES = RNExperimentName.ENABLE_TIMELINE_FRAMES, } export enum GenAiEnterprisePolicyValue { diff --git a/front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.ts b/front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.ts index 4587238338e7..7e9dd3525895 100644 --- a/front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.ts +++ b/front_end/entrypoints/rn_fusebox/FuseboxFeatureObserver.ts @@ -29,6 +29,12 @@ const UIStrings = { * @description Title shown when a feature is unavailable due to multiple React Native hosts. */ multiHostFeatureUnavailableTitle: 'Feature is unavailable', + /** + * @description Message for the "settings changed" banner shown when a reload + * is required for frame timings in the Performance panel. + */ + reloadRequiredForTimelineFramesMessage: + 'Frame timings and screenshots are now available in the Performance panel. Please reload to enable.', /** * @description Detail message shown when a feature is disabled due to multiple React Native hosts. */ @@ -75,7 +81,7 @@ export class FuseboxFeatureObserver implements #handleMetadataUpdated( event: Common.EventTarget.EventTargetEvent): void { // eslint-disable-next-line @typescript-eslint/naming-convention - const {unstable_isProfilingBuild, unstable_networkInspectionEnabled} = event.data; + const {unstable_isProfilingBuild, unstable_networkInspectionEnabled, unstable_frameRecordingEnabled} = event.data; if (unstable_isProfilingBuild) { FuseboxWindowTitleManager.instance().setSuffix('[PROFILING]'); @@ -87,6 +93,10 @@ export class FuseboxFeatureObserver implements if (!unstable_networkInspectionEnabled && !Root.Runtime.conditions.reactNativeExpoNetworkPanel()) { this.#hideNetworkPanel(); } + + if (unstable_frameRecordingEnabled) { + void this.#ensureTimelineFramesEnabled(); + } } #handleSystemStateChanged( @@ -132,6 +142,14 @@ export class FuseboxFeatureObserver implements }); } + async #ensureTimelineFramesEnabled(): Promise { + if (!Root.Runtime.experiments.isEnabled(Root.Runtime.RNExperimentName.ENABLE_TIMELINE_FRAMES)) { + Root.Runtime.experiments.setEnabled(Root.Runtime.RNExperimentName.ENABLE_TIMELINE_FRAMES, true); + UI.InspectorView?.InspectorView?.instance()?.displayReloadRequiredWarning( + i18nString(UIStrings.reloadRequiredForTimelineFramesMessage)); + } + } + #disableSingleHostOnlyFeatures(): void { if (this.#singleHostFeaturesDisabled) { return; diff --git a/front_end/generated/protocol.ts b/front_end/generated/protocol.ts index cf8325c6e0ca..5b88cf57fd4a 100644 --- a/front_end/generated/protocol.ts +++ b/front_end/generated/protocol.ts @@ -61,6 +61,11 @@ export namespace ReactNativeApplication { * Enables the Network Panel. */ unstable_networkInspectionEnabled?: boolean; + /** + * Whether Frame Timings and screenshots are supported in performance + * traces. + */ + unstable_frameRecordingEnabled?: boolean; } /** diff --git a/front_end/panels/timeline/TimelineFlameChartDataProvider.ts b/front_end/panels/timeline/TimelineFlameChartDataProvider.ts index 7c7e281d8d92..0e11347f5913 100644 --- a/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +++ b/front_end/panels/timeline/TimelineFlameChartDataProvider.ts @@ -615,7 +615,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW // In CPU Profiles the trace data will not have frames nor // screenshots, so we can keep this call as it will be a no-op in // these cases. - if (!this.isReactNative) { + if (Root.Runtime.experiments.isEnabled(Root.Runtime.RNExperimentName.ENABLE_TIMELINE_FRAMES) || !this.isReactNative) { this.#appendFramesAndScreenshotsTrack(); } diff --git a/front_end/panels/timeline/TimelinePanel.ts b/front_end/panels/timeline/TimelinePanel.ts index f49975849de5..45e3c43a8ee2 100644 --- a/front_end/panels/timeline/TimelinePanel.ts +++ b/front_end/panels/timeline/TimelinePanel.ts @@ -1189,7 +1189,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod // View this.panelToolbar.appendSeparator(); - if (!isNode && !isReactNative) { + if (!isNode && (Root.Runtime.experiments.isEnabled(Root.Runtime.RNExperimentName.ENABLE_TIMELINE_FRAMES) || !isReactNative)) { this.showScreenshotsToolbarCheckbox = this.createSettingCheckbox(this.showScreenshotsSetting, i18nString(UIStrings.captureScreenshots)); this.panelToolbar.appendToolbarItem(this.showScreenshotsToolbarCheckbox); diff --git a/front_end/testing/EnvironmentHelpers.ts b/front_end/testing/EnvironmentHelpers.ts index cc0af871c944..211940a69fdc 100644 --- a/front_end/testing/EnvironmentHelpers.ts +++ b/front_end/testing/EnvironmentHelpers.ts @@ -130,6 +130,7 @@ const REGISTERED_EXPERIMENTS = [ Root.Runtime.ExperimentName.TIMELINE_ALTERNATIVE_NAVIGATION, Root.Runtime.ExperimentName.REACT_NATIVE_SPECIFIC_UI, Root.Runtime.ExperimentName.NOT_REACT_NATIVE_SPECIFIC_UI, + Root.Runtime.ExperimentName.ENABLE_TIMELINE_FRAMES, ]; export async function initializeGlobalVars({reset = true} = {}) { diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.json b/third_party/blink/public/devtools_protocol/browser_protocol.json index 6e66ec5277e1..6543679373e1 100644 --- a/third_party/blink/public/devtools_protocol/browser_protocol.json +++ b/third_party/blink/public/devtools_protocol/browser_protocol.json @@ -69,6 +69,12 @@ "description": "Enables the Network Panel.", "optional": true, "type": "boolean" + }, + { + "name": "unstable_frameRecordingEnabled", + "description": "Whether Frame Timings and screenshots are supported in performance traces.", + "optional": true, + "type": "boolean" } ] }, 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 03fe397e4067..7ad05f5148bc 100644 --- a/third_party/blink/public/devtools_protocol/react_native_domains.pdl +++ b/third_party/blink/public/devtools_protocol/react_native_domains.pdl @@ -30,6 +30,8 @@ experimental domain ReactNativeApplication optional boolean unstable_isProfilingBuild # Enables the Network Panel. optional boolean unstable_networkInspectionEnabled + # Whether Frame Timings and screenshots are supported in performance traces. + optional boolean unstable_frameRecordingEnabled # Emitted when assertions about the debugger backend have changed. event systemStateChanged