Bug 1525533: Debug local addon as well via debugger client. r=jdescottes
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Mon, 25 Feb 2019 13:52:17 +0000
changeset 519282 6dadc0b701092c059a7103776170bb0828388ed6
parent 519281 705b9ae4d7280aac57cd326467387b77424aaab6
child 519283 9cc065c29f512dfc300ebcce54c3511bc12a139b
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1525533
milestone67.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 1525533: Debug local addon as well via debugger client. r=jdescottes Differential Revision: https://phabricator.services.mozilla.com/D20786
devtools/client/aboutdebugging-new/src/actions/debug-targets.js
devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_console.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_inspector.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_nobg.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_popup.js
devtools/client/aboutdebugging/components/addons/Target.js
devtools/client/aboutdebugging/modules/addon.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -7,18 +7,17 @@
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 const { remoteClientManager } =
   require("devtools/client/shared/remote-debugging/remote-client-manager");
 
 const { l10n } = require("../modules/l10n");
 
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   openTemporaryExtension,
   uninstallAddon,
 } = require("../modules/extensions-helper");
 
 const {
   getCurrentClient,
   getCurrentRuntime,
 } = require("../modules/runtimes-state-helper");
@@ -56,22 +55,17 @@ function inspectDebugTarget(type, id) {
           const remoteId = remoteClientManager.getRemoteId(runtime.id, runtime.type);
           window.open(`about:devtools-toolbox?type=tab&id=${id}&remoteId=${remoteId}`);
         } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
           window.open(`about:devtools-toolbox?type=tab&id=${id}`);
         }
         break;
       }
       case DEBUG_TARGETS.EXTENSION: {
-        if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
-          const devtoolsClient = runtimeDetails.clientWrapper.client;
-          await debugRemoteAddon(id, devtoolsClient);
-        } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
-          debugLocalAddon(id);
-        }
+        await debugAddon(id, runtimeDetails.clientWrapper.client);
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
         const devtoolsClient = runtimeDetails.clientWrapper.client;
         const front = devtoolsClient.getActor(id);
         gDevToolsBrowser.openWorkerToolbox(front);
         break;
--- a/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
@@ -1,73 +1,45 @@
 /* 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 { Cc, Ci } = require("chrome");
-loader.lazyImporter(this, "BrowserToolboxProcess",
-  "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 const {Toolbox} = require("devtools/client/framework/toolbox");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
 
-let browserToolboxProcess = null;
-let remoteAddonToolbox = null;
-function closeToolbox() {
-  if (browserToolboxProcess) {
-    browserToolboxProcess.close();
-  }
-
-  if (remoteAddonToolbox) {
-    remoteAddonToolbox.destroy();
-  }
-}
+let addonToolbox = null;
 
 /**
- * Start debugging an addon in the current instance of Firefox.
- *
- * @param {String} addonID
- *        String id of the addon to debug.
- */
-exports.debugLocalAddon = async function(addonID) {
-  // Close previous addon debugging toolbox.
-  closeToolbox();
-
-  browserToolboxProcess = BrowserToolboxProcess.init({
-    addonID,
-    onClose: () => {
-      browserToolboxProcess = null;
-    },
-  });
-};
-
-/**
- * Start debugging an addon in a remote instance of Firefox.
+ * Start debugging an addon.
  *
  * @param {String} id
  *        The addon id to debug.
  * @param {DebuggerClient} client
- *        Required for remote debugging.
+ *        Required for debugging.
  */
-exports.debugRemoteAddon = async function(id, client) {
+exports.debugAddon = async function(id, client) {
   const addonFront = await client.mainRoot.getAddon({ id });
 
   const target = await addonFront.connect();
 
   // Close previous addon debugging toolbox.
-  closeToolbox();
+  if (addonToolbox) {
+    addonToolbox.destroy();
+  }
 
   const hostType = Toolbox.HostType.WINDOW;
-  remoteAddonToolbox = await gDevTools.showToolbox(target, null, hostType);
-  remoteAddonToolbox.once("destroy", () => {
-    remoteAddonToolbox = null;
+  addonToolbox = await gDevTools.showToolbox(target, null, hostType);
+  addonToolbox.once("destroy", () => {
+    addonToolbox = null;
   });
 };
 
 /**
  * Uninstall the addon with the provided id.
  * Resolves when the addon shutdown has completed.
  */
 exports.uninstallAddon = async function(addonID) {
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_console.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_console.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  */
@@ -40,39 +36,35 @@ add_task(async function testWebExtension
                     this.browser.runtime.getManifest());
       };
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+function toolboxTestScript(toolbox) {
   function findMessages(hud, text, selector = ".message") {
     const messages = hud.ui.outputNode.querySelectorAll(selector);
     const elements = Array.prototype.filter.call(
       messages,
       (el) => el.textContent.includes(text)
     );
     return elements;
   }
@@ -87,13 +79,12 @@ function toolboxTestScript() {
     .then(async console => {
       const { hud } = console;
       const { jsterm } = hud;
       const onMessage = waitFor(() => {
         return findMessages(hud, "Background page function called").length > 0;
       });
       await jsterm.execute("myWebExtensionAddonFunction()");
       await onMessage;
-      await toolbox.destroy();
+      await toolbox.closeToolbox();
     })
     .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_inspector.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_inspector.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  */
@@ -37,39 +33,35 @@ add_task(async function testWebExtension
     background: function() {
       document.body.innerText = "Background Page Body Test Content";
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+async function toolboxTestScript(toolbox) {
   toolbox.selectTool("inspector")
     .then(inspector => {
       return inspector.walker.querySelector(inspector.walker.rootNode, "body");
     })
     .then((nodeActor) => {
       if (!nodeActor) {
         throw new Error("nodeActor not found");
       }
@@ -89,16 +81,15 @@ function toolboxTestScript() {
         throw new Error(
           `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
         );
       }
 
       dump("Got the expected inline text content in the selected node\n");
       return Promise.resolve();
     })
-    .then(() => toolbox.destroy())
+    .then(() => toolbox.closeToolbox())
     .catch((error) => {
       dump("Error while running code in the browser toolbox process:\n");
       dump(error + "\n");
       dump("stack:\n" + error.stack + "\n");
     });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_nobg.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_nobg.js
@@ -8,20 +8,16 @@ Services.scriptloader.loadSubScript(CHRO
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
 const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
  *   the expected message, which warns the user that the current page is not a real
  *   webextension context);
  */
 add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
@@ -31,88 +27,62 @@ add_task(async function testWebExtension
 
   await installTemporaryExtensionFromXPI({
     // Do not pass any `background` script.
     id: ADDON_NOBG_ID,
     name: ADDON_NOBG_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NOBG_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  env.set("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME", ADDON_NOBG_NAME);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-    env.set("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME", "");
-  });
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NOBG_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
-  // This is webextension toolbox process. So we can't access mochitest framework.
-  const waitUntil = async function(predicate, interval = 10) {
-    if (await predicate()) {
-      return true;
-    }
-    return new Promise(resolve => {
-      toolbox.win.setTimeout(function() {
-        waitUntil(predicate, interval).then(() => resolve(true));
-      }, interval);
-    });
-  };
-
-  // We pass the expected target name as an environment variable because this method
-  // runs in a different process and cannot access the const defined in this test file.
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  const expectedName = env.get("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME");
-
+async function toolboxTestScript(toolbox) {
   const targetName = toolbox.target.name;
   const isAddonTarget = toolbox.target.isAddon;
-  if (!(isAddonTarget && targetName === expectedName)) {
-    dump(`Expected target name "${expectedName}", got ${targetName}`);
+  if (!(isAddonTarget && targetName === ADDON_NOBG_NAME)) {
+    dump(`Expected target name "${ADDON_NOBG_NAME}", got ${targetName}`);
     throw new Error("Toolbox doesn't have the expected target");
   }
 
   toolbox.selectTool("inspector").then(async inspector => {
     let nodeActor;
 
     dump(`Wait the fallback window to be fully loaded\n`);
-    await waitUntil(async () => {
+    await asyncWaitUntil(async () => {
       nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
       return nodeActor && nodeActor.inlineTextChild;
     });
 
     dump("Got a nodeActor with an inline text child\n");
     const expectedValue = "Your addon does not have any document opened yet.";
     const actualValue = nodeActor.inlineTextChild._form.nodeValue;
 
     if (actualValue !== expectedValue) {
       throw new Error(
         `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
       );
     }
 
     dump("Got the expected inline text content in the selected node\n");
 
-    await toolbox.destroy();
+    await toolbox.closeToolbox();
   }).catch((error) => {
     dump("Error while running code in the browser toolbox process:\n");
     dump(error + "\n");
     dump("stack:\n" + error.stack + "\n");
   });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_popup.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_popup.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  * - the webextension developer toolbox has a working Inspector panel, with the
@@ -115,25 +111,23 @@ add_task(async function testWebExtension
       },
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
 
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   info("Wait until the addon popup is opened from the test script");
   await onReadyForOpenPopup;
 
   const widgetId = ADDON_ID.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
   const browserActionId = widgetId + "-browser-action";
   const browserActionEl = window.document.getElementById(browserActionId);
 
@@ -156,20 +150,17 @@ add_task(async function testWebExtension
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+async function toolboxTestScript(toolbox) {
   let jsterm;
   const popupFramePromise = new Promise(resolve => {
     const listener = data => {
       if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
         toolbox.target.off("frame-update", listener);
         resolve();
       }
     };
@@ -205,54 +196,40 @@ function toolboxTestScript() {
         // Wait the new frame update (once the extension popup has been opened).
         popupFramePromise,
       ]);
 
       dump(`Clicking the frame list button\n`);
       const btn = toolbox.doc.getElementById("command-button-frames");
       btn.click();
 
-      // This is webextension toolbox process. So we can't access mochitest framework.
-      const waitUntil = function(predicate, interval = 10) {
-        if (predicate()) {
-          return Promise.resolve(true);
-        }
-        return new Promise(resolve => {
-          toolbox.win.setTimeout(function() {
-            waitUntil(predicate, interval).then(() => resolve(true));
-          }, interval);
-        });
-      };
-      await waitUntil(() => btn.style.pointerEvents === "none");
-      dump(`Clicked the frame list button\n`);
-
       const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
       const frames = Array.from(menuList.querySelectorAll(".command"));
 
       if (frames.length != 2) {
         throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
       }
 
       const popupFrameBtn = frames.filter((frame) => {
         return frame.querySelector(".label").textContent.endsWith("popup.html");
       }).pop();
 
       if (!popupFrameBtn) {
         throw Error("Extension Popup frame not found in the listed frames");
       }
 
       const waitForNavigated = toolbox.target.once("navigate");
-
       popupFrameBtn.click();
-
+      // Clicking the menu item may do highlighting.
+      await waitUntil(() => toolbox.highlighter);
+      await Promise.race([toolbox.highlighter.once("node-highlight"), wait(1000)]);
       await waitForNavigated;
 
       await jsterm.execute("myWebExtensionPopupAddonFunction()");
 
-      await toolbox.destroy();
+      await toolbox.closeToolbox();
     })
     .catch((error) => {
       dump("Error while running code in the browser toolbox process:\n");
       dump(error + "\n");
       dump("stack:\n" + error.stack + "\n");
     });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging/components/addons/Target.js
+++ b/devtools/client/aboutdebugging/components/addons/Target.js
@@ -5,18 +5,17 @@
 /* eslint-env browser */
 
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   getExtensionUuid,
   isTemporaryID,
   parseFileUri,
   uninstallAddon,
 } = require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
@@ -170,23 +169,18 @@ class AddonTarget extends Component {
   constructor(props) {
     super(props);
     this.debug = this.debug.bind(this);
     this.uninstall = this.uninstall.bind(this);
     this.reload = this.reload.bind(this);
   }
 
   debug() {
-    const { client, connect, target } = this.props;
-
-    if (connect.type === "REMOTE") {
-      debugRemoteAddon(target.addonID, client);
-    } else if (connect.type === "LOCAL") {
-      debugLocalAddon(target.addonID);
-    }
+    const { client, target } = this.props;
+    debugAddon(target.addonID, client);
   }
 
   uninstall() {
     const { target } = this.props;
     uninstallAddon(target.addonID);
   }
 
   async reload() {
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -2,18 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 loader.lazyImporter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
 
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   getExtensionUuid,
   openTemporaryExtension,
   parseFileUri,
   uninstallAddon,
 } = require("devtools/client/aboutdebugging-new/src/modules/extensions-helper");
 
 /**
  * Most of the implementation for this module has been moved to
@@ -25,14 +24,13 @@ exports.isTemporaryID = function(addonID
   return AddonManagerPrivate.isTemporaryInstallID(addonID);
 };
 
 /**
  * See JSDoc in devtools/client/aboutdebugging-new/src/modules/extensions-helper for all
  * the methods exposed below.
  */
 
-exports.debugLocalAddon = debugLocalAddon;
-exports.debugRemoteAddon = debugRemoteAddon;
+exports.debugAddon = debugAddon;
 exports.getExtensionUuid = getExtensionUuid;
 exports.openTemporaryExtension = openTemporaryExtension;
 exports.parseFileUri = parseFileUri;
 exports.uninstallAddon = uninstallAddon;
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -9,20 +9,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  */
 add_task(async function testWebExtensionsToolboxWebConsole() {
   const addonFile = ExtensionTestCommon.generateXPI({
     background: function() {
@@ -39,58 +35,50 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-              .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    function findMessages(hud, text, selector = ".message") {
-      const messages = hud.ui.outputNode.querySelectorAll(selector);
-      const elements = Array.prototype.filter.call(
-        messages,
-        (el) => el.textContent.includes(text)
-      );
-      return elements;
-    }
-
-    async function waitFor(condition) {
-      while (!condition()) {
-        await new Promise(done => window.setTimeout(done, 1000));
-      }
-    }
-
-    toolbox.selectTool("webconsole")
-      .then(async console => {
-        const { hud } = console;
-        const { jsterm } = hud;
-        const onMessage = waitFor(() => {
-          return findMessages(hud, "Background page function called").length > 0;
-        });
-        await jsterm.execute("myWebExtensionAddonFunction()");
-        await onMessage;
-        await toolbox.destroy();
-      })
-      .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
   await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  function findMessages(hud, text, selector = ".message") {
+    const messages = hud.ui.outputNode.querySelectorAll(selector);
+    const elements = Array.prototype.filter.call(
+      messages,
+      (el) => el.textContent.includes(text)
+    );
+    return elements;
+  }
+
+  async function waitFor(condition) {
+    while (!condition()) {
+      await new Promise(done => window.setTimeout(done, 1000));
+    }
+  }
+
+  toolbox.selectTool("webconsole")
+         .then(async console => {
+           const { hud } = console;
+           const { jsterm } = hud;
+           const onMessage = waitFor(() => {
+             return findMessages(hud, "Background page function called").length > 0;
+           });
+           await jsterm.execute("myWebExtensionAddonFunction()");
+           await onMessage;
+           await toolbox.destroy();
+         })
+         .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  */
 add_task(async function testWebExtensionsToolboxInspector() {
   const addonFile = ExtensionTestCommon.generateXPI({
     background: function() {
@@ -35,65 +31,58 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-        .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    toolbox.selectTool("inspector")
-      .then(inspector => {
-        return inspector.walker.querySelector(inspector.walker.rootNode, "body");
-      })
-      .then((nodeActor) => {
-        if (!nodeActor) {
-          throw new Error("nodeActor not found");
-        }
-
-        dump("Got a nodeActor\n");
-
-        if (!(nodeActor.inlineTextChild)) {
-          throw new Error("inlineTextChild not found");
-        }
-
-        dump("Got a nodeActor with an inline text child\n");
-
-        const expectedValue = "Background Page Body Test Content";
-        const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
+  debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
-        if (String(actualValue).trim() !== String(expectedValue).trim()) {
-          throw new Error(
-            `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
-          );
-        }
-
-        dump("Got the expected inline text content in the selected node\n");
-        return Promise.resolve();
-      })
-      .then(() => toolbox.destroy())
-      .catch((error) => {
-        dump("Error while running code in the browser toolbox process:\n");
-        dump(error + "\n");
-        dump("stack:\n" + error.stack + "\n");
-      });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-  debugBtn.click();
   await onToolboxClose;
-
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  toolbox.selectTool("inspector")
+         .then(inspector => {
+           return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+         })
+         .then((nodeActor) => {
+           if (!nodeActor) {
+             throw new Error("nodeActor not found");
+           }
+
+           dump("Got a nodeActor\n");
+
+           if (!(nodeActor.inlineTextChild)) {
+             throw new Error("inlineTextChild not found");
+           }
+
+           dump("Got a nodeActor with an inline text child\n");
+
+           const expectedValue = "Background Page Body Test Content";
+           const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+           if (String(actualValue).trim() !== String(expectedValue).trim()) {
+             throw new Error(
+               `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+             );
+           }
+
+           dump("Got the expected inline text content in the selected node\n");
+           return Promise.resolve();
+         })
+         .then(() => toolbox.destroy())
+         .catch((error) => {
+           dump("Error while running code in the browser toolbox process:\n");
+           dump(error + "\n");
+           dump("stack:\n" + error.stack + "\n");
+         });
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
 const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
  *   the expected message, which warns the user that the current page is not a real
  *   webextension context);
  */
 add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
@@ -34,69 +30,50 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-        .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    // This is webextension toolbox process. So we can't access mochitest framework.
-    const waitUntil = async function(predicate, interval = 10) {
-      if (await predicate()) {
-        return true;
-      }
-      return new Promise(resolve => {
-        toolbox.win.setTimeout(function() {
-          waitUntil(predicate, interval).then(() => resolve(true));
-        }, interval);
-      });
-    };
-
-    toolbox.selectTool("inspector").then(async inspector => {
-      let nodeActor;
-
-      dump(`Wait the fallback window to be fully loaded\n`);
-      await waitUntil(async () => {
-        nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
-        return nodeActor && nodeActor.inlineTextChild;
-      });
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
+  debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
-      dump("Got a nodeActor with an inline text child\n");
-      const expectedValue = "Your addon does not have any document opened yet.";
-      const actualValue = nodeActor.inlineTextChild._form.nodeValue;
-
-      if (actualValue !== expectedValue) {
-        throw new Error(
-          `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
-        );
-      }
-
-      dump("Got the expected inline text content in the selected node\n");
-
-      await toolbox.destroy();
-    }).catch((error) => {
-      dump("Error while running code in the browser toolbox process:\n");
-      dump(error + "\n");
-      dump("stack:\n" + error.stack + "\n");
-    });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-  debugBtn.click();
   await onToolboxClose;
-
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  toolbox.selectTool("inspector").then(async inspector => {
+    let nodeActor;
+
+    dump(`Wait the fallback window to be fully loaded\n`);
+    await asyncWaitUntil(async () => {
+      nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
+      return nodeActor && nodeActor.inlineTextChild;
+    });
+
+    dump("Got a nodeActor with an inline text child\n");
+    const expectedValue = "Your addon does not have any document opened yet.";
+    const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+    if (actualValue !== expectedValue) {
+      throw new Error(
+        `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+      );
+    }
+
+    dump("Got the expected inline text content in the selected node\n");
+
+    await toolbox.destroy();
+  }).catch((error) => {
+    dump("Error while running code in the browser toolbox process:\n");
+    dump(error + "\n");
+    dump("stack:\n" + error.stack + "\n");
+  });
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
@@ -117,122 +113,21 @@ add_task(async function testWebExtension
     // the web console.
     onPopupCustomMessage = waitForExtensionTestMessage("popupPageFunctionCalled");
   });
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-
-  const testScript = function() {
-    /* eslint-disable no-undef */
-
-    let jsterm;
-    const popupFramePromise = new Promise(resolve => {
-      const listener = data => {
-        if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
-          toolbox.target.off("frame-update", listener);
-          resolve();
-        }
-      };
-      toolbox.target.on("frame-update", listener);
-    });
-
-    const waitForFrameListUpdate = toolbox.target.once("frame-update");
-
-    toolbox.selectTool("webconsole")
-      .then(async (console) => {
-        const clickNoAutoHideMenu = () => {
-          return new Promise(resolve => {
-            toolbox.doc.getElementById("toolbox-meatball-menu-button").click();
-            toolbox.doc.addEventListener("popupshown", () => {
-              const menuItem =
-                    toolbox.doc.getElementById("toolbox-meatball-menu-noautohide");
-              menuItem.click();
-              resolve();
-            }, { once: true });
-          });
-        };
-
-        dump(`Clicking the menu button\n`);
-        await clickNoAutoHideMenu();
-        dump(`Clicked the menu button\n`);
-
-        jsterm = console.hud.jsterm;
-        jsterm.execute("myWebExtensionShowPopup()");
-
-        await Promise.all([
-          // Wait the initial frame update (which list the background page).
-          waitForFrameListUpdate,
-          // Wait the new frame update (once the extension popup has been opened).
-          popupFramePromise,
-        ]);
-
-        dump(`Clicking the frame list button\n`);
-        const btn = toolbox.doc.getElementById("command-button-frames");
-        btn.click();
-
-        // This is webextension toolbox process. So we can't access mochitest framework.
-        const waitUntil = function(predicate, interval = 10) {
-          if (predicate()) {
-            return Promise.resolve(true);
-          }
-          return new Promise(resolve => {
-            toolbox.win.setTimeout(function() {
-              waitUntil(predicate, interval).then(() => resolve(true));
-            }, interval);
-          });
-        };
-        await waitUntil(() => btn.style.pointerEvents === "none");
-        dump(`Clicked the frame list button\n`);
-
-        const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
-        const frames = Array.from(menuList.querySelectorAll(".command"));
-
-        if (frames.length != 2) {
-          throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
-        }
-
-        const popupFrameBtn = frames.filter((frame) => {
-          return frame.querySelector(".label").textContent.endsWith("popup.html");
-        }).pop();
-
-        if (!popupFrameBtn) {
-          throw Error("Extension Popup frame not found in the listed frames");
-        }
-
-        const waitForNavigated = toolbox.target.once("navigate");
-
-        popupFrameBtn.click();
-
-        await waitForNavigated;
-
-        await jsterm.execute("myWebExtensionPopupAddonFunction()");
-
-        await toolbox.destroy();
-      })
-      .catch((error) => {
-        dump("Error while running code in the browser toolbox process:\n");
-        dump(error + "\n");
-        dump("stack:\n" + error.stack + "\n");
-      });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
   await onReadyForOpenPopup;
 
   const browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
   const browserActionEl = window.document.getElementById(browserActionId);
 
   ok(browserActionEl, "Got the browserAction button from the browser UI");
   browserActionEl.click();
@@ -240,17 +135,95 @@ add_task(async function testWebExtension
 
   const args = await onPopupCustomMessage;
   ok(true, "Received console message from the popup page function as expected");
   is(args[0], "popupPageFunctionCalled", "Got the expected console message");
   is(args[1] && args[1].name, ADDON_NAME,
      "Got the expected manifest from WebExtension API");
 
   await onToolboxClose;
-
   info("Addon toolbox closed");
 
   is(Services.prefs.getBoolPref("ui.popup.disable_autohide"), false,
      "disable_autohide should be reset to false when the toolbox is closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  let jsterm;
+  const popupFramePromise = new Promise(resolve => {
+    const listener = data => {
+      if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
+        toolbox.target.off("frame-update", listener);
+        resolve();
+      }
+    };
+    toolbox.target.on("frame-update", listener);
+  });
+
+  const waitForFrameListUpdate = toolbox.target.once("frame-update");
+
+  toolbox.selectTool("webconsole")
+         .then(async (console) => {
+           const clickNoAutoHideMenu = () => {
+             return new Promise(resolve => {
+               toolbox.doc.getElementById("toolbox-meatball-menu-button").click();
+               toolbox.doc.addEventListener("popupshown", () => {
+                 const menuItem =
+                   toolbox.doc.getElementById("toolbox-meatball-menu-noautohide");
+                 menuItem.click();
+                 resolve();
+               }, { once: true });
+             });
+           };
+
+           dump(`Clicking the menu button\n`);
+           await clickNoAutoHideMenu();
+           dump(`Clicked the menu button\n`);
+
+           jsterm = console.hud.jsterm;
+           jsterm.execute("myWebExtensionShowPopup()");
+
+           await Promise.all([
+             // Wait the initial frame update (which list the background page).
+             waitForFrameListUpdate,
+             // Wait the new frame update (once the extension popup has been opened).
+             popupFramePromise,
+           ]);
+
+           dump(`Clicking the frame list button\n`);
+           const btn = toolbox.doc.getElementById("command-button-frames");
+           btn.click();
+
+           const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
+           const frames = Array.from(menuList.querySelectorAll(".command"));
+
+           if (frames.length != 2) {
+             throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
+           }
+
+           const popupFrameBtn = frames.filter((frame) => {
+             return frame.querySelector(".label").textContent.endsWith("popup.html");
+           }).pop();
+
+           if (!popupFrameBtn) {
+             throw Error("Extension Popup frame not found in the listed frames");
+           }
+
+           const waitForNavigated = toolbox.target.once("navigate");
+           popupFrameBtn.click();
+           // Clicking the menu item may do highlighting.
+           await waitUntil(() => toolbox.highlighter);
+           await Promise.race([toolbox.highlighter.once("node-highlight"), wait(1000)]);
+           await waitForNavigated;
+
+           await jsterm.execute("myWebExtensionPopupAddonFunction()");
+
+           await toolbox.destroy();
+         })
+         .catch((error) => {
+           dump("Error while running code in the browser toolbox process:\n");
+           dump(error + "\n");
+           dump("stack:\n" + error.stack + "\n");
+         });
+};