Bug 1240907 - Flatten RemoteBrowserTabActor into BrowserTabActor. r=ochameau
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 06 Jul 2016 16:17:03 -0500
changeset 346232 88d6dfa406b85488a60819029a8d4c1dab19f96a
parent 346231 7b8dcecb19336ebb963750fb35968bb840e34e35
child 346233 02016837381082d203059b4cc193f5f4ced3ef50
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1240907
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1240907 - Flatten RemoteBrowserTabActor into BrowserTabActor. r=ochameau The removes the legacy path for non-e10s that avoids the messageManager. We now use the messageManager for all cases, both e10s off and on. Unsurprisingly, this revealed a variety of race conditions in various tests, so they've been cleaned up as well. MozReview-Commit-ID: EXEWehphLIY
browser/base/content/nsContextMenu.js
devtools/client/canvasdebugger/canvasdebugger.js
devtools/client/canvasdebugger/test/head.js
devtools/client/commandline/test/browser.ini
devtools/client/commandline/test/browser_cmd_appcache_valid.js
devtools/client/commandline/test/browser_cmd_highlight_02.js
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
devtools/client/storage/test/head.js
devtools/client/storage/test/storage-listings.html
devtools/client/storage/test/storage-secured-iframe.html
devtools/client/storage/test/storage-unsecured-iframe.html
devtools/client/webaudioeditor/controller.js
devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js
devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js
devtools/client/webaudioeditor/test/browser_callwatcher-01.js
devtools/client/webaudioeditor/test/browser_callwatcher-02.js
devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js
devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js
devtools/client/webaudioeditor/test/browser_wa_controller-01.js
devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js
devtools/client/webaudioeditor/test/browser_wa_graph-click.js
devtools/client/webaudioeditor/test/browser_wa_graph-markers.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js
devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js
devtools/client/webaudioeditor/test/browser_wa_graph-selected.js
devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js
devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js
devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js
devtools/client/webaudioeditor/test/browser_wa_inspector-width.js
devtools/client/webaudioeditor/test/browser_wa_inspector.js
devtools/client/webaudioeditor/test/browser_wa_navigate.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js
devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
devtools/client/webaudioeditor/test/browser_wa_properties-view.js
devtools/client/webaudioeditor/test/browser_wa_reset-02.js
devtools/client/webaudioeditor/test/browser_wa_reset-03.js
devtools/client/webaudioeditor/test/head.js
devtools/client/webconsole/test/browser.ini
devtools/client/webide/test/browser_tabs.js
devtools/client/webide/test/head.js
devtools/client/webide/test/test_autoconnect_runtime.html
devtools/client/webide/test/test_autoselect_project.html
devtools/client/webide/test/test_device_permissions.html
devtools/client/webide/test/test_device_preferences.html
devtools/client/webide/test/test_device_runtime.html
devtools/client/webide/test/test_device_settings.html
devtools/client/webide/test/test_fullscreenToolbox.html
devtools/client/webide/test/test_runtime.html
devtools/client/webide/test/test_telemetry.html
devtools/client/webide/test/test_toolbox.html
devtools/server/actors/call-watcher.js
devtools/server/actors/childtab.js
devtools/server/actors/director-manager.js
devtools/server/actors/director-registry.js
devtools/server/actors/root.js
devtools/server/actors/utils/actor-registry-utils.js
devtools/server/actors/webaudio.js
devtools/server/actors/webbrowser.js
devtools/server/actors/webconsole.js
devtools/server/child.js
devtools/server/content-server.jsm
devtools/server/main.js
devtools/server/tests/browser/browser.ini
devtools/server/tests/browser/browser_navigateEvents.js
devtools/server/tests/mochitest/inspector-helpers.js
devtools/server/tests/mochitest/test_animation_actor-lifetime.html
devtools/server/tests/mochitest/test_inspector-anonymous.html
devtools/server/tests/mochitest/test_inspector-dead-nodes.html
devtools/server/tests/mochitest/test_inspector-search.html
devtools/server/tests/mochitest/test_setupInParentChild.html
devtools/server/tests/unit/test_client_request.js
devtools/shared/client/main.js
devtools/shared/fronts/inspector.js
devtools/shared/transport/transport.js
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -592,24 +592,20 @@ nsContextMenu.prototype = {
 
     return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       let inspector = toolbox.getCurrentPanel();
 
       // new-node-front tells us when the node has been selected, whether the
       // browser is remote or not.
       let onNewNode = inspector.selection.once("new-node-front");
 
-      if (this.isRemote) {
-        this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
-        inspector.walker.findInspectingNode().then(nodeFront => {
-          inspector.selection.setNodeFront(nodeFront, "browser-context-menu");
-        });
-      } else {
-        inspector.selection.setNode(this.target, "browser-context-menu");
-      }
+      this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
+      inspector.walker.findInspectingNode().then(nodeFront => {
+        inspector.selection.setNodeFront(nodeFront, "browser-context-menu");
+      });
 
       return onNewNode.then(() => {
         // Now that the node has been selected, wait until the inspector is
         // fully updated.
         return inspector.once("inspector-updated");
       });
     });
   },
--- a/devtools/client/canvasdebugger/canvasdebugger.js
+++ b/devtools/client/canvasdebugger/canvasdebugger.js
@@ -123,16 +123,22 @@ function shutdownCanvasDebugger() {
 /**
  * Functions handling target-related lifetime events.
  */
 var EventsHandler = {
   /**
    * Listen for events emitted by the current tab target.
    */
   initialize: function () {
+    // Make sure the backend is prepared to handle <canvas> contexts.
+    // Since actors are created lazily on the first request to them, we need to send an
+    // early request to ensure the CallWatcherActor is running and watching for new window
+    // globals.
+    gFront.setup({ reload: false });
+
     this._onTabNavigated = this._onTabNavigated.bind(this);
     gTarget.on("will-navigate", this._onTabNavigated);
     gTarget.on("navigate", this._onTabNavigated);
   },
 
   /**
    * Remove events emitted by the current tab target.
    */
@@ -143,18 +149,16 @@ var EventsHandler = {
 
   /**
    * Called for each location change in the debugged tab.
    */
   _onTabNavigated: function (event) {
     if (event != "will-navigate") {
       return;
     }
-    // Make sure the backend is prepared to handle <canvas> contexts.
-    gFront.setup({ reload: false });
 
     // Reset UI.
     SnapshotsListView.empty();
     CallsListView.empty();
 
     $("#record-snapshot").removeAttribute("checked");
     $("#record-snapshot").removeAttribute("disabled");
     $("#record-snapshot").hidden = false;
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -153,16 +153,17 @@ function once(aTarget, aEventName, aUseC
 
   for (let [add, remove] of [
     ["on", "off"], // Use event emitter before DOM events for consistency
     ["addEventListener", "removeEventListener"],
     ["addListener", "removeListener"]
   ]) {
     if ((add in aTarget) && (remove in aTarget)) {
       aTarget[add](aEventName, function onEvent(...aArgs) {
+        info("Got event: '" + aEventName + "' on " + aTarget + ".");
         aTarget[remove](aEventName, onEvent, aUseCapture);
         deferred.resolve(...aArgs);
       }, aUseCapture);
       break;
     }
   }
 
   return deferred.promise;
--- a/devtools/client/commandline/test/browser.ini
+++ b/devtools/client/commandline/test/browser.ini
@@ -53,16 +53,17 @@ support-files =
  browser_cmd_csscoverage_sheetA.css
  browser_cmd_csscoverage_sheetB.css
  browser_cmd_csscoverage_sheetC.css
  browser_cmd_csscoverage_sheetD.css
 [browser_cmd_folder.js]
 skip-if = (e10s && debug) # Bug 1034511 (docShell leaks on debug)
 [browser_cmd_highlight_01.js]
 [browser_cmd_highlight_02.js]
+skip-if = false
 [browser_cmd_highlight_03.js]
 [browser_cmd_inject.js]
 support-files =
  browser_cmd_inject.html
 [browser_cmd_csscoverage_util.js]
 skip-if = (e10s && debug) # Bug 1034511 (docShell leaks on debug)
 [browser_cmd_jsb.js]
 support-files =
--- a/devtools/client/commandline/test/browser_cmd_appcache_valid.js
+++ b/devtools/client/commandline/test/browser_cmd_appcache_valid.js
@@ -7,23 +7,23 @@ const TEST_URI = "http://sub1.test2.exam
                  "commandline/test/browser_cmd_appcache_valid_index.html";
 
 function test() {
   return Task.spawn(spawnTest).then(finish, helpers.handleError);
 }
 
 function* spawnTest() {
   let options = yield helpers.openTab(TEST_URI);
-  yield helpers.openToolbar(options);
 
   info("adding cache listener.");
-
   // Wait for site to be cached.
   yield helpers.listenOnce(gBrowser.contentWindow.applicationCache, "cached");
 
+  yield helpers.openToolbar(options);
+
   // Pages containing an appcache the notification bar gives options to allow
   // or deny permission for the app to save data offline. Let's click Allow.
   let notificationID = "offline-app-requested-sub1.test2.example.com";
   let notification = PopupNotifications.getNotification(notificationID, gBrowser.selectedBrowser);
 
   if (notification) {
     info("Authorizing offline storage.");
     notification.mainAction.callback();
--- a/devtools/client/commandline/test/browser_cmd_highlight_02.js
+++ b/devtools/client/commandline/test/browser_cmd_highlight_02.js
@@ -9,36 +9,37 @@ const TEST_PAGE = "data:text/html;charse
 
 function test() {
   return Task.spawn(function* () {
     let options = yield helpers.openTab(TEST_PAGE);
     yield helpers.openToolbar(options);
 
     info("highlighting the body node");
     yield runCommand("highlight body", options);
-    is(getHighlighterNumber(), 1, "The highlighter element exists for body");
+    is(yield getHighlighterNumber(), 1, "The highlighter element exists for body");
 
     info("highlighting the div node");
     yield runCommand("highlight div", options);
-    is(getHighlighterNumber(), 1, "The highlighter element exists for div");
+    is(yield getHighlighterNumber(), 1, "The highlighter element exists for div");
 
     info("highlighting the body node again, asking to keep the div");
     yield runCommand("highlight body --keep", options);
-    is(getHighlighterNumber(), 2, "2 highlighter elements have been created");
+    is(yield getHighlighterNumber(), 2, "2 highlighter elements have been created");
 
     info("unhighlighting all nodes");
     yield runCommand("unhighlight", options);
-    is(getHighlighterNumber(), 0, "All highlighters have been removed");
+    is(yield getHighlighterNumber(), 0, "All highlighters have been removed");
 
     yield helpers.closeToolbar(options);
     yield helpers.closeTab(options);
   }).then(finish, helpers.handleError);
 }
 
 function getHighlighterNumber() {
-  // Note that this only works as long as gcli tests aren't run with e10s on.
-  // To make this e10s ready, execute this in a content frame script instead.
-  return require("devtools/shared/gcli/commands/highlight").highlighters.length;
+  return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+    const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    return require("devtools/shared/gcli/commands/highlight").highlighters.length;
+  });
 }
 
 function* runCommand(cmd, options) {
   yield helpers.audit(options, [{ setup: cmd, exec: {} }]);
 }
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -289,17 +289,17 @@ skip-if = (os == 'mac' && e10s && debug)
 [browser_dbg_interrupts.js]
 skip-if = e10s && debug
 [browser_dbg_listaddons.js]
 skip-if = e10s && debug
 tags = addons
 [browser_dbg_listtabs-01.js]
 skip-if = e10s # TODO
 [browser_dbg_listtabs-02.js]
-skip-if = e10s # TODO
+skip-if = true # Never worked for remote frames, needs a mock DebuggerServerConnection
 [browser_dbg_listtabs-03.js]
 skip-if = e10s && debug
 [browser_dbg_listworkers.js]
 [browser_dbg_location-changes-01-simple.js]
 skip-if = e10s && debug
 [browser_dbg_location-changes-02-blank.js]
 skip-if = e10s && debug
 [browser_dbg_location-changes-03-new.js]
--- a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
@@ -34,29 +34,27 @@ function test() {
       gClient.request({ to: globalActor, type: "ping" }, aResponse => {
         is(aResponse.pong, "pong", "Actor should respond to requests.");
 
         // Send another ping to see if the same actor is used.
         gClient.request({ to: globalActor, type: "ping" }, aResponse => {
           is(aResponse.pong, "pong", "Actor should respond to requests.");
 
           // Make sure that lazily-created actors are created only once.
-          let conn = transport._serverConnection;
-
-          // First we look for the pool of global actors.
-          let extraPools = conn._extraPools;
-          let globalPool;
+          let count = 0;
+          for (let connID of Object.getOwnPropertyNames(DebuggerServer._connections)) {
+            let conn = DebuggerServer._connections[connID];
+            let actorPrefix = conn._prefix + "test_one";
+            for (let pool of conn._extraPools) {
+              count += Object.keys(pool._actors).filter(e => {
+                return e.startsWith(actorPrefix);
+              }).length;
+            }
+          }
 
-          let actorPrefix = conn._prefix + "test_one";
-          let count = 0;
-          for (let pool of extraPools) {
-            count += Object.keys(pool._actors).filter(e => {
-              return e.startsWith(actorPrefix);
-            }).length;
-          }
           is(count, 2,
             "Only two actor exists in all pools. One tab actor and one global.");
 
           gClient.close(finish);
         });
       });
     });
   });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
@@ -75,17 +75,18 @@ function testAttachRemovedTab() {
     let deferred = promise.defer();
 
     gClient.addListener("paused", (aEvent, aPacket) => {
       ok(false, "Attaching to an exited tab actor shouldn't generate a pause.");
       deferred.reject();
     });
 
     gClient.request({ to: gTab2Actor, type: "attach" }, aResponse => {
-      is(aResponse.type, "exited", "Tab should consider itself exited.");
+      is(aResponse.error, "connectionClosed",
+         "Connection is gone since the tab was removed.");
       deferred.resolve();
     });
 
     return deferred.promise;
   });
 }
 
 function closeConnection() {
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -79,16 +79,27 @@ function* openTabAndSetupStorage(url) {
       };
       _getAllWindows(baseWindow);
 
       return windows;
     }
 
     let windows = getAllWindows(content);
     for (let win of windows) {
+      let readyState = win.document.readyState;
+      info(`Found a window: ${readyState}`);
+      if (readyState != "complete") {
+        yield new Promise(resolve => {
+          let onLoad = () => {
+            win.removeEventListener("load", onLoad);
+            resolve();
+          };
+          win.addEventListener("load", onLoad);
+        });
+      }
       if (win.setup) {
         yield win.setup();
       }
     }
   });
 
   // open storage inspector
   return yield openStoragePanel();
--- a/devtools/client/storage/test/storage-listings.html
+++ b/devtools/client/storage/test/storage-listings.html
@@ -21,17 +21,17 @@ document.cookie = "c1=foobar; expires=" 
 document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
 document.cookie = "c3=foobar-2; secure=true; expires=" +
   new Date(cookieExpiresTime2).toGMTString() + "; path=/";
 // ... and some local storage items ..
 localStorage.setItem("ls1", "foobar");
 localStorage.setItem("ls2", "foobar-2");
 // ... and finally some session storage items too
 sessionStorage.setItem("ss1", "foobar-3");
-dump("added cookies and stuff from main page\n");
+dump("added cookies and storage from main page\n");
 
 let idbGenerator = function*() {
   let request = indexedDB.open("idb1", 1);
   request.onerror = function() {
     throw new Error("error opening db connection");
   };
   let db = yield new Promise(done => {
     request.onupgradeneeded = event => {
@@ -82,17 +82,17 @@ let idbGenerator = function*() {
     };
   });
   // Prevents AbortError during close()
   yield new Promise(done => {
     request.onsuccess = done;
   });
   db2.close();
 
-  dump("added cookies and stuff from main page\n");
+  dump("added indexedDB from main page\n");
 };
 
 function deleteDB(dbName) {
   return new Promise(resolve => {
     dump("removing database " + dbName + " from " + document.location + "\n");
     indexedDB.deleteDatabase(dbName).onsuccess = resolve;
   });
 }
--- a/devtools/client/storage/test/storage-secured-iframe.html
+++ b/devtools/client/storage/test/storage-secured-iframe.html
@@ -7,16 +7,17 @@ Iframe for testing multiple host detetio
   <meta charset="utf-8">
 </head>
 <body>
 <script type="application/javascript;version=1.7">
 "use strict";
 document.cookie = "sc1=foobar;";
 localStorage.setItem("iframe-s-ls1", "foobar");
 sessionStorage.setItem("iframe-s-ss1", "foobar-2");
+dump("added cookies and storage from secured iframe\n");
 
 let idbGenerator = function*() {
   let request = indexedDB.open("idb-s1", 1);
   request.onerror = function() {
     throw new Error("error opening db connection");
   };
   let db = yield new Promise(done => {
     request.onupgradeneeded = event => {
@@ -60,17 +61,17 @@ let idbGenerator = function*() {
   transaction = db2.transaction(["obj-s2"], "readwrite");
   let store3 = transaction.objectStore("obj-s2");
   store3.add({id3: 16, name2: "foo", email: "foo@bar.com"});
   yield new Promise(success => {
     transaction.oncomplete = success;
   });
 
   db2.close();
-  dump("added cookies and stuff from secured iframe\n");
+  dump("added indexedDB from secured iframe\n");
 };
 
 function deleteDB(dbName) {
   return new Promise(resolve => {
     dump("removing database " + dbName + " from " + document.location + "\n");
     indexedDB.deleteDatabase(dbName).onsuccess = resolve;
   });
 }
--- a/devtools/client/storage/test/storage-unsecured-iframe.html
+++ b/devtools/client/storage/test/storage-unsecured-iframe.html
@@ -8,12 +8,12 @@ Iframe for testing multiple host detetio
 </head>
 <body>
 <script>
 "use strict";
 document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
 localStorage.setItem("iframe-u-ls1", "foobar");
 sessionStorage.setItem("iframe-u-ss1", "foobar1");
 sessionStorage.setItem("iframe-u-ss2", "foobar2");
-console.log("added cookies and stuff from unsecured iframe");
+dump("added cookies and storage from unsecured iframe\n");
 </script>
 </body>
 </html>
--- a/devtools/client/webaudioeditor/controller.js
+++ b/devtools/client/webaudioeditor/controller.js
@@ -64,16 +64,22 @@ var WebAudioEditorController = {
     // If not, get the JSON directly. Using the actor method is preferable so the client
     // knows exactly what methods are supported on the server.
     let actorHasDefinition = yield gTarget.actorHasMethod("webaudio", "getDefinition");
     if (actorHasDefinition) {
       AUDIO_NODE_DEFINITION = yield gFront.getDefinition();
     } else {
       AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json");
     }
+
+    // Make sure the backend is prepared to handle audio contexts.
+    // Since actors are created lazily on the first request to them, we need to send an
+    // early request to ensure the CallWatcherActor is running and watching for new window
+    // globals.
+    gFront.setup({ reload: false });
   }),
 
   /**
    * Remove events emitted by the current tab target.
    */
   destroy: function () {
     gTarget.off("will-navigate", this._onTabNavigated);
     gTarget.off("navigate", this._onTabNavigated);
@@ -128,21 +134,16 @@ var WebAudioEditorController = {
   },
 
   /**
    * Called for each location change in the debugged tab.
    */
   _onTabNavigated: Task.async(function* (event, {isFrameSwitching}) {
     switch (event) {
       case "will-navigate": {
-        // Make sure the backend is prepared to handle audio contexts.
-        if (!isFrameSwitching) {
-          yield gFront.setup({ reload: false });
-        }
-
         // Clear out current UI.
         this.reset();
 
         // When switching to an iframe, ensure displaying the reload button.
         // As the document has already been loaded without being hooked.
         if (isFrameSwitching) {
           $("#reload-notice").hidden = false;
           $("#waiting-notice").hidden = true;
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js
+++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectnode-disconnect.js
@@ -6,23 +6,22 @@
  * Uses the editor front as the actors do not retain connect state.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [dest, osc, gain] = actors;
 
   info("Disconnecting oscillator...");
   osc.disconnect();
   yield Promise.all([
     waitForGraphRendered(panelWin, 3, 1),
     once(gAudioNodes, "disconnect")
   ]);
--- a/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js
+++ b/devtools/client/webaudioeditor/test/browser_audionode-actor-connectparam.js
@@ -6,23 +6,22 @@
  * Uses the editor front as the actors do not retain connect state.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [dest, osc, gain] = actors;
 
   yield osc.disconnect();
 
   osc.connectParam(gain, "gain");
   yield Promise.all([
     waitForGraphRendered(panelWin, 3, 1, 1),
     once(gAudioNodes, "connect")
--- a/devtools/client/webaudioeditor/test/browser_callwatcher-01.js
+++ b/devtools/client/webaudioeditor/test/browser_callwatcher-01.js
@@ -10,17 +10,17 @@
 
 const BUG_1130901_URL = EXAMPLE_URL + "doc_bug_1130901.html";
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUG_1130901_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
+  let rendered = waitForGraphRendered(panelWin, 3, 0);
   reload(target);
-
-  yield waitForGraphRendered(panelWin, 3, 0);
+  yield rendered;
 
   ok(true, "Successfully created a node from AudioContext via `call`.");
   ok(true, "Successfully created a node from AudioContext via `apply`.");
 
   yield teardown(target);
 });
--- a/devtools/client/webaudioeditor/test/browser_callwatcher-02.js
+++ b/devtools/client/webaudioeditor/test/browser_callwatcher-02.js
@@ -11,19 +11,19 @@ const BUG_1112378_URL = EXAMPLE_URL + "d
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUG_1112378_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
   loadFrameScripts();
 
+  let rendered = waitForGraphRendered(panelWin, 2, 0);
   reload(target);
-
-  yield waitForGraphRendered(panelWin, 2, 0);
+  yield rendered;
 
   let error = yield evalInDebuggee("throwError()");
   is(error.lineNumber, 21, "error has correct lineNumber");
   is(error.columnNumber, 11, "error has correct columnNumber");
   is(error.name, "TypeError", "error has correct name");
   is(error.message, "Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error has correct message");
   is(error.stringified, "TypeError: Argument 1 is not valid for any of the 2-argument overloads of AudioNode.connect.", "error is stringified correctly");
   is(error.instanceof, true, "error is correctly an instanceof TypeError");
--- a/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-01.js
@@ -8,23 +8,24 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
+
   let $tabbox = $("#web-audio-editor-tabs");
 
   // Oscillator node
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   yield waitForInspectorRender(panelWin, EVENTS);
   $tabbox.selectedIndex = 1;
 
   ok(isVisible($("#automation-graph-container")), "graph container should be visible");
--- a/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_automation-view-02.js
@@ -8,25 +8,24 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(AUTOMATION_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, AutomationView } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
-
   // Oscillator node
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   yield waitForInspectorRender(panelWin, EVENTS);
   click(panelWin, $("#automation-tab"));
 
   ok(AutomationView._selectedParamName, "frequency",
     "AutomatioView is set on 'frequency'");
   ok($(".automation-param-button[data-param='frequency']").getAttribute("selected"),
--- a/devtools/client/webaudioeditor/test/browser_wa_controller-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_controller-01.js
@@ -9,20 +9,20 @@
 
 const BUG_1125817_URL = EXAMPLE_URL + "doc_bug_1125817.html";
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUG_1125817_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     once(gAudioNodes, "add", 2),
     once(gAudioNodes, "disconnect"),
     waitForGraphRendered(panelWin, 2, 0)
   ]);
+  reload(target);
+  yield events;
 
   ok(true, "Successfully disconnected a just-created node.");
 
   yield teardown(target);
 });
--- a/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_destroy-node-01.js
@@ -11,22 +11,22 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [created] = yield Promise.all([
+  let events = Promise.all([
     getNSpread(gAudioNodes, "add", 13),
     waitForGraphRendered(panelWin, 13, 2)
   ]);
+  reload(target);
+  let [created] = yield events;
 
   // Flatten arrays of event arguments and take the first (AudioNodeModel)
   // and get its ID.
   let actorIDs = created.map(ev => ev[0].id);
 
   // Click a soon-to-be dead buffer node
   yield clickGraphNode(panelWin, actorIDs[5]);
 
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-click.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-click.js
@@ -8,23 +8,22 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let panelWin = panel.panelWin;
   let { gFront, $, $$, InspectorView } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors, _] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 8),
     waitForGraphRendered(panel.panelWin, 8, 8)
   ]);
-
+  reload(target);
+  let [actors, _] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
 
   yield clickGraphNode(panelWin, nodeIds[1], true);
 
   ok(InspectorView.isVisible(), "InspectorView visible after selecting a node.");
   is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set.");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-markers.js
@@ -14,22 +14,22 @@ add_task(function* () {
 
   let currentTheme = Services.prefs.getCharPref("devtools.theme");
 
   ok(MARKER_STYLING.light, "Marker styling exists for light theme.");
   ok(MARKER_STYLING.dark, "Marker styling exists for dark theme.");
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
 
   is(getFill($("#arrowhead")), MARKER_STYLING[currentTheme],
     "marker initially matches theme.");
 
   // Switch to light
   setTheme("light");
   is(getFill($("#arrowhead")), MARKER_STYLING.light,
     "marker styling matches light theme on change.");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-01.js
@@ -9,25 +9,24 @@ var connectCount = 0;
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
   gAudioNodes.on("connect", onConnectNode);
 
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [destId, oscId, gainId] = actors.map(actor => actor.actorID);
 
   ok(findGraphNode(panelWin, oscId).classList.contains("type-OscillatorNode"), "found OscillatorNode with class");
   ok(findGraphNode(panelWin, gainId).classList.contains("type-GainNode"), "found GainNode with class");
   ok(findGraphNode(panelWin, destId).classList.contains("type-AudioDestinationNode"), "found AudioDestinationNode with class");
   is(findGraphEdge(panelWin, oscId, gainId).toString(), "[object SVGGElement]", "found edge for osc -> gain");
   is(findGraphEdge(panelWin, gainId, destId).toString(), "[object SVGGElement]", "found edge for gain -> dest");
 
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-02.js
@@ -7,23 +7,22 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$ } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 8),
     waitForGraphRendered(panelWin, 8, 8)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let nodeIDs = actors.map(actor => actor.actorID);
 
   let types = ["AudioDestinationNode", "OscillatorNode", "GainNode", "ScriptProcessorNode",
                "OscillatorNode", "GainNode", "AudioBufferSourceNode", "BiquadFilterNode"];
 
 
   types.forEach((type, i) => {
     ok(findGraphNode(panelWin, nodeIDs[i]).classList.contains("type-" + type), "found " + type + " with class");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-03.js
@@ -5,23 +5,22 @@
  * Tests to ensure that selected nodes stay selected on graph redraw.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [dest, osc, gain] = actors;
 
   yield clickGraphNode(panelWin, gain.actorID);
   ok(findGraphNode(panelWin, gain.actorID).classList.contains("selected"),
     "Node selected once.");
 
   // Disconnect a node to trigger a rerender
   osc.disconnect();
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-04.js
@@ -7,23 +7,22 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 5),
     waitForGraphRendered(panelWin, 5, 2, 3)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let nodeIDs = actors.map(actor => actor.actorID);
 
   let [, carrier, gain, mod1, mod2] = nodeIDs;
 
   let edges = [
     [mod1, gain, "gain", "mod1 -> gain[gain]"],
     [mod2, carrier, "frequency", "mod2 -> carrier[frequency]"],
     [mod2, carrier, "detune", "mod2 -> carrier[detune]"]
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-05.js
@@ -5,23 +5,22 @@
  * Tests to ensure that param connections trigger graph redraws
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 2, 0)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [dest, osc, gain] = actors;
 
   yield osc.disconnect();
 
   osc.connectParam(gain, "gain");
   yield waitForGraphRendered(panelWin, 3, 1, 1);
   ok(true, "Graph re-rendered upon param connection");
 
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-render-06.js
@@ -7,19 +7,19 @@
 
 const BUG_1141261_URL = EXAMPLE_URL + "doc_bug_1141261.html";
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUG_1141261_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 1, 0)
   ]);
+  reload(target);
+  yield events;
 
   ok(true, "Graph correctly shows gain node as disconnected");
 
   yield teardown(target);
 });
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-selected.js
@@ -7,23 +7,22 @@
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS } = panelWin;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
-
+  reload(target);
+  let [actors] = yield events;
   let [destId, oscId, gainId] = actors.map(actor => actor.actorID);
 
   ok(!findGraphNode(panelWin, destId).classList.contains("selected"),
     "No nodes selected on start. (destination)");
   ok(!findGraphNode(panelWin, oscId).classList.contains("selected"),
     "No nodes selected on start. (oscillator)");
   ok(!findGraphNode(panelWin, gainId).classList.contains("selected"),
     "No nodes selected on start. (gain)");
--- a/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_graph-zoom.js
@@ -8,36 +8,36 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, ContextView } = panelWin;
 
   let started = once(gFront, "start-context");
 
   yield Promise.all([
+    waitForGraphRendered(panelWin, 3, 2),
     reload(target),
-    waitForGraphRendered(panelWin, 3, 2)
   ]);
 
   is(ContextView.getCurrentScale(), 1, "Default graph scale is 1.");
   is(ContextView.getCurrentTranslation()[0], 20, "Default x-translation is 20.");
   is(ContextView.getCurrentTranslation()[1], 20, "Default y-translation is 20.");
 
   // Change both attribute and D3's internal store
   panelWin.d3.select("#graph-target").attr("transform", "translate([100, 400]) scale(10)");
   ContextView._zoomBinding.scale(10);
   ContextView._zoomBinding.translate([100, 400]);
 
   is(ContextView.getCurrentScale(), 10, "After zoom, scale is 10.");
   is(ContextView.getCurrentTranslation()[0], 100, "After zoom, x-translation is 100.");
   is(ContextView.getCurrentTranslation()[1], 400, "After zoom, y-translation is 400.");
 
   yield Promise.all([
+    waitForGraphRendered(panelWin, 3, 2),
     reload(target),
-    waitForGraphRendered(panelWin, 3, 2)
   ]);
 
   is(ContextView.getCurrentScale(), 1, "After refresh, graph scale is 1.");
   is(ContextView.getCurrentTranslation()[0], 20, "After refresh, x-translation is 20.");
   is(ContextView.getCurrentTranslation()[1], 20, "After refresh, y-translation is 20.");
 
   yield teardown(target);
 });
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-bypass-01.js
@@ -5,22 +5,22 @@
  * Tests that nodes are correctly bypassed when bypassing.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   // Wait for the node to be set as well as the inspector to come fully into the view
   yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true);
 
   let $bypass = $("toolbarbutton.bypass");
 
   is((yield actors[1].isBypassed()), false, "AudioNodeActor is not bypassed by default.");
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-toggle.js
@@ -9,22 +9,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
 
   // Open inspector pane
   $("#inspector-pane-toggle").click();
   yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
 
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_inspector-width.js
@@ -9,41 +9,42 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
 
   // Open inspector pane
   $("#inspector-pane-toggle").click();
   yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
 
   let newInspectorWidth = 500;
 
   // Setting width to new_inspector_width
   $("#web-audio-inspector").setAttribute("width", newInspectorWidth);
-  reload(target);
 
   // Width should be 500 after reloading
-  [actors] = yield Promise.all([
+  events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  [actors] = yield events;
   nodeIds = actors.map(actor => actor.actorID);
 
   // Open inspector pane
   $("#inspector-pane-toggle").click();
   yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
 
   yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
 
--- a/devtools/client/webaudioeditor/test/browser_wa_inspector.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_inspector.js
@@ -9,22 +9,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
   let gVars = InspectorView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
   ok(isVisible($("#web-audio-editor-details-pane-empty")),
     "InspectorView empty message should show when no node's selected.");
   ok(!isVisible($("#web-audio-editor-tabs")),
     "InspectorView tabs view should be hidden when no node's selected.");
 
--- a/devtools/client/webaudioeditor/test/browser_wa_navigate.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_navigate.js
@@ -6,33 +6,33 @@
  * the audio graph if both pages have an AudioContext.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
-  reload(target);
-
-  var [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  yield events;
 
   var { nodes, edges } = countGraphObjects(panelWin);
   is(nodes, 3, "should only be 3 nodes.");
   is(edges, 2, "should only be 2 edges.");
 
-  navigate(target, SIMPLE_NODES_URL);
-
-  var [actors] = yield Promise.all([
+  events = Promise.all([
     getN(gFront, "create-node", 15),
     waitForGraphRendered(panelWin, 15, 0)
   ]);
+  navigate(target, SIMPLE_NODES_URL);
+  yield events;
 
   is($("#reload-notice").hidden, true,
     "The 'reload this page' notice should be hidden after context found after navigation.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for an audio context' notice should be hidden after context found after navigation.");
   is($("#content").hidden, false,
     "The tool's content should reappear without closing and reopening the toolbox.");
 
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-01.js
@@ -8,22 +8,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   // Wait for the node to be set as well as the inspector to come fully into the view
   yield Promise.all([
     waitForInspectorRender(panelWin, EVENTS),
     once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
   ]);
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-edit-02.js
@@ -8,22 +8,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(COMPLEX_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 8),
     waitForGraphRendered(panelWin, 8, 8)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   click(panelWin, findGraphNode(panelWin, nodeIds[3]));
   // Wait for the node to be set as well as the inspector to come fully into the view
   yield Promise.all([
     waitForInspectorRender(panelWin, EVENTS),
     once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED),
   ]);
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-media-nodes.js
@@ -39,26 +39,26 @@ add_task(function* () {
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   // Auto enable getUserMedia
   let mediaPermissionPref = Services.prefs.getBoolPref(MEDIA_PERMISSION);
   Services.prefs.setBoolPref(MEDIA_PERMISSION, true);
 
-  reload(target);
-
   yield loadFrameScripts();
 
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 4),
     waitForGraphRendered(panelWin, 4, 0)
   ]);
+  reload(target);
+  let [actors] = yield events;
+  let nodeIds = actors.map(actor => actor.actorID);
 
-  let nodeIds = actors.map(actor => actor.actorID);
   let types = [
     "AudioDestinationNode", "MediaElementAudioSourceNode",
     "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode"
   ];
 
   let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
 
   for (let i = 0; i < types.length; i++) {
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params-objects.js
@@ -9,22 +9,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(BUFFER_AND_ARRAY_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 3),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   click(panelWin, findGraphNode(panelWin, nodeIds[2]));
   yield waitForInspectorRender(panelWin, EVENTS);
   checkVariableView(gVars, 0, {
     "curve": "Float32Array"
   }, "WaveShaper's `curve` is listed as an `Float32Array`.");
 
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view-params.js
@@ -9,25 +9,26 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
   yield loadFrameScripts();
 
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     getN(gFront, "create-node", 15),
     waitForGraphRendered(panelWin, 15, 0)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
+
   let types = [
     "AudioDestinationNode", "AudioBufferSourceNode", "ScriptProcessorNode",
     "AnalyserNode", "GainNode", "DelayNode", "BiquadFilterNode", "WaveShaperNode",
     "PannerNode", "ConvolverNode", "ChannelSplitterNode", "ChannelMergerNode",
     "DynamicsCompressorNode", "OscillatorNode"
   ];
 
   let defaults = yield Promise.all(types.map(type => nodeDefaultValues(type)));
--- a/devtools/client/webaudioeditor/test/browser_wa_properties-view.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_properties-view.js
@@ -8,22 +8,22 @@
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, EVENTS, PropertiesView } = panelWin;
   let gVars = PropertiesView._propsView;
 
   let started = once(gFront, "start-context");
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   // Gain node
   click(panelWin, findGraphNode(panelWin, nodeIds[2]));
   yield waitForInspectorRender(panelWin, EVENTS);
 
   ok(isVisible($("#properties-content")), "Parameters shown when they exist.");
   ok(!isVisible($("#properties-empty")),
--- a/devtools/client/webaudioeditor/test/browser_wa_reset-02.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_reset-02.js
@@ -6,32 +6,32 @@
  * the graph.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $ } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  yield events;
 
   let { nodes, edges } = countGraphObjects(panelWin);
   is(nodes, 3, "should only be 3 nodes.");
   is(edges, 2, "should only be 2 edges.");
 
-  reload(target);
-
-  [actors] = yield Promise.all([
+  events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  yield events;
 
   ({ nodes, edges } = countGraphObjects(panelWin));
   is(nodes, 3, "after reload, should only be 3 nodes.");
   is(edges, 2, "after reload, should only be 2 edges.");
 
   yield teardown(target);
 });
--- a/devtools/client/webaudioeditor/test/browser_wa_reset-03.js
+++ b/devtools/client/webaudioeditor/test/browser_wa_reset-03.js
@@ -6,38 +6,38 @@
  * the inspector and selected node.
  */
 
 add_task(function* () {
   let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
   let { panelWin } = panel;
   let { gFront, $, InspectorView } = panelWin;
 
-  reload(target);
-
-  let [actors] = yield Promise.all([
+  let events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  let [actors] = yield events;
   let nodeIds = actors.map(actor => actor.actorID);
 
   yield clickGraphNode(panelWin, nodeIds[1], true);
   ok(InspectorView.isVisible(), "InspectorView visible after selecting a node.");
   is(InspectorView.getCurrentAudioNode().id, nodeIds[1], "InspectorView has correct node set.");
 
   /**
    * Reload
    */
 
-  reload(target);
-
-  [actors] = yield Promise.all([
+  events = Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
+  reload(target);
+  [actors] = yield events;
   nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
   is(InspectorView.getCurrentAudioNode(), null,
     "InspectorView has no current node set on reset.");
 
   yield clickGraphNode(panelWin, nodeIds[2], true);
   ok(InspectorView.isVisible(),
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -203,21 +203,23 @@ function teardown(aTarget) {
 // us to listen for `n` events and return a promise resolving to them.
 //
 // Takes a `front` object that is an event emitter, the number of
 // programs that should be listened to and waited on, and an optional
 // `onAdd` function that calls with the entire actors array on program link
 function getN(front, eventName, count, spread) {
   let actors = [];
   let deferred = Promise.defer();
+  info(`Waiting for ${count} ${eventName} events`);
   front.on(eventName, function onEvent(...args) {
     let actor = args[0];
     if (actors.length !== count) {
       actors.push(spread ? args : actor);
     }
+    info(`Got ${actors.length} / ${count} ${eventName} events`);
     if (actors.length === count) {
       front.off(eventName, onEvent);
       deferred.resolve(actors);
     }
   });
   return deferred.promise;
 }
 
@@ -232,18 +234,21 @@ function getNSpread(front, eventName, co
 /**
  * Waits for the UI_GRAPH_RENDERED event to fire, but only
  * resolves when the graph was rendered with the correct count of
  * nodes and edges.
  */
 function waitForGraphRendered(front, nodeCount, edgeCount, paramEdgeCount) {
   let deferred = Promise.defer();
   let eventName = front.EVENTS.UI_GRAPH_RENDERED;
+  info(`Wait for graph rendered with ${nodeCount} nodes, ${edgeCount} edges`);
   front.on(eventName, function onGraphRendered(_, nodes, edges, pEdges) {
     let paramEdgesDone = paramEdgeCount != null ? paramEdgeCount === pEdges : true;
+    info(`Got graph rendered with ${nodes} / ${nodeCount} nodes, ` +
+         `${edges} / ${edgeCount} edges`);
     if (nodes === nodeCount && edges === edgeCount && paramEdgesDone) {
       front.off(eventName, onGraphRendered);
       deferred.resolve();
     }
   });
   return deferred.promise;
 }
 
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -262,17 +262,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_bug_632817.js]
 [browser_webconsole_bug_642108_pruneTest.js]
 [browser_webconsole_autocomplete_and_selfxss.js]
 subsuite = clipboard
 [browser_webconsole_bug_644419_log_limits.js]
 [browser_webconsole_bug_646025_console_file_location.js]
 [browser_webconsole_bug_651501_document_body_autocomplete.js]
 [browser_webconsole_bug_653531_highlighter_console_helper.js]
-skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+skip-if = true # Requires direct access to content nodes
 [browser_webconsole_bug_658368_time_methods.js]
 [browser_webconsole_bug_659907_console_dir.js]
 [browser_webconsole_bug_660806_history_nav.js]
 [browser_webconsole_bug_664131_console_group.js]
 [browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js]
 [browser_webconsole_bug_704295.js]
 [browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js]
 [browser_webconsole_bug_737873_mixedcontent.js]
--- a/devtools/client/webide/test/browser_tabs.js
+++ b/devtools/client/webide/test/browser_tabs.js
@@ -4,18 +4,16 @@
 
 const TEST_URI = "http://example.com/browser/devtools/client/webide/test/doc_tabs.html";
 
 function test() {
   waitForExplicitFinish();
   requestCompleteLog();
 
   Task.spawn(function* () {
-    const { DebuggerServer } = require("devtools/server/main");
-
     // Since we test the connections set below, destroy the server in case it
     // was left open.
     DebuggerServer.destroy();
     DebuggerServer.init();
     DebuggerServer.addBrowserActors();
 
     let tab = yield addTab(TEST_URI);
 
--- a/devtools/client/webide/test/head.js
+++ b/devtools/client/webide/test/head.js
@@ -1,23 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
-const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
-const {gDevTools} = require("devtools/client/framework/devtools");
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
 const promise = require("promise");
 const Services = require("Services");
-const {Task} = require("devtools/shared/task");
-const {AppProjects} = require("devtools/client/webide/modules/app-projects");
+const { Task } = require("devtools/shared/task");
+const { AppProjects } = require("devtools/client/webide/modules/app-projects");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { DebuggerServer } = require("devtools/server/main");
 DevToolsUtils.testing = true;
 
 var TEST_BASE;
 if (window.location === "chrome://browser/content/browser.xul") {
   TEST_BASE = "chrome://mochitests/content/browser/devtools/client/webide/test/";
 } else {
   TEST_BASE = "chrome://mochitests/content/chrome/devtools/client/webide/test/";
 }
@@ -40,38 +41,37 @@ registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
   Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
   Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters");
   Services.prefs.clearUserPref("devtools.webide.busyTimeout");
   Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
   Services.prefs.clearUserPref("devtools.webide.lastConnectedRuntime");
 });
 
-function openWebIDE(autoInstallAddons) {
+var openWebIDE = Task.async(function* (autoInstallAddons) {
   info("opening WebIDE");
 
   Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", !!autoInstallAddons);
   Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", !!autoInstallAddons);
 
-  let deferred = promise.defer();
-
   let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
   let win = ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
 
-  win.addEventListener("load", function onLoad() {
-    win.removeEventListener("load", onLoad);
-    info("WebIDE open");
-    SimpleTest.requestCompleteLog();
-    SimpleTest.executeSoon(() => {
-      deferred.resolve(win);
+  yield new Promise(resolve => {
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad);
+      SimpleTest.requestCompleteLog();
+      SimpleTest.executeSoon(resolve);
     });
   });
 
-  return deferred.promise;
-}
+  info("WebIDE open");
+
+  return win;
+});
 
 function closeWebIDE(win) {
   info("Closing WebIDE");
 
   let deferred = promise.defer();
 
   Services.prefs.clearUserPref("devtools.webide.widget.enabled");
 
@@ -225,8 +225,24 @@ function connectToLocalRuntime(win) {
   items[1].click();
   return updated;
 }
 
 function handleError(aError) {
   ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   finish();
 }
+
+function waitForConnectionChange(expectedState, count = 1) {
+  return new Promise(resolve => {
+    let onConnectionChange = (_, state) => {
+      if (state != expectedState) {
+        return;
+      }
+      if (--count != 0) {
+        return;
+      }
+      DebuggerServer.off("connectionchange", onConnectionChange);
+      resolve();
+    };
+    DebuggerServer.on("connectionchange", onConnectionChange);
+  });
+}
--- a/devtools/client/webide/test/test_autoconnect_runtime.html
+++ b/devtools/client/webide/test/test_autoconnect_runtime.html
@@ -14,18 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function*() {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
 
           let win = yield openWebIDE();
           let docRuntime = getRuntimeDocument(win);
 
@@ -48,40 +46,44 @@
           };
           win.AppManager.runtimeList.usb.push(fakeRuntime);
           win.AppManager.update("runtime-list");
 
           let panelNode = docRuntime.querySelector("#runtime-panel");
           let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 1, "Found one runtime button");
 
-          let deferred = promise.defer();
-          win.AppManager.connection.once(
-              win.Connection.Events.CONNECTED,
-              () => deferred.resolve());
+          let connectionsChanged = waitForConnectionChange("opened", 2);
           items[0].click();
 
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
           yield win.UI._busyPromise;
-          is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
+
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Connected");
+
+          connectionsChanged = waitForConnectionChange("closed", 2);
 
           yield nextTick();
-
           yield closeWebIDE(win);
 
+          yield connectionsChanged;
           is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
 
+          connectionsChanged = waitForConnectionChange("opened", 2);
+
           win = yield openWebIDE();
 
           win.AppManager.runtimeList.usb.push(fakeRuntime);
           win.AppManager.update("runtime-list");
 
           yield waitForUpdate(win, "runtime-targets");
 
-          is(Object.keys(DebuggerServer._connections).length, 1, "Automatically reconnected");
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Automatically reconnected");
 
           yield win.Cmds.disconnectRuntime();
 
           yield closeWebIDE(win);
 
           DebuggerServer.destroy();
 
           SimpleTest.finish();
--- a/devtools/client/webide/test/test_autoselect_project.html
+++ b/devtools/client/webide/test/test_autoselect_project.html
@@ -14,72 +14,78 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
 
           let win = yield openWebIDE();
           let docRuntime = getRuntimeDocument(win);
           let docProject = getProjectDocument(win);
 
           let panelNode = docRuntime.querySelector("#runtime-panel");
           let items = panelNode.querySelectorAll(".runtime-panel-item-other");
           is(items.length, 2, "Found 2 runtime buttons");
 
           // Connect to local runtime
+          let connectionsChanged = waitForConnectionChange("opened", 2);
           items[1].click();
 
           yield waitForUpdate(win, "runtime-targets");
 
-          is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected");
 
           ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
 
           // Select main process
           yield win.Cmds.showProjectPanel();
           yield waitForUpdate(win, "runtime-targets");
           SimpleTest.executeSoon(() => {
             docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
           });
 
           yield waitForUpdate(win, "project");
 
           let lastProject = Services.prefs.getCharPref("devtools.webide.lastSelectedProject");
           is(lastProject, "mainProcess:", "Last project is main process");
 
+          connectionsChanged = waitForConnectionChange("closed", 2);
+
           yield nextTick();
           yield closeWebIDE(win);
 
+          yield connectionsChanged;
           is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
 
+          connectionsChanged = waitForConnectionChange("opened", 2);
+
           // Re-open, should reselect main process after connection
           win = yield openWebIDE();
 
           docRuntime = getRuntimeDocument(win);
 
           panelNode = docRuntime.querySelector("#runtime-panel");
           items = panelNode.querySelectorAll(".runtime-panel-item-other");
           is(items.length, 2, "Found 2 runtime buttons");
 
           // Connect to local runtime
           items[1].click();
 
           yield waitForUpdate(win, "runtime-targets");
 
-          is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected");
           ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
           is(win.AppManager.selectedProject.type, "mainProcess", "Main process reselected");
 
           // Wait for the toolbox to be fully loaded
           yield win.UI.toolboxPromise;
 
           // If we happen to pass a project object targeting the same context,
           // here, the main process, the `selectedProject` attribute shouldn't be updated
--- a/devtools/client/webide/test/test_device_permissions.html
+++ b/devtools/client/webide/test/test_device_permissions.html
@@ -14,18 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
 
           let win = yield openWebIDE();
 
           let permIframe = win.document.querySelector("#deck-panel-permissionstable");
--- a/devtools/client/webide/test/test_device_preferences.html
+++ b/devtools/client/webide/test/test_device_preferences.html
@@ -14,18 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
 
           let win = yield openWebIDE();
 
           let prefIframe = win.document.querySelector("#deck-panel-devicepreferences");
--- a/devtools/client/webide/test/test_device_runtime.html
+++ b/devtools/client/webide/test/test_device_runtime.html
@@ -14,18 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
 
           let win = yield openWebIDE();
 
           let detailsIframe = win.document.querySelector("#deck-panel-runtimedetails");
--- a/devtools/client/webide/test/test_device_settings.html
+++ b/devtools/client/webide/test/test_device_settings.html
@@ -14,18 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function*() {
-          const { DebuggerServer } = require("devtools/server/main");
-
           if (SpecialPowers.isMainProcess()) {
             Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
           }
 
           if (!DebuggerServer.initialized) {
             DebuggerServer.init();
             DebuggerServer.addBrowserActors();
           }
--- a/devtools/client/webide/test/test_fullscreenToolbox.html
+++ b/devtools/client/webide/test/test_fullscreenToolbox.html
@@ -23,17 +23,16 @@
         docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
         return deferred.promise;
       }
 
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          const { DebuggerServer } = require("devtools/server/main");
           let win = yield openWebIDE();
           let docProject = getProjectDocument(win);
           let docRuntime = getRuntimeDocument(win);
           win.AppManager.update("runtime-list");
 
           yield connectToLocal(win, docRuntime);
 
           // Select main process
--- a/devtools/client/webide/test/test_runtime.html
+++ b/devtools/client/webide/test/test_runtime.html
@@ -13,17 +13,16 @@
   </head>
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
-        const { DebuggerServer } = require("devtools/server/main");
         let win;
 
         SimpleTest.registerCleanupFunction(() => {
           Task.spawn(function*() {
             if (win) {
               yield closeWebIDE(win);
             }
             DebuggerServer.destroy();
@@ -93,27 +92,24 @@
 
           yield winProject.projectList.importPackagedApp(packagedAppLocation);
           yield waitForUpdate(win, "project-validated");
 
           let panelNode = docRuntime.querySelector("#runtime-panel");
           let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 3, "Found 3 runtime buttons");
 
-          let deferred = promise.defer();
-          win.AppManager.connection.once(
-              win.Connection.Events.CONNECTED,
-              () => deferred.resolve());
-
+          let connectionsChanged = waitForConnectionChange("opened", 2);
           items[0].click();
 
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
           yield win.UI._busyPromise;
 
-          is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Connected");
 
           yield waitForUpdate(win, "runtime-global-actors");
 
           ok(isPlayActive(), "play button is enabled 1");
           ok(!isStopActive(), "stop button is disabled 1");
           let oldProject = win.AppManager.selectedProject;
           win.AppManager.selectedProject = null;
 
@@ -124,29 +120,33 @@
           win.AppManager._selectedProject = oldProject;
           win.UI.updateCommands();
 
           yield nextTick();
 
           ok(isPlayActive(), "play button is enabled 3");
           ok(!isStopActive(), "stop button is disabled 3");
 
+          connectionsChanged = waitForConnectionChange("closed", 2);
           yield win.Cmds.disconnectRuntime();
 
+          yield connectionsChanged;
           is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
 
           ok(win.AppManager.selectedProject, "A project is still selected");
           ok(!isPlayActive(), "play button is disabled 4");
           ok(!isStopActive(), "stop button is disabled 4");
 
+          connectionsChanged = waitForConnectionChange("opened", 2);
           docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
 
           yield waitForUpdate(win, "runtime-targets");
 
-          is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
+          yield connectionsChanged;
+          is(Object.keys(DebuggerServer._connections).length, 2, "Locally connected");
 
           ok(win.AppManager.isMainProcessDebuggable(), "Main process available");
 
           // Select main process
           SimpleTest.executeSoon(() => {
             docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
           });
 
--- a/devtools/client/webide/test/test_telemetry.html
+++ b/devtools/client/webide/test/test_telemetry.html
@@ -14,17 +14,16 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       const Telemetry = require("devtools/client/shared/telemetry");
       const { _DeprecatedUSBRuntime, _WiFiRuntime, _SimulatorRuntime,
               _gRemoteRuntime, _gLocalRuntime, RuntimeTypes }
             = require("devtools/client/webide/modules/runtimes");
-      const { DebuggerServer } = require("devtools/server/main");
 
       // Because we need to gather stats for the period of time that a tool has
       // been opened we make use of setTimeout() to create tool active times.
       const TOOL_DELAY = 200;
 
       function patchTelemetry() {
         Telemetry.prototype.telemetryInfo = {};
         Telemetry.prototype._oldlog = Telemetry.prototype.log;
--- a/devtools/client/webide/test/test_toolbox.html
+++ b/devtools/client/webide/test/test_toolbox.html
@@ -10,18 +10,16 @@
     <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
     <script type="application/javascript;version=1.8" src="head.js"></script>
     <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   </head>
 
   <body>
 
     <script type="application/javascript;version=1.8">
-      const { DebuggerServer } = require("devtools/server/main");
-
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         let win;
 
         SimpleTest.registerCleanupFunction(() => {
           Task.spawn(function*() {
             if (win) {
--- a/devtools/server/actors/call-watcher.js
+++ b/devtools/server/actors/call-watcher.js
@@ -228,19 +228,23 @@ var FunctionCallActor = protocol.ActorCl
  */
 var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this._onGlobalCreated = this._onGlobalCreated.bind(this);
     this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
     this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
+    on(this.tabActor, "window-ready", this._onGlobalCreated);
+    on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
   },
   destroy: function (conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
+    off(this.tabActor, "window-ready", this._onGlobalCreated);
+    off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
     this.finalize();
   },
 
   /**
    * Lightweight listener invoked whenever an instrumented function is called
    * while recording. We're doing this to avoid the event emitter overhead,
    * since this is expected to be a very hot function.
    */
@@ -259,19 +263,16 @@ var CallWatcherActor = exports.CallWatch
     this._timestampEpoch = 0;
 
     this._functionCalls = [];
     this._tracedGlobals = tracedGlobals || [];
     this._tracedFunctions = tracedFunctions || [];
     this._holdWeak = !!holdWeak;
     this._storeCalls = !!storeCalls;
 
-    on(this.tabActor, "window-ready", this._onGlobalCreated);
-    on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
-
     if (startRecording) {
       this.resumeRecording();
     }
     if (performReload) {
       this.tabActor.window.location.reload();
     }
   },
 
@@ -282,19 +283,16 @@ var CallWatcherActor = exports.CallWatch
    */
   finalize: function () {
     if (!this._initialized) {
       return;
     }
     this._initialized = false;
     this._finalized = true;
 
-    off(this.tabActor, "window-ready", this._onGlobalCreated);
-    off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
-
     this._tracedGlobals = null;
     this._tracedFunctions = null;
   },
 
   /**
    * Returns whether the instrumented function calls are currently recorded.
    */
   isRecording: function () {
@@ -330,16 +328,20 @@ var CallWatcherActor = exports.CallWatch
   eraseRecording: function () {
     this._functionCalls = [];
   },
 
   /**
    * Invoked whenever the current tab actor's document global is created.
    */
   _onGlobalCreated: function ({window, id, isTopLevel}) {
+    if (!this._initialized) {
+      return;
+    }
+
     // TODO: bug 981748, support more than just the top-level documents.
     if (!isTopLevel) {
       return;
     }
 
     let self = this;
     this._tracedWindowId = id;
 
--- a/devtools/server/actors/childtab.js
+++ b/devtools/server/actors/childtab.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+var { Cr } = require("chrome");
 var { TabActor } = require("devtools/server/actors/webbrowser");
 
 /**
  * Tab actor for documents living in a child process.
  *
  * Depends on TabActor, defined in webbrowser.js.
  */
 
@@ -49,20 +50,32 @@ Object.defineProperty(ContentActor.proto
     return this.window.document.title;
   },
   enumerable: true,
   configurable: true
 });
 
 ContentActor.prototype.exit = function () {
   if (this._sendForm) {
-    this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
+    try {
+      this._chromeGlobal.removeMessageListener("debug:form", this._sendForm);
+    } catch (e) {
+      if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+        throw e;
+      }
+      // In some cases, especially when using messageManagers in non-e10s mode, we reach
+      // this point with a dead messageManager which only throws errors but does not
+      // seem to indicate in any other way that it is dead.
+    }
     this._sendForm = null;
   }
-  return TabActor.prototype.exit.call(this);
+
+  TabActor.prototype.exit.call(this);
+
+  this._chromeGlobal = null;
 };
 
 /**
  * On navigation events, our URL and/or title may change, so we update our
  * counterpart in the parent process that participates in the tab list.
  */
 ContentActor.prototype._sendForm = function () {
   this._chromeGlobal.sendAsyncMessage("debug:form", this.form());
--- a/devtools/server/actors/director-manager.js
+++ b/devtools/server/actors/director-manager.js
@@ -1,10 +1,8 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const events = require("sdk/event/core");
 const protocol = require("devtools/shared/protocol");
@@ -120,18 +118,23 @@ var MessagePortActor = exports.MessagePo
    * raise an exception if the port is null
    */
   close: function () {
     if (!this.port) {
       console.error(ERR_MESSAGEPORT_FINALIZED);
       return;
     }
 
-    this.port.onmessage = null;
-    this.port.close();
+    try {
+      this.port.onmessage = null;
+      this.port.close();
+    } catch (e) {
+      // The port might be a dead object
+      console.error(e);
+    }
   },
 
   finalize: function () {
     this.close();
     this.port = null;
   },
 });
 
--- a/devtools/server/actors/director-registry.js
+++ b/devtools/server/actors/director-registry.js
@@ -146,79 +146,80 @@ exports.setupParentProcess = function se
         return DirectorRegistry.list();
       default:
         console.error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD, msg.json.method);
         throw new Error(ERR_DIRECTOR_PARENT_UNKNOWN_METHOD);
     }
   }
 };
 
-// skip child setup if this actor module is not running in a child process
-if (DebuggerServer.isInChildProcess) {
-  setupChildProcess();
-}
-
-function setupChildProcess() {
-  const { sendSyncMessage } = DebuggerServer.parentMessageManager;
-
-  DebuggerServer.setupInParent({
-    module: "devtools/server/actors/director-registry",
-    setupParent: "setupParentProcess"
-  });
-
-  DirectorRegistry.install = notImplemented.bind(null, "install");
-  DirectorRegistry.uninstall = notImplemented.bind(null, "uninstall");
-  DirectorRegistry.clear = notImplemented.bind(null, "clear");
-
-  DirectorRegistry.get = callParentProcess.bind(null, "get");
-  DirectorRegistry.list = callParentProcess.bind(null, "list");
-
-  /* child process helpers */
-
-  function notImplemented(method) {
-    console.error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD, method);
-    throw Error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD);
-  }
-
-  function callParentProcess(method, ...args) {
-    var reply = sendSyncMessage("debug:director-registry-request", {
-      method: method,
-      args: args
-    });
-
-    if (reply.length === 0) {
-      console.error(ERR_DIRECTOR_CHILD_NO_REPLY);
-      throw Error(ERR_DIRECTOR_CHILD_NO_REPLY);
-    } else if (reply.length > 1) {
-      console.error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
-      throw Error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
-    }
-
-    return reply[0];
-  }
-}
-
 /**
  * The DirectorRegistry Actor is a global actor which manages install/uninstall of
  * director scripts definitions.
  */
 const DirectorRegistryActor = exports.DirectorRegistryActor = protocol.ActorClassWithSpec(directorRegistrySpec, {
   /* init & destroy methods */
   initialize: function (conn, parentActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
+    this.maybeSetupChildProcess(conn);
   },
   destroy: function (conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
     this.finalize();
   },
 
   finalize: function () {
     // nothing to cleanup
   },
 
+  maybeSetupChildProcess(conn) {
+    // skip child setup if this actor module is not running in a child process
+    if (!DebuggerServer.isInChildProcess) {
+      return;
+    }
+
+    const { sendSyncMessage } = conn.parentMessageManager;
+
+    conn.setupInParent({
+      module: "devtools/server/actors/director-registry",
+      setupParent: "setupParentProcess"
+    });
+
+    DirectorRegistry.install = notImplemented.bind(null, "install");
+    DirectorRegistry.uninstall = notImplemented.bind(null, "uninstall");
+    DirectorRegistry.clear = notImplemented.bind(null, "clear");
+
+    DirectorRegistry.get = callParentProcess.bind(null, "get");
+    DirectorRegistry.list = callParentProcess.bind(null, "list");
+
+    /* child process helpers */
+
+    function notImplemented(method) {
+      console.error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD, method);
+      throw Error(ERR_DIRECTOR_CHILD_NOTIMPLEMENTED_METHOD);
+    }
+
+    function callParentProcess(method, ...args) {
+      var reply = sendSyncMessage("debug:director-registry-request", {
+        method: method,
+        args: args
+      });
+
+      if (reply.length === 0) {
+        console.error(ERR_DIRECTOR_CHILD_NO_REPLY);
+        throw Error(ERR_DIRECTOR_CHILD_NO_REPLY);
+      } else if (reply.length > 1) {
+        console.error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
+        throw Error(ERR_DIRECTOR_CHILD_MULTIPLE_REPLIES);
+      }
+
+      return reply[0];
+    }
+  },
+
   /**
    * Install a new director-script definition.
    *
    * @param String id
    *        The director-script definition identifier.
    * @param String scriptCode
    *        The director-script javascript source.
    * @param Object scriptOptions
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -191,16 +191,24 @@ RootActor.prototype = {
       from: this.actorID,
       applicationType: this.applicationType,
       /* This is not in the spec, but it's used by tests. */
       testConnectionPrefix: this.conn.prefix,
       traits: this.traits
     };
   },
 
+  forwardingCancelled: function (prefix) {
+    return {
+      from: this.actorID,
+      type: "forwardingCancelled",
+      prefix,
+    };
+  },
+
   /**
    * Disconnects the actor from the browser window.
    */
   disconnect: function () {
     /* Tell the live lists we aren't watching any more. */
     if (this._parameters.tabList) {
       this._parameters.tabList.onListChanged = null;
     }
--- a/devtools/server/actors/utils/actor-registry-utils.js
+++ b/devtools/server/actors/utils/actor-registry-utils.js
@@ -15,19 +15,31 @@ const promise = require("promise");
  * Support for actor registration. Main used by ActorRegistryActor
  * for dynamic registration of new actors.
  *
  * @param sourceText {String} Source of the actor implementation
  * @param fileName {String} URL of the actor module (for proper stack traces)
  * @param options {Object} Configuration object
  */
 exports.registerActor = function (sourceText, fileName, options) {
+  // Register in the current process
+  exports.registerActorInCurrentProcess(sourceText, fileName, options);
+  // Register in any child processes
+  return DebuggerServer.setupInChild({
+    module: "devtools/server/actors/utils/actor-registry-utils",
+    setupChild: "registerActorInCurrentProcess",
+    args: [sourceText, fileName, options],
+    waitForEval: true
+  });
+};
+
+exports.registerActorInCurrentProcess = function (sourceText, fileName, options) {
   const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
   const sandbox = Cu.Sandbox(principal);
-  const exports = sandbox.exports = {};
+  sandbox.exports = {};
   sandbox.require = require;
 
   Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
 
   let { prefix, constructor, type } = options;
 
   if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) {
     DebuggerServer.addGlobalActor({
@@ -37,41 +49,30 @@ exports.registerActor = function (source
   }
 
   if (type.tab && !DebuggerServer.tabActorFactories.hasOwnProperty(prefix)) {
     DebuggerServer.addTabActor({
       constructorName: constructor,
       constructorFun: sandbox[constructor]
     }, prefix);
   }
-
-  // Also register in all child processes in case the current scope
-  // is chrome parent process.
-  if (!DebuggerServer.isInChildProcess) {
-    return DebuggerServer.setupInChild({
-      module: "devtools/server/actors/utils/actor-registry-utils",
-      setupChild: "registerActor",
-      args: [sourceText, fileName, options],
-      waitForEval: true
-    });
-  }
-  return promise.resolve();
 };
 
 exports.unregisterActor = function (options) {
+  // Unregister in the current process
+  exports.unregisterActorInCurrentProcess(options);
+  // Unregister in any child processes
+  DebuggerServer.setupInChild({
+    module: "devtools/server/actors/utils/actor-registry-utils",
+    setupChild: "unregisterActorInCurrentProcess",
+    args: [options]
+  });
+};
+
+exports.unregisterActorInCurrentProcess = function (options) {
   if (options.tab) {
     DebuggerServer.removeTabActor(options);
   }
 
   if (options.global) {
     DebuggerServer.removeGlobalActor(options);
   }
-
-  // Also unregister it from all child processes in case the current
-  // scope is chrome parent process.
-  if (!DebuggerServer.isInChildProcess) {
-    DebuggerServer.setupInChild({
-      module: "devtools/server/actors/utils/actor-registry-utils",
-      setupChild: "unregisterActor",
-      args: [options]
-    });
-  }
 };
--- a/devtools/server/actors/webaudio.js
+++ b/devtools/server/actors/webaudio.js
@@ -699,16 +699,24 @@ var WebAudioActor = exports.WebAudioActo
     }
   },
 
   /**
    * Ensures that the new global has recording on
    * so we can proxy the function calls.
    */
   _onGlobalCreated: function () {
+    // Used to track when something is happening with the web audio API
+    // the first time, to ultimately fire `start-context` event
+    this._firstNodeCreated = false;
+
+    // Clear out stored nativeIDs on reload as we do not want to track
+    // AudioNodes that are no longer on this document.
+    this._nativeToActorID.clear();
+
     this._callWatcher.resumeRecording();
   },
 
   /**
    * Fired when an automation event is added to an AudioNode.
    */
   _onAutomationEvent: function ({node, paramName, eventName, args}) {
     emit(this, "automation-event", {
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -310,20 +310,16 @@ BrowserTabList.prototype._getChildren = 
   return gBrowser.browsers.filter(browser => {
     // Filter tabs that are closing. listTabs calls made right after TabClose
     // events still list tabs in process of being closed.
     let tab = gBrowser.getTabForBrowser(browser);
     return !tab.closing;
   });
 };
 
-BrowserTabList.prototype._isRemoteBrowser = function (browser) {
-  return browser.getAttribute("remote") == "true";
-};
-
 BrowserTabList.prototype.getList = function () {
   let topXULWindow = Services.wm.getMostRecentWindow(
     DebuggerServer.chromeWindowType);
   let selectedBrowser = null;
   if (topXULWindow) {
     selectedBrowser = this._getSelectedBrowser(topXULWindow);
   }
 
@@ -362,27 +358,22 @@ BrowserTabList.prototype.getList = funct
 };
 
 BrowserTabList.prototype._getActorForBrowser = function (browser) {
   // Do we have an existing actor for this browser? If not, create one.
   let actor = this._actorByBrowser.get(browser);
   if (actor) {
     this._foundCount++;
     return actor.update();
-  } else if (this._isRemoteBrowser(browser)) {
-    actor = new RemoteBrowserTabActor(this._connection, browser);
-    this._actorByBrowser.set(browser, actor);
-    this._checkListening();
-    return actor.connect();
   }
 
   actor = new BrowserTabActor(this._connection, browser);
   this._actorByBrowser.set(browser, actor);
   this._checkListening();
-  return promise.resolve(actor);
+  return actor.connect();
 };
 
 BrowserTabList.prototype.getTab = function ({ outerWindowID, tabId }) {
   if (typeof outerWindowID == "number") {
     // First look for in-process frames with this ID
     let window = Services.wm.getOuterWindowWithId(outerWindowID);
     // Safety check to prevent debugging top level window via getTab
     if (window instanceof Ci.nsIDOMChromeWindow) {
@@ -613,18 +604,17 @@ DevToolsUtils.makeInfallible(function (e
     case "TabClose": {
       let actor = this._actorByBrowser.get(browser);
       if (actor) {
         this._handleActorClose(actor, browser);
       }
       break;
     }
     case "TabRemotenessChange": {
-      // We have to remove the cached actor as we have to create a new instance
-      // based on BrowserTabActor or RemoteBrowserTabActor.
+      // We have to remove the cached actor as we have to create a new instance.
       let actor = this._actorByBrowser.get(browser);
       if (actor) {
         this._actorByBrowser.delete(browser);
         // Don't create a new actor; iterate will take care of that. Just notify.
         this._notifyListChanged();
         this._checkListening();
       }
       break;
@@ -841,19 +831,18 @@ exports.BrowserTabList = BrowserTabList;
  * Note that *all* these events are dispatched in the following order
  * when we switch the context of the TabActor to a given iframe:
  *  - will-navigate
  *  - window-destroyed
  *  - changed-toplevel-document
  *  - window-ready
  *  - navigate
  *
- * This class is subclassed by BrowserTabActor and
- * ContentActor. Subclasses are expected to implement a getter
- * for the docShell property.
+ * This class is subclassed by ContentActor and others.
+ * Subclasses are expected to implement a getter for the docShell property.
  *
  * @param connection DebuggerServerConnection
  *        The conection to the client.
  */
 function TabActor(connection) {
   this.conn = connection;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
@@ -2138,134 +2127,49 @@ TabActor.prototype.requestTypes = {
   "listFrames": TabActor.prototype.onListFrames,
   "listWorkers": TabActor.prototype.onListWorkers,
   "resolveLocation": TabActor.prototype.onResolveLocation
 };
 
 exports.TabActor = TabActor;
 
 /**
- * Creates a tab actor for handling requests to a single in-process
- * <xul:browser> tab, or <html:iframe>.
- * Most of the implementation comes from TabActor.
+ * Creates a tab actor for handling requests to a single browser frame.
+ * Both <xul:browser> and <iframe mozbrowser> are supported.
+ * 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 connection DebuggerServerConnection
- *        The connection to the client.
- * @param browser browser
- *        The frame instance that contains this tab.
+ * @param connection The main RDP connection.
+ * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
  */
 function BrowserTabActor(connection, browser) {
-  TabActor.call(this, connection, browser);
-  this._browser = browser;
-  if (typeof browser.getTabBrowser == "function") {
-    this._tabbrowser = browser.getTabBrowser();
-  }
-
-  Object.defineProperty(this, "docShell", {
-    value: this._browser.docShell,
-    configurable: true
-  });
-}
-
-BrowserTabActor.prototype = Object.create(TabActor.prototype);
-
-BrowserTabActor.prototype.constructor = BrowserTabActor;
-
-Object.defineProperty(BrowserTabActor.prototype, "title", {
-  get() {
-    // On Fennec, we can check the session store data for zombie tabs
-    if (this._browser.__SS_restore) {
-      let sessionStore = this._browser.__SS_data;
-      // Get the last selected entry
-      let entry = sessionStore.entries[sessionStore.index - 1];
-      return entry.title;
-    }
-    let title = this.contentDocument.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) {
-      let tab = this._tabbrowser._getTabForContentWindow(this.window);
-      if (tab) {
-        title = tab.label;
-      }
-    }
-    return title;
-  },
-  enumerable: true,
-  configurable: false
-});
-
-Object.defineProperty(BrowserTabActor.prototype, "url", {
-  get() {
-    // On Fennec, we can check the session store data for zombie tabs
-    if (this._browser.__SS_restore) {
-      let sessionStore = this._browser.__SS_data;
-      // Get the last selected entry
-      let entry = sessionStore.entries[sessionStore.index - 1];
-      return entry.url;
-    }
-    if (this.webNavigation.currentURI) {
-      return this.webNavigation.currentURI.spec;
-    }
-    return null;
-  },
-  enumerable: true,
-  configurable: true
-});
-
-Object.defineProperty(BrowserTabActor.prototype, "browser", {
-  get() {
-    return this._browser;
-  },
-  enumerable: true,
-  configurable: false
-});
-
-BrowserTabActor.prototype.disconnect = function () {
-  TabActor.prototype.disconnect.call(this);
-  this._browser = null;
-  this._tabbrowser = null;
-};
-
-BrowserTabActor.prototype.exit = function () {
-  TabActor.prototype.exit.call(this);
-  this._browser = null;
-  this._tabbrowser = null;
-};
-
-exports.BrowserTabActor = BrowserTabActor;
-
-/**
- * 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 connection The main RDP connection.
- * @param browser XUL <browser> element to connect to.
- */
-function RemoteBrowserTabActor(connection, browser) {
   this._conn = connection;
   this._browser = browser;
   this._form = null;
 }
 
-RemoteBrowserTabActor.prototype = {
+BrowserTabActor.prototype = {
   connect() {
     let onDestroy = () => {
       this._form = null;
     };
-    let connect = DebuggerServer.connectToChild(
-      this._conn, this._browser, onDestroy);
+    let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
     return connect.then(form => {
       this._form = form;
       return this;
     });
   },
 
+  get _tabbrowser() {
+    if (typeof this._browser.getTabBrowser == "function") {
+      return this._browser.getTabBrowser();
+    }
+    return null;
+  },
+
   get _mm() {
     // Get messageManager from XUL browser (which might be a specialized tunnel for RDM)
     // or else fallback to asking the frameLoader itself.
     return this._browser.messageManager ||
            this._browser.frameLoader.messageManager;
   },
 
   update() {
@@ -2286,26 +2190,75 @@ RemoteBrowserTabActor.prototype = {
       this._mm.addMessageListener("debug:form", onFormUpdate);
       this._mm.sendAsyncMessage("debug:form");
       return deferred.promise;
     }
 
     return this.connect();
   },
 
+  /**
+   * If we don't have a title from the content side because it's a zombie tab, try to find
+   * it on the chrome side.
+   */
+  get title() {
+    // On Fennec, we can check the session store data for zombie tabs
+    if (this._browser.__SS_restore) {
+      let sessionStore = this._browser.__SS_data;
+      // Get the last selected entry
+      let entry = sessionStore.entries[sessionStore.index - 1];
+      return entry.title;
+    }
+    // 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 (this._tabbrowser) {
+      let tab = this._tabbrowser.getTabForBrowser(this._browser);
+      if (tab) {
+        return tab.label;
+      }
+    }
+    return "";
+  },
+
+  /**
+   * If we don't have a url from the content side because it's a zombie tab, try to find
+   * it on the chrome side.
+   */
+  get url() {
+    // On Fennec, we can check the session store data for zombie tabs
+    if (this._browser.__SS_restore) {
+      let sessionStore = this._browser.__SS_data;
+      // Get the last selected entry
+      let entry = sessionStore.entries[sessionStore.index - 1];
+      return entry.url;
+    }
+    return null;
+  },
+
   form() {
-    return this._form;
+    let form = Object.assign({}, this._form);
+    // In some cases, the title and url fields might be empty.  Zombie tabs (not yet
+    // restored) are a good example.  In such cases, try to look up values for these
+    // fields using other data in the parent process.
+    if (!form.title) {
+      form.title = this.title;
+    }
+    if (!form.url) {
+      form.url = this.url;
+    }
+    return form;
   },
 
   exit() {
     this._browser = null;
   },
 };
 
-exports.RemoteBrowserTabActor = RemoteBrowserTabActor;
+exports.BrowserTabActor = BrowserTabActor;
 
 function BrowserAddonList(connection) {
   this._connection = connection;
   this._actorByAddonId = new Map();
   this._onListChanged = null;
 }
 
 BrowserAddonList.prototype.getList = function () {
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -603,17 +603,19 @@ WebConsoleActor.prototype =
         case "NetworkActivity":
           if (!this.networkMonitor) {
             // Create a StackTraceCollector that's going to be shared both by the
             // NetworkMonitorChild (getting messages about requests from parent) and
             // by the NetworkMonitor that directly watches service workers requests.
             this.stackTraceCollector = new StackTraceCollector({ window, appId });
             this.stackTraceCollector.init();
 
-            if (appId || messageManager) {
+            let processBoundary = Services.appinfo.processType !=
+                                  Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+            if ((appId || messageManager) && processBoundary) {
               // Start a network monitor in the parent process to listen to
               // most requests than happen in parent
               this.networkMonitor =
                 new NetworkMonitorChild(appId, messageManager,
                                         this.parentActor.actorID, this);
               this.networkMonitor.init();
               // Spawn also one in the child to listen to service workers
               this.networkMonitorChild = new NetworkMonitor({ window }, this);
--- a/devtools/server/child.js
+++ b/devtools/server/child.js
@@ -1,32 +1,33 @@
 /* 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";
 
-/* global addMessageListener, removeMessageListener, sendAsyncMessage */
+/* global addEventListener, addMessageListener, removeMessageListener, sendAsyncMessage */
 
 try {
   var chromeGlobal = this;
 
   // Encapsulate in its own scope to allows loading this frame script more than once.
   (function () {
-    let Cu = Components.utils;
-    let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    const Cu = Components.utils;
+    const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
     const DevToolsUtils = require("devtools/shared/DevToolsUtils");
     const { dumpn } = DevToolsUtils;
     const { DebuggerServer, ActorPool } = require("devtools/server/main");
 
-    // Note that this frame script may be evaluated in non-e10s build. In such case,
-    // DebuggerServer is already going to be initialized.
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
-      DebuggerServer.isInChildProcess = true;
+      // For non-e10s mode, there is only one server instance, so be sure the browser
+      // actors get loaded.
+      DebuggerServer.addBrowserActors();
     }
 
     // In case of apps being loaded in parent process, DebuggerServer is already
     // initialized, but child specific actors are not registered. Otherwise, for apps in
     // child process, we need to load actors the first time we load child.js.
     DebuggerServer.addChildActors();
 
     let connections = new Map();
@@ -93,16 +94,26 @@ try {
       let conn = connections.get(prefix);
       if (conn) {
         conn.close();
         connections.delete(prefix);
       }
     });
     addMessageListener("debug:disconnect", onDisconnect);
 
+    // In non-e10s mode, the "debug:disconnect" message isn't always received before the
+    // messageManager connection goes away.  Watching for "unload" here ensures we close
+    // any connections when the frame is unloaded.
+    addEventListener("unload", () => {
+      for (let conn of connections.values()) {
+        conn.close();
+      }
+      connections.clear();
+    });
+
     let onInspect = DevToolsUtils.makeInfallible(function (msg) {
       // Store the node to be inspected in a global variable (gInspectingNode). Later
       // we'll fetch this variable again using the findInspectingNode request over the
       // remote debugging protocol.
       let inspector = require("devtools/server/actors/inspector");
       inspector.setInspectingNode(msg.objects.node);
     });
     addMessageListener("debug:inspect", onInspect);
--- a/devtools/server/content-server.jsm
+++ b/devtools/server/content-server.jsm
@@ -18,17 +18,16 @@ function init(msg) {
   // the debugger with all devtools modules, nor break the debugger itself with using it
   // in the same process.
   let devtools = new DevToolsLoader();
   devtools.invisibleToDebugger = true;
   let { DebuggerServer, ActorPool } = devtools.require("devtools/server/main");
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
-    DebuggerServer.isInChildProcess = true;
   }
 
   // In case of apps being loaded in parent process, DebuggerServer is already
   // initialized, but child specific actors are not registered.
   // Otherwise, for child process, we need to load actors the first
   // time we load child.js
   DebuggerServer.addChildActors();
 
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -891,39 +891,37 @@ var DebuggerServer = {
           });
         }
       };
       dbg.addListener(listener);
     });
   },
 
   /**
-   * Check if the caller is running in a content child process.
-   * (Eventually set by child.js)
-   *
-   * @return boolean
-   *         true if the caller is running in a content
+   * Check if the server is running in the child process.
    */
-  isInChildProcess: false,
+  get isInChildProcess() {
+    return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+  },
 
   /**
    * In a chrome parent process, ask all content child processes
    * to execute a given module setup helper.
    *
    * @param module
    *        The module to be required
    * @param setupChild
    *        The name of the setup helper exported by the above module
    *        (setup helper signature: function ({mm}) { ... })
    * @param waitForEval (optional)
    *        If true, the returned promise only resolves once code in child
    *        is evaluated
    */
   setupInChild({ module, setupChild, args, waitForEval }) {
-    if (this.isInChildProcess || this._childMessageManagers.size == 0) {
+    if (this._childMessageManagers.size == 0) {
       return Promise.resolve();
     }
     let deferred = Promise.defer();
 
     // If waitForEval is set, pass a unique id and expect child.js to send
     // a message back once the code in child is evaluated.
     if (typeof (waitForEval) != "boolean") {
       waitForEval = false;
@@ -1295,17 +1293,32 @@ var DebuggerServer = {
       if ((handler.name && handler.name == actor.name) ||
           (handler.id && handler.id == actor.id)) {
         delete DebuggerServer.globalActorFactories[name];
         for (let connID of Object.getOwnPropertyNames(this._connections)) {
           this._connections[connID].rootActor.removeActorByName(name);
         }
       }
     }
-  }
+  },
+
+  /**
+   * ⚠ TESTING ONLY! ⚠ Searches all active connections for an actor matching an ID.
+   * This is helpful for some tests which depend on reaching into the server to check some
+   * properties of an actor.
+   */
+  _searchAllConnectionsForActor(actorID) {
+    for (let connID of Object.getOwnPropertyNames(this._connections)) {
+      let actor = this._connections[connID].getActor(actorID);
+      if (actor) {
+        return actor;
+      }
+    }
+    return null;
+  },
 };
 
 // Expose these to save callers the trouble of importing DebuggerSocket
 DevToolsUtils.defineLazyGetter(DebuggerServer, "Authenticators", () => {
   return Authentication.Authenticators;
 });
 DevToolsUtils.defineLazyGetter(DebuggerServer, "AuthenticationResult", () => {
   return Authentication.AuthenticationResult;
@@ -1387,17 +1400,19 @@ DebuggerServerConnection.prototype = {
   /**
    * Message manager used to communicate with the parent process,
    * set by child.js. Is only defined for connections instantiated
    * within a child process.
    */
   parentMessageManager: null,
 
   close() {
-    this._transport.close();
+    if (this._transport) {
+      this._transport.close();
+    }
   },
 
   send(packet) {
     this.transport.send(packet);
   },
 
   /**
    * Used when sending a bulk reply from an actor.
@@ -1591,16 +1606,23 @@ DebuggerServerConnection.prototype = {
   },
 
   /*
    * Stop forwarding messages to actors whose names begin with
    * |prefix+'/'|. Such messages will now elicit 'noSuchActor' errors.
    */
   cancelForwarding(prefix) {
     this._forwardingPrefixes.delete(prefix);
+
+    // Notify the client that forwarding in now cancelled for this prefix.
+    // There could be requests in progress that the client should abort rather leaving
+    // handing indefinitely.
+    if (this.rootActor) {
+      this.send(this.rootActor.forwardingCancelled(prefix));
+    }
   },
 
   sendActorEvent(actorID, eventName, event = {}) {
     event.from = actorID;
     event.type = eventName;
     this.send(event);
   },
 
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -62,19 +62,19 @@ skip-if = e10s # Bug 1183605 - devtools/
 [browser_markers-styles.js]
 [browser_markers-timestamp.js]
 [browser_navigateEvents.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-allocation-data.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-profiler-01.js]
 [browser_perf-profiler-02.js]
-skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+skip-if = true # Needs to be updated for async actor destruction
 [browser_perf-profiler-03.js]
-skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+skip-if = true # Needs to be updated for async actor destruction
 [browser_perf-realtime-markers.js]
 [browser_perf-recording-actor-01.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-recording-actor-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-samples-01.js]
 [browser_perf-samples-02.js]
 #[browser_perf-front-profiler-01.js] bug 1077464
--- a/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -27,26 +27,26 @@ function assertEvent(event, data) {
     case x++:
       is(event, "unload-dialog", "We get the dialog on first page unload");
       break;
     case x++:
       is(event, "will-navigate", "The very first event is will-navigate on server side");
       is(data.newURI, URL2, "newURI property is correct");
       break;
     case x++:
-      is(event, "tabNavigated", "Right after will-navigate, the client receive tabNavigated");
+      is(event, "request", "RDP is async with messageManager, the request happens after will-navigate");
+      is(data, URL2);
+      break;
+    case x++:
+      is(event, "tabNavigated", "After the request, the client receive tabNavigated");
       is(data.state, "start", "state is start");
       is(data.url, URL2, "url property is correct");
       is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct");
       break;
     case x++:
-      is(event, "request", "Given that locally, the Debugger protocol is sync, the request happens after tabNavigated");
-      is(data, URL2);
-      break;
-    case x++:
       is(event, "DOMContentLoaded");
       is(content.document.readyState, "interactive");
       break;
     case x++:
       is(event, "load");
       is(content.document.readyState, "complete");
       break;
     case x++:
@@ -101,18 +101,17 @@ function getServerTabActor(callback) {
 
   // Connect to this tab
   let transport = DebuggerServer.connectPipe();
   client = new DebuggerClient(transport);
   connectDebuggerClient(client).then(form => {
     let actorID = form.actor;
     client.attachTab(actorID, function (aResponse, aTabClient) {
       // !Hack! Retrieve a server side object, the BrowserTabActor instance
-      let conn = transport._serverConnection;
-      let tabActor = conn.getActor(actorID);
+      let tabActor = DebuggerServer._searchAllConnectionsForActor(actorID);
       callback(tabActor);
     });
   });
 
   client.addListener("tabNavigated", function (aEvent, aPacket) {
     assertEvent("tabNavigated", aPacket);
   });
 }
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -116,18 +116,17 @@ function serverOwnershipSubtree(walker, 
   }
   return {
     name: actor.actorID,
     children: sortOwnershipChildren(children)
   };
 }
 
 function serverOwnershipTree(walker) {
-  let serverConnection = walker.conn._transport._serverConnection;
-  let serverWalker = serverConnection.getActor(walker.actorID);
+  let serverWalker = DebuggerServer._searchAllConnectionsForActor(walker.actorID);
 
   return {
     root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
     orphaned: [...serverWalker._orphaned].map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
     retained: [...serverWalker._retainedOrphans].map(o => serverOwnershipSubtree(serverWalker, o.rawNode))
   };
 }
 
--- a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
+++ b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
@@ -42,18 +42,17 @@ window.onload = function() {
 
   addAsyncTest(function* testActorLifetime() {
 
     info ("Testing animated node actor");
     let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode,
       ".animated");
     yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
 
-    let serverConnection = animationsFront.conn._transport._serverConnection;
-    let animationsActor = serverConnection.getActor(animationsFront.actorID);
+    let animationsActor = DebuggerServer._searchAllConnectionsForActor(animationsFront.actorID);
 
     is(animationsActor.actors.length, 1,
       "AnimationActor have 1 AnimationPlayerActors");
 
     info ("Testing AnimationPlayerActors release");
     let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode,
       ".still");
     yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
--- a/devtools/server/tests/mochitest/test_inspector-anonymous.html
+++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html
@@ -67,18 +67,17 @@ window.onload = function() {
     is (children.nodes.length, 2, "No native anon content for form control");
 
     runNextTest();
   });
 
   addAsyncTest(function* testNativeAnonymousStartingNode() {
     info ("Tests attaching an element that a walker can't see.");
 
-    let serverConnection = gWalker.conn._transport._serverConnection;
-    let serverWalker = serverConnection.getActor(gWalker.actorID);
+    let serverWalker = DebuggerServer._searchAllConnectionsForActor(gWalker.actorID);
     let docwalker = new _documentWalker(
       gInspectee.querySelector("select"),
       gInspectee.defaultView,
       nodeFilterConstants.SHOW_ALL,
       () => {
         return nodeFilterConstants.FILTER_ACCEPT
       }
     );
--- a/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
+++ b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
@@ -37,314 +37,336 @@ addAsyncTest(function() {
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.parents(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.parents(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.parents() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.children(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.children(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.children() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.siblings(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.siblings(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.siblings() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.nextSibling(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.nextSibling(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.nextSibling() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.previousSibling(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.previousSibling(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.previousSibling() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.addPseudoClassLock(nodeFront) before the load completes " +
     "shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.addPseudoClassLock(nodeFront, ":hover");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.addPseudoClassLock() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.removePseudoClassLock(nodeFront) before the load completes " +
     "shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.removePseudoClassLock(nodeFront, ":hover");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.removePseudoClassLock() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.clearPseudoClassLocks(nodeFront) before the load completes " +
     "shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.clearPseudoClassLocks(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.clearPseudoClassLocks() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.innerHTML(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.innerHTML(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.innerHTML() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.setInnerHTML() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.outerHTML(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.outerHTML(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.outerHTML() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.setOuterHTML() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " +
     "fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.insertAdjacentHTML(nodeFront, "afterEnd",
     "<span>new adjacent HTML</span>");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.insertAdjacentHTML() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.removeNode(nodeFront) before the load completes should throw");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   let hasThrown = false;
   try {
     yield gWalker.removeNode(nodeFront);
   } catch (e) {
     hasThrown = true;
   }
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(hasThrown, "The call to walker.removeNode() threw");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.removeNodes([nodeFront]) before the load completes should throw");
 
   let nodeFront1 = yield gWalker.querySelector(gWalker.rootNode, "h1");
   let nodeFront2 = yield gWalker.querySelector(gWalker.rootNode, "#longstring");
   let nodeFront3 = yield gWalker.querySelector(gWalker.rootNode, "#shortstring");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   let hasThrown = false;
   try {
     yield gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]);
   } catch (e) {
     hasThrown = true;
   }
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(hasThrown, "The call to walker.removeNodes() threw");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.insertBefore(nodeFront, parent, null) before the load completes " +
     "shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
   let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.insertBefore(nodeFront, newParentFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.insertBefore() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.insertBefore(nodeFront, parent, sibling) before the load completes " +
     "shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
   let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
   let siblingFront = yield gWalker.querySelector(gWalker.rootNode, "#b");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.insertBefore(nodeFront, newParentFront, siblingFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.insertBefore() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.editTagName(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.editTagName(nodeFront, "h2");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.editTagName() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.hideNode(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.hideNode(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.hideNode() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.unhideNode(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.unhideNode(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.unhideNode() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.releaseNode(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.releaseNode(nodeFront);
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.releaseNode() didn't fail");
   runNextTest();
 });
 
 addAsyncTest(function() {
   info("Getting a nodeFront, reloading the page, and calling " +
     "walker.querySelector(nodeFront) before the load completes shouldn't fail");
 
   let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+  let newRoot = waitForMutation(gWalker, isNewRoot);
   gDoc.defaultView.location.reload();
   yield gWalker.querySelector(nodeFront, "h1");
-  yield waitForMutation(gWalker, isNewRoot);
+  yield newRoot;
 
   ok(true, "The call to walker.querySelector() didn't fail");
   runNextTest();
 });
 
 addTest(function cleanup() {
   gWalker = gDoc = null;
   runNextTest();
--- a/devtools/server/tests/mochitest/test_inspector-search.html
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -41,18 +41,17 @@ window.onload = function() {
         inspector = InspectorFront(client, tab);
         resolve();
       });
     });
 
     let walkerFront = yield inspector.getWalker();
     ok(walkerFront, "getWalker() should return an actor.");
 
-    let serverConnection = walkerFront.conn._transport._serverConnection;
-    walkerActor = serverConnection.getActor(walkerFront.actorID);
+    walkerActor = DebuggerServer._searchAllConnectionsForActor(walkerFront.actorID);
     ok(walkerActor,
       "Got a reference to the walker actor (" + walkerFront.actorID + ")");
 
     walkerSearch = walkerActor.walkerSearch;
 
     runNextTest();
   });
 
--- a/devtools/server/tests/mochitest/test_setupInParentChild.html
+++ b/devtools/server/tests/mochitest/test_setupInParentChild.html
@@ -35,17 +35,17 @@ window.onload = function() {
 }
 
 function runTests() {
   // Create a minimal iframe with a message manager
   let iframe = document.createElement("iframe");
   iframe.mozbrowser = true;
   document.body.appendChild(iframe);
 
-  let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+  let mm = iframe.frameLoader.messageManager;
 
   // Instantiate a minimal server
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
   }
   if (!DebuggerServer.createRootActor) {
     DebuggerServer.addBrowserActors();
   }
--- a/devtools/server/tests/unit/test_client_request.js
+++ b/devtools/server/tests/unit/test_client_request.js
@@ -151,17 +151,17 @@ function test_close_client_while_sending
   let pendingRequest = gClient.request({
     to: gActorId,
     type: "hello"
   });
 
   let expectReply = promise.defer();
   gClient.expectReply("root", function (response) {
     do_check_eq(response.error, "connectionClosed");
-    do_check_eq(response.message, "server side packet from 'root' can't be received as the connection just closed.");
+    do_check_eq(response.message, "server side packet can't be received as the connection just closed.");
     expectReply.resolve();
   });
 
   gClient.close(() => {
     activeRequest.then(() => {
       ok(false, "First request unexpectedly succeed while closing the connection");
     }, response => {
       do_check_eq(response.error, "connectionClosed");
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -937,16 +937,26 @@ DebuggerClient.prototype = {
     // If we have a registered Front for this actor, let it handle the packet
     // and skip all the rest of this unpleasantness.
     let front = this.getActor(aPacket.from);
     if (front) {
       front.onPacket(aPacket);
       return;
     }
 
+    // Check for "forwardingCancelled" here instead of using a client to handle it.
+    // This is necessary because we might receive this event while the client is closing,
+    // and the clients have already been removed by that point.
+    if (this.mainRoot &&
+        aPacket.from == this.mainRoot.actor &&
+        aPacket.type == "forwardingCancelled") {
+      this.purgeRequests(aPacket.prefix);
+      return;
+    }
+
     if (this._clients.has(aPacket.from) && aPacket.type) {
       let client = this._clients.get(aPacket.from);
       let type = aPacket.type;
       if (client.events.indexOf(type) != -1) {
         client.emit(type, aPacket);
         // we ignore the rest, as the client is expected to handle this packet.
         return;
       }
@@ -1067,45 +1077,21 @@ DebuggerClient.prototype = {
 
   /**
    * Called by DebuggerTransport when the underlying stream is closed.
    *
    * @param aStatus nsresult
    *        The status code that corresponds to the reason for closing
    *        the stream.
    */
-  onClosed: function (aStatus) {
+  onClosed: function () {
     this._closed = true;
     this.emit("closed");
 
-    // Reject all pending and active requests
-    let reject = function (type, request, actor) {
-      // Server can send packets on its own and client only pass a callback
-      // to expectReply, so that there is no request object.
-      let msg;
-      if (request.request) {
-        msg = "'" + request.request.type + "' " + type + " request packet" +
-              " to '" + actor + "' " +
-              "can't be sent as the connection just closed.";
-      } else {
-        msg = "server side packet from '" + actor + "' can't be received " +
-              "as the connection just closed.";
-      }
-      let packet = { error: "connectionClosed", message: msg };
-      request.emit("json-reply", packet);
-    };
-
-    let pendingRequests = new Map(this._pendingRequests);
-    this._pendingRequests.clear();
-    pendingRequests.forEach((list, actor) => {
-      list.forEach(request => reject("pending", request, actor));
-    });
-    let activeRequests = new Map(this._activeRequests);
-    this._activeRequests.clear();
-    activeRequests.forEach(reject.bind(null, "active"));
+    this.purgeRequests();
 
     // The |_pools| array on the client-side currently is used only by
     // protocol.js to store active fronts, mirroring the actor pools found in
     // the server.  So, read all usages of "pool" as "protocol.js front".
     //
     // In the normal case where we shutdown cleanly, the toolbox tells each tool
     // to close, and they each call |destroy| on any fronts they were using.
     // When |destroy| or |cleanup| is called on a protocol.js front, it also
@@ -1119,16 +1105,61 @@ DebuggerClient.prototype = {
     // from |_pools|.  This saves the toolbox from hanging indefinitely, in case
     // it waits for some server response before shutdown that will now never
     // arrive.
     for (let pool of this._pools) {
       pool.cleanup();
     }
   },
 
+  /**
+   * Purge pending and active requests in this client.
+   *
+   * @param prefix string (optional)
+   *        If a prefix is given, only requests for actor IDs that start with the prefix
+   *        will be cleaned up.  This is useful when forwarding of a portion of requests
+   *        is cancelled on the server.
+   */
+  purgeRequests(prefix = "") {
+    let reject = function (type, request) {
+      // Server can send packets on its own and client only pass a callback
+      // to expectReply, so that there is no request object.
+      let msg;
+      if (request.request) {
+        msg = "'" + request.request.type + "' " + type + " request packet" +
+              " to '" + request.actor + "' " +
+              "can't be sent as the connection just closed.";
+      } else {
+        msg = "server side packet can't be received as the connection just closed.";
+      }
+      let packet = { error: "connectionClosed", message: msg };
+      request.emit("json-reply", packet);
+    };
+
+    let pendingRequestsToReject = [];
+    this._pendingRequests.forEach((requests, actor) => {
+      if (!actor.startsWith(prefix)) {
+        return;
+      }
+      this._pendingRequests.delete(actor);
+      pendingRequestsToReject = pendingRequestsToReject.concat(requests);
+    });
+    pendingRequestsToReject.forEach(request => reject("pending", request));
+
+    let activeRequestsToReject = [];
+    this._activeRequests.forEach((request, actor) => {
+      if (!actor.startsWith(prefix)) {
+        return;
+      }
+      this._activeRequests.delete(actor);
+      activeRequestsToReject = activeRequestsToReject.concat(request);
+    });
+    activeRequestsToReject.forEach(request => reject("active", request));
+  },
+
   registerClient: function (client) {
     let actorID = client.actor;
     if (!actorID) {
       throw new Error("DebuggerServer.registerClient expects " +
                       "a client instance with an `actor` attribute.");
     }
     if (!Array.isArray(client.events)) {
       throw new Error("DebuggerServer.registerClient expects " +
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -419,21 +419,22 @@ const NodeFront = FrontClassWithSpec(nod
   },
 
   /**
    * Get an nsIDOMNode for the given node front.  This only works locally,
    * and is only intended as a stopgap during the transition to the remote
    * protocol.  If you depend on this you're likely to break soon.
    */
   rawNode: function (rawNode) {
-    if (!this.conn._transport._serverConnection) {
+    if (!this.isLocalToBeDeprecated()) {
       console.warn("Tried to use rawNode on a remote connection.");
       return null;
     }
-    let actor = this.conn._transport._serverConnection.getActor(this.actorID);
+    const { DebuggerServer } = require("devtools/server/main");
+    let actor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
     if (!actor) {
       // Can happen if we try to get the raw node for an already-expired
       // actor.
       return null;
     }
     return actor.rawNode;
   }
 });
@@ -890,18 +891,18 @@ const WalkerFront = FrontClassWithSpec(w
 
   // XXX hack during transition to remote inspector: get a proper NodeFront
   // for a given local node.  Only works locally.
   frontForRawNode: function (rawNode) {
     if (!this.isLocal()) {
       console.warn("Tried to use frontForRawNode on a remote connection.");
       return null;
     }
-    let walkerActor = this.conn._transport._serverConnection
-      .getActor(this.actorID);
+    const { DebuggerServer } = require("devtools/server/main");
+    let walkerActor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
     if (!walkerActor) {
       throw Error("Could not find client side for actor " + this.actorID);
     }
     let nodeActor = walkerActor._ref(rawNode);
 
     // Pass the node through a read/write pair to create the client side actor.
     let nodeType = types.getType("domnode");
     let returnNode = nodeType.read(
@@ -911,17 +912,17 @@ const WalkerFront = FrontClassWithSpec(w
     for (let extraActor of extras) {
       top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
     }
 
     if (top !== this.rootNode) {
       // Imported an already-orphaned node.
       this._orphaned.add(top);
       walkerActor._orphaned
-        .add(this.conn._transport._serverConnection.getActor(top.actorID));
+        .add(DebuggerServer._searchAllConnectionsForActor(top.actorID));
     }
     return returnNode;
   },
 
   removeNode: custom(Task.async(function* (node) {
     let previousSibling = yield this.previousSibling(node);
     let nextSibling = yield this._removeNode(node);
     return {
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/transport.js
@@ -728,29 +728,47 @@
 
     hooks: null,
 
     ready: function () {
       this._sender.addMessageListener(this._messageName, this);
     },
 
     close: function () {
-      this._sender.removeMessageListener(this._messageName, this);
+      try {
+        this._sender.removeMessageListener(this._messageName, this);
+      } catch (e) {
+        if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+          throw e;
+        }
+        // In some cases, especially when using messageManagers in non-e10s mode, we reach
+        // this point with a dead messageManager which only throws errors but does not
+        // seem to indicate in any other way that it is dead.
+      }
       this.emit("onClosed");
       this.hooks.onClosed();
     },
 
     receiveMessage: function ({data}) {
       this.emit("onPacket", data);
       this.hooks.onPacket(data);
     },
 
     send: function (packet) {
       this.emit("send", packet);
-      this._sender.sendAsyncMessage(this._messageName, packet);
+      try {
+        this._sender.sendAsyncMessage(this._messageName, packet);
+      } catch (e) {
+        if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+          throw e;
+        }
+        // In some cases, especially when using messageManagers in non-e10s mode, we reach
+        // this point with a dead messageManager which only throws errors but does not
+        // seem to indicate in any other way that it is dead.
+      }
     },
 
     startBulkSend: function () {
       throw new Error("Can't send bulk data to child processes.");
     }
   };
 
   exports.ChildDebuggerTransport = ChildDebuggerTransport;