Persist setting for flutter frames chart visibility and expose control in offline mode (#5339)

diff --git a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection.png b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection.png
index fb67d73..794a544 100644
--- a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection.png
+++ b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection.png
Binary files differ
diff --git a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection_2.png b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection_2.png
index fb67d73..794a544 100644
--- a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection_2.png
+++ b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_frame_selection_2.png
Binary files differ
diff --git a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_initial_load.png b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_initial_load.png
index 4ef07f0..475a9c9 100644
--- a/packages/devtools_app/integration_test/test_infra/goldens/perfetto_initial_load.png
+++ b/packages/devtools_app/integration_test/test_infra/goldens/perfetto_initial_load.png
Binary files differ
diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart
index e5ad6f8..af521bb 100644
--- a/packages/devtools_app/lib/src/app.dart
+++ b/packages/devtools_app/lib/src/app.dart
@@ -46,11 +46,11 @@
 import 'shared/console/primitives/simple_items.dart';
 import 'shared/dialogs.dart';
 import 'shared/globals.dart';
+import 'shared/offline_screen.dart';
 import 'shared/primitives/auto_dispose.dart';
 import 'shared/primitives/utils.dart';
 import 'shared/routing.dart';
 import 'shared/screen.dart';
-import 'shared/snapshot_screen.dart';
 import 'shared/theme.dart';
 import 'shared/ui/hover.dart';
 
@@ -277,13 +277,13 @@
       for (final screen in widget.screens)
         screen.screen.screenId: _buildTabbedPage,
       snapshotPageId: (_, __, args, ___) {
-        final snapshotArgs = SnapshotArguments.fromArgs(args);
+        final snapshotArgs = OfflineDataArguments.fromArgs(args);
         return DevToolsScaffold.withChild(
           key: UniqueKey(),
           ideTheme: ideTheme,
           child: MultiProvider(
             providers: _providedControllers(offline: true),
-            child: SnapshotScreenBody(snapshotArgs, _screens),
+            child: OfflineScreenBody(snapshotArgs, _screens),
           ),
         );
       },
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
index 15c1eed..feb20d9 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
@@ -13,7 +13,7 @@
 import '../../../../../shared/globals.dart';
 import '../../../../../shared/primitives/auto_dispose.dart';
 import '../../../../../shared/theme.dart';
-import '../../../performance_screen.dart';
+import '../performance_controls.dart';
 import 'enhance_tracing_controller.dart';
 
 class EnhanceTracingButton extends StatelessWidget {
@@ -37,7 +37,7 @@
       icon: icon,
       tooltip: 'Add more detail to the Timeline trace',
       minScreenWidthForTextBeforeScaling:
-          SecondaryPerformanceControls.minScreenWidthForTextBeforeScaling,
+          PerformanceControls.minScreenWidthForTextBeforeScaling,
       extensions: enhanceTracingExtensions,
       forceShowOverlayController:
           enhanceTracingController.showMenuStreamController,
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/controls/more_debugging_options.dart b/packages/devtools_app/lib/src/screens/performance/panes/controls/more_debugging_options.dart
index 0b0f85d..eec753b 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/controls/more_debugging_options.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/controls/more_debugging_options.dart
@@ -9,7 +9,7 @@
 import '../../../../shared/feature_flags.dart';
 import '../../../../shared/globals.dart';
 import '../../../../shared/theme.dart';
-import '../../performance_screen.dart';
+import 'performance_controls.dart';
 
 class MoreDebuggingOptionsButton extends StatelessWidget {
   const MoreDebuggingOptionsButton({Key? key}) : super(key: key);
@@ -23,7 +23,7 @@
       icon: Icons.build,
       tooltip: 'Opens a list of options you can use to help debug performance',
       minScreenWidthForTextBeforeScaling:
-          SecondaryPerformanceControls.minScreenWidthForTextBeforeScaling,
+          PerformanceControls.minScreenWidthForTextBeforeScaling,
       extensions: [
         extensions.disableClipLayers,
         extensions.disableOpacityLayers,
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/controls/performance_controls.dart b/packages/devtools_app/lib/src/screens/performance/panes/controls/performance_controls.dart
new file mode 100644
index 0000000..8e25f66
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/performance/panes/controls/performance_controls.dart
@@ -0,0 +1,167 @@
+// Copyright 2019 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 'dart:async';
+
+import 'package:flutter/material.dart';
+
+import '../../../../service/service_extension_widgets.dart';
+import '../../../../service/service_extensions.dart' as extensions;
+import '../../../../shared/analytics/analytics.dart' as ga;
+import '../../../../shared/analytics/constants.dart' as gac;
+import '../../../../shared/common_widgets.dart';
+import '../../../../shared/globals.dart';
+import '../../../../shared/theme.dart';
+import '../../panes/timeline_events/timeline_events_controller.dart';
+import '../../performance_controller.dart';
+import 'enhance_tracing/enhance_tracing.dart';
+import 'more_debugging_options.dart';
+import 'performance_settings.dart';
+
+class PerformanceControls extends StatelessWidget {
+  const PerformanceControls({
+    required this.controller,
+    required this.onClear,
+  });
+
+  static const minScreenWidthForTextBeforeScaling = 920.0;
+
+  final PerformanceController controller;
+
+  final VoidCallback onClear;
+
+  @override
+  Widget build(BuildContext context) {
+    return OfflineAwareControls(
+      controlsBuilder: (offline) {
+        return Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            ValueListenableBuilder<EventsControllerStatus>(
+              valueListenable: controller.timelineEventsController.status,
+              builder: (context, status, _) {
+                return _PrimaryControls(
+                  controller: controller,
+                  processing: status == EventsControllerStatus.processing,
+                  offline: offline,
+                  onClear: onClear,
+                );
+              },
+            ),
+            if (!offline)
+              Padding(
+                padding: const EdgeInsets.only(left: defaultSpacing),
+                child: _SecondaryPerformanceControls(controller: controller),
+              ),
+          ],
+        );
+      },
+    );
+  }
+}
+
+class _PrimaryControls extends StatelessWidget {
+  const _PrimaryControls({
+    Key? key,
+    required this.controller,
+    required this.processing,
+    required this.offline,
+    required this.onClear,
+  }) : super(key: key);
+
+  final PerformanceController controller;
+
+  final bool processing;
+
+  final bool offline;
+
+  final VoidCallback onClear;
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        if (serviceManager.connectedApp!.isFlutterAppNow!) ...[
+          VisibilityButton(
+            show: preferences.performance.showFlutterFramesChart,
+            onPressed:
+                controller.flutterFramesController.toggleShowFlutterFrames,
+            label: 'Flutter frames',
+            tooltip: 'Toggle visibility of the Flutter frames chart',
+          ),
+          const SizedBox(width: denseSpacing),
+        ],
+        if (!offline)
+          OutlinedIconButton(
+            icon: Icons.block,
+            tooltip: 'Clear all data on the Performance screen',
+            onPressed: processing ? null : _clearPerformanceData,
+          ),
+      ],
+    );
+  }
+
+  Future<void> _clearPerformanceData() async {
+    ga.select(gac.performance, gac.clear);
+    await controller.clearData();
+    onClear();
+  }
+}
+
+class _SecondaryPerformanceControls extends StatelessWidget {
+  const _SecondaryPerformanceControls({
+    Key? key,
+    required this.controller,
+  }) : super(key: key);
+
+  final PerformanceController controller;
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.end,
+      children: [
+        if (serviceManager.connectedApp!.isFlutterAppNow!) ...[
+          ServiceExtensionButtonGroup(
+            minScreenWidthForTextBeforeScaling:
+                PerformanceControls.minScreenWidthForTextBeforeScaling,
+            extensions: [
+              extensions.performanceOverlay,
+            ],
+          ),
+          const SizedBox(width: denseSpacing),
+          EnhanceTracingButton(controller.enhanceTracingController),
+          const SizedBox(width: denseSpacing),
+          const MoreDebuggingOptionsButton(),
+        ],
+        const SizedBox(width: defaultSpacing),
+        OutlinedIconButton(
+          icon: Icons.file_download,
+          tooltip: 'Export data',
+          onPressed: _exportPerformanceData,
+        ),
+        const SizedBox(width: denseSpacing),
+        SettingsOutlinedButton(
+          onPressed: () => _openSettingsDialog(context),
+        ),
+      ],
+    );
+  }
+
+  void _exportPerformanceData() {
+    ga.select(gac.performance, gac.export);
+    controller.exportData();
+    // TODO(kenz): investigate if we need to do any error handling here. Is the
+    // download always successful?
+  }
+
+  void _openSettingsDialog(BuildContext context) {
+    unawaited(
+      showDialog(
+        context: context,
+        builder: (context) => PerformanceSettingsDialog(controller),
+      ),
+    );
+  }
+}
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
index 2ccef1b..943249a 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart
@@ -49,7 +49,7 @@
       secondListenable: framesController.displayRefreshRate,
       builder: (context, frames, displayRefreshRate, child) {
         return ValueListenableBuilder<bool>(
-          valueListenable: framesController.showFlutterFramesChart,
+          valueListenable: preferences.performance.showFlutterFramesChart,
           builder: (context, show, _) {
             return _FlutterFramesChart(
               framesController: framesController,
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
index c9c16ad..f9ff023 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/flutter_frames/flutter_frames_controller.dart
@@ -28,12 +28,10 @@
   ValueListenable<List<FlutterFrame>> get flutterFrames => _flutterFrames;
   final _flutterFrames = ListValueNotifier<FlutterFrame>([]);
 
-  /// Whether we should show the Flutter frames chart.
-  ValueListenable<bool> get showFlutterFramesChart => _showFlutterFramesChart;
-  final _showFlutterFramesChart = ValueNotifier<bool>(true);
+  /// Controls the visibility of the Flutter frames chart.
   void toggleShowFlutterFrames(bool value) {
-    _showFlutterFramesChart.value = value;
-    unawaited(setIsActiveFeature(_showFlutterFramesChart.value));
+    preferences.performance.showFlutterFramesChart.value = value;
+    unawaited(setIsActiveFeature(value));
   }
 
   /// Whether flutter frames are currently being recorded.
@@ -93,7 +91,9 @@
   @override
   Future<void> setIsActiveFeature(bool value) async {
     final isFlutterApp = serviceManager.connectedApp?.isFlutterAppNow ?? false;
-    value = isFlutterApp && _showFlutterFramesChart.value;
+    final shouldShowFramesChart =
+        preferences.performance.showFlutterFramesChart.value;
+    value = isFlutterApp && shouldShowFramesChart;
     await super.setIsActiveFeature(value);
   }
 
diff --git a/packages/devtools_app/lib/src/screens/performance/performance_screen.dart b/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
index c76ece8..be3a149 100644
--- a/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
+++ b/packages/devtools_app/lib/src/screens/performance/performance_screen.dart
@@ -2,15 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:devtools_shared/devtools_shared.dart';
 import 'package:flutter/material.dart';
 
-import '../../service/service_extension_widgets.dart';
-import '../../service/service_extensions.dart' as extensions;
 import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
 import '../../shared/banner_messages.dart';
 import '../../shared/common_widgets.dart';
 import '../../shared/globals.dart';
@@ -20,11 +15,8 @@
 import '../../shared/theme.dart';
 import '../../shared/ui/icons.dart';
 import '../../shared/utils.dart';
-import 'panes/controls/enhance_tracing/enhance_tracing.dart';
-import 'panes/controls/more_debugging_options.dart';
-import 'panes/controls/performance_settings.dart';
+import 'panes/controls/performance_controls.dart';
 import 'panes/flutter_frames/flutter_frames_chart.dart';
-import 'panes/timeline_events/timeline_events_controller.dart';
 import 'performance_controller.dart';
 import 'tabbed_performance_view.dart';
 
@@ -112,7 +104,10 @@
             controller.offlinePerformanceData!.frames.isNotEmpty;
         return Column(
           children: [
-            if (!offlineMode) _buildPerformanceControls(),
+            PerformanceControls(
+              controller: controller,
+              onClear: () => setState(() {}),
+            ),
             const SizedBox(height: denseRowSpacing),
             if (isOfflineFlutterApp ||
                 (!offlineMode && serviceManager.connectedApp!.isFlutterAppNow!))
@@ -126,127 +121,4 @@
       },
     );
   }
-
-  Widget _buildPerformanceControls() {
-    return Row(
-      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-      children: [
-        ValueListenableBuilder<EventsControllerStatus>(
-          valueListenable: controller.timelineEventsController.status,
-          builder: (context, status, _) {
-            return _PrimaryControls(
-              controller: controller,
-              processing: status == EventsControllerStatus.processing,
-              onClear: () => setState(() {}),
-            );
-          },
-        ),
-        const SizedBox(width: defaultSpacing),
-        SecondaryPerformanceControls(controller: controller),
-      ],
-    );
-  }
-}
-
-class _PrimaryControls extends StatelessWidget {
-  const _PrimaryControls({
-    Key? key,
-    required this.controller,
-    required this.processing,
-    required this.onClear,
-  }) : super(key: key);
-
-  final PerformanceController controller;
-
-  final bool processing;
-
-  final VoidCallback onClear;
-
-  @override
-  Widget build(BuildContext context) {
-    return Row(
-      children: [
-        if (serviceManager.connectedApp!.isFlutterAppNow!) ...[
-          VisibilityButton(
-            show: controller.flutterFramesController.showFlutterFramesChart,
-            onPressed:
-                controller.flutterFramesController.toggleShowFlutterFrames,
-            label: 'Flutter frames',
-            tooltip: 'Toggle visibility of the Flutter frames chart',
-          ),
-          const SizedBox(width: denseSpacing),
-        ],
-        OutlinedIconButton(
-          icon: Icons.block,
-          tooltip: 'Clear all data on the Performance screen',
-          onPressed: processing ? null : _clearPerformanceData,
-        ),
-      ],
-    );
-  }
-
-  Future<void> _clearPerformanceData() async {
-    ga.select(gac.performance, gac.clear);
-    await controller.clearData();
-    onClear();
-  }
-}
-
-class SecondaryPerformanceControls extends StatelessWidget {
-  const SecondaryPerformanceControls({
-    Key? key,
-    required this.controller,
-  }) : super(key: key);
-
-  static const minScreenWidthForTextBeforeScaling = 920.0;
-
-  final PerformanceController controller;
-
-  @override
-  Widget build(BuildContext context) {
-    return Row(
-      mainAxisAlignment: MainAxisAlignment.end,
-      children: [
-        if (serviceManager.connectedApp!.isFlutterAppNow!) ...[
-          ServiceExtensionButtonGroup(
-            minScreenWidthForTextBeforeScaling:
-                minScreenWidthForTextBeforeScaling,
-            extensions: [
-              extensions.performanceOverlay,
-            ],
-          ),
-          const SizedBox(width: denseSpacing),
-          EnhanceTracingButton(controller.enhanceTracingController),
-          const SizedBox(width: denseSpacing),
-          const MoreDebuggingOptionsButton(),
-        ],
-        const SizedBox(width: defaultSpacing),
-        OutlinedIconButton(
-          icon: Icons.file_download,
-          tooltip: 'Export data',
-          onPressed: _exportPerformanceData,
-        ),
-        const SizedBox(width: denseSpacing),
-        SettingsOutlinedButton(
-          onPressed: () => _openSettingsDialog(context),
-        ),
-      ],
-    );
-  }
-
-  void _exportPerformanceData() {
-    ga.select(gac.performance, gac.export);
-    controller.exportData();
-    // TODO(kenz): investigate if we need to do any error handling here. Is the
-    // download always successful?
-  }
-
-  void _openSettingsDialog(BuildContext context) {
-    unawaited(
-      showDialog(
-        context: context,
-        builder: (context) => PerformanceSettingsDialog(controller),
-      ),
-    );
-  }
 }
diff --git a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
index a109fc9..044e5d9 100644
--- a/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
+++ b/packages/devtools_app/lib/src/screens/profiler/profiler_screen.dart
@@ -171,12 +171,12 @@
     );
     final profilerScreen = Column(
       children: [
-        if (!offlineController.offlineMode.value)
-          ProfilerScreenControls(
-            controller: controller,
-            recording: recording,
-            processing: processing,
-          ),
+        ProfilerScreenControls(
+          controller: controller,
+          recording: recording,
+          processing: processing,
+          offline: offlineController.offlineMode.value,
+        ),
         const SizedBox(height: denseRowSpacing),
         Expanded(
           child: OutlineDecoration(
@@ -261,6 +261,7 @@
     required this.controller,
     required this.recording,
     required this.processing,
+    required this.offline,
   });
 
   final ProfilerScreenController controller;
@@ -269,20 +270,29 @@
 
   final bool processing;
 
+  final bool offline;
+
   @override
   Widget build(BuildContext context) {
     return Row(
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
       children: [
-        _PrimaryControls(
-          controller: controller,
-          recording: recording,
-        ),
-        const SizedBox(width: defaultSpacing),
-        _SecondaryControls(
-          controller: controller,
-          profilerBusy: recording || processing,
-        ),
+        if (offline)
+          const Padding(
+            padding: EdgeInsets.only(right: defaultSpacing),
+            child: ExitOfflineButton(),
+          )
+        else ...[
+          _PrimaryControls(
+            controller: controller,
+            recording: recording,
+          ),
+          const SizedBox(width: defaultSpacing),
+          _SecondaryControls(
+            controller: controller,
+            profilerBusy: recording || processing,
+          ),
+        ],
       ],
     );
   }
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart
index 178e332..53d4ac9 100644
--- a/packages/devtools_app/lib/src/shared/analytics/constants.dart
+++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart
@@ -59,6 +59,7 @@
 const performanceOverlay = 'performanceOverlay';
 const performanceOverlayDocs = 'performanceOverlayDocs';
 const timelineFlameChartHelp = 'timelineFlameChartHelp';
+const framesChartVisibility = 'framesChartVisibility';
 const selectFlutterFrame = 'selectFlutterFrame';
 const traceEventProcessingTime = 'traceEventProcessingTime';
 const trackRebuilds = 'trackRebuilds';
@@ -79,7 +80,6 @@
 const intrinsicOperationsDocs = 'intrinsicOperationsDocs';
 const shaderCompilationDocs = 'shaderCompilationDocs';
 const shaderCompilationDocsTooltipLink = 'shaderCompilationDocsTooltipLink';
-const analyzeSelectedFrame = 'analyzeSelectedFrame';
 const collectRasterStats = 'collectRasterStats';
 const perfettoModeTraceEventProcessingTime =
     'traceEventProcessingTime-perfettoMode';
diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart
index 92d4f14..395f012 100644
--- a/packages/devtools_app/lib/src/shared/common_widgets.dart
+++ b/packages/devtools_app/lib/src/shared/common_widgets.dart
@@ -23,6 +23,7 @@
 import 'primitives/auto_dispose.dart';
 import 'primitives/flutter_widgets/linked_scroll_controller.dart';
 import 'primitives/utils.dart';
+import 'routing.dart';
 import 'theme.dart';
 import 'ui/icons.dart';
 import 'ui/label.dart';
@@ -717,21 +718,57 @@
 }
 
 /// Common button for exiting offline mode.
-///
-/// Consumers of this widget will be responsible for including the following in
-/// onPressed:
-///
-/// setState(() {
-///   offlineController.exitOfflineMode();
-/// }
-class ExitOfflineButton extends IconLabelButton {
-  const ExitOfflineButton({required VoidCallback onPressed})
-      : super(
-          key: const Key('exit offline button'),
-          onPressed: onPressed,
-          label: 'Exit offline mode',
-          icon: Icons.clear,
+class ExitOfflineButton extends StatelessWidget {
+  const ExitOfflineButton({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final routerDelegate = DevToolsRouterDelegate.of(context);
+    return IconLabelButton(
+      key: const Key('exit offline button'),
+      label: 'Exit offline mode',
+      icon: Icons.clear,
+      onPressed: () {
+        offlineController.exitOfflineMode();
+        // Use Router.neglect to replace the current history entry with
+        // the homepage so that clicking Back will not return here.
+        Router.neglect(
+          context,
+          () => routerDelegate.navigateHome(clearScreenParam: true),
         );
+      },
+    );
+  }
+}
+
+class OfflineAwareControls extends StatelessWidget {
+  const OfflineAwareControls({
+    required this.controlsBuilder,
+    super.key,
+  });
+
+  final Widget Function(bool) controlsBuilder;
+
+  @override
+  Widget build(BuildContext context) {
+    return ValueListenableBuilder<bool>(
+      valueListenable: offlineController.offlineMode,
+      builder: (context, offline, _) {
+        return Row(
+          children: [
+            if (offlineController.offlineMode.value)
+              const Padding(
+                padding: EdgeInsets.only(right: defaultSpacing),
+                child: ExitOfflineButton(),
+              ),
+            Expanded(
+              child: controlsBuilder(offline),
+            ),
+          ],
+        );
+      },
+    );
+  }
 }
 
 /// A small element containing some accessory information, often a numeric
diff --git a/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart b/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart
index 808d614..0f11b06 100644
--- a/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart
+++ b/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart
@@ -202,5 +202,6 @@
   void exitOfflineMode() {
     serviceManager.connectedApp = _previousConnectedApp;
     _offlineMode.value = false;
+    offlineDataJson.clear();
   }
 }
diff --git a/packages/devtools_app/lib/src/shared/offline_screen.dart b/packages/devtools_app/lib/src/shared/offline_screen.dart
new file mode 100644
index 0000000..eef0314
--- /dev/null
+++ b/packages/devtools_app/lib/src/shared/offline_screen.dart
@@ -0,0 +1,106 @@
+// Copyright 2019 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 'package:collection/collection.dart' show IterableExtension;
+import 'package:flutter/material.dart';
+
+import 'common_widgets.dart';
+import 'globals.dart';
+import 'primitives/auto_dispose.dart';
+import 'screen.dart';
+import 'theme.dart';
+
+/// The screen used for displaying offline data (data that does not require a
+/// connected app to explore).
+///
+/// This screen can be displayed both when DevTools is and is not connected to
+/// an app.
+class OfflineScreenBody extends StatefulWidget {
+  const OfflineScreenBody(this.args, this.possibleScreens);
+
+  final OfflineDataArguments args;
+
+  /// All possible screens, both visible and hidden, that DevTools was started
+  /// with.
+  ///
+  /// This will include screens that are only available when connected to an app
+  /// as well as screens that are only available based on the presence of a
+  /// conditional library.
+  ///
+  /// These screens are stored here so that we can import files for all screens,
+  /// regardless of whether an app is connected to DevTools or whether a
+  /// connected app contains the correct conditional library.
+  final List<Screen> possibleScreens;
+
+  @override
+  _OfflineScreenBodyState createState() => _OfflineScreenBodyState();
+}
+
+class _OfflineScreenBodyState extends State<OfflineScreenBody>
+    with AutoDisposeMixin {
+  Screen? _screen;
+
+  @override
+  void initState() {
+    super.initState();
+    _initScreen();
+    addAutoDisposeListener(offlineController.offlineMode, () {
+      if (!offlineController.offlineMode.value) {
+        setState(() {
+          _screen = null;
+        });
+      }
+    });
+  }
+
+  @override
+  void didUpdateWidget(OfflineScreenBody oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.args != oldWidget.args ||
+        widget.possibleScreens != oldWidget.possibleScreens) {
+      _initScreen();
+    }
+  }
+
+  void _initScreen() {
+    _screen = widget.possibleScreens.firstWhereOrNull(
+      (s) => s.screenId == widget.args.screenId,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _screen != null
+        ? _screen!.build(context)
+        : _OfflineLoadError(screenId: widget.args.screenId);
+  }
+}
+
+class _OfflineLoadError extends StatelessWidget {
+  const _OfflineLoadError({required this.screenId});
+
+  final String? screenId;
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: [
+        CenteredMessage(
+          'Cannot load offline data for screen \'$screenId\'',
+        ),
+        const SizedBox(height: defaultSpacing),
+        const ExitOfflineButton(),
+      ],
+    );
+  }
+}
+
+class OfflineDataArguments {
+  OfflineDataArguments(this.screenId);
+
+  OfflineDataArguments.fromArgs(Map<String, String?> args)
+      : this(args['screen']);
+
+  final String? screenId;
+}
diff --git a/packages/devtools_app/lib/src/shared/preferences.dart b/packages/devtools_app/lib/src/shared/preferences.dart
index 6a28366..ead7ca7 100644
--- a/packages/devtools_app/lib/src/shared/preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences.dart
@@ -34,6 +34,9 @@
   MemoryPreferencesController get memory => _memory;
   final _memory = MemoryPreferencesController();
 
+  PerformancePreferencesController get performance => _performance;
+  final _performance = PerformancePreferencesController();
+
   CpuProfilerPreferencesController get cpuProfiler => _cpuProfiler;
   final _cpuProfiler = CpuProfilerPreferencesController();
 
@@ -62,6 +65,7 @@
 
     await inspector.init();
     await memory.init();
+    await performance.init();
     await cpuProfiler.init();
 
     setGlobal(PreferencesController, this);
@@ -71,6 +75,8 @@
   void dispose() {
     inspector.dispose();
     memory.dispose();
+    performance.dispose();
+    cpuProfiler.dispose();
     super.dispose();
   }
 
@@ -412,3 +418,30 @@
         await storage.getValue(_displayTreeGuidelinesId) == 'true';
   }
 }
+
+class PerformancePreferencesController extends DisposableController
+    with AutoDisposeControllerMixin {
+  final showFlutterFramesChart = ValueNotifier<bool>(true);
+
+  static final _showFlutterFramesChartId =
+      '${gac.performance}.${gac.framesChartVisibility}';
+
+  Future<void> init() async {
+    addAutoDisposeListener(
+      showFlutterFramesChart,
+      () {
+        storage.setValue(
+          _showFlutterFramesChartId,
+          showFlutterFramesChart.value.toString(),
+        );
+        ga.select(
+          gac.performance,
+          gac.framesChartVisibility,
+          value: showFlutterFramesChart.value ? 1 : 0,
+        );
+      },
+    );
+    showFlutterFramesChart.value =
+        await storage.getValue(_showFlutterFramesChartId) != 'false';
+  }
+}
diff --git a/packages/devtools_app/lib/src/shared/snapshot_screen.dart b/packages/devtools_app/lib/src/shared/snapshot_screen.dart
deleted file mode 100644
index 5758203..0000000
--- a/packages/devtools_app/lib/src/shared/snapshot_screen.dart
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2019 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 'package:collection/collection.dart' show IterableExtension;
-import 'package:flutter/material.dart';
-
-import 'common_widgets.dart';
-import 'globals.dart';
-import 'routing.dart';
-import 'screen.dart';
-import 'theme.dart';
-
-/// The screen used for displaying a snapshot of imported data.
-///
-/// This screen can be displayed both when DevTools is and is not connected to
-/// an app.
-class SnapshotScreenBody extends StatefulWidget {
-  const SnapshotScreenBody(this.args, this.possibleScreens);
-
-  final SnapshotArguments args;
-
-  /// All possible screens, both visible and hidden, that DevTools was started
-  /// with.
-  ///
-  /// This will include screens that are only available when connected to an app
-  /// as well as screens that are only available based on the presence of a
-  /// conditional library.
-  ///
-  /// These screens are stored here so that we can import files for all screens,
-  /// regardless of whether an app is connected to DevTools or whether a
-  /// connected app contains the correct conditional library.
-  final List<Screen> possibleScreens;
-
-  @override
-  _SnapshotScreenBodyState createState() => _SnapshotScreenBodyState();
-}
-
-class _SnapshotScreenBodyState extends State<SnapshotScreenBody> {
-  Screen? _screen;
-
-  @override
-  void initState() {
-    super.initState();
-    _initScreen();
-  }
-
-  @override
-  void didChangeDependencies() {
-    super.didChangeDependencies();
-  }
-
-  @override
-  void didUpdateWidget(SnapshotScreenBody oldWidget) {
-    super.didUpdateWidget(oldWidget);
-    if (widget.args != oldWidget.args ||
-        widget.possibleScreens != oldWidget.possibleScreens) {
-      _initScreen();
-    }
-  }
-
-  void _initScreen() {
-    _screen = widget.possibleScreens.firstWhereOrNull(
-      (s) => s.screenId == widget.args.screenId,
-    );
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final routerDelegate = DevToolsRouterDelegate.of(context);
-    return Column(
-      children: [
-        Row(
-          children: [
-            ExitOfflineButton(
-              onPressed: () {
-                offlineController.exitOfflineMode();
-                reset();
-                // Use Router.neglect to replace the current history entry with
-                // the homepage so that clicking Back will not return here.
-                Router.neglect(
-                  context,
-                  () => routerDelegate.navigateHome(clearScreenParam: true),
-                );
-              },
-            ),
-          ],
-        ),
-        const SizedBox(height: denseRowSpacing),
-        Expanded(
-          child:
-              _screen != null ? _screen!.build(context) : _buildSnapshotError(),
-        ),
-      ],
-    );
-  }
-
-  Widget _buildSnapshotError() {
-    return CenteredMessage(
-      'Cannot load snapshot for screen \'${widget.args.screenId}\'',
-    );
-  }
-
-  void reset() {
-    setState(() {
-      offlineController.offlineDataJson.clear();
-      _screen = null;
-    });
-  }
-}
-
-class SnapshotArguments {
-  SnapshotArguments(this.screenId);
-
-  SnapshotArguments.fromArgs(Map<String, String?> args) : this(args['screen']);
-
-  final String? screenId;
-}
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index 2df6916..c6ba6a4 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -12,7 +12,7 @@
 TODO: Remove this section if there are not any general updates.
 
 ## Performance updates
-TODO: Remove this section if there are not any general updates.
+* Persist a user's preference for whether the Flutter Frames chart should be shown by default. - [#5339](https://github.com/flutter/devtools/pull/5339)
 
 ## CPU profiler updates
 * Add ability to inspect statistics for a CPU profile - [#5340](https://github.com/flutter/devtools/pull/5340)
diff --git a/packages/devtools_app/test/performance/enhance_tracing_test.dart b/packages/devtools_app/test/performance/controls/enhance_tracing_test.dart
similarity index 100%
rename from packages/devtools_app/test/performance/enhance_tracing_test.dart
rename to packages/devtools_app/test/performance/controls/enhance_tracing_test.dart
diff --git a/packages/devtools_app/test/performance/controls/performance_controls_test.dart b/packages/devtools_app/test/performance/controls/performance_controls_test.dart
new file mode 100644
index 0000000..421ee2b
--- /dev/null
+++ b/packages/devtools_app/test/performance/controls/performance_controls_test.dart
@@ -0,0 +1,109 @@
+// Copyright 2019 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 'package:devtools_app/devtools_app.dart';
+import 'package:devtools_app/src/screens/performance/panes/controls/performance_controls.dart';
+import 'package:devtools_app/src/shared/config_specific/import_export/import_export.dart';
+import 'package:devtools_test/devtools_test.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+void main() {
+  const windowSize = Size(3000.0, 1000.0);
+
+  setUp(() {
+    setGlobal(DevToolsExtensionPoints, ExternalDevToolsExtensionPoints());
+    setGlobal(IdeTheme, IdeTheme());
+    setGlobal(PreferencesController, PreferencesController());
+    setGlobal(OfflineModeController, OfflineModeController());
+    setGlobal(NotificationService, NotificationService());
+  });
+
+  group('$PerformanceControls', () {
+    late MockServiceConnectionManager mockServiceManager;
+    late MockPerformanceController mockPerformanceController;
+
+    setUp(() {
+      mockServiceManager = MockServiceConnectionManager();
+      when(mockServiceManager.serviceExtensionManager)
+          .thenReturn(FakeServiceExtensionManager());
+      final connectedApp = MockConnectedApp();
+      mockConnectedApp(
+        connectedApp,
+        isFlutterApp: true,
+        isProfileBuild: false,
+        isWebApp: false,
+      );
+      when(mockServiceManager.connectedApp).thenReturn(connectedApp);
+      setGlobal(ServiceConnectionManager, mockServiceManager);
+      mockPerformanceController = createMockPerformanceControllerWithDefaults();
+    });
+
+    tearDown(() {
+      offlineController.exitOfflineMode();
+    });
+
+    Future<void> _pumpControls(WidgetTester tester) async {
+      await tester.pumpWidget(
+        wrapWithControllers(
+          PerformanceControls(
+            controller: mockPerformanceController,
+            onClear: () {},
+          ),
+          performance: mockPerformanceController,
+        ),
+      );
+      await tester.pumpAndSettle();
+    }
+
+    testWidgetsWithWindowSize('builds for Flutter app', windowSize,
+        (WidgetTester tester) async {
+      await _pumpControls(tester);
+      expect(find.byType(ExitOfflineButton), findsNothing);
+      expect(find.byType(VisibilityButton), findsOneWidget);
+      expect(find.byIcon(Icons.block), findsOneWidget);
+      expect(find.text('Performance Overlay'), findsOneWidget);
+      expect(find.text('Enhance Tracing'), findsOneWidget);
+      expect(find.text('More debugging options'), findsOneWidget);
+      expect(find.byIcon(Icons.file_download), findsOneWidget);
+      expect(find.byIcon(Icons.settings), findsOneWidget);
+    });
+
+    testWidgetsWithWindowSize('builds for non flutter app', windowSize,
+        (WidgetTester tester) async {
+      mockConnectedApp(
+        mockServiceManager.connectedApp!,
+        isFlutterApp: false,
+        isProfileBuild: false,
+        isWebApp: false,
+      );
+      await _pumpControls(tester);
+
+      expect(find.byType(ExitOfflineButton), findsNothing);
+      expect(find.byType(VisibilityButton), findsNothing);
+      expect(find.byIcon(Icons.block), findsOneWidget);
+      expect(find.text('Performance Overlay'), findsNothing);
+      expect(find.text('Enhance Tracing'), findsNothing);
+      expect(find.text('More debugging options'), findsNothing);
+      expect(find.byIcon(Icons.file_download), findsOneWidget);
+      expect(find.byIcon(Icons.settings), findsOneWidget);
+    });
+
+    testWidgetsWithWindowSize('builds for offline mode', windowSize,
+        (WidgetTester tester) async {
+      offlineController.enterOfflineMode();
+      await _pumpControls(tester);
+      expect(find.byType(ExitOfflineButton), findsOneWidget);
+      expect(find.byType(VisibilityButton), findsOneWidget);
+      expect(find.byIcon(Icons.block), findsNothing);
+      expect(find.text('Performance Overlay'), findsNothing);
+      expect(find.text('Enhance Tracing'), findsNothing);
+      expect(find.text('More debugging options'), findsNothing);
+      expect(find.byIcon(Icons.file_download), findsNothing);
+      expect(find.byIcon(Icons.settings), findsNothing);
+      offlineController.exitOfflineMode();
+    });
+  });
+}
diff --git a/packages/devtools_app/test/performance/flutter_frames/flutter_frames_chart_test.dart b/packages/devtools_app/test/performance/flutter_frames/flutter_frames_chart_test.dart
index ee9e301..743303f 100644
--- a/packages/devtools_app/test/performance/flutter_frames/flutter_frames_chart_test.dart
+++ b/packages/devtools_app/test/performance/flutter_frames/flutter_frames_chart_test.dart
@@ -43,6 +43,8 @@
       setGlobal(OfflineModeController, OfflineModeController());
       setGlobal(IdeTheme, IdeTheme());
       setGlobal(NotificationService, NotificationService());
+      setGlobal(DevToolsExtensionPoints, ExternalDevToolsExtensionPoints());
+      setGlobal(PreferencesController, PreferencesController());
 
       framesController = FlutterFramesController(
         createMockPerformanceControllerWithDefaults(),
diff --git a/packages/devtools_app/test/performance/performance_controller_test.dart b/packages/devtools_app/test/performance/performance_controller_test.dart
index 32726cc..e187cc2 100644
--- a/packages/devtools_app/test/performance/performance_controller_test.dart
+++ b/packages/devtools_app/test/performance/performance_controller_test.dart
@@ -17,6 +17,8 @@
     setUp(() {
       setGlobal(IdeTheme, IdeTheme());
       setGlobal(OfflineModeController, OfflineModeController());
+      setGlobal(DevToolsExtensionPoints, ExternalDevToolsExtensionPoints());
+      setGlobal(PreferencesController, PreferencesController());
       mockServiceManager = MockServiceConnectionManager();
       final connectedApp = MockConnectedApp();
       mockConnectedApp(
diff --git a/packages/devtools_app/test/performance/performance_screen_test.dart b/packages/devtools_app/test/performance/performance_screen_test.dart
index 0aea004..c7d117e 100644
--- a/packages/devtools_app/test/performance/performance_screen_test.dart
+++ b/packages/devtools_app/test/performance/performance_screen_test.dart
@@ -6,10 +6,13 @@
 import 'dart:async';
 
 import 'package:devtools_app/devtools_app.dart';
+import 'package:devtools_app/src/screens/performance/panes/controls/performance_controls.dart';
 import 'package:devtools_app/src/screens/performance/panes/flutter_frames/flutter_frames_chart.dart';
 import 'package:devtools_app/src/screens/performance/panes/timeline_events/legacy/event_details.dart';
 import 'package:devtools_app/src/screens/performance/panes/timeline_events/legacy/timeline_flame_chart.dart';
+import 'package:devtools_app/src/screens/performance/tabbed_performance_view.dart';
 import 'package:devtools_app/src/shared/config_specific/import_export/import_export.dart';
+import 'package:devtools_shared/devtools_test_utils.dart';
 import 'package:devtools_test/devtools_test.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -19,66 +22,71 @@
 import '../test_infra/test_data/performance.dart';
 
 void main() {
-  setGlobal(DevToolsExtensionPoints, ExternalDevToolsExtensionPoints());
-  setGlobal(IdeTheme, IdeTheme());
-  setGlobal(PreferencesController, PreferencesController());
-  late PerformanceController controller;
-  late FakeServiceManager fakeServiceManager;
-
-  Future<void> _setUpServiceManagerWithTimeline(
-    Map<String, dynamic> timelineJson,
-  ) async {
-    fakeServiceManager = FakeServiceManager(
-      service: FakeServiceManager.createFakeService(
-        timelineData: vm_service.Timeline.parse(timelineJson),
-      ),
-    );
-    when(
-      fakeServiceManager.errorBadgeManager.errorCountNotifier('performance'),
-    ).thenReturn(ValueNotifier<int>(0));
-    final app = fakeServiceManager.connectedApp!;
-    when(app.initialized).thenReturn(Completer()..complete(true));
-    when(app.isDartWebAppNow).thenReturn(false);
-    when(app.isFlutterAppNow).thenReturn(true);
-    when(app.isProfileBuild).thenAnswer((_) => Future.value(false));
-    when(app.flutterVersionNow).thenReturn(
-      FlutterVersion.parse((await fakeServiceManager.flutterVersion).json!),
-    );
-    when(app.isDartCliAppNow).thenReturn(false);
-    when(app.isProfileBuildNow).thenReturn(true);
-    when(app.isDartWebApp).thenAnswer((_) async => false);
-    when(app.isProfileBuild).thenAnswer((_) async => false);
-    setGlobal(ServiceConnectionManager, fakeServiceManager);
-    setGlobal(NotificationService, NotificationService());
-    setGlobal(OfflineModeController, OfflineModeController());
-  }
-
-  Future<void> pumpPerformanceScreen(
-    WidgetTester tester, {
-    bool runAsync = false,
-  }) async {
-    await tester.pumpWidget(
-      wrapWithControllers(
-        const PerformanceScreenBody(),
-        performance: controller,
-      ),
-    );
-    await tester.pumpAndSettle();
-
-    if (runAsync) {
-      // Await a small delay to allow the PerformanceController to complete
-      // initialization.
-      await Future.delayed(const Duration(seconds: 1));
-    }
-
-    expect(find.byType(PerformanceScreenBody), findsOneWidget);
-  }
-
   const windowSize = Size(3000.0, 1000.0);
 
-  group('PerformanceScreen', () {
+  setUp(() {
+    setGlobal(DevToolsExtensionPoints, ExternalDevToolsExtensionPoints());
+    setGlobal(IdeTheme, IdeTheme());
+    setGlobal(PreferencesController, PreferencesController());
+    setGlobal(OfflineModeController, OfflineModeController());
+    setGlobal(NotificationService, NotificationService());
+  });
+
+  group('$PerformanceScreen', () {
+    late PerformanceController controller;
+    late FakeServiceManager fakeServiceManager;
+
+    Future<void> _setUpServiceManagerWithTimeline(
+      Map<String, dynamic> timelineJson,
+    ) async {
+      fakeServiceManager = FakeServiceManager(
+        service: FakeServiceManager.createFakeService(
+          timelineData: vm_service.Timeline.parse(timelineJson),
+        ),
+      );
+      when(
+        fakeServiceManager.errorBadgeManager.errorCountNotifier('performance'),
+      ).thenReturn(ValueNotifier<int>(0));
+      final app = fakeServiceManager.connectedApp!;
+      when(app.initialized).thenReturn(Completer()..complete(true));
+      when(app.isDartWebAppNow).thenReturn(false);
+      when(app.isFlutterAppNow).thenReturn(true);
+      when(app.isProfileBuild).thenAnswer((_) => Future.value(false));
+      when(app.flutterVersionNow).thenReturn(
+        FlutterVersion.parse((await fakeServiceManager.flutterVersion).json!),
+      );
+      when(app.isDartCliAppNow).thenReturn(false);
+      when(app.isProfileBuildNow).thenReturn(true);
+      when(app.isDartWebApp).thenAnswer((_) async => false);
+      when(app.isProfileBuild).thenAnswer((_) async => false);
+      setGlobal(ServiceConnectionManager, fakeServiceManager);
+    }
+
+    Future<void> pumpPerformanceScreen(
+      WidgetTester tester, {
+      bool runAsync = false,
+    }) async {
+      await tester.pumpWidget(
+        wrapWithControllers(
+          const PerformanceScreenBody(),
+          performance: controller,
+        ),
+      );
+      await tester.pumpAndSettle();
+
+      if (runAsync) {
+        // Await a small delay to allow the PerformanceController to complete
+        // initialization.
+        await Future.delayed(const Duration(seconds: 1));
+      }
+
+      expect(find.byType(PerformanceScreenBody), findsOneWidget);
+    }
+
     setUp(() async {
+      preferences.performance.showFlutterFramesChart.value = true;
       await _setUpServiceManagerWithTimeline(testTimelineJson);
+      await shortDelay();
       controller = PerformanceController();
       await controller.initialized;
     });
@@ -99,18 +107,13 @@
       await tester.runAsync(() async {
         await pumpPerformanceScreen(tester, runAsync: true);
         await tester.pumpAndSettle();
+        expect(find.byType(PerformanceControls), findsOneWidget);
         expect(find.byType(FlutterFramesChart), findsOneWidget);
+        expect(find.byType(TabbedPerformanceView), findsOneWidget);
         expect(
           find.text('Select a frame above to view analysis data.'),
           findsOneWidget,
         );
-        expect(find.byType(VisibilityButton), findsOneWidget);
-        expect(find.byIcon(Icons.block), findsOneWidget);
-        expect(find.text('Performance Overlay'), findsOneWidget);
-        expect(find.text('Enhance Tracing'), findsOneWidget);
-        expect(find.text('More debugging options'), findsOneWidget);
-        expect(find.byIcon(Icons.file_download), findsOneWidget);
-        expect(find.byIcon(Icons.settings), findsOneWidget);
       });
     });
 
@@ -126,6 +129,7 @@
         );
         await pumpPerformanceScreen(tester, runAsync: true);
         await tester.pumpAndSettle();
+        expect(find.byType(PerformanceControls), findsOneWidget);
         expect(find.byType(FlutterFramesChart), findsNothing);
         expect(find.byType(TimelineFlameChart), findsOneWidget);
         expect(
@@ -133,13 +137,6 @@
           findsNothing,
         );
         expect(find.byType(EventDetails), findsOneWidget);
-        expect(find.byType(VisibilityButton), findsNothing);
-        expect(find.byIcon(Icons.block), findsOneWidget);
-        expect(find.text('Performance Overlay'), findsNothing);
-        expect(find.text('Enhance Tracing'), findsNothing);
-        expect(find.text('More debugging options'), findsNothing);
-        expect(find.byIcon(Icons.file_download), findsOneWidget);
-        expect(find.byIcon(Icons.settings), findsOneWidget);
 
         // Verify the state of the splitter.
         final splitFinder = find.byType(Split);
@@ -149,7 +146,7 @@
       });
     });
 
-    group('Performance controls', () {
+    group('controls', () {
       testWidgetsWithWindowSize(
           'can expand and collapse flutter frames chart', windowSize,
           (WidgetTester tester) async {
@@ -163,7 +160,7 @@
           // The flutter frames chart is visible.
           expect(find.byType(FramesChartControls), findsOneWidget);
           expect(
-            controller.flutterFramesController.showFlutterFramesChart.value,
+            preferences.performance.showFlutterFramesChart.value,
             isTrue,
           );
 
@@ -173,7 +170,7 @@
           // The flutter frames chart should no longer be visible.
           expect(find.byType(FramesChartControls), findsNothing);
           expect(
-            controller.flutterFramesController.showFlutterFramesChart.value,
+            preferences.performance.showFlutterFramesChart.value,
             isFalse,
           );
 
@@ -183,7 +180,7 @@
           // The flutter frames chart should be visible again.
           expect(find.byType(FramesChartControls), findsOneWidget);
           expect(
-            controller.flutterFramesController.showFlutterFramesChart.value,
+            preferences.performance.showFlutterFramesChart.value,
             isTrue,
           );
         });
diff --git a/packages/devtools_app/test/shared/preferences_controller_test.dart b/packages/devtools_app/test/shared/preferences_controller_test.dart
index b9da825..a7f0e6c 100644
--- a/packages/devtools_app/test/shared/preferences_controller_test.dart
+++ b/packages/devtools_app/test/shared/preferences_controller_test.dart
@@ -221,4 +221,55 @@
       );
     });
   });
+
+  group('$PerformancePreferencesController', () {
+    late PerformancePreferencesController controller;
+    late FlutterTestStorage storage;
+
+    setUp(() async {
+      setGlobal(Storage, storage = FlutterTestStorage());
+      controller = PerformancePreferencesController();
+      await controller.init();
+    });
+
+    test('has expected default values', () async {
+      expect(controller.showFlutterFramesChart.value, isTrue);
+    });
+
+    test('stores values and reads them on init', () async {
+      storage.values.clear();
+
+      // Remember original values.
+      final showFramesChart = controller.showFlutterFramesChart.value;
+
+      // Flip the values in controller.
+      controller.showFlutterFramesChart.value = !showFramesChart;
+
+      // Check the values are stored.
+      expect(storage.values, hasLength(1));
+
+      // Reload the values from storage.
+      await controller.init();
+
+      // Check they did not change back to default.
+      expect(
+        controller.showFlutterFramesChart.value,
+        !showFramesChart,
+      );
+
+      // Flip the values in storage.
+      for (var key in storage.values.keys) {
+        storage.values[key] = (!(storage.values[key] == 'true')).toString();
+      }
+
+      // Reload the values from storage.
+      await controller.init();
+
+      // Check they flipped values are loaded.
+      expect(
+        controller.showFlutterFramesChart.value,
+        showFramesChart,
+      );
+    });
+  });
 }
diff --git a/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart b/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart
index 8f2951c..3af5654 100644
--- a/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart
+++ b/packages/devtools_test/lib/src/mocks/fake_vm_service_wrapper.dart
@@ -403,8 +403,7 @@
   ) async {
     final httpProfile = await getHttpProfile(isolateId);
     return Future.value(
-      httpProfile.requests
-          .firstWhereOrNull((request) => request.id == id),
+      httpProfile.requests.firstWhereOrNull((request) => request.id == id),
     );
   }
 
diff --git a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart
index a731892..f31922b 100644
--- a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart
+++ b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart
@@ -43,6 +43,9 @@
       .thenReturn(ValueNotifier<bool>(true));
   when(timelineEventsController.legacyController)
       .thenReturn(legacyTimelineEventsController);
+  when(timelineEventsController.status).thenReturn(
+    ValueNotifier<EventsControllerStatus>(EventsControllerStatus.empty),
+  );
   when(legacyTimelineEventsController.searchMatches)
       .thenReturn(const FixedValueListenable<List<TimelineEvent>>([]));
   when(legacyTimelineEventsController.searchInProgressNotifier)