Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 10 Mar 2014 17:10:25 -0400
changeset 191043 41d962d23e817c17a13d8cad45d80d96910c8b6a
parent 191020 c54b412511887420dfe96623db55b0c366232287 (current diff)
parent 191042 a996c3c2f00d220ee385db4502279c3cbff21772 (diff)
child 191044 67485526e241bb70f4eab24f1936356469c6130e
child 191068 91a34de66289511aa1dae4ebeb13818a15612b73
child 191191 88184c55d33510529026f467babf602a79cda44c
child 191222 dc80a6efd2a750ffebcf1e722886de0c8641eb6c
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.0a1
first release with
nightly linux32
41d962d23e81 / 30.0a1 / 20140311030201 / files
nightly linux64
41d962d23e81 / 30.0a1 / 20140311030201 / files
nightly mac
41d962d23e81 / 30.0a1 / 20140311030201 / files
nightly win32
41d962d23e81 / 30.0a1 / 20140311030201 / files
nightly win64
41d962d23e81 / 30.0a1 / 20140311030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c.
js/src/vm/Debugger.cpp
mobile/android/chrome/content/browser.js
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1628,17 +1628,17 @@ BrowserGlue.prototype = {
   ensurePlacesDefaultQueriesInitialized:
   function BG_ensurePlacesDefaultQueriesInitialized() {
     // This is actual version of the smart bookmarks, must be increased every
     // time smart bookmarks change.
     // When adding a new smart bookmark below, its newInVersion property must
     // be set to the version it has been added in, we will compare its value
     // to users' smartBookmarksVersion and add new smart bookmarks without
     // recreating old deleted ones.
-    const SMART_BOOKMARKS_VERSION = 6;
+    const SMART_BOOKMARKS_VERSION = 7;
     const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
     const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
 
     // TODO bug 399268: should this be a pref?
     const MAX_RESULTS = 10;
 
     // Get current smart bookmarks version.  If not set, create them.
     let smartBookmarksCurrentVersion = 0;
@@ -1660,76 +1660,86 @@ BrowserGlue.prototype = {
 
         let smartBookmarks = {
           MostVisited: {
             title: bundle.GetStringFromName("mostVisitedTitle"),
             uri: NetUtil.newURI("place:sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
                                 "&maxResults=" + MAX_RESULTS),
             parent: PlacesUtils.toolbarFolderId,
-            position: toolbarIndex++,
+            get position() { return toolbarIndex++; },
             newInVersion: 1
           },
           RecentlyBookmarked: {
             title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
             uri: NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
                                 "&folder=UNFILED_BOOKMARKS" +
                                 "&folder=TOOLBAR" +
                                 "&queryType=" +
                                 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
                                 "&sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
                                 "&maxResults=" + MAX_RESULTS +
                                 "&excludeQueries=1"),
             parent: PlacesUtils.bookmarksMenuFolderId,
-            position: menuIndex++,
+            get position() { return menuIndex++; },
             newInVersion: 1
           },
           RecentTags: {
             title: bundle.GetStringFromName("recentTagsTitle"),
             uri: NetUtil.newURI("place:"+
                                 "type=" +
                                 Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
                                 "&sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
                                 "&maxResults=" + MAX_RESULTS),
             parent: PlacesUtils.bookmarksMenuFolderId,
-            position: menuIndex++,
+            get position() { return menuIndex++; },
             newInVersion: 1
           },
         };
 
         if (Services.metro && Services.metro.supported) {
           smartBookmarks.Windows8Touch = {
-            title: bundle.GetStringFromName("windows8TouchTitle"),
-            uri: NetUtil.newURI("place:folder=" +
-                                PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0] +
-                                "&queryType=" +
-                                Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
-                                "&sort=" +
-                                Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
-                                "&maxResults=" + MAX_RESULTS +
-                                "&excludeQueries=1"),
+            title: PlacesUtils.getString("windows8TouchTitle"),
+            get uri() {
+              let metroBookmarksRoot = PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {});
+              if (metroBookmarksRoot.length > 0) {
+                return NetUtil.newURI("place:folder=" +
+                                      metroBookmarksRoot[0] +
+                                      "&queryType=" +
+                                      Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+                                      "&sort=" +
+                                      Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+                                      "&maxResults=" + MAX_RESULTS +
+                                      "&excludeQueries=1")
+              }
+              return null;
+            },
             parent: PlacesUtils.bookmarksMenuFolderId,
-            position: menuIndex++,
-            newInVersion: 6
+            get position() { return menuIndex++; },
+            newInVersion: 7
           };
         }
 
         // Set current itemId, parent and position if Smart Bookmark exists,
         // we will use these informations to create the new version at the same
         // position.
         let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
         smartBookmarkItemIds.forEach(function (itemId) {
           let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
           if (queryId in smartBookmarks) {
             let smartBookmark = smartBookmarks[queryId];
+            if (!smartBookmark.uri) {
+              PlacesUtils.bookmarks.removeItem(itemId);
+              return;
+            }
             smartBookmark.itemId = itemId;
             smartBookmark.parent = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
-            smartBookmark.position = PlacesUtils.bookmarks.getItemIndex(itemId);
+            smartBookmark.updatedPosition = PlacesUtils.bookmarks.getItemIndex(itemId);
           }
           else {
             // We don't remove old Smart Bookmarks because user could still
             // find them useful, or could have personalized them.
             // Instead we remove the Smart Bookmark annotation.
             PlacesUtils.annotations.removeItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
           }
         });
@@ -1737,30 +1747,30 @@ BrowserGlue.prototype = {
         for (let queryId in smartBookmarks) {
           let smartBookmark = smartBookmarks[queryId];
 
           // We update or create only changed or new smart bookmarks.
           // Also we respect user choices, so we won't try to create a smart
           // bookmark if it has been removed.
           if (smartBookmarksCurrentVersion > 0 &&
               smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
-              !smartBookmark.itemId)
+              !smartBookmark.itemId || !smartBookmark.uri)
             continue;
 
           // Remove old version of the smart bookmark if it exists, since it
           // will be replaced in place.
           if (smartBookmark.itemId) {
             PlacesUtils.bookmarks.removeItem(smartBookmark.itemId);
           }
 
           // Create the new smart bookmark and store its updated itemId.
           smartBookmark.itemId =
             PlacesUtils.bookmarks.insertBookmark(smartBookmark.parent,
                                                  smartBookmark.uri,
-                                                 smartBookmark.position,
+                                                 smartBookmark.updatedPosition || smartBookmark.position,
                                                  smartBookmark.title);
           PlacesUtils.annotations.setItemAnnotation(smartBookmark.itemId,
                                                     SMART_BOOKMARKS_ANNO,
                                                     queryId, 0,
                                                     PlacesUtils.annotations.EXPIRE_NEVER);
         }
 
         // If we are creating all Smart Bookmarks from ground up, add a
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -58,16 +58,15 @@ let (XULAppInfo = {
   };
   let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
                             "XULAppInfo", "@mozilla.org/xre/app-info;1",
                             XULAppInfoFactory);
 }
 
 // Smart bookmarks constants.
-let isMetroSupported = Services.metro && Services.metro.supported;
-const SMART_BOOKMARKS_VERSION = 6
+const SMART_BOOKMARKS_VERSION = 7;
 const SMART_BOOKMARKS_ON_TOOLBAR = 1;
-const SMART_BOOKMARKS_ON_MENU = isMetroSupported ? 4 : 3; // Takes in count the additional separator.
+const SMART_BOOKMARKS_ON_MENU =  3; // Takes into account the additional separator.
 
 // Default bookmarks constants.
 const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;
 const DEFAULT_BOOKMARKS_ON_MENU = 1;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/addon-source/browser_dbg_addon3/lib/main.js
@@ -0,0 +1,13 @@
+var { Cc, Ci } = require("chrome");
+var { once } = require("sdk/system/events");
+
+var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+var observer = {
+  observe: function () {
+    debugger;
+  }
+};
+
+once("sdk:loader:destroy", () => observerService.removeObserver(observer, "debuggerAttached"));
+
+observerService.addObserver(observer, "debuggerAttached", false);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/addon-source/browser_dbg_addon3/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "browser_dbg_addon3",
+  "title": "browser_dbg_addon3",
+  "id": "jid1-ami3akps3baaeg",
+  "description": "a basic add-on",
+  "author": "",
+  "license": "MPL 2.0",
+  "version": "0.1"
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..673b31b9d1748647f72e94801e16591fecf7122b
GIT binary patch
literal 7423
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
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   addon1.xpi
   addon2.xpi
+  addon3.xpi
   code_binary_search.coffee
   code_binary_search.js
   code_binary_search.map
   code_blackboxing_blackboxme.js
   code_blackboxing_one.js
   code_blackboxing_three.js
   code_blackboxing_two.js
   code_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -75,16 +76,17 @@ support-files =
   doc_watch-expressions.html
   doc_watch-expression-button.html
   doc_with-frame.html
   head.js
   sjs_random-javascript.sjs
   testactors.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
+[browser_dbg_addonactor.js]
 [browser_dbg_auto-pretty-print-01.js]
 [browser_dbg_auto-pretty-print-02.js]
 [browser_dbg_bfcache.js]
 [browser_dbg_blackboxing-01.js]
 [browser_dbg_blackboxing-02.js]
 [browser_dbg_blackboxing-03.js]
 [browser_dbg_blackboxing-04.js]
 [browser_dbg_blackboxing-05.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_addonactor.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Make sure we can attach to addon actors.
+
+const ADDON3_URL = EXAMPLE_URL + "addon3.xpi";
+const ADDON_MODULE_URL = "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js";
+
+var gAddon, gClient, gThreadClient;
+
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect((aType, aTraits) => {
+    is(aType, "browser",
+      "Root actor should identify itself as a browser.");
+
+    installAddon()
+      .then(attachAddonActorForUrl.bind(null, gClient, ADDON3_URL))
+      .then(attachAddonThread)
+      .then(testDebugger)
+      .then(testSources)
+      .then(uninstallAddon)
+      .then(closeConnection)
+      .then(finish)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+}
+
+function installAddon () {
+  return addAddon(ADDON3_URL).then(aAddon => {
+    gAddon = aAddon;
+  });
+}
+
+function attachAddonThread ([aGrip, aResponse]) {
+  info("attached addon actor for URL");
+  let deferred = promise.defer();
+
+  gClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
+    info("attached thread");
+    gThreadClient = aThreadClient;
+    gThreadClient.resume(deferred.resolve);
+  });
+  return deferred.promise;
+}
+
+function testDebugger() {
+  info('Entering testDebugger');
+  let deferred = promise.defer();
+
+  once(gClient, "paused").then(() => {
+    ok(true, "Should be able to attach to addon actor");
+    gThreadClient.resume(deferred.resolve)
+  });
+
+  Services.obs.notifyObservers(null, "debuggerAttached", null);
+
+  return deferred.promise;
+}
+
+function testSources() {
+  let deferred = promise.defer();
+
+  gThreadClient.getSources(aResponse => {
+    // source URLs contain launch-specific temporary directory path,
+    // hence the ".contains" call.
+    const matches = aResponse.sources.filter(s =>
+      s.url.contains(ADDON_MODULE_URL));
+    is(matches.length, 1,
+      "the main script of the addon is present in the source list");
+    deferred.resolve();
+  });
+
+  return deferred.promise;
+}
+
+function uninstallAddon() {
+  return removeAddon(gAddon);
+}
+
+function closeConnection () {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  gClient = null;
+  gAddon = null;
+  gThreadClient = null;
+});
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -149,16 +149,17 @@ function getTabActorForUrl(aClient, aUrl
     let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
     deferred.resolve(tabActor);
   });
 
   return deferred.promise;
 }
 
 function getAddonActorForUrl(aClient, aUrl) {
+  info("Get addon actor for URL: " + aUrl);
   let deferred = promise.defer();
 
   aClient.listAddons(aResponse => {
     let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop();
     deferred.resolve(addonActor);
   });
 
   return deferred.promise;
@@ -656,8 +657,19 @@ function stopTracing(aPanel) {
 function filterTraces(aPanel, f) {
   const traces = aPanel.panelWin.document
     .getElementById("tracer-traces")
     .querySelector("scrollbox")
     .children;
   return Array.filter(traces, f);
 }
 
+function attachAddonActorForUrl(aClient, aUrl) {
+  let deferred = promise.defer();
+
+  getAddonActorForUrl(aClient, aUrl).then(aGrip => {
+    aClient.attachAddon(aGrip.actor, aResponse => {
+      deferred.resolve([aGrip, aResponse]);
+    });
+  });
+
+  return deferred.promise;
+}
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -557,17 +557,21 @@ TabWebProgressListener.prototype = {
     }
   },
 
   /**
    * Destroy the progress listener instance.
    */
   destroy: function TWPL_destroy() {
     if (this.target.tab) {
-      this.target.tab.linkedBrowser.removeProgressListener(this);
+      try {
+        this.target.tab.linkedBrowser.removeProgressListener(this);
+      } catch (ex) {
+        // This can throw when a tab crashes in e10s.
+      }
     }
     this.target._webProgressListener = null;
     this.target._navRequest = null;
     this.target._navWindow = null;
     this.target = null;
   }
 };
 
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -126,16 +126,109 @@ var Scratchpad = {
         obj[key.trim()] = val.trim();
       }
     });
 
     return obj;
   },
 
   /**
+   * Add the event listeners for popupshowing events.
+   */
+  _setupPopupShowingListeners: function SP_setupPopupShowing() {
+    let elementIDs = ['sp-menu_editpopup', 'scratchpad-text-popup'];
+
+    for (let elementID of elementIDs) {
+      let elem = document.getElementById(elementID);
+      if (elem) {
+        elem.addEventListener("popupshowing", function () {
+          goUpdateGlobalEditMenuItems();
+          let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_findAgain'];
+          commands.forEach(goUpdateCommand);
+        });
+      }
+    }
+  },
+
+  /**
+   * Add the event event listeners for command events.
+   */
+  _setupCommandListeners: function SP_setupCommands() {
+    let commands = {
+      "cmd_gotoLine": () => {
+        goDoCommand('cmd_gotoLine');
+      },
+      "sp-cmd-newWindow": () => {
+        Scratchpad.openScratchpad();
+      },
+      "sp-cmd-openFile": () => {
+        Scratchpad.openFile();
+      },
+      "sp-cmd-clearRecentFiles": () => {
+        Scratchpad.clearRecentFiles();
+      },
+      "sp-cmd-save": () => {
+        Scratchpad.saveFile();
+      },
+      "sp-cmd-saveas": () => {
+        Scratchpad.saveFileAs();
+      },
+      "sp-cmd-revert": () => {
+        Scratchpad.promptRevert();
+      },
+      "sp-cmd-close": () => {
+        Scratchpad.close();
+      },
+      "sp-cmd-run": () => {
+        Scratchpad.run();
+      },
+      "sp-cmd-inspect": () => {
+        Scratchpad.inspect();
+      },
+      "sp-cmd-display": () => {
+        Scratchpad.display();
+      },
+      "sp-cmd-pprint": () => {
+        Scratchpad.prettyPrint();
+      },
+      "sp-cmd-contentContext": () => {
+        Scratchpad.setContentContext();
+      },
+      "sp-cmd-browserContext": () => {
+        Scratchpad.setBrowserContext();
+      },
+      "sp-cmd-reloadAndRun": () => {
+        Scratchpad.reloadAndRun();
+      },
+      "sp-cmd-evalFunction": () => {
+        Scratchpad.evalTopLevelFunction();
+      },
+      "sp-cmd-errorConsole": () => {
+        Scratchpad.openErrorConsole();
+      },
+      "sp-cmd-webConsole": () => {
+        Scratchpad.openWebConsole();
+      },
+      "sp-cmd-documentationLink": () => {
+        Scratchpad.openDocumentationPage();
+      },
+      "sp-cmd-hideSidebar": () => {
+        Scratchpad.sidebar.hide();
+      }
+    }
+
+    for (let command in commands) {
+      let elem = document.getElementById(command);
+      if (elem) {
+        elem.addEventListener("command", commands[command]);
+      }
+    }
+  },
+
+  /**
    * The script execution context. This tells Scratchpad in which context the
    * script shall execute.
    *
    * Possible values:
    *   - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
    *   tab content window object.
    *   - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
    *   currently active chrome window object.
@@ -1163,17 +1256,17 @@ var Scratchpad = {
         menuitem.setAttribute("type", "radio");
         menuitem.setAttribute("label", filePaths[i]);
 
         if (filePaths[i] === filename) {
           menuitem.setAttribute("checked", true);
           menuitem.setAttribute("disabled", true);
         }
 
-        menuitem.setAttribute("oncommand", "Scratchpad.openFile(" + i + ");");
+        menuitem.addEventListener("command", Scratchpad.openFile.bind(Scratchpad, i));
         recentFilesPopup.appendChild(menuitem);
       }
 
       recentFilesPopup.appendChild(document.createElement("menuseparator"));
       let clearItems = document.createElement("menuitem");
       clearItems.setAttribute("id", "sp-menu-clear_recent");
       clearItems.setAttribute("label",
                               this.strings.
@@ -1508,16 +1601,18 @@ var Scratchpad = {
         this.dirty = !state.saved;
 
       this.initialized = true;
       this._triggerObservers("Ready");
       this.populateRecentFilesMenu();
       PreferenceObserver.init();
       CloseObserver.init();
     }).then(null, (err) => console.log(err.message));
+    this._setupCommandListeners();
+    this._setupPopupShowingListeners();
   },
 
   /**
    * The Source Editor "change" event handler. This function updates the
    * Scratchpad window title to show an asterisk when there are unsaved changes.
    *
    * @private
    */
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -28,51 +28,42 @@
         width="640" height="480"
         persist="screenX screenY width height sizemode">
 
 <script type="application/javascript;version=1.8"
         src="chrome://browser/content/devtools/theme-switching.js"/>
 <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 <script type="application/javascript" src="chrome://browser/content/devtools/scratchpad.js"/>
 
-
-<script type="application/javascript">
-  function goUpdateSourceEditorMenuItems() {
-    goUpdateGlobalEditMenuItems();
-    let commands = ['cmd_undo', 'cmd_redo', 'cmd_delete', 'cmd_findAgain'];
-    commands.forEach(goUpdateCommand);
-  }
-</script>
-
 <commandset id="editMenuCommands"/>
 
 <commandset id="sourceEditorCommands">
-  <command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
+  <command id="cmd_gotoLine" oncommand=";"/>
 </commandset>
 
 <commandset id="sp-commandset">
-  <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
-  <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
-  <command id="sp-cmd-clearRecentFiles" oncommand="Scratchpad.clearRecentFiles();"/>
-  <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
-  <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
-  <command id="sp-cmd-revert" oncommand="Scratchpad.promptRevert();" disabled="true"/>
-  <command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
-  <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
-  <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
-  <command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
-  <command id="sp-cmd-pprint" oncommand="Scratchpad.prettyPrint();"/>
-  <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
-  <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
-  <command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
-  <command id="sp-cmd-evalFunction" oncommand="Scratchpad.evalTopLevelFunction();"/>
-  <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
-  <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
-  <command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
-  <command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
+  <command id="sp-cmd-newWindow" oncommand=";"/>
+  <command id="sp-cmd-openFile" oncommand=";"/>
+  <command id="sp-cmd-clearRecentFiles" oncommand=";"/>
+  <command id="sp-cmd-save" oncommand=";"/>
+  <command id="sp-cmd-saveas" oncommand=";"/>
+  <command id="sp-cmd-revert" oncommand=";" disabled="true"/>
+  <command id="sp-cmd-close" oncommand=";"/>
+  <command id="sp-cmd-run" oncommand=";"/>
+  <command id="sp-cmd-inspect" oncommand=";"/>
+  <command id="sp-cmd-display" oncommand=";"/>
+  <command id="sp-cmd-pprint" oncommand=";"/>
+  <command id="sp-cmd-contentContext" oncommand=";"/>
+  <command id="sp-cmd-browserContext" oncommand=";" disabled="true"/>
+  <command id="sp-cmd-reloadAndRun" oncommand=";"/>
+  <command id="sp-cmd-evalFunction" oncommand=";"/>
+  <command id="sp-cmd-errorConsole" oncommand=";" disabled="true"/>
+  <command id="sp-cmd-webConsole" oncommand=";"/>
+  <command id="sp-cmd-documentationLink" oncommand=";"/>
+  <command id="sp-cmd-hideSidebar" oncommand=";"/>
 </commandset>
 
 <keyset id="editMenuKeys"/>
 
 <keyset id="sp-keyset">
   <key id="sp-key-window"
        key="&newWindowCmd.commandkey;"
        command="sp-cmd-newWindow"
@@ -167,18 +158,17 @@
                 key="sp-key-close"
                 accesskey="&closeCmd.accesskey;"
                 command="sp-cmd-close"/>
     </menupopup>
   </menu>
 
   <menu id="sp-edit-menu" label="&editMenu.label;"
         accesskey="&editMenu.accesskey;">
-    <menupopup id="sp-menu_editpopup"
-               onpopupshowing="goUpdateSourceEditorMenuItems()">
+    <menupopup id="sp-menu_editpopup">
       <menuitem id="menu_undo"/>
       <menuitem id="menu_redo"/>
       <menuseparator/>
       <menuitem id="menu_cut"/>
       <menuitem id="menu_copy"/>
       <menuitem id="menu_paste"/>
       <menuseparator/>
       <menuitem id="menu_selectAll"/>
@@ -300,18 +290,17 @@
   <toolbarbutton id="sp-toolbar-pprint"
                  class="devtools-toolbarbutton"
                  label="&pprint.label;"
                  command="sp-cmd-pprint"/>
 </toolbar>
 
 
 <popupset id="scratchpad-popups">
-  <menupopup id="scratchpad-text-popup"
-             onpopupshowing="goUpdateSourceEditorMenuItems()">
+  <menupopup id="scratchpad-text-popup">
     <menuitem id="cMenu_cut"/>
     <menuitem id="cMenu_copy"/>
     <menuitem id="cMenu_paste"/>
     <menuitem id="cMenu_delete"/>
     <menuseparator/>
     <menuitem id="cMenu_selectAll"/>
     <menuseparator/>
     <menuitem id="sp-text-run"
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -741,16 +741,50 @@ Tooltip.prototype = {
           def.resolve();
         }
       });
     } else {
       def.reject();
     }
 
     return def.promise;
+  },
+
+  /**
+   * Set the content of the tooltip to display a font family preview.
+   * This is based on Lea Verou's Dablet. See https://github.com/LeaVerou/dabblet
+   * for more info.
+   *
+   * @param {String} font
+   *        The font family value.
+   */
+  setFontFamilyContent: function(font) {
+    let def = promise.defer();
+
+    if (font) {
+      // Main container
+      let vbox = this.doc.createElement("vbox");
+      vbox.setAttribute("flex", "1");
+
+      // Display the font family previewer
+      let previewer = this.doc.createElement("description");
+      previewer.setAttribute("flex", "1");
+      previewer.style.fontFamily = font;
+      previewer.classList.add("devtools-tooltip-font-previewer-text");
+      previewer.textContent = "(ABCabc123&@%)";
+      vbox.appendChild(previewer);
+
+      this.content = vbox;
+
+      def.resolve();
+    } else {
+      def.reject();
+    }
+
+    return def.promise;
   }
 };
 
 /**
  * Base class for all (color, gradient, ...)-swatch based value editors inside
  * tooltips
  *
  * @param {XULDocument} doc
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -526,24 +526,30 @@ CssHtmlTree.prototype = {
       let propName = propValue.parentNode.querySelector(".property-name");
       if (propName.textContent === "background-image") {
         let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
         let uri = CssLogic.getBackgroundImageUriFromProperty(propValue.textContent);
         return this.tooltip.setRelativeImageContent(uri, inspector.inspector, maxDim);
       }
     }
 
-    // Test for css transform
     if (target.classList.contains("property-value")) {
       let propValue = target;
       let propName = target.parentNode.querySelector(".property-name");
+
+      // Test for css transform
       if (propName.textContent === "transform") {
         return this.tooltip.setCssTransformContent(propValue.textContent,
           this.pageStyle, this.viewedElement);
       }
+
+      // Test for font family
+      if (propName.textContent === "font-family") {
+        return this.tooltip.setFontFamilyContent(propValue.textContent);
+      }
     }
 
     // If the target isn't one that should receive a tooltip, signal it by rejecting
     // a promise
     return promise.reject();
   },
 
   /**
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1137,16 +1137,37 @@ CssRuleView.prototype = {
         let uri = CssLogic.getBackgroundImageUriFromProperty(property.value,
           property.rule.domRule.href);
         this.previewTooltip.setRelativeImageContent(uri,
           this.inspector.inspector, maxDim).then(def.resolve);
         hasTooltip = true;
       }
     }
 
+    // Get the nodes containing the property name and property value,
+    // and test for font family
+    let propertyRoot = target.parentNode;
+    let propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
+
+    if (!propertyNameNode) {
+      propertyRoot = propertyRoot.parentNode;
+      propertyNameNode = propertyRoot.querySelector(".ruleview-propertyname");
+    }
+
+    let propertyName;
+    if (propertyNameNode) {
+      propertyName = propertyNameNode.textContent;
+    }
+
+    if (propertyName === "font-family" &&
+        target.classList.contains("ruleview-propertyvalue")) {
+      this.previewTooltip.setFontFamilyContent(target.textContent).then(def.resolve);
+      hasTooltip = true;
+    }
+
     if (hasTooltip) {
       this.colorPicker.revert();
       this.colorPicker.hide();
     } else {
       def.reject();
     }
 
     return def.promise;
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -51,16 +51,18 @@ support-files =
 [browser_ruleview_bug_902966_revert_value_on_ESC.js]
 [browser_ruleview_pseudoelement.js]
 support-files = browser_ruleview_pseudoelement.html
 [browser_computedview_bug835808_keyboard_nav.js]
 [browser_bug913014_matched_expand.js]
 [browser_bug765105_background_image_tooltip.js]
 [browser_bug889638_rule_view_color_picker.js]
 [browser_bug726427_csstransform_tooltip.js]
+[browser_bug702577_fontfamily_tooltip_shorthand.js]
+[browser_bug702577_fontfamily_tooltip_longhand.js]
 [browser_bug940500_rule_view_pick_gradient_color.js]
 [browser_ruleview_original_source_link.js]
 support-files =
   sourcemaps.html
   sourcemaps.css
   sourcemaps.css.map
   sourcemaps.scss
 [browser_computedview_original_source_link.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug702577_fontfamily_tooltip_longhand.js
@@ -0,0 +1,131 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let contentDoc;
+let inspector;
+let ruleView;
+let computedView;
+
+const PAGE_CONTENT = [
+  '<style type="text/css">',
+  '  #testElement {',
+  '    font-family: cursive;',
+  '    color: #333;',
+  '    padding-left: 70px;',
+  '  }',
+  '</style>',
+  '<div id="testElement">test element</div>'
+].join("\n");
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html;charset=utf-8,font family longhand tooltip test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    startTests();
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.querySelector("#testElement"));
+  inspector.once("inspector-updated", testRuleView);
+}
+
+function endTests() {
+  contentDoc = inspector = ruleView = computedView = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function assertTooltipShownOn(tooltip, element, cb) {
+  // If there is indeed a show-on-hover on element, the xul panel will be shown
+  tooltip.panel.addEventListener("popupshown", function shown() {
+    tooltip.panel.removeEventListener("popupshown", shown, true);
+    cb();
+  }, true);
+  tooltip._showOnHover(element);
+}
+
+function testRuleView() {
+  info("Testing font-family tooltips in the rule view");
+
+  let panel = ruleView.previewTooltip.panel;
+
+  // Check that the rule view has a tooltip and that a XUL panel has been created
+  ok(ruleView.previewTooltip, "Tooltip instance exists");
+  ok(panel, "XUL panel exists");
+
+  // Get the font family property inside the rule view
+  let {valueSpan} = getRuleViewProperty("font-family");
+
+  // And verify that the tooltip gets shown on this property
+  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    let description = panel.getElementsByTagName("description")[0];
+    is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
+
+    ruleView.previewTooltip.hide();
+
+    testComputedView();
+  });
+}
+
+function testComputedView() {
+  info("Testing font-family tooltips in the computed view");
+
+  inspector.sidebar.select("computedview");
+  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+  let doc = computedView.styleDocument;
+
+  let panel = computedView.tooltip.panel;
+  let {valueSpan} = getComputedViewProperty("font-family");
+
+  assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
+    let description = panel.getElementsByTagName("description")[0];
+    is(description.style.fontFamily, "cursive", "Tooltips contains correct font-family style");
+
+    computedView.tooltip.hide();
+
+    endTests();
+  });
+}
+
+function getRuleViewProperty(name) {
+  let prop = null;
+  [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
+    let nameSpan = property.querySelector(".ruleview-propertyname");
+    let valueSpan = property.querySelector(".ruleview-propertyvalue");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
+
+function getComputedViewProperty(name) {
+  let prop = null;
+  [].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
+    let nameSpan = property.querySelector(".property-name");
+    let valueSpan = property.querySelector(".property-value");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug702577_fontfamily_tooltip_shorthand.js
@@ -0,0 +1,133 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let contentDoc;
+let inspector;
+let ruleView;
+let computedView;
+
+const PAGE_CONTENT = [
+  '<style type="text/css">',
+  '  #testElement {',
+  '    font: italic bold .8em/1.2 Arial;',
+  '  }',
+  '</style>',
+  '<div id="testElement">test element</div>'
+].join("\n");
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html;charset=utf-8,font family shorthand tooltip test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    startTests();
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.querySelector("#testElement"));
+  inspector.once("inspector-updated", testRuleView);
+}
+
+function endTests() {
+  contentDoc = inspector = ruleView = computedView = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function assertTooltipShownOn(tooltip, element, cb) {
+  // If there is indeed a show-on-hover on element, the xul panel will be shown
+  tooltip.panel.addEventListener("popupshown", function shown() {
+    tooltip.panel.removeEventListener("popupshown", shown, true);
+    cb();
+  }, true);
+  tooltip._showOnHover(element);
+}
+
+function testRuleView() {
+  info("Testing font-family tooltips in the rule view");
+
+  let panel = ruleView.previewTooltip.panel;
+
+  // Check that the rule view has a tooltip and that a XUL panel has been created
+  ok(ruleView.previewTooltip, "Tooltip instance exists");
+  ok(panel, "XUL panel exists");
+
+  // Get the computed font family property inside the font rule view
+  let propertyList = ruleView.element.querySelectorAll(".ruleview-propertylist");
+  let fontExpander = propertyList[1].querySelectorAll(".ruleview-expander")[0];
+  fontExpander.click();
+
+  let {valueSpan} = getRuleViewProperty("font-family");
+
+  // And verify that the tooltip gets shown on this property
+  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    let description = panel.getElementsByTagName("description")[0];
+    is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
+
+    ruleView.previewTooltip.hide();
+
+    testComputedView();
+  });
+}
+
+function testComputedView() {
+  info("Testing font-family tooltips in the computed view");
+
+  inspector.sidebar.select("computedview");
+  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+  let doc = computedView.styleDocument;
+
+  let panel = computedView.tooltip.panel;
+  let {valueSpan} = getComputedViewProperty("font-family");
+
+  assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
+    let description = panel.getElementsByTagName("description")[0];
+    is(description.style.fontFamily, "Arial", "Tooltips contains correct font-family style");
+
+    computedView.tooltip.hide();
+
+    endTests();
+  });
+}
+
+function getRuleViewProperty(name) {
+  let prop = null;
+  [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-computedlist"), property => {
+    let nameSpan = property.querySelector(".ruleview-propertyname");
+    let valueSpan = property.querySelector(".ruleview-propertyvalue");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
+
+function getComputedViewProperty(name) {
+  let prop = null;
+  [].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
+    let nameSpan = property.querySelector(".property-name");
+    let valueSpan = property.querySelector(".property-value");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -65,20 +65,16 @@ detailsPane.noItems=No items
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of items
 # example: 111 items
 detailsPane.itemsCountLabel=One item;#1 items
 
 mostVisitedTitle=Most Visited
 recentlyBookmarkedTitle=Recently Bookmarked
 recentTagsTitle=Recent Tags
-# LOCALIZATION NOTE (windows8TouchTitle): this is the name of the folder used
-# to store bookmarks created in Metro mode and share bookmarks between Metro
-# and Desktop.
-windows8TouchTitle=Windows 8 Touch
 
 OrganizerQueryHistory=History
 OrganizerQueryDownloads=Downloads
 OrganizerQueryAllBookmarks=All Bookmarks
 OrganizerQueryTags=Tags
 
 # LOCALIZATION NOTE (tagResultLabel) :
 # Noun used to describe the location bar autocomplete result type
--- a/browser/metro/components/BrowserStartup.js
+++ b/browser/metro/components/BrowserStartup.js
@@ -3,16 +3,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
 // Custom factory object to ensure that we're a singleton
 const BrowserStartupServiceFactory = {
   _instance: null,
   createInstance: function (outer, iid) {
     if (outer != null)
       throw Components.results.NS_ERROR_NO_AGGREGATION;
     return this._instance || (this._instance = new BrowserStartup());
   }
@@ -57,17 +64,45 @@ BrowserStartup.prototype = {
                   getService(Ci.nsIAnnotationService);
       let metroRootItems = annos.getItemsWithAnnotation("metro/bookmarksRoot", {});
       if (metroRootItems.length > 0)
         return; // no need to do initial import
     }
 
     Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
 
-    BookmarkJSONUtils.importFromURL("chrome://browser/locale/bookmarks.json", false);
+    Task.spawn(function() {
+      yield BookmarkJSONUtils.importFromURL("chrome://browser/locale/bookmarks.json", false);
+
+      // Create the new smart bookmark.
+      const MAX_RESULTS = 10;
+      const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+
+      // Place the Metro folder at the end of the smart bookmarks list.
+      let maxIndex =  Math.max.apply(null,
+        PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO).map(id => {
+          return PlacesUtils.bookmarks.getItemIndex(id);
+        }));
+      let smartBookmarkId =
+        PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+                                             NetUtil.newURI("place:folder=" +
+                                                            PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0] +
+                                                            "&queryType=" +
+                                                            Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+                                                            "&sort=" +
+                                                            Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+                                                            "&maxResults=" + MAX_RESULTS +
+                                                            "&excludeQueries=1"),
+                                             maxIndex + 1,
+                                             PlacesUtils.getString("windows8TouchTitle"));
+      PlacesUtils.annotations.setItemAnnotation(smartBookmarkId,
+                                                SMART_BOOKMARKS_ANNO,
+                                                "Windows8Touch", 0,
+                                                PlacesUtils.annotations.EXPIRE_NEVER);
+    });
   },
 
   _startupActions: function() {
   },
 
   // nsIObserver
   observe: function(aSubject, aTopic, aData) {
     switch (aTopic) {
--- a/browser/themes/shared/devtools/common.css
+++ b/browser/themes/shared/devtools/common.css
@@ -169,16 +169,24 @@
 .devtools-tooltip-simple-text:first-child {
   margin-top: -4px;
 }
 
 .devtools-tooltip-simple-text:last-child {
   margin-bottom: -4px;
 }
 
+/* Tooltip: Font Family Previewer Text */
+.devtools-tooltip-font-previewer-text {
+  max-width: 400px;
+  line-height: 1.5;
+  font-size: 150%;
+  text-align: center;
+}
+
 /* Tooltip: Alert Icon */
 
 .devtools-tooltip-alert-icon {
   width: 32px;
   height: 32px;
   margin: 6px;
   -moz-margin-end: 20px;
 }
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -307,16 +307,20 @@ div.CodeMirror span.eval-text {
   }
 }
 
 .theme-tooltip-panel .devtools-tooltip-simple-text {
   color: white;
   border-bottom: 1px solid #434850;
 }
 
+.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
+  color: white;
+}
+
 .theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
   border-bottom: 0;
 }
 
 .devtools-horizontal-splitter {
   border-bottom: 1px solid black;
 }
 
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -316,16 +316,20 @@ div.CodeMirror span.eval-text {
   }
 }
 
 .theme-tooltip-panel .devtools-tooltip-simple-text {
   color: black;
   border-bottom: 1px solid #d9e1e8;
 }
 
+.theme-tooltip-panel .devtools-tooltip-font-previewer-text {
+  color: black;
+}
+
 .theme-tooltip-panel .devtools-tooltip-simple-text:last-child {
   border-bottom: 0;
 }
 
 .devtools-horizontal-splitter {
   border-bottom: 1px solid #aaa;
 }
 
--- a/browser/themes/windows/downloads/downloads-aero.css
+++ b/browser/themes/windows/downloads/downloads-aero.css
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %define WINDOWS_AERO
 %include downloads.css
 %undef WINDOWS_AERO
 
-@media (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+       (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
   richlistitem[type="download"] {
     border: 1px solid transparent;
     border-bottom: 1px solid hsl(213,40%,90%);
   }
 
   #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
     border: 1px solid hsl(213,45%,65%);
     box-shadow: 0 0 0 1px hsla(0,0%,100%,.5) inset,
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -15,42 +15,81 @@
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
 #downloadsHistory {
   background: transparent;
-  color: -moz-nativehyperlinktext;
   cursor: pointer;
 }
 
+%ifdef WINDOWS_AERO
+@media (-moz-os-version: windows-vista),
+       (-moz-os-version: windows-win7) {
+%endif
+#downloadsHistory {
+  color: -moz-nativehyperlinktext;
+}
+%ifdef WINDOWS_AERO
+}
+%endif
+
 #downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
   outline: 1px -moz-dialogtext dotted;
   outline-offset: -1px;
 }
 
 #downloadsHistory > .button-box {
   border: none;
   margin: 1em;
 }
 
+#downloadsPanel[hasdownloads] > #downloadsFooter {
+  background-color: hsla(210,4%,10%,.04);
+  box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
+  transition-duration: 150ms;
+  transition-property: background-color;
+}
+
+#downloadsPanel[hasdownloads] > #downloadsFooter:hover {
+  background-color: hsla(210,4%,10%,.05);
+}
+
+#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
+  background-color: hsla(210,4%,10%,.1);
+  box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
+}
+
+%ifdef WINDOWS_AERO
+@media (-moz-os-version: windows-vista),
+       (-moz-os-version: windows-win7) {
+%endif
 @media (-moz-windows-default-theme) {
   #downloadsPanel[hasdownloads] > #downloadsFooter {
+    border-bottom-left-radius: 3px;
+    border-bottom-right-radius: 3px;
+    transition-duration: 0s;
+  }
+
+  #downloadsPanel[hasdownloads] > #downloadsFooter,
+  #downloadsPanel[hasdownloads] > #downloadsFooter:hover,
+  #downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
 %ifdef WINDOWS_AERO
     background-color: #f1f5fb;
 %else
     background-color: hsla(216,45%,88%,.98);
 %endif
     box-shadow: 0px 1px 2px rgb(204,214,234) inset;
-    border-bottom-left-radius: 3px;
-    border-bottom-right-radius: 3px;
   }
 }
+%ifdef WINDOWS_AERO
+}
+%endif
 
 /*** Downloads Summary and List items ***/
 
 #downloadsSummary,
 richlistitem[type="download"] {
   height: 7em;
   -moz-padding-end: 0;
   color: inherit;
@@ -162,24 +201,50 @@ richlistitem[type="download"]:first-chil
 
 #downloadsPanel[keyfocus] .downloadButton:focus > .button-box {
   border: 1px dotted ThreeDDarkShadow;
 }
 
 /*** Highlighted list items ***/
 
 #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+  background-color: hsla(210,4%,10%,.08);
+  outline: 1px solid hsla(210,4%,10%,.1);
+  outline-offset: -1px;
+  cursor: pointer;
+}
+
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
+  background-color: hsla(210,4%,10%,.15);
+  outline: 1px solid hsla(210,4%,10%,.15);
+  box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
+}
+
+%ifdef WINDOWS_AERO
+@media (-moz-os-version: windows-vista),
+       (-moz-os-version: windows-win7) {
+%endif
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
   border-radius: 3px;
+  outline: 0;
   border-top: 1px solid hsla(0,0%,100%,.2);
   border-bottom: 1px solid hsla(0,0%,0%,.2);
   background-color: Highlight;
   color: HighlightText;
-  cursor: pointer;
 }
 
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
+  background-color: Highlight;
+  outline: 0;
+  box-shadow: none;
+}
+%ifdef WINDOWS_AERO
+}
+%endif
+
 /*** Button icons ***/
 
 .downloadButton.downloadCancel {
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2741,19 +2741,26 @@ Debugger::findAllGlobals(JSContext *cx, 
 {
     THIS_DEBUGGER(cx, argc, vp, "findAllGlobals", args, dbg);
 
     RootedObject result(cx, NewDenseEmptyArray(cx));
     if (!result)
         return false;
 
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+        if (c->options().invisibleToDebugger())
+            continue;
+
         c->zone()->scheduledForDestruction = false;
 
         GlobalObject *global = c->maybeGlobal();
+
+        if (cx->runtime()->isSelfHostingGlobal(global))
+            continue;
+
         if (global) {
             /*
              * We pulled |global| out of nowhere, so it's possible that it was
              * marked gray by XPConnect. Since we're now exposing it to JS code,
              * we need to mark it black.
              */
             JS::ExposeGCThingToActiveJS(global, JSTRACE_OBJECT);
 
--- a/mobile/android/base/home/HomeConfigInvalidator.java
+++ b/mobile/android/base/home/HomeConfigInvalidator.java
@@ -99,17 +99,17 @@ public class HomeConfigInvalidator imple
         });
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
                 Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
-                handlePanelInstall(createPanelConfigFromMessage(message));
+                handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
             } else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
                 Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
                 final String panelId = message.getString(JSON_KEY_PANEL_ID);
                 handlePanelUninstall(panelId);
             } else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
                 Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
                 handlePanelUpdate(createPanelConfigFromMessage(message));
             }
@@ -121,30 +121,33 @@ public class HomeConfigInvalidator imple
     private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
         final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
         return new PanelConfig(json);
     }
 
      /**
      * Adds a new PanelConfig to the HomeConfig.
      *
+     * This posts the invalidation of HomeConfig immediately.
+     *
      * @param panelConfig panel to add
      */
     public void installPanel(PanelConfig panelConfig) {
-        handlePanelInstall(panelConfig);
+        Log.d(LOGTAG, "installPanel: " + panelConfig.getTitle());
+        handlePanelInstall(panelConfig, InvalidationMode.IMMEDIATE);
     }
 
     /**
      * Runs in the gecko thread.
      */
-    private void handlePanelInstall(PanelConfig panelConfig) {
+    private void handlePanelInstall(PanelConfig panelConfig, InvalidationMode mode) {
         mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
         Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
 
-        scheduleInvalidation(InvalidationMode.DELAYED);
+        scheduleInvalidation(mode);
     }
 
     /**
      * Runs in the gecko thread.
      */
     private void handlePanelUninstall(String panelId) {
         mPendingChanges.offer(new ConfigChange(ChangeType.UNINSTALL, panelId));
         Log.d(LOGTAG, "handlePanelUninstall: " + mPendingChanges.size());
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -219,16 +219,20 @@ public class HomePager extends ViewPager
 
     @Override
     public void setCurrentItem(int item, boolean smoothScroll) {
         super.setCurrentItem(item, smoothScroll);
 
         if (mDecor != null) {
             mDecor.onPageSelected(item);
         }
+
+        if (mHomeBanner != null) {
+            mHomeBanner.setActive(item == mDefaultPageIndex);
+        }
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             // Drop the soft keyboard by stealing focus from the URL bar.
             requestFocus();
         }
@@ -295,32 +299,40 @@ public class HomePager extends ViewPager
             // Restore original background.
             setBackgroundDrawable(mOriginalBackground);
         }
 
         // Re-install the adapter with the final state
         // in the pager.
         setAdapter(adapter);
 
-        // Use the default panel as defined in the HomePager's configuration
-        // if the initial panel wasn't explicitly set by the load() caller,
-        // or if the initial panel is not found in the adapter.
-        final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
-        if (itemPosition > -1) {
-            setCurrentItem(itemPosition, false);
-            mInitialPanelId = null;
+        if (count == 0) {
+            mDefaultPageIndex = -1;
+
+            // Hide the banner if there are no enabled panels.
+            if (mHomeBanner != null) {
+                mHomeBanner.setActive(false);
+            }
         } else {
             for (int i = 0; i < count; i++) {
-                final PanelConfig panelConfig = enabledPanels.get(i);
-                if (panelConfig.isDefault()) {
+                if (enabledPanels.get(i).isDefault()) {
                     mDefaultPageIndex = i;
-                    setCurrentItem(i, false);
                     break;
                 }
             }
+
+            // Use the default panel if the initial panel wasn't explicitly set by the
+            // load() caller, or if the initial panel is not found in the adapter.
+            final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
+            if (itemPosition > -1) {
+                setCurrentItem(itemPosition, false);
+                mInitialPanelId = null;
+            } else {
+                setCurrentItem(mDefaultPageIndex, false);
+            }
         }
     }
 
     private class ConfigLoaderCallbacks implements LoaderCallbacks<HomeConfig.State> {
         @Override
         public Loader<HomeConfig.State> onCreateLoader(int id, Bundle args) {
             return new HomeConfigLoader(mContext, mConfig);
         }
--- a/mobile/android/base/home/HomePanelPicker.java
+++ b/mobile/android/base/home/HomePanelPicker.java
@@ -14,16 +14,17 @@ import android.support.v4.app.LoaderMana
 import android.support.v4.content.Loader;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
@@ -130,21 +131,30 @@ public class HomePanelPicker extends Fra
 
         final PickerAdapter adapter = (PickerAdapter) mListView.getAdapter();
         adapter.updateFromPanelInfos(availablePanels);
     }
 
     private void installNewPanelAndQuit(PanelInfo panelInfo) {
         final PanelConfig newPanelConfig = panelInfo.toPanelConfig();
         HomeConfigInvalidator.getInstance().installPanel(newPanelConfig);
+        showToastForNewPanel(newPanelConfig);
 
         setResult(Activity.RESULT_OK);
         finish();
     }
 
+    private void showToastForNewPanel(PanelConfig panelConfig) {
+        String panelName = panelConfig.getTitle();
+
+        // Display toast.
+        final String successMsg = getResources().getString(R.string.home_add_panel_installed, panelName);
+        Toast.makeText(this, successMsg, Toast.LENGTH_SHORT).show();
+    }
+
     // ViewHolder for rows of the panel picker.
     private static class PanelRow {
         final TextView title;
         int position;
 
         public PanelRow(View view) {
             title = (TextView) view.findViewById(R.id.title);
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -88,16 +88,20 @@
 <!ENTITY pref_developer_remotedebugging_docs "Learn more">
 <!ENTITY pref_remember_signons "Remember passwords">
 
 <!ENTITY pref_category_home "Home">
 <!ENTITY pref_category_home_panels "Panels">
 <!ENTITY pref_home_add_panel "Add panel">
 <!ENTITY home_add_panel_title "Add new panel">
 <!ENTITY home_add_panel_empty "Sorry, we couldn\'t find any panels for you to add.">
+<!-- Localization note (home_add_panel_installed):
+     The &formatS; will be replaced with the name of the new panel the user just
+     selected to be added to the home page. -->
+<!ENTITY home_add_panel_installed "\'&formatS;\' added to homepage">
 <!ENTITY pref_category_home_content_settings "Content settings">
 <!ENTITY pref_home_updates "Automatic updates">
 <!ENTITY pref_home_updates_enabled "Enabled">
 <!ENTITY pref_home_updates_wifi "Only over Wi-Fi">
 
 <!-- Localization note: These are shown in the left sidebar on tablets -->
 <!ENTITY pref_header_customize "Customize">
 <!ENTITY pref_header_display "Display">
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -70,16 +70,17 @@ public class GeckoPreferences
 
     private static final String NON_PREF_PREFIX = "android.not_a_preference.";
     public static final String INTENT_EXTRA_RESOURCES = "resource";
     public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled";
 
     private static boolean sIsCharEncodingEnabled = false;
     private boolean mInitialized = false;
     private int mPrefsRequestId = 0;
+    private PanelsPreferenceCategory mPanelsPreferenceCategory;
 
     // These match keys in resources/xml*/preferences*.xml
     private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
     private static final String PREFS_HOME_ADD_PANEL = NON_PREF_PREFIX + "home.add_panel";
     private static final String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled";
     private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
     private static final String PREFS_TELEMETRY_ENABLED = "datareporting.telemetry.enabled";
     private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled";
@@ -274,19 +275,18 @@ public class GeckoPreferences
                   setResult(RESULT_CODE_EXIT_SETTINGS);
                   finish();
               }
               break;
 
           case HomePanelPicker.REQUEST_CODE_ADD_PANEL:
               switch (resultCode) {
                   case Activity.RESULT_OK:
-                      // XXX: Bug 976925 - UI after adding a panel.
-                      setResult(RESULT_CODE_EXIT_SETTINGS);
-                      finish();
+                     // Panel installed, refresh panels list.
+                     mPanelsPreferenceCategory.refresh();
                       break;
                   case Activity.RESULT_CANCELED:
                       // Dialog was cancelled, do nothing.
                       break;
                   default:
                       Log.w(LOGTAG, "Unhandled ADD_PANEL result code " + requestCode);
                       break;
               }
@@ -343,16 +343,18 @@ public class GeckoPreferences
             if (pref instanceof PreferenceGroup) {
                 // If no datareporting is enabled, remove UI.
                 if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) {
                     if (!AppConstants.MOZ_DATA_REPORTING) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
+                } else if (pref instanceof PanelsPreferenceCategory) {
+                    mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref;
                 }
                 setupPreferences((PreferenceGroup) pref, prefs);
             } else {
                 pref.setOnPreferenceChangeListener(this);
                 if (!AppConstants.MOZ_UPDATER &&
                     PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
                     preferences.removePreference(pref);
                     i--;
--- a/mobile/android/base/preferences/PanelsPreferenceCategory.java
+++ b/mobile/android/base/preferences/PanelsPreferenceCategory.java
@@ -64,16 +64,31 @@ public class PanelsPreferenceCategory ex
             public void onPostExecute(HomeConfig.State configState) {
                 mConfigEditor = configState.edit();
                 displayHomeConfig(configState);
             }
         };
         mLoadTask.execute();
     }
 
+    /**
+     * Reload the Home Panels list from HomeConfig.
+     */
+    public void refresh() {
+        // Clear all the existing home panels, but leave the
+        // first item (Add panels).
+        int prefCount = getPreferenceCount();
+        while (prefCount > 1) {
+            removePreference(getPreference(1));
+            prefCount--;
+        }
+
+        loadHomeConfig();
+    }
+
     private void displayHomeConfig(HomeConfig.State configState) {
         for (PanelConfig panelConfig : configState) {
             // Create and add the pref.
             final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this);
             pref.setTitle(panelConfig.getTitle());
             pref.setKey(panelConfig.getId());
             // XXX: Pull icon from PanelInfo.
             addPreference(pref);
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -111,16 +111,17 @@
   <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
   <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
 
   <string name="pref_category_home">&pref_category_home;</string>
   <string name="pref_category_home_panels">&pref_category_home_panels;</string>
   <string name="pref_home_add_panel">&pref_home_add_panel;</string>
   <string name="home_add_panel_title">&home_add_panel_title;</string>
   <string name="home_add_panel_empty">&home_add_panel_empty;</string>
+  <string name="home_add_panel_installed">&home_add_panel_installed;</string>
   <string name="pref_category_home_content_settings">&pref_category_home_content_settings;</string>
   <string name="pref_home_updates">&pref_home_updates;</string>
   <string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>
   <string name="pref_home_updates_wifi">&pref_home_updates_wifi;</string>
 
   <string name="pref_header_customize">&pref_header_customize;</string>
   <string name="pref_header_display">&pref_header_display;</string>
   <string name="pref_header_privacy_short">&pref_header_privacy_short;</string>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -7948,17 +7948,17 @@ var ExternalApps = {
         if (apps.length > 1) {
           // Use the HelperApps prompt here to filter out any Http handlers
           HelperApps.prompt(apps, {
             title: Strings.browser.GetStringFromName("openInApp.pageAction"),
             buttons: [
               Strings.browser.GetStringFromName("openInApp.ok"),
               Strings.browser.GetStringFromName("openInApp.cancel")
             ]
-          }, function(result) {
+          }, (result) => {
             if (result.button != 0) {
               return;
             }
             apps[result.icongrid0].launch(this._pageActionUri);
           });
         } else {
           apps[0].launch(this._pageActionUri);
         }
--- a/services/common/tests/unit/test_hawkclient.js
+++ b/services/common/tests/unit/test_hawkclient.js
@@ -337,16 +337,17 @@ add_task(function test_multiple_401_retr
   };
 
   // We begin with no offset
   do_check_eq(client.localtimeOffsetMsec, 0);
 
   // Request will have bad timestamp; client will retry once
   try {
     yield client.request("/maybe", method, credentials);
+    do_throw("Expected an error");
   } catch (err) {
     do_check_eq(err.code, 401);
   }
   do_check_eq(attempts, 2);
 
   yield deferredStop(server);
 });
 
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -62,32 +62,32 @@ let publicProperties = [
 AccountState = function(fxaInternal) {
   this.fxaInternal = fxaInternal;
 };
 
 AccountState.prototype = {
   cert: null,
   keyPair: null,
   signedInUser: null,
-  whenVerifiedPromise: null,
-  whenKeysReadyPromise: null,
+  whenVerifiedDeferred: null,
+  whenKeysReadyDeferred: null,
 
   get isCurrent() this.fxaInternal && this.fxaInternal.currentAccountState === this,
 
   abort: function() {
-    if (this.whenVerifiedPromise) {
-      this.whenVerifiedPromise.reject(
+    if (this.whenVerifiedDeferred) {
+      this.whenVerifiedDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
-      this.whenVerifiedPromise = null;
+      this.whenVerifiedDeferred = null;
     }
 
-    if (this.whenKeysReadyPromise) {
-      this.whenKeysReadyPromise.reject(
+    if (this.whenKeysReadyDeferred) {
+      this.whenKeysReadyDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
-      this.whenKeysReadyPromise = null;
+      this.whenKeysReadyDeferred = null;
     }
     this.cert = null;
     this.keyPair = null;
     this.signedInUser = null;
     this.fxaInternal = null;
   },
 
   getUserAccountData: function() {
@@ -479,23 +479,23 @@ FxAccountsInternal.prototype = {
     let currentState = this.currentAccountState;
     return currentState.getUserAccountData().then((data) => {
       if (!data) {
         throw new Error("Can't get keys; User is not signed in");
       }
       if (data.kA && data.kB) {
         return data;
       }
-      if (!currentState.whenKeysReadyPromise) {
-        currentState.whenKeysReadyPromise = Promise.defer();
+      if (!currentState.whenKeysReadyDeferred) {
+        currentState.whenKeysReadyDeferred = Promise.defer();
         this.fetchAndUnwrapKeys(data.keyFetchToken).then(data => {
-          currentState.whenKeysReadyPromise.resolve(data);
+          currentState.whenKeysReadyDeferred.resolve(data);
         });
       }
-      return currentState.whenKeysReadyPromise.promise;
+      return currentState.whenKeysReadyDeferred.promise;
     }).then(result => currentState.resolve(result));
    },
 
   fetchAndUnwrapKeys: function(keyFetchToken) {
     log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
     let currentState = this.currentAccountState;
     return Task.spawn(function* task() {
       // Sign out if we don't have a key fetch token.
@@ -602,21 +602,21 @@ FxAccountsInternal.prototype = {
   },
 
   whenVerified: function(data) {
     let currentState = this.currentAccountState;
     if (data.verified) {
       log.debug("already verified");
       return currentState.resolve(data);
     }
-    if (!currentState.whenVerifiedPromise) {
+    if (!currentState.whenVerifiedDeferred) {
       log.debug("whenVerified promise starts polling for verified email");
       this.pollEmailStatus(currentState, data.sessionToken, "start");
     }
-    return currentState.whenVerifiedPromise.promise.then(
+    return currentState.whenVerifiedDeferred.promise.then(
       result => currentState.resolve(result)
     );
   },
 
   notifyObservers: function(topic) {
     log.debug("Notifying observers of " + topic);
     Services.obs.notifyObservers(null, topic, null);
   },
@@ -624,53 +624,53 @@ FxAccountsInternal.prototype = {
   // XXX - pollEmailStatus should maybe be on the AccountState object?
   pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) {
     log.debug("entering pollEmailStatus: " + why);
     if (why == "start") {
       // If we were already polling, stop and start again.  This could happen
       // if the user requested the verification email to be resent while we
       // were already polling for receipt of an earlier email.
       this.pollTimeRemaining = this.POLL_SESSION;
-      if (!currentState.whenVerifiedPromise) {
-        currentState.whenVerifiedPromise = Promise.defer();
+      if (!currentState.whenVerifiedDeferred) {
+        currentState.whenVerifiedDeferred = Promise.defer();
       }
     }
 
     this.checkEmailStatus(sessionToken)
       .then((response) => {
         log.debug("checkEmailStatus -> " + JSON.stringify(response));
         if (response && response.verified) {
           // Bug 947056 - Server should be able to tell FxAccounts.jsm to back
           // off or stop polling altogether
           currentState.getUserAccountData()
             .then((data) => {
               data.verified = true;
               return currentState.setUserAccountData(data);
             })
             .then((data) => {
               // Now that the user is verified, we can proceed to fetch keys
-              if (currentState.whenVerifiedPromise) {
-                currentState.whenVerifiedPromise.resolve(data);
-                delete currentState.whenVerifiedPromise;
+              if (currentState.whenVerifiedDeferred) {
+                currentState.whenVerifiedDeferred.resolve(data);
+                delete currentState.whenVerifiedDeferred;
               }
             });
         } else {
           log.debug("polling with step = " + this.POLL_STEP);
           this.pollTimeRemaining -= this.POLL_STEP;
           log.debug("time remaining: " + this.pollTimeRemaining);
           if (this.pollTimeRemaining > 0) {
             this.currentTimer = setTimeout(() => {
               this.pollEmailStatus(currentState, sessionToken, "timer")}, this.POLL_STEP);
             log.debug("started timer " + this.currentTimer);
           } else {
-            if (currentState.whenVerifiedPromise) {
-              currentState.whenVerifiedPromise.reject(
+            if (currentState.whenVerifiedDeferred) {
+              currentState.whenVerifiedDeferred.reject(
                 new Error("User email verification timed out.")
               );
-              delete currentState.whenVerifiedPromise;
+              delete currentState.whenVerifiedDeferred;
             }
           }
         }
       });
     },
 
   // Return the URI of the remote UI flows.
   getAccountsURI: function() {
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -1,40 +1,61 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-crypto/utils.js");
 
 const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
 
 function run_test() {
   run_next_test();
 }
 
+// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
+let ACCOUNT_KEYS = {
+  keyFetch:     h("8081828384858687 88898a8b8c8d8e8f"+
+                  "9091929394959697 98999a9b9c9d9e9f"),
+
+  response:     h("ee5c58845c7c9412 b11bbd20920c2fdd"+
+                  "d83c33c9cd2c2de2 d66b222613364636"+
+                  "c2c0f8cfbb7c6304 72c0bd88451342c6"+
+                  "c05b14ce342c5ad4 6ad89e84464c993c"+
+                  "3927d30230157d08 17a077eef4b20d97"+
+                  "6f7a97363faf3f06 4c003ada7d01aa70"),
+
+  kA:           h("2021222324252627 28292a2b2c2d2e2f"+
+                  "3031323334353637 38393a3b3c3d3e3f"),
+
+  wrapKB:       h("4041424344454647 48494a4b4c4d4e4f"+
+                  "5051525354555657 58595a5b5c5d5e5f"),
+};
+
+// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
+let SESSION_KEYS = {
+  sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
+                  "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
+
+  tokenID:      h("c0a29dcf46174973 da1378696e4c82ae"+
+                  "10f723cf4f4d9f75 e39f4ae3851595ab"),
+
+  reqHMACkey:   h("9d8f22998ee7f579 8b887042466b72d5"+
+                  "3e56ab0c094388bf 65831f702d2febc0"),
+};
+
 function deferredStop(server) {
   let deferred = Promise.defer();
   server.stop(deferred.resolve);
   return deferred.promise;
 }
 
-add_test(function test_hawk_credentials() {
-  let client = new FxAccountsClient();
-
-  let sessionToken = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
-  let result = client._deriveHawkCredentials(sessionToken, "session");
-
-  do_check_eq(result.id, "639503a218ffbb62983e9628be5cd64a0438d0ae81b2b9dadeb900a83470bc6b");
-  do_check_eq(CommonUtils.bytesAsHex(result.key), "3a0188943837ab228fe74e759566d0e4837cbcc7494157aac4da82025b2811b2");
-
-  run_next_test();
-});
-
 add_task(function test_authenticated_get_request() {
   let message = "{\"msg\": \"Great Success!\"}";
   let credentials = {
     id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
     key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
     algorithm: "sha256"
   };
   let method = "GET";
@@ -89,16 +110,17 @@ add_task(function test_500_error() {
       response.bodyOutputStream.write(message, message.length);
     }
   });
 
   let client = new FxAccountsClient(server.baseURI);
 
   try {
     yield client._request("/foo", method);
+    do_throw("Expected to catch an exception");
   } catch (e) {
     do_check_eq(500, e.code);
     do_check_eq("Internal Server Error", e.message);
   }
 
   yield deferredStop(server);
 });
 
@@ -166,68 +188,75 @@ add_task(function test_signUp() {
       // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
       do_check_eq(jsonBody.email, "andré@example.org");
 
       if (!created) {
         do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
         created = true;
 
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(creationMessage, creationMessage.length);
+        response.bodyOutputStream.write(creationMessage, creationMessage.length);
+        return;
       }
 
       // Error trying to create same account a second time
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signUp('andré@example.org', 'pässwörd');
   do_check_eq("uid", result.uid);
   do_check_eq("sessionToken", result.sessionToken);
   do_check_eq("keyFetchToken", result.keyFetchToken);
 
   // Try to create account again.  Triggers error path.
   try {
     result = yield client.signUp('andré@example.org', 'pässwörd');
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(101, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signIn() {
   let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
+
   let server = httpd_setup({
     "/account/login": function(request, response) {
       let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
       let jsonBody = JSON.parse(body);
 
       if (jsonBody.email == "mé@example.com") {
         do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
+        response.bodyOutputStream.write(sessionMessage, sessionMessage.length);
+        return;
       }
 
       // Error trying to sign in to nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signIn('mé@example.com', 'bigsecret');
   do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
 
   // Trigger error path
   try {
     result = yield client.signIn("yøü@bad.example.org", "nofear");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signOut() {
@@ -236,32 +265,35 @@ add_task(function test_signOut() {
   let signedOut = false;
 
   let server = httpd_setup({
     "/session/destroy": function(request, response) {
       if (!signedOut) {
         signedOut = true;
         do_check_true(request.hasHeader("Authorization"));
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
+        response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
+        return;
       }
 
       // Error trying to sign out of nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signOut("FakeSession");
   do_check_eq(typeof result, "object");
 
   // Trigger error path
   try {
     result = yield client.signOut("FakeSession");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_recoveryEmailStatus() {
@@ -269,93 +301,87 @@ add_task(function test_recoveryEmailStat
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let tries = 0;
 
   let server = httpd_setup({
     "/recovery_email/status": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
 
       if (tries === 0) {
+        tries += 1;
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(emailStatus, emailStatus.length);
+        response.bodyOutputStream.write(emailStatus, emailStatus.length);
+        return;
       }
 
       // Second call gets an error trying to query a nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN);
   do_check_eq(result.verified, true);
 
   // Trigger error path
   try {
     result = yield client.recoveryEmailStatus("some bogus session");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_resendVerificationEmail() {
   let emptyMessage = "{}";
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let tries = 0;
 
   let server = httpd_setup({
     "/recovery_email/resend_code": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
       if (tries === 0) {
+        tries += 1;
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
+        response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
+        return;
       }
 
       // Second call gets an error trying to query a nonexistent account
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN);
   do_check_eq(JSON.stringify(result), emptyMessage);
 
   // Trigger error path
   try {
     result = yield client.resendVerificationEmail("some bogus session");
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_accountKeys() {
-  // Vectors: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
-
-  let keyFetch = h("8081828384858687 88898a8b8c8d8e8f"+
-                   "9091929394959697 98999a9b9c9d9e9f");
-
-  let response = h("ee5c58845c7c9412 b11bbd20920c2fdd"+
-                   "d83c33c9cd2c2de2 d66b222613364636"+
-                   "c2c0f8cfbb7c6304 72c0bd88451342c6"+
-                   "c05b14ce342c5ad4 6ad89e84464c993c"+
-                   "3927d30230157d08 17a077eef4b20d97"+
-                   "6f7a97363faf3f06 4c003ada7d01aa70");
-
-  let kA =       h("2021222324252627 28292a2b2c2d2e2f"+
-                   "3031323334353637 38393a3b3c3d3e3f");
-
-  let wrapKB =   h("4041424344454647 48494a4b4c4d4e4f"+
-                   "5051525354555657 58595a5b5c5d5e5f");
-
-  let responseMessage = JSON.stringify({bundle: response});
+  // Four calls to accountKeys().  The first one should work correctly, and we
+  // should get a valid bundle back, in exchange for our keyFetch token, from
+  // which we correctly derive kA and wrapKB.  The subsequent three calls
+  // should all trigger separate error paths.
+  let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
   let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
   let emptyMessage = "{}";
   let attempt = 0;
 
   let server = httpd_setup({
     "/account/keys": function(request, response) {
       do_check_true(request.hasHeader("Authorization"));
       attempt += 1;
@@ -370,55 +396,60 @@ add_task(function test_accountKeys() {
         case 2:
           // Second time, return no bundle to trigger client error
           response.setStatusLine(request.httpVersion, 200, "OK");
           response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
           break;
 
         case 3:
           // Return gibberish to trigger client MAC error
-          let garbage = response;
-          garbage[0] = 0; // tweak a byte
+          // Tweak a byte
+          let garbageResponse = JSON.stringify({
+            bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
+          });
           response.setStatusLine(request.httpVersion, 200, "OK");
-          response.bodyOutputStream.write(responseMessage, responseMessage.length);
+          response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
           break;
 
         case 4:
           // Trigger error for nonexistent account
           response.setStatusLine(request.httpVersion, 400, "Bad request");
           response.bodyOutputStream.write(errorMessage, errorMessage.length);
           break;
       }
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
 
   // First try, all should be good
-  let result = yield client.accountKeys(keyFetch);
-  do_check_eq(CommonUtils.hexToBytes(kA), result.kA);
-  do_check_eq(CommonUtils.hexToBytes(wrapKB), result.wrapKB);
+  let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
+  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
 
   // Second try, empty bundle should trigger error
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(expectedError.message, "failed to retrieve keys");
   }
 
   // Third try, bad bundle results in MAC error
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(expectedError.message, "error unbundling encryption keys");
   }
 
   // Fourth try, pretend account doesn't exist
   try {
-    result = yield client.accountKeys(keyFetch);
+    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_signCertificate() {
@@ -432,32 +463,35 @@ add_task(function test_signCertificate()
 
       if (tries === 0) {
         tries += 1;
         let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
         let jsonBody = JSON.parse(body);
         do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
         do_check_eq(jsonBody.duration, 600);
         response.setStatusLine(request.httpVersion, 200, "OK");
-        return response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
+        response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
+        return;
       }
 
       // Second attempt, trigger error
       response.setStatusLine(request.httpVersion, 400, "Bad request");
-      return response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      response.bodyOutputStream.write(errorMessage, errorMessage.length);
+      return;
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600);
   do_check_eq("baz", result.bar);
 
   // Account doesn't exist
   try {
     result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
+    do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_accountExists() {
@@ -497,37 +531,28 @@ add_task(function test_accountExists() {
           break;
       }
     },
   });
 
   let client = new FxAccountsClient(server.baseURI);
   let result;
 
-  try {
-    result = yield client.accountExists("i.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.code, 400);
-    do_check_eq(expectedError.errno, 103);
-  }
+  result = yield client.accountExists("i.exist@example.com");
+  do_check_true(result);
 
-  try {
-    result = yield client.accountExists("i.also.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.errno, 103);
-  }
+  result = yield client.accountExists("i.also.exist@example.com");
+  do_check_true(result);
 
-  try {
-    result = yield client.accountExists("i.dont.exist@example.com");
-  } catch(expectedError) {
-    do_check_eq(expectedError.errno, 102);
-  }
+  result = yield client.accountExists("i.dont.exist@example.com");
+  do_check_false(result);
 
   try {
     result = yield client.accountExists("i.break.things@example.com");
+    do_throw("Expected to catch an exception");
   } catch(unexpectedError) {
     do_check_eq(unexpectedError.code, 500);
   }
 
   yield deferredStop(server);
 });
 
 add_task(function test_email_case() {
@@ -578,12 +603,23 @@ add_task(function test_email_case() {
 
   let result = yield client.signIn(clientEmail, "123456");
   do_check_eq(result.areWeHappy, "yes");
   do_check_eq(attempts, 2);
 
   yield deferredStop(server);
 });
 
+add_task(function test__deriveHawkCredentials() {
+  let client = new FxAccountsClient("https://example.org");
+
+  let credentials = client._deriveHawkCredentials(
+    SESSION_KEYS.sessionToken, "sessionToken");
+
+  do_check_eq(credentials.algorithm, "sha256");
+  do_check_eq(credentials.id, SESSION_KEYS.tokenID);
+  do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
+});
+
 // turn formatted test vectors into normal hex strings
 function h(hexStr) {
   return hexStr.replace(/\s+/g, "");
 }
--- a/toolkit/components/osfile/modules/ospath_unix.jsm
+++ b/toolkit/components/osfile/modules/ospath_unix.jsm
@@ -74,24 +74,27 @@ exports.dirname = dirname;
  * in a directory.
  *
  * Example: Obtaining $TMP/foo/bar in an OS-independent manner
  *  var tmpDir = OS.Constants.Path.tmpDir;
  *  var path = OS.Path.join(tmpDir, "foo", "bar");
  *
  * Under Unix, this will return "/tmp/foo/bar".
  */
-let join = function(path /*...*/) {
+let join = function(...path) {
   // If there is a path that starts with a "/", eliminate everything before
   let paths = [];
-  for each(let i in arguments) {
-    if (i.length != 0 && i[0] == "/") {
-      paths = [i];
+  for (let subpath of path) {
+    if (subpath == null) {
+      throw new TypeError("invalid path component");
+    }
+    if (subpath.length != 0 && subpath[0] == "/") {
+      paths = [subpath];
     } else {
-      paths.push(i);
+      paths.push(subpath);
     }
   }
   return paths.join("/");
 };
 exports.join = join;
 
 /**
  * Normalize a path by removing any unneeded ".", "..", "//".
--- a/toolkit/components/osfile/modules/ospath_win.jsm
+++ b/toolkit/components/osfile/modules/ospath_win.jsm
@@ -135,17 +135,20 @@ exports.dirname = dirname;
  *  var path = OS.Path.join(tmpDir, "foo", "bar");
  *
  * Under Windows, this will return "$TMP\foo\bar".
  */
 let join = function(...path) {
   let paths = [];
   let root;
   let absolute = false;
-  for each(let subpath in path) {
+  for (let subpath of path) {
+    if (subpath == null) {
+      throw new TypeError("invalid path component");
+    }
     let drive = this.winGetDrive(subpath);
     if (drive) {
       root = drive;
       let component = trimBackslashes(subpath.slice(drive.length));
       if (component) {
         paths = [component];
       } else {
         paths = [];
@@ -174,16 +177,20 @@ exports.join = join;
  * Return the drive name of a path, or |null| if the path does
  * not contain a drive name.
  *
  * Drive name appear either as "DriveName:..." (the return drive
  * name includes the ":") or "\\\\DriveName..." (the returned drive name
  * includes "\\\\").
  */
 let winGetDrive = function(path) {
+  if (path == null) {
+    throw new TypeError("path is invalid");
+  }
+
   if (path.startsWith("\\\\")) {
     // UNC path
     if (path.length == 2) {
       return null;
     }
     let index = path.indexOf("\\", 2);
     if (index == -1) {
       return path;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4896,16 +4896,30 @@
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADDETACH_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
   },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_ADDONDETACH_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_ADDONDETACH_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
+  },
   "DEVTOOLS_DEBUGGER_RDP_LOCAL_TABDETACH_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_TABDETACH_MS": {
--- a/toolkit/devtools/apps/app-actor-front.js
+++ b/toolkit/devtools/apps/app-actor-front.js
@@ -254,17 +254,17 @@ function getTargetForApp(client, webapps
 exports.getTargetForApp = getTargetForApp;
 
 function reloadApp(client, webappsActor, manifestURL) {
   let deferred = promise.defer();
   getTargetForApp(client,
                   webappsActor,
                   manifestURL).
     then((target) => {
-      // Request the ContentAppActor to reload the app
+      // Request the ContentActor to reload the app
       let request = {
         to: target.form.actor,
         type: "reload",
         manifestURL: manifestURL
       };
       client.request(request, (res) => {
         deferred.resolve();
       });
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -232,16 +232,17 @@ const UnsolicitedPauses = {
  */
 this.DebuggerClient = function (aTransport)
 {
   this._transport = aTransport;
   this._transport.hooks = this;
 
   // Map actor ID to client instance for each actor type.
   this._threadClients = new Map;
+  this._addonClients = new Map;
   this._tabClients = new Map;
   this._tracerClients = new Map;
   this._consoleClients = new Map;
 
   this._pendingRequests = [];
   this._activeRequests = new Map;
   this._eventsEnabled = true;
 
@@ -408,18 +409,20 @@ DebuggerClient.prototype = {
           }
         });
       }
     };
 
     detachClients(this._consoleClients, () => {
       detachClients(this._threadClients, () => {
         detachClients(this._tabClients, () => {
-          this._transport.close();
-          this._transport = null;
+          detachClients(this._addonClients, () => {
+            this._transport.close();
+            this._transport = null;
+          });
         });
       });
     });
   },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listTabs()'.
@@ -462,16 +465,41 @@ DebuggerClient.prototype = {
         tabClient = new TabClient(this, aResponse);
         this._tabClients.set(aTabActor, tabClient);
       }
       aOnResponse(aResponse, tabClient);
     });
   },
 
   /**
+   * Attach to an addon actor.
+   *
+   * @param string aAddonActor
+   *        The actor ID for the addon to attach.
+   * @param function aOnResponse
+   *        Called with the response packet and a AddonClient
+   *        (which will be undefined on error).
+   */
+  attachAddon: function DC_attachAddon(aAddonActor, aOnResponse) {
+    let packet = {
+      to: aAddonActor,
+      type: "attach"
+    };
+    this.request(packet, aResponse => {
+      let addonClient;
+      if (!aResponse.error) {
+        addonClient = new AddonClient(this, aAddonActor);
+        this._addonClients[aAddonActor] = addonClient;
+        this.activeAddon = addonClient;
+      }
+      aOnResponse(aResponse, addonClient);
+    });
+  },
+
+  /**
    * Attach to a Web Console actor.
    *
    * @param string aConsoleActor
    *        The ID for the console actor to attach to.
    * @param array aListeners
    *        The console listeners you want to start.
    * @param function aOnResponse
    *        Called with the response packet and a WebConsoleClient
@@ -738,17 +766,22 @@ DebuggerClient.prototype = {
     return pool ? pool.get(actorID) : null;
   },
 
   poolFor: function (actorID) {
     for (let pool of this._pools) {
       if (pool.has(actorID)) return pool;
     }
     return null;
-  }
+  },
+
+  /**
+   * Currently attached addon.
+   */
+  activeAddon: null
 }
 
 eventSource(DebuggerClient.prototype);
 
 // Constants returned by `FeatureCompatibilityShim.onPacketTest`.
 const SUPPORTED = 1;
 const NOT_SUPPORTED = 2;
 const SKIP = 3;
@@ -1044,16 +1077,46 @@ TabClient.prototype = {
     options: args(0)
   }, {
     telemetry: "RECONFIGURETAB"
   }),
 };
 
 eventSource(TabClient.prototype);
 
+function AddonClient(aClient, aActor) {
+  this._client = aClient;
+  this._actor = aActor;
+  this.request = this._client.request;
+}
+
+AddonClient.prototype = {
+  get actor() { return this._actor; },
+  get _transport() { return this._client._transport; },
+
+  /**
+   * Detach the client from the addon actor.
+   *
+   * @param function aOnResponse
+   *        Called with the response packet.
+   */
+  detach: DebuggerClient.requester({
+    type: "detach"
+  }, {
+    after: function(aResponse) {
+      if (this._client.activeAddon === this._client._addonClients[this.actor]) {
+        this._client.activeAddon = null
+      }
+      delete this._client._addonClients[this.actor];
+      return aResponse;
+    },
+    telemetry: "ADDONDETACH"
+  })
+};
+
 /**
  * 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
  * 'listChildProcesses' methods forward to that root actor.
  *
  * @param aClient object
  *      The client connection to which this actor belongs.
--- a/toolkit/devtools/server/actors/childtab.js
+++ b/toolkit/devtools/server/actors/childtab.js
@@ -2,67 +2,57 @@
  * 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";
 
 /**
  * Tab actor for documents living in a child process.
  *
- * Depends on BrowserTabActor, defined in webbrowser.js actor.
+ * Depends on TabActor, defined in webbrowser.js.
  */
 
 /**
  * Creates a tab actor for handling requests to the single tab, like
- * attaching and detaching. ContentAppActor respects the actor factories
+ * attaching and detaching. ContentActor respects the actor factories
  * registered with DebuggerServer.addTabActor.
  *
  * @param connection DebuggerServerConnection
  *        The conection to the client.
- * @param browser browser
- *        The browser instance that contains this tab.
+ * @param chromeGlobal
+ *        The content script global holding |content| and |docShell| properties for a tab.
  */
-function ContentAppActor(connection, browser)
+function ContentActor(connection, chromeGlobal)
 {
-  BrowserTabActor.call(this, connection, browser);
+  TabActor.call(this, connection, chromeGlobal);
+  this._chromeGlobal = chromeGlobal;
 }
 
-ContentAppActor.prototype = Object.create(BrowserTabActor.prototype);
+ContentActor.prototype = Object.create(TabActor.prototype);
 
-ContentAppActor.prototype.constructor = ContentAppActor;
+ContentActor.prototype.constructor = ContentActor;
 
-Object.defineProperty(ContentAppActor.prototype, "title", {
+Object.defineProperty(ContentActor.prototype, "docShell", {
   get: function() {
-    return this.browser.title;
+    return this._chromeGlobal.docShell;
   },
   enumerable: true,
   configurable: false
 });
 
-Object.defineProperty(ContentAppActor.prototype, "url", {
-  get: function() {
-    return this.browser.document.documentURI;
-  },
-  enumerable: true,
-  configurable: false
-});
-
-Object.defineProperty(ContentAppActor.prototype, "window", {
-  get: function() {
-    return this.browser;
-  },
-  enumerable: true,
-  configurable: false
-});
+ContentActor.prototype.exit = function() {
+  TabActor.prototype.exit.call(this);
+  this._chromeGlobal = null;
+};
 
 // Override grip just to rename this._tabActorPool to this._tabActorPool2
 // in order to prevent it to be cleaned on detach.
-// We have to keep tab actors alive as we keep the ContentAppActor
+// We have to keep tab actors alive as we keep the ContentActor
 // alive after detach and reuse it for multiple debug sessions.
-ContentAppActor.prototype.grip = function () {
+ContentActor.prototype.grip = function () {
   let response = {
     'actor': this.actorID,
     'title': this.title,
     'url': this.url
   };
 
   // Walk over tab actors added by extensions and add them to a new ActorPool.
   let actorPool = new ActorPool(this.conn);
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -182,26 +182,26 @@ let HighlighterActor = protocol.ActorCla
     return this._walker.attachElement(node);
   },
 
   /**
    * Get the right target for listening to mouse events while in pick mode.
    * - On a firefox desktop content page: tabActor is a BrowserTabActor from
    *   which the browser property will give us a target we can use to listen to
    *   events, even in nested iframes.
-   * - On B2G: tabActor is a ContentAppActor which doesn't have a browser but
+   * - On B2G: tabActor is a ContentActor which doesn't have a browser but
    *   since it overrides BrowserTabActor, it does get a browser property
    *   anyway, which points to its window object.
    * - When using the Browser Toolbox (to inspect firefox desktop): tabActor is
    *   the RootActor, in which case, the window property can be used to listen
    *   to events
    */
   _getPickerListenerTarget: function() {
     let actor = this._tabActor;
-    return actor.isRootActor ? actor.window : actor.browser;
+    return actor.isRootActor ? actor.window : actor.chromeEventHandler;
   },
 
   _startPickerListeners: function() {
     let target = this._getPickerListenerTarget();
     target.addEventListener("mousemove", this._onHovered, true);
     target.addEventListener("click", this._onPick, true);
     target.addEventListener("mousedown", this._preventContentEvent, true);
     target.addEventListener("mouseup", this._preventContentEvent, true);
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -4557,16 +4557,106 @@ update(ChromeDebuggerActor.prototype, {
         type: "newGlobal",
         // TODO: after bug 801084 lands see if we need to JSONify this.
         hostAnnotations: aGlobal.hostAnnotations
       });
     }
   }
 });
 
+/**
+ * Creates an actor for handling add-on debugging. AddonThreadActor is
+ * a thin wrapper over ThreadActor.
+ *
+ * @param aConnection object
+ *        The DebuggerServerConnection with which this AddonThreadActor
+ *        is associated. (Currently unused, but required to make this
+ *        constructor usable with addGlobalActor.)
+ *
+ * @param aHooks object
+ *        An object with preNest and postNest methods for calling
+ *        when entering and exiting a nested event loops.
+ *
+ * @param aAddonID string
+ *        ID of the add-on this actor will debug. It will be used to
+ *        filter out globals marked for debugging.
+ */
+
+function AddonThreadActor(aConnect, aHooks, aAddonID) {
+  this.addonID = aAddonID;
+  ThreadActor.call(this, aHooks);
+}
+
+AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
+
+update(AddonThreadActor.prototype, {
+  constructor: AddonThreadActor,
+
+  // A constant prefix that will be used to form the actor ID by the server.
+  actorPrefix: "addonThread",
+
+  /**
+   * Override the eligibility check for scripts and sources to make
+   * sure every script and source with a URL is stored when debugging
+   * add-ons.
+   */
+  _allowSource: (aSourceURL) => !!aSourceURL,
+
+  /**
+   * An object that will be used by ThreadActors to tailor their
+   * behaviour depending on the debugging context being required (chrome,
+   * addon or content). The methods that this object provides must
+   * be bound to the ThreadActor before use.
+   */
+  globalManager: {
+    findGlobals: function ADA_findGlobals() {
+      for (let global of this.dbg.findAllGlobals()) {
+        if (this._checkGlobal(global)) {
+          this.dbg.addDebuggee(global);
+        }
+      }
+    },
+
+    /**
+     * A function that the engine calls when a new global object
+     * has been created.
+     *
+     * @param aGlobal Debugger.Object
+     *        The new global object that was created.
+     */
+    onNewGlobal: function ADA_onNewGlobal(aGlobal) {
+      if (this._checkGlobal(aGlobal)) {
+        this.addDebuggee(aGlobal);
+        // Notify the client.
+        this.conn.send({
+          from: this.actorID,
+          type: "newGlobal",
+          // TODO: after bug 801084 lands see if we need to JSONify this.
+          hostAnnotations: aGlobal.hostAnnotations
+        });
+      }
+    }
+  },
+
+  /**
+   * Checks if the provided global belongs to the debugged add-on.
+   *
+   * @param aGlobal Debugger.Object
+   */
+  _checkGlobal: function ADA_checkGlobal(aGlobal) {
+    let metadata;
+    try {
+      // This will fail for non-Sandbox objects, hence the try-catch block.
+      metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
+    } catch (e) {
+    }
+
+    return metadata && metadata.addonID === this.addonID;
+  }
+});
 
 /**
  * Manages the sources for a thread. Handles source maps, locations in the
  * sources, etc for ThreadActors.
  */
 function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate,
                        aOnNewSource) {
   this._thread = aThreadActor;
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -115,17 +115,17 @@ function WebappsActor(aConnection) {
 
   Cu.import("resource://gre/modules/Webapps.jsm");
   Cu.import("resource://gre/modules/AppsUtils.jsm");
   Cu.import("resource://gre/modules/FileUtils.jsm");
   Cu.import('resource://gre/modules/Services.jsm');
   promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
 
   // Keep reference of already created app actors.
-  // key: app frame message manager, value: ContentTabActor's grip() value
+  // key: app frame message manager, value: ContentActor's grip() value
   this._appActorsMap = new Map();
 
   this.conn = aConnection;
   this._uploads = [];
   this._actorPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._actorPool);
 }
 
@@ -784,83 +784,16 @@ WebappsActor.prototype = {
       }));
     }
 
     return promise.all(appPromises).then(() => {
       return { apps: apps };
     });
   },
 
-  _connectToApp: function (aFrame) {
-    let deferred = Promise.defer();
-
-    let mm = aFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
-    mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
-
-    let childTransport, prefix;
-
-    let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
-      mm.removeMessageListener("debug:actor", onActorCreated);
-
-      dump("***** Got debug:actor\n");
-      let { actor, appId } = msg.json;
-      prefix = msg.json.prefix;
-
-      // Pipe Debugger message from/to parent/child via the message manager
-      childTransport = new ChildDebuggerTransport(mm, prefix);
-      childTransport.hooks = {
-        onPacket: this.conn.send.bind(this.conn),
-        onClosed: function () {}
-      };
-      childTransport.ready();
-
-      this.conn.setForwarding(prefix, childTransport);
-
-      debug("establishing forwarding for app with prefix " + prefix);
-
-      this._appActorsMap.set(mm, actor);
-
-      deferred.resolve(actor);
-    }).bind(this);
-    mm.addMessageListener("debug:actor", onActorCreated);
-
-    let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
-      if (subject == mm) {
-        Services.obs.removeObserver(onMessageManagerDisconnect, topic);
-        if (childTransport) {
-          // If we have a child transport, the actor has already
-          // been created. We need to stop using this message manager.
-          childTransport.close();
-          this.conn.cancelForwarding(prefix);
-        } else {
-          // Otherwise, the app has been closed before the actor
-          // had a chance to be created, so we are not able to create
-          // the actor.
-          deferred.resolve(null);
-        }
-        let actor = this._appActorsMap.get(mm);
-        if (actor) {
-          // The ContentAppActor within the child process doesn't necessary
-          // have to time to uninitialize itself when the app is closed/killed.
-          // So ensure telling the client that the related actor is detached.
-          this.conn.send({ from: actor.actor,
-                           type: "tabDetached" });
-          this._appActorsMap.delete(mm);
-        }
-      }
-    }).bind(this);
-    Services.obs.addObserver(onMessageManagerDisconnect,
-                             "message-manager-disconnect", false);
-
-    let prefixStart = this.conn.prefix + "child";
-    mm.sendAsyncMessage("debug:connect", { prefix: prefixStart });
-
-    return deferred.promise;
-  },
-
   getAppActor: function ({ manifestURL }) {
     debug("getAppActor\n");
 
     let appFrame = null;
     for each (let frame in this._appFrames()) {
       if (frame.getAttribute("mozapp") == manifestURL) {
         appFrame = frame;
         break;
@@ -879,23 +812,31 @@ WebappsActor.prototype = {
 
     return this._isAppAllowedForURL(manifestURL).then(allowed => {
       if (!allowed) {
         return notFoundError;
       }
 
       // Only create a new actor, if we haven't already
       // instanciated one for this connection.
+      let map = this._appActorsMap;
       let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
                        .frameLoader
                        .messageManager;
-      let actor = this._appActorsMap.get(mm);
+      let actor = map.get(mm);
       if (!actor) {
-        return this._connectToApp(appFrame)
-                   .then(function (actor) ({ actor: actor }));
+        let onConnect = actor => {
+          map.set(mm, actor);
+          return { actor: actor };
+        };
+        let onDisconnect = mm => {
+          map.delete(mm);
+        };
+        return DebuggerServer.connectToChild(this.conn, mm, onDisconnect)
+                             .then(onConnect);
       }
 
       return { actor: actor };
     });
   },
 
   watchApps: function () {
     this._openedApps = new Set();
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -212,49 +212,61 @@ BrowserTabList.prototype.getList = funct
   let initialMapSize = this._actorByBrowser.size;
   let foundCount = 0;
 
   // To avoid mysterious behavior if tabs are closed or opened mid-iteration,
   // we update the map first, and then make a second pass over it to yield
   // the actors. Thus, the sequence yielded is always a snapshot of the
   // actors that were live when we began the iteration.
 
+  let actorPromises = [];
+
   // Iterate over all navigator:browser XUL windows.
   for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
     let selectedBrowser = this._getSelectedBrowser(win);
     if (!selectedBrowser) {
       continue;
     }
 
     // For each tab in this XUL window, ensure that we have an actor for
     // it, reusing existing actors where possible. We actually iterate
     // over 'browser' XUL elements, and BrowserTabActor uses
     // browser.contentWindow.wrappedJSObject as the debuggee global.
     for (let browser of this._getChildren(win)) {
       // Do we have an existing actor for this browser? If not, create one.
       let actor = this._actorByBrowser.get(browser);
       if (actor) {
+        actorPromises.push(promise.resolve(actor));
         foundCount++;
+      } else if (browser.isRemoteBrowser) {
+        actor = new RemoteBrowserTabActor(this._connection, browser);
+        this._actorByBrowser.set(browser, actor);
+        let promise = actor.connect().then((form) => {
+          actor._form = form;
+          return actor;
+        });
+        actorPromises.push(promise);
       } else {
         actor = new BrowserTabActor(this._connection, browser, win.gBrowser);
         this._actorByBrowser.set(browser, actor);
+        actorPromises.push(promise.resolve(actor));
       }
 
       // Set the 'selected' properties on all actors correctly.
       actor.selected = (win === topXULWindow && browser === selectedBrowser);
     }
   }
 
   if (this._testing && initialMapSize !== foundCount)
     throw Error("_actorByBrowser map contained actors for dead tabs");
 
   this._mustNotify = true;
   this._checkListening();
 
-  return promise.resolve([actor for ([_, actor] of this._actorByBrowser)]);
+  return promise.all(actorPromises);
 };
 
 Object.defineProperty(BrowserTabList.prototype, 'onListChanged', {
   enumerable: true, configurable:true,
   get: function() { return this._onListChanged; },
   set: function(v) {
     if (v !== null && typeof v !== 'function') {
       throw Error("onListChanged property may only be set to 'null' or a function");
@@ -456,116 +468,128 @@ BrowserTabList.prototype.onCloseWindow =
         this._handleActorClose(actor, browser);
       }
     }
   }, "BrowserTabList.prototype.onCloseWindow's delayed body"), 0);
 }, "BrowserTabList.prototype.onCloseWindow");
 
 /**
  * Creates a tab actor for handling requests to a browser tab, like
- * attaching and detaching. BrowserTabActor respects the actor factories
+ * attaching and detaching. TabActor respects the actor factories
  * registered with DebuggerServer.addTabActor.
  *
+ * This class is subclassed by BrowserTabActor and
+ * ContentActor. Subclasses are expected to implement a getter
+ * the docShell properties.
+ *
  * @param aConnection DebuggerServerConnection
  *        The conection to the client.
- * @param aBrowser browser
- *        The browser instance that contains this tab.
- * @param aTabBrowser tabbrowser
- *        The tabbrowser that can receive nsIWebProgressListener events.
+ * @param aChromeEventHandler
+ *        An object on which listen for DOMWindowCreated and pageshow events.
  */
-function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
+function TabActor(aConnection, aChromeEventHandler)
 {
   this.conn = aConnection;
-  this._browser = aBrowser;
-  this._tabbrowser = aTabBrowser;
+  this._chromeEventHandler = aChromeEventHandler;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
   this._extraActors = {};
 
   this._onWindowCreated = this.onWindowCreated.bind(this);
 }
 
-// XXX (bug 710213): BrowserTabActor attach/detach/exit/disconnect is a
+// XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
 // *complete* mess, needs to be rethought asap.
 
-BrowserTabActor.prototype = {
-  get browser() { return this._browser; },
-
-  get exited() { return !this.browser; },
+TabActor.prototype = {
+  get exited() { return !this._chromeEventHandler; },
   get attached() { return !!this._attached; },
 
   _tabPool: null,
   get tabActorPool() { return this._tabPool; },
 
   _contextPool: null,
   get contextActorPool() { return this._contextPool; },
 
   _pendingNavigation: null,
 
   // A constant prefix that will be used to form the actor ID by the server.
   actorPrefix: "tab",
 
   /**
+   * An object on which listen for DOMWindowCreated and pageshow events.
+   */
+  get chromeEventHandler() {
+    return this._chromeEventHandler;
+  },
+
+  /**
+   * Getter for the tab's doc shell.
+   */
+  get docShell() {
+    throw "The docShell getter should be implemented by a subclass of TabActor";
+  },
+
+  /**
+   * Getter for the tab content's DOM window.
+   */
+  get window() {
+    return this.docShell
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindow);
+  },
+
+  /**
+   * Getter for the nsIWebProgress for watching this window.
+   */
+  get webProgress() {
+    return this.docShell
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebProgress);
+  },
+
+  /**
+   * Getter for the nsIWebNavigation for the tab.
+   */
+  get webNavigation() {
+    return this.docShell
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebNavigation);
+  },
+
+  /**
+   * Getter for the tab's document.
+   */
+  get contentDocument() {
+    return this.webNavigation.document;
+  },
+
+  /**
    * Getter for the tab title.
    * @return string
    *         Tab title.
    */
   get title() {
-    let title = this.browser.contentTitle;
-    // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
-    // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
-    // as the title.
-    if (!title && this._tabbrowser) {
-      title = this._tabbrowser
-                  ._getTabForContentWindow(this.window).label;
-    }
-    return title;
+    return this.contentDocument.contentTitle;
   },
 
   /**
    * Getter for the tab URL.
    * @return string
    *         Tab URL.
    */
   get url() {
-    if (this.browser.currentURI) {
-      return this.browser.currentURI.spec;
+    if (this.webNavigation.currentURI) {
+      return this.webNavigation.currentURI.spec;
     }
     // Abrupt closing of the browser window may leave callbacks without a
     // currentURI.
     return null;
   },
 
-  /**
-   * Getter for the tab content window, will be used by child actors to target
-   * the right window.
-   * @return nsIDOMWindow
-   *         Tab content window.
-   */
-  get window() {
-    if (this.browser instanceof Ci.nsIDOMWindow) {
-      return this.browser;
-    } else if (this.browser instanceof Ci.nsIDOMElement) {
-      return this.browser.contentWindow;
-    } else {
-      return null;
-    }
-  },
-
-  /**
-   * Getter for the best nsIWebProgress for to watching this window.
-   */
-  get webProgress() {
-    return this.window
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDocShell)
-      .QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIWebProgress);
-  },
-
   form: function BTA_form() {
     dbg_assert(!this.exited,
                "grip() shouldn't be called on exited browser actor.");
     dbg_assert(this.actorID,
                "tab should have an actorID.");
 
     let windowUtils = this.window
       .QueryInterface(Ci.nsIInterfaceRequestor)
@@ -606,18 +630,17 @@ BrowserTabActor.prototype = {
       return;
     }
 
     if (this._detach()) {
       this.conn.send({ from: this.actorID,
                        type: "tabDetached" });
     }
 
-    this._browser = null;
-    this._tabbrowser = null;
+    this._chromeEventHandler = null;
   },
 
   /* Support for DebuggerServer.addTabActor. */
   _createExtraActors: CommonCreateExtraActors,
   _appendExtraActors: CommonAppendExtraActors,
 
   /**
    * Does the actual work of attching to a tab.
@@ -631,21 +654,19 @@ BrowserTabActor.prototype = {
     dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
     this._tabPool = new ActorPool(this.conn);
     this.conn.addActorPool(this._tabPool);
 
     // ... and a pool for context-lifetime actors.
     this._pushContext();
 
     // Watch for globals being created in this tab.
-    this.browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
-    this.browser.addEventListener("pageshow", this._onWindowCreated, true);
-    if (this._tabbrowser) {
-      this._progressListener = new DebuggerProgressListener(this);
-    }
+    this.chromeEventHandler.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
+    this.chromeEventHandler.addEventListener("pageshow", this._onWindowCreated, true);
+    this._progressListener = new DebuggerProgressListener(this);
 
     this._attached = true;
   },
 
   /**
    * Creates a thread actor and a pool for context-lifetime actors. It then sets
    * up the content window for debugging.
    */
@@ -677,22 +698,20 @@ BrowserTabActor.prototype = {
    *
    * @returns false if the tab wasn't attached or true of detahing succeeds.
    */
   _detach: function BTA_detach() {
     if (!this.attached) {
       return false;
     }
 
-    if (this._progressListener) {
-      this._progressListener.destroy();
-    }
+    this._progressListener.destroy();
 
-    this.browser.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
-    this.browser.removeEventListener("pageshow", this._onWindowCreated, true);
+    this.chromeEventHandler.removeEventListener("DOMWindowCreated", this._onWindowCreated, true);
+    this.chromeEventHandler.removeEventListener("pageshow", this._onWindowCreated, true);
 
     this._popContext();
 
     // Shut down actors that belong to this tab's pool.
     this.conn.removeActorPool(this._tabPool);
     this._tabPool = null;
     if (this._tabActorPool) {
       this.conn.removeActorPool(this._tabActorPool, true);
@@ -731,29 +750,29 @@ BrowserTabActor.prototype = {
   /**
    * Reload the page in this tab.
    */
   onReload: function(aRequest) {
     // Wait a tick so that the response packet can be dispatched before the
     // subsequent navigation event packet.
     Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
       this.window.location.reload();
-    }, "BrowserTabActor.prototype.onReload's delayed body"), 0);
+    }, "TabActor.prototype.onReload's delayed body"), 0);
     return {};
   },
 
   /**
    * Navigate this tab to a new location
    */
   onNavigateTo: function(aRequest) {
     // Wait a tick so that the response packet can be dispatched before the
     // subsequent navigation event packet.
     Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
       this.window.location = aRequest.url;
-    }, "BrowserTabActor.prototype.onNavigateTo's delayed body"), 0);
+    }, "TabActor.prototype.onNavigateTo's delayed body"), 0);
     return {};
   },
 
   /**
    * Reconfigure options.
    */
   onReconfigure: function (aRequest) {
     let options = aRequest.options || {};
@@ -793,70 +812,54 @@ BrowserTabActor.prototype = {
 
   /**
    * Disable or enable the cache via docShell.
    */
   _setCacheEnabled: function(allow) {
     let enable =  Ci.nsIRequest.LOAD_NORMAL;
     let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
                   Ci.nsIRequest.INHIBIT_CACHING;
-    if (this.window) {
-      let docShell = this.window
-                         .QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDocShell);
-
-      docShell.defaultLoadFlags = allow ? enable : disable;
+    if (this.docShell) {
+      this.docShell.defaultLoadFlags = allow ? enable : disable;
     }
   },
 
   /**
    * Disable or enable JS via docShell.
    */
   _setJavascriptEnabled: function(allow) {
-    if (this.window) {
-      let docShell = this.window
-                         .QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDocShell);
-
-      docShell.allowJavascript = allow;
+    if (this.docShell) {
+      this.docShell.allowJavascript = allow;
     }
   },
 
   /**
    * Return cache allowed status.
    */
   _getCacheEnabled: function() {
-    if (!this.window) {
+    if (!this.docShell) {
       // The tab is already closed.
       return null;
     }
 
     let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
                   Ci.nsIRequest.INHIBIT_CACHING;
-    let docShell = this.window
-                       .QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell);
-
-    return docShell.defaultLoadFlags !== disable;
+    return this.docShell.defaultLoadFlags !== disable;
   },
 
   /**
    * Return JS allowed status.
    */
   _getJavascriptEnabled: function() {
-    if (!this.window) {
+    if (!this.docShell) {
       // The tab is already closed.
       return null;
     }
 
-    let docShell = this.window
-                       .QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell);
-
-    return docShell.allowJavascript;
+    return this.docShell.allowJavascript;
   },
 
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest: function BTA_preNest() {
     if (!this.window) {
       // The tab is already closed.
@@ -895,31 +898,31 @@ BrowserTabActor.prototype = {
    */
   onWindowCreated:
   DevToolsUtils.makeInfallible(function BTA_onWindowCreated(evt) {
     // 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 ) {
+    if (evt.target === this.contentDocument) {
       this.threadActor.clearDebuggees();
       if (this.threadActor.dbg) {
         this.threadActor.dbg.enabled = true;
         this.threadActor.global = evt.target.defaultView.wrappedJSObject;
         this.threadActor.maybePauseOnExceptions();
       }
     }
 
     // Refresh the debuggee list when a new window object appears (top window or
     // iframe).
     if (this.threadActor.attached) {
       this.threadActor.findGlobals();
     }
-  }, "BrowserTabActor.prototype.onWindowCreated"),
+  }, "TabActor.prototype.onWindowCreated"),
 
   /**
    * Tells if the window.console object is native or overwritten by script in
    * the page.
    *
    * @param nsIDOMWindow aWindow
    *        The window object you want to check.
    * @return boolean
@@ -930,22 +933,110 @@ BrowserTabActor.prototype = {
     // loaded after the BrowserTabActor
     return WebConsoleActor.prototype.hasNativeConsoleAPI(aWindow);
   }
 };
 
 /**
  * The request types this actor can handle.
  */
-BrowserTabActor.prototype.requestTypes = {
-  "attach": BrowserTabActor.prototype.onAttach,
-  "detach": BrowserTabActor.prototype.onDetach,
-  "reload": BrowserTabActor.prototype.onReload,
-  "navigateTo": BrowserTabActor.prototype.onNavigateTo,
-  "reconfigure": BrowserTabActor.prototype.onReconfigure
+TabActor.prototype.requestTypes = {
+  "attach": TabActor.prototype.onAttach,
+  "detach": TabActor.prototype.onDetach,
+  "reload": TabActor.prototype.onReload,
+  "navigateTo": TabActor.prototype.onNavigateTo,
+  "reconfigure": TabActor.prototype.onReconfigure
+};
+
+/**
+ * Creates a tab actor for handling requests to a single in-process
+ * <browser> tab. Most of the implementation comes from TabActor.
+ *
+ * @param aConnection DebuggerServerConnection
+ *        The conection to the client.
+ * @param aBrowser browser
+ *        The browser instance that contains this tab.
+ * @param aTabBrowser tabbrowser
+ *        The tabbrowser that can receive nsIWebProgressListener events.
+ */
+function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
+{
+  TabActor.call(this, aConnection, aBrowser);
+  this._browser = aBrowser;
+  this._tabbrowser = aTabBrowser;
+}
+
+BrowserTabActor.prototype = Object.create(TabActor.prototype);
+
+BrowserTabActor.prototype.constructor = BrowserTabActor;
+
+Object.defineProperty(BrowserTabActor.prototype, "docShell", {
+  get: function() {
+    return this._browser.docShell;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+Object.defineProperty(BrowserTabActor.prototype, "title", {
+  get: function() {
+    let title = this.contentDocument.contentTitle;
+    // If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
+    // tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
+    // as the title.
+    if (!title && this._tabbrowser) {
+      title = this._tabbrowser._getTabForContentWindow(this.window).label;
+    }
+    return title;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+Object.defineProperty(BrowserTabActor.prototype, "browser", {
+  get: function() {
+    return this._browser;
+  },
+  enumerable: true,
+  configurable: false
+});
+
+BrowserTabActor.prototype.exit = function() {
+  TabActor.prototype.exit.call(this);
+  this._browser = null;
+  this._tabbrowser = null;
+};
+
+/**
+ * This actor is a shim that connects to a ContentActor in a remote
+ * browser process. All RDP packets get forwarded using the message
+ * manager.
+ *
+ * @param aConnection The main RDP connection.
+ * @param aBrowser XUL <browser> element to connect to.
+ */
+function RemoteBrowserTabActor(aConnection, aBrowser)
+{
+  this._conn = aConnection;
+  this._browser = aBrowser;
+  this._form = null;
+}
+
+RemoteBrowserTabActor.prototype = {
+  connect: function() {
+    return DebuggerServer.connectToChild(this._conn, this._browser.messageManager);
+  },
+
+  form: function() {
+    return this._form;
+  },
+
+  exit: function() {
+    this._browser = null;
+  },
 };
 
 function BrowserAddonList(aConnection)
 {
   this._connection = aConnection;
   this._actorByAddonId = new Map();
   this._onListChanged = null;
 }
@@ -988,30 +1079,40 @@ BrowserAddonList.prototype.onInstalled =
 BrowserAddonList.prototype.onUninstalled = function (aAddon) {
   this._actorByAddonId.delete(aAddon.id);
   this._onListChanged();
 };
 
 function BrowserAddonActor(aConnection, aAddon) {
   this.conn = aConnection;
   this._addon = aAddon;
+  this._contextPool = null;
+  this._threadActor = null;
   AddonManager.addAddonListener(this);
 }
 
 BrowserAddonActor.prototype = {
   actorPrefix: "addon",
 
+  get exited() {
+    return !this._addon;
+  },
+
   get id() {
     return this._addon.id;
   },
 
   get url() {
     return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
   },
 
+  get attached() {
+    return this._threadActor;
+  },
+
   form: function BAA_form() {
     dbg_assert(this.actorID, "addon should have an actorID.");
 
     return {
       actor: this.actorID,
       id: this.id,
       url: this.url
     };
@@ -1019,38 +1120,107 @@ BrowserAddonActor.prototype = {
 
   disconnect: function BAA_disconnect() {
     AddonManager.removeAddonListener(this);
   },
 
   onUninstalled: function BAA_onUninstalled(aAddon) {
     if (aAddon != this._addon)
       return;
+
+    if (this.attached) {
+      this.onDetach();
+      this.conn.send({ from: this.actorID, type: "tabDetached" });
+    }
+
     this._addon = null;
     AddonManager.removeAddonListener(this);
   },
+
+  onAttach: function BAA_onAttach() {
+    if (this.exited) {
+      return { type: "exited" };
+    }
+
+    if (!this.attached) {
+      this._contextPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._contextPool);
+
+      this._threadActor = new AddonThreadActor(this.conn, this,
+                                               this._addon.id);
+      this._contextPool.addActor(this._threadActor);
+    }
+
+    return { type: "tabAttached", threadActor: this._threadActor.actorID };
+  },
+
+  onDetach: function BAA_onDetach() {
+    if (!this.attached) {
+      return { error: "wrongState" };
+    }
+
+    this.conn.removeActorPool(this._contextPool);
+    this._contextPool = null;
+
+    this._threadActor = null;
+
+    return { type: "detached" };
+  },
+
+  preNest: function() {
+    let e = Services.wm.getEnumerator(null);
+    while (e.hasMoreElements()) {
+      let win = e.getNext();
+      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+      windowUtils.suppressEventHandling(true);
+      windowUtils.suspendTimeouts();
+    }
+  },
+
+  postNest: function() {
+    let e = Services.wm.getEnumerator(null);
+    while (e.hasMoreElements()) {
+      let win = e.getNext();
+      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+      windowUtils.resumeTimeouts();
+      windowUtils.suppressEventHandling(false);
+    }
+  }
+};
+
+BrowserAddonActor.prototype.requestTypes = {
+  "attach": BrowserAddonActor.prototype.onAttach,
+  "detach": BrowserAddonActor.prototype.onDetach
 };
 
 /**
  * The DebuggerProgressListener object is an nsIWebProgressListener which
  * handles onStateChange events for the inspected browser. If the user tries to
  * navigate away from a paused page, the listener makes sure that the debuggee
  * is resumed before the navigation begins.
  *
- * @param BrowserTabActor aBrowserTabActor
+ * @param TabActor aTabActor
  *        The tab actor associated with this listener.
  */
-function DebuggerProgressListener(aBrowserTabActor) {
-  this._tabActor = aBrowserTabActor;
-  this._tabActor._tabbrowser.addProgressListener(this);
+function DebuggerProgressListener(aTabActor) {
+  this._tabActor = aTabActor;
+  this._tabActor.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
   let EventEmitter = devtools.require("devtools/toolkit/event-emitter");
   EventEmitter.decorate(this);
 }
 
 DebuggerProgressListener.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIWebProgressListener,
+    Ci.nsISupportsWeakReference,
+    Ci.nsISupports,
+  ]),
+
   onStateChange:
   DevToolsUtils.makeInfallible(function DPL_onStateChange(aProgress, aRequest, aFlag, aStatus) {
     let isStart = aFlag & Ci.nsIWebProgressListener.STATE_START;
     let isStop = aFlag & Ci.nsIWebProgressListener.STATE_STOP;
     let isDocument = aFlag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     let isNetwork = aFlag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
     let isRequest = aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
     let isWindow = aFlag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
@@ -1098,19 +1268,18 @@ DebuggerProgressListener.prototype = {
       this.emit("navigate", packet);
     }
   }, "DebuggerProgressListener.prototype.onStateChange"),
 
   /**
    * Destroy the progress listener instance.
    */
   destroy: function DPL_destroy() {
-    if (this._tabActor._tabbrowser.removeProgressListener) {
-      try {
-        this._tabActor._tabbrowser.removeProgressListener(this);
-      } catch (ex) {
-        // This can throw during browser shutdown.
-      }
+    try {
+      this._tabActor.webProgress.removeProgressListener(this);
+    } catch (ex) {
+      // This can throw during browser shutdown.
     }
+
     this._tabActor._progressListener = null;
     this._tabActor = null;
   }
 };
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -1,14 +1,16 @@
 /* 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";
 
+let chromeGlobal = this;
+
 // Encapsulate in its own scope to allows loading this frame script
 // more than once.
 (function () {
   let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
   const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 
   if (!DebuggerServer.initialized) {
@@ -21,24 +23,20 @@
   // time we load child.js
   DebuggerServer.addChildActors();
 
   let onConnect = DevToolsUtils.makeInfallible(function (msg) {
     removeMessageListener("debug:connect", onConnect);
 
     let mm = msg.target;
 
-    let prefix = msg.data.prefix + docShell.appId;
+    let conn = DebuggerServer.connectToParent(msg.data.prefix, mm);
 
-    let conn = DebuggerServer.connectToParent(prefix, mm);
-
-    let actor = new DebuggerServer.ContentAppActor(conn, content);
+    let actor = new DebuggerServer.ContentActor(conn, chromeGlobal);
     let actorPool = new ActorPool(conn);
     actorPool.addActor(actor);
     conn.addActorPool(actorPool);
 
-    sendAsyncMessage("debug:actor", {actor: actor.grip(),
-                                     appId: docShell.appId,
-                                     prefix: prefix});
+    sendAsyncMessage("debug:actor", {actor: actor.grip()});
   });
 
   addMessageListener("debug:connect", onConnect);
 })();
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -33,16 +33,17 @@ Object.defineProperty(this, "Components"
 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
 
 const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
 Cu.import("resource://gre/modules/reflect.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource://gre/modules/jsdebugger.jsm");
 addDebuggerToGlobal(this);
 
 function loadSubScript(aURL)
 {
   try {
     let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Ci.mozIJSSubScriptLoader);
@@ -370,17 +371,17 @@ var DebuggerServer = {
     // but childtab.js hasn't been loaded yet.
     if (!("WebConsoleActor" in this)) {
       this.addTabActors();
     }
     // But webbrowser.js and childtab.js aren't loaded from shell.js.
     if (!("BrowserTabActor" in this)) {
       this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
     }
-    if (!("ContentAppActor" in this)) {
+    if (!("ContentActor" in this)) {
       this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
     }
   },
 
   /**
    * Install tab actors.
    */
   addTabActors: function() {
@@ -519,16 +520,81 @@ var DebuggerServer = {
    */
   connectToParent: function(aPrefix, aMessageManager) {
     this._checkInit();
 
     let transport = new ChildDebuggerTransport(aMessageManager, aPrefix);
     return this._onConnection(transport, aPrefix, true);
   },
 
+  connectToChild: function(aConnection, aMessageManager, aOnDisconnect) {
+    let deferred = Promise.defer();
+
+    let mm = aMessageManager;
+    mm.loadFrameScript("resource://gre/modules/devtools/server/child.js", false);
+
+    let actor, childTransport;
+    let prefix = aConnection.allocID("child");
+
+    let onActorCreated = DevToolsUtils.makeInfallible(function (msg) {
+      mm.removeMessageListener("debug:actor", onActorCreated);
+
+      // Pipe Debugger message from/to parent/child via the message manager
+      childTransport = new ChildDebuggerTransport(mm, prefix);
+      childTransport.hooks = {
+        onPacket: aConnection.send.bind(aConnection),
+        onClosed: function () {}
+      };
+      childTransport.ready();
+
+      aConnection.setForwarding(prefix, childTransport);
+
+      dumpn("establishing forwarding for app with prefix " + prefix);
+
+      actor = msg.json.actor;
+
+      deferred.resolve(actor);
+    }).bind(this);
+    mm.addMessageListener("debug:actor", onActorCreated);
+
+    let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
+      if (subject == mm) {
+        Services.obs.removeObserver(onMessageManagerDisconnect, topic);
+        if (childTransport) {
+          // If we have a child transport, the actor has already
+          // been created. We need to stop using this message manager.
+          childTransport.close();
+          aConnection.cancelForwarding(prefix);
+        } else {
+          // Otherwise, the app has been closed before the actor
+          // had a chance to be created, so we are not able to create
+          // the actor.
+          deferred.resolve(null);
+        }
+        if (actor) {
+          // The ContentActor within the child process doesn't necessary
+          // have to time to uninitialize itself when the app is closed/killed.
+          // So ensure telling the client that the related actor is detached.
+          aConnection.send({ from: actor.actor, type: "tabDetached" });
+          actor = null;
+        }
+
+        if (aOnDisconnect) {
+          aOnDisconnect(mm);
+        }
+      }
+    }).bind(this);
+    Services.obs.addObserver(onMessageManagerDisconnect,
+                             "message-manager-disconnect", false);
+
+    mm.sendAsyncMessage("debug:connect", { prefix: prefix });
+
+    return deferred.promise;
+  },
+
   // nsIServerSocketListener implementation
 
   onSocketAccepted:
   DevToolsUtils.makeInfallible(function DS_onSocketAccepted(aSocket, aTransport) {
     if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") && !this._allowConnection()) {
       return;
     }
     dumpn("New debugging connection on " + aTransport.host + ":" + aTransport.port);
--- a/toolkit/locales/en-US/chrome/places/places.properties
+++ b/toolkit/locales/en-US/chrome/places/places.properties
@@ -25,8 +25,12 @@ finduri-MonthYear=%1$S %2$S
 localhost=(local files)
 
 # LOCALIZATION NOTE
 # The string is used for showing file size of each backup in the "fileRestorePopup" popup
 # %1$S is the file size
 # %2$S is the file size unit
 backupFileSizeText=%1$S %2$S
 
+# LOCALIZATION NOTE (windows8TouchTitle): this is the name of the folder used
+# to store bookmarks created in Metro mode and share bookmarks between Metro
+# and Desktop.
+windows8TouchTitle=Windows 8 Touch
\ No newline at end of file