Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 11 Apr 2013 12:03:46 -0400
changeset 128458 7b8ed29c6bc0126f8f5f704ff279e3608ea2d75e
parent 128448 c950d2ea4c5bd2c0bc6d6e2a9e7da0542bc47b9c (current diff)
parent 128457 b9d56a1e0a6166f373b95f0deca5997c3158814f (diff)
child 128459 bc4dd82e27cd96481d97f8f586f52c8542efdd34
child 128661 541385e4332184b0ab4a64d1e47b8b997ea7739c
push id26319
push userryanvm@gmail.com
push dateThu, 11 Apr 2013 16:03:49 +0000
treeherdermozilla-inbound@7b8ed29c6bc0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
7b8ed29c6bc0 / 23.0a1 / 20130412030828 / files
nightly linux64
7b8ed29c6bc0 / 23.0a1 / 20130412030828 / files
nightly mac
7b8ed29c6bc0 / 23.0a1 / 20130412030828 / files
nightly win32
7b8ed29c6bc0 / 23.0a1 / 20130412030828 / files
nightly win64
7b8ed29c6bc0 / 23.0a1 / 20130412030828 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
browser/base/jar.mn
browser/devtools/profiler/profiler.css
browser/devtools/webconsole/webconsole.js
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -1,12 +1,11 @@
-# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
-# 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/.
+/* 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/. */
 
 //******** define a js object to implement nsITreeView
 function pageInfoTreeView(treeid, copycol)
 {
   // copycol is the index number for the column that we want to add to
   // the copy-n-paste buffer when the user hits accel-c
   this.treeid = treeid;
   this.copycol = copycol;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4266,17 +4266,17 @@
 
     <handlers>
       <handler event="mouseover"><![CDATA[
         let anonid = event.originalTarget.getAttribute("anonid");
         if (anonid == "close-button")
           this.mOverCloseButton = true;
 
         let tab = event.target;
-        if (tab.selected)
+        if (tab.selected || tab.closing)
           return;
 
         let tabContainer = this.parentNode;
         let visibleTabs = tabContainer.tabbrowser.visibleTabs;
         let tabIndex = visibleTabs.indexOf(tab);
         if (tabIndex == 0) {
           tabContainer._beforeHoveredTab = null;
         } else {
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -60,17 +60,17 @@ browser.jar:
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
         content/browser/content.js                    (content/content.js)
         content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
         content/browser/newtab/preload.xhtml          (content/newtab/preload.xhtml)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
-*       content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
+        content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
         content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
 #ifdef MOZ_SERVICES_SYNC
         content/browser/sync/aboutSyncTabs.xul        (content/sync/aboutSyncTabs.xul)
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -132,25 +132,25 @@ let DebuggerController = {
     if (this._connection) {
       return this._connection.promise;
     }
 
     let deferred = this._connection = Promise.defer();
 
     if (!window._isChromeDebugger) {
       let target = this._target;
-      let { client, form } = target;
+      let { client, form, threadActor } = target;
       target.on("close", this._onTabDetached);
       target.on("navigate", this._onTabNavigated);
       target.on("will-navigate", this._onTabNavigated);
 
       if (target.chrome) {
         this._startChromeDebugging(client, form.chromeDebugger, deferred.resolve);
       } else {
-        this._startDebuggingTab(client, form, deferred.resolve);
+        this._startDebuggingTab(client, threadActor, deferred.resolve);
       }
 
       return deferred.promise;
     }
 
     // Chrome debugging needs to make the connection to the debuggee.
     let transport = debuggerSocketConnect(Prefs.chromeDebuggingHost,
                                           Prefs.chromeDebuggingPort);
@@ -171,28 +171,27 @@ let DebuggerController = {
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   disconnect: function DC_disconnect() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
       return;
     }
-    this.client.removeListener("tabNavigated", this._onTabNavigated);
-    this.client.removeListener("tabDetached", this._onTabDetached);
 
     // When debugging local or a remote instance, the connection is closed by
     // the RemoteTarget.
     if (window._isChromeDebugger) {
+      this.client.removeListener("tabNavigated", this._onTabNavigated);
+      this.client.removeListener("tabDetached", this._onTabDetached);
       this.client.close();
     }
 
     this._connection = null;
     this.client = null;
-    this.tabClient = null;
     this.activeThread = null;
   },
 
   /**
    * Called for each location change in the debugged tab.
    *
    * @param string aType
    *        Packet type.
@@ -222,51 +221,43 @@ let DebuggerController = {
     this.shutdownDebugger();
   },
 
   /**
    * Sets up a debugging session.
    *
    * @param DebuggerClient aClient
    *        The debugger client.
-   * @param object aTabGrip
+   * @param string aThreadActor
    *        The remote protocol grip of the tab.
    * @param function aCallback
    *        A function to invoke once the client attached to the active thread.
    */
-  _startDebuggingTab: function DC__startDebuggingTab(aClient, aTabGrip, aCallback) {
+  _startDebuggingTab: function DC__startDebuggingTab(aClient, aThreadActor, aCallback) {
     if (!aClient) {
       Cu.reportError("No client found!");
       return;
     }
     this.client = aClient;
 
-    aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => {
-      if (!aTabClient) {
-        Cu.reportError("No tab client found!");
+    aClient.attachThread(aThreadActor, (aResponse, aThreadClient) => {
+      if (!aThreadClient) {
+        Cu.reportError("Couldn't attach to thread: " + aResponse.error);
         return;
       }
-      this.tabClient = aTabClient;
-
-      aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
-        if (!aThreadClient) {
-          Cu.reportError("Couldn't attach to thread: " + aResponse.error);
-          return;
-        }
-        this.activeThread = aThreadClient;
+      this.activeThread = aThreadClient;
 
-        this.ThreadState.connect();
-        this.StackFrames.connect();
-        this.SourceScripts.connect();
-        aThreadClient.resume(this._ensureResumptionOrder);
+      this.ThreadState.connect();
+      this.StackFrames.connect();
+      this.SourceScripts.connect();
+      aThreadClient.resume(this._ensureResumptionOrder);
 
-        if (aCallback) {
-          aCallback();
-        }
-      });
+      if (aCallback) {
+        aCallback();
+      }
     });
   },
 
   /**
    * Warn if resuming execution produced a wrongOrder error.
    */
   _ensureResumptionOrder: function DC__ensureResumptionOrder(aResponse) {
     if (aResponse.error == "wrongOrder") {
@@ -326,17 +317,16 @@ let DebuggerController = {
   },
 
   _isInitialized: false,
   _isDestroyed: false,
   _startup: null,
   _shutdown: null,
   _connection: null,
   client: null,
-  tabClient: null,
   activeThread: null
 };
 
 /**
  * ThreadState keeps the UI up to date with the state of the
  * thread (paused/attached/etc.).
  */
 function ThreadState() {
@@ -1715,19 +1705,16 @@ Object.defineProperties(window, {
     get: function() ViewHelpers.dispatchEvent,
   },
   "editor": {
     get: function() DebuggerView.editor
   },
   "gClient": {
     get: function() DebuggerController.client
   },
-  "gTabClient": {
-    get: function() DebuggerController.tabClient
-  },
   "gThreadClient": {
     get: function() DebuggerController.activeThread
   },
   "gThreadState": {
     get: function() DebuggerController.ThreadState
   },
   "gStackFrames": {
     get: function() DebuggerController.StackFrames
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-data.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data.js
@@ -639,42 +639,44 @@ function testKeyboardAccessibility(callb
             is(gVariablesView._parent.scrollTop, 0,
               "The variables view shouldn't scroll when pressing the DOWN key.");
 
             EventUtils.sendKey("UP", gDebugger);
             executeSoon(function() {
               is(gVariablesView._parent.scrollTop, 0,
                 "The variables view shouldn't scroll when pressing the UP key.");
 
-
               EventUtils.sendKey("PAGE_DOWN", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp5",
                 "The someProp5 item should be focused now.");
 
               EventUtils.sendKey("DOWN", gDebugger);
               is(gVariablesView.getFocusedItem().name, "0",
                 "The 0 item should be focused now.");
 
               EventUtils.sendKey("END", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo item should be focused now.");
 
               EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "bar",
+                "The bar item should be focused now.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo item should be focused now.");
+
+              EventUtils.sendKey("RIGHT", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo item should still be focused now.");
 
-              EventUtils.sendKey("RIGHT", gDebugger);
-              is(gVariablesView.getFocusedItem().name, "bar",
-                "The bar item should still be focused now.");
-
               EventUtils.sendKey("PAGE_DOWN", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo item should still be focused now.");
 
-
               EventUtils.sendKey("PAGE_UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "__proto__",
                 "The __proto__ item should be focused now.");
 
               EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "set",
                 "The set item should be focused now.");
 
@@ -697,33 +699,32 @@ function testKeyboardAccessibility(callb
               EventUtils.sendKey("LEFT", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp0",
                 "The someProp0 item should still be focused now.");
 
               EventUtils.sendKey("PAGE_UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp0",
                 "The someProp0 item should still be focused now.");
 
-
               for (let i = 0; i < 16; i++) {
                 // Advance to the first collapsed __proto__ property.
-                EventUtils.sendKey("RIGHT", gDebugger);
+                EventUtils.sendKey("DOWN", gDebugger);
               }
               is(gVariablesView.getFocusedItem().name, "__proto__",
                 "The __proto__ item should be focused now.");
               is(gVariablesView.getFocusedItem().expanded, false,
                 "The __proto__ item shouldn't be expanded yet.");
 
               EventUtils.sendKey("RIGHT", gDebugger);
               is(gVariablesView.getFocusedItem().name, "__proto__",
                 "The __proto__ item should still be focused.");
               is(gVariablesView.getFocusedItem().expanded, true,
                 "The __proto__ item should be expanded now.");
 
-              for (let i = 0; i < 2; i++) {
+              for (let i = 0; i < 3; i++) {
                 // Advance to the fifth top-level someProp5 property.
                 EventUtils.sendKey("LEFT", gDebugger);
               }
               is(gVariablesView.getFocusedItem().name, "5",
                 "The fifth array item should be focused.");
               is(gVariablesView.getFocusedItem().expanded, false,
                 "The fifth array item should not be expanded now.");
 
@@ -738,67 +739,65 @@ function testKeyboardAccessibility(callb
 
               EventUtils.sendKey("LEFT", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp5",
                 "The someProp5 item should still be focused.");
               is(gVariablesView.getFocusedItem().expanded, false,
                 "The someProp5 item should not be expanded now.");
 
               EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp5",
+                "The someProp5 item should still be focused.");
+
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp4",
                 "The someProp4 item should be focused.");
 
-              EventUtils.sendKey("LEFT", gDebugger);
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp3",
                 "The someProp3 item should be focused.");
 
-              EventUtils.sendKey("LEFT", gDebugger);
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp2",
                 "The someProp2 item should be focused.");
 
-              EventUtils.sendKey("LEFT", gDebugger);
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp1",
                 "The someProp1 item should be focused.");
 
-              EventUtils.sendKey("LEFT", gDebugger);
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp0",
                 "The someProp0 item should be focused.");
 
-              EventUtils.sendKey("LEFT", gDebugger);
+              EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "someProp0",
                 "The someProp0 item should still be focused.");
 
               for (let i = 0; i < 32; i++) {
                 // Advance to the last property in this scope.
                 EventUtils.sendKey("DOWN", gDebugger);
               }
               is(gVariablesView.getFocusedItem().name, "__proto__",
                 "The top-level __proto__ item should be focused.");
 
               EventUtils.sendKey("DOWN", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo scope should be focused now.");
               is(gVariablesView.getFocusedItem().expanded, true,
-                "The foo scope should already be expanded yet.");
+                "The foo scope should already be expanded.");
 
               EventUtils.sendKey("LEFT", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo scope should be focused now.");
               is(gVariablesView.getFocusedItem().expanded, false,
                 "The foo scope shouldn't be expanded now.");
 
               EventUtils.sendKey("DOWN", gDebugger);
-              is(gVariablesView.getFocusedItem().name, "foo",
-                "The foo scope should still be focused.");
-              is(gVariablesView.getFocusedItem().expanded, true,
-                "The foo scope should be expanded now.");
-
-              EventUtils.sendKey("DOWN", gDebugger);
               is(gVariablesView.getFocusedItem().name, "bar",
-                "The bar variable should still be focused.");
+                "The bar variable should be focused.");
               is(gVariablesView.getFocusedItem().expanded, false,
                 "The bar variable shouldn't be expanded.");
               is(gVariablesView.getFocusedItem().visible, true,
                 "The bar variable shouldn't be hidden.");
 
               EventUtils.sendKey("BACK_SPACE", gDebugger);
               is(gVariablesView.getFocusedItem().name, "bar",
                 "The bar variable should still be focused.");
@@ -809,16 +808,30 @@ function testKeyboardAccessibility(callb
 
               EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "foo",
                 "The foo scope should be focused.");
 
               EventUtils.sendKey("UP", gDebugger);
               is(gVariablesView.getFocusedItem().name, "__proto__",
                 "The top-level __proto__ item should be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The top-level __proto__ item should not be expanded.");
+
+              EventUtils.sendKey("RIGHT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The top-level __proto__ item should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, true,
+                "The top-level __proto__ item should be expanded.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The top-level __proto__ item should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The top-level __proto__ item should not be expanded.");
 
               executeSoon(callback);
             });
           });
         });
       });
     });
   });
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -275,22 +275,30 @@ TabTarget.prototype = {
 
     this._setupRemoteListeners();
 
     if (this.isRemote) {
       // In the remote debugging case, the protocol connection will have been
       // already initialized in the connection screen code.
       this._remote.resolve(null);
     } else {
-      this._client.connect(function(aType, aTraits) {
-        this._client.listTabs(function(aResponse) {
+      this._client.connect((aType, aTraits) => {
+        this._client.listTabs(aResponse => {
           this._form = aResponse.tabs[aResponse.selected];
-          this._remote.resolve(null);
-        }.bind(this));
-      }.bind(this));
+
+          this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
+            if (!aTabClient) {
+              this._remote.reject("Unable to attach to the tab");
+              return;
+            }
+            this.threadActor = aResponse.threadActor;
+            this._remote.resolve(null);
+          });
+        });
+      });
     }
 
     return this._remote.promise;
   },
 
   /**
    * Listen to the different events.
    */
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -33,17 +33,16 @@ browser.jar:
 *   content/browser/source-editor-overlay.xul     (sourceeditor/source-editor-overlay.xul)
     content/browser/debugger.xul                  (debugger/debugger.xul)
     content/browser/debugger.css                  (debugger/debugger.css)
     content/browser/debugger-controller.js        (debugger/debugger-controller.js)
     content/browser/debugger-view.js              (debugger/debugger-view.js)
     content/browser/debugger-toolbar.js           (debugger/debugger-toolbar.js)
     content/browser/debugger-panes.js             (debugger/debugger-panes.js)
     content/browser/profiler.xul                  (profiler/profiler.xul)
-    content/browser/profiler.css                  (profiler/profiler.css)
     content/browser/devtools/cleopatra.html       (profiler/cleopatra/cleopatra.html)
     content/browser/devtools/profiler/cleopatra/css/ui.css              (profiler/cleopatra/css/ui.css)
     content/browser/devtools/profiler/cleopatra/css/tree.css            (profiler/cleopatra/css/tree.css)
     content/browser/devtools/profiler/cleopatra/css/devtools.css        (profiler/cleopatra/css/devtools.css)
     content/browser/devtools/profiler/cleopatra/js/strings.js           (profiler/cleopatra/js/strings.js)
     content/browser/devtools/profiler/cleopatra/js/parser.js            (profiler/cleopatra/js/parser.js)
     content/browser/devtools/profiler/cleopatra/js/parserWorker.js      (profiler/cleopatra/js/parserWorker.js)
     content/browser/devtools/profiler/cleopatra/js/tree.js              (profiler/cleopatra/js/tree.js)
deleted file mode 100644
--- a/browser/devtools/profiler/profiler.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.profile-name {
-  font-size: 13px;
-  padding: 8px;
-}
-
-#profiles-list > li {
-  width: 180px;
-  cursor: pointer;
-}
-
-.splitview-nav-container button {
-  color: white;
-  background-clip: padding-box;
-  border-bottom: 1px solid hsla(210,16%,76%,.1);
-  box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
-  -moz-padding-end: 8px;
-  -moz-box-align: center;
-}
\ No newline at end of file
--- a/browser/devtools/profiler/profiler.xul
+++ b/browser/devtools/profiler/profiler.xul
@@ -2,18 +2,18 @@
 
 <!-- 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/. -->
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/profiler.css"?>
 <?xml-stylesheet href="chrome://browser/content/splitview.css"?>
-<?xml-stylesheet href="chrome://browser/content/profiler.css"?>
 
 <!DOCTYPE window [
 <!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
   %profilerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <box flex="1" id="profiler-chrome" class="splitview-root">
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -729,50 +729,37 @@ VariablesView.prototype = {
 
     switch (e.keyCode) {
       case e.DOM_VK_UP:
         // Always rewind focus.
         this.focusPrevItem(true);
         return;
 
       case e.DOM_VK_DOWN:
-        // Only expand scopes before advancing focus.
-        if (!(item instanceof Variable) &&
-            !(item instanceof Property) &&
-            !item._isExpanded && item._isArrowVisible) {
-          item.expand();
-        } else {
-          this.focusNextItem(true);
-        }
+        // Always advance focus.
+        this.focusNextItem(true);
         return;
 
       case e.DOM_VK_LEFT:
-        // If this is a collapsed or un-expandable item that has an expandable
-        // variable or property parent, collapse and focus the owner view.
-        if (!item._isExpanded || !item._isArrowVisible) {
-          let ownerView = item.ownerView;
-          if ((ownerView instanceof Variable ||
-               ownerView instanceof Property) &&
-               ownerView._isExpanded && ownerView._isArrowVisible) {
-            if (this._focusItem(ownerView, true)) {
-              return;
-            }
-          }
-        }
         // Collapse scopes, variables and properties before rewinding focus.
         if (item._isExpanded && item._isArrowVisible) {
           item.collapse();
         } else {
-          this.focusPrevItem(true);
+          this._focusItem(item.ownerView);
         }
         return;
 
       case e.DOM_VK_RIGHT:
+        // Nothing to do here if this item never expands.
+        if (!item._isArrowVisible) {
+          return;
+        }
+
         // Expand scopes, variables and properties before advancing focus.
-        if (!item._isExpanded && item._isArrowVisible) {
+        if (!item._isExpanded) {
           item.expand();
         } else {
           this.focusNextItem(true);
         }
         return;
 
       case e.DOM_VK_PAGE_UP:
         // Rewind a certain number of elements based on the container height.
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -4395,17 +4395,16 @@ function WebConsoleConnectionProxy(aWebC
   this.target = aTarget;
 
   this._onPageError = this._onPageError.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
-  this._onAttachTab = this._onAttachTab.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
   /**
    * The owning Web Console instance.
@@ -4433,22 +4432,16 @@ WebConsoleConnectionProxy.prototype = {
    * The WebConsoleClient object.
    *
    * @see WebConsoleClient
    * @type object
    */
   webConsoleClient: null,
 
   /**
-   * The TabClient instance we use.
-   * @type object
-   */
-  tabClient: null,
-
-  /**
    * Tells if the connection is established.
    * @type boolean
    */
   connected: false,
 
   /**
    * Timer used for the connection.
    * @private
@@ -4463,24 +4456,16 @@ WebConsoleConnectionProxy.prototype = {
    * The WebConsoleActor ID.
    *
    * @private
    * @type string
    */
   _consoleActor: null,
 
   /**
-   * The TabActor ID.
-   *
-   * @private
-   * @type string
-   */
-  _tabActor: null,
-
-  /**
    * Tells if the window.console object of the remote web page is the native
    * object or not.
    * @private
    * @type boolean
    */
   _hasNativeConsoleAPI: false,
 
   /**
@@ -4513,27 +4498,25 @@ WebConsoleConnectionProxy.prototype = {
 
     let client = this.client = this.target.client;
 
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
-    client.addListener("tabNavigated", this._onTabNavigated);
-
+    this.target.on("will-navigate", this._onTabNavigated);
+    this.target.on("navigate", this._onTabNavigated);
+
+    this._consoleActor = this.target.form.consoleActor;
     if (!this.target.chrome) {
-      // target.form is a TabActor grip
-      this._attachTab(this.target.form);
+      let tab = this.target.form;
+      this.owner.onLocationChange(tab.url, tab.title);
     }
-    else {
-      // target.form is a RootActor grip
-      this._consoleActor = this.target.form.consoleActor;
-      this._attachConsole();
-    }
+    this._attachConsole();
 
     return promise;
   },
 
   /**
    * Connection timeout handler.
    * @private
    */
@@ -4543,53 +4526,16 @@ WebConsoleConnectionProxy.prototype = {
       error: "timeout",
       message: l10n.getStr("connectionTimeout"),
     };
 
     this._connectDefer.reject(error);
   },
 
   /**
-   * Attach to the tab actor.
-   *
-   * @private
-   * @param object aTab
-   *        Grip for the tab to attach to.
-   */
-  _attachTab: function WCCP__attachTab(aTab)
-  {
-    this._consoleActor = aTab.consoleActor;
-    this._tabActor = aTab.actor;
-    this.owner.onLocationChange(aTab.url, aTab.title);
-    this.client.attachTab(this._tabActor, this._onAttachTab);
-  },
-
-  /**
-   * The "attachTab" response handler.
-   *
-   * @private
-   * @param object aResponse
-   *        The JSON response object received from the server.
-   * @param object aTabClient
-   *        The TabClient instance for the attached tab.
-   */
-  _onAttachTab: function WCCP__onAttachTab(aResponse, aTabClient)
-  {
-    if (aResponse.error) {
-      Cu.reportError("attachTab failed: " + aResponse.error + " " +
-                     aResponse.message);
-      this._connectDefer.reject(aResponse);
-      return;
-    }
-
-    this.tabClient = aTabClient;
-    this._attachConsole();
-  },
-
-  /**
    * Attach to the Web Console actor.
    * @private
    */
   _attachConsole: function WCCP__attachConsole()
   {
     let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                      "FileActivity"];
     this.client.attachConsole(this._consoleActor, listeners,
@@ -4748,25 +4694,25 @@ WebConsoleConnectionProxy.prototype = {
    * @private
    * @param string aType
    *        Message type.
    * @param object aPacket
    *        The message received from the server.
    */
   _onTabNavigated: function WCCP__onTabNavigated(aType, aPacket)
   {
-    if (!this.owner || aPacket.from != this._tabActor) {
+    if (!this.owner) {
       return;
     }
 
     if (aPacket.url) {
       this.owner.onLocationChange(aPacket.url, aPacket.title);
     }
 
-    if (aPacket.state == "stop" && !aPacket.nativeConsoleAPI) {
+    if (aType == "navigate" && !aPacket.nativeConsoleAPI) {
       this.owner.logWarningAboutReplacedAPI();
     }
   },
 
   /**
    * Release an object actor.
    *
    * @param string aActor
@@ -4802,17 +4748,16 @@ WebConsoleConnectionProxy.prototype = {
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
     this.client.removeListener("networkEvent", this._onNetworkEvent);
     this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("tabNavigated", this._onTabNavigated);
 
     this.client = null;
     this.webConsoleClient = null;
-    this.tabClient = null;
     this.target = null;
     this.connected = false;
     this.owner = null;
     this._disconnecter.resolve(null);
 
     return this._disconnecter.promise;
   },
 };
--- a/browser/modules/RecentWindow.jsm
+++ b/browser/modules/RecentWindow.jsm
@@ -28,17 +28,16 @@ this.RecentWindow = {
   getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
     let checkPrivacy = typeof aOptions == "object" &&
                        "private" in aOptions;
 
     let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
 
     function isSuitableBrowserWindow(win) {
       return (!win.closed &&
-              win.toolbar.visible &&
               (allowPopups || win.toolbar.visible) &&
               (!checkPrivacy ||
                PrivateBrowsingUtils.permanentPrivateBrowsing ||
                PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
     }
 
 #ifdef BROKEN_WM_Z_ORDER
     let win = Services.wm.getMostRecentWindow("navigator:browser");
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/devtools/profiler.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+%include ../../shared/devtools/profiler.inc.css
\ No newline at end of file
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -161,16 +161,17 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
   skin/classic/browser/devtools/splitview.css         (devtools/splitview.css)
   skin/classic/browser/devtools/styleeditor.css       (devtools/styleeditor.css)
   skin/classic/browser/devtools/debugger.css          (devtools/debugger.css)
+* skin/classic/browser/devtools/profiler.css          (devtools/profiler.css)
   skin/classic/browser/devtools/netmonitor.css        (devtools/netmonitor.css)
   skin/classic/browser/devtools/magnifying-glass.png  (devtools/magnifying-glass.png)
   skin/classic/browser/devtools/option-icon.png       (devtools/option-icon.png)
   skin/classic/browser/devtools/itemToggle.png        (devtools/itemToggle.png)
   skin/classic/browser/devtools/itemArrow-rtl.png     (devtools/itemArrow-rtl.png)
   skin/classic/browser/devtools/itemArrow-ltr.png     (devtools/itemArrow-ltr.png)
   skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
   skin/classic/browser/devtools/inspect-button.png    (devtools/inspect-button.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/devtools/profiler.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+%include ../../shared/devtools/profiler.inc.css
\ No newline at end of file
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -249,16 +249,17 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
   skin/classic/browser/devtools/splitview.css               (devtools/splitview.css)
   skin/classic/browser/devtools/styleeditor.css             (devtools/styleeditor.css)
 * skin/classic/browser/devtools/debugger.css                (devtools/debugger.css)
+* skin/classic/browser/devtools/profiler.css                (devtools/profiler.css)
   skin/classic/browser/devtools/netmonitor.css              (devtools/netmonitor.css)
   skin/classic/browser/devtools/magnifying-glass.png        (devtools/magnifying-glass.png)
   skin/classic/browser/devtools/option-icon.png             (devtools/option-icon.png)
   skin/classic/browser/devtools/itemToggle.png              (devtools/itemToggle.png)
   skin/classic/browser/devtools/itemArrow-rtl.png           (devtools/itemArrow-rtl.png)
   skin/classic/browser/devtools/itemArrow-ltr.png           (devtools/itemArrow-ltr.png)
   skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
   skin/classic/browser/devtools/inspect-button.png          (devtools/inspect-button.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/profiler.inc.css
@@ -0,0 +1,24 @@
+%if 0
+/* 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/. */
+%endif
+
+.profile-name {
+  font-size: 13px;
+  padding: 8px;
+}
+
+#profiles-list > li {
+  width: 180px;
+  cursor: pointer;
+}
+
+.splitview-nav-container button {
+  color: white;
+  background-clip: padding-box;
+  border-bottom: 1px solid hsla(210,16%,76%,.1);
+  box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
+  -moz-padding-end: 8px;
+  -moz-box-align: center;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/devtools/profiler.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+%include ../../shared/devtools/profiler.inc.css
\ No newline at end of file
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -189,16 +189,17 @@ browser.jar:
         skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
         skin/classic/browser/devtools/splitview.css                 (devtools/splitview.css)
         skin/classic/browser/devtools/styleeditor.css               (devtools/styleeditor.css)
         skin/classic/browser/devtools/debugger.css                  (devtools/debugger.css)
+*       skin/classic/browser/devtools/profiler.css                  (devtools/profiler.css)
         skin/classic/browser/devtools/netmonitor.css                (devtools/netmonitor.css)
         skin/classic/browser/devtools/magnifying-glass.png          (devtools/magnifying-glass.png)
         skin/classic/browser/devtools/option-icon.png               (devtools/option-icon.png)
         skin/classic/browser/devtools/itemToggle.png                (devtools/itemToggle.png)
         skin/classic/browser/devtools/itemArrow-rtl.png             (devtools/itemArrow-rtl.png)
         skin/classic/browser/devtools/itemArrow-ltr.png             (devtools/itemArrow-ltr.png)
         skin/classic/browser/devtools/background-noise-toolbar.png  (devtools/background-noise-toolbar.png)
         skin/classic/browser/devtools/inspect-button.png            (devtools/inspect-button.png)
@@ -432,16 +433,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
         skin/classic/aero/browser/devtools/splitview.css             (devtools/splitview.css)
         skin/classic/aero/browser/devtools/styleeditor.css           (devtools/styleeditor.css)
         skin/classic/aero/browser/devtools/debugger.css              (devtools/debugger.css)
+*       skin/classic/aero/browser/devtools/profiler.css              (devtools/profiler.css)
         skin/classic/aero/browser/devtools/netmonitor.css            (devtools/netmonitor.css)
         skin/classic/aero/browser/devtools/magnifying-glass.png      (devtools/magnifying-glass.png)
         skin/classic/aero/browser/devtools/option-icon.png           (devtools/option-icon.png)
         skin/classic/aero/browser/devtools/itemToggle.png            (devtools/itemToggle.png)
         skin/classic/aero/browser/devtools/itemArrow-rtl.png         (devtools/itemArrow-rtl.png)
         skin/classic/aero/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
         skin/classic/aero/browser/devtools/itemArrow-ltr.png         (devtools/itemArrow-ltr.png)
         skin/classic/aero/browser/devtools/inspect-button.png        (devtools/inspect-button.png)
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3015,10 +3015,238 @@
     "description": "Time (ms) for a WAL checkpoint after collecting all measurements."
   },
   "POPUP_NOTIFICATION_MAINACTION_TRIGGERED_MS": {
     "kind": "linear",
     "low": 25,
     "high": "80 * 25",
     "n_buckets": "80 + 1",
     "description": "The time (in milliseconds) after showing a PopupNotification that the mainAction was first triggered"
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RESUME_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'resume' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RESUME_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'resume' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_INTERRUPT_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_INTERRUPT_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_CLIENTEVALUATE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_CLIENTEVALUATE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASEMANY_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASEMANY_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADGRIPS_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADGRIPS_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_SOURCES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'sources' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_SOURCES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'sources' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_FRAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'frames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_FRAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'frames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PARAMETERNAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PARAMETERNAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_OWNPROPERTYNAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_OWNPROPERTYNAMES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPEANDPROPERTIES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPEANDPROPERTIES_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROPERTY_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'property' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROPERTY_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'property' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_SUBSTRING_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'substring' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_SUBSTRING_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'substring' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'release' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'release' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTTABS_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTTABS_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DELETE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'delete' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DELETE_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'delete' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADDETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADDETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_TABDETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_TABDETACH_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
   }
 }
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -55,16 +55,18 @@ XPCOMUtils.defineLazyGetter(this, "gUnic
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = 'utf8';
   return converter;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+  "resource://gre/modules/Deprecated.jsm");
 
 /**
  * Utilities for dealing with promises and Task.jsm
  */
 const TaskUtils = {
   /**
    * Add logging to a promise.
    *
@@ -516,16 +518,24 @@ this.PageThumbsStorage = {
     return this._convertToHexString(hash.finish(false));
   },
 
   _convertToHexString: function Storage_convertToHexString(aData) {
     let hex = "";
     for (let i = 0; i < aData.length; i++)
       hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
     return hex;
+  },
+
+  // Deprecated, please do not use
+  getFileForURL: function Storage_getFileForURL_DEPRECATED(aURL) {
+    Deprecated.warning("PageThumbs.getFileForURL is deprecated. Please use PageThumbs.getFilePathForURL and OS.File",
+                       "https://developer.mozilla.org/docs/JavaScript_OS.File");
+    // Note: Once this method has been removed, we can get rid of the dependency towards FileUtils
+    return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
   }
 };
 
 let PageThumbsStorageMigrator = {
   get currentVersion() {
     try {
       return Services.prefs.getIntPref(PREF_STORAGE_VERSION);
     } catch (e) {
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
@@ -64,16 +64,25 @@ function runTests() {
   ok(true, "second thumbnail moved");
 
   yield whenFileRemoved(roaming);
   ok(true, "roaming thumbnail directory removed");
 
   // Check that our existing thumbnail wasn't overwritten.
   is(getFileContents(file), "no-overwrite-plz",
     "existing thumbnail was not overwritten");
+
+  // Sanity check: ensure that, until it is removed, deprecated
+  // function |getFileForURL| points to the same path as
+  // |getFilePathForURL|.
+  if ("getFileForURL" in PageThumbsStorage) {
+    let file = PageThumbsStorage.getFileForURL(URL);
+    is(file.path, PageThumbsStorage.getFilePathForURL(URL),
+       "Deprecated getFileForURL and getFilePathForURL return the same path");
+  }
 }
 
 function changeLocation(aLocation, aNewDir) {
   let oldDir = gDirSvc.get(aLocation, Ci.nsILocalFile);
   gDirSvc.undefine(aLocation);
   gDirSvc.set(aLocation, aNewDir);
 
   registerCleanupFunction(function () {
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -213,18 +213,109 @@ this.DebuggerClient = function DebuggerC
 
   this._pendingRequests = [];
   this._activeRequests = {};
   this._eventsEnabled = true;
 
   this.compat = new ProtocolCompatibility(this, [
     new SourcesShim(),
   ]);
+
+  this.request = this.request.bind(this);
 }
 
+/**
+ * A declarative helper for defining methods that send requests to the server.
+ *
+ * @param aPacketSkeleton
+ *        The form of the packet to send. Can specify fields to be filled from
+ *        the parameters by using the |args| function.
+ * @param telemetry
+ *        The unique suffix of the telemetry histogram id.
+ * @param before
+ *        The function to call before sending the packet. Is passed the packet,
+ *        and the return value is used as the new packet. The |this| context is
+ *        the instance of the client object we are defining a method for.
+ * @param after
+ *        The function to call after the response is received. It is passed the
+ *        response, and the return value is considered the new response that
+ *        will be passed to the callback. The |this| context is the instance of
+ *        the client object we are defining a method for.
+ */
+DebuggerClient.requester = function DC_requester(aPacketSkeleton, { telemetry,
+                                                 before, after }) {
+  return function (...args) {
+    let histogram, startTime;
+    if (telemetry) {
+      let transportType = this._transport instanceof LocalDebuggerTransport
+        ? "LOCAL_"
+        : "REMOTE_";
+      let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
+        + transportType + telemetry + "_MS";
+      histogram = Services.telemetry.getHistogramById(histogramId);
+      startTime = +new Date;
+    }
+
+    let outgoingPacket = {
+      to: aPacketSkeleton.to || this.actor
+    };
+
+    let maxPosition = -1;
+    for (let k of Object.keys(aPacketSkeleton)) {
+      if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
+        let { position } = aPacketSkeleton[k];
+        outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
+        maxPosition = Math.max(position, maxPosition);
+      } else {
+        outgoingPacket[k] = aPacketSkeleton[k];
+      }
+    }
+
+    if (before) {
+      outgoingPacket = before.call(this, outgoingPacket);
+    }
+
+    this.request(outgoingPacket, function (aResponse) {
+      if (after) {
+        let { from } = aResponse;
+        aResponse = after.call(this, aResponse);
+        if (!aResponse.from) {
+          aResponse.from = from;
+        }
+      }
+
+      // The callback is always the last parameter.
+      let thisCallback = args[maxPosition + 1];
+      if (thisCallback) {
+        thisCallback(aResponse);
+      }
+
+      if (histogram) {
+        histogram.add(+new Date - startTime);
+      }
+    }.bind(this));
+
+  };
+};
+
+function args(aPos) {
+  return new DebuggerClient.Argument(aPos);
+}
+
+DebuggerClient.Argument = function DCP(aPosition) {
+  this.position = aPosition;
+};
+
+DebuggerClient.Argument.prototype.getArgument = function DCP_getArgument(aParams) {
+  if (!this.position in aParams) {
+    throw new Error("Bad index into params: " + this.position);
+  }
+  return aParams[this.position];
+};
+
 DebuggerClient.prototype = {
   /**
    * Connect to the server and start exchanging protocol messages.
    *
    * @param aOnConnected function
    *        If specified, will be called when the greeting packet is
    *        received from the debugging server.
    */
@@ -299,22 +390,22 @@ DebuggerClient.prototype = {
   },
 
   /**
    * List the open tabs.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listTabs: function DC_listTabs(aOnResponse) {
-    let packet = { to: ROOT_ACTOR_NAME, type: "listTabs" };
-    this.request(packet, function(aResponse) {
-      aOnResponse(aResponse);
-    });
-  },
+  listTabs: DebuggerClient.requester({
+    to: ROOT_ACTOR_NAME,
+    type: "listTabs"
+  }, {
+    telemetry: "LISTTABS"
+  }),
 
   /**
    * Attach to a tab actor.
    *
    * @param string aTabActor
    *        The actor ID for the tab to attach.
    * @param function aOnResponse
    *        Called with the response packet and a TabClient
@@ -390,23 +481,22 @@ DebuggerClient.prototype = {
    * Release an object actor.
    *
    * @param string aActor
    *        The actor ID to send the request to.
    * @param aOnResponse function
    *        If specified, will be called with the response packet when
    *        debugging server responds.
    */
-  release: function DC_release(aActor, aOnResponse) {
-    let packet = {
-      to: aActor,
-      type: "release",
-    };
-    this.request(packet, aOnResponse);
-  },
+  release: DebuggerClient.requester({
+    to: args(0),
+    type: "release"
+  }, {
+    telemetry: "RELEASE"
+  }),
 
   /**
    * Send a request to the debugging server.
    *
    * @param aRequest object
    *        A JSON packet to send to the debugging server.
    * @param aOnResponse function
    *        If specified, will be called with the response packet when
@@ -731,38 +821,40 @@ SSProto.translatePacket = function SS_tr
  * @param aClient DebuggerClient
  *        The debugger client parent.
  * @param aActor string
  *        The actor ID for this tab.
  */
 function TabClient(aClient, aActor) {
   this._client = aClient;
   this._actor = aActor;
+  this.request = this._client.request;
 }
 
 TabClient.prototype = {
+  get actor() { return this._actor },
+
   /**
    * Detach the client from the tab actor.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  detach: function TabC_detach(aOnResponse) {
-    let self = this;
-    let packet = { to: this._actor, type: "detach" };
-    this._client.request(packet, function(aResponse) {
-      if (self.activeTab === self._client._tabClients[self._actor]) {
-        delete self.activeTab;
+  detach: DebuggerClient.requester({
+    type: "detach"
+  }, {
+    after: function (aResponse) {
+      if (this.activeTab === this._client._tabClients[this.actor]) {
+        delete this.activeTab;
       }
-      delete self._client._tabClients[self._actor];
-      if (aOnResponse) {
-        aOnResponse(aResponse);
-      }
-    });
-  }
+      delete this._client._tabClients[this.actor];
+      return aResponse;
+    },
+    telemetry: "TABDETACH"
+  }),
 };
 
 eventSource(TabClient.prototype);
 
 /**
  * Creates a thread client for the remote debugging protocol server. This client
  * is a front to the thread actor created in the server side, hiding the
  * protocol details in a traditional JavaScript API.
@@ -774,16 +866,17 @@ eventSource(TabClient.prototype);
  */
 function ThreadClient(aClient, aActor) {
   this._client = aClient;
   this._actor = aActor;
   this._frameCache = [];
   this._scriptCache = {};
   this._pauseGrips = {};
   this._threadGrips = {};
+  this.request = this._client.request;
 }
 
 ThreadClient.prototype = {
   _state: "paused",
   get state() { return this._state; },
   get paused() { return this._state === "paused"; },
 
   _pauseOnExceptions: false,
@@ -798,91 +891,95 @@ ThreadClient.prototype = {
       throw Error(aCommand + " command sent while not paused.");
     }
   },
 
   /**
    * Resume a paused thread. If the optional aLimit parameter is present, then
    * the thread will also pause when that limit is reached.
    *
-   * @param function aOnResponse
-   *        Called with the response packet.
    * @param [optional] object aLimit
    *        An object with a type property set to the appropriate limit (next,
    *        step, or finish) per the remote debugging protocol specification.
+   *        Use null to specify no limit.
+   * @param function aOnResponse
+   *        Called with the response packet.
    */
-  resume: function TC_resume(aOnResponse, aLimit) {
-    this._assertPaused("resume");
-
-    // Put the client in a tentative "resuming" state so we can prevent
-    // further requests that should only be sent in the paused state.
-    this._state = "resuming";
+  _doResume: DebuggerClient.requester({
+    type: "resume",
+    resumeLimit: args(0)
+  }, {
+    before: function (aPacket) {
+      this._assertPaused("resume");
 
-    let self = this;
-    let packet = {
-      to: this._actor,
-      type: "resume",
-      resumeLimit: aLimit,
-      pauseOnExceptions: this._pauseOnExceptions
-    };
-    this._client.request(packet, function(aResponse) {
+      // Put the client in a tentative "resuming" state so we can prevent
+      // further requests that should only be sent in the paused state.
+      this._state = "resuming";
+
+      aPacket.pauseOnExceptions = this._pauseOnExceptions;
+      return aPacket;
+    },
+    after: function (aResponse) {
       if (aResponse.error) {
         // There was an error resuming, back to paused state.
-        self._state = "paused";
+        this._state = "paused";
       }
-      if (aOnResponse) {
-        aOnResponse(aResponse);
-      }
-    });
+      return aResponse;
+    },
+    telemetry: "RESUME"
+  }),
+
+  /**
+   * Resume a paused thread.
+   */
+  resume: function TC_resume(aOnResponse) {
+    this._doResume(null, aOnResponse);
   },
 
   /**
    * Step over a function call.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   stepOver: function TC_stepOver(aOnResponse) {
-    this.resume(aOnResponse, { type: "next" });
+    this._doResume({ type: "next" }, aOnResponse);
   },
 
   /**
    * Step into a function call.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   stepIn: function TC_stepIn(aOnResponse) {
-    this.resume(aOnResponse, { type: "step" });
+    this._doResume({ type: "step" }, aOnResponse);
   },
 
   /**
    * Step out of a function call.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   stepOut: function TC_stepOut(aOnResponse) {
-    this.resume(aOnResponse, { type: "finish" });
+    this._doResume({ type: "finish" }, aOnResponse);
   },
 
   /**
    * Interrupt a running thread.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  interrupt: function TC_interrupt(aOnResponse) {
-    let packet = { to: this._actor, type: "interrupt" };
-    this._client.request(packet, function(aResponse) {
-      if (aOnResponse) {
-        aOnResponse(aResponse);
-      }
-    });
-  },
+  interrupt: DebuggerClient.requester({
+    type: "interrupt"
+  }, {
+    telemetry: "INTERRUPT"
+  }),
 
   /**
    * Enable or disable pausing when an exception is thrown.
    *
    * @param boolean aFlag
    *        Enables pausing if true, disables otherwise.
    * @param function aOnResponse
    *        Called with the response packet.
@@ -911,57 +1008,56 @@ ThreadClient.prototype = {
    * @param string aFrame
    *        The actor ID of the frame where the evaluation should take place.
    * @param string aExpression
    *        The expression that will be evaluated in the scope of the frame
    *        above.
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  eval: function TC_eval(aFrame, aExpression, aOnResponse) {
-    this._assertPaused("eval");
-
-    // Put the client in a tentative "resuming" state so we can prevent
-    // further requests that should only be sent in the paused state.
-    this._state = "resuming";
-
-    let self = this;
-    let request = { to: this._actor, type: "clientEvaluate",
-                    frame: aFrame, expression: aExpression };
-    this._client.request(request, function(aResponse) {
+  eval: DebuggerClient.requester({
+    type: "clientEvaluate",
+    frame: args(0),
+    expression: args(1)
+  }, {
+    before: function (aPacket) {
+      this._assertPaused("eval");
+      // Put the client in a tentative "resuming" state so we can prevent
+      // further requests that should only be sent in the paused state.
+      this._state = "resuming";
+      return aPacket;
+    },
+    after: function (aResponse) {
       if (aResponse.error) {
         // There was an error resuming, back to paused state.
         self._state = "paused";
       }
-
-      if (aOnResponse) {
-        aOnResponse(aResponse);
-      }
-    });
-  },
+      return aResponse;
+    },
+    telemetry: "CLIENTEVALUATE"
+  }),
 
   /**
    * Detach from the thread actor.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  detach: function TC_detach(aOnResponse) {
-    let self = this;
-    let packet = { to: this._actor, type: "detach" };
-    this._client.request(packet, function(aResponse) {
-      if (self.activeThread === self._client._threadClients[self._actor]) {
-        delete self.activeThread;
+  detach: DebuggerClient.requester({
+    type: "detach"
+  }, {
+    after: function (aResponse) {
+      if (this.activeThread === this._client._threadClients[this.actor]) {
+        delete this.activeThread;
       }
-      delete self._client._threadClients[self._actor];
-      if (aOnResponse) {
-        aOnResponse(aResponse);
-      }
-    });
-  },
+      delete this._client._threadClients[this.actor];
+      return aResponse;
+    },
+    telemetry: "THREADDETACH"
+  }),
 
   /**
    * Request to set a breakpoint in the specified location.
    *
    * @param object aLocation
    *        The source location object where the breakpoint will be set.
    * @param function aOnResponse
    *        Called with the thread's response.
@@ -1002,80 +1098,79 @@ ThreadClient.prototype = {
     }.bind(this));
   },
 
   /**
    * Release multiple thread-lifetime object actors. If any pause-lifetime
    * actors are included in the request, a |notReleasable| error will return,
    * but all the thread-lifetime ones will have been released.
    *
-   * @param array aActors
+   * @param array actors
    *        An array with actor IDs to release.
    */
-  releaseMany: function TC_releaseMany(aActors, aOnResponse) {
-    let packet = {
-      to: this._actor,
-      type: "releaseMany",
-      actors: aActors
-    };
-    this._client.request(packet, aOnResponse);
-  },
+  releaseMany: DebuggerClient.requester({
+    type: "releaseMany",
+    actors: args(0),
+  }, {
+    telemetry: "RELEASEMANY"
+  }),
 
   /**
    * Promote multiple pause-lifetime object actors to thread-lifetime ones.
    *
-   * @param array aActors
+   * @param array actors
    *        An array with actor IDs to promote.
    */
-  threadGrips: function TC_threadGrips(aActors, aOnResponse) {
-    let packet = {
-      to: this._actor,
-      type: "threadGrips",
-      actors: aActors
-    };
-    this._client.request(packet, aOnResponse);
-  },
+  threadGrips: DebuggerClient.requester({
+    type: "threadGrips",
+    actors: args(0)
+  }, {
+    telemetry: "THREADGRIPS"
+  }),
 
   /**
    * Request the loaded sources for the current thread.
    *
    * @param aOnResponse Function
    *        Called with the thread's response.
    */
   getSources: function TC_getSources(aOnResponse) {
     // This is how we should get sources if the server supports "sources"
     // requests.
-    function getSources(aOnResponse) {
-      let packet = { to: this._actor, type: "sources" };
-      this._client.request(packet, aOnResponse);
-    }
+    let getSources = DebuggerClient.requester({
+      type: "sources"
+    }, {
+      telemetry: "SOURCES"
+    });
 
     // This is how we should deduct what sources exist from the existing scripts
     // when the server does not support "sources" requests.
-    function getSourcesBackwardsCompat(aOnResponse) {
-      this._client.request({
-        to: this._actor,
-        type: "scripts"
-      }, function (aResponse) {
+    let getSourcesBackwardsCompat = DebuggerClient.requester({
+      type: "scripts"
+    }, {
+      after: function (aResponse) {
         if (aResponse.error) {
-          aOnResponse(aResponse);
-          return;
+          return aResponse;
         }
-        let sourceActorsByURL = aResponse.scripts.reduce(function (aSourceActorsByURL, aScript) {
-          aSourceActorsByURL[aScript.url] = aScript.source;
-          return aSourceActorsByURL;
-        }, {});
-        aOnResponse({
+
+        let sourceActorsByURL = aResponse.scripts
+          .reduce(function (aSourceActorsByURL, aScript) {
+            aSourceActorsByURL[aScript.url] = aScript.source;
+            return aSourceActorsByURL;
+          }, {});
+
+        return {
           sources: [
             { url: url, actor: sourceActorsByURL[url] }
             for (url of Object.keys(sourceActorsByURL))
           ]
-        });
-      });
-    }
+        }
+      },
+      telemetry: "SOURCES"
+    });
 
     // On the first time `getSources` is called, patch the thread client with
     // the best method for the server's capabilities.
     let threadClient = this;
     this.compat.supportsFeature("sources").then(function () {
       threadClient.getSources = getSources;
     }, function () {
       threadClient.getSources = getSourcesBackwardsCompat;
@@ -1090,17 +1185,17 @@ ThreadClient.prototype = {
       return;
     }
     this.interrupt(function(aResponse) {
       if (aResponse) {
         aError(aResponse);
         return;
       }
       aAction();
-      this.resume(function() {});
+      this.resume();
     }.bind(this));
   },
 
   /**
    * Clear the thread's source script cache. A scriptscleared event
    * will be sent.
    */
   _clearScripts: function TC_clearScripts() {
@@ -1117,23 +1212,23 @@ ThreadClient.prototype = {
    *        The number of the youngest stack frame to return (the youngest
    *        frame is 0).
    * @param aCount integer
    *        The maximum number of frames to return, or null to return all
    *        frames.
    * @param aOnResponse function
    *        Called with the thread's response.
    */
-  getFrames: function TC_getFrames(aStart, aCount, aOnResponse) {
-    this._assertPaused("frames");
-
-    let packet = { to: this._actor, type: "frames",
-                   start: aStart, count: aCount ? aCount : undefined };
-    this._client.request(packet, aOnResponse);
-  },
+  getFrames: DebuggerClient.requester({
+    type: "frames",
+    start: args(0),
+    count: args(1)
+  }, {
+    telemetry: "FRAMES"
+  }),
 
   /**
    * An array of cached frames. Clients can observe the framesadded and
    * framescleared event to keep up to date on changes to this cache,
    * and can fill it using the fillFrames method.
    */
   get cachedFrames() { return this._frameCache; },
 
@@ -1305,117 +1400,105 @@ eventSource(ThreadClient.prototype);
  *        The debugger client parent.
  * @param aGrip object
  *        A pause-lifetime object grip returned by the protocol.
  */
 function GripClient(aClient, aGrip)
 {
   this._grip = aGrip;
   this._client = aClient;
+  this.request = this._client.request;
 }
 
 GripClient.prototype = {
   get actor() { return this._grip.actor },
 
   valid: true,
 
   /**
    * Request the names of a function's formal parameters.
    *
    * @param aOnResponse function
    *        Called with an object of the form:
    *        { parameterNames:[<parameterName>, ...] }
    *        where each <parameterName> is the name of a parameter.
    */
-  getParameterNames: function GC_getParameterNames(aOnResponse) {
-    if (this._grip["class"] !== "Function") {
-      throw "getParameterNames is only valid for function grips.";
-    }
-
-    let packet = { to: this.actor, type: "parameterNames" };
-    this._client.request(packet, function (aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  },
+  getParameterNames: DebuggerClient.requester({
+    type: "parameterNames"
+  }, {
+    before: function (aPacket) {
+      if (this._grip["class"] !== "Function") {
+        throw new Error("getParameterNames is only valid for function grips.");
+      }
+      return aPacket;
+    },
+    telemetry: "PARAMETERNAMES"
+  }),
 
   /**
    * Request the names of the properties defined on the object and not its
    * prototype.
    *
    * @param aOnResponse function Called with the request's response.
    */
-  getOwnPropertyNames: function GC_getOwnPropertyNames(aOnResponse) {
-    let packet = { to: this.actor, type: "ownPropertyNames" };
-    this._client.request(packet, function (aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  },
+  getOwnPropertyNames: DebuggerClient.requester({
+    type: "ownPropertyNames"
+  }, {
+    telemetry: "OWNPROPERTYNAMES"
+  }),
 
   /**
    * Request the prototype and own properties of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
-  getPrototypeAndProperties: function GC_getPrototypeAndProperties(aOnResponse) {
-    let packet = { to: this.actor,
-                   type: "prototypeAndProperties" };
-    this._client.request(packet, function (aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  },
+  getPrototypeAndProperties: DebuggerClient.requester({
+    type: "prototypeAndProperties"
+  }, {
+    telemetry: "PROTOTYPEANDPROPERTIES"
+  }),
 
   /**
    * Request the property descriptor of the object's specified property.
    *
    * @param aName string The name of the requested property.
    * @param aOnResponse function Called with the request's response.
    */
-  getProperty: function GC_getProperty(aName, aOnResponse) {
-    let packet = { to: this.actor, type: "property",
-                   name: aName };
-    this._client.request(packet, function (aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  },
+  getProperty: DebuggerClient.requester({
+    type: "property",
+    name: args(0)
+  }, {
+    telemetry: "PROPERTY"
+  }),
 
   /**
    * Request the prototype of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
-  getPrototype: function GC_getPrototype(aOnResponse) {
-    let packet = { to: this.actor, type: "prototype" };
-    this._client.request(packet, function (aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  }
+  getPrototype: DebuggerClient.requester({
+    type: "prototype"
+  }, {
+    telemetry: "PROTOTYPE"
+  }),
 };
 
 /**
  * A LongStringClient provides a way to access "very long" strings from the
  * debugger server.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
  * @param aGrip Object
  *        A pause-lifetime long string grip returned by the protocol.
  */
 function LongStringClient(aClient, aGrip) {
   this._grip = aGrip;
   this._client = aClient;
+  this.request = this._client.request;
 }
 
 LongStringClient.prototype = {
   get actor() { return this._grip.actor; },
   get length() { return this._grip.length; },
   get initial() { return this._grip.initial; },
 
   valid: true,
@@ -1425,23 +1508,23 @@ LongStringClient.prototype = {
    *
    * @param aStart Number
    *        The starting index.
    * @param aEnd Number
    *        The ending index.
    * @param aCallback Function
    *        The function called when we receive the substring.
    */
-  substring: function LSC_substring(aStart, aEnd, aCallback) {
-    let packet = { to: this.actor,
-                   type: "substring",
-                   start: aStart,
-                   end: aEnd };
-    this._client.request(packet, aCallback);
-  }
+  substring: DebuggerClient.requester({
+    type: "substring",
+    start: args(0),
+    end: args(1)
+  }, {
+    telemetry: "SUBSTRING"
+  }),
 };
 
 /**
  * A SourceClient provides a way to access the source text of a script.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
  * @param aForm Object
@@ -1498,34 +1581,32 @@ SourceClient.prototype = {
  * @param aLocation object
  *        The location of the breakpoint. This is an object with two properties:
  *        url and line.
  */
 function BreakpointClient(aClient, aActor, aLocation) {
   this._client = aClient;
   this._actor = aActor;
   this.location = aLocation;
+  this.request = this._client.request;
 }
 
 BreakpointClient.prototype = {
 
   _actor: null,
   get actor() { return this._actor; },
 
   /**
    * Remove the breakpoint from the server.
    */
-  remove: function BC_remove(aOnResponse) {
-    let packet = { to: this._actor, type: "delete" };
-    this._client.request(packet, function(aResponse) {
-                                   if (aOnResponse) {
-                                     aOnResponse(aResponse);
-                                   }
-                                 });
-  }
+  remove: DebuggerClient.requester({
+    type: "delete"
+  }, {
+    telemetry: "DELETE"
+  }),
 };
 
 eventSource(BreakpointClient.prototype);
 
 /**
  * Connects to a debugger server socket and returns a DebuggerTransport.
  *
  * @param aHost string
--- a/toolkit/devtools/debugger/dbg-transport.js
+++ b/toolkit/devtools/debugger/dbg-transport.js
@@ -196,17 +196,17 @@ DebuggerTransport.prototype = {
  * DebuggerTransport, but instead of transmitting serialized messages across a
  * connection it merely calls the packet dispatcher of the other side.
  *
  * @param aOther LocalDebuggerTransport
  *        The other endpoint for this debugger connection.
  *
  * @see DebuggerTransport
  */
-function LocalDebuggerTransport(aOther)
+this.LocalDebuggerTransport = function LocalDebuggerTransport(aOther)
 {
   this.other = aOther;
   this.hooks = null;
 }
 
 LocalDebuggerTransport.prototype = {
   /**
    * Transmit a message by directly calling the onPacket handler of the other