Bug 1302062 - Use React on performance recording list; r=jsantell
authorGreg Tatum <tatum.creative@gmail.com>
Tue, 20 Sep 2016 09:47:59 -0500
changeset 315366 c651f7174f37747de99eaafd27b2d78fb10bead3
parent 315365 8de3dbbe8e3a3a5cefaf224f8d2764c4998d1afb
child 315367 50143dbdcb47bf47c8827c8777b0e11e92e25418
push id30748
push usercbook@mozilla.com
push dateWed, 28 Sep 2016 13:53:19 +0000
treeherdermozilla-central@8c84b7618840 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell
bugs1302062
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1302062 - Use React on performance recording list; r=jsantell
devtools/client/performance/components/moz.build
devtools/client/performance/components/recording-list-item.js
devtools/client/performance/components/recording-list.js
devtools/client/performance/performance-controller.js
devtools/client/performance/performance.xul
devtools/client/performance/test/browser_perf-calltree-js-events.js
devtools/client/performance/test/browser_perf-console-record-01.js
devtools/client/performance/test/browser_perf-console-record-02.js
devtools/client/performance/test/browser_perf-console-record-03.js
devtools/client/performance/test/browser_perf-console-record-04.js
devtools/client/performance/test/browser_perf-console-record-05.js
devtools/client/performance/test/browser_perf-console-record-06.js
devtools/client/performance/test/browser_perf-console-record-07.js
devtools/client/performance/test/browser_perf-console-record-08.js
devtools/client/performance/test/browser_perf-details-03-without-allocations.js
devtools/client/performance/test/browser_perf-details-04-toolbar-buttons.js
devtools/client/performance/test/browser_perf-loading-01.js
devtools/client/performance/test/browser_perf-loading-02.js
devtools/client/performance/test/browser_perf-options-show-jit-optimizations.js
devtools/client/performance/test/browser_perf-overview-render-04.js
devtools/client/performance/test/browser_perf-recording-notices-02.js
devtools/client/performance/test/browser_perf-recording-notices-03.js
devtools/client/performance/test/browser_perf-recording-selected-01.js
devtools/client/performance/test/browser_perf-recording-selected-02.js
devtools/client/performance/test/browser_perf-recording-selected-03.js
devtools/client/performance/test/browser_perf-recording-selected-04.js
devtools/client/performance/test/browser_perf-recordings-clear-01.js
devtools/client/performance/test/browser_perf-recordings-clear-02.js
devtools/client/performance/test/browser_perf-tree-view-11.js
devtools/client/performance/test/helpers/moz.build
devtools/client/performance/test/helpers/recording-utils.js
devtools/client/performance/views/recordings.js
devtools/client/themes/performance.css
--- a/devtools/client/performance/components/moz.build
+++ b/devtools/client/performance/components/moz.build
@@ -3,15 +3,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'jit-optimizations-item.js',
     'jit-optimizations.js',
     'recording-button.js',
     'recording-controls.js',
+    'recording-list-item.js',
+    'recording-list.js',
     'waterfall-header.js',
     'waterfall-tree-row.js',
     'waterfall-tree.js',
     'waterfall.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/components/recording-list-item.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {DOM, createClass} = require("devtools/client/shared/vendor/react");
+const {div, li, span, button} = DOM;
+const {L10N} = require("devtools/client/performance/modules/global");
+
+module.exports = createClass({
+  displayName: "Recording List Item",
+
+  render() {
+    const {
+      label,
+      duration,
+      onSelect,
+      onSave,
+      isLoading,
+      isSelected,
+      isRecording
+    } = this.props;
+
+    const className = `recording-list-item ${isSelected ? "selected" : ""}`;
+
+    let durationText;
+    if (isLoading) {
+      durationText = L10N.getStr("recordingsList.loadingLabel");
+    } else if (isRecording) {
+      durationText = L10N.getStr("recordingsList.recordingLabel");
+    } else {
+      durationText = L10N.getFormatStr("recordingsList.durationLabel", duration);
+    }
+
+    return (
+      li({ className, onClick: onSelect },
+        div({ className: "recording-list-item-label" },
+          label
+        ),
+        div({ className: "recording-list-item-footer" },
+          span({ className: "recording-list-item-duration" }, durationText),
+          button({ className: "recording-list-item-save", onClick: onSave },
+            L10N.getStr("recordingsList.saveLabel")
+          )
+        )
+      )
+    );
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/components/recording-list.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {DOM, createClass} = require("devtools/client/shared/vendor/react");
+const {L10N} = require("devtools/client/performance/modules/global");
+const {ul, div} = DOM;
+
+module.exports = createClass({
+  displayName: "Recording List",
+
+  render() {
+    const {
+      items,
+      itemComponent: Item,
+    } = this.props;
+
+    return items.length > 0
+      ? ul({ className: "recording-list" }, ...items.map(Item))
+      : div({ className: "recording-list-empty" }, L10N.getStr("noRecordingsText"));
+  }
+});
--- a/devtools/client/performance/performance-controller.js
+++ b/devtools/client/performance/performance-controller.js
@@ -22,23 +22,26 @@ var { gDevTools } = require("devtools/cl
 var EVENTS = require("devtools/client/performance/events");
 Object.defineProperty(this, "EVENTS", {
   value: EVENTS,
   enumerable: true,
   writable: false
 });
 
 /* exported React, ReactDOM, JITOptimizationsView, RecordingControls, RecordingButton,
-   Waterfall, Services, promise, EventEmitter, DevToolsUtils, system */
+   RecordingList, RecordingListItem, Services, Waterfall, promise, EventEmitter,
+   DevToolsUtils, system */
 var React = require("devtools/client/shared/vendor/react");
 var ReactDOM = require("devtools/client/shared/vendor/react-dom");
 var Waterfall = React.createFactory(require("devtools/client/performance/components/waterfall"));
 var JITOptimizationsView = React.createFactory(require("devtools/client/performance/components/jit-optimizations"));
 var RecordingControls = React.createFactory(require("devtools/client/performance/components/recording-controls"));
 var RecordingButton = React.createFactory(require("devtools/client/performance/components/recording-button"));
+var RecordingList = React.createFactory(require("devtools/client/performance/components/recording-list"));
+var RecordingListItem = React.createFactory(require("devtools/client/performance/components/recording-list-item"));
 
 var Services = require("Services");
 var promise = require("promise");
 var EventEmitter = require("devtools/shared/event-emitter");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var flags = require("devtools/shared/flags");
 var system = require("devtools/shared/system");
 
--- a/devtools/client/performance/performance.xul
+++ b/devtools/client/performance/performance.xul
@@ -79,17 +79,19 @@
 
   <hbox id="body" class="theme-body performance-tool" flex="1">
 
     <!-- Sidebar: controls and recording list -->
     <vbox id="recordings-pane">
       <hbox id="recordings-controls">
         <html:div id='recording-controls-mount'/>
       </hbox>
-      <vbox id="recordings-list" class="theme-sidebar" flex="1"/>
+      <vbox id="recordings-list" class="theme-sidebar" flex="1">
+        <html:div id="recording-list-mount"/>
+      </vbox>
     </vbox>
 
     <!-- Main panel content -->
     <vbox id="performance-pane" flex="1">
 
       <!-- Top toolbar controls -->
       <toolbar id="performance-toolbar"
                class="devtools-toolbar">
--- a/devtools/client/performance/test/browser_perf-calltree-js-events.js
+++ b/devtools/client/performance/test/browser_perf-calltree-js-events.js
@@ -29,21 +29,28 @@ add_task(function* () {
   yield rendered;
 
   // Mock the profile used so we can get a deterministic tree created.
   let profile = synthesizeProfile();
   let threadNode = new ThreadNode(profile.threads[0], OverviewView.getTimeInterval());
   JsCallTreeView._populateCallTree(threadNode);
   JsCallTreeView.emit(EVENTS.UI_JS_CALL_TREE_RENDERED);
 
+  let firstTreeItem = $("#js-calltree-view .call-tree-item");
+
+  // DE-XUL: There are focus issues with XUL. Focus first, then synthesize the clicks
+  // so that keyboard events work correctly.
+  firstTreeItem.focus();
+
   let count = 0;
   let onFocus = () => count++;
   JsCallTreeView.on("focus", onFocus);
 
-  click($("#js-calltree-view .call-tree-item"));
+  click(firstTreeItem);
+
   key("VK_DOWN");
   key("VK_DOWN");
   key("VK_DOWN");
   key("VK_DOWN");
 
   JsCallTreeView.off("focus", onFocus);
   is(count, 4, "Several focus events are fired for the calltree.");
 
--- a/devtools/client/performance/test/browser_perf-console-record-01.js
+++ b/devtools/client/performance/test/browser_perf-console-record-01.js
@@ -5,36 +5,39 @@
 /**
  * Tests if the profiler is populated by console recordings that have finished
  * before it was opened.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitUntil } = require("devtools/client/performance/test/helpers/wait-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   yield console.profile("rust");
   yield console.profileEnd("rust");
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { PerformanceController, RecordingsView, WaterfallView } = panel.panelWin;
+  let { PerformanceController, WaterfallView } = panel.panelWin;
 
   yield waitUntil(() => PerformanceController.getRecordings().length == 1);
   yield waitUntil(() => WaterfallView.wasRenderedAtLeastOnce);
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 1, "One recording found in the performance panel.");
   is(recordings[0].isConsole(), true, "Recording came from console.profile.");
   is(recordings[0].getLabel(), "rust", "Correct label in the recording model.");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  const selected = getSelectedRecording(panel);
+
+  is(selected, recordings[0],
     "The profile from console should be selected as it's the only one.");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-console-record-02.js
+++ b/devtools/client/performance/test/browser_perf-console-record-02.js
@@ -8,43 +8,45 @@
  */
 
 const { Constants } = require("devtools/client/performance/modules/constants");
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { waitUntil } = require("devtools/client/performance/test/helpers/wait-utils");
 const { times } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   yield console.profile("rust");
   yield console.profile("rust2");
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { EVENTS, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
   yield waitUntil(() => PerformanceController.getRecordings().length == 2);
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
   is(recordings[0].isConsole(), true, "Recording came from console.profile (1).");
   is(recordings[0].getLabel(), "rust", "Correct label in the recording model (1).");
   is(recordings[0].isRecording(), true, "Recording is still recording (1).");
   is(recordings[1].isConsole(), true, "Recording came from console.profile (2).");
   is(recordings[1].getLabel(), "rust2", "Correct label in the recording model (2).");
   is(recordings[1].isRecording(), true, "Recording is still recording (2).");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  const selected = getSelectedRecording(panel);
+  is(selected, recordings[0],
     "The first console recording should be selected.");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
   let stopped = waitForRecordingStoppedEvents(panel, {
--- a/devtools/client/performance/test/browser_perf-console-record-03.js
+++ b/devtools/client/performance/test/browser_perf-console-record-03.js
@@ -6,45 +6,47 @@
  * Tests if the profiler is populated by in-progress console recordings, and
  * also console recordings that have finished before it was opened.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { waitUntil } = require("devtools/client/performance/test/helpers/wait-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   yield console.profile("rust");
   yield console.profileEnd("rust");
   yield console.profile("rust2");
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { PerformanceController, RecordingsView, WaterfallView } = panel.panelWin;
+  let { PerformanceController, WaterfallView } = panel.panelWin;
 
   yield waitUntil(() => PerformanceController.getRecordings().length == 2);
   yield waitUntil(() => WaterfallView.wasRenderedAtLeastOnce);
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
   is(recordings[0].isConsole(), true, "Recording came from console.profile (1).");
   is(recordings[0].getLabel(), "rust", "Correct label in the recording model (1).");
   is(recordings[0].isRecording(), false, "Recording is still recording (1).");
   is(recordings[1].isConsole(), true, "Recording came from console.profile (2).");
   is(recordings[1].getLabel(), "rust2", "Correct label in the recording model (2).");
   is(recordings[1].isRecording(), true, "Recording is still recording (2).");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  const selected = getSelectedRecording(panel);
+  is(selected, recordings[0],
     "The first console recording should be selected.");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   let stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when a finished recording is selected
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
--- a/devtools/client/performance/test/browser_perf-console-record-04.js
+++ b/devtools/client/performance/test/browser_perf-console-record-04.js
@@ -7,42 +7,44 @@
  * after being opened.
  */
 
 const { Constants } = require("devtools/client/performance/modules/constants");
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { times } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { EVENTS, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profile("rust");
   yield started;
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 1, "One recording found in the performance panel.");
   is(recordings[0].isConsole(), true, "Recording came from console.profile.");
   is(recordings[0].getLabel(), "rust", "Correct label in the recording model.");
   is(recordings[0].isRecording(), true, "Recording is still recording.");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  const selected = getSelectedRecording(panel);
+  is(selected, recordings[0],
     "The profile from console should be selected as it's the only one.");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
   let stopped = waitForRecordingStoppedEvents(panel, {
--- a/devtools/client/performance/test/browser_perf-console-record-05.js
+++ b/devtools/client/performance/test/browser_perf-console-record-05.js
@@ -7,42 +7,44 @@
  * in the recording list.
  */
 
 const { Constants } = require("devtools/client/performance/modules/constants");
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { times } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { EVENTS, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profile("rust");
   yield started;
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 1, "One recording found in the performance panel.");
   is(recordings[0].isConsole(), true, "Recording came from console.profile (1).");
   is(recordings[0].getLabel(), "rust", "Correct label in the recording model (1).");
   is(recordings[0].isRecording(), true, "Recording is still recording (1).");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  let selected = getSelectedRecording(panel);
+  is(selected, recordings[0],
     "The profile from console should be selected as it's the only one.");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
   let stopped = waitForRecordingStoppedEvents(panel, {
@@ -65,19 +67,20 @@ add_task(function* () {
   yield started;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
   is(recordings[1].isConsole(), true, "Recording came from console.profile (2).");
   is(recordings[1].getLabel(), "rust", "Correct label in the recording model (2).");
   is(recordings[1].isRecording(), true, "Recording is still recording (2).");
 
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  selected = getSelectedRecording(panel);
+  is(selected, recordings[0],
     "The profile from console should still be selected");
-  is(RecordingsView.selectedItem.attachment.getLabel(), "rust",
+  is(selected.getLabel(), "rust",
     "The profile label for the first recording is correct.");
 
   stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when a finished recording is selected
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
--- a/devtools/client/performance/test/browser_perf-console-record-06.js
+++ b/devtools/client/performance/test/browser_perf-console-record-06.js
@@ -6,36 +6,37 @@
  * Tests that console recordings can overlap (not completely nested).
  */
 
 const { Constants } = require("devtools/client/performance/modules/constants");
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { times } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { EVENTS, PerformanceController, OverviewView, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profile("rust");
   yield started;
 
   let recordings = PerformanceController.getRecordings();
   is(recordings.length, 1, "A recording found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(getSelectedRecording(panel), recordings[0],
     "The first console recording should be selected.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
   started = waitForRecordingStartedEvents(panel, {
@@ -47,49 +48,49 @@ add_task(function* () {
     // in-progress recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profile("golang");
   yield started;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(getSelectedRecording(panel), recordings[0],
     "The first console recording should still be selected.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
   let stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profileEnd("rust");
   yield stopped;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(getSelectedRecording(panel), recordings[0],
     "The first console recording should still be selected.");
   is(recordings[0].isRecording(), false,
     "The first console recording should no longer be recording.");
 
   stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when a finished recording is selected
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
   });
   yield console.profileEnd("golang");
   yield stopped;
 
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 2, "Two recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(getSelectedRecording(panel), recordings[0],
     "The first console recording should still be selected.");
   is(recordings[1].isRecording(), false,
     "The second console recording should no longer be recording.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-console-record-07.js
+++ b/devtools/client/performance/test/browser_perf-console-record-07.js
@@ -7,25 +7,26 @@
  * most recent console recording, and console.profileEnd() with a label that
  * does not match any pending recordings does nothing.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { idleWait } = require("devtools/client/performance/test/helpers/wait-utils");
+const { getSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { PerformanceController, RecordingsView } = panel.panelWin;
+  let { PerformanceController } = panel.panelWin;
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profile();
   yield started;
 
@@ -49,21 +50,22 @@ add_task(function* () {
     // the view state won't switch to "console-recording" unless the new
     // in-progress recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profile("2");
   yield started;
 
   let recordings = PerformanceController.getRecordings();
+  let selected = getSelectedRecording(panel);
   is(recordings.length, 3, "Three recordings found in the performance panel.");
   is(recordings[0].getLabel(), "", "Checking label of recording 1");
   is(recordings[1].getLabel(), "1", "Checking label of recording 2");
   is(recordings[2].getLabel(), "2", "Checking label of recording 3");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(selected, recordings[0],
     "The first console recording should be selected.");
 
   is(recordings[0].isRecording(), true,
     "All recordings should now be started. (1)");
   is(recordings[1].isRecording(), true,
     "All recordings should now be started. (2)");
   is(recordings[2].isRecording(), true,
     "All recordings should now be started. (3)");
@@ -76,35 +78,37 @@ add_task(function* () {
     skipWaitingForSubview: true,
     // the view state won't switch to "recorded" unless the new
     // finished recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profileEnd();
   yield stopped;
 
+  selected = getSelectedRecording(panel);
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 3, "Three recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(selected, recordings[0],
     "The first console recording should still be selected.");
 
   is(recordings[0].isRecording(), true, "The not most recent recording should not stop " +
     "when calling console.profileEnd with no args.");
   is(recordings[1].isRecording(), true, "The not most recent recording should not stop " +
     "when calling console.profileEnd with no args.");
   is(recordings[2].isRecording(), false, "Only the most recent recording should stop " +
     "when calling console.profileEnd with no args.");
 
   info("Trying to `profileEnd` a non-existent console recording.");
   console.profileEnd("fxos");
   yield idleWait(1000);
 
+  selected = getSelectedRecording(panel);
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 3, "Three recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(selected, recordings[0],
     "The first console recording should still be selected.");
 
   is(recordings[0].isRecording(), true,
     "The first recording should not be ended yet.");
   is(recordings[1].isRecording(), true,
     "The second recording should not be ended yet.");
   is(recordings[2].isRecording(), false,
     "The third recording should still be ended.");
@@ -117,38 +121,40 @@ add_task(function* () {
     skipWaitingForSubview: true,
     // the view state won't switch to "recorded" unless the new
     // finished recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profileEnd();
   yield stopped;
 
+  selected = getSelectedRecording(panel);
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 3, "Three recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(selected, recordings[0],
     "The first console recording should still be selected.");
 
   is(recordings[0].isRecording(), true,
     "The first recording should not be ended yet.");
   is(recordings[1].isRecording(), false,
     "The second recording should not be ended yet.");
   is(recordings[2].isRecording(), false,
     "The third recording should still be ended.");
 
   stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profileEnd();
   yield stopped;
 
+  selected = getSelectedRecording(panel);
   recordings = PerformanceController.getRecordings();
   is(recordings.length, 3, "Three recordings found in the performance panel.");
-  is(RecordingsView.selectedItem.attachment, recordings[0],
+  is(selected, recordings[0],
     "The first console recording should be selected.");
 
   is(recordings[0].isRecording(), false,
     "All recordings should now be ended. (1)");
   is(recordings[1].isRecording(), false,
     "All recordings should now be ended. (2)");
   is(recordings[2].isRecording(), false,
     "All recordings should now be ended. (3)");
--- a/devtools/client/performance/test/browser_perf-console-record-08.js
+++ b/devtools/client/performance/test/browser_perf-console-record-08.js
@@ -8,104 +8,164 @@
  */
 
 const { Constants } = require("devtools/client/performance/modules/constants");
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 const { once, times } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
+
+/**
+ * The following are bit flag constants that are used to represent the state of a
+ * recording.
+ */
+
+// Represents a manually recorded profile, if a user hit the record button.
+const MANUAL = 0;
+// Represents a recorded profile from console.profile().
+const CONSOLE = 1;
+// Represents a profile that is currently recording.
+const RECORDING = 2;
+// Represents a profile that is currently selected.
+const SELECTED = 4;
+
+/**
+ * Utility function to provide a meaningful inteface for testing that the bits
+ * match for the recording state.
+ * @param {integer} expected - The expected bit values packed in an integer.
+ * @param {integer} actual - The actual bit values packed in an integer.
+ */
+function hasBitFlag(expected, actual) {
+  return !!(expected & actual);
+}
 
 add_task(function* () {
   // This test seems to take a very long time to finish on Linux VMs.
   requestLongerTimeout(4);
 
   let { target, console } = yield initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
-  let { EVENTS, PerformanceController, RecordingsView, OverviewView } = panel.panelWin;
+  let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
-  info("Starting console.profile()...");
+  info("Recording 1 - Starting console.profile()...");
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   yield console.profile("rust");
   yield started;
-  testRecordings(PerformanceController, [C + S + R]);
+  testRecordings(PerformanceController, [
+    CONSOLE + SELECTED + RECORDING
+  ]);
 
-  info("Starting manual recording...");
+  info("Recording 2 - Starting manual recording...");
   yield startRecording(panel);
-  testRecordings(PerformanceController, [C + R, R + S]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + RECORDING + SELECTED
+  ]);
 
-  info("Starting console.profile(\"3\")...");
+  info("Recording 3 - Starting console.profile(\"3\")...");
   started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when an in-progress recording is selected
     skipWaitingForOverview: true,
     // the view state won't switch to "console-recording" unless the new
     // in-progress recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profile("3");
   yield started;
-  testRecordings(PerformanceController, [C + R, R + S, C + R]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + RECORDING + SELECTED,
+    CONSOLE + RECORDING
+  ]);
 
-  info("Starting console.profile(\"4\")...");
+  info("Recording 4 - Starting console.profile(\"4\")...");
   started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when an in-progress  recording is selected
     skipWaitingForOverview: true,
     // the view state won't switch to "console-recording" unless the new
     // in-progress recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profile("4");
   yield started;
-  testRecordings(PerformanceController, [C + R, R + S, C + R, C + R]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + RECORDING + SELECTED,
+    CONSOLE + RECORDING,
+    CONSOLE + RECORDING
+  ]);
 
-  info("Ending console.profileEnd()...");
+  info("Recording 4 - Ending console.profileEnd()...");
   let stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when a finished recording is selected
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
     // the view state won't switch to "recorded" unless the new
     // finished recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profileEnd();
   yield stopped;
-  testRecordings(PerformanceController, [C + R, R + S, C + R, C]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + RECORDING + SELECTED,
+    CONSOLE + RECORDING,
+    CONSOLE
+  ]);
 
-  info("Select last recording...");
+  info("Recording 4 - Select last recording...");
   let recordingSelected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 3;
+  setSelectedRecording(panel, 3);
   yield recordingSelected;
-  testRecordings(PerformanceController, [C + R, R, C + R, C + S]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + RECORDING,
+    CONSOLE + RECORDING,
+    CONSOLE + SELECTED
+  ]);
   ok(!OverviewView.isRendering(),
     "Stop rendering overview when a completed recording is selected.");
 
-  info("Stop manual recording...");
+  info("Recording 2 - Stop manual recording.");
+
   yield stopRecording(panel);
-  testRecordings(PerformanceController, [C + R, S, C + R, C]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL + SELECTED,
+    CONSOLE + RECORDING,
+    CONSOLE
+  ]);
   ok(!OverviewView.isRendering(),
     "Stop rendering overview when a completed recording is selected.");
 
-  info("Select first recording...");
+  info("Recording 1 - Select first recording.");
   recordingSelected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield recordingSelected;
-  testRecordings(PerformanceController, [C + R + S, 0, C + R, C]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING + SELECTED,
+    MANUAL,
+    CONSOLE + RECORDING,
+    CONSOLE
+  ]);
   ok(OverviewView.isRendering(),
     "Should be rendering overview a recording in progress is selected.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
@@ -117,75 +177,92 @@ add_task(function* () {
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
     // the view state won't switch to "recorded" unless the new
     // finished recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profileEnd();
   yield stopped;
-  testRecordings(PerformanceController, [C + R + S, 0, C, C]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING + SELECTED,
+    MANUAL,
+    CONSOLE,
+    CONSOLE
+  ]);
   ok(OverviewView.isRendering(),
     "Should be rendering overview a recording in progress is selected.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
-  info("Start one more manual recording...");
+  info("Recording 5 - Start one more manual recording.");
   yield startRecording(panel);
-  testRecordings(PerformanceController, [C + R, 0, C, C, R + S]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL,
+    CONSOLE,
+    CONSOLE,
+    MANUAL + RECORDING + SELECTED
+  ]);
   ok(OverviewView.isRendering(),
     "Should be rendering overview a recording in progress is selected.");
 
   // Ensure overview is still rendering.
   yield times(OverviewView, EVENTS.UI_OVERVIEW_RENDERED, 3, {
     expectedArgs: { "1": Constants.FRAMERATE_GRAPH_LOW_RES_INTERVAL }
   });
 
-  info("Stop manual recording...");
+  info("Recording 5 - Stop manual recording.");
   yield stopRecording(panel);
-  testRecordings(PerformanceController, [C + R, 0, C, C, S]);
+  testRecordings(PerformanceController, [
+    CONSOLE + RECORDING,
+    MANUAL,
+    CONSOLE,
+    CONSOLE,
+    MANUAL + SELECTED
+  ]);
   ok(!OverviewView.isRendering(),
   "Stop rendering overview when a completed recording is selected.");
 
-  info("Ending console.profileEnd()...");
+  info("Recording 1 - Ending console.profileEnd()...");
   stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true,
     // only emitted when a finished recording is selected
     skipWaitingForOverview: true,
     skipWaitingForSubview: true,
     // the view state won't switch to "recorded" unless the new
     // in-progress recording is selected, which won't happen
     skipWaitingForViewState: true,
   });
   yield console.profileEnd();
   yield stopped;
-  testRecordings(PerformanceController, [C, 0, C, C, S]);
+  testRecordings(PerformanceController, [
+    CONSOLE,
+    MANUAL,
+    CONSOLE,
+    CONSOLE,
+    MANUAL + SELECTED
+  ]);
   ok(!OverviewView.isRendering(),
     "Stop rendering overview when a completed recording is selected.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
 
-// is console
-const C = 1;
-// is recording
-const R = 2;
-// is selected
-const S = 4;
-
-function testRecordings(controller, expected) {
+function testRecordings(controller, expectedBitFlags) {
   let recordings = controller.getRecordings();
   let current = controller.getCurrentRecording();
-  is(recordings.length, expected.length, "Expected number of recordings.");
+  is(recordings.length, expectedBitFlags.length, "Expected number of recordings.");
 
   recordings.forEach((recording, i) => {
-    ok(recording.isConsole() == !!(expected[i] & C),
+    const expected = expectedBitFlags[i];
+    is(recording.isConsole(), hasBitFlag(expected, CONSOLE),
       `Recording ${i + 1} has expected console state.`);
-    ok(recording.isRecording() == !!(expected[i] & R),
+    is(recording.isRecording(), hasBitFlag(expected, RECORDING),
       `Recording ${i + 1} has expected console state.`);
-    ok((recording == current) == !!(expected[i] & S),
+    is((recording == current), hasBitFlag(expected, SELECTED),
       `Recording ${i + 1} has expected selected state.`);
   });
 }
--- a/devtools/client/performance/test/browser_perf-details-03-without-allocations.js
+++ b/devtools/client/performance/test/browser_perf-details-03-without-allocations.js
@@ -9,27 +9,27 @@
  * to a default panel instead.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { UI_ENABLE_ALLOCATIONS_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let {
     EVENTS,
     $,
-    RecordingsView,
     DetailsView,
     WaterfallView,
     MemoryCallTreeView,
     MemoryFlameGraphView
   } = panel.panelWin;
 
   let flameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
   let callBtn = $("toolbarbutton[data-view='memory-calltree']");
@@ -75,31 +75,31 @@ add_task(function* () {
   yield rendered;
 
   ok(DetailsView.isViewSelected(MemoryFlameGraphView),
     "The memory flamegraph view can now be selected.");
 
   // Select the first recording with no memory data.
   selected = once(DetailsView, EVENTS.UI_DETAILS_VIEW_SELECTED);
   rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
   yield rendered;
 
   ok(DetailsView.isViewSelected(WaterfallView), "The waterfall view is now selected " +
     "when switching back to a recording that does not have memory data.");
 
   is(callBtn.hidden, true,
     "The `memory-calltree` button is hidden when recording has no memory data.");
   is(flameBtn.hidden, true,
     "The `memory-flamegraph` button is hidden when recording has no memory data.");
 
   // Go back to the recording with memory data.
   rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield rendered;
 
   ok(DetailsView.isViewSelected(WaterfallView),
     "The waterfall view is still selected in the details view.");
 
   is(callBtn.hidden, false,
     "The `memory-calltree` button is shown when recording has memory data.");
   is(flameBtn.hidden, false,
--- a/devtools/client/performance/test/browser_perf-details-04-toolbar-buttons.js
+++ b/devtools/client/performance/test/browser_perf-details-04-toolbar-buttons.js
@@ -6,28 +6,28 @@
  * Tests that the details view hides the toolbar buttons when a recording
  * doesn't exist or is in progress.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording, getSelectedRecordingIndex } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let {
     EVENTS,
     $,
     PerformanceController,
-    RecordingsView,
     WaterfallView
   } = panel.panelWin;
 
   let waterfallBtn = $("toolbarbutton[data-view='waterfall']");
   let jsFlameBtn = $("toolbarbutton[data-view='js-flamegraph']");
   let jsCallBtn = $("toolbarbutton[data-view='js-calltree']");
   let memFlameBtn = $("toolbarbutton[data-view='memory-flamegraph']");
   let memCallBtn = $("toolbarbutton[data-view='memory-calltree']");
@@ -79,39 +79,41 @@ add_task(function* () {
     "The `js-calltree` button is hidden when another recording starts.");
   is(memFlameBtn.hidden, true,
     "The `memory-flamegraph` button is hidden when another recording starts.");
   is(memCallBtn.hidden, true,
     "The `memory-calltree` button is hidden when another recording starts.");
 
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
   let rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
   yield rendered;
 
-  is(RecordingsView.selectedIndex, 0,
+  let selectedIndex = getSelectedRecordingIndex(panel);
+  is(selectedIndex, 0,
     "The first recording was selected again.");
 
   is(waterfallBtn.hidden, false,
     "The `waterfall` button is visible when first recording selected.");
   is(jsFlameBtn.hidden, false,
     "The `js-flamegraph` button is visible when first recording selected.");
   is(jsCallBtn.hidden, false,
     "The `js-calltree` button is visible when first recording selected.");
   is(memFlameBtn.hidden, true,
     "The `memory-flamegraph` button is hidden when first recording selected.");
   is(memCallBtn.hidden, true,
     "The `memory-calltree` button is hidden when first recording selected.");
 
   selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield selected;
 
-  is(RecordingsView.selectedIndex, 1,
+  selectedIndex = getSelectedRecordingIndex(panel);
+  is(selectedIndex, 1,
     "The second recording was selected again.");
 
   is(waterfallBtn.hidden, true,
     "The `waterfall button` still is hidden when second recording selected.");
   is(jsFlameBtn.hidden, true,
     "The `js-flamegraph button` still is hidden when second recording selected.");
   is(jsCallBtn.hidden, true,
     "The `js-calltree button` still is hidden when second recording selected.");
@@ -119,17 +121,18 @@ add_task(function* () {
     "The `memory-flamegraph button` still is hidden when second recording selected.");
   is(memCallBtn.hidden, true,
     "The `memory-calltree button` still is hidden when second recording selected.");
 
   rendered = once(WaterfallView, EVENTS.UI_WATERFALL_RENDERED);
   yield stopRecording(panel);
   yield rendered;
 
-  is(RecordingsView.selectedIndex, 1,
+  selectedIndex = getSelectedRecordingIndex(panel);
+  is(selectedIndex, 1,
     "The second recording is still selected.");
 
   is(waterfallBtn.hidden, false,
     "The `waterfall` button is visible when second recording finished.");
   is(jsFlameBtn.hidden, false,
     "The `js-flamegraph` button is visible when second recording finished.");
   is(jsCallBtn.hidden, false,
     "The `js-calltree` button is visible when second recording finished.");
--- a/devtools/client/performance/test/browser_perf-loading-01.js
+++ b/devtools/client/performance/test/browser_perf-loading-01.js
@@ -6,46 +6,47 @@
  * Tests that the recordings view shows the right label while recording, after
  * recording, and once the record has loaded.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecording, getDurationLabelText } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { EVENTS, L10N, $, PerformanceController, RecordingsView } = panel.panelWin;
+  let { EVENTS, L10N, PerformanceController } = panel.panelWin;
 
   yield startRecording(panel);
 
-  let durationLabel = $(".recording-item-duration", RecordingsView.selectedItem.target);
-  is(durationLabel.getAttribute("value"),
+  is(getDurationLabelText(panel, 0),
     L10N.getStr("recordingsList.recordingLabel"),
     "The duration node should show the 'recording' message while recording");
 
   let recordingStopping = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, {
     expectedArgs: { "1": "recording-stopping" }
   });
   let recordingStopped = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, {
     expectedArgs: { "1": "recording-stopped" }
   });
   let everythingStopped = stopRecording(panel);
 
   yield recordingStopping;
-  is(durationLabel.getAttribute("value"),
+  is(getDurationLabelText(panel, 0),
     L10N.getStr("recordingsList.loadingLabel"),
     "The duration node should show the 'loading' message while stopping");
 
   yield recordingStopped;
-  is(durationLabel.getAttribute("value"),
+  const selected = getSelectedRecording(panel);
+  is(getDurationLabelText(panel, 0),
     L10N.getFormatStr("recordingsList.durationLabel",
-    RecordingsView.selectedItem.attachment.getDuration().toFixed(0)),
+    selected.getDuration().toFixed(0)),
     "The duration node should show the duration after the record has stopped");
 
   yield everythingStopped;
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-loading-02.js
+++ b/devtools/client/performance/test/browser_perf-loading-02.js
@@ -8,24 +8,25 @@
  * Also test that the details view isn't locked if the recording that is being
  * stopped isn't the active one.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecordingIndex, setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { EVENTS, $, PerformanceController, RecordingsView } = panel.panelWin;
+  let { EVENTS, $, PerformanceController } = panel.panelWin;
   let detailsContainer = $("#details-pane-container");
   let recordingNotice = $("#recording-notice");
   let loadingNotice = $("#loading-notice");
   let detailsPane = $("#details-pane");
 
   yield startRecording(panel);
 
   is(detailsContainer.selectedPanel, recordingNotice,
@@ -47,35 +48,35 @@ add_task(function* () {
   is(detailsContainer.selectedPanel, detailsPane,
     "The details panel is shown after the record has stopped.");
 
   yield everythingStopped;
   yield startRecording(panel);
 
   info("While the 2nd record is still going, switch to the first one.");
   let recordingSelected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield recordingSelected;
 
   recordingStopping = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, {
     expectedArgs: { "1": "recording-stopping" }
   });
   recordingStopped = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, {
     expectedArgs: { "1": "recording-stopped" }
   });
   everythingStopped = stopRecording(panel);
 
   yield recordingStopping;
   is(detailsContainer.selectedPanel, detailsPane,
     "The details panel is still shown while the 2nd record is being stopped.");
-  is(RecordingsView.selectedIndex, 0,
+  is(getSelectedRecordingIndex(panel), 0,
     "The first record is still selected.");
 
   yield recordingStopped;
 
   is(detailsContainer.selectedPanel, detailsPane,
     "The details panel is still shown after the 2nd record has stopped.");
-  is(RecordingsView.selectedIndex, 1,
+  is(getSelectedRecordingIndex(panel), 1,
     "The second record is now selected.");
 
   yield everythingStopped;
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-options-show-jit-optimizations.js
+++ b/devtools/client/performance/test/browser_perf-options-show-jit-optimizations.js
@@ -4,23 +4,23 @@
 /* eslint-disable */
 // Bug 1235788, increase time out of this test
 requestLongerTimeout(2);
 
 /**
  * Tests that the JIT Optimizations view renders optimization data
  * if on, and displays selected frames on focus.
  */
-
+ const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 Services.prefs.setBoolPref(INVERT_PREF, false);
 
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
-  let { OverviewView, DetailsView, OptimizationsListView, JsCallTreeView, RecordingsView } = panel.panelWin;
+  let { OverviewView, DetailsView, OptimizationsListView, JsCallTreeView } = panel.panelWin;
 
   let profilerData = { threads: [gThread] };
 
   is(Services.prefs.getBoolPref(JIT_PREF), false, "record JIT Optimizations pref off by default");
   Services.prefs.setBoolPref(JIT_PREF, true);
   is(Services.prefs.getBoolPref(JIT_PREF), true, "toggle on record JIT Optimizations");
 
   // Make two recordings, so we have one to switch to later, as the
@@ -53,24 +53,24 @@ function* spawnTest() {
   let rendered = once(JsCallTreeView, "focus");
   mousedown(window, $$(".call-tree-item")[2]);
   yield rendered;
   let isHidden = $("#jit-optimizations-view").classList.contains("hidden");
   ok(!isHidden, "opts view should be visible when selecting a frame with opts");
 
   let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
   rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield Promise.all([select, rendered]);
 
   isHidden = $("#jit-optimizations-view").classList.contains("hidden");
   ok(isHidden, "opts view is hidden when switching recordings");
 
   rendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield rendered;
 
   rendered = once(JsCallTreeView, "focus");
   mousedown(window, $$(".call-tree-item")[2]);
   yield rendered;
   isHidden = $("#jit-optimizations-view").classList.contains("hidden");
   ok(!isHidden, "opts view should be visible when selecting a frame with opts");
 
--- a/devtools/client/performance/test/browser_perf-overview-render-04.js
+++ b/devtools/client/performance/test/browser_perf-overview-render-04.js
@@ -8,24 +8,25 @@
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { UI_ENABLE_MEMORY_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { waitUntil } = require("devtools/client/performance/test/helpers/wait-utils");
 const { isVisible } = require("devtools/client/performance/test/helpers/dom-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { $, EVENTS, PerformanceController, RecordingsView, OverviewView } = panel.panelWin;
+  let { $, EVENTS, PerformanceController, OverviewView } = panel.panelWin;
 
   // Enable memory to test.
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
 
   // Set realtime rendering off.
   OverviewView.isRealtimeRenderingEnabled = () => false;
 
   let updated = 0;
@@ -48,22 +49,22 @@ add_task(function* () {
   is(updated, 1, "Overview graphs rendered upon completion.");
 
   yield startRecording(panel, { skipWaitingForOverview: true });
 
   is(isVisible($("#overview-pane")), false,
      "Overview graphs hidden again when starting new recording.");
   is(updated, 1, "Overview graphs have not been updated again.");
 
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   is(isVisible($("#overview-pane")), true,
      "Overview graphs no longer hidden when switching back to complete recording.");
   is(updated, 1, "Overview graphs have not been updated again.");
 
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   is(isVisible($("#overview-pane")), false,
      "Overview graphs hidden again when going back to inprogress recording.");
   is(updated, 1, "Overview graphs have not been updated again.");
 
   yield stopRecording(panel);
 
   is(isVisible($("#overview-pane")), true,
      "overview graphs no longer hidden when recording finishes");
--- a/devtools/client/performance/test/browser_perf-recording-notices-02.js
+++ b/devtools/client/performance/test/browser_perf-recording-notices-02.js
@@ -6,29 +6,29 @@
  * Tests that the recording notice panes are toggled when going between
  * a completed recording and an in-progress recording.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let {
     EVENTS,
     $,
     PerformanceController,
     PerformanceView,
-    RecordingsView
   } = panel.panelWin;
 
   let MAIN_CONTAINER = $("#performance-view");
   let CONTENT = $("#performance-view-content");
   let DETAILS_CONTAINER = $("#details-pane-container");
   let RECORDING = $("#recording-notice");
   let DETAILS = $("#details-pane");
 
@@ -37,26 +37,26 @@ add_task(function* () {
 
   yield startRecording(panel);
 
   is(PerformanceView.getState(), "recording", "Correct state during recording.");
   is(MAIN_CONTAINER.selectedPanel, CONTENT, "Showing main view with timeline.");
   is(DETAILS_CONTAINER.selectedPanel, RECORDING, "Showing recording panel.");
 
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
 
   is(PerformanceView.getState(), "recorded",
      "Correct state during recording but selecting a completed recording.");
   is(MAIN_CONTAINER.selectedPanel, CONTENT, "Showing main view with timeline.");
   is(DETAILS_CONTAINER.selectedPanel, DETAILS, "Showing recorded panel.");
 
   selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield selected;
 
   is(PerformanceView.getState(), "recording",
      "Correct state when switching back to recording in progress.");
   is(MAIN_CONTAINER.selectedPanel, CONTENT, "Showing main view with timeline.");
   is(DETAILS_CONTAINER.selectedPanel, RECORDING, "Showing recording panel.");
 
   yield stopRecording(panel);
--- a/devtools/client/performance/test/browser_perf-recording-notices-03.js
+++ b/devtools/client/performance/test/browser_perf-recording-notices-03.js
@@ -10,16 +10,17 @@
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { PROFILER_BUFFER_SIZE_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { pmmLoadFrameScripts, pmmStopProfiler, pmmClearFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { waitUntil } = require("devtools/client/performance/test/helpers/wait-utils");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   // Make sure the profiler module is stopped so we can set a new buffer limit.
   pmmLoadFrameScripts(gBrowser);
   yield pmmStopProfiler();
 
   // Keep the profiler's buffer large, but still get to 1% relatively quick.
   Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000000);
@@ -31,17 +32,16 @@ add_task(function* () {
 
   let { panel } = yield initPerformanceInTab({ tab: target.tab });
   let {
     gFront,
     EVENTS,
     $,
     PerformanceController,
     PerformanceView,
-    RecordingsView
   } = panel.panelWin;
 
   // Set a fast profiler-status update interval.
   yield gFront.setProfilerStatusInterval(10);
 
   let DETAILS_CONTAINER = $("#details-pane-container");
   let NORMAL_BUFFER_STATUS_MESSAGE = $("#recording-notice .buffer-status-message");
   let CONSOLE_BUFFER_STATUS_MESSAGE =
@@ -83,17 +83,17 @@ add_task(function* () {
     PerformanceController.getCurrentRecording());
   either(DETAILS_CONTAINER.getAttribute("buffer-status"), "in-progress", "full",
     "Container has [buffer-status=in-progress] or [buffer-status=full].");
   ok(NORMAL_BUFFER_STATUS_MESSAGE.value.indexOf(gPercent + "%") !== -1,
     "Buffer status text has correct percentage.");
 
   // Select the console recording.
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield selected;
 
   yield waitUntil(function* () {
     [, gPercent] = yield once(PerformanceView,
                               EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED,
                               { spreadArgs: true });
     return gPercent > 0;
   });
@@ -104,17 +104,17 @@ add_task(function* () {
     "Container has [buffer-status=in-progress] or [buffer-status=full].");
   ok(CONSOLE_BUFFER_STATUS_MESSAGE.value.indexOf(gPercent + "%") !== -1,
     "Buffer status text has correct percentage for console recording.");
 
   // Stop the console profile, then select the original manual recording.
   yield console.profileEnd("rust");
 
   selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
 
   yield waitUntil(function* () {
     [, gPercent] = yield once(PerformanceView,
                               EVENTS.UI_RECORDING_PROFILER_STATUS_RENDERED,
                               { spreadArgs: true });
     return gPercent > Math.floor(bufferUsage * 100);
   });
--- a/devtools/client/performance/test/browser_perf-recording-selected-01.js
+++ b/devtools/client/performance/test/browser_perf-recording-selected-01.js
@@ -6,39 +6,40 @@
  * Tests if the profiler correctly handles multiple recordings and can
  * successfully switch between them.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording, getRecordingsCount, getSelectedRecordingIndex } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController } = panel.panelWin;
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  is(RecordingsView.itemCount, 2,
+  is(getRecordingsCount(panel), 2,
     "There should be two recordings visible.");
-  is(RecordingsView.selectedIndex, 1,
+  is(getSelectedRecordingIndex(panel), 1,
     "The second recording item should be selected.");
 
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
 
-  is(RecordingsView.itemCount, 2,
+  is(getRecordingsCount(panel), 2,
     "There should still be two recordings visible.");
-  is(RecordingsView.selectedIndex, 0,
+  is(getSelectedRecordingIndex(panel), 0,
     "The first recording item should be selected.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-recording-selected-02.js
+++ b/devtools/client/performance/test/browser_perf-recording-selected-02.js
@@ -6,53 +6,53 @@
  * Tests if the profiler correctly handles multiple recordings and can
  * successfully switch between them, even when one of them is in progress.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { getSelectedRecordingIndex, setSelectedRecording, getRecordingsCount } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   // This test seems to take a very long time to finish on Linux VMs.
   requestLongerTimeout(4);
 
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController } = panel.panelWin;
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield startRecording(panel);
 
-  is(RecordingsView.itemCount, 2,
+  is(getRecordingsCount(panel), 2,
     "There should be two recordings visible.");
-  is(RecordingsView.selectedIndex, 1,
+  is(getSelectedRecordingIndex(panel), 1,
     "The new recording item should be selected.");
 
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
 
-  is(RecordingsView.itemCount, 2,
+  is(getRecordingsCount(panel), 2,
     "There should still be two recordings visible.");
-  is(RecordingsView.selectedIndex, 0,
+  is(getSelectedRecordingIndex(panel), 0,
     "The first recording item should be selected now.");
 
   selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield selected;
 
-  is(RecordingsView.itemCount, 2,
+  is(getRecordingsCount(panel), 2,
     "There should still be two recordings visible.");
-  is(RecordingsView.selectedIndex, 1,
+  is(getSelectedRecordingIndex(panel), 1,
     "The second recording item should be selected again.");
 
   yield stopRecording(panel);
 
   yield teardownToolboxAndRemoveTab(panel);
 });
-
--- a/devtools/client/performance/test/browser_perf-recording-selected-03.js
+++ b/devtools/client/performance/test/browser_perf-recording-selected-03.js
@@ -7,34 +7,35 @@
  * Tests if the profiler UI does not forget that recording is active when
  * selected recording changes.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { $, EVENTS, PerformanceController, RecordingsView } = panel.panelWin;
+  let { $, EVENTS, PerformanceController } = panel.panelWin;
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield startRecording(panel);
 
   info("Selecting recording #0 and waiting for it to be displayed.");
 
   let selected = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield selected;
 
   ok($("#main-record-button").classList.contains("checked"),
     "Button is still checked after selecting another item.");
   ok(!$("#main-record-button").hasAttribute("disabled"),
     "Button is not locked after selecting another item.");
 
   yield stopRecording(panel);
--- a/devtools/client/performance/test/browser_perf-recording-selected-04.js
+++ b/devtools/client/performance/test/browser_perf-recording-selected-04.js
@@ -5,24 +5,25 @@
 /**
  * Tests that all components can get rerendered for a profile when switching.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { UI_ENABLE_MEMORY_PREF, UI_ENABLE_ALLOCATIONS_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording, waitForAllWidgetsRendered } = require("devtools/client/performance/test/helpers/actions");
+const { setSelectedRecording } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { DetailsView, DetailsSubview, RecordingsView } = panel.panelWin;
+  let { DetailsView, DetailsSubview } = panel.panelWin;
 
   // Enable memory to test the memory overview.
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
 
   // Enable allocations to test the memory-calltree and memory-flamegraph.
   Services.prefs.setBoolPref(UI_ENABLE_ALLOCATIONS_PREF, true);
 
   yield startRecording(panel);
@@ -38,21 +39,21 @@ add_task(function* () {
   yield DetailsView.selectView("js-flamegraph");
   yield DetailsView.selectView("memory-calltree");
   yield DetailsView.selectView("memory-flamegraph");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
   let rerender = waitForAllWidgetsRendered(panel);
-  RecordingsView.selectedIndex = 0;
+  setSelectedRecording(panel, 0);
   yield rerender;
 
   ok(true, "All widgets were rendered when selecting the first recording.");
 
   rerender = waitForAllWidgetsRendered(panel);
-  RecordingsView.selectedIndex = 1;
+  setSelectedRecording(panel, 1);
   yield rerender;
 
   ok(true, "All widgets were rendered when selecting the second recording.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-recordings-clear-01.js
+++ b/devtools/client/performance/test/browser_perf-recordings-clear-01.js
@@ -5,49 +5,50 @@
 /**
  * Tests that clearing recordings empties out the recordings list and toggles
  * the empty notice state.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPanelInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
+const { getRecordingsCount } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPanelInNewTab({
     tool: "performance",
     url: SIMPLE_URL,
     win: window
   });
 
-  let { PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
+  let { PerformanceController, PerformanceView } = panel.panelWin;
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  is(RecordingsView.itemCount, 1,
-    "RecordingsView should have one recording.");
+  is(getRecordingsCount(panel), 1,
+    "The recordings list should have one recording.");
   isnot(PerformanceView.getState(), "empty",
     "PerformanceView should not be in an empty state.");
   isnot(PerformanceController.getCurrentRecording(), null,
     "There should be a current recording.");
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  is(RecordingsView.itemCount, 2,
-    "RecordingsView should have two recordings.");
+  is(getRecordingsCount(panel), 2,
+    "The recordings list should have two recordings.");
   isnot(PerformanceView.getState(), "empty",
     "PerformanceView should not be in an empty state.");
   isnot(PerformanceController.getCurrentRecording(), null,
     "There should be a current recording.");
 
   yield PerformanceController.clearRecordings();
 
-  is(RecordingsView.itemCount, 0,
-    "RecordingsView should be empty.");
+  is(getRecordingsCount(panel), 0,
+    "The recordings list should be empty.");
   is(PerformanceView.getState(), "empty",
     "PerformanceView should be in an empty state.");
   is(PerformanceController.getCurrentRecording(), null,
     "There should be no current recording.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-recordings-clear-02.js
+++ b/devtools/client/performance/test/browser_perf-recordings-clear-02.js
@@ -6,63 +6,64 @@
  * Tests that clearing recordings empties out the recordings list and stops
  * a current recording if recording and can continue recording after.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPanelInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { times, once } = require("devtools/client/performance/test/helpers/event-utils");
+const { getRecordingsCount } = require("devtools/client/performance/test/helpers/recording-utils");
 
 add_task(function* () {
   let { panel } = yield initPanelInNewTab({
     tool: "performance",
     url: SIMPLE_URL,
     win: window
   });
 
-  let { EVENTS, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
+  let { EVENTS, PerformanceController, PerformanceView } = panel.panelWin;
 
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  is(RecordingsView.itemCount, 1,
-    "RecordingsView should have one recording.");
+  is(getRecordingsCount(panel), 1,
+    "The recordings list should have one recording.");
   isnot(PerformanceView.getState(), "empty",
     "PerformanceView should not be in an empty state.");
   isnot(PerformanceController.getCurrentRecording(), null,
     "There should be a current recording.");
 
   yield startRecording(panel);
 
-  is(RecordingsView.itemCount, 2,
-    "RecordingsView should have two recordings.");
+  is(getRecordingsCount(panel), 2,
+    "The recordings list should have two recordings.");
   isnot(PerformanceView.getState(), "empty",
     "PerformanceView should not be in an empty state.");
   isnot(PerformanceController.getCurrentRecording(), null,
     "There should be a current recording.");
 
   let recordingDeleted = times(PerformanceController, EVENTS.RECORDING_DELETED, 2);
   let recordingStopped = once(PerformanceController, EVENTS.RECORDING_STATE_CHANGE, {
     expectedArgs: { "1": "recording-stopped" }
   });
 
   PerformanceController.clearRecordings();
 
   yield recordingDeleted;
   yield recordingStopped;
 
-  is(RecordingsView.itemCount, 0,
-    "RecordingsView should be empty.");
+  is(getRecordingsCount(panel), 0,
+    "The recordings list should be empty.");
   is(PerformanceView.getState(), "empty",
     "PerformanceView should be in an empty state.");
   is(PerformanceController.getCurrentRecording(), null,
     "There should be no current recording.");
 
   // Bug 1169146: Try another recording after clearing mid-recording.
   yield startRecording(panel);
   yield stopRecording(panel);
 
-  is(RecordingsView.itemCount, 1,
-    "RecordingsView should have one recording.");
+  is(getRecordingsCount(panel), 1,
+    "The recordings list should have one recording.");
 
   yield teardownToolboxAndRemoveTab(panel);
 });
--- a/devtools/client/performance/test/browser_perf-tree-view-11.js
+++ b/devtools/client/performance/test/browser_perf-tree-view-11.js
@@ -7,17 +7,17 @@
  * icon is next to the frame with optimizations
  */
 
 var { CATEGORY_MASK } = require("devtools/client/performance/modules/categories");
 
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
-  let { OverviewView, DetailsView, JsCallTreeView, RecordingsView } = panel.panelWin;
+  let { OverviewView, DetailsView, JsCallTreeView } = panel.panelWin;
 
   let profilerData = { threads: [gThread] };
 
   Services.prefs.setBoolPref(JIT_PREF, true);
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
   Services.prefs.setBoolPref(INVERT_PREF, false);
 
   // Make two recordings, so we have one to switch to later, as the
--- a/devtools/client/performance/test/helpers/moz.build
+++ b/devtools/client/performance/test/helpers/moz.build
@@ -7,13 +7,14 @@
 DevToolsModules(
     'actions.js',
     'dom-utils.js',
     'event-utils.js',
     'input-utils.js',
     'panel-utils.js',
     'prefs.js',
     'profiler-mm-utils.js',
+    'recording-utils.js',
     'synth-utils.js',
     'tab-utils.js',
     'urls.js',
     'wait-utils.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/helpers/recording-utils.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * These utilities provide a functional interface for accessing the particulars
+ * about the recording's details.
+ */
+
+/**
+ * Access the selected view from the panel's recording list.
+ *
+ * @param {object} panel - The current panel.
+ * @return {object} The recording model.
+ */
+exports.getSelectedRecording = function (panel) {
+  const view = panel.panelWin.RecordingsView;
+  return view.selected;
+};
+
+/**
+ * Set the selected index of the recording via the panel.
+ *
+ * @param {object} panel - The current panel.
+ * @return {number} index
+ */
+exports.setSelectedRecording = function (panel, index) {
+  const view = panel.panelWin.RecordingsView;
+  view.setSelectedByIndex(index);
+  return index;
+};
+
+/**
+ * Access the selected view from the panel's recording list.
+ *
+ * @param {object} panel - The current panel.
+ * @return {number} index
+ */
+exports.getSelectedRecordingIndex = function (panel) {
+  const view = panel.panelWin.RecordingsView;
+  return view.getSelectedIndex();
+};
+
+exports.getDurationLabelText = function (panel, elementIndex) {
+  const { $$ } = panel.panelWin;
+  const elements = $$(".recording-list-item-duration", panel.panelWin.document);
+  return elements[elementIndex].innerHTML;
+};
+
+exports.getRecordingsCount = function (panel) {
+  const { $$ } = panel.panelWin;
+  return $$(".recording-list-item", panel.panelWin.document).length;
+};
--- a/devtools/client/performance/views/recordings.js
+++ b/devtools/client/performance/views/recordings.js
@@ -4,98 +4,108 @@
 /* import-globals-from ../performance-controller.js */
 /* import-globals-from ../performance-view.js */
 /* globals document, window */
 "use strict";
 
 /**
  * Functions handling the recordings UI.
  */
-var RecordingsView = Heritage.extend(WidgetMethods, {
+var RecordingsView = {
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function () {
-    this.widget = new SideMenuWidget($("#recordings-list"));
-
     this._onSelect = this._onSelect.bind(this);
     this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
     this._onNewRecording = this._onNewRecording.bind(this);
     this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
     this._onRecordingDeleted = this._onRecordingDeleted.bind(this);
     this._onRecordingExported = this._onRecordingExported.bind(this);
 
-    this.emptyText = L10N.getStr("noRecordingsText");
-
     PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
     PerformanceController.on(EVENTS.RECORDING_ADDED, this._onNewRecording);
     PerformanceController.on(EVENTS.RECORDING_DELETED, this._onRecordingDeleted);
     PerformanceController.on(EVENTS.RECORDING_EXPORTED, this._onRecordingExported);
-    this.widget.addEventListener("select", this._onSelect, false);
+
+    // DE-XUL: Begin migrating the recording sidebar to React. Temporarily hold state
+    // here.
+    this._listState = {
+      recordings: [],
+      labels: new WeakMap(),
+      selected: null,
+    };
+    this._listMount = PerformanceUtils.createHtmlMount($("#recording-list-mount"));
+    this._renderList();
+  },
+
+  /**
+   * Get the index of the currently selected recording. Only used by tests.
+   * @return {integer} index
+   */
+  getSelectedIndex() {
+    const { recordings, selected } = this._listState;
+    return recordings.indexOf(selected);
+  },
+
+  /**
+   * Set the currently selected recording via its index. Only used by tests.
+   * @param {integer} index
+   */
+  setSelectedByIndex(index) {
+    this._onSelect(this._listState.recordings[index]);
+    this._renderList();
+  },
+
+  /**
+   * DE-XUL: During the migration, this getter will access the selected recording from
+   * the private _listState object so that tests will continue to pass.
+   */
+  get selected() {
+    return this._listState.selected;
+  },
+
+  /**
+   * DE-XUL: During the migration, this getter will access the number of recordings.
+   */
+  get itemCount() {
+    return this._listState.recordings.length;
+  },
+
+  /**
+   * DE-XUL: Render the recording list using React.
+   */
+  _renderList: function () {
+    const {recordings, labels, selected} = this._listState;
+
+    const recordingList = RecordingList({
+      itemComponent: RecordingListItem,
+      items: recordings.map(recording => ({
+        onSelect: () => this._onSelect(recording),
+        onSave: () => this._onSaveButtonClick(recording),
+        isLoading: !recording.isRecording() && !recording.isCompleted(),
+        isRecording: recording.isRecording(),
+        isSelected: recording === selected,
+        duration: recording.getDuration().toFixed(0),
+        label: labels.get(recording),
+      }))
+    });
+
+    ReactDOM.render(recordingList, this._listMount);
   },
 
   /**
    * Destruction function, called when the tool is closed.
    */
   destroy: function () {
     PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE,
                               this._onRecordingStateChange);
     PerformanceController.off(EVENTS.RECORDING_ADDED, this._onNewRecording);
     PerformanceController.off(EVENTS.RECORDING_DELETED, this._onRecordingDeleted);
     PerformanceController.off(EVENTS.RECORDING_EXPORTED, this._onRecordingExported);
-    this.widget.removeEventListener("select", this._onSelect, false);
-  },
-
-  /**
-   * Adds an empty recording to this container.
-   *
-   * @param RecordingModel recording
-   *        A model for the new recording item created.
-   */
-  addEmptyRecording: function (recording) {
-    let titleNode = document.createElement("label");
-    titleNode.className = "plain recording-item-title";
-    titleNode.setAttribute("crop", "end");
-    titleNode.setAttribute("value", recording.getLabel() ||
-      L10N.getFormatStr("recordingsList.itemLabel", this.itemCount + 1));
-
-    let durationNode = document.createElement("label");
-    durationNode.className = "plain recording-item-duration";
-    durationNode.setAttribute("value",
-      L10N.getStr("recordingsList.recordingLabel"));
-
-    let saveNode = document.createElement("label");
-    saveNode.className = "plain recording-item-save";
-    saveNode.addEventListener("click", this._onSaveButtonClick);
-
-    let hspacer = document.createElement("spacer");
-    hspacer.setAttribute("flex", "1");
-
-    let footerNode = document.createElement("hbox");
-    footerNode.className = "recording-item-footer";
-    footerNode.appendChild(durationNode);
-    footerNode.appendChild(hspacer);
-    footerNode.appendChild(saveNode);
-
-    let vspacer = document.createElement("spacer");
-    vspacer.setAttribute("flex", "1");
-
-    let contentsNode = document.createElement("vbox");
-    contentsNode.className = "recording-item";
-    contentsNode.setAttribute("flex", "1");
-    contentsNode.appendChild(titleNode);
-    contentsNode.appendChild(vspacer);
-    contentsNode.appendChild(footerNode);
-
-    // Append a recording item to this container.
-    return this.push([contentsNode], {
-      // Store the recording model that contains all the data to be
-      // rendered in the item.
-      attachment: recording
-    });
   },
 
   /**
    * Called when a new recording is stored in the UI. This handles
    * when recordings are lazily loaded (like a console.profile occurring
    * before the tool is loaded) or imported. In normal manual recording cases,
    * this will also be fired.
    */
@@ -107,119 +117,86 @@ var RecordingsView = Heritage.extend(Wid
    * Signals that a recording has changed state.
    *
    * @param string state
    *        Can be "recording-started", "recording-stopped", "recording-stopping"
    * @param RecordingModel recording
    *        Model of the recording that was started.
    */
   _onRecordingStateChange: function (_, state, recording) {
-    let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
-    if (!recordingItem) {
-      recordingItem = this.addEmptyRecording(recording);
+    const { recordings, labels } = this._listState;
+
+    if (!recordings.includes(recording)) {
+      recordings.push(recording);
+      labels.set(recording, recording.getLabel() ||
+        L10N.getFormatStr("recordingsList.itemLabel", recordings.length));
 
       // If this is a manual recording, immediately select it, or
       // select a console profile if its the only one
-      if (!recording.isConsole() || this.selectedIndex === -1) {
-        this.selectedItem = recordingItem;
+      if (!recording.isConsole() || !this._listState.selected) {
+        this._onSelect(recording);
       }
     }
 
-    recordingItem.isRecording = recording.isRecording();
-
-    // This recording is in the process of stopping.
-    if (!recording.isRecording() && !recording.isCompleted()) {
-      // Mark the corresponding item as loading.
-      let durationNode = $(".recording-item-duration", recordingItem.target);
-      durationNode.setAttribute("value", L10N.getStr("recordingsList.loadingLabel"));
+    // Determine if the recording needs to be selected.
+    const isCompletedManualRecording = !recording.isConsole() && recording.isCompleted();
+    if (recording.isImported() || isCompletedManualRecording) {
+      this._onSelect(recording);
     }
 
-    // Render the recording item with finalized information (timing, etc)
-    if (recording.isCompleted() && !recordingItem.finalized) {
-      this.finalizeRecording(recordingItem);
-      // Select the recording if it was a manual recording only
-      if (!recording.isConsole()) {
-        this.forceSelect(recordingItem);
-      }
-    }
-
-    // Auto select imported items.
-    if (recording.isImported()) {
-      this.selectedItem = recordingItem;
-    }
+    this._renderList();
   },
 
   /**
    * Clears out all non-console recordings.
    */
   _onRecordingDeleted: function (_, recording) {
-    let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
-    this.remove(recordingItem);
-  },
-
-  /**
-   * Adds recording data to a recording item in this container.
-   *
-   * @param Item recordingItem
-   *        An item inserted via `RecordingsView.addEmptyRecording`.
-   */
-  finalizeRecording: function (recordingItem) {
-    let model = recordingItem.attachment;
-    recordingItem.finalized = true;
-
-    let saveNode = $(".recording-item-save", recordingItem.target);
-    saveNode.setAttribute("value",
-      L10N.getStr("recordingsList.saveLabel"));
-
-    let durationMillis = model.getDuration().toFixed(0);
-    let durationNode = $(".recording-item-duration", recordingItem.target);
-    durationNode.setAttribute("value",
-      L10N.getFormatStr("recordingsList.durationLabel", durationMillis));
+    const { recordings } = this._listState;
+    const index = recordings.indexOf(recording);
+    if (index === -1) {
+      throw new Error("Attempting to remove a recording that doesn't exist.");
+    }
+    recordings.splice(index, 1);
+    this._renderList();
   },
 
   /**
    * The select listener for this container.
    */
-  _onSelect: Task.async(function* ({ detail: recordingItem }) {
-    if (!recordingItem) {
-      return;
-    }
-
-    let model = recordingItem.attachment;
-    this.emit(EVENTS.UI_RECORDING_SELECTED, model);
+  _onSelect: Task.async(function* (recording) {
+    this._listState.selected = recording;
+    this.emit(EVENTS.UI_RECORDING_SELECTED, recording);
+    this._renderList();
   }),
 
   /**
    * The click listener for the "save" button of each item in this container.
    */
-  _onSaveButtonClick: function (e) {
+  _onSaveButtonClick: function (recording) {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"),
             Ci.nsIFilePicker.modeSave);
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
     fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
     fp.defaultString = "profile.json";
 
     fp.open({ done: result => {
       if (result == Ci.nsIFilePicker.returnCancel) {
         return;
       }
-      let recordingItem = this.getItemForElement(e.target);
-      this.emit(EVENTS.UI_EXPORT_RECORDING, recordingItem.attachment, fp.file);
+      this.emit(EVENTS.UI_EXPORT_RECORDING, recording, fp.file);
     }});
   },
 
   _onRecordingExported: function (_, recording, file) {
     if (recording.isConsole()) {
       return;
     }
-    let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
-    let titleNode = $(".recording-item-title", recordingItem.target);
-    titleNode.setAttribute("value", file.leafName.replace(/\..+$/, ""));
-  },
-
-  toString: () => "[object RecordingsView]"
-});
+    const name = file.leafName.replace(/\..+$/, "");
+    this._listState.labels.set(recording, name);
+    this._renderList();
+  }
+};
 
 /**
  * Convenient way of emitting events from the RecordingsView.
  */
 EventEmitter.decorate(RecordingsView);
--- a/devtools/client/themes/performance.css
+++ b/devtools/client/themes/performance.css
@@ -139,43 +139,91 @@
 
 /*
  * DE-XUL: The height of the toolbar is not correct without tweaking the line-height.
  */
 #recordings-pane .devtools-toolbar {
   line-height: 0;
 }
 
-#recordings-list {
-  max-width: 300px;
+.theme-sidebar {
+  position: relative;
+}
+
+/**
+ * DE-XUL: This is probably only needed for the html:div inside of a vbox.
+ */
+#recordings-list > div {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
 }
 
-.recording-item {
-  padding: 4px;
+.recording-list {
+  width: var(--sidebar-width);
+  min-width: var(--sidebar-width);
+  margin: 0;
+  padding: 0;
+  background-color: var(--theme-sidebar-background);
+  border-inline-end: 1px solid var(--theme-splitter-color);
 }
 
-.recording-item-title {
+.recording-list-item {
+  display: flex;
+  flex-direction: column;
+  color: var(--theme-body-color);
+  border-bottom: 1px solid rgba(128,128,128,0.15);
+  padding: 8px;
+  cursor: default;
+}
+
+.recording-list-item.selected {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
+
+.recording-list-empty {
+  padding: 8px;
+}
+
+.recording-list-item-label {
   font-size: 110%;
 }
 
-.recording-item-footer {
+.recording-list-item-footer {
   padding-top: 4px;
   font-size: 90%;
+  display: flex;
+  justify-content: space-between;
 }
 
-.recording-item-save {
+.recording-list-item-save {
+  background: none;
+  border: none;
   text-decoration: underline;
   cursor: pointer;
+  font-size: 90%;
+  padding:0;
 }
 
-.recording-item-duration,
-.recording-item-save {
+.recording-list-item-duration,
+.recording-list-item-save {
   color: var(--theme-body-color-alt);
 }
 
+.recording-list-item.selected .recording-list-item-duration,
+.recording-list-item.selected .recording-list-item-save {
+  color: var(--theme-body-color-alt);
+  color: var(--theme-selection-color);
+}
+
 #recordings-list .selected label {
   /* Text inside a selected item should not be custom colored. */
   color: inherit !important;
 }
 
 /* Recording notices */
 
 .notice-container {