Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 23 Jan 2014 15:56:13 -0500
changeset 180945 066c526104ef75518dd40e9d4c976b45c7c2269a
parent 180923 4cd99f8706182108aefab73499c56fb29a4d8a36 (current diff)
parent 180944 67d36c90a7ea74533ed7e029ebb41ce030edaed2 (diff)
child 180974 624d042739e6ed0755fd9e953c65866e934af6a6
child 180987 3ba925830469d57a7a08e1857f0f468596e4025f
child 181039 6c84e3865bfb7c951056361b6ca3166030d83572
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c.
toolkit/components/telemetry/TelemetryPing.js
toolkit/components/telemetry/TelemetryPing.manifest
toolkit/components/telemetry/nsITelemetryPing.idl
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1098,16 +1098,18 @@ let RemoteDebugger = {
         DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
         DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
         if ("nsIProfiler" in Ci) {
           DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
         }
         DebuggerServer.registerModule("devtools/server/actors/inspector");
         DebuggerServer.registerModule("devtools/server/actors/styleeditor");
         DebuggerServer.registerModule("devtools/server/actors/stylesheets");
+        DebuggerServer.registerModule("devtools/server/actors/tracer");
+        DebuggerServer.registerModule("devtools/server/actors/webgl");
       }
       DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');
       DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
       DebuggerServer.registerModule("devtools/server/actors/device");
 
 #ifdef MOZ_WIDGET_GONK
       DebuggerServer.onConnectionChange = function(what) {
         AdbController.updateState();
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -520,18 +520,16 @@
 #ifdef MOZ_SERVICES_HEALTHREPORT
 @BINPATH@/components/HealthReportComponents.manifest
 @BINPATH@/components/HealthReportService.js
 #endif
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
-@BINPATH@/components/TelemetryPing.js
-@BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1332,12 +1332,12 @@ pref("geo.wifi.uri", "https://www.google
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // The URL where remote content that composes the UI for Firefox Accounts should
 // be fetched. Must use HTTPS.
-pref("firefox.accounts.remoteUrl", "https://accounts.dev.lcip.org/?service=sync");
+pref("identity.fxaccounts.remote.uri", "https://accounts.dev.lcip.org/?service=sync");
 
 // The URL of the Firefox Accounts auth server backend
 pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -41,24 +41,26 @@ var FeedHandler = {
       return false;
 
     // Build the menu showing the available feed choices for viewing.
     var itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
     for (let feedInfo of feeds) {
       var item = document.createElement(itemNodeType);
       var baseTitle = feedInfo.title || feedInfo.href;
       var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]);
-      item.setAttribute("class", "feed-" + itemNodeType);
       item.setAttribute("label", labelStr);
       item.setAttribute("feed", feedInfo.href);
       item.setAttribute("tooltiptext", feedInfo.href);
       item.setAttribute("crop", "center");
+      let className = "feed-" + itemNodeType;
       if (isSubview) {
         item.setAttribute("tabindex", "0");
+        className += " subviewbutton";
       }
+      item.setAttribute("class", className);
       container.appendChild(item);
     }
     return true;
   },
 
   /**
    * Subscribe to a given feed.  Called when
    *   1. Page has a single feed and user clicks feed icon in location bar
--- a/browser/base/content/test/general/browser_aboutAccounts.js
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -1,89 +1,89 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-  "resource://gre/modules/Task.jsm");
-
-registerCleanupFunction(function() {
-  // Ensure we don't pollute prefs for next tests.
-  Services.prefs.clearUserPref("firefox.accounts.remoteUrl");
-});
-
-let gTests = [
-
-{
-  desc: "Test the remote commands",
-  setup: function ()
-  {
-    Services.prefs.setCharPref("firefox.accounts.remoteUrl",
-                               "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
-  },
-  run: function ()
-  {
-    let deferred = Promise.defer();
-
-    let results = 0;
-    try {
-      let win = gBrowser.contentWindow;
-      win.addEventListener("message", function testLoad(e) {
-        if (e.data.type == "testResult") {
-          ok(e.data.pass, e.data.info);
-          results++;
-        }
-        else if (e.data.type == "testsComplete") {
-          is(results, e.data.count, "Checking number of results received matches the number of tests that should have run");
-          win.removeEventListener("message", testLoad, false, true);
-          deferred.resolve();
-        }
-
-      }, false, true);
-
-    } catch(e) {
-      ok(false, "Failed to get all commands");
-      deferred.reject();
-    }
-    return deferred.promise;
-  }
-},
-
-
-]; // gTests
-
-function test()
-{
-  waitForExplicitFinish();
-
-  Task.spawn(function () {
-    for (let test of gTests) {
-      info(test.desc);
-      test.setup();
-
-      yield promiseNewTabLoadEvent("about:accounts");
-
-      yield test.run();
-
-      gBrowser.removeCurrentTab();
-    }
-
-    finish();
-  });
-}
-
-function promiseNewTabLoadEvent(aUrl, aEventType="load")
-{
-  let deferred = Promise.defer();
-  let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
-  tab.linkedBrowser.addEventListener(aEventType, function load(event) {
-    tab.linkedBrowser.removeEventListener(aEventType, load, true);
-    let iframe = tab.linkedBrowser.contentDocument.getElementById("remote");
-      iframe.addEventListener("load", function frameLoad(e) {
-        iframe.removeEventListener("load", frameLoad, false);
-        deferred.resolve();
-      }, false);
-    }, true);
-  return deferred.promise;
-}
-
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+
+registerCleanupFunction(function() {
+  // Ensure we don't pollute prefs for next tests.
+  Services.prefs.clearUserPref("identity.fxaccounts.remote.uri");
+});
+
+let gTests = [
+
+{
+  desc: "Test the remote commands",
+  setup: function ()
+  {
+    Services.prefs.setCharPref("identity.fxaccounts.remote.uri",
+                               "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
+  },
+  run: function ()
+  {
+    let deferred = Promise.defer();
+
+    let results = 0;
+    try {
+      let win = gBrowser.contentWindow;
+      win.addEventListener("message", function testLoad(e) {
+        if (e.data.type == "testResult") {
+          ok(e.data.pass, e.data.info);
+          results++;
+        }
+        else if (e.data.type == "testsComplete") {
+          is(results, e.data.count, "Checking number of results received matches the number of tests that should have run");
+          win.removeEventListener("message", testLoad, false, true);
+          deferred.resolve();
+        }
+
+      }, false, true);
+
+    } catch(e) {
+      ok(false, "Failed to get all commands");
+      deferred.reject();
+    }
+    return deferred.promise;
+  }
+},
+
+
+]; // gTests
+
+function test()
+{
+  waitForExplicitFinish();
+
+  Task.spawn(function () {
+    for (let test of gTests) {
+      info(test.desc);
+      test.setup();
+
+      yield promiseNewTabLoadEvent("about:accounts");
+
+      yield test.run();
+
+      gBrowser.removeCurrentTab();
+    }
+
+    finish();
+  });
+}
+
+function promiseNewTabLoadEvent(aUrl, aEventType="load")
+{
+  let deferred = Promise.defer();
+  let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+  tab.linkedBrowser.addEventListener(aEventType, function load(event) {
+    tab.linkedBrowser.removeEventListener(aEventType, load, true);
+    let iframe = tab.linkedBrowser.contentDocument.getElementById("remote");
+      iframe.addEventListener("load", function frameLoad(e) {
+        iframe.removeEventListener("load", frameLoad, false);
+        deferred.resolve();
+      }, false);
+    }, true);
+  return deferred.promise;
+}
+
--- a/browser/base/content/test/general/browser_popupNotification.js
+++ b/browser/base/content/test/general/browser_popupNotification.js
@@ -977,17 +977,17 @@ var tests = [
       // checkPopup checks for the matching label. Note that this assumes that
       // this.notifyObj.mainAction is the same as notification.mainAction,
       // which could be a problem if we ever decided to deep-copy.
       checkPopup(popup, this.notifyObj);
       triggerMainCommand(popup);
     },
     onHidden: function() { }
   },
-  { // Test #31 - Moving a tab to a new window should remove non-swappable
+  { // Test #34 - Moving a tab to a new window should remove non-swappable
     // notifications.
     run: function() {
       gBrowser.selectedTab = gBrowser.addTab("about:blank");
       let notifyObj = new basicNotification();
       showNotification(notifyObj);
       let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
       whenDelayedStartupFinished(win, function() {
         let [tab] = win.gBrowser.tabs;
@@ -997,17 +997,17 @@ var tests = [
            "no notification displayed in new window");
         ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
         ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
         win.close();
         goNext();
       });
     }
   },
-  { // Test #32 - Moving a tab to a new window should preserve swappable notifications.
+  { // Test #35 - Moving a tab to a new window should preserve swappable notifications.
     run: function() {
       gBrowser.selectedTab = gBrowser.addTab("about:blank");
       let notifyObj = new basicNotification();
       let originalCallback = notifyObj.options.eventCallback;
         notifyObj.options.eventCallback = function (eventName) {
           originalCallback(eventName);
           return eventName == "swapping";
         };
@@ -1019,16 +1019,60 @@ var tests = [
         let anchor = win.document.getElementById("default-notification-icon");
         win.PopupNotifications._reshowNotifications(anchor);
         checkPopup(win.PopupNotifications.panel, notifyObj);
         ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
         win.close();
         goNext();
       });
     }
+  },
+  { // Test #36 - the hideNotNow option
+    run: function () {
+      this.notifyObj = new basicNotification();
+      this.notifyObj.options.hideNotNow = true;
+      this.notifyObj.mainAction.dismiss = true;
+      showNotification(this.notifyObj);
+    },
+    onShown: function (popup) {
+      // checkPopup verifies that the Not Now item is hidden, and that no separator is added.
+      checkPopup(popup, this.notifyObj);
+      triggerMainCommand(popup);
+    },
+    onHidden: function (popup) { }
+  },
+  { // Test #37 - the main action callback can keep the notification.
+    run: function () {
+      this.notifyObj = new basicNotification();
+      this.notifyObj.mainAction.dismiss = true;
+      showNotification(this.notifyObj);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.notifyObj);
+      triggerMainCommand(popup);
+    },
+    onHidden: function (popup) {
+      ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+      ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+    }
+  },
+  { // Test #38 - a secondary action callback can keep the notification.
+    run: function () {
+      this.notifyObj = new basicNotification();
+      this.notifyObj.secondaryActions[0].dismiss = true;
+      showNotification(this.notifyObj);
+    },
+    onShown: function (popup) {
+      checkPopup(popup, this.notifyObj);
+      triggerSecondaryCommand(popup, 0);
+    },
+    onHidden: function (popup) {
+      ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+      ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+    }
   }
 ];
 
 function showNotification(notifyObj) {
   return PopupNotifications.show(notifyObj.browser,
                                  notifyObj.id,
                                  notifyObj.message,
                                  notifyObj.anchorID,
@@ -1058,17 +1102,22 @@ function checkPopup(popup, notificationO
   if (notificationObj.mainAction) {
     is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
     is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
   }
   let actualSecondaryActions = Array.filter(notification.childNodes,
                                             function (child) child.nodeName == "menuitem");
   let secondaryActions = notificationObj.secondaryActions || [];
   let actualSecondaryActionsCount = actualSecondaryActions.length;
-  if (secondaryActions.length) {
+  if (notificationObj.options.hideNotNow) {
+    is(notification.getAttribute("hidenotnow"), "true", "Not Now item hidden");
+    if (secondaryActions.length)
+      is(notification.lastChild.tagName, "menuitem", "no menuseparator");
+  }
+  else if (secondaryActions.length) {
     is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
   }
   is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
   secondaryActions.forEach(function (a, i) {
     is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
     is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
   });
 }
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -33,74 +33,84 @@
                        label="&quitApplicationCmd.label;"
                        tooltiptext="&quitApplicationCmd.label;"
 #endif
                        command="cmd_quitApplication"/>
       </footer>
     </panelview>
 
     <panelview id="PanelUI-history" flex="1">
-      <label value="&appMenuHistory.label;"/>
+      <label value="&appMenuHistory.label;" class="panel-subview-header"/>
       <toolbarbutton id="appMenuViewHistorySidebar" tabindex="0"
                      label="&appMenuHistory.viewSidebar.label;"
                      type="checkbox"
+                     class="subviewbutton"
                      oncommand="toggleSidebar('viewHistorySidebar'); PanelUI.hide();">
         <observes element="viewHistorySidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarbutton id="appMenuClearRecentHistory" tabindex="0"
                      label="&appMenuHistory.clearRecent.label;"
+                     class="subviewbutton"
                      command="Tools:Sanitize"/>
 #ifdef MOZ_SERVICES_SYNC
       <toolbarbutton id="sync-tabs-menuitem2"
-                     class="syncTabsMenuItem"
+                     class="syncTabsMenuItem subviewbutton"
                      label="&syncTabsMenu2.label;"
                      oncommand="BrowserOpenSyncTabs();"
                      disabled="true"/>
 #endif
       <toolbarbutton id="appMenuRestoreLastSession" tabindex="0"
                      label="&appMenuHistory.restoreSession.label;"
+                     class="subviewbutton"
                      command="Browser:RestoreLastSession"/>
       <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
       <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
       <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
       <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
       <menuseparator id="PanelUI-historyItems-separator"/>
       <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
-      <label value="&appMenuHistory.showAll.label;"
-             id="PanelUI-historyMore"
-             class="text-link"
-             onclick="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
+      <toolbarbutton id="PanelUI-historyMore" tabindex="0"
+                     class="panel-subview-footer subviewbutton"
+                     label="&appMenuHistory.showAll.label;"
+                     oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
     </panelview>
 
-    <panelview id="PanelUI-bookmarks" flex="1">
+    <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
+      <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
       <toolbarbutton id="panelMenuBookmarkThisPage"
                      label="&bookmarkThisPageCmd.label;"
+                     class="subviewbutton"
                      command="Browser:AddBookmarkAs"
                      onclick="PanelUI.hide();"/>
       <toolbarseparator/>
       <toolbarbutton id="panelMenu_showAllBookmarks"
                      label="&showAllBookmarks2.label;"
+                     class="subviewbutton"
                      command="Browser:ShowAllBookmarks"
                      onclick="PanelUI.hide();"/>
       <toolbarbutton id="panelMenu_viewBookmarksSidebar"
                      label="&viewBookmarksSidebar2.label;"
+                     class="subviewbutton"
                      oncommand="toggleSidebar('viewBookmarksSidebar'); PanelUI.hide();">
         <observes element="viewBookmarksSidebar" attribute="checked"/>
       </toolbarbutton>
       <toolbarbutton id="panelMenu_viewBookmarksToolbar"
                      label="&viewBookmarksToolbar.label;"
                      type="checkbox"
                      toolbarId="PersonalToolbar"
+                     class="subviewbutton"
                      oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
       <toolbarseparator/>
       <toolbarbutton id="panelMenu_bookmarksToolbar"
                      label="&personalbarCmd.label;"
+                     class="subviewbutton"
                      oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
       <toolbarbutton id="panelMenu_unsortedBookmarks"
                      label="&unsortedBookmarksCmd.label;"
+                     class="subviewbutton"
                      oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
       <toolbarseparator/>
       <toolbaritem id="panelMenu_bookmarksMenu"
                    flex="1"
                    orient="vertical"
                    smoothscroll="false"
                    onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
                    oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
@@ -111,27 +121,27 @@
 
     </panelview>
 
     <panelview id="PanelUI-socialapi" flex="1"/>
 
     <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);"></panelview>
 
     <panelview id="PanelUI-helpView" flex="1">
-      <label value="&helpMenu.label;"/>
+      <label value="&helpMenu.label;" class="panel-subview-header"/>
       <vbox id="PanelUI-helpItems"/>
     </panelview>
 
     <panelview id="PanelUI-developer" flex="1">
-      <label value="&webDeveloperMenu.label;"/>
+      <label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
       <vbox id="PanelUI-developerItems"/>
     </panelview>
 
     <panelview id="PanelUI-characterEncodingView" flex="1">
-      <label value="&charsetMenu.label;"/>
+      <label value="&charsetMenu.label;" class="panel-subview-header"/>
 
       <vbox id="PanelUI-characterEncodingView-customlist"
             class="PanelUI-characterEncodingView-list"/>
       <vbox>
         <label value="&charsetMenuAutodet.label;"/>
         <vbox id="PanelUI-characterEncodingView-autodetect"
               class="PanelUI-characterEncodingView-list"/>
       </vbox>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -404,16 +404,17 @@ const PanelUI = {
         continue;
       let button = document.createElementNS(NSXUL, "toolbarbutton");
       // Copy specific attributes from a menuitem of the Help menu
       for (let attrName of attrs) {
         if (!node.hasAttribute(attrName))
           continue;
         button.setAttribute(attrName, node.getAttribute(attrName));
       }
+      button.setAttribute("class", "subviewbutton");
       fragment.appendChild(button);
     }
     items.appendChild(fragment);
 
     this.addEventListener("command", PanelUI.onCommandHandler);
   },
 
   _onHelpViewHide: function(aEvent) {
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -97,16 +97,17 @@ const CustomizableWidgets = [{
               let uri = row.getResultByIndex(1);
               let title = row.getResultByIndex(2);
               let icon = row.getResultByIndex(6);
 
               let item = doc.createElementNS(kNSXUL, "toolbarbutton");
               item.setAttribute("label", title || uri);
               item.setAttribute("tabindex", "0");
               item.setAttribute("targetURI", uri);
+              item.setAttribute("class", "subviewbutton");
               item.addEventListener("command", function (aEvent) {
                 onHistoryVisit(uri, aEvent, item);
               });
               item.addEventListener("click", function (aEvent) {
                 onHistoryVisit(uri, aEvent, item);
               });
               if (icon)
                 item.setAttribute("image", "moz-anno:favicon:" + icon);
@@ -147,22 +148,34 @@ const CustomizableWidgets = [{
         tabsFromOtherComputers.removeAttribute("disabled");
       } else {
         tabsFromOtherComputers.setAttribute("disabled", true);
       }
 #endif
 
       let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(doc.defaultView, "toolbarbutton");
       let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
-      separator.hidden = !tabsFragment.childElementCount;
+      let elementCount = tabsFragment.childElementCount;
+      separator.hidden = !elementCount;
+      while (--elementCount >= 0) {
+        if (tabsFragment.children[elementCount].localName != "toolbarbutton")
+          continue;
+        tabsFragment.children[elementCount].setAttribute("class", "subviewbutton");
+      }
       recentlyClosedTabs.appendChild(tabsFragment);
 
       let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(doc.defaultView, "toolbarbutton");
       separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
-      separator.hidden = !windowsFragment.childElementCount;
+      elementCount = windowsFragment.childElementCount;
+      separator.hidden = !elementCount;
+      while (--elementCount >= 0) {
+        if (windowsFragment.children[elementCount].localName != "toolbarbutton")
+          continue;
+        windowsFragment.children[elementCount].setAttribute("class", "subviewbutton");
+      }
       recentlyClosedWindows.appendChild(windowsFragment);
     },
     onViewHiding: function(aEvent) {
       LOG("History view is being hidden!");
     }
   }, {
     id: "privatebrowsing-button",
     shortcutId: "key_privatebrowsing",
@@ -239,16 +252,17 @@ const CustomizableWidgets = [{
           continue;
 
         let item;
         if (node.localName == "menuseparator") {
           item = doc.createElementNS(kNSXUL, "menuseparator");
         } else if (node.localName == "menuitem") {
           item = doc.createElementNS(kNSXUL, "toolbarbutton");
           item.setAttribute("tabindex", "0");
+          item.setAttribute("class", "subviewbutton");
         } else {
           continue;
         }
         for (let attr of attrs) {
           let attrVal = node.getAttribute(attr);
           if (attrVal)
             item.setAttribute(attr, attrVal);
         }
@@ -710,16 +724,17 @@ const CustomizableWidgets = [{
         let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
         elem.setAttribute("label", item.name);
         elem.section = aSection;
         elem.value = item.value;
         if (item.current)
           elem.setAttribute("current", "true");
         if (disabled)
           elem.setAttribute("disabled", "true");
+        elem.setAttribute("class", "subviewbutton");
         containerElem.appendChild(elem);
       }
     },
     onViewShowing: function(aEvent) {
       let document = aEvent.target.ownerDocument;
 
       this.populateList(document,
                         "PanelUI-characterEncodingView-customlist",
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1775,17 +1775,17 @@ PlacesPanelMenuView.prototype = {
 
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
     }
     else {
       button = document.createElement("toolbarbutton");
-      button.className = "bookmark-item";
+      button.className = "bookmark-item subviewbutton";
       button.setAttribute("label", aChild.title);
       let icon = aChild.icon;
       if (icon)
         button.setAttribute("image", icon);
 
       if (PlacesUtils.containerTypes.indexOf(type) != -1) {
         button.setAttribute("container", "true");
 
--- a/browser/components/sessionstore/src/SessionCookies.jsm
+++ b/browser/components/sessionstore/src/SessionCookies.jsm
@@ -55,17 +55,19 @@ let SessionCookiesInternal = {
     this._ensureInitialized();
 
     for (let window of windows) {
       let cookies = [];
 
       // Collect all hosts for the current window.
       let hosts = this.getHostsForWindow(window, true);
 
-      for (let [host, isPinned] in Iterator(hosts)) {
+      for (let host of Object.keys(hosts)) {
+        let isPinned = hosts[host];
+
         for (let cookie of CookieStore.getCookiesForHost(host)) {
           // _getCookiesForHost() will only return hosts with the right privacy
           // rules, so there is no need to do anything special with this call
           // to PrivacyLevel.canSave().
           if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
             cookies.push(cookie);
           }
         }
@@ -297,17 +299,17 @@ let CookieStore = {
   getCookiesForHost: function (host) {
     if (!this._hosts.has(host)) {
       return [];
     }
 
     let cookies = [];
 
     for (let pathToNamesMap of this._hosts.get(host).values()) {
-      cookies = cookies.concat([cookie for (cookie of pathToNamesMap.values())]);
+      cookies.push(...pathToNamesMap.values());
     }
 
     return cookies;
   },
 
   /**
    * Stores a given cookie.
    *
--- a/browser/components/sessionstore/src/SessionStorage.jsm
+++ b/browser/components/sessionstore/src/SessionStorage.jsm
@@ -108,19 +108,19 @@ let SessionStorageInternal = {
       let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
       let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
 
       // There is no need to pass documentURI, it's only used to fill documentURI property of
       // domstorage event, which in this case has no consumer. Prevention of events in case
       // of missing documentURI will be solved in a followup bug to bug 600307.
       let storage = storageManager.createStorage(principal, "", aDocShell.usePrivateBrowsing);
 
-      for (let [key, value] in Iterator(data)) {
+      for (let key of Object.keys(data)) {
         try {
-          storage.setItem(key, value);
+          storage.setItem(key, data[key]);
         } catch (e) {
           // throws e.g. for URIs that can't have sessionStorage
           console.error(e);
         }
       }
     }
   },
 
--- a/browser/components/sessionstore/test/browser_615394-SSWindowState_events.js
+++ b/browser/components/sessionstore/test/browser_615394-SSWindowState_events.js
@@ -283,17 +283,18 @@ function test_setBrowserState() {
   }
 
   window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
   window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
   Services.ww.registerNotification(windowObserver);
 
   waitForBrowserState(lameMultiWindowState, function() {
     let checkedWindows = 0;
-    for each (let [id, winEvents] in Iterator(windowEvents)) {
+    for (let id of Object.keys(windowEvents)) {
+      let winEvents = windowEvents[id];
       is(winEvents.busyEventCount, 1,
          "[test_setBrowserState] window" + id + " busy event count correct");
       is(winEvents.readyEventCount, 1,
          "[test_setBrowserState] window" + id + " ready event count correct");
       checkedWindows++;
     }
     is(checkedWindows, 2,
        "[test_setBrowserState] checked 2 windows");
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -230,17 +230,17 @@ let DebuggerController = {
       target.on("close", this._onTabDetached);
       target.on("navigate", this._onTabNavigated);
       target.on("will-navigate", this._onTabNavigated);
       this.client = client;
 
       if (target.chrome) {
         this._startChromeDebugging(chromeDebugger, startedDebugging.resolve);
       } else {
-        this._startDebuggingTab(threadActor, startedDebugging.resolve);
+        this._startDebuggingTab(startedDebugging.resolve);
         const startedTracing = promise.defer();
         this._startTracingTab(traceActor, startedTracing.resolve);
 
         return promise.all([startedDebugging.promise, startedTracing.promise]);
       }
 
       return startedDebugging.promise;
     }
@@ -334,38 +334,40 @@ let DebuggerController = {
     if (aResponse.error == "wrongOrder") {
       DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
     }
   },
 
   /**
    * Sets up a debugging session.
    *
-   * @param string aThreadActor
-   *        The remote protocol grip of the tab.
    * @param function aCallback
    *        A function to invoke once the client attaches to the active thread.
    */
-  _startDebuggingTab: function(aThreadActor, aCallback) {
-    this.client.attachThread(aThreadActor, (aResponse, aThreadClient) => {
+  _startDebuggingTab: function(aCallback) {
+    this._target.activeTab.attachThread({
+      useSourceMaps: Prefs.sourceMapsEnabled
+    }, (aResponse, aThreadClient) => {
       if (!aThreadClient) {
         Cu.reportError("Couldn't attach to thread: " + aResponse.error);
         return;
       }
       this.activeThread = aThreadClient;
 
       this.ThreadState.connect();
       this.StackFrames.connect();
       this.SourceScripts.connect();
-      aThreadClient.resume(this._ensureResumptionOrder);
+      if (aThreadClient.paused) {
+        aThreadClient.resume(this._ensureResumptionOrder);
+      }
 
       if (aCallback) {
         aCallback();
       }
-    }, { useSourceMaps: Prefs.sourceMapsEnabled });
+    });
   },
 
   /**
    * Sets up a chrome debugging session.
    *
    * @param object aChromeDebugger
    *        The remote protocol grip of the chrome debugger.
    * @param function aCallback
@@ -377,17 +379,19 @@ let DebuggerController = {
         Cu.reportError("Couldn't attach to thread: " + aResponse.error);
         return;
       }
       this.activeThread = aThreadClient;
 
       this.ThreadState.connect();
       this.StackFrames.connect();
       this.SourceScripts.connect();
-      aThreadClient.resume(this._ensureResumptionOrder);
+      if (aThreadClient.paused) {
+        aThreadClient.resume(this._ensureResumptionOrder);
+      }
 
       if (aCallback) {
         aCallback();
       }
     }, { useSourceMaps: Prefs.sourceMapsEnabled });
   },
 
   /**
@@ -414,31 +418,33 @@ let DebuggerController = {
     });
   },
 
   /**
    * Detach and reattach to the thread actor with useSourceMaps true, blow
    * away old sources and get them again.
    */
   reconfigureThread: function(aUseSourceMaps) {
-    this.client.reconfigureThread({ useSourceMaps: aUseSourceMaps }, aResponse => {
+    this.activeThread.reconfigure({ useSourceMaps: aUseSourceMaps }, aResponse => {
       if (aResponse.error) {
         let msg = "Couldn't reconfigure thread: " + aResponse.message;
         Cu.reportError(msg);
         dumpn(msg);
         return;
       }
 
       // Reset the view and fetch all the sources again.
       DebuggerView.handleTabNavigation();
       this.SourceScripts.handleTabNavigation();
 
       // Update the stack frame list.
-      this.activeThread._clearFrames();
-      this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+      if (this.activeThread.paused) {
+        this.activeThread._clearFrames();
+        this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+      }
     });
   },
 
   /**
    * Attempts to quit the current process if allowed.
    *
    * @return object
    *         A promise that is resolved if the app will quit successfully.
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -1063,17 +1063,34 @@ FilterView.prototype = {
    * Called when a filtering key sequence was pressed.
    *
    * @param string aOperator
    *        The operator to use for filtering.
    */
   _doSearch: function(aOperator = "", aText = "") {
     this._searchbox.focus();
     this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
-    this._searchbox.value = aOperator + (aText || DebuggerView.editor.getSelection());
+
+    if (aText) {
+      this._searchbox.value = aOperator + aText;
+    }
+    else if (DebuggerView.editor.somethingSelected()) {
+      this._searchbox.value = aOperator + DebuggerView.editor.getSelection();
+    }
+    else {
+      let cursor = DebuggerView.editor.getCursor();
+      let content = DebuggerView.editor.getText();
+      let location = DebuggerView.Sources.selectedValue;
+      let source = DebuggerController.Parser.get(content, location);
+      let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch });
+
+      if (identifier && identifier.name) {
+        this._searchbox.value = aOperator + identifier.name;
+      }
+    }
   },
 
   /**
    * Called when the source location filter key sequence was pressed.
    */
   _doFileSearch: function() {
     this._doSearch();
     this._searchboxHelpPanel.openPopup(this._searchbox);
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -242,14 +242,15 @@ support-files =
 [browser_dbg_variables-view-popup-07.js]
 [browser_dbg_variables-view-popup-08.js]
 [browser_dbg_variables-view-popup-09.js]
 [browser_dbg_variables-view-reexpand-01.js]
 [browser_dbg_variables-view-reexpand-02.js]
 [browser_dbg_variables-view-webidl.js]
 [browser_dbg_watch-expressions-01.js]
 [browser_dbg_watch-expressions-02.js]
+[browser_dbg_search-function.js]
 [browser_dbg_chrome-create.js]
 skip-if = os == "linux" # Bug 847558
 [browser_dbg_on-pause-raise.js]
 skip-if = os == "linux" # Bug 888811 & bug 891176
 [browser_dbg_break-on-dom-event.js]
 skip-if = os == "mac" # Bug 895426
--- a/browser/devtools/debugger/test/browser_dbg_break-on-dom-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-01.js
@@ -27,17 +27,17 @@ function test() {
     is(gView.instrumentsPaneTab, "variables-tab",
       "The variables tab should be selected by default.");
 
     Task.spawn(function() {
       yield waitForSourceShown(aPanel, ".html");
       is(gEvents.itemCount, 0, "There should be no events before reloading.");
 
       let reloaded = waitForSourcesAfterReload();
-      gDebugger.gClient.activeTab.reload();
+      gDebugger.DebuggerController._target.activeTab.reload();
 
       is(gEvents.itemCount, 0, "There should be no events while reloading.");
       yield reloaded;
       is(gEvents.itemCount, 0, "There should be no events after reloading.");
 
       yield closeDebuggerAndFinish(aPanel);
     });
 
--- a/browser/devtools/debugger/test/browser_dbg_break-on-dom-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-02.js
@@ -42,17 +42,17 @@ function test() {
     }
 
     function testFetchOnReloadWhenFocused() {
       return Task.spawn(function() {
         let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
 
         let reloading = once(gDebugger.gTarget, "will-navigate");
         let reloaded = waitForSourcesAfterReload();
-        gDebugger.gClient.activeTab.reload();
+        gDebugger.DebuggerController._target.activeTab.reload();
 
         yield reloading;
 
         is(gEvents.itemCount, 0,
           "There should be no events displayed in the view while reloading.");
         ok(true,
           "Event listeners were removed when the target started navigating.");
 
@@ -84,17 +84,17 @@ function test() {
         gView.toggleInstrumentsPane({ visible: true, animated: false }, 0);
         is(gView.instrumentsPaneHidden, false,
           "The instruments pane should still be visible.");
         is(gView.instrumentsPaneTab, "variables-tab",
           "The variables tab should be selected.");
 
         let reloading = once(gDebugger.gTarget, "will-navigate");
         let reloaded = waitForSourcesAfterReload();
-        gDebugger.gClient.activeTab.reload();
+        gDebugger.DebuggerController._target.activeTab.reload();
 
         yield reloading;
 
         is(gEvents.itemCount, 0,
           "There should be no events displayed in the view while reloading.");
         ok(true,
           "Event listeners were removed when the target started navigating.");
 
--- a/browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-event.js
@@ -63,17 +63,17 @@ function pauseDebuggee() {
 }
 
 // Test pause on all events.
 function testBreakOnAll() {
   let deferred = promise.defer();
 
   // Test calling pauseOnDOMEvents from a paused state.
   gThreadClient.pauseOnDOMEvents("*", (aPacket) => {
-    is(aPacket, undefined,
+    is(aPacket.error, undefined,
       "The pause-on-any-event request completed successfully.");
 
     gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
       is(aPacket.why.type, "pauseOnDOMEvents",
         "A hidden breakpoint was hit.");
       is(aPacket.frame.callee.name, "keyupHandler",
         "The keyupHandler is entered.");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_search-function.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that Debugger Search uses the identifier under cursor
+ * if nothing is selected or manually passed
+ */
+
+"use strict";
+
+function test() {
+
+  const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+    let Source = 'code_function-search-01.js';
+    let Debugger = aPanel.panelWin;
+    let Editor = Debugger.DebuggerView.editor;
+    let Filtering = Debugger.DebuggerView.Filtering;
+
+    waitForSourceShown(aPanel, Source).then(() => {
+      Editor.setCursor({ line: 7, ch: 0});
+      Filtering._doSearch("@");
+      is(Filtering._searchbox.value, "@test", "Searchbox value should be set to the identifier under cursor if no aText or selection provided");
+      closeDebuggerAndFinish(aPanel);
+    });
+  });
+};
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-02.js
@@ -3,35 +3,33 @@
 
 /**
  * Test that we can toggle between the original and generated sources.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
 const JS_URL = EXAMPLE_URL + "code_binary_search.js";
 
-let gTab, gDebuggee, gPanel, gDebugger;
-let gEditor, gSources, gFrames, gPrefs, gOptions;
+let gDebuggee, gPanel, gDebugger, gEditor;
+let gSources, gFrames, gPrefs, gOptions;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
-    gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
     gPrefs = gDebugger.Prefs;
     gOptions = gDebugger.DebuggerView.Options;
 
     waitForSourceShown(gPanel, ".coffee")
       .then(testToggleGeneratedSource)
       .then(testSetBreakpoint)
-      .then(testHitBreakpoint)
       .then(testToggleOnPause)
       .then(testResume)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
@@ -63,44 +61,33 @@ function testToggleGeneratedSource() {
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
 
   gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 7 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a js file.");
 
-    deferred.resolve();
-  });
-
-  return deferred.promise;
-}
-
-function testHitBreakpoint() {
-  let deferred = promise.defer();
-
-  gDebugger.gThreadClient.resume(aResponse => {
-    ok(!aResponse.error, "Shouldn't get an error resuming.");
-    is(aResponse.type, "resumed", "Type should be 'resumed'.");
+    gDebugger.gClient.addOneTimeListener("resumed", () => {
+      waitForCaretAndScopes(gPanel, 7).then(() => {
+        // Make sure that we have JavaScript stack frames.
+        is(gFrames.itemCount, 1,
+          "Should have only one frame.");
+        is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1,
+          "First frame should not be a coffee source frame.");
+        isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
+          "First frame should be a JS frame.");
 
-    waitForCaretAndScopes(gPanel, 7).then(() => {
-      // Make sure that we have JavaScript stack frames.
-      is(gFrames.itemCount, 1,
-        "Should have only one frame.");
-      is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1,
-        "First frame should not be a coffee source frame.");
-      isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
-        "First frame should be a JS frame.");
+        deferred.resolve();
+      });
 
-      deferred.resolve();
+      // This will cause the breakpoint to be hit, and put us back in the
+      // paused state.
+      gDebuggee.binary_search([0, 2, 3, 5, 7, 10], 5);
     });
-
-    // This will cause the breakpoint to be hit, and put us back in the
-    // paused state.
-    gDebuggee.binary_search([0, 2, 3, 5, 7, 10], 5);
   });
 
   return deferred.promise;
 }
 
 function testToggleOnPause() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".coffee", 5).then(() => {
     is(gPrefs.sourceMapsEnabled, true,
@@ -143,17 +130,16 @@ function testResume() {
 
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 registerCleanupFunction(function() {
-  gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gFrames = null;
   gPrefs = null;
   gOptions = null;
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
@@ -3,33 +3,31 @@
 
 /**
  * Test that we can debug minified javascript with source maps.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_minified.html";
 const JS_URL = EXAMPLE_URL + "code_math.js";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gDebuggee, gPanel, gDebugger;
 let gEditor, gSources, gFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
-    gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForSourceShown(gPanel, JS_URL)
       .then(checkInitialSource)
       .then(testSetBreakpoint)
-      .then(testHitBreakpoint)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkInitialSource() {
@@ -40,59 +38,44 @@ function checkInitialSource() {
   is(gEditor.getText().split("\n").length, 46,
     "The debugger's editor should have the original source displayed, " +
     "not the whitespace stripped minified version.");
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
 
-  gDebugger.gThreadClient.interrupt(aResponse => {
-    gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 30, column: 21 }, aResponse => {
-      ok(!aResponse.error,
-        "Should be able to set a breakpoint in a js file.");
-      ok(!aResponse.actualLocation,
-        "Should be able to set a breakpoint on line 30 and column 10.");
+  gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 30, column: 21 }, aResponse => {
+    ok(!aResponse.error,
+      "Should be able to set a breakpoint in a js file.");
+    ok(!aResponse.actualLocation,
+      "Should be able to set a breakpoint on line 30 and column 10.");
 
-      deferred.resolve();
+    gDebugger.gClient.addOneTimeListener("resumed", () => {
+      waitForCaretAndScopes(gPanel, 30).then(() => {
+        // Make sure that we have the right stack frames.
+        is(gFrames.itemCount, 9,
+          "Should have nine frames.");
+        is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".min.js"), -1,
+          "First frame should not be a minified JS frame.");
+        isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
+          "First frame should be a JS frame.");
+
+        deferred.resolve();
+      });
+
+      // This will cause the breakpoint to be hit, and put us back in the
+      // paused state.
+      gDebuggee.arithmetic();
     });
   });
 
   return deferred.promise;
 }
 
-function testHitBreakpoint() {
-  let deferred = promise.defer();
-
-  gDebugger.gThreadClient.resume(aResponse => {
-    ok(!aResponse.error, "Shouldn't get an error resuming.");
-    is(aResponse.type, "resumed", "Type should be 'resumed'.");
-
-    waitForCaretAndScopes(gPanel, 30).then(() => {
-      // Make sure that we have the right stack frames.
-      is(gFrames.itemCount, 9,
-        "Should have nine frames.");
-      is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".min.js"), -1,
-        "First frame should not be a minified JS frame.");
-      isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
-        "First frame should be a JS frame.");
-
-      deferred.resolve();
-    });
-
-    // This will cause the breakpoint to be hit, and put us back in the
-    // paused state.
-    gDebuggee.arithmetic();
-  });
-
-  return deferred.promise;
-}
-
-
 registerCleanupFunction(function() {
-  gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
@@ -96,17 +96,17 @@ function testSetBreakpoint() {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function reloadPage() {
   let loaded = waitForSourceAndCaret(gPanel, ".js", 3);
-  gDebugger.gClient.activeTab.reload();
+  gDebugger.DebuggerController._target.activeTab.reload();
   return loaded.then(() => ok(true, "Page was reloaded and execution resumed."));
 }
 
 function testHitBreakpoint() {
   let deferred = promise.defer();
 
   gDebugger.gThreadClient.resume(aResponse => {
     ok(!aResponse.error, "Shouldn't get an error resuming.");
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -405,17 +405,17 @@ function ensureThreadClientState(aPanel,
     return promise.resolve(null);
   } else {
     return waitForThreadEvents(aPanel, aState);
   }
 }
 
 function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
   let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
-  let activeTab = aPanel.panelWin.gClient.activeTab;
+  let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
   aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
   return finished;
 }
 
 function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) {
   let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
   content.history[aDirection]();
   return finished;
--- a/browser/devtools/fontinspector/font-inspector.js
+++ b/browser/devtools/fontinspector/font-inspector.js
@@ -189,27 +189,26 @@ FontInspector.prototype = {
    * Select the <body> to show all the fonts included in the document.
    */
   showAll: function FI_showAll() {
     if (!this.isActive() ||
         !this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       return;
     }
-    let node = this.inspector.selection.nodeFront;
-    let contentDocument = node.ownerDocument;
-    let root = contentDocument.documentElement;
-    if (contentDocument.body) {
-      root = contentDocument.body;
-    }
-    this.inspector.selection.setNode(root, "fontinspector");
+
+    // Select the body node to show all fonts
+    let walker = this.inspector.walker;
+
+    walker.getRootNode().then(root => walker.querySelector(root, "body")).then(body => {
+      this.inspector.selection.setNodeFront(body, "fontinspector");
+    });
   },
 }
 
-
 window.setPanel = function(panel) {
   window.fontInspector = new FontInspector(panel, window);
 }
 
 window.onunload = function() {
   if (window.fontInspector) {
     window.fontInspector.destroy();
   }
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -7,18 +7,18 @@ let {devtools} = Cu.import("resource://g
 let TargetFactory = devtools.TargetFactory;
 
 let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
 function test() {
   waitForExplicitFinish();
 
   let doc;
-  let node;
   let view;
+  let viewDoc;
   let inspector;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
@@ -38,65 +38,88 @@ function test() {
       let target = TargetFactory.forTab(gBrowser.selectedTab);
       gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
         openFontInspector(toolbox.getCurrentPanel());
       });
     }
   }
 
   function openFontInspector(aInspector) {
+    info("Inspector open");
     inspector = aInspector;
 
-    info("Inspector open");
-
     inspector.selection.setNode(doc.body);
     inspector.sidebar.select("fontinspector");
-    inspector.sidebar.once("fontinspector-ready", viewReady);
+    inspector.sidebar.once("fontinspector-ready", testBodyFonts);
   }
 
-  function viewReady() {
+  function testBodyFonts() {
     info("Font Inspector ready");
 
     view = inspector.sidebar.getWindowForTab("fontinspector");
+    viewDoc = view.document;
 
     ok(!!view.fontInspector, "Font inspector document is alive.");
 
-    let d = view.document;
-
-    let s = d.querySelectorAll("#all-fonts > section");
+    let s = viewDoc.querySelectorAll("#all-fonts > section");
     is(s.length, 2, "Found 2 fonts");
 
     is(s[0].querySelector(".font-name").textContent,
        "DeLarge Bold", "font 0: Right font name");
     ok(s[0].classList.contains("is-remote"),
        "font 0: is remote");
     is(s[0].querySelector(".font-url").value,
        "http://mochi.test:8888/browser/browser/devtools/fontinspector/test/browser_font.woff",
        "font 0: right url");
     is(s[0].querySelector(".font-format").textContent,
        "woff", "font 0: right font format");
     is(s[0].querySelector(".font-css-name").textContent,
        "bar", "font 0: right css name");
 
-
     let font1Name = s[1].querySelector(".font-name").textContent;
     let font1CssName = s[1].querySelector(".font-css-name").textContent;
 
     // On Linux test machines, the Arial font doesn't exist.
     // The fallback is "Liberation Sans"
 
     ok((font1Name == "Arial") || (font1Name == "Liberation Sans"),
        "font 1: Right font name");
     ok(s[1].classList.contains("is-local"), "font 1: is local");
     ok((font1CssName == "Arial") || (font1CssName == "Liberation Sans"),
        "Arial", "font 1: right css name");
 
-    executeSoon(function() {
-      gDevTools.once("toolbox-destroyed", finishUp);
-      inspector._toolbox.destroy();
+    testDivFonts();
+  }
+
+  function testDivFonts() {
+    inspector.selection.setNode(doc.querySelector("div"));
+    inspector.once("inspector-updated", () => {
+      let s = viewDoc.querySelectorAll("#all-fonts > section");
+      is(s.length, 1, "Found 1 font on DIV");
+      is(s[0].querySelector(".font-name").textContent, "DeLarge Bold",
+        "The DIV font has the right name");
+
+      testShowAllFonts();
+    });
+  }
+
+  function testShowAllFonts() {
+    viewDoc.querySelector("#showall").click();
+    inspector.once("inspector-updated", () => {
+      is(inspector.selection.node, doc.body, "Show all fonts selected the body node");
+      let s = viewDoc.querySelectorAll("#all-fonts > section");
+      is(s.length, 2, "And font-inspector still shows 2 fonts for body");
+
+      finishUp();
     });
   }
 
   function finishUp() {
-    gBrowser.removeCurrentTab();
-    finish();
+    executeSoon(function() {
+      gDevTools.once("toolbox-destroyed", () => {
+        doc = view = viewDoc = inspector = null;
+        gBrowser.removeCurrentTab();
+        finish();
+      });
+      inspector._toolbox.destroy();
+    });
   }
 }
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -279,16 +279,17 @@ TabTarget.prototype = {
     this._setupRemoteListeners();
 
     let attachTab = () => {
       this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
         if (!aTabClient) {
           this._remote.reject("Unable to attach to the tab");
           return;
         }
+        this.activeTab = aTabClient;
         this.threadActor = aResponse.threadActor;
         this._remote.resolve(null);
       });
     };
 
     if (this.isLocalTab) {
       this._client.connect((aType, aTraits) => {
         this._client.listTabs(aResponse => {
@@ -439,53 +440,57 @@ TabTarget.prototype = {
     // non-remoted targets.
     this.off("thread-resumed", this._handleThreadState);
     this.off("thread-paused", this._handleThreadState);
 
     if (this._tab) {
       this._teardownListeners();
     }
 
+    let cleanupAndResolve = () => {
+      this._cleanup();
+      this._destroyer.resolve(null);
+    };
     // If this target was not remoted, the promise will be resolved before the
     // function returns.
     if (this._tab && !this._client) {
-      this._cleanup();
-      this._destroyer.resolve(null);
+      cleanupAndResolve();
     } else if (this._client) {
       // If, on the other hand, this target was remoted, the promise will be
       // resolved after the remote connection is closed.
       this._teardownRemoteListeners();
 
       if (this.isLocalTab) {
         // We started with a local tab and created the client ourselves, so we
         // should close it.
-        this._client.close(() => {
-          this._cleanup();
-          this._destroyer.resolve(null);
-        });
+        this._client.close(cleanupAndResolve);
       } else {
         // The client was handed to us, so we are not responsible for closing
-        // it.
-        this._cleanup();
-        this._destroyer.resolve(null);
+        // it. We just need to detach from the tab, if already attached.
+        if (this.activeTab) {
+          this.activeTab.detach(cleanupAndResolve);
+        } else {
+          cleanupAndResolve();
+        }
       }
     }
 
     return this._destroyer.promise;
   },
 
   /**
    * Clean up references to what this target points to.
    */
   _cleanup: function TabTarget__cleanup() {
     if (this._tab) {
       targets.delete(this._tab);
     } else {
       promiseTargets.delete(this._form);
     }
+    this.activeTab = null;
     this._client = null;
     this._tab = null;
     this._form = null;
     this._remote = null;
   },
 
   toString: function() {
     return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -207,17 +207,17 @@ OptionsPanel.prototype = {
           newValue: this.value
         };
         data.oldValue = Services.prefs.getCharPref(data.pref);
         Services.prefs.setCharPref(data.pref, data.newValue);
         gDevTools.emit("pref-changed", data);
       }.bind(menulist));
     }
 
-    this.target.client.attachTab(this.target.client.activeTab._actor, (response) => {
+    this.target.client.attachTab(this.target.activeTab._actor, (response) => {
       this._origJavascriptEnabled = response.javascriptEnabled;
       this._origCacheEnabled = response.cacheEnabled;
 
       this._populateDisableJSCheckbox();
       this._populateDisableCacheCheckbox();
     });
   },
 
@@ -243,33 +243,33 @@ OptionsPanel.prototype = {
    */
   _disableJSClicked: function(event) {
     let checked = event.target.checked;
 
     let options = {
       "javascriptEnabled": !checked
     };
 
-    this.target.client.reconfigureTab(options);
+    this.target.activeTab.reconfigure(options);
   },
 
   /**
    * Disables the cache for the currently loaded tab.
    *
    * @param {Event} event
    *        The event sent by checking / unchecking the disable cache checkbox.
    */
   _disableCacheClicked: function(event) {
     let checked = event.target.checked;
 
     let options = {
       "cacheEnabled": !checked
     };
 
-    this.target.client.reconfigureTab(options);
+    this.target.activeTab.reconfigure(options);
   },
 
   destroy: function() {
     if (this.destroyPromise) {
       return this.destroyPromise;
     }
 
     let deferred = promise.defer();
@@ -286,16 +286,16 @@ OptionsPanel.prototype = {
     this._disableJSClicked = this._disableCacheClicked = null;
 
     // If the cache or JavaScript is disabled we need to revert them to their
     // original values.
     let options = {
       "cacheEnabled": this._origCacheEnabled,
       "javascriptEnabled": this._origJavascriptEnabled
     };
-    this.target.client.reconfigureTab(options, () => {
+    this.target.activeTab.reconfigure(options, () => {
       this.toolbox = null;
       deferred.resolve();
     }, true);
 
     return deferred.promise;
   }
 };
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -1212,17 +1212,17 @@ Toolbox.prototype = {
     gDevTools.off("tool-unregistered", this._toolUnregistered);
 
     let outstanding = [];
     for (let [id, panel] of this._toolPanels) {
       try {
         outstanding.push(panel.destroy());
       } catch (e) {
         // We don't want to stop here if any panel fail to close.
-        console.error(e);
+        console.error("Panel " + id + ":", e);
       }
     }
 
     // Destroying the walker and inspector fronts
     outstanding.push(this.destroyInspector());
 
     // Removing buttons
     this._pickerButton.removeEventListener("command", this.togglePicker, false);
--- a/browser/devtools/shadereditor/test/head.js
+++ b/browser/devtools/shadereditor/test/head.js
@@ -224,22 +224,22 @@ function ensurePixelIs(aDebuggee, aPosit
 }
 
 function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
   executeSoon(() => content.history[aDirection]());
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.client.activeTab.navigateTo(aUrl));
+  executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function reload(aTarget, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.client.activeTab.reload());
+  executeSoon(() => aTarget.activeTab.reload());
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function initBackend(aUrl) {
   info("Initializing a shader editor front.");
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init(() => true);
--- a/browser/devtools/shared/Parser.jsm
+++ b/browser/devtools/shared/Parser.jsm
@@ -4,16 +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 Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "Reflect", "resource://gre/modules/reflect.jsm");
 
 this.EXPORTED_SYMBOLS = ["Parser", "ParserHelpers", "SyntaxTreeVisitor"];
 
 /**
  * A JS parser using the reflection API.
@@ -60,31 +61,31 @@ Parser.prototype = {
     if (!scriptMatches.length) {
       // Reflect.parse throws when encounters a syntax error.
       try {
         let nodes = Reflect.parse(aSource);
         let length = aSource.length;
         syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
       } catch (e) {
         this.errors.push(e);
-        log(aUrl, e);
+        DevToolsUtils.reportException(aUrl, e);
       }
     }
     // Generate the AST nodes for each script.
     else {
       for (let script of scriptMatches) {
         // Reflect.parse throws when encounters a syntax error.
         try {
           let nodes = Reflect.parse(script);
           let offset = aSource.indexOf(script);
           let length = script.length;
           syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
         } catch (e) {
           this.errors.push(e);
-          log(aUrl, e);
+          DevToolsUtils.reportException(aUrl, e);
         }
       }
     }
 
     let pool = new SyntaxTreesPool(syntaxTrees);
 
     // Cache the syntax trees pool by the specified url. This is entirely
     // optional, but it's strongly encouraged to cache ASTs because
@@ -220,17 +221,17 @@ SyntaxTreesPool.prototype = {
           scriptLength: syntaxTree.length,
           scriptOffset: syntaxTree.offset,
           parseResults: syntaxTree[aFunction].apply(syntaxTree, aParams)
         });
       } catch (e) {
         // Can't guarantee that the tree traversal logic is forever perfect :)
         // Language features may be added, in which case the recursive methods
         // need to be updated. If an exception is thrown here, file a bug.
-        log("syntax tree", e);
+        DevToolsUtils.reportException("syntax tree", e);
       }
     }
     this._cache.set(requestId, results);
     return results;
   },
 
   _trees: null,
   _cache: null
@@ -2336,31 +2337,9 @@ let SyntaxTreeVisitor = {
       }
     }
     if (aCallbacks.onLiteral) {
       aCallbacks.onLiteral(aNode);
     }
   }
 };
 
-/**
- * Logs a warning.
- *
- * @param string aStr
- *        The message to be displayed.
- * @param Exception aEx
- *        The thrown exception.
- */
-function log(aStr, aEx) {
-  let msg = "Warning: " + aStr + ", " + aEx.message;
-
-  if ("lineNumber" in aEx && "columnNumber" in aEx) {
-    msg += ", line: " + aEx.lineNumber + ", column: " + aEx.columnNumber;
-  }
-  if ("stack" in aEx) {
-    msg += "\n" + aEx.stack;
-  }
-
-  Cu.reportError(msg);
-  dump(msg + "\n");
-};
-
 XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", () => Reflect);
--- a/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm
+++ b/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm
@@ -162,17 +162,19 @@ BreadcrumbsWidget.prototype = {
   ensureElementIsVisible: function(aElement) {
     if (!aElement) {
       return;
     }
 
     // Repeated calls to ensureElementIsVisible would interfere with each other
     // and may sometimes result in incorrect scroll positions.
     setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => {
-      this._list.ensureElementIsVisible(aElement);
+      if (this._list.ensureElementIsVisible) {
+        this._list.ensureElementIsVisible(aElement);
+      }
     });
   },
 
   /**
    * The underflow and overflow listener for the arrowscrollbox container.
    */
   _onUnderflow: function({ target }) {
     if (target != this._list) {
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -505,18 +505,16 @@
 @BINPATH@/components/Weave.js
 #endif
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/CaptivePortalDetectComponents.manifest
 @BINPATH@/components/captivedetect.js
 #endif
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
-@BINPATH@/components/TelemetryPing.js
-@BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/SettingsService.js
 @BINPATH@/components/SettingsService.manifest
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -472,16 +472,20 @@ getUserMedia.noVideo.label = No Video
 getUserMedia.noAudio.label = No Audio
 getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
 getUserMedia.shareSelectedDevices.accesskey = S
 getUserMedia.denyRequest.label = Don't Share
 getUserMedia.denyRequest.accesskey = D
 getUserMedia.sharingCamera.message2 = You are currently sharing your camera with this page.
 getUserMedia.sharingMicrophone.message2 = You are currently sharing your microphone with this page.
 getUserMedia.sharingCameraAndMicrophone.message2 = You are currently sharing your camera and microphone with this page.
+getUserMedia.continueSharing.label = Continue Sharing
+getUserMedia.continueSharing.accesskey = C
+getUserMedia.stopSharing.label = Stop Sharing
+getUserMedia.stopSharing.accesskey = S
 
 # Mixed Content Blocker Doorhanger Notification
 # LOCALIZATION NOTE - %S is brandShortName
 mixedContentBlocked.message = %S has blocked content that isn't secure.
 mixedContentBlocked.keepBlockingButton.label = Keep Blocking
 mixedContentBlocked.keepBlockingButton.accesskey = B
 mixedContentBlocked.unblock.label = Disable Protection on This Page
 mixedContentBlocked.unblock.accesskey = D
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -638,19 +638,16 @@ Desktop browser's sync prefs.
         <description>&sync.flyout.pairSuccess.description1;</description>
         <description>&sync.flyout.pairSuccess.description2;</description>
       </vbox>
 
     </flyoutpanel>
 #endif
 
     <flyoutpanel id="prefs-flyoutpanel" class="flyout-narrow" headertext="&optionsHeader.title;">
-      <settings id="prefs-charencoding" label="&optionsHeader.char.title;">
-        <setting pref="browser.menu.showCharacterEncoding" title="&optionsHeader.char.options.label;" type="bool"/>
-      </settings>
       <settings id="prefs-privdata" label="&clearPrivateData.title;">
         <description>&clearPrivateData.label;</description>
 
         <checkbox id="prefs-privdata-history" itemName="history" label="&clearPrivateData.history;" checked="true" />
 
         <checkbox id="prefs-privdata-other" label="&clearPrivateData.otherdata;"/>
         <hbox id="prefs-privdata-subitems" >
           <checkbox class="privdata-subitem-item" checked="true" itemName="downloads" label="&clearPrivateData.downloadHist;"/>
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -116,17 +116,17 @@ HelperAppLauncherDialog.prototype = {
     let fragment =  ContentUtil.populateFragmentFromString(
                       document.createDocumentFragment(),
                       msg,
                       {
                         text: aLauncher.suggestedFileName,
                         className: "download-filename-text"
                       },
                       {
-                        text: aLauncher.suggestedFileName,
+                        text: aLauncher.downloadSize,
                         className: "download-size-text"
                       },
                       {
                         text: aLauncher.source.host,
                         className: "download-host-text"
                       }
                     );
     let newBar = notificationBox.appendNotification("",
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -246,19 +246,36 @@ function showBrowserSpecificIndicator(aB
     Cu.reportError("showBrowserSpecificIndicator: got neither video nor audio access");
     return;
   }
 
   let chromeWin = aBrowser.ownerDocument.defaultView;
   let stringBundle = chromeWin.gNavigatorBundle;
 
   let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
-  let mainAction = null;
-  let secondaryActions = null;
+
+  let windowId = aBrowser.contentWindow
+                         .QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils)
+                         .currentInnerWindowID;
+  let mainAction = {
+    label: stringBundle.getString("getUserMedia.continueSharing.label"),
+    accessKey: stringBundle.getString("getUserMedia.continueSharing.accesskey"),
+    callback: function () {},
+    dismiss: true
+  };
+  let secondaryActions = [{
+    label: stringBundle.getString("getUserMedia.stopSharing.label"),
+    accessKey: stringBundle.getString("getUserMedia.stopSharing.accesskey"),
+    callback: function () {
+      Services.obs.notifyObservers(null, "getUserMedia:revoke", windowId);
+    }
+  }];
   let options = {
+    hideNotNow: true,
     dismissed: true,
     eventCallback: function(aTopic) aTopic == "swapping"
   };
   chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
                                     "webRTC-sharingDevices-notification-icon", mainAction,
                                     secondaryActions, options);
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -154,17 +154,17 @@ toolbarbutton.chevron:-moz-locale-dir(rt
 
   toolbarbutton.chevron > .toolbarbutton-icon {
     width: 13px;
   }
 }
 
 /* ----- BOOKMARK BUTTONS ----- */
 
-toolbarbutton.bookmark-item,
+toolbarbutton.bookmark-item:not(.subviewbutton),
 #personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder {
   font-weight: bold;
   color: #222;
   border: 0;
   border-radius: 10000px;
   padding: 1px 8px;
   margin: 0 0 1px;
 }
@@ -194,18 +194,18 @@ toolbarbutton.bookmark-item,
   margin: 0 !important;
 }
 
 toolbarbutton.bookmark-item:hover,
 toolbarbutton.bookmark-item[open="true"] {
   background-color: rgba(0, 0, 0, .205);
 }
 
-toolbarbutton.bookmark-item:hover,
-toolbarbutton.bookmark-item[open="true"] {
++toolbarbutton.bookmark-item:hover:not(.subviewbutton),
++toolbarbutton.bookmark-item[open="true"]:not(.subviewbutton) {
   color: #FFF !important;
   text-shadow: 0 1px rgba(0, 0, 0, .4) !important;
 }
 
 .bookmark-item:hover > .toolbarbutton-menu-dropmarker,
 .bookmark-item[open="true"] > .toolbarbutton-menu-dropmarker {
   -moz-image-region: rect(5px, 7px, 10px, 0);
 }
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -1,18 +1,14 @@
 /* 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/customizableui/panelUIOverlay.inc.css
 
-.panel-subviews {
-  background-color: #f5f5f5;
-}
-
 @media (min-resolution: 2dppx) {
   #customization-palette toolbarbutton > .toolbarbutton-icon,
   #PanelUI-contents toolbarbutton > .toolbarbutton-icon {
     width: 20px;
   }
 
   #PanelUI-customize {
     list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -1,44 +1,62 @@
 /* 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/. */
 
 %filter substitution
 
 %define menuPanelWidth 22.35em
 %define exitSubviewGutterWidth 38px
-%define buttonStateHover :not(:-moz-any([disabled],[checked="true"],[open],:active)):hover
-%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active)
+%define buttonStateHover :not(:-moz-any([disabled],[open],[checked="true"],[_moz-menuactive="true"],:active)):hover
+%define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],[_moz-menuactive="true"],:hover:active)
 
 %include ../browser.inc
 
 .panel-subviews {
-  background-image: linear-gradient(to bottom, white 1px, rgba(255, 255, 255, 0) 15px);
-  background-color: -moz-dialog;
-  box-shadow: -1px 0px 0px rgba(0, 0, 0, 0.2), -1px 0px 2px rgba(0, 0, 0, 0.1), 1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+  padding: 4px;
+  background-color: hsla(0,0%,100%,.97);
+  background-clip: padding-box;
+  border-right: 1px solid hsla(210,4%,10%,.2);
+  border-left: 1px solid hsla(210,4%,10%,.2);
+  box-shadow: 0 3px 5px hsla(210,4%,10%,.1),
+              0 0 7px hsla(210,4%,10%,.1);
+  color: #222426;
   -moz-margin-start: @exitSubviewGutterWidth@;
 }
 
-.panel-subviews:-moz-locale-dir(rtl) {
-  box-shadow: 1px 0px 0px rgba(0, 0, 0, 0.2), 1px 0px 2px rgba(0, 0, 0, 0.1), -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
-}
-
 .panel-viewstack[viewtype="main"] > .panel-subviews {
   transform: translateX(@menuPanelWidth@);
 }
 
 .panel-viewstack[viewtype="main"] > .panel-subviews:-moz-locale-dir(rtl) {
   transform: translateX(-@menuPanelWidth@);
 }
 
 .panel-viewstack:not([viewtype="main"]) > .panel-mainview > #PanelUI-mainView {
   -moz-box-flex: 1;
 }
 
+.panel-subview-header,
+.subviewbutton.panel-subview-footer {
+  padding: 12px;
+  background-color: hsla(210,4%,10%,.04);
+}
+
+.panel-subview-header {
+  margin: -4px -4px 4px;
+  box-shadow: 0 -1px 0 hsla(210,4%,10%,.08) inset;
+  color: #797c80;
+}
+
+.subviewbutton.panel-subview-footer {
+  margin: 4px -4px -4px;
+  box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
+}
+
 #PanelUI-mainView {
   display: flex;
   flex-direction: column;
 }
 
 #app-extension-point-end > #PanelUI-menu-button {
   padding: 2px 5px;
 }
@@ -312,58 +330,90 @@ toolbarpaletteitem[place="palette"] > to
   background-image: linear-gradient(rgb(38,115,191), rgb(38,125,191));
 }
 
 #customization-palette .toolbarbutton-multiline-text,
 #customization-palette .toolbarbutton-text {
   display: none;
 }
 
-panelview toolbarbutton,
-#widget-overflow-list > toolbarbutton,
+panelview .toolbarbutton-1,
+panelview .subviewbutton,
+.widget-overflow-list .toolbarbutton-1,
 .customizationmode-button,
 #edit-controls@inAnyPanel@ > toolbarbutton,
 #zoom-controls@inAnyPanel@ > toolbarbutton,
 #BMB_bookmarksPopup > menu,
 #BMB_bookmarksPopup > menuitem {
   -moz-appearance: none;
   padding: 2px 6px;
   background-color: hsla(210,4%,10%,0);
   border-radius: 2px;
   border: 1px solid;
   border-color: hsla(210,4%,10%,0);
   transition-property: background-color, border-color;
   transition-duration: 150ms;
 }
 
+.PanelUI-subView .subviewbutton.panel-subview-footer {
+  border-radius: 0;
+  border: none;
+}
+
+.PanelUI-subView .subviewbutton.panel-subview-footer > .toolbarbutton-text {
+  -moz-padding-start: 0;
+  text-align: center;
+}
+
+.PanelUI-subView .subviewbutton:not(.panel-subview-footer) {
+  margin: 2px 0;
+}
+
+.PanelUI-subView .subviewbutton:not(.panel-subview-footer) > .toolbarbutton-text {
+  font-size: 1.1em;
+}
+
+.PanelUI-subView .subviewbutton.bookmark-item {
+  font-weight: normal;
+  color: inherit;
+}
+
+.PanelUI-subView menuseparator,
+.PanelUI-subView toolbarseparator {
+  -moz-margin-start: -5px;
+  -moz-margin-end: -4px;
+}
+
 panelview .toolbarbutton-1,
-#widget-overflow-list > toolbarbutton {
+.widget-overflow-list .toolbarbutton-1 {
   margin-top: 6px;
 }
 
-panelview toolbarbutton@buttonStateHover@,
-#widget-overflow-list > toolbarbutton@buttonStateHover@,
+panelview .toolbarbutton-1@buttonStateHover@,
+panelview .subviewbutton@buttonStateHover@,
+.widget-overflow-list .toolbarbutton-1@buttonStateHover@,
 .customizationmode-button,
 #edit-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@,
 #zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@,
 #BMB_bookmarksPopup > menu@buttonStateHover@,
 #BMB_bookmarksPopup > menuitem@buttonStateHover@ {
   background-color: hsla(210,4%,10%,.08);
   border-color: hsla(210,4%,10%,.1);
 }
 
 
 #edit-controls@inAnyPanel@@buttonStateHover@,
 #zoom-controls@inAnyPanel@@buttonStateHover@ {
   border-color: hsla(210,4%,10%,.1);
 }
 
-panelview toolbarbutton@buttonStateActive@,
+panelview .toolbarbutton-1@buttonStateActive@,
+panelview .subviewbutton@buttonStateActive@,
 .customizationmode-button@buttonStateActive@,
-#widget-overflow-list > toolbarbutton@buttonStateActive@,
+.widget-overflow-list .toolbarbutton-1@buttonStateActive@,
 #edit-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@,
 #zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@,
 #BMB_bookmarksPopup > menu@buttonStateActive@,
 #BMB_bookmarksPopup > menuitem@buttonStateActive@ {
   background-color: hsla(210,4%,10%,.15);
   border-color: hsla(210,4%,10%,.15);
   box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
 }
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -796,17 +796,17 @@ pref("general.useragent.override.youtube
 
 // When true, phone number linkification is enabled.
 pref("browser.ui.linkify.phone", false);
 
 // Enables/disables Spatial Navigation
 pref("snav.enabled", true);
 
 // URL to fetch about:accounts web content from.
-pref("firefox.accounts.remoteUrl", "https://accounts.dev.lcip.org/mobile");
+pref("identity.fxaccounts.remote.uri", "https://accounts.dev.lcip.org/mobile");
 
 // This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
 // this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
 // repackager of this code using an alternate snippet url, please keep your users safe
 pref("browser.snippets.updateUrl", "https://snippets.mozilla.com/json/%SNIPPETS_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
 
 // How frequently we check for new snippets, in seconds (1 day)
 pref("browser.snippets.updateInterval", 86400);
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -97,19 +97,16 @@ public final class HomeConfig {
         private static final String JSON_KEY_TYPE = "type";
         private static final String JSON_KEY_TITLE = "title";
         private static final String JSON_KEY_ID = "id";
         private static final String JSON_KEY_LAYOUT = "layout";
         private static final String JSON_KEY_VIEWS = "views";
         private static final String JSON_KEY_DEFAULT = "default";
         private static final String JSON_KEY_DISABLED = "disabled";
 
-        private static final int IS_DEFAULT = 1;
-        private static final int IS_DISABLED = 1;
-
         public enum Flags {
             DEFAULT_PANEL,
             DISABLED_PANEL
         }
 
         public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             mType = PanelType.fromId(json.getString(JSON_KEY_TYPE));
             mTitle = json.getString(JSON_KEY_TITLE);
@@ -133,23 +130,21 @@ public final class HomeConfig {
                     mViews.add(viewConfig);
                 }
             } else {
                 mViews = null;
             }
 
             mFlags = EnumSet.noneOf(Flags.class);
 
-            final boolean isDefault = (json.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
-            if (isDefault) {
+            if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
                 mFlags.add(Flags.DEFAULT_PANEL);
             }
 
-            final boolean isDisabled = (json.optInt(JSON_KEY_DISABLED, -1) == IS_DISABLED);
-            if (isDisabled) {
+            if (json.optBoolean(JSON_KEY_DISABLED, false)) {
                 mFlags.add(Flags.DISABLED_PANEL);
             }
 
             validate();
         }
 
         @SuppressWarnings("unchecked")
         public PanelConfig(Parcel in) {
@@ -295,21 +290,21 @@ public final class HomeConfig {
                     final JSONObject jsonViewConfig = viewConfig.toJSON();
                     jsonViews.put(jsonViewConfig);
                 }
 
                 json.put(JSON_KEY_VIEWS, jsonViews);
             }
 
             if (mFlags.contains(Flags.DEFAULT_PANEL)) {
-                json.put(JSON_KEY_DEFAULT, IS_DEFAULT);
+                json.put(JSON_KEY_DEFAULT, true);
             }
 
             if (mFlags.contains(Flags.DISABLED_PANEL)) {
-                json.put(JSON_KEY_DISABLED, IS_DISABLED);
+                json.put(JSON_KEY_DISABLED, true);
             }
 
             return json;
         }
 
         @Override
         public int describeContents() {
             return 0;
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -216,17 +216,22 @@ public class TopSitesPanel extends HomeF
                 return false;
             }
         });
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
+
+        // Discard any additional item clicks on the list
+        // as the panel is getting destroyed (see bug 930160).
+        mList.setOnItemClickListener(null);
         mList = null;
+
         mGrid = null;
         mListAdapter = null;
         mGridAdapter = null;
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
--- a/mobile/android/chrome/content/PluginHelper.js
+++ b/mobile/android/chrome/content/PluginHelper.js
@@ -265,17 +265,17 @@ var PluginHelper = {
         break;
       }
 
       case "PluginNotFound": {
         // On devices where we don't support Flash, there will be a
         // "Learn More..." link in the missing plugin error message.
         let learnMoreLink = doc.getAnonymousElementByAttribute(plugin, "class", "unsupportedLearnMoreLink");
         let learnMoreUrl = Services.urlFormatter.formatURLPref("app.support.baseURL");
-        learnMoreUrl += "why-cant-firefox-mobile-play-flash-on-my-device";
+        learnMoreUrl += "mobile-flash-unsupported";
         learnMoreLink.href = learnMoreUrl;
         overlay.classList.add("visible");
         break;
       }
     }
   },
 
   // Helper to get the binding handler type from a plugin object
--- a/mobile/android/chrome/content/aboutAccounts.js
+++ b/mobile/android/chrome/content/aboutAccounts.js
@@ -88,17 +88,17 @@ let wrapper = {
     sendMessageToJava({
       type: "FxAccount:Verified",
       data: data,
     });
   },
 
   get accountsURI() {
     delete this.accountsURI;
-    return this.accountsURI = Services.urlFormatter.formatURLPref("firefox.accounts.remoteUrl");
+    return this.accountsURI = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.uri");
   },
 
   handleRemoteCommand: function (evt) {
     log.debug('command: ' + evt.detail.command);
     let data = evt.detail.data;
 
     switch (evt.detail.command) {
       case "create":
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -379,18 +379,16 @@
 @BINPATH@/components/messageWakeupService.manifest
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
-@BINPATH@/components/TelemetryPing.js
-@BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
 @BINPATH@/components/Push.js
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -577,17 +577,17 @@ this.FxAccounts.prototype = Object.freez
    *         The promise is rejected if a storage error occurs.
    */
   signOut: function signOut() {
     return internal.signOut();
   },
 
   // Return the URI of the remote UI flows.
   getAccountsURI: function() {
-    let url = Services.urlFormatter.formatURLPref("firefox.accounts.remoteUrl");
+    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.uri");
     if (!/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
       throw new Error("Firefox Accounts server must use HTTPS");
     }
     return url;
   }
 });
 
 /**
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -111,23 +111,23 @@ let MockFxAccounts = function() {
   FxAccounts.apply(this, [mockInternal]);
 };
 MockFxAccounts.prototype = {
   __proto__: FxAccounts.prototype,
 };
 
 add_test(function test_non_https_remote_server_uri() {
   Services.prefs.setCharPref(
-    "firefox.accounts.remoteUrl",
+    "identity.fxaccounts.remote.uri",
     "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
   do_check_throws_message(function () {
     fxAccounts.getAccountsURI();
   }, "Firefox Accounts server must use HTTPS");
 
-  Services.prefs.clearUserPref("firefox.accounts.remoteUrl");
+  Services.prefs.clearUserPref("identity.fxaccounts.remote.uri");
 
   run_next_test();
 });
 
 add_task(function test_get_signed_in_user_initially_unset() {
   // This test, unlike the rest, uses an un-mocked FxAccounts instance.
   // However, we still need to pass an object to the constructor to
   // force it to expose "internal", so we can test the disk storage.
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -61,21 +61,45 @@ WeaveService.prototype = {
   ensureLoaded: function () {
     Components.utils.import("resource://services-sync/main.js");
 
     // Side-effect of accessing the service is that it is instantiated.
     Weave.Service;
   },
 
   get fxAccountsEnabled() {
-    let fxAccountsEnabled = false;
+    // first check if Firefox accounts is available at all.  This is so we can
+    // get this landed without forcing Fxa to be used (and require nightly
+    // testers to manually set this pref)
+    // Once we decide we want Fxa to be available, we just remove this block.
+    let fxAccountsAvailable;
     try {
-      fxAccountsEnabled = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
+      fxAccountsAvailable = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
     } catch (_) {
     }
+    if (!fxAccountsAvailable) {
+      // Currently we don't support toggling this pref after initialization, so
+      // inject the pref value as a regular boolean.
+      delete this.fxAccountsEnabled;
+      this.fxAccountsEnabled = false;
+      return false;
+    }
+    // work out what identity manager to use.  This is stored in a preference;
+    // if the preference exists, we trust it.
+    let fxAccountsEnabled;
+    try {
+      fxAccountsEnabled = Services.prefs.getBoolPref("services.sync.fxaccounts.enabled");
+    } catch (_) {
+      // That pref doesn't exist - so let's assume this is a first-run.
+      // If sync already appears configured, we assume it's for the legacy
+      // provider.
+      let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
+      fxAccountsEnabled = !prefs.prefHasUserValue("username");
+      Services.prefs.setBoolPref("services.sync.fxaccounts.enabled", fxAccountsEnabled);
+    }
     // Currently we don't support toggling this pref after initialization, so
     // inject the pref value as a regular boolean.
     delete this.fxAccountsEnabled;
     return this.fxAccountsEnabled = fxAccountsEnabled;
   },
 
   maybeInitWithFxAccountsAndEnsureLoaded: function() {
     Components.utils.import("resource://services-sync/main.js");
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -60,17 +60,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIMIMEService");
 
 const TEST_TARGET_FILE_NAME = "test-download.txt";
 const TEST_STORE_FILE_NAME = "test-downloads.json";
 
 const TEST_REFERRER_URL = "http://www.example.com/referrer.html";
 
 const TEST_DATA_SHORT = "This test string is downloaded.";
-// Generate using gzipCompressString in TelemetryPing.js.
+// Generate using gzipCompressString in TelemetryPing.jsm.
 const TEST_DATA_SHORT_GZIP_ENCODED_FIRST = [
  31,139,8,0,0,0,0,0,0,3,11,201,200,44,86,40,73,45,46,81,40,46,41,202,204
 ];
 const TEST_DATA_SHORT_GZIP_ENCODED_SECOND = [
   75,87,0,114,83,242,203,243,114,242,19,83,82,83,244,0,151,222,109,43,31,0,0,0
 ];
 const TEST_DATA_SHORT_GZIP_ENCODED =
   TEST_DATA_SHORT_GZIP_ENCODED_FIRST.concat(TEST_DATA_SHORT_GZIP_ENCODED_SECOND);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4861,16 +4861,44 @@
   },
   "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE_REMOTE_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took to display a selected source to the user."
   },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETAB_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETAB_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETHREAD_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETHREAD_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
+  },
   "WEBRTC_ICE_SUCCESS_RATE": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "The number of failed ICE Connections (0) vs. number of successful ICE connections (1)."
   },
   "WEBRTC_CALL_DURATION": {
     "expires_in_version": "never",
     "kind": "exponential",
deleted file mode 100644
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* 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";
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm")
-Components.utils.import("resource://gre/modules/Deprecated.jsm");
-
-let JSM = {};
-Components.utils.import("resource://gre/modules/TelemetryPing.jsm", JSM);
-
-function TelemetryPing() {
-  Deprecated.warning("nsITelemetryPing is deprecated. Please use TelemetryPing.jsm instead",
-    "https://bugzilla.mozilla.org/show_bug.cgi?id=913070");
-}
-
-TelemetryPing.prototype = Object.create(JSM.TelemetryPing);
-TelemetryPing.prototype.classID = Components.ID("{55d6a5fa-130e-4ee6-a158-0133af3b86ba}");
-TelemetryPing.prototype.QueryInterface = XPCOMUtils.generateQI([Components.interfaces.nsITelemetryPing]);
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelemetryPing]);
deleted file mode 100644
--- a/toolkit/components/telemetry/TelemetryPing.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {55d6a5fa-130e-4ee6-a158-0133af3b86ba} TelemetryPing.js
-contract @mozilla.org/base/telemetry-ping;1 {55d6a5fa-130e-4ee6-a158-0133af3b86ba}
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -3,34 +3,31 @@
 # 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/.
 
 TEST_DIRS += ['tests']
 
 XPIDL_SOURCES += [
     'nsITelemetry.idl',
-    'nsITelemetryPing.idl',
 ]
 
 XPIDL_MODULE = 'telemetry'
 
 EXPORTS.mozilla += [
     'ProcessedStack.h',
     'Telemetry.h',
     'ThreadHangStats.h',
 ]
 
 SOURCES += [
     'Telemetry.cpp',
 ]
 
 EXTRA_COMPONENTS += [
-    'TelemetryPing.js',
-    'TelemetryPing.manifest',
     'TelemetryStartup.js',
     'TelemetryStartup.manifest'
 ]
 
 EXTRA_JS_MODULES += [
     'TelemetryFile.jsm',
     'TelemetryStopwatch.jsm',
     'ThirdPartyCookieProbe.jsm',
deleted file mode 100644
--- a/toolkit/components/telemetry/nsITelemetryPing.idl
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* 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 "nsIObserver.idl"
-
-interface nsIFile;
-
-[scriptable, uuid(32fbb784-a20c-49aa-9db9-9a0da1c2f7d8)]
-interface nsITelemetryPing : nsIObserver {
-  /**
-   * Return the current telemetry payload.
-   */
-  jsval getPayload();
-
-  /**
-   * Save histograms to a file.
-   *
-   * @param aFile - File to load from.
-   * @param aSync - Use sync writes.
-   */
-  void saveHistograms(in nsIFile aFile, in boolean aSync);
-
-  /**
-   * Collect and store information about startup.
-   */
-  void gatherStartup();
-
-  /**
-   * Notify observers when loads and saves finish. Used only for testing.
-   */
-  void enableLoadSaveNotifications();
-
-  /**
-   * Cache the profile directory for later use.
-   */
-  void cacheProfileDirectory();
-
-  /**
-   * Inform the ping which AddOns are installed.
-   *
-   * @param aAddOns - The AddOns.
-   */
-  void setAddOns(in AString aAddOns);
-
-  /**
-   * Send a ping to a test server. Used only for testing.
-   *
-   * @param aServer - The server.
-   */
-  void testPing(in AString aServer);
-
-  /**
-   * Load histograms from a file.
-   *
-   * @param aFile - File to load from.
-   * @param aSync - Use sync reads.
-   */
-  void testLoadHistograms(in nsIFile aFile, in boolean aSync);
-
-  /**
-   * Return the path component of the current submission URL.
-   */
-  AString submissionPath();
-};
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -463,17 +463,17 @@
                       class="popup-notification-menubutton"
                       type="menu-button"
                       xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
             <xul:menupopup anonid="menupopup"
                            xbl:inherits="oncommand=menucommand">
               <children/>
               <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
                             label="&closeNotificationItem.label;"
-                            xbl:inherits="oncommand=closeitemcommand"/>
+                            xbl:inherits="oncommand=closeitemcommand,hidden=hidenotnow"/>
             </xul:menupopup>
           </xul:button>
         </xul:hbox>
       </xul:vbox>
       <xul:vbox pack="start">
         <xul:toolbarbutton anonid="closebutton"
                            class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                            xbl:inherits="oncommand=closebuttoncommand"
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -11,28 +11,32 @@ let { Promise: promise } = Components.ut
 let { Services } = Components.utils.import("resource://gre/modules/Services.jsm", {});
 
 /**
  * Turn the error |aError| into a string, without fail.
  */
 this.safeErrorString = function safeErrorString(aError) {
   try {
     let errorString = aError.toString();
-    if (typeof errorString === "string") {
+    if (typeof errorString == "string") {
       // Attempt to attach a stack to |errorString|. If it throws an error, or
       // isn't a string, don't use it.
       try {
         if (aError.stack) {
           let stack = aError.stack.toString();
-          if (typeof stack === "string") {
+          if (typeof stack == "string") {
             errorString += "\nStack: " + stack;
           }
         }
       } catch (ee) { }
 
+      if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
+        errorString += ", line: " + aError.lineNumber + ", column: " + aError.columnNumber;
+      }
+
       return errorString;
     }
   } catch (ee) { }
 
   return "<failed trying to find error description>";
 }
 
 /**
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -229,19 +229,22 @@ const UnsolicitedPauses = {
  * Creates a client for the remote debugging protocol server. This client
  * provides the means to communicate with the server and exchange the messages
  * required by the protocol in a traditional JavaScript API.
  */
 this.DebuggerClient = function (aTransport)
 {
   this._transport = aTransport;
   this._transport.hooks = this;
-  this._threadClients = {};
-  this._tabClients = {};
-  this._consoleClients = {};
+
+  // Map actor ID to client instance for each actor type.
+  this._threadClients = new Map;
+  this._tabClients = new Map;
+  this._tracerClients = new Map;
+  this._consoleClients = new Map;
 
   this._pendingRequests = [];
   this._activeRequests = new Map;
   this._eventsEnabled = true;
 
   this.compat = new ProtocolCompatibility(this, [
     new SourcesShim(),
   ]);
@@ -276,17 +279,17 @@ this.DebuggerClient = function (aTranspo
  * @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 (aPacketSkeleton,
                                      { telemetry, before, after }) {
-  return function (...args) {
+  return DevToolsUtils.makeInfallible(function (...args) {
     let histogram, startTime;
     if (telemetry) {
       let transportType = this._transport.onOutputStreamReady === undefined
         ? "LOCAL_"
         : "REMOTE_";
       let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
         + transportType + telemetry + "_MS";
       histogram = Services.telemetry.getHistogramById(histogramId);
@@ -306,41 +309,37 @@ DebuggerClient.requester = function (aPa
         outgoingPacket[k] = aPacketSkeleton[k];
       }
     }
 
     if (before) {
       outgoingPacket = before.call(this, outgoingPacket);
     }
 
-    this.request(outgoingPacket, function (aResponse) {
+    this.request(outgoingPacket, DevToolsUtils.makeInfallible(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) {
-        try {
-          thisCallback(aResponse);
-        } catch (e) {
-          DevToolsUtils.reportException("DebuggerClient.requester callback", e);
-        }
+        thisCallback(aResponse);
       }
 
       if (histogram) {
         histogram.add(+new Date - startTime);
       }
-    }.bind(this));
+    }.bind(this), "DebuggerClient.requester request callback"));
 
-  };
+  }, "DebuggerClient.requester");
 };
 
 function args(aPos) {
   return new DebuggerClient.Argument(aPos);
 }
 
 DebuggerClient.Argument = function (aPosition) {
   this.position = aPosition;
@@ -385,53 +384,45 @@ DebuggerClient.prototype = {
     this._eventsEnabled = false;
 
     if (aOnClosed) {
       this.addOneTimeListener('closed', function (aEvent) {
         aOnClosed();
       });
     }
 
-    // In this function, we're using the hoisting behavior of nested
-    // function definitions to write the code in the order it will actually
-    // execute. So converting to arrow functions to get rid of 'self' would
-    // be unhelpful here.
-    let self = this;
+    const detachClients = (clientMap, next) => {
+      const clients = clientMap.values();
+      const total = clientMap.size;
+      let numFinished = 0;
 
-    let continuation = function () {
-      self._consoleClients = {};
-      detachThread();
-    }
-
-    for each (let client in this._consoleClients) {
-      continuation = client.close.bind(client, continuation);
-    }
-
-    continuation();
+      if (total == 0) {
+        next();
+        return;
+      }
 
-    function detachThread() {
-      if (self.activeThread) {
-        self.activeThread.detach(detachTab);
-      } else {
-        detachTab();
+      for (let client of clients) {
+        let method = client instanceof WebConsoleClient ? "close" : "detach";
+        client[method](() => {
+          if (++numFinished === total) {
+            clientMap.clear();
+            next();
+          }
+        });
       }
-    }
+    };
 
-    function detachTab() {
-      if (self.activeTab) {
-        self.activeTab.detach(closeTransport);
-      } else {
-        closeTransport();
-      }
-    }
-
-    function closeTransport() {
-      self._transport.close();
-      self._transport = null;
-    }
+    detachClients(this._consoleClients, () => {
+      detachClients(this._threadClients, () => {
+        detachClients(this._tabClients, () => {
+          this._transport.close();
+          this._transport = null;
+        });
+      });
+    });
   },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listTabs()'.
    */
   listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
 
@@ -446,26 +437,35 @@ DebuggerClient.prototype = {
    *
    * @param string aTabActor
    *        The actor ID for the tab to attach.
    * @param function aOnResponse
    *        Called with the response packet and a TabClient
    *        (which will be undefined on error).
    */
   attachTab: function (aTabActor, aOnResponse) {
+    if (this._tabClients.has(aTabActor)) {
+      let cachedTab = this._tabClients.get(aTabActor);
+      let cachedResponse = {
+        cacheEnabled: cachedTab.cacheEnabled,
+        javascriptEnabled: cachedTab.javascriptEnabled
+      };
+      setTimeout(() => aOnResponse(cachedResponse, cachedTab), 0);
+      return;
+    }
+
     let packet = {
       to: aTabActor,
       type: "attach"
     };
     this.request(packet, (aResponse) => {
       let tabClient;
       if (!aResponse.error) {
-        tabClient = new TabClient(this, aTabActor);
-        this._tabClients[aTabActor] = tabClient;
-        this.activeTab = tabClient;
+        tabClient = new TabClient(this, aResponse);
+        this._tabClients.set(aTabActor, tabClient);
       }
       aOnResponse(aResponse, tabClient);
     });
   },
 
   /**
    * Attach to a Web Console actor.
    *
@@ -474,117 +474,98 @@ DebuggerClient.prototype = {
    * @param array aListeners
    *        The console listeners you want to start.
    * @param function aOnResponse
    *        Called with the response packet and a WebConsoleClient
    *        instance (which will be undefined on error).
    */
   attachConsole:
   function (aConsoleActor, aListeners, aOnResponse) {
+    if (this._consoleClients.has(aConsoleActor)) {
+      setTimeout(() => aOnResponse({}, this._consoleClients.get(aConsoleActor)), 0);
+      return;
+    }
+
     let packet = {
       to: aConsoleActor,
       type: "startListeners",
       listeners: aListeners,
     };
 
     this.request(packet, (aResponse) => {
       let consoleClient;
       if (!aResponse.error) {
         consoleClient = new WebConsoleClient(this, aConsoleActor);
-        this._consoleClients[aConsoleActor] = consoleClient;
+        this._consoleClients.set(aConsoleActor, consoleClient);
       }
       aOnResponse(aResponse, consoleClient);
     });
   },
 
   /**
-   * Attach to a thread actor.
+   * Attach to a global-scoped thread actor for chrome debugging.
    *
    * @param string aThreadActor
    *        The actor ID for the thread to attach.
    * @param function aOnResponse
    *        Called with the response packet and a ThreadClient
    *        (which will be undefined on error).
    * @param object aOptions
    *        Configuration options.
    *        - useSourceMaps: whether to use source maps or not.
    */
   attachThread: function (aThreadActor, aOnResponse, aOptions={}) {
-    let packet = {
+    if (this._threadClients.has(aThreadActor)) {
+      setTimeout(() => aOnResponse({}, this._threadClients.get(aThreadActor)), 0);
+      return;
+    }
+
+   let packet = {
       to: aThreadActor,
       type: "attach",
       options: aOptions
     };
     this.request(packet, (aResponse) => {
       if (!aResponse.error) {
         var threadClient = new ThreadClient(this, aThreadActor);
-        this._threadClients[aThreadActor] = threadClient;
-        this.activeThread = threadClient;
+        this._threadClients.set(aThreadActor, threadClient);
       }
       aOnResponse(aResponse, threadClient);
     });
   },
 
   /**
    * Attach to a trace actor.
    *
    * @param string aTraceActor
    *        The actor ID for the tracer to attach.
    * @param function aOnResponse
    *        Called with the response packet and a TraceClient
    *        (which will be undefined on error).
    */
   attachTracer: function (aTraceActor, aOnResponse) {
+    if (this._tracerClients.has(aTraceActor)) {
+      setTimeout(() => aOnResponse({}, this._tracerClients.get(aTraceActor)), 0);
+      return;
+    }
+
     let packet = {
       to: aTraceActor,
       type: "attach"
     };
     this.request(packet, (aResponse) => {
       if (!aResponse.error) {
-        let traceClient = new TraceClient(this, aTraceActor);
-        aOnResponse(aResponse, traceClient);
+        var traceClient = new TraceClient(this, aTraceActor);
+        this._tracerClients.set(aTraceActor, traceClient);
       }
+      aOnResponse(aResponse, traceClient);
     });
   },
 
   /**
-   * Reconfigure a thread actor.
-   *
-   * @param object aOptions
-   *        A dictionary object of the new options to use in the thread actor.
-   * @param function aOnResponse
-   *        Called with the response packet.
-   */
-  reconfigureThread: function (aOptions, aOnResponse) {
-    let packet = {
-      to: this.activeThread._actor,
-      type: "reconfigure",
-      options: aOptions
-    };
-    this.request(packet, aOnResponse);
-  },
-
-  /**
-   * Reconfigure a tab actor.
-   *
-   * @param object aOptions
-   *        A dictionary object of the new options to use in the tab actor.
-   * @param function aOnResponse
-   *        Called with the response packet.
-   */
-  reconfigureTab: function (aOptions, aOnResponse) {
-    let packet = {
-      to: this.activeTab._actor,
-      type: "reconfigure",
-      options: aOptions
-    };
-    this.request(packet, aOnResponse);
-  },
-
-  /**
    * 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.
    */
@@ -692,27 +673,28 @@ DebuggerClient.prototype = {
           !(aPacket.type == ThreadStateTypes.paused &&
             aPacket.why.type in UnsolicitedPauses)) {
         onResponse = this._activeRequests.get(aPacket.from);
         this._activeRequests.delete(aPacket.from);
       }
 
       // Packets that indicate thread state changes get special treatment.
       if (aPacket.type in ThreadStateTypes &&
-          aPacket.from in this._threadClients) {
-        this._threadClients[aPacket.from]._onThreadState(aPacket);
+          this._threadClients.has(aPacket.from)) {
+        this._threadClients.get(aPacket.from)._onThreadState(aPacket);
       }
       // On navigation the server resumes, so the client must resume as well.
       // We achieve that by generating a fake resumption packet that triggers
       // the client's thread state change listeners.
-      if (this.activeThread &&
-          aPacket.type == UnsolicitedNotifications.tabNavigated &&
-          aPacket.from in this._tabClients) {
-        let resumption = { from: this.activeThread._actor, type: "resumed" };
-        this.activeThread._onThreadState(resumption);
+      if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
+          this._tabClients.has(aPacket.from) &&
+          this._tabClients.get(aPacket.from).thread) {
+        let thread = this._tabClients.get(aPacket.from).thread;
+        let resumption = { from: thread._actor, type: "resumed" };
+        thread._onThreadState(resumption);
       }
       // Only try to notify listeners on events, not responses to requests
       // that lack a packet type.
       if (aPacket.type) {
         this.notify(aPacket.type, aPacket);
       }
 
       if (onResponse) {
@@ -954,43 +936,80 @@ SSProto.translatePacket = function (aPac
 
 /**
  * Creates a tab client for the remote debugging protocol server. This client
  * is a front to the tab actor created in the server side, hiding the protocol
  * details in a traditional JavaScript API.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
- * @param aActor string
- *        The actor ID for this tab.
+ * @param aForm object
+ *        The protocol form for this tab.
  */
-function TabClient(aClient, aActor) {
-  this._client = aClient;
-  this._actor = aActor;
-  this.request = this._client.request;
+function TabClient(aClient, aForm) {
+  this.client = aClient;
+  this._actor = aForm.from;
+  this._threadActor = aForm.threadActor;
+  this.javascriptEnabled = aForm.javascriptEnabled;
+  this.cacheEnabled = aForm.cacheEnabled;
+  this.thread = null;
+  this.request = this.client.request;
 }
 
 TabClient.prototype = {
   get actor() { return this._actor },
-  get _transport() { return this._client._transport; },
+  get _transport() { return this.client._transport; },
+
+  /**
+   * Attach to a thread actor.
+   *
+   * @param object aOptions
+   *        Configuration options.
+   *        - useSourceMaps: whether to use source maps or not.
+   * @param function aOnResponse
+   *        Called with the response packet and a ThreadClient
+   *        (which will be undefined on error).
+   */
+  attachThread: function(aOptions={}, aOnResponse) {
+    if (this.thread) {
+      setTimeout(() => aOnResponse({}, this.thread), 0);
+      return;
+    }
+
+    let packet = {
+      to: this._threadActor,
+      type: "attach",
+      options: aOptions
+    };
+    this.request(packet, (aResponse) => {
+      if (!aResponse.error) {
+        this.thread = new ThreadClient(this, this._threadActor);
+        this.client._threadClients.set(this._threadActor, this.thread);
+      }
+      aOnResponse(aResponse, this.thread);
+    });
+  },
 
   /**
    * Detach the client from the tab actor.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   detach: DebuggerClient.requester({
     type: "detach"
   }, {
+    before: function (aPacket) {
+      if (this.thread) {
+        this.thread.detach();
+      }
+      return aPacket;
+    },
     after: function (aResponse) {
-      if (this.activeTab === this._client._tabClients[this.actor]) {
-        this.activeTab = undefined;
-      }
-      delete this._client._tabClients[this.actor];
+      this.client._tabClients.delete(this.actor);
       return aResponse;
     },
     telemetry: "TABDETACH"
   }),
 
   /**
    * Reload the page in this tab.
    */
@@ -1007,16 +1026,31 @@ TabClient.prototype = {
    *        The URL to navigate to.
    */
   navigateTo: DebuggerClient.requester({
     type: "navigateTo",
     url: args(0)
   }, {
     telemetry: "NAVIGATETO"
   }),
+
+  /**
+   * Reconfigure the tab actor.
+   *
+   * @param object aOptions
+   *        A dictionary object of the new options to use in the tab actor.
+   * @param function aOnResponse
+   *        Called with the response packet.
+   */
+  reconfigure: DebuggerClient.requester({
+    type: "reconfigure",
+    options: args(0)
+  }, {
+    telemetry: "RECONFIGURETAB"
+  }),
 };
 
 eventSource(TabClient.prototype);
 
 /**
  * A RootClient object represents a root actor on the server. Each
  * DebuggerClient keeps a RootClient instance representing the root actor
  * for the initial connection; DebuggerClient's 'listTabs' and
@@ -1072,49 +1106,51 @@ RootClient.prototype = {
   get request()    { return this._client.request;    }
 };
 
 /**
  * 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.
  *
- * @param aClient DebuggerClient
- *        The debugger client parent.
+ * @param aClient DebuggerClient|TabClient
+ *        The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
+ *        for chrome debuggers).
  * @param aActor string
  *        The actor ID for this thread.
  */
 function ThreadClient(aClient, aActor) {
-  this._client = aClient;
+  this._parent = aClient;
+  this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
   this._actor = aActor;
   this._frameCache = [];
   this._scriptCache = {};
   this._pauseGrips = {};
   this._threadGrips = {};
-  this.request = this._client.request;
+  this.request = this.client.request;
 }
 
 ThreadClient.prototype = {
   _state: "paused",
   get state() { return this._state; },
   get paused() { return this._state === "paused"; },
 
   _pauseOnExceptions: false,
   _ignoreCaughtExceptions: false,
   _pauseOnDOMEvents: null,
 
   _actor: null,
   get actor() { return this._actor; },
 
-  get compat() { return this._client.compat; },
-  get _transport() { return this._client._transport; },
+  get compat() { return this.client.compat; },
+  get _transport() { return this.client._transport; },
 
   _assertPaused: function (aCommand) {
     if (!this.paused) {
-      throw Error(aCommand + " command sent while not paused.");
+      throw Error(aCommand + " command sent while not paused. Currently " + this._state);
     }
   },
 
   /**
    * Resume a paused thread. If the optional aLimit parameter is present, then
    * the thread will also pause when that limit is reached.
    *
    * @param [optional] object aLimit
@@ -1152,16 +1188,31 @@ ThreadClient.prototype = {
         this._state = "paused";
       }
       return aResponse;
     },
     telemetry: "RESUME"
   }),
 
   /**
+   * Reconfigure the thread actor.
+   *
+   * @param object aOptions
+   *        A dictionary object of the new options to use in the thread actor.
+   * @param function aOnResponse
+   *        Called with the response packet.
+   */
+  reconfigure: DebuggerClient.requester({
+    type: "reconfigure",
+    options: args(0)
+  }, {
+    telemetry: "RECONFIGURETHREAD"
+  }),
+
+  /**
    * Resume a paused thread.
    */
   resume: function (aOnResponse) {
     this._doResume(null, aOnResponse);
   },
 
   /**
    * Step over a function call.
@@ -1217,17 +1268,17 @@ ThreadClient.prototype = {
                                aIgnoreCaughtExceptions,
                                aOnResponse) {
     this._pauseOnExceptions = aPauseOnExceptions;
     this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
 
     // If the debuggee is paused, we have to send the flag via a reconfigure
     // request.
     if (this.paused) {
-      this._client.reconfigureThread({
+      this.reconfigure({
         pauseOnExceptions: aPauseOnExceptions,
         ignoreCaughtExceptions: aIgnoreCaughtExceptions
       }, aOnResponse);
       return;
     }
     // Otherwise send the flag using a standard resume request.
     this.interrupt(aResponse => {
       if (aResponse.error) {
@@ -1251,22 +1302,26 @@ ThreadClient.prototype = {
    * @param function onResponse
    *        Called with the response packet in a future turn of the event loop.
    */
   pauseOnDOMEvents: function (events, onResponse) {
     this._pauseOnDOMEvents = events;
     // If the debuggee is paused, the value of the array will be communicated in
     // the next resumption. Otherwise we have to force a pause in order to send
     // the array.
-    if (this.paused)
-      return void setTimeout(onResponse, 0);
+    if (this.paused) {
+      setTimeout(() => onResponse({}), 0);
+      return;
+    }
     this.interrupt(response => {
       // Can't continue if pausing failed.
-      if (response.error)
-        return void onResponse(response);
+      if (response.error) {
+        onResponse(response);
+        return;
+      }
       this.resume(onResponse);
     });
   },
 
   /**
    * Send a clientEvaluate packet to the debuggee. Response
    * will be a resume packet.
    *
@@ -1305,20 +1360,18 @@ ThreadClient.prototype = {
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   detach: DebuggerClient.requester({
     type: "detach"
   }, {
     after: function (aResponse) {
-      if (this.activeThread === this._client._threadClients[this.actor]) {
-        this.activeThread = null;
-      }
-      delete this._client._threadClients[this.actor];
+      this.client._threadClients.delete(this.actor);
+      this._parent.thread = null;
       return aResponse;
     },
     telemetry: "THREADDETACH"
   }),
 
   /**
    * Request to set a breakpoint in the specified location.
    *
@@ -1327,21 +1380,21 @@ ThreadClient.prototype = {
    * @param function aOnResponse
    *        Called with the thread's response.
    */
   setBreakpoint: function (aLocation, aOnResponse) {
     // A helper function that sets the breakpoint.
     let doSetBreakpoint = function (aCallback) {
       let packet = { to: this._actor, type: "setBreakpoint",
                      location: aLocation };
-      this._client.request(packet, function (aResponse) {
+      this.client.request(packet, function (aResponse) {
         // Ignoring errors, since the user may be setting a breakpoint in a
         // dead script that will reappear on a page reload.
         if (aOnResponse) {
-          let bpClient = new BreakpointClient(this._client, aResponse.actor,
+          let bpClient = new BreakpointClient(this.client, aResponse.actor,
                                               aLocation);
           if (aCallback) {
             aCallback(aOnResponse(aResponse, bpClient));
           } else {
             aOnResponse(aResponse, bpClient);
           }
         }
       }.bind(this));
@@ -1565,17 +1618,17 @@ ThreadClient.prototype = {
    * @param aGrip object
    *        A pause-lifetime object grip returned by the protocol.
    */
   pauseGrip: function (aGrip) {
     if (aGrip.actor in this._pauseGrips) {
       return this._pauseGrips[aGrip.actor];
     }
 
-    let client = new ObjectClient(this._client, aGrip);
+    let client = new ObjectClient(this.client, aGrip);
     this._pauseGrips[aGrip.actor] = client;
     return client;
   },
 
   /**
    * Get or create a long string client, checking the grip client cache if it
    * already exists.
    *
@@ -1585,17 +1638,17 @@ ThreadClient.prototype = {
    *        The property name of the grip client cache to check for existing
    *        clients in.
    */
   _longString: function (aGrip, aGripCacheName) {
     if (aGrip.actor in this[aGripCacheName]) {
       return this[aGripCacheName][aGrip.actor];
     }
 
-    let client = new LongStringClient(this._client, aGrip);
+    let client = new LongStringClient(this.client, aGrip);
     this[aGripCacheName][aGrip.actor] = client;
     return client;
   },
 
   /**
    * Return an instance of LongStringClient for the given long string grip that
    * is scoped to the current pause.
    *
@@ -1650,36 +1703,35 @@ ThreadClient.prototype = {
    * Handle thread state change by doing necessary cleanup and notifying all
    * registered listeners.
    */
   _onThreadState: function (aPacket) {
     this._state = ThreadStateTypes[aPacket.type];
     this._clearFrames();
     this._clearPauseGrips();
     aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
-    this._client._eventsEnabled && this.notify(aPacket.type, aPacket);
+    this.client._eventsEnabled && this.notify(aPacket.type, aPacket);
   },
 
   /**
    * Return an EnvironmentClient instance for the given environment actor form.
    */
   environment: function (aForm) {
-    return new EnvironmentClient(this._client, aForm);
+    return new EnvironmentClient(this.client, aForm);
   },
 
   /**
    * Return an instance of SourceClient for the given source actor form.
    */
   source: function (aForm) {
     if (aForm.actor in this._threadGrips) {
       return this._threadGrips[aForm.actor];
     }
 
-    return this._threadGrips[aForm.actor] = new SourceClient(this._client,
-                                                             aForm);
+    return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
   },
 
   /**
    * Request the prototype and own properties of mutlipleObjects.
    *
    * @param aOnResponse function
    *        Called with the request's response.
    * @param actors [string]
@@ -1719,18 +1771,25 @@ TraceClient.prototype = {
   get actor()   { return this._actor; },
   get tracing() { return this._activeTraces.size > 0; },
 
   get _transport() { return this._client._transport; },
 
   /**
    * Detach from the trace actor.
    */
-  detach: DebuggerClient.requester({ type: "detach" },
-                                   { telemetry: "TRACERDETACH" }),
+  detach: DebuggerClient.requester({
+    type: "detach"
+  }, {
+    after: function (aResponse) {
+      this._client._tracerClients.delete(this.actor);
+      return aResponse;
+    },
+    telemetry: "TRACERDETACH"
+  }),
 
   /**
    * Start a new trace.
    *
    * @param aTrace [string]
    *        An array of trace types to be recorded by the new trace.
    *
    * @param aName string
@@ -1959,31 +2018,31 @@ LongStringClient.prototype = {
   }, {
     telemetry: "SUBSTRING"
   }),
 };
 
 /**
  * A SourceClient provides a way to access the source text of a script.
  *
- * @param aClient DebuggerClient
- *        The debugger client parent.
+ * @param aClient ThreadClient
+ *        The thread client parent.
  * @param aForm Object
  *        The form sent across the remote debugging protocol.
  */
 function SourceClient(aClient, aForm) {
   this._form = aForm;
   this._isBlackBoxed = aForm.isBlackBoxed;
   this._isPrettyPrinted = aForm.isPrettyPrinted;
-  this._client = aClient;
+  this._activeThread = aClient;
+  this._client = aClient.client;
 }
 
 SourceClient.prototype = {
   get _transport() this._client._transport,
-  get _activeThread() this._client.activeThread,
   get isBlackBoxed() this._isBlackBoxed,
   get isPrettyPrinted() this._isPrettyPrinted,
   get actor() this._form.actor,
   get request() this._client.request,
   get url() this._form.url,
 
   /**
    * Black box this SourceClient's source.
@@ -2084,18 +2143,17 @@ SourceClient.prototype = {
     }
 
     if (typeof aResponse.source === "string") {
       aCallback(aResponse);
       return;
     }
 
     let { contentType, source } = aResponse;
-    let longString = this._client.activeThread.threadLongString(
-      source);
+    let longString = this._activeThread.threadLongString(source);
     longString.substring(0, longString.length, function (aResponse) {
       if (aResponse.error) {
         aCallback(aResponse);
         return;
       }
 
       aCallback({
         source: aResponse.substring,
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -650,18 +650,16 @@ ThreadActor.prototype = {
   },
 
   disconnect: function () {
     dumpn("in ThreadActor.prototype.disconnect");
     if (this._state == "paused") {
       this.onResume();
     }
 
-    this._state = "exited";
-
     this.clearDebuggees();
     this.conn.removeActorPool(this._threadLifetimePool);
     this._threadLifetimePool = null;
 
     if (this._prettyPrintWorker) {
       this._prettyPrintWorker.removeEventListener(
         "error", this._onPrettyPrintError, false);
       this._prettyPrintWorker.removeEventListener(
@@ -677,26 +675,28 @@ ThreadActor.prototype = {
     this.dbg = null;
   },
 
   /**
    * Disconnect the debugger and put the actor in the exited state.
    */
   exit: function () {
     this.disconnect();
+    this._state = "exited";
   },
 
   // Request handlers
   onAttach: function (aRequest) {
     if (this.state === "exited") {
       return { type: "exited" };
     }
 
     if (this.state !== "detached") {
-      return { error: "wrongState" };
+      return { error: "wrongState",
+               message: "Current state is " + this.state };
     }
 
     this._state = "attached";
 
     update(this._options, aRequest.options || {});
 
     // Initialize an event loop stack. This can't be done in the constructor,
     // because this.conn is not yet initialized by the actor pool at that time.
@@ -736,16 +736,18 @@ ThreadActor.prototype = {
     } catch (e) {
       reportError(e);
       return { error: "notAttached", message: e.toString() };
     }
   },
 
   onDetach: function (aRequest) {
     this.disconnect();
+    this._state = "detached";
+
     dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet");
     return {
       type: "detached"
     };
   },
 
   onReconfigure: function (aRequest) {
     if (this.state == "exited") {
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -672,17 +672,17 @@ WebappsActor.prototype = {
       req.open("GET", iconURL, false);
       req.responseType = "blob";
 
       try {
         req.send(null);
       } catch(e) {
         deferred.resolve({
           error: "noIcon",
-          message: "The icon file '" + iconURL + "' doesn't exists"
+          message: "The icon file '" + iconURL + "' doesn't exist"
         });
         return;
       }
 
       // Convert the blog to a base64 encoded data URI
       let reader = Cc["@mozilla.org/files/filereader;1"]
                      .createInstance(Ci.nsIDOMFileReader);
       reader.onload = function () {
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -885,36 +885,34 @@ BrowserTabActor.prototype = {
 
   /**
    * Handle location changes, by clearing the previous debuggees and enabling
    * debugging, which may have been disabled temporarily by the
    * DebuggerProgressListener.
    */
   onWindowCreated:
   makeInfallible(function BTA_onWindowCreated(evt) {
-    if (evt.target === this.browser.contentDocument) {
-      // pageshow events for non-persisted pages have already been handled by a
-      // prior DOMWindowCreated event.
-      if (evt.type == "pageshow" && !evt.persisted) {
-        return;
-      }
-      if (this._attached) {
-        this.threadActor.clearDebuggees();
-        if (this.threadActor.dbg) {
-          this.threadActor.dbg.enabled = true;
-          this.threadActor.maybePauseOnExceptions();
-        }
+    // pageshow events for non-persisted pages have already been handled by a
+    // prior DOMWindowCreated event.
+    if (!this._attached || (evt.type == "pageshow" && !evt.persisted)) {
+      return;
+    }
+    if (evt.target === this.browser.contentDocument ) {
+      this.threadActor.clearDebuggees();
+      if (this.threadActor.dbg) {
+        this.threadActor.dbg.enabled = true;
+        this.threadActor.global = evt.target.defaultView.wrappedJSObject;
+        this.threadActor.maybePauseOnExceptions();
       }
     }
 
-    if (this._attached) {
-      this.threadActor.global = evt.target.defaultView.wrappedJSObject;
-      if (this.threadActor.attached) {
-        this.threadActor.findGlobals();
-      }
+    // Refresh the debuggee list when a new window object appears (top window or
+    // iframe).
+    if (this.threadActor.attached) {
+      this.threadActor.findGlobals();
     }
   }, "BrowserTabActor.prototype.onWindowCreated"),
 
   /**
    * Tells if the window.console object is native or overwritten by script in
    * the page.
    *
    * @param nsIDOMWindow aWindow
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -371,16 +371,17 @@ var DebuggerServer = {
       this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
       this.addActors("resource://gre/modules/devtools/server/actors/script.js");
       this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
       this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
       this.registerModule("devtools/server/actors/inspector");
       this.registerModule("devtools/server/actors/webgl");
       this.registerModule("devtools/server/actors/stylesheets");
       this.registerModule("devtools/server/actors/styleeditor");
+      this.registerModule("devtools/server/actors/tracer");
     }
     if (!("ContentAppActor" in DebuggerServer)) {
       this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
     }
   },
 
   /**
    * Listens on the given port or socket file for remote debugger connections.
--- a/toolkit/devtools/server/tests/unit/head_dbg.js
+++ b/toolkit/devtools/server/tests/unit/head_dbg.js
@@ -149,19 +149,20 @@ function attachTestTab(aClient, aTitle, 
 }
 
 // Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
 // that tab's thread. Pass |aCallback| the thread attach response packet, a
 // TabClient referring to the tab, and a ThreadClient referring to the
 // thread.
 function attachTestThread(aClient, aTitle, aCallback) {
   attachTestTab(aClient, aTitle, function (aResponse, aTabClient) {
-    aClient.attachThread(aResponse.threadActor, function (aResponse, aThreadClient) {
+    function onAttach(aResponse, aThreadClient) {
       aCallback(aResponse, aTabClient, aThreadClient);
-    }, { useSourceMaps: true });
+    }
+    aTabClient.attachThread({ useSourceMaps: true }, onAttach);
   });
 }
 
 // Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
 // thread, and then resume it. Pass |aCallback| the thread's response to
 // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
 // thread.
 function attachTestTabAndResume(aClient, aTitle, aCallback) {
--- a/toolkit/devtools/server/tests/unit/test_attach.js
+++ b/toolkit/devtools/server/tests/unit/test_attach.js
@@ -9,25 +9,25 @@ function run_test()
   initTestDebuggerServer();
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     attachTestTab(gClient, "test-1", function(aReply, aTabClient) {
-      test_attach(aReply.threadActor);
+      test_attach(aTabClient);
     });
   });
   do_test_pending();
 }
 
-function test_attach(aThreadActorID)
+function test_attach(aTabClient)
 {
-  gClient.attachThread(aThreadActorID, function(aResponse, aThreadClient) {
+  aTabClient.attachThread({}, function(aResponse, aThreadClient) {
     do_check_eq(aThreadClient.state, "paused");
     aThreadClient.resume(cleanup);
   });
 }
 
 function cleanup()
 {
   gClient.addListener("closed", function(aEvent) {
--- a/toolkit/devtools/server/tests/unit/test_breakpoint-18.js
+++ b/toolkit/devtools/server/tests/unit/test_breakpoint-18.js
@@ -38,22 +38,22 @@ function setUpCode() {
     },
     gDebuggee,
     "1.8",
     URL
   );
 }
 
 function setBreakpoint() {
+  gClient.addOneTimeListener("resumed", runCode);
   gThreadClient.setBreakpoint({
     url: URL,
     line: 1
   }, ({ error }) => {
     do_check_true(!error);
-    gThreadClient.resume(runCode);
   });
 }
 
 function runCode() {
   gClient.addOneTimeListener("paused", testBPHit);
   gDebuggee.test();
 }
 
--- a/toolkit/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
+++ b/toolkit/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
@@ -1,37 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
 var gClient;
+var gTabClient;
 var gDebuggee;
 
 function run_test()
 {
   initTestDebuggerServer();
   gDebuggee = testGlobal("test-1");
   DebuggerServer.addTestGlobal(gDebuggee);
 
   let transport = DebuggerServer.connectPipe();
   gClient = new DebuggerClient(transport);
   gClient.connect(function(aType, aTraits) {
     attachTestTab(gClient, "test-1", function(aReply, aTabClient) {
+      gTabClient = aTabClient;
       test_threadAttach(aReply.threadActor);
     });
   });
   do_test_pending();
 }
 
 function test_threadAttach(aThreadActorID)
 {
   do_print("Trying to attach to thread " + aThreadActorID);
-  gClient.attachThread(aThreadActorID, function(aResponse, aThreadClient) {
+  gTabClient.attachThread({}, function(aResponse, aThreadClient) {
     do_check_eq(aThreadClient.state, "paused");
     do_check_eq(aThreadClient.actor, aThreadActorID);
     aThreadClient.resume(function() {
       do_check_eq(aThreadClient.state, "attached");
       test_debugger_statement(aThreadClient);
     });
   });
 }
--- a/toolkit/devtools/server/tests/unit/test_interrupt.js
+++ b/toolkit/devtools/server/tests/unit/test_interrupt.js
@@ -15,31 +15,31 @@ function run_test()
   gClient.connect(function(aType, aTraits) {
     attachTestTab(gClient, "test-1", test_attach);
   });
   do_test_pending();
 }
 
 function test_attach(aResponse, aTabClient)
 {
-  gClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) {
+  aTabClient.attachThread({}, function(aResponse, aThreadClient) {
     do_check_eq(aThreadClient.paused, true);
     aThreadClient.resume(function() {
-      test_interrupt();
+      test_interrupt(aThreadClient);
     });
   });
 }
 
-function test_interrupt()
+function test_interrupt(aThreadClient)
 {
-  do_check_eq(gClient.activeThread.paused, false);
-  gClient.activeThread.interrupt(function(aResponse) {
-    do_check_eq(gClient.activeThread.paused, true);
-    gClient.activeThread.resume(function() {
-      do_check_eq(gClient.activeThread.paused, false);
+  do_check_eq(aThreadClient.paused, false);
+  aThreadClient.interrupt(function(aResponse) {
+    do_check_eq(aThreadClient.paused, true);
+    aThreadClient.resume(function() {
+      do_check_eq(aThreadClient.paused, false);
       cleanup();
     });
   });
 }
 
 function cleanup()
 {
   gClient.addListener("closed", function(aEvent) {
--- a/toolkit/devtools/server/tests/unit/test_nesting-03.js
+++ b/toolkit/devtools/server/tests/unit/test_nesting-03.js
@@ -1,50 +1,52 @@
 /* -*- Mode: javascript; js-indent-level: 2; -*- */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that we can detect nested event loops in tabs with the same URL.
 
 const { defer } = devtools.require("sdk/core/promise");
-var gClient1, gClient2;
+var gClient1, gClient2, gThreadClient1, gThreadClient2;
 
 function run_test() {
   initTestDebuggerServer();
   addTestGlobal("test-nesting1");
   addTestGlobal("test-nesting1");
   // Conect the first client to the first debuggee.
   gClient1 = new DebuggerClient(DebuggerServer.connectPipe());
   gClient1.connect(function () {
     attachTestThread(gClient1, "test-nesting1", function (aResponse, aTabClient, aThreadClient) {
+      gThreadClient1 = aThreadClient;
       start_second_connection();
     });
   });
   do_test_pending();
 }
 
 function start_second_connection() {
   gClient2 = new DebuggerClient(DebuggerServer.connectPipe());
   gClient2.connect(function () {
     attachTestThread(gClient2, "test-nesting1", function (aResponse, aTabClient, aThreadClient) {
+      gThreadClient2 = aThreadClient;
       test_nesting();
     });
   });
 }
 
 function test_nesting() {
   const { resolve, reject, promise } = defer();
 
-  gClient1.activeThread.resume(aResponse => {
+  gThreadClient1.resume(aResponse => {
     do_check_eq(aResponse.error, "wrongOrder");
-    gClient2.activeThread.resume(aResponse => {
+    gThreadClient2.resume(aResponse => {
       do_check_true(!aResponse.error);
-      do_check_eq(aResponse.from, gClient2.activeThread.actor);
+      do_check_eq(aResponse.from, gThreadClient2.actor);
 
-      gClient1.activeThread.resume(aResponse => {
+      gThreadClient1.resume(aResponse => {
         do_check_true(!aResponse.error);
-        do_check_eq(aResponse.from, gClient1.activeThread.actor);
+        do_check_eq(aResponse.from, gThreadClient1.actor);
 
         gClient1.close(() => finishClient(gClient2));
       });
     });
   });
 }
--- a/toolkit/devtools/server/tests/unit/test_objectgrips-13.js
+++ b/toolkit/devtools/server/tests/unit/test_objectgrips-13.js
@@ -54,13 +54,13 @@ function test_definition_site(func, obj)
     do_check_eq(column, 0);
 
     test_bad_definition_site(obj);
   });
 }
 
 function test_bad_definition_site(obj) {
   try {
-    obj.getDefinitionSite(() => do_check_true(false));
+    obj._client.request("definitionSite", () => do_check_true(false));
   } catch (e) {
     gThreadClient.resume(() => finishClient(gClient));
   }
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_reattach-thread.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that reattaching to a previously detached thread works.
+ */
+
+var gClient, gDebuggee, gThreadClient, gTabClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = testGlobal("test-reattach");
+  DebuggerServer.addTestGlobal(gDebuggee);
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect(() => {
+    attachTestTab(gClient, "test-reattach", (aReply, aTabClient) => {
+      gTabClient = aTabClient;
+      test_attach();
+    });
+  });
+  do_test_pending();
+}
+
+function test_attach()
+{
+  gTabClient.attachThread({}, (aResponse, aThreadClient) => {
+    do_check_eq(aThreadClient.state, "paused");
+    gThreadClient = aThreadClient;
+    aThreadClient.resume(test_detach);
+  });
+}
+
+function test_detach()
+{
+  gThreadClient.detach(() => {
+    do_check_eq(gThreadClient.state, "detached");
+    do_check_eq(gTabClient.thread, null);
+    test_reattach();
+  });
+}
+
+function test_reattach()
+{
+  gTabClient.attachThread({}, (aResponse, aThreadClient) => {
+    do_check_neq(gThreadClient, aThreadClient);
+    do_check_eq(aThreadClient.state, "paused");
+    do_check_eq(gTabClient.thread, aThreadClient);
+    aThreadClient.resume(cleanup);
+  });
+}
+
+function cleanup()
+{
+  gClient.close(do_test_finished);
+}
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -25,16 +25,17 @@ support-files =
 [test_dbgsocket.js]
 skip-if = toolkit == "gonk"
 reason = bug 821285
 [test_dbgsocket_connection_drop.js]
 [test_dbgactor.js]
 [test_dbgglobal.js]
 [test_dbgclient_debuggerstatement.js]
 [test_attach.js]
+[test_reattach-thread.js]
 [test_blackboxing-01.js]
 [test_blackboxing-02.js]
 [test_blackboxing-03.js]
 [test_blackboxing-04.js]
 [test_blackboxing-05.js]
 [test_blackboxing-06.js]
 [test_frameactor-01.js]
 [test_frameactor-02.js]
--- a/toolkit/devtools/tests/unit/test_safeErrorString.js
+++ b/toolkit/devtools/tests/unit/test_safeErrorString.js
@@ -14,16 +14,19 @@ function run_test() {
 
 function test_with_error() {
   let s = DevToolsUtils.safeErrorString(new Error("foo bar"));
   // Got the message.
   do_check_true(s.contains("foo bar"));
   // Got the stack.
   do_check_true(s.contains("test_with_error"))
   do_check_true(s.contains("test_safeErrorString.js"));
+  // Got the lineNumber and columnNumber.
+  do_check_true(s.contains("line"));
+  do_check_true(s.contains("column"));
 }
 
 function test_with_tricky_error() {
   let e = new Error("batman");
   e.stack = { toString: Object.create(null) };
   let s = DevToolsUtils.safeErrorString(e);
   // Still got the message, despite a bad stack property.
   do_check_true(s.contains("batman"));
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -191,16 +191,18 @@ PopupNotifications.prototype = {
    *        anchored to the iconBox.
    * @param mainAction
    *        A JavaScript object literal describing the notification button's
    *        action. If present, it must have the following properties:
    *          - label (string): the button's label.
    *          - accessKey (string): the button's accessKey.
    *          - callback (function): a callback to be invoked when the button is
    *            pressed.
+   *          - [optional] dismiss (boolean): If this is true, the notification
+   *            will be dismissed instead of removed after running the callback.
    *        If null, the notification will not have a button, and
    *        secondaryActions will be ignored.
    * @param secondaryActions
    *        An optional JavaScript array describing the notification's alternate
    *        actions. The array should contain objects with the same properties
    *        as mainAction. These are used to populate the notification button's
    *        dropdown menu.
    * @param options
@@ -244,16 +246,21 @@ PopupNotifications.prototype = {
    *                                 will be removed.
    *        neverShow:   Indicate that no popup should be shown for this
    *                     notification. Useful for just showing the anchor icon.
    *        removeOnDismissal:
    *                     Notifications with this parameter set to true will be
    *                     removed when they would have otherwise been dismissed
    *                     (i.e. any time the popup is closed due to user
    *                     interaction).
+   *        hideNotNow:  If true, indicates that the 'Not Now' menuitem should
+   *                     not be shown. If 'Not Now' is hidden, it needs to be
+   *                     replaced by another 'do nothing' item, so providing at
+   *                     least one secondary action is required; and one of the
+   *                     actions needs to have the 'dismiss' property set to true.
    *        popupIconURL:
    *                     A string. URL of the image to be displayed in the popup.
    *                     Normally specified in CSS using list-style-image and the
    *                     .popup-notification-icon[popupid=...] selector.
    * @returns the Notification object corresponding to the added notification.
    */
   show: function PopupNotifications_show(browser, id, message, anchorID,
                                          mainAction, secondaryActions, options) {
@@ -264,16 +271,20 @@ PopupNotifications.prototype = {
     if (!browser)
       throw "PopupNotifications_show: invalid browser";
     if (!id)
       throw "PopupNotifications_show: invalid ID";
     if (mainAction && isInvalidAction(mainAction))
       throw "PopupNotifications_show: invalid mainAction";
     if (secondaryActions && secondaryActions.some(isInvalidAction))
       throw "PopupNotifications_show: invalid secondaryActions";
+    if (options && options.hideNotNow &&
+        (!secondaryActions || !secondaryActions.length ||
+         !secondaryActions.concat(mainAction).some(action => action.dismiss)))
+      throw "PopupNotifications_show: 'Not Now' item hidden without replacement";
 
     let notification = new Notification(id, message, anchorID, mainAction,
                                         secondaryActions, browser, this, options);
 
     if (options && options.dismissed)
       notification.dismissed = true;
 
     let existingNotification = this.getNotification(id, browser);
@@ -544,17 +555,20 @@ PopupNotifications.prototype = {
           item.setAttribute("label", a.label);
           item.setAttribute("accesskey", a.accessKey);
           item.notification = n;
           item.action = a;
 
           popupnotification.appendChild(item);
         }, this);
 
-        if (n.secondaryActions.length) {
+        if (n.options.hideNotNow) {
+          popupnotification.setAttribute("hidenotnow", "true");
+        }
+        else if (n.secondaryActions.length) {
           let closeItemSeparator = doc.createElementNS(XUL_NS, "menuseparator");
           popupnotification.appendChild(closeItemSeparator);
         }
       }
 
       this.panel.appendChild(popupnotification);
 
       // The popupnotification may be hidden if we got it from the chrome
@@ -889,32 +903,42 @@ PopupNotifications.prototype = {
       return;
     }
     try {
       notification.mainAction.callback.call();
     } catch(error) {
       Cu.reportError(error);
     }
 
+    if (notification.mainAction.dismiss) {
+      this._dismiss();
+      return;
+    }
+
     this._remove(notification);
     this._update();
   },
 
   _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
     let target = event.originalTarget;
     if (!target.action || !target.notification)
       throw "menucommand target has no associated action/notification";
 
     event.stopPropagation();
     try {
       target.action.callback.call();
     } catch(error) {
       Cu.reportError(error);
     }
 
+    if (target.action.dismiss) {
+      this._dismiss();
+      return;
+    }
+
     this._remove(target.notification);
     this._update();
   },
 
   _notify: function PopupNotifications_notify(topic) {
     Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
   },
 };