Bug 951795 - Use WidgetMethod's empty text attribute instead of a deck in the tracer, r=fitzgen
authorVictor Porof <vporof@mozilla.com>
Tue, 07 Jan 2014 11:57:00 +0200
changeset 179273 19a7e3bd47c31ca1a1c3197f81a6013b70960698
parent 179272 9f2b115ff15abd0d0f5149de81b6fbbb320beb83
child 179274 c9b60fc06e4cee659bc281f913e16368c851beb7
child 179389 1e870df73ef5d39fd8ff9131fa050490ec8e92c2
push id462
push userraliiev@mozilla.com
push dateTue, 22 Apr 2014 00:22:30 +0000
treeherdermozilla-release@ac5db8c74ac0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs951795
milestone29.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 951795 - Use WidgetMethod's empty text attribute instead of a deck in the tracer, r=fitzgen
browser/devtools/debugger/debugger-controller.js
browser/devtools/debugger/debugger-panes.js
browser/devtools/debugger/debugger.xul
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_tracing-04.js
browser/devtools/debugger/test/browser_dbg_tracing-05.js
browser/devtools/shared/widgets/FastListWidget.js
browser/devtools/shared/widgets/SideMenuWidget.jsm
browser/devtools/shared/widgets/SimpleListWidget.jsm
browser/devtools/shared/widgets/ViewHelpers.jsm
browser/devtools/shared/widgets/widgets.css
browser/locales/en-US/chrome/browser/devtools/debugger.dtd
browser/locales/en-US/chrome/browser/devtools/debugger.properties
browser/themes/linux/devtools/debugger.css
browser/themes/linux/devtools/widgets.css
browser/themes/osx/devtools/debugger.css
browser/themes/osx/devtools/widgets.css
browser/themes/windows/devtools/debugger.css
browser/themes/windows/devtools/widgets.css
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -1534,16 +1534,17 @@ Tracer.prototype = {
    * Callback for handling a new call frame.
    */
   _onCall: function({ name, location, parameterNames, depth, arguments: args }) {
     const item = {
       name: name,
       location: location,
       id: this._idCounter++
     };
+
     this._stack.push(item);
     DebuggerView.Tracer.addTrace({
       type: "call",
       name: name,
       location: location,
       depth: depth,
       parameterNames: parameterNames,
       arguments: args,
@@ -1592,17 +1593,17 @@ Tracer.prototype = {
 
       getParameterNames: callback => callback(aObject),
       getPrototypeAndProperties: callback => callback(aObject),
       getPrototype: callback => callback(aObject),
 
       getOwnPropertyNames: (callback) => {
         callback({
           ownPropertyNames: aObject.ownProperties
-            ?  Object.keys(aObject.ownProperties)
+            ? Object.keys(aObject.ownProperties)
             : []
         });
       },
 
       getProperty: (property, callback) => {
         callback({
           descriptor: aObject.ownProperties
             ? aObject.ownProperties[property]
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1083,18 +1083,20 @@ function TracerView() {
   this._isNotSelectedItem = this._isNotSelectedItem.bind(this);
 
   this._unhighlightMatchingItems =
     DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this));
   this._onToggleTracing =
     DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
   this._onStartTracing =
     DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
-  this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this));
-  this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this));
+  this._onClear =
+    DevToolsUtils.makeInfallible(this._onClear.bind(this));
+  this._onSelect =
+    DevToolsUtils.makeInfallible(this._onSelect.bind(this));
   this._onMouseOver =
     DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
   this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this));
 }
 
 TracerView.MAX_TRACES = 200;
 
 TracerView.prototype = Heritage.extend(WidgetMethods, {
@@ -1113,37 +1115,37 @@ TracerView.prototype = Heritage.extend(W
       this._traceButton.remove();
       this._traceButton = null;
       this._tracerTab.remove();
       this._tracerTab = null;
       return;
     }
 
     this.widget = new FastListWidget(document.getElementById("tracer-traces"));
-
     this._traceButton.removeAttribute("hidden");
     this._tracerTab.removeAttribute("hidden");
 
-    this._tracerDeck = document.getElementById("tracer-deck");
     this._search = document.getElementById("tracer-search");
-
     this._template = document.getElementsByClassName("trace-item-template")[0];
     this._templateItem = this._template.getElementsByClassName("trace-item")[0];
     this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
     this._templateNameNode = this._template.getElementsByClassName("trace-name")[0];
 
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("mouseover", this._onMouseOver, false);
     this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
-
     this._search.addEventListener("input", this._onSearch, false);
 
     this._startTooltip = L10N.getStr("startTracingTooltip");
     this._stopTooltip = L10N.getStr("stopTracingTooltip");
+    this._tracingNotStartedString = L10N.getStr("tracingNotStartedText");
+    this._noFunctionCallsString = L10N.getStr("noFunctionCallsText");
+
     this._traceButton.setAttribute("tooltiptext", this._startTooltip);
+    this.emptyText = this._tracingNotStartedString;
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the TracerView");
 
@@ -1166,33 +1168,48 @@ TracerView.prototype = Heritage.extend(W
     } else {
       this._onStartTracing();
     }
   },
 
   /**
    * Function invoked either by the "startTracing" command or by
    * _onToggleTracing to start execution tracing in the backend.
+   *
+   * @return object
+   *         A promise resolved once the tracing has successfully started.
    */
   _onStartTracing: function() {
-    this._tracerDeck.selectedIndex = 0;
     this._traceButton.setAttribute("checked", true);
     this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
+
     this.empty();
-    DebuggerController.Tracer.startTracing();
+    this.emptyText = this._noFunctionCallsString;
+
+    let deferred = promise.defer();
+    DebuggerController.Tracer.startTracing(deferred.resolve);
+    return deferred.promise;
   },
 
   /**
    * Function invoked by _onToggleTracing to stop execution tracing in the
    * backend.
+   *
+   * @return object
+   *         A promise resolved once the tracing has successfully stopped.
    */
   _onStopTracing: function() {
     this._traceButton.removeAttribute("checked");
     this._traceButton.setAttribute("tooltiptext", this._startTooltip);
-    DebuggerController.Tracer.stopTracing();
+
+    this.emptyText = this._tracingNotStartedString;
+
+    let deferred = promise.defer();
+    DebuggerController.Tracer.stopTracing(deferred.resolve);
+    return deferred.promise;
   },
 
   /**
    * Function invoked by the "clearTraces" command to empty the traces pane.
    */
   _onClear: function() {
     this.empty();
   },
@@ -1331,17 +1348,16 @@ TracerView.prototype = Heritage.extend(W
   },
 
   /**
    * Select the traces tab in the sidebar.
    */
   selectTab: function() {
     const tabs = this._tracerTab.parentElement;
     tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
-    this._tracerDeck.selectedIndex = 0;
   },
 
   /**
    * Commit all staged items to the widget. Overridden so that we can call
    * |FastListWidget.prototype.flush|.
    */
   commit: function() {
     WidgetMethods.commit.call(this);
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -367,46 +367,35 @@
                              class="devtools-toolbarbutton"
                              tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
                              command="toggleBreakpointsCommand"/>
             </toolbar>
           </tabpanel>
           <tabpanel id="callstack-tabpanel">
             <vbox id="callstack-list" flex="1"/>
           </tabpanel>
-          <tabpanel id="tracer-tabpanel" flex="1">
-            <deck id="tracer-deck" selectedIndex="1" flex="1">
-              <vbox flex="1">
-                <vbox id="tracer-traces" flex="1">
-                  <hbox class="trace-item-template" hidden="true">
-                    <hbox class="trace-item" align="center" flex="1" crop="end">
-                      <label class="trace-type plain"/>
-                      <label class="trace-name plain" crop="end"/>
-                    </hbox>
-                  </hbox>
-                </vbox>
-                <toolbar id="tracer-toolbar" class="devtools-toolbar">
-                  <toolbarbutton id="clear-tracer"
-                                 label="&debuggerUI.clearButton;"
-                                 tooltiptext="&debuggerUI.clearButton.tooltip;"
-                                 command="clearTraces"
-                                 class="devtools-toolbarbutton"/>
-                  <textbox id="tracer-search"
-                           class="devtools-searchinput"
-                           flex="1"
-                           type="search"/>
-                </toolbar>
-              </vbox>
-              <vbox id="tracer-message"
-                    flex="1"
-                    align="center"
-                    pack="center">
-                <description value="&debuggerUI.tracingNotStarted.label;" />
-              </vbox>
-            </deck>
+          <tabpanel id="tracer-tabpanel">
+            <vbox id="tracer-traces" flex="1"/>
+            <hbox class="trace-item-template" hidden="true">
+              <hbox class="trace-item" align="center" flex="1" crop="end">
+                <label class="trace-type plain"/>
+                <label class="trace-name plain" crop="end"/>
+              </hbox>
+            </hbox>
+            <toolbar id="tracer-toolbar" class="devtools-toolbar">
+              <toolbarbutton id="clear-tracer"
+                             label="&debuggerUI.clearButton;"
+                             tooltiptext="&debuggerUI.clearButton.tooltip;"
+                             command="clearTraces"
+                             class="devtools-toolbarbutton"/>
+              <textbox id="tracer-search"
+                       class="devtools-searchinput"
+                       flex="1"
+                       type="search"/>
+            </toolbar>
           </tabpanel>
         </tabpanels>
       </tabbox>
       <splitter id="sources-and-editor-splitter"
                 class="devtools-side-splitter"/>
       <deck id="editor-deck" flex="4">
         <vbox id="editor"/>
         <vbox id="black-boxed-message" align="center" pack="center">
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -198,16 +198,17 @@ support-files =
 [browser_dbg_stack-07.js]
 [browser_dbg_step-out.js]
 [browser_dbg_tabactor-01.js]
 [browser_dbg_tabactor-02.js]
 [browser_dbg_tracing-01.js]
 [browser_dbg_tracing-02.js]
 [browser_dbg_tracing-03.js]
 [browser_dbg_tracing-04.js]
+[browser_dbg_tracing-05.js]
 [browser_dbg_variables-view-01.js]
 [browser_dbg_variables-view-02.js]
 [browser_dbg_variables-view-03.js]
 [browser_dbg_variables-view-04.js]
 [browser_dbg_variables-view-05.js]
 [browser_dbg_variables-view-accessibility.js]
 [browser_dbg_variables-view-data.js]
 [browser_dbg_variables-view-edit-getset-01.js]
--- a/browser/devtools/debugger/test/browser_dbg_tracing-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-04.js
@@ -2,26 +2,25 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test that when we click on logs, we get the parameters/return value in the variables view.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger, gVariables;
+let gTab, gDebuggee, gPanel, gDebugger;
 
 function test() {
   SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
     initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
       gTab = aTab;
       gDebuggee = aDebuggee;
       gPanel = aPanel;
       gDebugger = gPanel.panelWin;
-      gVariables = gDebugger.DebuggerView.Variables;
 
       waitForSourceShown(gPanel, "code_tracing-01.js")
         .then(() => startTracing(gPanel))
         .then(clickButton)
         .then(() => waitForClientEvents(aPanel, "traces"))
         .then(clickTraceCall)
         .then(testParams)
         .then(clickTraceReturn)
@@ -77,10 +76,9 @@ function testReturn() {
   is(value.getAttribute("value"), "120", "The variable value should be 120");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
-  gVariables = null;
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-05.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that text describing the tracing state is correctly displayed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gTracer, gL10N;
+
+function test() {
+  SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+    initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+      gTab = aTab;
+      gDebuggee = aDebuggee;
+      gPanel = aPanel;
+      gDebugger = gPanel.panelWin;
+      gTracer = gDebugger.DebuggerView.Tracer;
+      gL10N = gDebugger.L10N;
+
+      waitForSourceShown(gPanel, "code_tracing-01.js")
+        .then(testTracingNotStartedText)
+        .then(() => gTracer._onStartTracing())
+        .then(testFunctionCallsUnavailableText)
+        .then(clickButton)
+        .then(() => waitForClientEvents(aPanel, "traces"))
+        .then(testNoEmptyText)
+        .then(() => gTracer._onClear())
+        .then(testFunctionCallsUnavailableText)
+        .then(() => gTracer._onStopTracing())
+        .then(testTracingNotStartedText)
+        .then(() => gTracer._onClear())
+        .then(testTracingNotStartedText)
+        .then(() => {
+          const deferred = promise.defer();
+          SpecialPowers.popPrefEnv(deferred.resolve);
+          return deferred.promise;
+        })
+        .then(() => closeDebuggerAndFinish(gPanel))
+        .then(null, aError => {
+          DevToolsUtils.reportException("browser_dbg_tracing-05.js", aError);
+          ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+        });
+    });
+  });
+}
+
+function testTracingNotStartedText() {
+  let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+  ok(label,
+    "A label is displayed in the tracer tabpanel.");
+  is(label.getAttribute("value"), gL10N.getStr("tracingNotStartedText"),
+    "The correct {{tracingNotStartedText}} is displayed in the tracer tabpanel.");
+}
+
+function testFunctionCallsUnavailableText() {
+  let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+  ok(label,
+    "A label is displayed in the tracer tabpanel.");
+  is(label.getAttribute("value"), gL10N.getStr("noFunctionCallsText"),
+    "The correct {{noFunctionCallsText}} is displayed in the tracer tabpanel.");
+}
+
+function testNoEmptyText() {
+  let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+  ok(!label,
+    "No label should be displayed in the tracer tabpanel.");
+}
+
+function clickButton() {
+  EventUtils.sendMouseEvent({ type: "click" },
+                            gDebuggee.document.querySelector("button"),
+                            gDebuggee);
+}
+
+registerCleanupFunction(function() {
+  gTab = null;
+  gDebuggee = null;
+  gPanel = null;
+  gDebugger = null;
+  gTracer = null;
+  gL10N = null;
+});
--- a/browser/devtools/shared/widgets/FastListWidget.js
+++ b/browser/devtools/shared/widgets/FastListWidget.js
@@ -40,16 +40,17 @@ const FastListWidget = module.exports = 
   this._orderedMenuElementsArray = [];
   this._itemsByElement = new Map();
 
   // This widget emits events that can be handled in a MenuContainer.
   EventEmitter.decorate(this);
 
   // Delegate some of the associated node's methods to satisfy the interface
   // required by MenuContainer instances.
+  ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
   ViewHelpers.delegateWidgetEventMethods(this, aNode);
 }
 
 FastListWidget.prototype = {
   /**
    * Inserts an item in this container at the specified index, optionally
    * grouping by name.
    *
@@ -148,47 +149,43 @@ FastListWidget.prototype = {
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
   getItemAtIndex: function(index) {
     return this._orderedMenuElementsArray[index];
   },
 
   /**
-   * Returns the value of the named attribute on this container.
-   *
-   * @param string name
-   *        The name of the attribute.
-   * @return string
-   *         The current attribute value.
-   */
-  getAttribute: function(name) {
-    return this._parent.getAttribute(name);
-  },
-
-  /**
    * Adds a new attribute or changes an existing attribute on this container.
    *
    * @param string name
    *        The name of the attribute.
    * @param string value
    *        The desired attribute value.
    */
   setAttribute: function(name, value) {
     this._parent.setAttribute(name, value);
+
+    if (name == "emptyText") {
+      this._textWhenEmpty = value;
+    }
   },
 
   /**
    * Removes an attribute on this container.
    *
    * @param string name
    *        The name of the attribute.
    */
   removeAttribute: function(name) {
     this._parent.removeAttribute(name);
+
+    if (name == "emptyText") {
+      this._removeEmptyText();
+    }
   },
 
   /**
    * Ensures the specified element is visible.
    *
    * @param nsIDOMNode element
    *        The element to make visible.
    */
@@ -198,16 +195,56 @@ FastListWidget.prototype = {
     }
 
     // Ensure the element is visible but not scrolled horizontally.
     let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
     boxObject.ensureElementIsVisible(element);
     boxObject.scrollBy(-this._list.clientWidth, 0);
   },
 
+  /**
+   * Sets the text displayed in this container when empty.
+   * @param string aValue
+   */
+  set _textWhenEmpty(aValue) {
+    if (this._emptyTextNode) {
+      this._emptyTextNode.setAttribute("value", aValue);
+    }
+    this._emptyTextValue = aValue;
+    this._showEmptyText();
+  },
+
+  /**
+   * Creates and appends a label signaling that this container is empty.
+   */
+  _showEmptyText: function() {
+    if (this._emptyTextNode || !this._emptyTextValue) {
+      return;
+    }
+    let label = this.document.createElement("label");
+    label.className = "plain fast-list-widget-empty-text";
+    label.setAttribute("value", this._emptyTextValue);
+
+    this._parent.insertBefore(label, this._list);
+    this._emptyTextNode = label;
+  },
+
+  /**
+   * Removes the label signaling that this container is empty.
+   */
+  _removeEmptyText: function() {
+    if (!this._emptyTextNode) {
+      return;
+    }
+    this._parent.removeChild(this._emptyTextNode);
+    this._emptyTextNode = null;
+  },
+
   window: null,
   document: null,
   _parent: null,
   _list: null,
   _selectedItem: null,
   _orderedMenuElementsArray: null,
-  _itemsByElement: null
+  _itemsByElement: null,
+  _emptyTextNode: null,
+  _emptyTextValue: ""
 };
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -290,17 +290,17 @@ SideMenuWidget.prototype = {
     const widgetItem = this._itemsByElement.get(aNode);
     if (!widgetItem) {
       throw new Error("No item for " + aNode);
     }
     widgetItem.check(aCheckState);
   },
 
   /**
-   * Sets the text displayed in this container as a when empty.
+   * Sets the text displayed in this container when empty.
    * @param string aValue
    */
   set _textWhenEmpty(aValue) {
     if (this._emptyTextNode) {
       this._emptyTextNode.setAttribute("value", aValue);
     }
     this._emptyTextValue = aValue;
     this._showEmptyText();
--- a/browser/devtools/shared/widgets/SimpleListWidget.jsm
+++ b/browser/devtools/shared/widgets/SimpleListWidget.jsm
@@ -184,17 +184,17 @@ SimpleListWidget.prototype = {
     if (this._headerTextNode) {
       this._headerTextNode.setAttribute("value", aValue);
     }
     this._headerTextValue = aValue;
     this._showHeaderText();
   },
 
   /**
-   * Sets the text displayed in this container as a when empty.
+   * Sets the text displayed in this container when empty.
    * @param string aValue
    */
   set _textWhenEmpty(aValue) {
     if (this._emptyTextNode) {
       this._emptyTextNode.setAttribute("value", aValue);
     }
     this._emptyTextValue = aValue;
     this._showEmptyText();
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -762,17 +762,21 @@ this.WidgetMethods = {
   },
 
   /**
    * If supported by the widget, the label string temporarily added to this
    * container when there are no child items present.
    */
   set emptyText(aValue) {
     this._emptyText = aValue;
-    this._widget.setAttribute("emptyText", aValue);
+
+    // Apply the emptyText attribute right now if there are no child items.
+    if (!this._itemsByElement.size) {
+      this._widget.setAttribute("emptyText", aValue);
+    }
   },
 
   /**
    * If supported by the widget, the label string permanently added to this
    * container as a header.
    * @param string aValue
    */
   set headerText(aValue) {
--- a/browser/devtools/shared/widgets/widgets.css
+++ b/browser/devtools/shared/widgets/widgets.css
@@ -15,16 +15,22 @@
 
 /* SimpleListWidget */
 
 .simple-list-widget-container {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
+/* FastListWidget */
+
+.fast-list-widget-container {
+  overflow: auto;
+}
+
 /* SideMenuWidget */
 
 .side-menu-widget-container {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
 .side-menu-widget-item-contents {
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -45,20 +45,16 @@
   -  checkbox that toggles auto pretty print. -->
 <!ENTITY debuggerUI.autoPrettyPrint     "Auto Prettify Minified Sources">
 <!ENTITY debuggerUI.autoPrettyPrint.key "P">
 
 <!-- LOCALIZATION NOTE (debuggerUI.sources.toggleBreakpoints): This is the tooltip for the
   -  button that toggles all breakpoints for all sources. -->
 <!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
 
-<!-- LOCALIZATION NOTE (debuggerUI.tracingNotStarted.label): This is the text
-  -  displayed when tracing hasn't started in the debugger UI. -->
-<!ENTITY debuggerUI.tracingNotStarted.label "Tracing has not started.">
-
 <!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
   - the button to start execution tracing. -->
 <!ENTITY debuggerUI.startTracing "Start Tracing">
 
 <!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
   -  the button that clears the collected tracing data in the tracing tab. -->
 <!ENTITY debuggerUI.clearButton "Clear">
 
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -80,16 +80,24 @@ noSourcesText=This page has no sources.
 # LOCALIZATION NOTE (noEventListenersText): The text to display in the events tab
 # when there are no events.
 noEventListenersText=No event listeners to display
 
 # LOCALIZATION NOTE (noStackFramesText): The text to display in the call stack tab
 # when there are no stack frames.
 noStackFramesText=No stack frames to display
 
+# LOCALIZATION NOTE (noStackFramesText): The text to display in the traces tab
+# when there are no function calls.
+noFunctionCallsText=No function calls to display
+
+# LOCALIZATION NOTE (tracingNotStartedText): The text to display in the traces tab
+# when when tracing hasn't started yet.
+tracingNotStartedText=Tracing has not started
+
 # LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
 # the user hovers over the checkbox used to toggle an event breakpoint.
 eventCheckboxTooltip=Toggle breaking on this event
 
 # LOCALIZATION NOTE (eventOnSelector): The text to display in the events tab
 # for every event item, between the event type and event selector.
 eventOnSelector=on
 
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -96,47 +96,27 @@
   list-style-image: url(tracer-icon.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #trace[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#start-tracing {
-  padding: 4px;
-  margin: 4px;
-}
-
 #clear-tracer {
   /* Make this button as narrow as the text inside it. */
   min-width: 1px;
 }
 
-#tracer-message {
-  /* Prevent the container deck from aquiring the height from this message. */
-  min-height: 1px;
-}
-
 .trace-name {
   -moz-padding-start: 4px !important;
 }
 
-#tracer-traces > scrollbox {
-  overflow: scroll;
-  /* Hack to enable hardware acceleration */
-  transform: translateZ(0);
-}
-
 /* Tracer dark theme */
 
-.theme-dark #tracer-message {
-  color: #f5f7fa; /* Light foreground text */
-}
-
 .theme-dark .trace-item {
   color: #f5f7fa; /* Light foreground text */
 }
 
 .theme-dark .trace-item.selected-matching {
   background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
 }
 
@@ -162,20 +142,16 @@
 }
 
 .theme-dark .trace-syntax {
   color: #8fa1b2; /* Content text grey */
 }
 
 /* Tracer light theme */
 
-.theme-light #tracer-message {
-  color: #292e33; /* Dark foreground text */
-}
-
 .theme-light .trace-item {
   color: #292e33; /* Dark foreground text */
 }
 
 .theme-light .trace-item.selected-matching {
   background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
 }
 
--- a/browser/themes/linux/devtools/widgets.css
+++ b/browser/themes/linux/devtools/widgets.css
@@ -278,16 +278,34 @@
 }
 
 .simple-list-widget-perma-text,
 .simple-list-widget-empty-text {
   color: GrayText;
   padding: 4px 8px;
 }
 
+/* FastListWidget */
+
+.fast-list-widget-container {
+  /* Hack: force hardware acceleration */
+  transform: translateZ(1px);
+}
+
+.theme-dark .fast-list-widget-empty-text {
+  padding: 12px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.theme-light .fast-list-widget-empty-text {
+  padding: 4px 8px;
+  color: GrayText;
+}
+
 /* SideMenuWidget */
 
 .side-menu-widget-container {
   /* Hack: force hardware acceleration */
   transform: translateZ(1px);
 }
 
 .side-menu-widget-container[theme="dark"] {
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -98,47 +98,27 @@
   list-style-image: url(tracer-icon.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #trace[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#start-tracing {
-  padding: 4px;
-  margin: 4px;
-}
-
 #clear-tracer {
   /* Make this button as narrow as the text inside it. */
   min-width: 1px;
 }
 
-#tracer-message {
-  /* Prevent the container deck from aquiring the height from this message. */
-  min-height: 1px;
-}
-
 .trace-name {
   -moz-padding-start: 4px !important;
 }
 
-#tracer-traces > scrollbox {
-  overflow: scroll;
-  /* Hack to enable hardware acceleration */
-  transform: translateZ(0);
-}
-
 /* Tracer dark theme */
 
-.theme-dark #tracer-message {
-  color: #f5f7fa; /* Light foreground text */
-}
-
 .theme-dark .trace-item {
   color: #f5f7fa; /* Light foreground text */
 }
 
 .theme-dark .trace-item.selected-matching {
   background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
 }
 
@@ -164,20 +144,16 @@
 }
 
 .theme-dark .trace-syntax {
   color: #8fa1b2; /* Content text grey */
 }
 
 /* Tracer light theme */
 
-.theme-light #tracer-message {
-  color: #292e33; /* Dark foreground text */
-}
-
 .theme-light .trace-item {
   color: #292e33; /* Dark foreground text */
 }
 
 .theme-light .trace-item.selected-matching {
   background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
 }
 
--- a/browser/themes/osx/devtools/widgets.css
+++ b/browser/themes/osx/devtools/widgets.css
@@ -278,16 +278,34 @@
 }
 
 .simple-list-widget-perma-text,
 .simple-list-widget-empty-text {
   color: GrayText;
   padding: 4px 8px;
 }
 
+/* FastListWidget */
+
+.fast-list-widget-container {
+  /* Hack: force hardware acceleration */
+  transform: translateZ(1px);
+}
+
+.theme-dark .fast-list-widget-empty-text {
+  padding: 12px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.theme-light .fast-list-widget-empty-text {
+  padding: 4px 8px;
+  color: GrayText;
+}
+
 /* SideMenuWidget */
 
 .side-menu-widget-container {
   /* Hack: force hardware acceleration */
   transform: translateZ(1px);
 }
 
 .side-menu-widget-container[theme="dark"] {
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -96,47 +96,27 @@
   list-style-image: url(tracer-icon.png);
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #trace[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#start-tracing {
-  padding: 4px;
-  margin: 4px;
-}
-
 #clear-tracer {
   /* Make this button as narrow as the text inside it. */
   min-width: 1px;
 }
 
-#tracer-message {
-  /* Prevent the container deck from aquiring the height from this message. */
-  min-height: 1px;
-}
-
 .trace-name {
   -moz-padding-start: 4px !important;
 }
 
-#tracer-traces > scrollbox {
-  overflow: scroll;
-  /* Hack to enable hardware acceleration */
-  transform: translateZ(0);
-}
-
 /* Tracer dark theme */
 
-.theme-dark #tracer-message {
-  color: #f5f7fa; /* Light foreground text */
-}
-
 .theme-dark .trace-item {
   color: #f5f7fa; /* Light foreground text */
 }
 
 .theme-dark .trace-item.selected-matching {
   background-color: rgba(29, 79, 115, .4); /* Select highlight blue at 40% alpha */
 }
 
@@ -162,20 +142,16 @@
 }
 
 .theme-dark .trace-syntax {
   color: #8fa1b2; /* Content text grey */
 }
 
 /* Tracer light theme */
 
-.theme-light #tracer-message {
-  color: #292e33; /* Dark foreground text */
-}
-
 .theme-light .trace-item {
   color: #292e33; /* Dark foreground text */
 }
 
 .theme-light .trace-item.selected-matching {
   background-color: rgba(76, 158, 217, .4); /* Select highlight blue at 40% alpha */
 }
 
--- a/browser/themes/windows/devtools/widgets.css
+++ b/browser/themes/windows/devtools/widgets.css
@@ -282,16 +282,34 @@
 }
 
 .simple-list-widget-perma-text,
 .simple-list-widget-empty-text {
   color: GrayText;
   padding: 4px 8px;
 }
 
+/* FastListWidget */
+
+.fast-list-widget-container {
+  /* Hack: force hardware acceleration */
+  transform: translateZ(1px);
+}
+
+.theme-dark .fast-list-widget-empty-text {
+  padding: 12px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.theme-light .fast-list-widget-empty-text {
+  padding: 4px 8px;
+  color: GrayText;
+}
+
 /* SideMenuWidget */
 
 .side-menu-widget-container {
   /* Hack: force hardware acceleration */
   transform: translateZ(1px);
 }
 
 .side-menu-widget-container[theme="dark"] {