Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 30 Jul 2015 16:03:18 -0400
changeset 287073 b9d3529d35d37320d2713878fe0d41646fc752fb
parent 287060 2f635ac96f94fec418f95287858344b77f2cbd44 (current diff)
parent 287072 39fc38df750d11a792cd3cf2e10e712074588576 (diff)
child 287133 6a16c1845238d0bf7963dd18c42ec27ba8348047
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.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
Merge fx-team to m-c. a=merge
toolkit/devtools/LayoutHelpers.jsm
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5707,49 +5707,40 @@
                              xbl:inherits="fadein,pinned,selected,visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
       </xul:stack>
     </content>
 
     <implementation>
 
-      <!--
-          This code is the same as in toolkit/content/widgets/tabbox.xml,
-          except for the _tabAttrModified call. If you modify it, tabbox.xml
-          might also need to be modified.
-      -->
       <property name="_visuallySelected">
         <setter>
           <![CDATA[
           if (val)
             this.setAttribute("visuallyselected", "true");
           else
             this.removeAttribute("visuallyselected");
-          gBrowser._tabAttrModified(this, ["visuallyselected"]);
-
-          if (this.previousSibling && this.previousSibling.localName == "tab") {
-            if (val)
-              this.previousSibling.setAttribute("beforeselected", "true");
-            else
-              this.previousSibling.removeAttribute("beforeselected");
-            this.removeAttribute("first-tab");
-          }
+          this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
+
+          this._setPositionAttributes(val);
+
+          return val;
+          ]]>
+        </setter>
+      </property>
+
+      <property name="_logicallySelected">
+        <setter>
+          <![CDATA[
+          if (val)
+            this.setAttribute("selected", "true");
           else
-            this.setAttribute("first-tab", "true");
-
-          if (this.nextSibling && this.nextSibling.localName == "tab") {
-            if (val)
-              this.nextSibling.setAttribute("afterselected", "true");
-            else
-              this.nextSibling.removeAttribute("afterselected");
-            this.removeAttribute("last-tab");
-          }
-          else
-            this.setAttribute("last-tab", "true");
+            this.removeAttribute("selected");
+
           return val;
           ]]>
         </setter>
       </property>
 
       <property name="_selected">
         <setter>
           <![CDATA[
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -25,16 +25,18 @@ const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
+const PROMISE_DEBUGGER_URL =
+  "chrome://browser/content/devtools/promisedebugger/promise-debugger.xhtml";
 
 /**
  * Object defining the debugger view components.
  */
 let DebuggerView = {
   /**
    * Initializes the debugger view.
    *
@@ -88,16 +90,17 @@ let DebuggerView = {
     this.Filtering.destroy();
     this.StackFrames.destroy();
     this.StackFramesClassicList.destroy();
     this.Sources.destroy();
     this.VariableBubble.destroy();
     this.WatchExpressions.destroy();
     this.EventListeners.destroy();
     this.GlobalSearch.destroy();
+    this._destroyPromiseDebugger();
     this._destroyPanes();
     this._destroyEditor(deferred.resolve);
 
     return deferred.promise;
   },
 
   /**
    * Initializes the UI for all the displayed panes.
@@ -190,16 +193,38 @@ let DebuggerView = {
         case "properties":
           window.emit(EVENTS.FETCHED_PROPERTIES);
           break;
       }
     });
   },
 
   /**
+   * Initialie the Promise Debugger instance.
+   */
+  _initializePromiseDebugger: function() {
+    let iframe = this._promiseDebuggerIframe = document.createElement("iframe");
+    iframe.setAttribute("flex", 1);
+    iframe.setAttribute("src", PROMISE_DEBUGGER_URL);
+    this._promisePane.appendChild(iframe);
+  },
+
+  /**
+   * Destroy the Promise Debugger instance.
+   */
+  _destroyPromiseDebugger: function() {
+    if (this._promiseDebuggerIframe) {
+      this._promiseDebuggerIframe.parentNode.removeChild(
+        this._promiseDebuggerIframe);
+
+      this._promiseDebuggerIframe = null;
+    }
+  },
+
+  /**
    * Initializes the Editor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
   _initializeEditor: function(aCallback) {
     dumpn("Initializing the DebuggerView editor");
 
--- a/browser/devtools/debugger/views/sources-view.js
+++ b/browser/devtools/debugger/views/sources-view.js
@@ -659,16 +659,18 @@ SourcesView.prototype = Heritage.extend(
     this._togglePauseOnExceptionsButton.setAttribute("tooltiptext", tooltip);
     this._togglePauseOnExceptionsButton.setAttribute("state", state);
   },
 
   togglePromiseDebugger: function() {
     if (Prefs.promiseDebuggerEnabled) {
       let promisePane = this.DebuggerView._promisePane;
       promisePane.hidden = !promisePane.hidden;
+
+      this.DebuggerView._initializePromiseDebugger();
     }
   },
 
   hidePrettyPrinting: function() {
     this._prettyPrintButton.style.display = 'none';
 
     if (this._blackBoxButton.style.display === 'none') {
       let sep = document.querySelector('#sources-toolbar .devtools-separator');
--- a/browser/devtools/framework/selection.js
+++ b/browser/devtools/framework/selection.js
@@ -3,17 +3,17 @@
 /* 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 {Cu, Ci} = require("chrome");
 let EventEmitter = require("devtools/toolkit/event-emitter");
-Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
+let LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 /**
  * API
  *
  *   new Selection(walker=null, node=null, track={attributes,detached});
  *   destroy()
  *   node (readonly)
  *   setNode(node, origin="unknown")
--- a/browser/devtools/inspector/test/doc_frame_script.js
+++ b/browser/devtools/inspector/test/doc_frame_script.js
@@ -8,17 +8,18 @@
 //
 // Most listeners in the script expect "Test:"-namespaced messages from chrome,
 // then execute code upon receiving, and immediately send back a message.
 // This is so that chrome test code can execute code in content and wait for a
 // response.
 // Some listeners do not send a response message back.
 
 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-let {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {});
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let LayoutHelpers = devtools.require("devtools/toolkit/layout-helpers");
 let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
             .getService(Ci.mozIJSSubScriptLoader);
 let EventUtils = {};
 loader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
 
 /**
  * If the test page creates and triggeres the custom event
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -108,16 +108,18 @@ browser.jar:
     content/browser/devtools/performance/views/details-js-call-tree.js      (performance/views/details-js-call-tree.js)
     content/browser/devtools/performance/views/details-js-flamegraph.js     (performance/views/details-js-flamegraph.js)
     content/browser/devtools/performance/views/details-memory-call-tree.js  (performance/views/details-memory-call-tree.js)
     content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
     content/browser/devtools/performance/views/details-optimizations.js     (performance/views/details-optimizations.js)
     content/browser/devtools/performance/views/optimizations-list.js        (performance/views/optimizations-list.js)
     content/browser/devtools/performance/views/frames-list.js               (performance/views/frames-list.js)
     content/browser/devtools/performance/views/recordings.js           (performance/views/recordings.js)
+    content/browser/devtools/promisedebugger/promise-debugger.js       (promisedebugger/promise-debugger.js)
+    content/browser/devtools/promisedebugger/promise-debugger.xhtml    (promisedebugger/promise-debugger.xhtml)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
 *   content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
     content/browser/devtools/framework/toolbox.xul                     (framework/toolbox.xul)
     content/browser/devtools/framework/options-panel.css               (framework/options-panel.css)
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -26,18 +26,18 @@ const {HTMLEditor} = require("devtools/m
 const promise = require("resource://gre/modules/Promise.jsm").Promise;
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const Heritage = require("sdk/core/heritage");
 const {setTimeout, clearTimeout, setInterval, clearInterval} = require("sdk/timers");
 const {parseAttribute} = require("devtools/shared/node-attribute-parser");
 const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
 const {Task} = require("resource://gre/modules/Task.jsm");
-
-Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
+
 Cu.import("resource://gre/modules/devtools/Templater.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
  return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
 loader.lazyGetter(this, "AutocompletePopup", () => {
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -14,16 +14,17 @@ DIRS += [
     'fontinspector',
     'framework',
     'inspector',
     'layoutview',
     'markupview',
     'netmonitor',
     'performance',
     'projecteditor',
+    'promisedebugger',
     'responsivedesign',
     'scratchpad',
     'shadereditor',
     'shared',
     'sourceeditor',
     'storage',
     'styleeditor',
     'styleinspector',
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+EXTRA_JS_MODULES.devtools.promisedebugger += [
+    'promise-debugger.js'
+]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/promise-debugger.js
@@ -0,0 +1,7 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/promise-debugger.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mkozilla 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/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % promisedebuggerDTD SYSTEM "chrome://browser/locale/devtools/promisedebugger.dtd">
+  %promisedebuggerDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>&title;</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://browser/skin/devtools/promisedebugger.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
+  </head>
+  <body class="devtools-monospace" role="application">
+    <script type="application/javascript;version=1.8" src="promise-debugger.js"></script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/test/.eslintrc
@@ -0,0 +1,4 @@
+{
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../.eslintrc.mochitests"
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/test/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/promisedebugger/test/head.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
--- a/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
+++ b/browser/devtools/shared/test/browser_layoutHelpers-getBoxQuads.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that LayoutHelpers.getAdjustedQuads works properly in a variety of use
 // cases including iframes, scroll and zoom
 
 const {utils: Cu} = Components;
-const {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {});
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers-getBoxQuads.html";
 
 function test() {
   addTab(TEST_URI, function(browser, tab) {
     let doc = browser.contentDocument;
     let win = doc.defaultView;
 
--- a/browser/devtools/shared/test/browser_layoutHelpers.js
+++ b/browser/devtools/shared/test/browser_layoutHelpers.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that scrollIntoViewIfNeeded works properly.
-let {LayoutHelpers} = Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", {});
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 
 const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers.html";
 
 add_task(function*() {
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
   runTest(win);
   host.destroy();
--- a/browser/devtools/shared/widgets/Graphs.js
+++ b/browser/devtools/shared/widgets/Graphs.js
@@ -2,25 +2,24 @@
  * 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 { Cc, Ci, Cu, Cr } = require("chrome");
 
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const { Heritage, setNamedTimeout, clearNamedTimeout } = require("resource:///modules/devtools/ViewHelpers.jsm");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 loader.lazyImporter(this, "DevToolsWorker",
   "resource://gre/modules/devtools/shared/worker.js");
-loader.lazyImporter(this, "LayoutHelpers",
-  "resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
 const WORKER_URL = "resource:///modules/devtools/GraphsWorker.js";
 
 // Generic constants.
 
 const GRAPH_RESIZE_EVENTS_DRAIN = 100; // ms
--- a/browser/devtools/tilt/test/head.js
+++ b/browser/devtools/tilt/test/head.js
@@ -4,20 +4,18 @@
 
 let {devtools} = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TiltManager = devtools.require("devtools/tilt/tilt").TiltManager;
 let TiltGL = devtools.require("devtools/tilt/tilt-gl");
 let {EPSILON, TiltMath, vec3, mat3, mat4, quat4} = devtools.require("devtools/tilt/tilt-math");
 let TiltUtils = devtools.require("devtools/tilt/tilt-utils");
 let {TiltVisualizer} = devtools.require("devtools/tilt/tilt-visualizer");
 let DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils");
+let LayoutHelpers = devtools.require("devtools/toolkit/layout-helpers");
 
-let tempScope = {};
-Components.utils.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tempScope);
-let LayoutHelpers = tempScope.LayoutHelpers;
 
 const DEFAULT_HTML = "data:text/html," +
   "<DOCTYPE html>" +
   "<html>" +
     "<head>" +
       "<meta charset='utf-8'/>" +
       "<title>Three Laws</title>" +
     "</head>" +
--- a/browser/devtools/tilt/tilt-utils.js
+++ b/browser/devtools/tilt/tilt-utils.js
@@ -4,17 +4,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/. */
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
+let LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 const STACK_THICKNESS = 15;
 
 /**
  * Module containing various helper functions used throughout Tilt.
  */
 this.TiltUtils = {};
 module.exports = this.TiltUtils;
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/promisedebugger.dtd
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the Promise debugger panel
+     strings. The Promise debugger panel is part of the debugger -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
+
+<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
+  - keep it in English, or another language commonly spoken among web developers.
+  - You want to make that choice consistent across the developer tools.
+  - A good criteria is the language in which you'd find the best
+  - documentation on web development on the web. -->
+
+<!ENTITY title "Promise Debugger">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/promisedebugger.properties
@@ -0,0 +1,11 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used inside the Promise debugger
+# which is available as a panel in the Debugger.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -56,16 +56,18 @@
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
     locale/browser/devtools/VariablesView.dtd         (%chrome/browser/devtools/VariablesView.dtd)
     locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/devtools/sourceeditor.dtd          (%chrome/browser/devtools/sourceeditor.dtd)
     locale/browser/devtools/profiler.dtd              (%chrome/browser/devtools/profiler.dtd)
     locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
+    locale/browser/devtools/promisedebugger.dtd       (%chrome/browser/devtools/promisedebugger.dtd)
+    locale/browser/devtools/promisedebugger.properties  (%chrome/browser/devtools/promisedebugger.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/toolbox.properties     (%chrome/browser/devtools/toolbox.properties)
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
     locale/browser/devtools/timeline.dtd           (%chrome/browser/devtools/timeline.dtd)
     locale/browser/devtools/timeline.properties    (%chrome/browser/devtools/timeline.properties)
     locale/browser/devtools/projecteditor.properties     (%chrome/browser/devtools/projecteditor.properties)
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -334,16 +334,17 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
   skin/classic/browser/devtools/animationinspector.css          (../shared/devtools/animationinspector.css)
 * skin/classic/browser/devtools/canvasdebugger.css    (devtools/canvasdebugger.css)
 * skin/classic/browser/devtools/debugger.css          (devtools/debugger.css)
   skin/classic/browser/devtools/eyedropper.css        (../shared/devtools/eyedropper.css)
 * skin/classic/browser/devtools/netmonitor.css        (devtools/netmonitor.css)
 * skin/classic/browser/devtools/performance.css       (devtools/performance.css)
+  skin/classic/browser/devtools/promisedebugger.css   (../shared/devtools/promisedebugger.css)
   skin/classic/browser/devtools/timeline-filter.svg   (../shared/devtools/images/timeline-filter.svg)
 * skin/classic/browser/devtools/scratchpad.css        (devtools/scratchpad.css)
 * skin/classic/browser/devtools/shadereditor.css      (devtools/shadereditor.css)
 * skin/classic/browser/devtools/splitview.css         (../shared/devtools/splitview.css)
   skin/classic/browser/devtools/styleeditor.css       (../shared/devtools/styleeditor.css)
   skin/classic/browser/devtools/storage.css           (../shared/devtools/storage.css)
 * skin/classic/browser/devtools/webaudioeditor.css    (devtools/webaudioeditor.css)
   skin/classic/browser/devtools/magnifying-glass.png        (../shared/devtools/images/magnifying-glass.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -436,16 +436,17 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
   skin/classic/browser/devtools/animationinspector.css          (../shared/devtools/animationinspector.css)
 * skin/classic/browser/devtools/canvasdebugger.css          (devtools/canvasdebugger.css)
 * skin/classic/browser/devtools/debugger.css                (devtools/debugger.css)
   skin/classic/browser/devtools/eyedropper.css              (../shared/devtools/eyedropper.css)
 * skin/classic/browser/devtools/netmonitor.css              (devtools/netmonitor.css)
 * skin/classic/browser/devtools/performance.css             (devtools/performance.css)
+  skin/classic/browser/devtools/promisedebugger.css         (../shared/devtools/promisedebugger.css)
   skin/classic/browser/devtools/timeline-filter.svg         (../shared/devtools/images/timeline-filter.svg)
 * skin/classic/browser/devtools/scratchpad.css              (devtools/scratchpad.css)
 * skin/classic/browser/devtools/shadereditor.css            (devtools/shadereditor.css)
 * skin/classic/browser/devtools/splitview.css               (../shared/devtools/splitview.css)
   skin/classic/browser/devtools/styleeditor.css             (../shared/devtools/styleeditor.css)
   skin/classic/browser/devtools/storage.css                 (../shared/devtools/storage.css)
 * skin/classic/browser/devtools/webaudioeditor.css          (devtools/webaudioeditor.css)
   skin/classic/browser/devtools/magnifying-glass.png        (../shared/devtools/images/magnifying-glass.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/promisedebugger.css
@@ -0,0 +1,3 @@
+/* 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/. */
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -449,16 +449,17 @@ browser.jar:
         skin/classic/browser/devtools/breadcrumbs-scrollbutton.png  (../shared/devtools/images/breadcrumbs-scrollbutton.png)
         skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
         skin/classic/browser/devtools/animationinspector.css        (../shared/devtools/animationinspector.css)
         skin/classic/browser/devtools/eyedropper.css                (../shared/devtools/eyedropper.css)
 *       skin/classic/browser/devtools/canvasdebugger.css            (devtools/canvasdebugger.css)
 *       skin/classic/browser/devtools/debugger.css                  (devtools/debugger.css)
 *       skin/classic/browser/devtools/netmonitor.css                (devtools/netmonitor.css)
 *       skin/classic/browser/devtools/performance.css               (devtools/performance.css)
+        skin/classic/browser/devtools/promisedebugger.css           (../shared/devtools/promisedebugger.css)
         skin/classic/browser/devtools/timeline-filter.svg           (../shared/devtools/images/timeline-filter.svg)
 *       skin/classic/browser/devtools/scratchpad.css                (devtools/scratchpad.css)
 *       skin/classic/browser/devtools/shadereditor.css              (devtools/shadereditor.css)
         skin/classic/browser/devtools/storage.css                   (../shared/devtools/storage.css)
 *       skin/classic/browser/devtools/splitview.css                 (../shared/devtools/splitview.css)
         skin/classic/browser/devtools/styleeditor.css               (../shared/devtools/styleeditor.css)
 *       skin/classic/browser/devtools/webaudioeditor.css            (devtools/webaudioeditor.css)
         skin/classic/browser/devtools/magnifying-glass.png          (../shared/devtools/images/magnifying-glass.png)
--- a/js/xpconnect/tests/unit/test_isModuleLoaded.js
+++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js
@@ -1,33 +1,33 @@
 const Cu = Components.utils;
 
 function run_test() {
   // Existing module.
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
-  Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
-  do_check_true(Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"),
+  Cu.import("resource://gre/modules/ISO8601DateUtils.jsm");
+  do_check_true(Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
                 "isModuleLoaded returned true after loading that module");
-  Cu.unload("resource://gre/modules/devtools/LayoutHelpers.jsm");
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers.jsm"),
+  Cu.unload("resource://gre/modules/ISO8601DateUtils.jsm");
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"),
                 "isModuleLoaded returned false after unloading that module");
 
   // Non-existing module
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers1.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
   try {
-    Cu.import("resource://gre/modules/devtools/LayoutHelpers1.jsm");
+    Cu.import("resource://gre/modules/ISO8601DateUtils1.jsm");
     do_check_true(false,
                   "Should have thrown while trying to load a non existing file");
   } catch (ex) {}
-  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/devtools/LayoutHelpers1.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
 
   // incorrect url
   try {
-    Cu.isModuleLoaded("resource://modules/devtools/LayoutHelpers1.jsm");
+    Cu.isModuleLoaded("resource://modules/ISO8601DateUtils1.jsm");
     do_check_true(false,
                   "Should have thrown while trying to load a non existing file");
   } catch (ex) {
     do_check_true(true, "isModuleLoaded threw an exception while loading incorrect uri");
   }
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3983,16 +3983,17 @@ pref("font.name.serif.x-unicode", "dt-in
 pref("font.name.sans-serif.x-unicode", "dt-interface system-ucs2.cjk_japan-0");
 pref("font.name.monospace.x-unicode", "dt-interface user-ucs2.cjk_japan-0");
 
 # AIX
 #endif
 
 // Login Manager prefs
 pref("signon.rememberSignons",              true);
+pref("signon.rememberSignons.visibilityToggle", true);
 pref("signon.autofillForms",                true);
 pref("signon.autologin.proxy",              false);
 pref("signon.storeWhenAutocompleteOff",     true);
 pref("signon.ui.experimental",              false);
 pref("signon.debug",                        false);
 pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
 
 // Satchel (Form Manager) prefs
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -12,328 +12,782 @@ const { AddonManager } = Cu.import("reso
 const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
 const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 
 // about:performance observes notifications on this topic.
 // if a notification is sent, this causes the page to be updated immediately,
 // regardless of whether the page is on pause.
-const UPDATE_IMMEDIATELY_TOPIC = "about:performance-update-immediately";
+const TEST_DRIVER_TOPIC = "test-about:performance-test-driver";
 
 // about:performance posts notifications on this topic whenever the page
 // is updated.
 const UPDATE_COMPLETE_TOPIC = "about:performance-update-complete";
 
-/**
- * The various measures we display.
- */
-const MEASURES = [
-  {probe: "jank", key: "longestDuration", percentOfDeltaT: false, label: "Jank level"},
-  {probe: "jank", key: "totalUserTime", percentOfDeltaT: true, label: "User (%)"},
-  {probe: "jank", key: "totalSystemTime", percentOfDeltaT: true, label: "System (%)"},
-  {probe: "cpow", key: "totalCPOWTime", percentOfDeltaT: true, label: "Cross-Process (%)"},
-  {probe: "ticks",key: "ticks", percentOfDeltaT: false, label: "Activations"},
-];
+// How often we should add a sample to our buffer.
+const BUFFER_SAMPLING_RATE_MS = 1000;
+
+// The age of the oldest sample to keep.
+const BUFFER_DURATION_MS = 10000;
+
+// How often we should update
+const UPDATE_INTERVAL_MS = 5000;
+
+// The name of the application
+const BRAND_BUNDLE = Services.strings.createBundle(
+  "chrome://branding/locale/brand.properties");
+const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
+
+// The maximal number of items to display before showing a "Show All"
+// button.
+const MAX_NUMBER_OF_ITEMS_TO_DISPLAY = 3;
+
+// If the frequency of alerts is below this value,
+// we consider that the feature has no impact.
+const MAX_FREQUENCY_FOR_NO_IMPACT = .05;
+// If the frequency of alerts is above `MAX_FREQUENCY_FOR_NO_IMPACT`
+// and below this value, we consider that the feature impacts the
+// user rarely.
+const MAX_FREQUENCY_FOR_RARE = .1;
+// If the frequency of alerts is above `MAX_FREQUENCY_FOR_FREQUENT`
+// and below this value, we consider that the feature impacts the
+// user frequently. Anything above is consider permanent.
+const MAX_FREQUENCY_FOR_FREQUENT = .5;
+
+// If the number of high-impact alerts among all alerts is above
+// this value, we consider that the feature has a major impact
+// on user experience.
+const MIN_PROPORTION_FOR_MAJOR_IMPACT = .05;
+// Otherwise and if the number of medium-impact alerts among all
+// alerts is above this value, we consider that the feature has
+// a noticeable impact on user experience.
+const MIN_PROPORTION_FOR_NOTICEABLE_IMPACT = .1;
+
+// The current mode. Either `MODE_GLOBAL` to display a summary of results
+// since we opened about:performance or `MODE_RECENT` to display the latest
+// BUFFER_DURATION_MS ms.
+const MODE_GLOBAL = "global";
+const MODE_RECENT = "recent";
 
 /**
- * Used to control the live updates in the performance page.
+ * Find the <xul:tab> for a window id.
+ *
+ * This is useful e.g. for reloading or closing tabs.
+ *
+ * @return null If the xul:tab could not be found, e.g. if the
+ * windowId is that of a chrome window.
+ * @return {{tabbrowser: <xul:tabbrowser>, tab: <xul.tab>}} The
+ * tabbrowser and tab if the latter could be found.
  */
-let AutoUpdate = {
+function findTabFromWindow(windowId) {
+  let windows = Services.wm.getEnumerator("navigator:browser");
+  while (windows.hasMoreElements()) {
+    let win = windows.getNext();
+    let tabbrowser = win.gBrowser;
+    let foundBrowser = tabbrowser.getBrowserForOuterWindowID(windowId);
+    if (foundBrowser) {
+      return {tabbrowser, tab: tabbrowser.getTabForBrowser(foundBrowser)};
+    }
+  }
+  return null;
+}
 
+/**
+ * Utilities for dealing with PerformanceDiff
+ */
+let Delta = {
+  compare: function(a, b) {
+    // By definition, if either `a` or `b` has CPOW time and the other
+    // doesn't, it is larger.
+    if (a.cpow.totalCPOWTime && !b.cpow.totalCPOWTime) {
+      return 1;
+    }
+    if (b.cpow.totalCPOWTime && !a.cpow.totalCPOWTime) {
+      return -1;
+    }
+    return (
+      (a.jank.longestDuration - b.jank.longestDuration) ||
+      (a.jank.totalUserTime - b.jank.totalUserTime) ||
+      (a.jank.totalSystemTime - b.jank.totalSystemTime) ||
+      (a.cpow.totalCPOWTime - b.cpow.totalCPOWTime) ||
+      (a.ticks.ticks - b.ticks.ticks) ||
+      0
+    );
+  },
+  revCompare: function(a, b) {
+    return -Delta.compare(a, b);
+  },
   /**
-   * The timer that is created when setInterval is called.
+   * The highest value considered "good performance".
+   */
+  MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE: {
+    cpow: {
+      totalCPOWTime: 0,
+    },
+    jank: {
+      longestDuration: 3,
+      totalUserTime: Number.POSITIVE_INFINITY,
+      totalSystemTime: Number.POSITIVE_INFINITY
+    },
+    ticks: {
+      ticks: Number.POSITIVE_INFINITY,
+    }
+  },
+  /**
+   * The highest value considered "average performance".
    */
-  _timerId: null,
+  MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE: {
+    cpow: {
+      totalCPOWTime: Number.POSITIVE_INFINITY,
+    },
+    jank: {
+      longestDuration: 7,
+      totalUserTime: Number.POSITIVE_INFINITY,
+      totalSystemTime: Number.POSITIVE_INFINITY
+    },
+    ticks: {
+      ticks: Number.POSITIVE_INFINITY,
+    }
+  }
+}
+
+/**
+ * Utilities for dealing with state
+ */
+let State = {
+  _monitor: PerformanceStats.getMonitor(["jank", "cpow", "ticks"]),
 
   /**
-   * The dropdown DOM element.
+   * Indexed by the number of minutes since the snapshot was taken.
+   *
+   * @type {Array<{snapshot: PerformanceData, date: Date}>}
+   */
+  _buffer: [],
+  /**
+   * The first snapshot since opening the page.
+   *
+   * @type {snapshot: PerformnceData, date: Date}
    */
-  _intervalDropdown: null,
+  _oldest: null,
+
+  /**
+   * The latest snapshot.
+   *
+   * @type As _oldest
+   */
+  _latest: null,
+
+  /**
+   * The performance alerts for each group.
+   *
+   * This map is cleaned up during each update to avoid leaking references
+   * to groups that have been gc-ed.
+   *
+   * @type{Map<string, Array<number>} A map in which keys are
+   * values for `delta.groupId` and values are arrays
+   * [number of moderate-impact alerts, number of high-impact alerts]
+   */
+  _alerts: new Map(),
 
   /**
-   * Starts updating the performance data if the updates are paused.
+   * The date at which each group was first seen.
+   *
+   * This map is cleaned up during each update to avoid leaking references
+   * to groups that have been gc-ed.
+   *
+   * @type{Map<string, Date} A map in which keys are
+   * values for `delta.groupId` and values are approximate
+   * dates at which the group was first encountered.
    */
-  start: function () {
-    if (AutoUpdate._intervalDropdown == null){
-      AutoUpdate._intervalDropdown = document.getElementById("intervalDropdown");
-    }
-
-    if (AutoUpdate._timerId == null) {
-      let dropdownIndex = AutoUpdate._intervalDropdown.selectedIndex;
-      let dropdownValue = AutoUpdate._intervalDropdown.options[dropdownIndex].value;
-      AutoUpdate._timerId = window.setInterval(update, dropdownValue);
-    }
-  },
+  _firstSeen: new Map(),
 
   /**
-   * Stops the updates if the data is updating.
+   * Produce an annotated performance snapshot.
+   *
+   * @return {Object} An object extending `PerformanceDiff`
+   * with the following fields:
+   *  {Date} date The current date.
+   *  {Map<groupId, performanceData>} A map of performance data,
+   *  for faster access.
    */
-  stop: function () {
-    if (AutoUpdate._timerId == null) {
-      return;
+  _promiseSnapshot: Task.async(function*() {
+    let snapshot = yield this._monitor.promiseSnapshot();
+    snapshot.date = Date.now();
+    let componentsMap = new Map();
+    snapshot.componentsMap = componentsMap;
+    for (let component of snapshot.componentsData) {
+      componentsMap.set(component.groupId, component);
     }
-    clearInterval(AutoUpdate._timerId);
-    AutoUpdate._timerId = null;
+    return snapshot;
+  }),
+
+  /**
+   * Classify the "slowness" of a component.
+   *
+   * @return {number} 0 For a "fast" component.
+   * @return {number} 1 For an "average" component.
+   * @return {number} 2 For a "slow" component.
+   */
+  _getSlowness: function(component) {
+    if (Delta.compare(component, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
+      return 0;
+    }
+    if (Delta.compare(component, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE) <= 0) {
+      return 1;
+    }
+    return 2;
   },
 
   /**
-   * Updates the refresh interval when the dropdown selection is changed.
-   */
-  updateRefreshRate: function () {
-    AutoUpdate.stop();
-    AutoUpdate.start();
-  }
-
-};
-
-let State = {
-  _monitor: PerformanceStats.getMonitor([
-    "jank", "cpow", "ticks",
-    "jank-content", "cpow-content", "ticks-content",
-  ]),
-
-  /**
-   * @type{PerformanceData}
-   */
-  _processData: null,
-  /**
-   * A mapping from name to PerformanceData
-   *
-   * @type{Map}
-   */
-  _componentsData: new Map(),
-
-  /**
-   * A number of milliseconds since the high-performance epoch.
-   */
-  _date: window.performance.now(),
-
-  /**
-   * Fetch the latest information, compute diffs.
+   * Update the internal state.
    *
    * @return {Promise}
-   * @resolve An object with the following fields:
-   * - `components`: an array of `PerformanceDiff` representing
-   *   the components, sorted by `longestDuration`, then by `totalUserTime`
-   * - `process`: a `PerformanceDiff` representing the entire process;
-   * - `deltaT`: the number of milliseconds elapsed since the data
-   *   was last displayed.
    */
   update: Task.async(function*() {
-    let snapshot = yield this._monitor.promiseSnapshot();
-    let newData = new Map();
-    let deltas = [];
-    for (let componentNew of snapshot.componentsData) {
-      let key = componentNew.groupId;
-      let componentOld = State._componentsData.get(key);
-      deltas.push(componentNew.subtract(componentOld));
-      newData.set(key, componentNew);
+    // If the buffer is empty, add one value for bootstraping purposes.
+    if (this._buffer.length == 0) {
+      if (this._oldest) {
+        throw new Error("Internal Error, we shouldn't have a `_oldest` value yet.");
+      }
+      this._latest = this._oldest = yield this._promiseSnapshot();
+      this._buffer.push(this._oldest);
+      yield new Promise(resolve => setTimeout(resolve, BUFFER_SAMPLING_RATE_MS * 1.1));
     }
-    State._componentsData = newData;
-    let now = window.performance.now();
-    let process = snapshot.processData.subtract(State._processData);
-    let result = {
-      components: deltas.filter(x => x.ticks.ticks > 0),
-      process: snapshot.processData.subtract(State._processData),
-      deltaT: now - State._date
-    };
-    result.components.sort((a, b) => {
-      if (a.longestDuration < b.longestDuration) {
-        return true;
-      }
-      if (a.longestDuration == b.longestDuration) {
-        return a.totalUserTime <= b.totalUserTime
-      }
-      return false;
-    });
-    State._processData = snapshot.processData;
-    State._date = now;
-    return result;
-  })
-};
 
 
-let update = Task.async(function*() {
-  yield updateLiveData();
-  yield updateSlowAddons();
-  Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, "");
-});
+    let now = Date.now();
+    
+    // If we haven't sampled in a while, add a sample to the buffer.
+    let latestInBuffer = this._buffer[this._buffer.length - 1];
+    let deltaT = now - latestInBuffer.date;
+    if (deltaT > BUFFER_SAMPLING_RATE_MS) {
+      this._latest = this._oldest = yield this._promiseSnapshot();
+      this._buffer.push(this._latest);
+
+      // Update alerts
+      let cleanedUpAlerts = new Map(); // A new map of alerts, without dead components.
+      for (let component of this._latest.componentsData) {
+        let slowness = this._getSlowness(component, deltaT);
+        let myAlerts = this._alerts.get(component.groupId) || [0, 0];
+        if (slowness > 0) {
+          myAlerts[slowness - 1]++;
+        }
+        cleanedUpAlerts.set(component.groupId, myAlerts);
+        this._alerts = cleanedUpAlerts;
+      }
+    }
+
+    // If we have too many samples, remove the oldest sample.
+    let oldestInBuffer = this._buffer[0];
+    if (oldestInBuffer.date + BUFFER_DURATION_MS < this._latest.date) {
+      this._buffer.shift();
+    }
+  }),
+
+  /**
+   * @return {Promise}
+   */
+  promiseDeltaSinceStartOfTime: function() {
+    return this._promiseDeltaSince(this._oldest);
+  },
+
+  /**
+   * @return {Promise}
+   */
+  promiseDeltaSinceStartOfBuffer: function() {
+    return this._promiseDeltaSince(this._buffer[0]);
+  },
+
+  /**
+   * @return {Promise}
+   */
+  _promiseDeltaSince: Task.async(function*(oldest) {
+    let current = this._latest;
+    if (!oldest) {
+      throw new TypeError();
+    }
+    if (!current) {
+      throw new TypeError();
+    }
 
-/**
- * Update the list of slow addons
- */
-let updateSlowAddons = Task.async(function*() {
-  try {
-    let data = AddonWatcher.alerts;
-    if (data.size == 0) {
-      // Nothing to display.
-      return;
-    }
-    let alerts = 0;
-    for (let [addonId, details] of data) {
-      for (let k of Object.keys(details.alerts)) {
-        alerts += details.alerts[k];
+    let addons = [];
+    let webpages = [];
+    let system = [];
+    let groups = new Set();
+
+    // We rebuild the map during each iteration to make sure that
+    // we do not maintain references to groups that has been removed
+    // (e.g. pages that have been closed).
+    let oldFirstSeen = this._firstSeen;
+    let cleanedUpFirstSeen = new Map();
+    this._firstSeen = cleanedUpFirstSeen;
+    for (let component of current.componentsData) {
+      // Enrich `delta` with `alerts`.
+      let delta = component.subtract(oldest.componentsMap.get(component.groupId));
+      delta.alerts = (this._alerts.get(component.groupId) || []).slice();
+
+      // Enrich `delta` with `age`.
+      let creationDate = oldFirstSeen.get(component.groupId) || current.date;
+      cleanedUpFirstSeen.set(component.groupId, creationDate);
+      delta.age = current.date - creationDate;
+
+      groups.add(component.groupId);
+
+      // Enrich `delta` with `fullName` and `readableName`.
+      delta.fullName = delta.name;
+      delta.readableName = delta.name;
+      if (component.addonId) {
+        let found = yield new Promise(resolve =>
+          AddonManager.getAddonByID(delta.addonId, a => {
+            if (a) {
+              delta.readableName = a.name;
+              resolve(true);
+            } else {
+              resolve(false);
+            }
+          }));
+        delta.fullName = delta.addonId;
+        if (found) {
+          // If the add-on manager doesn't know about an add-on, it's
+          // probably not a real add-on.
+          addons.push(delta);
+        }
+      } else if (!delta.isSystem || delta.title) {
+        // Wallpaper hack. For some reason, about:performance (and only about:performance)
+        // appears twice in the list. Only one of them is a window.
+        if (delta.title == document.title) {
+          if (!findTabFromWindow(delta.windowId)) {
+            // Not a real page.
+            system.push(delta);
+            continue;
+          }
+        }
+        delta.readableName = delta.title || delta.name;
+        webpages.push(delta);
+      } else {
+        system.push(delta);
       }
     }
 
-    if (!alerts) {
-      // Still nothing to display.
-      return;
-    }
-
-
-    let elData = document.getElementById("slowAddonsList");
-    elData.innerHTML = "";
-    let elTable = document.createElement("table");
-    elData.appendChild(elTable);
+    return {
+      addons,
+      webpages,
+      system,
+      groups,
+      duration: current.date - oldest.date
+    };
+  }),
+};
 
-    // Generate header
-    let elHeader = document.createElement("tr");
-    elTable.appendChild(elHeader);
-    for (let name of [
-      "Alerts",
-      "Jank level alerts",
-      "(highest jank)",
-      "Cross-Process alerts",
-      "(highest CPOW)"
-    ]) {
-      let elName = document.createElement("td");
-      elName.textContent = name;
-      elHeader.appendChild(elName);
-      elName.classList.add("header");
-    }
-    for (let [addonId, details] of data) {
-      let elAddon = document.createElement("tr");
-
-      // Display the number of occurrences of each alerts
-      let elTotal = document.createElement("td");
-      let total = 0;
-      for (let k of Object.keys(details.alerts)) {
-        total += details.alerts[k];
-      }
-      elTotal.textContent = total;
-      elAddon.appendChild(elTotal);
-
-      for (let filter of ["longestDuration", "totalCPOWTime"]) {
-        for (let stat of ["alerts", "peaks"]) {
-          let el = document.createElement("td");
-          el.textContent = details[stat][filter] || 0;
-          elAddon.appendChild(el);
+let View = {
+  /**
+   * A cache for all the per-item DOM elements that are reused across refreshes.
+   *
+   * Reusing the same elements means that elements that were hidden (respectively
+   * visible) in an iteration remain hidden (resp visible) in the next iteration.
+   */
+  DOMCache: {
+    _map: new Map(),
+    /**
+     * @param {string} groupId The groupId for the item that we are displaying.
+     * @return {null} If the `groupId` doesn't have a component cached yet.
+     * Otherwise, the value stored with `set`.
+     */
+    get: function(groupId) {
+      return this._map.get(groupId);
+    },
+    set: function(groupId, value) {
+      this._map.set(groupId, value);
+    },
+    /**
+     * Remove all the elements whose key does not appear in `set`.
+     *
+     * @param {Set} set a set of groupId.
+     */
+    trimTo: function(set) {
+      let remove = [];
+      for (let key of this._map.keys()) {
+        if (!set.has(key)) {
+          remove.push(key);
         }
       }
-
-      // Display the name of the add-on
-      let elName = document.createElement("td");
-      elAddon.appendChild(elName);
-      AddonManager.getAddonByID(addonId, a => {
-        elName.textContent = a ? a.name : addonId
-      });
-
-      elTable.appendChild(elAddon);
+      for (let key of remove) {
+        this._map.delete(key);
+      }
     }
-  } catch (ex) {
-    console.error(ex);
-  }
-});
-
-/**
- * Update the table of live data.
- */
-let updateLiveData = Task.async(function*() {
-  try {
-    let dataElt = document.getElementById("liveData");
-    dataElt.innerHTML = "";
+  },
+  /**
+   * Display the items in a category.
+   *
+   * @param {Array<PerformanceDiff>} subset The items to display. They will
+   * be displayed in the order of `subset`.
+   * @param {string} id The id of the DOM element that will contain the items.
+   * @param {string} nature The nature of the subset. One of "addons", "webpages" or "system".
+   * @param {string} currentMode The current display mode. One of MODE_GLOBAL or MODE_RECENT.
+   */
+  updateCategory: function(subset, id, nature, deltaT, currentMode) {
+    subset = subset.slice().sort(Delta.revCompare);
 
-    // Generate table headers
-    let headerElt = document.createElement("tr");
-    dataElt.appendChild(headerElt);
-    headerElt.classList.add("header");
-    for (let column of [...MEASURES, {key: "name", name: ""}, {key: "process", name: ""}]) {
-      let el = document.createElement("td");
-      el.classList.add(column.key);
-      el.textContent = column.label;
-      headerElt.appendChild(el);
-    }
-
-    let deltas = yield State.update();
-
-    for (let item of [deltas.process, ...deltas.components]) {
-      let row = document.createElement("tr");
-      if (item.addonId) {
-        row.classList.add("addon");
-      } else if (item.isSystem) {
-        row.classList.add("platform");
-      } else {
-        row.classList.add("content");
-      }
-      dataElt.appendChild(row);
+    // Grab everything from the DOM before cleaning up
+    let eltContainer = this._setupStructure(id);
 
-      // Measures
-      for (let {probe, key, percentOfDeltaT} of MEASURES) {
-        let el = document.createElement("td");
-        el.classList.add(key);
-        el.classList.add("contents");
-        row.appendChild(el);
-
-        let rawValue = item[probe][key];
-        let value = percentOfDeltaT ? Math.round(rawValue / deltas.deltaT) : rawValue;
-        if (key == "longestDuration") {
-          value += 1;
-          el.classList.add("jank" + value);
+    // An array of `cachedElements` that need to be added
+    let toAdd = [];
+    for (let delta of subset) {
+      let cachedElements = this._grabOrCreateElements(delta, nature);
+      toAdd.push(cachedElements);
+      cachedElements.eltTitle.textContent = delta.readableName;
+      cachedElements.eltName.textContent = `Full name: ${delta.fullName}.`;
+      cachedElements.eltLoaded.textContent = `Measure start: ${Math.round(delta.age/1000)} seconds ago.`
+      cachedElements.eltProcess.textContent = `Process: ${delta.processId} (${delta.isChildProcess?"child":"parent"}).`;
+      let eltImpact = cachedElements.eltImpact;
+      if (currentMode == MODE_RECENT) {
+        cachedElements.eltRoot.setAttribute("impact", delta.jank.longestDuration + 1);
+        if (Delta.compare(delta, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
+          eltImpact.textContent = ` currently performs well.`;
+        } else if (Delta.compare(delta, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE)) {
+          eltImpact.textContent = ` may currently be slowing down ${BRAND_NAME}.`;
+        } else {
+          eltImpact.textContent = ` is currently considerably slowing down ${BRAND_NAME}.`;
         }
-        el.textContent = value;
-      }
+        cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.jank.longestDuration + 1}/${delta.jank.durations.length}.`;
+        cachedElements.eltCPU.textContent = `CPU usage: ${Math.min(100, Math.ceil(delta.jank.totalUserTime/deltaT))}%.`;
+        cachedElements.eltSystem.textContent = `System usage: ${Math.min(100, Math.ceil(delta.jank.totalSystemTime/deltaT))}%.`;
+        cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/deltaT)}%.`;
+      } else {
+        if (delta.alerts.length == 0) {
+          eltImpact.textContent = " has performed well so far.";
+          cachedElements.eltFPS.textContent = `Impact on framerate: no impact.`;
+        } else {
+          let sum = /* medium impact */ delta.alerts[0] + /* high impact */ delta.alerts[1];
+          let frequency = sum * 1000 / delta.age;
 
-      {
-        // Name
-        let el = document.createElement("td");
-        let id = item.id;
-        el.classList.add("contents");
-        el.classList.add("name");
-        row.appendChild(el);
-        if (item.addonId) {
-          let _el = el;
-          let _item = item;
-          AddonManager.getAddonByID(item.addonId, a => {
-            _el.textContent = a ? a.name : _item.name
-          });
-        } else {
-          el.textContent = item.title || item.name;
-        }
-      }
+          let describeFrequency;
+          if (frequency <= MAX_FREQUENCY_FOR_NO_IMPACT) {
+            describeFrequency = `has no impact on the performance of ${BRAND_NAME}.`
+            cachedElements.eltRoot.classList.add("impact0");
+          } else {
+            let describeImpact;
+            if (frequency <= MAX_FREQUENCY_FOR_RARE) {
+              describeFrequency = `rarely slows down ${BRAND_NAME}.`;
+            } else if (frequency <= MAX_FREQUENCY_FOR_FREQUENT) {
+              describeFrequency = `has slown down ${BRAND_NAME} frequently.`;
+            } else {
+              describeFrequency = `seems to have slown down ${BRAND_NAME} very often.`;
+            }
+            // At this stage, `sum != 0`
+            if (delta.alerts[1] / sum > MIN_PROPORTION_FOR_MAJOR_IMPACT) {
+              describeImpact = "When this happens, the slowdown is generally important."
+            } else {
+              describeImpact = "When this happens, the slowdown is generally noticeable."
+            }
 
-      {
-        // Process information.
-        let el = document.createElement("td");
-        el.classList.add("contents");
-        el.classList.add("process");
-        row.appendChild(el);
-        if (item.isChildProcess) {
-          el.textContent = "(child)";
-          row.classList.add("child");
-        } else {
-          el.textContent = "(parent)";
-          row.classList.add("parent");
+            eltImpact.textContent = ` ${describeFrequency} ${describeImpact}`;
+            cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.alerts[1]} high-impacts, ${delta.alerts[0]} medium-impact.`;
+          }
+          cachedElements.eltCPU.textContent = `CPU usage: ${Math.min(100, Math.ceil(delta.jank.totalUserTime/delta.age))}% (total ${delta.jank.totalUserTime}ms).`;
+          cachedElements.eltSystem.textContent = `System usage: ${Math.min(100, Math.ceil(delta.jank.totalSystemTime/delta.age))}% (total ${delta.jank.totalSystemTime}ms).`;
+          cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/delta.age)}% (total ${delta.cpow.totalCPOWTime}ms).`;
         }
       }
     }
-  } catch (ex) {
-    console.error(ex);
-  }
-});
+    this._insertElements(toAdd, id);
+  },
+
+  _insertElements: function(elements, id) {
+    let eltContainer = document.getElementById(id);
+    eltContainer.classList.remove("measuring");
+    eltContainer.eltVisibleContent.innerHTML = "";
+    eltContainer.eltHiddenContent.innerHTML = "";
+    eltContainer.appendChild(eltContainer.eltShowMore);
+
+    for (let i = 0; i < elements.length && i < MAX_NUMBER_OF_ITEMS_TO_DISPLAY; ++i) {
+      let cachedElements = elements[i];
+      eltContainer.eltVisibleContent.appendChild(cachedElements.eltRoot);
+    }
+    for (let i = MAX_NUMBER_OF_ITEMS_TO_DISPLAY; i < elements.length; ++i) {
+      let cachedElements = elements[i];
+      eltContainer.eltHiddenContent.appendChild(cachedElements.eltRoot);
+    }
+    if (elements.length <= MAX_NUMBER_OF_ITEMS_TO_DISPLAY) {
+      eltContainer.eltShowMore.classList.add("hidden");
+    } else {
+      eltContainer.eltShowMore.classList.remove("hidden");
+    }
+    if (elements.length == 0) {
+      eltContainer.textContent = "Nothing";
+    }
+  },
+  _setupStructure: function(id) {
+    let eltContainer = document.getElementById(id);
+    if (!eltContainer.eltVisibleContent) {
+      eltContainer.eltVisibleContent = document.createElement("ul");
+      eltContainer.eltVisibleContent.classList.add("visible_items");
+      eltContainer.appendChild(eltContainer.eltVisibleContent);
+    }
+    if (!eltContainer.eltHiddenContent) {
+      eltContainer.eltHiddenContent = document.createElement("ul");
+      eltContainer.eltHiddenContent.classList.add("hidden");
+      eltContainer.eltHiddenContent.classList.add("hidden_additional_items");
+      eltContainer.appendChild(eltContainer.eltHiddenContent);
+    }
+    if (!eltContainer.eltShowMore) {
+      eltContainer.eltShowMore = document.createElement("button");
+      eltContainer.eltShowMore.textContent = "Show all";
+      eltContainer.eltShowMore.classList.add("show_all_items");
+      eltContainer.appendChild(eltContainer.eltShowMore);
+      eltContainer.eltShowMore.addEventListener("click", function() {
+        if (eltContainer.eltHiddenContent.classList.contains("hidden")) {
+          eltContainer.eltHiddenContent.classList.remove("hidden");
+          eltContainer.eltShowMore.textContent = "Hide";
+        } else {
+          eltContainer.eltHiddenContent.classList.add("hidden");
+          eltContainer.eltShowMore.textContent = "Show all";
+        }
+      });
+    }
+    return eltContainer;
+  },
+
+  _grabOrCreateElements: function(delta, nature) {
+    let cachedElements = this.DOMCache.get(delta.groupId);
+    if (cachedElements) {
+      if (cachedElements.eltRoot.parentElement) {
+        cachedElements.eltRoot.parentElement.removeChild(cachedElements.eltRoot);
+      }
+    } else {
+      this.DOMCache.set(delta.groupId, cachedElements = {});
+
+      let eltDelta = document.createElement("li");
+      eltDelta.classList.add("delta");
+      cachedElements.eltRoot = eltDelta;
+
+      let eltSpan = document.createElement("span");
+      eltDelta.appendChild(eltSpan);
 
-function go() {
-  document.getElementById("playButton").addEventListener("click", () => AutoUpdate.start());
-  document.getElementById("pauseButton").addEventListener("click", () => AutoUpdate.stop());
+      let eltSummary = document.createElement("span");
+      eltSummary.classList.add("summary");
+      eltSpan.appendChild(eltSummary);
+
+      let eltTitle = document.createElement("span");
+      eltTitle.classList.add("title");
+      eltSummary.appendChild(eltTitle);
+      cachedElements.eltTitle = eltTitle;
+
+      let eltImpact = document.createElement("span");
+      eltImpact.classList.add("impact");
+      eltSummary.appendChild(eltImpact);
+      cachedElements.eltImpact = eltImpact;
+
+      let eltShowMore = document.createElement("a");
+      eltShowMore.classList.add("more");
+      eltSpan.appendChild(eltShowMore);
+      eltShowMore.textContent = "more";
+      eltShowMore.href = "";
+      eltShowMore.addEventListener("click", () => {
+        if (eltDetails.classList.contains("hidden")) {
+          eltDetails.classList.remove("hidden");
+          eltShowMore.textContent = "less";
+        } else {
+          eltDetails.classList.add("hidden");
+          eltShowMore.textContent = "more";
+        }
+      });
+
+      // Add buttons
+      if (nature == "addons") {
+        eltSpan.appendChild(document.createElement("br"));
+        let eltDisable = document.createElement("button");
+        eltDisable.textContent = "Disable";
+        eltSpan.appendChild(eltDisable);
 
-  document.getElementById("intervalDropdown").addEventListener("change", () => AutoUpdate.updateRefreshRate());
+        let eltUninstall = document.createElement("button");
+        eltUninstall.textContent = "Uninstall";
+        eltSpan.appendChild(eltUninstall);
+
+        let eltRestart = document.createElement("button");
+        eltRestart.textContent = `Restart ${BRAND_NAME} to apply your changes.`
+        eltRestart.classList.add("hidden");
+        eltSpan.appendChild(eltRestart);
+
+        eltRestart.addEventListener("click", () => {
+          Services.startup.quit(Services.startup.eForceQuit | Services.startup.eRestart);
+        });
+        AddonManager.getAddonByID(delta.addonId, addon => {
+          eltDisable.addEventListener("click", () => {
+            addon.userDisabled = true;
+            if (addon.pendingOperations == addon.PENDING_NONE) {
+              // Restartless add-on is now disabled.
+              return;
+            }
+            eltDisable.classList.add("hidden");
+            eltUninstall.classList.add("hidden");
+            eltRestart.classList.remove("hidden");
+          });
+
+          eltUninstall.addEventListener("click", () => {
+            addon.uninstall();
+            if (addon.pendingOperations == addon.PENDING_NONE) {
+              // Restartless add-on is now disabled.
+              return;
+            }
+            eltDisable.classList.add("hidden");
+            eltUninstall.classList.add("hidden");
+            eltRestart.classList.remove("hidden");
+          });
+        });
+      } else if (nature == "webpages") {
+        eltSpan.appendChild(document.createElement("br"));
 
-  // Compute initial state immediately, then wait a little
-  // before we start computing diffs and refreshing.
-  State.update();
-  window.setTimeout(update, 500);
+        let eltCloseTab = document.createElement("button");
+        eltCloseTab.textContent = "Close tab";
+        eltSpan.appendChild(eltCloseTab);
+        let windowId = delta.windowId;
+        eltCloseTab.addEventListener("click", () => {
+          let found = findTabFromWindow(windowId);
+          if (!found) {
+            // Cannot find the tab. Maybe it is closed already?
+            return;
+          }
+          let {tabbrowser, tab} = found;
+          tabbrowser.removeTab(tab);
+        });
+
+        let eltReloadTab = document.createElement("button");
+        eltReloadTab.textContent = "Reload tab";
+        eltSpan.appendChild(eltReloadTab);
+        eltReloadTab.addEventListener("click", () => {
+          let found = findTabFromWindow(windowId);
+          if (!found) {
+            // Cannot find the tab. Maybe it is closed already?
+            return;
+          }
+          let {tabbrowser, tab} = found;
+          tabbrowser.reloadTab(tab);
+        });
+      }
+
+      // Prepare details
+      let eltDetails = document.createElement("ul");
+      eltDetails.classList.add("details");
+      eltDetails.classList.add("hidden");
+      eltSpan.appendChild(eltDetails);
+
+      for (let [name, className] of [
+        ["eltName", "name"],
+        ["eltFPS", "fps"],
+        ["eltCPU", "cpu"],
+        ["eltSystem", "system"],
+        ["eltCPOW", "cpow"],
+        ["eltLoaded", "loaded"],
+        ["eltProcess", "process"]
+      ]) {
+        let elt = document.createElement("li");
+        elt.classList.add(className);
+        eltDetails.appendChild(elt);
+        cachedElements[name] = elt;
+      }
+    }
+
+    return cachedElements;
+  },
+};
+
+let Control = {
+  init: function() {
+    this._initAutorefresh();
+    this._initDisplayMode();
+  },
+  update: Task.async(function*() {
+    let mode = this._displayMode;
+    if (this._autoRefreshInterval) {
+      // Update the state only if we are not on pause.
+      yield State.update();
+    }
+    let state = yield (mode == MODE_GLOBAL?
+      State.promiseDeltaSinceStartOfTime():
+      State.promiseDeltaSinceStartOfBuffer());
+
+    for (let category of ["webpages", "addons"]) {
+      yield Promise.resolve();
+      yield View.updateCategory(state[category], category, category, state.duration, mode);
+    }
+    yield Promise.resolve();
 
-  let observer = update;
-  
-  Services.obs.addObserver(update, UPDATE_IMMEDIATELY_TOPIC, false);
-  window.addEventListener("unload", () => Services.obs.removeObserver(update, UPDATE_IMMEDIATELY_TOPIC));
-}
+    // Make sure that we do not keep obsolete stuff around.
+    View.DOMCache.trimTo(state.groups);
+
+    // Inform watchers
+    Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, mode);
+  }),
+  _setOptions: function(options) {
+    let eltRefresh = document.getElementById("check-autorefresh");
+    if ((options.autoRefresh > 0) != eltRefresh.checked) {
+      eltRefresh.click();
+    }
+    let eltCheckRecent = document.getElementById("check-display-recent");
+    if (!!options.displayRecent != eltCheckRecent.checked) {
+      eltCheckRecent.click();
+    }
+  },
+  _initAutorefresh: function() {
+    let onRefreshChange = (shouldUpdateNow = false) => {
+      if (eltRefresh.checked == !!this._autoRefreshInterval) {
+        // Nothing to change.
+        return;
+      }
+      if (eltRefresh.checked) {
+        this._autoRefreshInterval = window.setInterval(() => Control.update(), UPDATE_INTERVAL_MS);
+        if (shouldUpdateNow) {
+          Control.update();
+        }
+      } else {
+        window.clearInterval(this._autoRefreshInterval);
+        this._autoRefreshInterval = null;
+      }
+    }
+
+    let eltRefresh = document.getElementById("check-autorefresh");
+    eltRefresh.addEventListener("change", () => onRefreshChange(true));
+
+    onRefreshChange(false);
+  },
+  _autoRefreshInterval: null,
+  _initDisplayMode: function() {
+    let onModeChange = (shouldUpdateNow) => {
+      if (eltCheckRecent.checked) {
+        this._displayMode = MODE_RECENT;
+      } else {
+        this._displayMode = MODE_GLOBAL;
+      }
+      if (shouldUpdateNow) {
+        Control.update();
+      }
+    };
+
+    let eltCheckRecent = document.getElementById("check-display-recent");
+    let eltLabelRecent = document.getElementById("label-display-recent");
+    eltCheckRecent.addEventListener("click", () => onModeChange(true));
+    eltLabelRecent.textContent = `Display only the latest ${Math.round(BUFFER_DURATION_MS/1000)}s`;
+
+    onModeChange(false);
+  },
+  // The display mode. One of `MODE_GLOBAL` or `MODE_RECENT`.
+  _displayMode: MODE_GLOBAL,
+};
+
+let go = Task.async(function*() {
+  Control.init();
+
+  // Setup a hook to allow tests to configure and control this page
+  let testUpdate = function(subject, topic, value) {
+    let options = JSON.parse(value);
+    Control._setOptions(options);
+    Control.update();
+  };
+  Services.obs.addObserver(testUpdate, TEST_DRIVER_TOPIC, false);
+  window.addEventListener("unload", () => Services.obs.removeObserver(testUpdate, TEST_DRIVER_TOPIC));
+
+  yield Control.update();
+  yield new Promise(resolve => setTimeout(resolve, BUFFER_SAMPLING_RATE_MS * 1.1));
+  yield Control.update();
+});
--- a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -4,106 +4,110 @@
    - 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/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>about:performance</title>
     <script type="text/javascript;version=1.8" src="chrome://global/content/aboutPerformance.js"></script>
     <style>
-      td.addon {
-        display: inline-block;
-        width: 400px;
-      }
-      td.time {
-        display: inline-block;
-        width: 100px;
-      }
-      td.cpow {
-        display: inline-block;
-        width: 100px;
-      }
-      .header {
-        font-weight: bold;
-      }
-      tr.details {
-        font-weight: lighter;
-        color: gray;
+      @import url("chrome://global/skin/in-content/common.css");
+      .hidden {
         display: none;
       }
-      tr.addon {
-        background-color: white;
-      }
-      tr.platform {
-        background-color: rgb(255, 255, 200);
-      }
-      tr.content {
-        background-color: rgb(200, 255, 255);
-      }
-      td.jank0 {
-        color: rgb(0, 0, 0);
+      .summary .title {
         font-weight: bold;
       }
-      td.jank1 {
-        color: rgb(25, 0, 0);
-        font-weight: bold;
+      a {
+        text-decoration: none;
+      }
+      a.more {
+        margin-left: 2ch;
       }
-      td.jank2 {
-        color: rgb(50, 0, 0);
-        font-weight: bold;
+      ul.hidden_additional_items {
+        padding-top: 0;
+        margin-top: 0;
+      }
+      ul.visible_items {
+        padding-bottom: 0;
+        margin-bottom: 0;
       }
-      td.jank3 {
-        color: rgb(75, 0, 0);
-        font-weight: bold;
+      li.delta {
+        margin-top: .5em;
+      }
+      h2 {
+        margin-top: 1cm;
       }
-      td.jank4 {
-        color: rgb(100, 0, 0);
-        font-weight: bold;
+      button.show_all_items {
+        margin-top: .5cm;
+        margin-left: 1cm;
       }
-      td.jank5 {
-        color: rgb(125, 0, 0);
-        font-weight: bold;
+      body {
+        margin-left: 1cm;
+      }
+      div.measuring {
+         background: url(chrome://global/skin/media/throbber.png) no-repeat center;
+         min-width: 36px;
+         min-height: 36px;
       }
-      td.jank6 {
-        color: rgb(150, 0, 0);
-        font-weight: bold;
+      li.delta {
+        border-left-width: 5px;
+        border-left-style: solid;
+        padding-left: 1em;
+        list-style: none;
+      }
+      li.delta[impact="0"] {
+        border-left-color: rgb(0, 255, 0);
       }
-      td.jank7 {
-        color: rgb(175, 0, 0);
-        font-weight: bold;
+      li.delta[impact="1"] {
+        border-left-color: rgb(24, 231, 0);
+      }
+      li.delta[impact="2"] {
+        border-left-color: rgb(48, 207, 0);
+      }
+      li.delta[impact="3"] {
+        border-left-color: rgb(72, 183, 0);
+      }
+      li.delta[impact="4"] {
+        border-left-color: rgb(96, 159, 0);
       }
-      td.jank8 {
-        color: rgb(200, 0, 0);
-        font-weight: bold;
+      li.delta[impact="5"] {
+        border-left-color: rgb(120, 135, 0);
+      }
+      li.delta[impact="6"] {
+        border-left-color: rgb(144, 111, 0);
+      }
+      li.delta[impact="7"] {
+        border-left-color: rgb(168, 87, 0);
       }
-      td.jank9 {
-        color: rgb(225, 0, 0);
-        font-weight: bold;
+      li.delta[impact="8"] {
+        border-left-color: rgb(192, 63, 0);
+      }
+      li.delta[impact="9"] {
+        border-left-color: rgb(216, 39, 0);
       }
-      td.jank10 {
-        color: rgb(255, 0, 0);
-        font-weight: bold;
+      li.delta[impact="10"] {
+        border-left-color: rgb(240, 15, 0);
+      }
+      li.delta[impact="11"] {
+        border-left-color: rgb(255, 0, 0);
       }
     </style>
   </head>
   <body onload="go()">
-
-    <h1>Performance monitor</h1>
-
-    <input type="button" id="playButton" value="Play" />
-    <input type="button" id="pauseButton" value="Pause" />
-    <select id="intervalDropdown">
-      <option value="500">0.5 seconds</option>
-      <option value="1000">1 second</option>
-      <option value="2000">2 seconds</option>
-      <option value="5000" selected="selected">5 seconds</option>
-      <option value="10000">10 seconds</option>
-    </select>
-    <table id="liveData">
-    </table>
-
-    <h1>Slow add-ons alerts</h1>
-    <div id="slowAddonsList">
-      (none)
+    <div>
+      <input type="checkbox" checked="false" id="check-display-recent"></input> 
+      <label for="check-display-recent" id="label-display-recent">Display only the last few seconds.</label>
+      <input type="checkbox" checked="true" id="check-autorefresh"></input>
+      <label for="check-autorefresh">Refresh automatically</label>
     </div>
-
+    <div>
+      <h2>Performance of Add-ons</h2>
+      <div id="addons" class="measuring">
+      </div>
+    </div>
+    <div>
+      <h2>Performance of Web pages</h2>
+      <div id="webpages" class="measuring">
+      </div>
+    </div>
   </body>
 </html>
--- a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
+++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js
@@ -15,73 +15,89 @@ function frameScript() {
     content.postMessage("stop", "*");
     sendAsyncMessage("aboutperformance-test:done", null);
   });
   addMessageListener("aboutperformance-test:setTitle", ({data: title}) => {
     content.document.title = title;
     sendAsyncMessage("aboutperformance-test:setTitle", null);
   });
   
-  addMessageListener("aboutperformance-test:hasItems", ({data: title}) => {
-    let observer = function() {
+  addMessageListener("aboutperformance-test:hasItems", ({data: {title, options}}) => {
+    let observer = function(subject, topic, mode) {
       Services.obs.removeObserver(observer, "about:performance-update-complete");
-      let hasPlatform = false;
-      let hasTitle = false;
+      let hasTitleInWebpages = false;
+      let hasTitleInAddons = false;
 
       try {
-        let eltData = content.document.getElementById("liveData");
-        if (!eltData) {
+        let eltWeb = content.document.getElementById("webpages");
+        let eltAddons = content.document.getElementById("addons");
+        if (!eltWeb || !eltAddons) {
           return;
         }
 
-        // Find if we have a row for "platform"
-        hasPlatform = eltData.querySelector("tr.platform") != null;
+        let addonTitles = [for (eltContent of eltAddons.querySelectorAll("span.title")) eltContent.textContent];
+        let webTitles = [for (eltContent of eltWeb.querySelectorAll("span.title")) eltContent.textContent];
 
-        // Find if we have a row for our content page
-        let titles = [for (eltContent of eltData.querySelectorAll("td.contents.name")) eltContent.textContent];
+        hasTitleInAddons = addonTitles.includes(title);
+        hasTitleInWebpages = webTitles.includes(title);
 
-        hasTitle = titles.includes(title);
       } catch (ex) {
         Cu.reportError("Error in content: " + ex);
         Cu.reportError(ex.stack);
       } finally {
-        sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasTitle});
+        sendAsyncMessage("aboutperformance-test:hasItems", {hasTitleInAddons, hasTitleInWebpages, mode});
       }
     }
     Services.obs.addObserver(observer, "about:performance-update-complete", false);
-    Services.obs.notifyObservers(null, "about:performance-update-immediately", "");
+    Services.obs.notifyObservers(null, "test-about:performance-test-driver", JSON.stringify(options));
   });
 }
 
+let gTabAboutPerformance = null;
+let gTabContent = null;
 
-add_task(function* go() {
+add_task(function* init() {
   info("Setting up about:performance");
-  let tabAboutPerformance = gBrowser.selectedTab = gBrowser.addTab("about:performance");
-  yield ContentTask.spawn(tabAboutPerformance.linkedBrowser, null, frameScript);
+  gTabAboutPerformance = gBrowser.selectedTab = gBrowser.addTab("about:performance");
+  yield ContentTask.spawn(gTabAboutPerformance.linkedBrowser, null, frameScript);
 
   info(`Setting up ${URL}`);
-  let tabContent = gBrowser.addTab(URL);
-  yield ContentTask.spawn(tabContent.linkedBrowser, null, frameScript);
+  gTabContent = gBrowser.addTab(URL);
+  yield ContentTask.spawn(gTabContent.linkedBrowser, null, frameScript);
+});
 
+let promiseExpectContent = Task.async(function*(options) {
   let title = "Testing about:performance " + Math.random();
-  info(`Setting up title ${title}`);
-  while (true) {
-    yield promiseContentResponse(tabContent.linkedBrowser, "aboutperformance-test:setTitle", title);
+  for (let i = 0; i < 30; ++i) {
+    yield promiseContentResponse(gTabContent.linkedBrowser, "aboutperformance-test:setTitle", title);
+    let {hasTitleInWebpages, hasTitleInAddons, mode} = (yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", {title, options}));
+    if (hasTitleInWebpages && ((mode == "recent") == options.displayRecent)) {
+      Assert.ok(!hasTitleInAddons, "The title appears in webpages, but not in addons");
+      return true;
+    }
+    info(`Title not found, trying again ${i}/30`);
     yield new Promise(resolve => setTimeout(resolve, 100));
-    let {hasPlatform, hasTitle} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", title));
-    info(`Platform: ${hasPlatform}, title: ${hasTitle}`);
-    if (hasPlatform && hasTitle) {
-      Assert.ok(true, "Found a row for <platform> and a row for our page");
-      break;
+  }
+  return false;
+});
+
+add_task(function* tests() {
+    for (let autoRefresh of [100, -1]) {
+      for (let displayRecent of [true, false]) {
+        info(`Testing ${autoRefresh > 0?"with":"without"} autoRefresh, in ${displayRecent?"recent":"global"} mode`);
+        let found = yield promiseExpectContent({autoRefresh, displayRecent});
+        Assert.equal(found, autoRefresh > 0, "The page title appears iff about:performance is set to auto-refresh");
+      }
     }
-  }
+});
 
+add_task(function* cleanup() {
   // Cleanup
   info("Cleaning up");
-  yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:done", null);
+  yield promiseContentResponse(gTabAboutPerformance.linkedBrowser, "aboutperformance-test:done", null);
 
   info("Closing tabs");
   for (let tab of gBrowser.tabs) {
     yield BrowserTestUtils.removeTab(tab);
   }
 
   info("Done");
   gBrowser.selectedTab = null;
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -842,24 +842,31 @@ LoginManagerPrompter.prototype = {
       if (element) {
         element.setAttribute("buttonlabel", label);
         element.setAttribute("buttonaccesskey", accessKey);
         updateButtonStatus(element);
       }
     };
 
     let writeDataToUI = () => {
+      // setAttribute is used since the <textbox> binding may not be attached yet.
       chromeDoc.getElementById("password-notification-username")
                .setAttribute("placeholder", usernamePlaceholder);
       chromeDoc.getElementById("password-notification-username")
                .setAttribute("value", login.username);
-      chromeDoc.getElementById("password-notification-password")
-               .setAttribute("value", login.password);
-      chromeDoc.getElementById("password-notification-password")
-               .setAttribute("show-content", showPasswordPlaceholder);
+
+      let passwordField = chromeDoc.getElementById("password-notification-password");
+      // Ensure the type is reset so the field is masked.
+      passwordField.setAttribute("type", "password");
+      passwordField.setAttribute("value", login.password);
+      if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
+        passwordField.setAttribute("show-content", showPasswordPlaceholder);
+      } else {
+        passwordField.setAttribute("show-content", "");
+      }
       updateButtonLabel();
     };
 
     let readDataFromUI = () => {
       login.username =
         chromeDoc.getElementById("password-notification-username").value;
       login.password =
         chromeDoc.getElementById("password-notification-password").value;
@@ -875,23 +882,24 @@ LoginManagerPrompter.prototype = {
       // Gets the caret position before changing the type of the textbox
       let selectionStart = passwordField.selectionStart;
       let selectionEnd = passwordField.selectionEnd;
       if (focusEvent.rangeParent != null) {
         // Check for a click over the SHOW placeholder
         selectionStart = passwordField.value.length;
         selectionEnd = passwordField.value.length;
       }
-      passwordField.type = "";
+      passwordField.setAttribute("type", "");
       passwordField.selectionStart = selectionStart;
       passwordField.selectionEnd = selectionEnd;
     };
 
     let onPasswordBlur = () => {
-      chromeDoc.getElementById("password-notification-password").type = "password";
+      // Use setAttribute in case the <textbox> binding isn't applied.
+      chromeDoc.getElementById("password-notification-password").setAttribute("type", "password");
     };
 
     let onNotificationClick = (clickEvent) => {
       // Removes focus from textboxes when we click elsewhere on the doorhanger.
       let focusedElement = Services.focus.focusedElement;
       if (!focusedElement || focusedElement.nodeName != "html:input") {
         // No input is focused so we don't need to blur
         return;
@@ -979,18 +987,20 @@ LoginManagerPrompter.prototype = {
         eventCallback: function (topic) {
           switch (topic) {
             case "showing":
               currentNotification = this;
               chromeDoc.getElementById("password-notification-username")
                        .addEventListener("input", onInput);
               chromeDoc.getElementById("password-notification-password")
                        .addEventListener("input", onInput);
-              chromeDoc.getElementById("password-notification-password")
-                       .addEventListener("focus", onPasswordFocus);
+              if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
+                chromeDoc.getElementById("password-notification-password")
+                         .addEventListener("focus", onPasswordFocus);
+              }
               chromeDoc.getElementById("password-notification-password")
                        .addEventListener("blur", onPasswordBlur);
               break;
             case "shown":
               chromeDoc.getElementById("notification-popup")
                          .addEventListener("click", onNotificationClick);
               writeDataToUI();
               break;
--- a/toolkit/components/places/nsMaybeWeakPtr.h
+++ b/toolkit/components/places/nsMaybeWeakPtr.h
@@ -33,16 +33,17 @@ public:
   MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr<T> &ref) { mPtr = ref; }
 
   bool operator==(const nsMaybeWeakPtr<T> &other) const {
     return mPtr == other.mPtr;
   }
 
   operator const nsCOMPtr<T>() const { return GetValue(); }
 
+  nsISupports* GetRawValue() const { return mPtr.get(); }
 protected:
   const nsCOMPtr<T> GetValue() const {
     return nsCOMPtr<T>(dont_AddRef(static_cast<T*>
                                               (GetValueAs(NS_GET_TEMPLATE_IID(T)))));
   }
 };
 
 // nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -3972,25 +3972,24 @@ TraverseBookmarkFolderObservers(nsTrimIn
                                        "mBookmarkFolderObservers value[i]");
     nsNavHistoryResultNode* node = aData->ElementAt(i);
     cb->NoteXPCOMChild(node);
   }
   return PL_DHASH_NEXT;
 }
 
 static void
-traverseResultObservers(nsMaybeWeakPtrArray<nsINavHistoryResultObserver> aObservers,
+traverseResultObservers(nsMaybeWeakPtrArray<nsINavHistoryResultObserver>& aObservers,
                         void *aClosure)
 {
   nsCycleCollectionTraversalCallback* cb =
     static_cast<nsCycleCollectionTraversalCallback*>(aClosure);
   for (uint32_t i = 0; i < aObservers.Length(); ++i) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mResultObservers value[i]");
-    const nsCOMPtr<nsINavHistoryResultObserver> &obs = aObservers.ElementAt(i);
-    cb->NoteXPCOMChild(obs);
+    cb->NoteXPCOMChild(aObservers.ElementAt(i).GetRawValue());
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNavHistoryResult)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
   traverseResultObservers(tmp->mObservers, &cb);
   tmp->mBookmarkFolderObservers.Enumerate(&TraverseBookmarkFolderObservers, &cb);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAllBookmarksObservers)
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -722,76 +722,53 @@
             return null;
           ]]>
         </getter>
       </property>
 
       <property name="selected" readonly="true"
                 onget="return this.getAttribute('selected') == 'true';"/>
 
-      <!--
-          This code is the same as in browser/base/content/tabbrowser.xml,
-          except for the _tabAttrModified call. If you modify it, tabbrowser.xml
-          should probably also be modified.
-      -->
-      <property name="_visuallySelected">
-        <setter>
-          <![CDATA[
-          if (val)
-            this.setAttribute("visuallyselected", "true");
-          else
-            this.removeAttribute("visuallyselected");
-
-          if (this.previousSibling && this.previousSibling.localName == "tab") {
-            if (val)
-              this.previousSibling.setAttribute("beforeselected", "true");
-            else
-              this.previousSibling.removeAttribute("beforeselected");
-            this.removeAttribute("first-tab");
-          }
-          else
-            this.setAttribute("first-tab", "true");
-
-          if (this.nextSibling && this.nextSibling.localName == "tab") {
-            if (val)
-              this.nextSibling.setAttribute("afterselected", "true");
-            else
-              this.nextSibling.removeAttribute("afterselected");
-            this.removeAttribute("last-tab");
-          }
-          else
-            this.setAttribute("last-tab", "true");
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <property name="_logicallySelected">
-        <setter>
-          <![CDATA[
+      <property name="_selected">
+        <setter><![CDATA[
           if (val)
             this.setAttribute("selected", "true");
           else
             this.removeAttribute("selected");
 
+          this._setPositionAttributes(val);
+
           return val;
-          ]]>
-        </setter>
+        ]]></setter>
       </property>
 
-      <property name="_selected">
-        <setter>
-          <![CDATA[
-          // If our tab switching is synchronous, then logical selection = visual selection
-          this._logicallySelected = val;
-          this._visuallySelected = val;
-          return val;
-          ]]>
-        </setter>
-      </property>
+      <method name="_setPositionAttributes">
+        <parameter name="aSelected"/>
+        <body><![CDATA[
+          if (this.previousSibling && this.previousSibling.localName == "tab") {
+            if (aSelected)
+              this.previousSibling.setAttribute("beforeselected", "true");
+            else
+              this.previousSibling.removeAttribute("beforeselected");
+            this.removeAttribute("first-tab");
+          } else {
+            this.setAttribute("first-tab", "true");
+          }
+
+          if (this.nextSibling && this.nextSibling.localName == "tab") {
+            if (aSelected)
+              this.nextSibling.setAttribute("afterselected", "true");
+            else
+              this.nextSibling.removeAttribute("afterselected");
+            this.removeAttribute("last-tab");
+          } else {
+            this.setAttribute("last-tab", "true");
+          }
+        ]]></body>
+      </method>
 
       <property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
                                    onset="this.setAttribute('linkedpanel', val); return val;"/>
 
       <field name="arrowKeysShouldWrap" readonly="true">
 #ifdef XP_MACOSX
         true
 #else
--- a/toolkit/devtools/gcli/commands/screenshot.js
+++ b/toolkit/devtools/gcli/commands/screenshot.js
@@ -2,19 +2,19 @@
  * 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 { Cc, Ci, Cu } = require("chrome");
 const l10n = require("gcli/l10n");
 const { Services } = require("resource://gre/modules/Services.jsm");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
 loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
                            .getService(Ci.nsIStringBundleService)
                            .createBundle("chrome://branding/locale/brand.properties")
                            .GetStringFromName("brandShortName");
 
rename from toolkit/devtools/LayoutHelpers.jsm
rename to toolkit/devtools/layout-helpers.js
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/layout-helpers.js
@@ -1,32 +1,23 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
-const Cu = Components.utils;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-  "resource://gre/modules/Services.jsm");
-
-this.EXPORTED_SYMBOLS = ["LayoutHelpers"];
+let {Ci} = require("chrome")
 
 let LayoutHelpers = function(aTopLevelWindow) {
   this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                      .getInterface(Ci.nsIWebNavigation)
                                      .QueryInterface(Ci.nsIDocShell);
 };
 
-this.LayoutHelpers = LayoutHelpers;
+module.exports = LayoutHelpers;
 
 LayoutHelpers.prototype = {
 
   /**
    * Get box quads adjusted for iframes and zoom level.
    * @param {DOMNode} node The node for which we are to get the box model region
    * quads.
    * @param {String} region The box model region to return: "content",
@@ -349,18 +340,18 @@ LayoutHelpers.prototype = {
    *         The element in which the window is embedded.
    */
   getFrameElement: function(win) {
     if (this.isTopLevelWindow(win)) {
       return null;
     }
 
     let winUtils = win.
-      QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-      getInterface(Components.interfaces.nsIDOMWindowUtils);
+      QueryInterface(Ci.nsIInterfaceRequestor).
+      getInterface(Ci.nsIDOMWindowUtils);
 
     return winUtils.containerElement;
   },
 
   /**
    * Get the x/y offsets for of all the parent frames of a given node
    *
    * @param {DOMNode} node
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -30,23 +30,23 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 EXTRA_JS_MODULES.devtools += [
     'async-utils.js',
     'content-observer.js',
     'css-color.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'event-emitter.js',
     'event-parsers.js',
+    'layout-helpers.js',
     'output-parser.js',
     'path.js',
     'worker-loader.js',
 ]
 
 EXTRA_JS_MODULES.devtools += [
     'Console.jsm',
-    'LayoutHelpers.jsm',
     'Loader.jsm',
     'Require.jsm',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors += [
     'server/actors/highlighter.css'
 ]
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -6,27 +6,26 @@
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const protocol = require("devtools/server/protocol");
 const {Arg, Option, method, RetVal} = protocol;
 const events = require("sdk/event/core");
 const Heritage = require("sdk/core/heritage");
 const EventEmitter = require("devtools/toolkit/event-emitter");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 loader.lazyRequireGetter(this, "CssLogic",
   "devtools/styleinspector/css-logic", true);
 loader.lazyRequireGetter(this, "setIgnoreLayoutChanges",
   "devtools/server/actors/layout", true);
 loader.lazyGetter(this, "DOMUtils", function() {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
-loader.lazyImporter(this, "LayoutHelpers",
-  "resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 // FIXME: add ":visited" and ":link" after bug 713106 is fixed
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 // Note that the order of items in this array is important because it is used
 // for drawing the BoxModelHighlighter's path elements correctly.
 const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
 const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
 const SVG_NS = "http://www.w3.org/2000/svg";
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -65,16 +65,17 @@ const {Class} = require("sdk/core/herita
 const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
 const {
   HighlighterActor,
   CustomHighlighterActor,
   isTypeRegistered,
 } = require("devtools/server/actors/highlighter");
 const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
   require("devtools/server/actors/layout");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 const {EventParsers} = require("devtools/toolkit/event-parsers");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
@@ -111,18 +112,16 @@ const PSEUDO_SELECTORS = [
   [":checked", 1],
   ["::selection", 0]
 ];
 
 
 let HELPER_SHEET = ".__fx-devtools-hide-shortcut__ { visibility: hidden !important } ";
 HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important; outline-offset: -2px!important } ";
 
-Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
-
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/toolkit/DevToolsUtils");
 
 loader.lazyGetter(this, "DOMParser", function() {
   return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
 
 loader.lazyGetter(this, "eventListenerService", function() {
--- a/toolkit/devtools/server/actors/storage.js
+++ b/toolkit/devtools/server/actors/storage.js
@@ -14,21 +14,20 @@ try {
   // we don't use it there anyway.
 }
 const {async} = require("devtools/async-utils");
 const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 const promise = require("promise");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
-loader.lazyImporter(this, "LayoutHelpers",
-                    "resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 let gTrackedMessageManager = new Map();
 
 // Maximum number of cookies/local storage key-value-pairs that can be sent
 // over the wire to the client in one request.
 const MAX_STORE_OBJECT_COUNT = 50;
 // Interval for the batch job that sends the accumilated update packets to the
 // client (ms).
--- a/toolkit/devtools/styleinspector/css-logic.js
+++ b/toolkit/devtools/styleinspector/css-logic.js
@@ -36,16 +36,17 @@
  * - why their expectations may not have been fulfilled
  * - how browsers process CSS
  * @constructor
  */
 
 const { Cc, Ci, Cu } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 let pseudos = new Set([
   ":after",
   ":before",
   ":first-letter",
   ":first-line",
   ":selection",
   ":-moz-color-swatch",
@@ -71,17 +72,16 @@ let pseudos = new Set([
 
 const PSEUDO_ELEMENT_SET = pseudos;
 exports.PSEUDO_ELEMENT_SET = PSEUDO_ELEMENT_SET;
 
 // This should be ok because none of the functions that use this should be used
 // on the worker thread, where Cu is not available.
 if (Cu) {
   Cu.importGlobalProperties(['CSS']);
-  Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 }
 
 function CssLogic()
 {
   // The cache of examined CSS properties.
   _propertyInfos: {};
 }
 
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -6,24 +6,24 @@
 
 "use strict";
 
 const {Cc, Ci, Cu, components} = require("chrome");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
-loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
 // Note that these are only used in WebConsoleCommands, see $0 and pprint().
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+const LayoutHelpers = require("devtools/toolkit/layout-helpers");
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;