Bug 1485660 - Switch from listening from DebuggerClient to TargetFront. r=jdescottes
☠☠ backed out by 7a97fe41a34f ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 11 Oct 2018 15:46:28 +0000
changeset 499174 cfb160882db9439a359b47e3aa68383833d4877a
parent 499173 3642f2d6677725156a16768d7109e86a0583af09
child 499175 40a3c2dd1b38a33b31a99a6810b6df1f15dfb85d
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1485660
milestone64.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 1485660 - Switch from listening from DebuggerClient to TargetFront. r=jdescottes * debugger-controller and events.js are special and require to support two cases because this is the only production codepath that can have a TabTarget or a WorkerTarget. Thus, leading to either TargetFront or WorkerClient on target.activeTab. * webide.js doesn't need to listen for tabNavigated, this is redundant with tabListChanged. * application's initializer. In case you are wondering this code can't be spawn against a WorkerTarget. The application panel doesn't work in worker toolboxes. * The code modified in target is in TabTarget, so we don't have to support the WorkerClient case, we always have a TargetFront here. * I tried to update the doc file the best I can but this all feel outdated. MozReview-Commit-ID: 2hGchebfIub Depends on D7458 Differential Revision: https://phabricator.services.mozilla.com/D7459
devtools/client/application/initializer.js
devtools/client/debugger/debugger-controller.js
devtools/client/debugger/new/src/client/firefox/events.js
devtools/client/debugger/test/mochitest/head.js
devtools/client/framework/target.js
devtools/client/framework/test/browser_toolbox_target.js
devtools/client/shared/test/browser_dbg_navigation.js
devtools/client/webide/modules/tab-store.js
devtools/docs/backend/client-api.md
devtools/server/tests/browser/browser_navigateEvents.js
devtools/server/tests/browser/browser_webextension_inspected_window.js
devtools/server/tests/mochitest/webextension-helpers.js
devtools/server/tests/unit/test_sourcemaps-01.js
--- a/devtools/client/application/initializer.js
+++ b/devtools/client/application/initializer.js
@@ -38,18 +38,17 @@ window.Application = {
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
     const serviceContainer = {
       selectTool(toolId) {
         return toolbox.selectTool(toolId);
       }
     };
-
-    this.client.addListener("workerListChanged", this.updateWorkers);
+    this.toolbox.target.activeTab.on("workerListChanged", this.updateWorkers);
     this.client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     this.client.addListener("registration-changed", this.updateWorkers);
     this.client.addListener("processListChanged", this.updateWorkers);
     this.toolbox.target.on("navigate", this.updateDomain);
 
     this.updateDomain();
     await this.updateWorkers();
 
@@ -84,17 +83,17 @@ window.Application = {
     this.actions.updateWorkers(service);
   },
 
   updateDomain() {
     this.actions.updateDomain(this.toolbox.target.url);
   },
 
   destroy() {
-    this.client.removeListener("workerListChanged", this.updateWorkers);
+    this.toolbox.target.activeTab.off("workerListChanged", this.updateWorkers);
     this.client.removeListener("serviceWorkerRegistrationListChanged",
       this.updateWorkers);
     this.client.removeListener("registration-changed", this.updateWorkers);
     this.client.removeListener("processListChanged", this.updateWorkers);
 
     this.toolbox.target.off("navigate", this.updateDomain);
 
     unmountComponentAtNode(this.mount);
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -473,21 +473,32 @@ Workers.prototype = {
   },
 
   connect: function () {
     if (!Prefs.workersEnabled) {
       return;
     }
 
     this._updateWorkerList();
-    this._tabClient.addListener("workerListChanged", this._onWorkerListChanged);
+
+    // `_tabClient` can be BrowsingContextTargetFront (protocol.js front) or
+    // WorkerClient/DebuggerClient (old fashion client)
+    if (typeof(this._tabClient.on) == "function") {
+      this._tabClient.on("workerListChanged", this._onWorkerListChanged);
+    } else {
+      this._tabClient.addListener("workerListChanged", this._onWorkerListChanged);
+    }
   },
 
   disconnect: function () {
-    this._tabClient.removeListener("workerListChanged", this._onWorkerListChanged);
+    if (typeof(this._tabClient.on) == "function") {
+      this._tabClient.off("workerListChanged", this._onWorkerListChanged);
+    } else {
+      this._tabClient.removeListener("workerListChanged", this._onWorkerListChanged);
+    }
   },
 
   _updateWorkerList: function () {
     if (!this._tabClient.listWorkers) {
       return;
     }
 
     this._tabClient.listWorkers().then((response) => {
--- a/devtools/client/debugger/new/src/client/firefox/events.js
+++ b/devtools/client/debugger/new/src/client/firefox/events.js
@@ -36,17 +36,23 @@ function setupEvents(dependencies) {
   });
 
   if (threadClient) {
     Object.keys(clientEvents).forEach(eventName => {
       threadClient.addListener(eventName, clientEvents[eventName]);
     });
 
     if (threadClient._parent) {
-      threadClient._parent.addListener("workerListChanged", workerListChanged);
+      // Parent may be BrowsingContextTargetFront and be protocol.js.
+      // Or DebuggerClient/WorkerClient and still be old fashion actor.
+      if (threadClient._parent.on) {
+        threadClient._parent.on("workerListChanged", workerListChanged);
+      } else {
+        threadClient._parent.addListener("workerListChanged", workerListChanged);
+      }
     }
   }
 }
 
 async function paused(_, packet) {
   // If paused by an explicit interrupt, which are generated by the
   // slow script dialog and internal events such as setting
   // breakpoints, ignore the event.
@@ -108,9 +114,9 @@ function workerListChanged() {
 }
 
 const clientEvents = {
   paused,
   resumed,
   newSource
 };
 exports.setupEvents = setupEvents;
-exports.clientEvents = clientEvents;
\ No newline at end of file
+exports.clientEvents = clientEvents;
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -175,18 +175,18 @@ function getAddonActorForId(aClient, aAd
     deferred.resolve(addonTargetActor);
   });
 
   return deferred.promise;
 }
 
 async function attachTargetActorForUrl(aClient, aUrl) {
   let grip = await getTargetActorForUrl(aClient, aUrl);
-  let [ response ] = await aClient.attachTarget(grip.actor);
-  return [grip, response];
+  let [ response, front ] = await aClient.attachTarget(grip.actor);
+  return [grip, response, front];
 }
 
 async function attachThreadActorForUrl(aClient, aUrl) {
   let [grip, response] = await attachTargetActorForUrl(aClient, aUrl);
   let [response2, threadClient] = await aClient.attachThread(response.threadActor);
   await threadClient.resume();
   return threadClient;
 }
@@ -1104,24 +1104,19 @@ function findWorker(workers, url) {
   return null;
 }
 
 function attachWorker(tabClient, worker) {
   info("Attaching to worker with url '" + worker.url + "'.");
   return tabClient.attachWorker(worker.actor);
 }
 
-function waitForWorkerListChanged(tabClient) {
+function waitForWorkerListChanged(targetFront) {
   info("Waiting for worker list to change.");
-  return new Promise(function (resolve) {
-    tabClient.addListener("workerListChanged", function listener() {
-      tabClient.removeListener("workerListChanged", listener);
-      resolve();
-    });
-  });
+  return targetFront.once("workerListChanged");
 }
 
 function attachThread(workerClient, options) {
   info("Attaching to thread.");
   return workerClient.attachThread(options);
 }
 
 function waitForWorkerClose(workerClient) {
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -189,16 +189,17 @@ const TargetFactory = exports.TargetFact
  *                  frame scripts...
  * @param {xul:tab} tab (optional)
  *                  If the target is a local Firefox tab, a reference to the firefox
  *                  frontend tab object.
  */
 function TabTarget({ form, client, chrome, tab = null }) {
   EventEmitter.decorate(this);
   this.destroy = this.destroy.bind(this);
+  this._onTabNavigated = this._onTabNavigated.bind(this);
   this.activeTab = this.activeConsole = null;
 
   this._form = form;
   this._url = form.url;
   this._title = form.title;
 
   this._client = client;
   this._chrome = chrome;
@@ -513,16 +514,22 @@ TabTarget.prototype = {
       return this._attach;
     }
 
     // Attach the target actor
     const attachTarget = async () => {
       const [response, tabClient] = await this._client.attachTarget(this._form.actor);
       this.activeTab = tabClient;
       this.threadActor = response.threadActor;
+
+      this.activeTab.on("tabNavigated", this._onTabNavigated);
+      this._onFrameUpdate = packet => {
+        this.emit("frame-update", packet);
+      };
+      this.activeTab.on("frameUpdate", this._onFrameUpdate);
     };
 
     // Attach the console actor
     const attachConsole = async () => {
       const [, consoleClient] = await this._client.attachConsole(
         this._form.consoleActor, []);
       this.activeConsole = consoleClient;
 
@@ -544,25 +551,27 @@ TabTarget.prototype = {
           to: this._form.actor, type: "connect",
         });
 
         this._form = form;
         this._url = form.url;
         this._title = form.title;
       }
 
-      this._setupRemoteListeners();
-
       // AddonActor and chrome debugging on RootActor don't inherit from
       // BrowsingContextTargetActor (i.e. this.isBrowsingContext=false) and don't need
       // to be attached.
       if (this.isBrowsingContext) {
         await attachTarget();
       }
 
+      // _setupRemoteListeners has to be called after the potential call to `attachTarget`
+      // as it depends on `activeTab` which is set by this method.
+      this._setupRemoteListeners();
+
       // But all target actor have a console actor to attach
       return attachConsole();
     })();
 
     return this._attach;
   },
 
   /**
@@ -581,79 +590,104 @@ TabTarget.prototype = {
     if (this._tab.ownerDocument.defaultView) {
       this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
     }
     this._tab.removeEventListener("TabClose", this);
     this._tab.removeEventListener("TabRemotenessChange", this);
   },
 
   /**
+   * Event listener for tabNavigated packet sent by activeTab's front.
+   */
+  _onTabNavigated: function(packet) {
+    const event = Object.create(null);
+    event.url = packet.url;
+    event.title = packet.title;
+    event.nativeConsoleAPI = packet.nativeConsoleAPI;
+    event.isFrameSwitching = packet.isFrameSwitching;
+
+    // Keep the title unmodified when a developer toolbox switches frame
+    // for a tab (Bug 1261687), but always update the title when the target
+    // is a WebExtension (where the addon name is always included in the title
+    // and the url is supposed to be updated every time the selected frame changes).
+    if (!packet.isFrameSwitching || this.isWebExtension) {
+      this._url = packet.url;
+      this._title = packet.title;
+    }
+
+    // Send any stored event payload (DOMWindow or nsIRequest) for backwards
+    // compatibility with non-remotable tools.
+    if (packet.state == "start") {
+      event._navPayload = this._navRequest;
+      this.emit("will-navigate", event);
+      this._navRequest = null;
+    } else {
+      event._navPayload = this._navWindow;
+      this.emit("navigate", event);
+      this._navWindow = null;
+    }
+  },
+
+  /**
    * Setup listeners for remote debugging, updating existing ones as necessary.
    */
   _setupRemoteListeners: function() {
     this.client.addListener("closed", this.destroy);
 
-    this._onTabDetached = (type, packet) => {
-      // We have to filter message to ensure that this detach is for this tab
-      if (packet.from == this._form.actor) {
-        this.destroy();
-      }
-    };
-    this.client.addListener("tabDetached", this._onTabDetached);
-
-    this._onTabNavigated = (type, packet) => {
-      const event = Object.create(null);
-      event.url = packet.url;
-      event.title = packet.title;
-      event.nativeConsoleAPI = packet.nativeConsoleAPI;
-      event.isFrameSwitching = packet.isFrameSwitching;
+    // For now, only browsing-context inherited actors are using a front,
+    // for which events have to be listened on the front itself.
+    // For other actors (ContentProcessTargetActor and AddonTargetActor), events should
+    // still be listened directly on the client. This should be ultimately cleaned up to
+    // only listen from a front by bug 1465635.
+    if (this.activeTab) {
+      this.activeTab.on("tabDetached", this.destroy);
 
-      // Keep the title unmodified when a developer toolbox switches frame
-      // for a tab (Bug 1261687), but always update the title when the target
-      // is a WebExtension (where the addon name is always included in the title
-      // and the url is supposed to be updated every time the selected frame changes).
-      if (!packet.isFrameSwitching || this.isWebExtension) {
-        this._url = packet.url;
-        this._title = packet.title;
-      }
+      // These events should be ultimately listened from the thread client as
+      // they are coming from it and no longer go through the Target Actor/Front.
+      this._onSourceUpdated = packet => this.emit("source-updated", packet);
+      this.activeTab.on("newSource", this._onSourceUpdated);
+      this.activeTab.on("updatedSource", this._onSourceUpdated);
+    } else {
+      this._onTabDetached = (type, packet) => {
+        // We have to filter message to ensure that this detach is for this tab
+        if (packet.from == this._form.actor) {
+          this.destroy();
+        }
+      };
+      this.client.addListener("tabDetached", this._onTabDetached);
 
-      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
-      // compatibility with non-remotable tools.
-      if (packet.state == "start") {
-        event._navPayload = this._navRequest;
-        this.emit("will-navigate", event);
-        this._navRequest = null;
-      } else {
-        event._navPayload = this._navWindow;
-        this.emit("navigate", event);
-        this._navWindow = null;
-      }
-    };
-    this.client.addListener("tabNavigated", this._onTabNavigated);
-
-    this._onFrameUpdate = (type, packet) => {
-      this.emit("frame-update", packet);
-    };
-    this.client.addListener("frameUpdate", this._onFrameUpdate);
-
-    this._onSourceUpdated = (event, packet) => this.emit("source-updated", packet);
-    this.client.addListener("newSource", this._onSourceUpdated);
-    this.client.addListener("updatedSource", this._onSourceUpdated);
+      this._onSourceUpdated = (type, packet) => this.emit("source-updated", packet);
+      this.client.addListener("newSource", this._onSourceUpdated);
+      this.client.addListener("updatedSource", this._onSourceUpdated);
+    }
   },
 
   /**
    * Teardown listeners for remote debugging.
    */
   _teardownRemoteListeners: function() {
+    // Remove listeners set in _setupRemoteListeners
     this.client.removeListener("closed", this.destroy);
-    this.client.removeListener("tabNavigated", this._onTabNavigated);
-    this.client.removeListener("tabDetached", this._onTabDetached);
-    this.client.removeListener("frameUpdate", this._onFrameUpdate);
-    this.client.removeListener("newSource", this._onSourceUpdated);
-    this.client.removeListener("updatedSource", this._onSourceUpdated);
+    if (this.activeTab) {
+      this.activeTab.off("tabDetached", this.destroy);
+      this.activeTab.off("newSource", this._onSourceUpdated);
+      this.activeTab.off("updatedSource", this._onSourceUpdated);
+    } else {
+      this.client.removeListener("tabDetached", this._onTabDetached);
+      this.client.removeListener("newSource", this._onSourceUpdated);
+      this.client.removeListener("updatedSource", this._onSourceUpdated);
+    }
+
+    // Remove listeners set in attachTarget
+    if (this.activeTab) {
+      this.activeTab.off("tabNavigated", this._onTabNavigated);
+      this.activeTab.off("frameUpdate", this._onFrameUpdate);
+    }
+
+    // Remove listeners set in attachConsole
     if (this.activeConsole && this._onInspectObject) {
       this.activeConsole.off("inspectObject", this._onInspectObject);
     }
   },
 
   /**
    * Handle tabs events.
    */
--- a/devtools/client/framework/test/browser_toolbox_target.js
+++ b/devtools/client/framework/test/browser_toolbox_target.js
@@ -32,17 +32,17 @@ add_task(async function() {
   await onLoad;
 
   // Also wait for toolbox-ready, as toolbox document load isn't enough, there
   // is plenty of asynchronous steps during toolbox load
   info("Waiting for toolbox-ready");
   const toolbox = await onToolboxReady;
 
   const onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
-  const onTabDetached = once(toolbox.target.client, "tabDetached");
+  const onTabDetached = toolbox.target.activeTab.once("tabDetached");
 
   info("Removing the iframes");
   toolboxIframe.remove();
 
   // And wait for toolbox-destroyed as toolbox unload is also full of
   // asynchronous operation that outlast unload event
   info("Waiting for toolbox-destroyed");
   await onToolboxDestroyed;
--- a/devtools/client/shared/test/browser_dbg_navigation.js
+++ b/devtools/client/shared/test/browser_dbg_navigation.js
@@ -34,62 +34,62 @@ function test() {
       .then(testDetach)
       .then(finish)
       .catch(error => {
         ok(false, "Got an error: " + error.message + "\n" + error.stack);
       });
   });
 }
 
-function testNavigate([aGrip, aResponse]) {
+function testNavigate(targetFront) {
   const outstanding = [promise.defer(), promise.defer()];
 
-  gClient.addListener("tabNavigated", function onTabNavigated(event, packet) {
+  targetFront.on("tabNavigated", function onTabNavigated(packet) {
     is(packet.url.split("/").pop(), TAB2_FILE,
       "Got a tab navigation notification.");
 
     info(JSON.stringify(packet));
     info(JSON.stringify(event));
 
     if (packet.state == "start") {
       ok(true, "Tab started to navigate.");
       outstanding[0].resolve();
     } else {
       ok(true, "Tab finished navigating.");
-      gClient.removeListener("tabNavigated", onTabNavigated);
+      targetFront.off("tabNavigated", onTabNavigated);
       outstanding[1].resolve();
     }
   });
 
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TAB2_URL);
   return promise.all(outstanding.map(e => e.promise))
-                .then(() => aGrip.actor);
+                .then(() => targetFront);
 }
 
-function testDetach(actor) {
-  const deferred = promise.defer();
-
-  gClient.addOneTimeListener("tabDetached", (type, packet) => {
-    ok(true, "Got a tab detach notification.");
-    is(packet.from, actor, "tab detach message comes from the expected actor");
-    deferred.resolve(gClient.close());
-  });
+async function testDetach(targetFront) {
+  const onDetached = targetFront.once("tabDetached");
 
   removeTab(gBrowser.selectedTab);
-  return deferred.promise;
+
+  const packet = await onDetached;
+  ok(true, "Got a tab detach notification.");
+  is(packet.from, targetFront.actorID,
+    "tab detach message comes from the expected actor");
+
+  return gClient.close();
 }
 
 registerCleanupFunction(function() {
   gClient = null;
 });
 
 async function attachTargetActorForUrl(client, url) {
   const grip = await getTargetActorForUrl(client, url);
-  const [ response ] = await client.attachTarget(grip.actor);
-  return [grip, response];
+  const [, targetFront] = await client.attachTarget(grip.actor);
+  return targetFront;
 }
 
 function getTargetActorForUrl(client, url) {
   const deferred = promise.defer();
 
   client.listTabs().then(response => {
     const targetActor = response.tabs.filter(grip => grip.url == url).pop();
     deferred.resolve(targetActor);
--- a/devtools/client/webide/modules/tab-store.js
+++ b/devtools/client/webide/modules/tab-store.js
@@ -56,25 +56,21 @@ TabStore.prototype = {
     this._selectedTabTargetPromise = null;
   },
 
   _onStatusChanged: function() {
     if (this._connection.status == Connection.Status.CONNECTED) {
       // Watch for changes to remote browser tabs
       this._connection.client.addListener("tabListChanged",
                                           this._onTabListChanged);
-      this._connection.client.addListener("tabNavigated",
-                                          this._onTabNavigated);
       this.listTabs();
     } else {
       if (this._connection.client) {
         this._connection.client.removeListener("tabListChanged",
                                                this._onTabListChanged);
-        this._connection.client.removeListener("tabNavigated",
-                                               this._onTabNavigated);
       }
       this._resetStore();
     }
   },
 
   _onTabListChanged: function() {
     this.listTabs().then(() => this.emit("tab-list"))
                    .catch(console.error);
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -16,18 +16,16 @@ function start() {
   DebuggerServer.registerAllActors();
 
   // Listen to an nsIPipe
   let transport = DebuggerServer.connectPipe();
 
   // Start the client.
   client = new DebuggerClient(transport);
 
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 ```
 
 If a TCP socket is required, the function should be split in two parts, a server-side and a client-side, like this:
@@ -46,19 +44,16 @@ function startServer() {
 }
 
 async function startClient() {
   let transport = await DebuggerClient.socketConnect({ host: "localhost", port: 2929 });
 
   // Start the client.
   client = new DebuggerClient(transport);
 
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
-
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 ```
 
 ## Shutting down
@@ -84,33 +79,36 @@ function attachToTab() {
 
     // Attach to the tab.
     client.attachTarget(tab.actor).then(([response, tabClient]) => {
       if (!tabClient) {
         return;
       }
 
       // Now the tabClient is ready and can be used.
+
+      // Attach listeners for client events.
+      tabClient.addListener("tabNavigated", onTab);
     });
   });
 }
 ```
 
 The debugger client will send event notifications for a number of events the application may be interested in. These events include state changes in the debugger, like pausing and resuming, stack frames or source scripts being ready for retrieval, etc.
 
 ## Handling location changes
 
 When the user navigates away from a page, a `tabNavigated` event will be fired. The proper way to handle this event is to detach from the previous thread and tab and attach to the new ones:
 
 ```javascript
 async function onTab() {
   // Detach from the previous thread.
   await client.activeThread.detach();
   // Detach from the previous tab.
-  await client.activeTab.detach();
+  await tabClient.activeTab.detach();
   // Start debugging the new tab.
   start();
 }
 ```
 
 ## Debugging JavaScript running in a browser tab
 
 Once the application is attached to a tab, it can attach to its thread in order to interact with the JavaScript debugger:
@@ -164,18 +162,16 @@ function startDebugger() {
   let transport = DebuggerServer.connectPipe();
   // For an nsIServerSocket we do this:
   // DebuggerServer.openListener(port);
   // ...and this at the client:
   // let transport = debuggerSocketConnect(host, port);
 
   // Start the client.
   client = new DebuggerClient(transport);
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 
 function shutdownDebugger() {
   client.close();
--- a/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -97,22 +97,22 @@ function onMessage({ data }) {
 
 async function connectAndAttachTab() {
   // Ensure having a minimal server
   initDebuggerServer();
 
   // Connect to this tab
   const transport = DebuggerServer.connectPipe();
   const client = new DebuggerClient(transport);
-  client.addListener("tabNavigated", function(event, packet) {
+  const form = await connectDebuggerClient(client);
+  const actorID = form.actor;
+  const [, targetFront ] = await client.attachTarget(actorID);
+  targetFront.on("tabNavigated", function(packet) {
     assertEvent("tabNavigated", packet);
   });
-  const form = await connectDebuggerClient(client);
-  const actorID = form.actor;
-  await client.attachTarget(actorID);
   return { client, actorID };
 }
 
 add_task(async function() {
   // Open a test tab
   const browser = await addTab(URL1);
 
   // Listen for alert() call being made in navigate-first during unload
--- a/devtools/server/tests/browser/browser_webextension_inspected_window.js
+++ b/devtools/server/tests/browser/browser_webextension_inspected_window.js
@@ -21,42 +21,42 @@ async function setup(pageUrl) {
     lineNumber: 1,
     addonId: extension.id,
   };
 
   const target = await addTabTarget(pageUrl);
 
   const { client, form } = target;
 
-  const [, tabClient] = await client.attachTarget(form.actor);
+  const [, targetFront] = await client.attachTarget(form.actor);
 
   const [, consoleClient] = await client.attachConsole(form.consoleActor, []);
 
   const inspectedWindowFront = target.getFront("webExtensionInspectedWindow");
 
   return {
     client, form,
-    tabClient, consoleClient,
+    targetFront, consoleClient,
     inspectedWindowFront,
     extension, fakeExtCallerInfo,
   };
 }
 
 async function teardown({client, extension}) {
   await client.close();
   DebuggerServer.destroy();
   gBrowser.removeCurrentTab();
   await extension.unload();
 }
 
-function waitForNextTabNavigated(client) {
+function waitForNextTabNavigated(targetFront) {
   return new Promise(resolve => {
-    client.addListener("tabNavigated", function tabNavigatedListener(evt, pkt) {
+    targetFront.on("tabNavigated", function tabNavigatedListener(pkt) {
       if (pkt.state == "stop" && !pkt.isFrameSwitching) {
-        client.removeListener("tabNavigated", tabNavigatedListener);
+        targetFront.off("tabNavigated", tabNavigatedListener);
         resolve();
       }
     });
   });
 }
 
 function consoleEvalJS(consoleClient, jsCode) {
   return new Promise(resolve => {
@@ -222,111 +222,111 @@ add_task(async function test_exception_i
      "Got the expected stack trace in the exception message");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=cache`);
 
   // Test reload with bypassCache=false.
 
-  const waitForNoBypassCacheReload = waitForNextTabNavigated(client);
+  const waitForNoBypassCacheReload = waitForNextTabNavigated(targetFront);
   const reloadResult = await inspectedWindowFront.reload(fakeExtCallerInfo,
                                                          {ignoreCache: false});
 
   ok(!reloadResult, "Got the expected undefined result from inspectedWindow reload");
 
   await waitForNoBypassCacheReload;
 
   const noBypassCacheEval = await consoleEvalJS(consoleClient,
                                                 "document.body.textContent");
 
   is(noBypassCacheEval.result, "empty cache headers",
      "Got the expected result with reload forceBypassCache=false");
 
   // Test reload with bypassCache=true.
 
-  const waitForForceBypassCacheReload = waitForNextTabNavigated(client);
+  const waitForForceBypassCacheReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {ignoreCache: true});
 
   await waitForForceBypassCacheReload;
 
   const forceBypassCacheEval = await consoleEvalJS(consoleClient,
                                                    "document.body.textContent");
 
   is(forceBypassCacheEval.result, "no-cache:no-cache",
      "Got the expected result with reload forceBypassCache=true");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
 
   // Test reload with custom userAgent.
 
-  const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {userAgent: "Customized User Agent"});
 
   await waitForCustomUserAgentReload;
 
   const customUserAgentEval = await consoleEvalJS(consoleClient,
                                                   "document.body.textContent");
 
   is(customUserAgentEval.result, "Customized User Agent",
      "Got the expected result on reload with a customized userAgent");
 
   // Test reload with no custom userAgent.
 
-  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
 
   await waitForNoCustomUserAgentReload;
 
   const noCustomUserAgentEval = await consoleEvalJS(consoleClient,
                                                     "document.body.textContent");
 
   is(noCustomUserAgentEval.result, window.navigator.userAgent,
      "Got the expected result with reload without a customized userAgent");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_injectedScript() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
 
   // Test reload with an injectedScript.
 
-  const waitForInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {injectedScript: `new ${injectedScript}`});
   await waitForInjectedScriptReload;
 
   const injectedScriptEval = await consoleEvalJS(consoleClient,
                                                  `(${collectEvalResults})()`);
 
   const expectedResult = (new Array(5)).fill("injected script executed first");
 
   SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
      "Got the expected result on reload with an injected script");
 
   // Test reload without an injectedScript.
 
-  const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForNoInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
   await waitForNoInjectedScriptReload;
 
   const noInjectedScriptEval = await consoleEvalJS(consoleClient,
                                                    `(${collectEvalResults})()`);
 
   const newExpectedResult = (new Array(5)).fill("injected script NOT executed");
 
@@ -334,68 +334,68 @@ add_task(async function test_exception_i
                       "Got the expected result on reload with no injected script");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
 
   // Test reload with custom userAgent three times (and then
   // check that only the first one has affected the page reload.
 
-  const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForCustomUserAgentReload = waitForNextTabNavigated(targetFront);
 
   inspectedWindowFront.reload(fakeExtCallerInfo, {userAgent: "Customized User Agent 1"});
   inspectedWindowFront.reload(fakeExtCallerInfo, {userAgent: "Customized User Agent 2"});
 
   await waitForCustomUserAgentReload;
 
   const customUserAgentEval = await consoleEvalJS(consoleClient,
                                                   "document.body.textContent");
 
   is(customUserAgentEval.result, "Customized User Agent 1",
      "Got the expected result on reload with a customized userAgent");
 
   // Test reload with no custom userAgent.
 
-  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
 
   await waitForNoCustomUserAgentReload;
 
   const noCustomUserAgentEval = await consoleEvalJS(consoleClient,
                                                     "document.body.textContent");
 
   is(noCustomUserAgentEval.result, window.navigator.userAgent,
      "Got the expected result with reload without a customized userAgent");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_stopped() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
 
   // Test reload on a page that calls window.stop() immediately during the page loading
 
-  const waitForPageLoad = waitForNextTabNavigated(client);
+  const waitForPageLoad = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.eval(fakeExtCallerInfo,
                                   "window.location += '&stop=windowStop'");
 
   info("Load a webpage that calls 'window.stop()' while is still loading");
   await waitForPageLoad;
 
   info("Starting a reload with an injectedScript");
-  const waitForInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {injectedScript: `new ${injectedScript}`});
   await waitForInjectedScriptReload;
 
   const injectedScriptEval = await consoleEvalJS(consoleClient,
                                                  `(${collectEvalResults})()`);
 
   // The page should have stopped during the reload and only one injected script
@@ -403,17 +403,17 @@ add_task(async function test_exception_i
   const expectedResult = (new Array(1)).fill("injected script executed first");
 
   SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
      "The injected script has been executed on the 'stopped' page reload");
 
   // Reload again with no options.
 
   info("Reload the tab again without any reload options");
-  const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForNoInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
   await waitForNoInjectedScriptReload;
 
   const noInjectedScriptEval = await consoleEvalJS(consoleClient,
                                                    `(${collectEvalResults})()`);
 
   // The page should have stopped during the reload and no injected script should
   // have been executed during this second reload (or it would mean that the previous
--- a/devtools/server/tests/mochitest/webextension-helpers.js
+++ b/devtools/server/tests/mochitest/webextension-helpers.js
@@ -46,29 +46,29 @@ SimpleTest.registerCleanupFunction(funct
 function setWebExtensionOOPMode(oopMode) {
   return SpecialPowers.pushPrefEnv({
     "set": [
       ["extensions.webextensions.remote", oopMode],
     ]
   });
 }
 
-function waitForFramesUpdated({client}, matchFn) {
+function waitForFramesUpdated(target, matchFn) {
   return new Promise(resolve => {
-    const listener = (evt, data) => {
+    const listener = data => {
       if (typeof matchFn === "function" && !matchFn(data)) {
         return;
       } else if (!data.frames) {
         return;
       }
 
-      client.removeListener("frameUpdate", listener);
+      target.activeTab.off("frameUpdate", listener);
       resolve(data.frames);
     };
-    client.addListener("frameUpdate", listener);
+    target.activeTab.on("frameUpdate", listener);
   });
 }
 
 function collectFrameUpdates({client}, matchFn) {
   const collected = [];
 
   const listener = (evt, data) => {
     if (matchFn(data)) {
--- a/devtools/server/tests/unit/test_sourcemaps-01.js
+++ b/devtools/server/tests/unit/test_sourcemaps-01.js
@@ -39,17 +39,17 @@ function test_simple_source_map() {
     Assert.equal(packet.type, "newSource");
     Assert.ok(!!packet.source);
 
     Assert.ok(expectedSources.has(packet.source.url),
               "The source url should be one of our original sources.");
     expectedSources.delete(packet.source.url);
 
     if (expectedSources.size === 0) {
-      gClient.removeListener("newSource", _onNewSource);
+      gThreadClient.removeListener("newSource", _onNewSource);
       finishClient(gClient);
     }
   });
 
   let { code, map } = (new SourceNode(null, null, null, [
     new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
     new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
     new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),