Bug 1368199 - Replaced uses of 'defer' with 'new Promise' in the devtools/client/framework directory. r?jryans draft
authorsreeise <reeisesean@gmail.com>
Tue, 12 Jun 2018 21:56:10 -0400
changeset 811709 66b1b7f27a68aed1a9f63421224d8f6f84984461
parent 811678 f8acdf0185d786809bfbe8cabab081400dc47c68
push id114397
push userbmo:reeisesean@gmail.com
push dateThu, 28 Jun 2018 01:18:51 +0000
reviewersjryans
bugs1368199
milestone63.0a1
Bug 1368199 - Replaced uses of 'defer' with 'new Promise' in the devtools/client/framework directory. r?jryans MozReview-Commit-ID: J1j6K2M2zWf
devtools/client/framework/attach-thread.js
devtools/client/framework/devtools-browser.js
devtools/client/framework/target.js
devtools/client/framework/test/browser_devtools_api_destroy.js
devtools/client/framework/test/browser_toolbox_options.js
devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
devtools/client/framework/test/browser_toolbox_sidebar.js
devtools/client/framework/test/browser_toolbox_sidebar_events.js
devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
devtools/client/framework/test/head.js
devtools/client/framework/test/helper_disable_cache.js
devtools/client/framework/toolbox-hosts.js
devtools/client/framework/toolbox-options.js
devtools/client/framework/toolbox.js
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -1,16 +1,15 @@
 /* -*- indent-tabs-mode: nil; 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/. */
 
 const Services = require("Services");
-const defer = require("devtools/shared/defer");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
 function handleThreadState(toolbox, event, packet) {
   // Suppress interrupted events by default because the thread is
   // paused/resumed a lot for various actions.
   if (event === "paused" && packet.why.type === "interrupted") {
@@ -32,18 +31,16 @@ function handleThreadState(toolbox, even
       toolbox.selectTool("jsdebugger", packet.why.type);
     }
   } else if (event === "resumed") {
     toolbox.unhighlightTool("jsdebugger");
   }
 }
 
 function attachThread(toolbox) {
-  const deferred = defer();
-
   const target = toolbox.target;
   const { form: { chromeDebugger, actor } } = target;
 
   // Sourcemaps are always turned off when using the new debugger
   // frontend. This is because it does sourcemapping on the
   // client-side, so the server should not do it.
   let useSourceMaps = false;
   let autoBlackBox = false;
@@ -53,68 +50,67 @@ function attachThread(toolbox) {
     useSourceMaps = Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled");
     autoBlackBox = Services.prefs.getBoolPref("devtools.debugger.auto-black-box");
   } else {
     ignoreFrameEnvironment = true;
   }
 
   const threadOptions = { useSourceMaps, autoBlackBox, ignoreFrameEnvironment };
 
-  const handleResponse = ([res, threadClient]) => {
-    if (res.error) {
-      deferred.reject(new Error("Couldn't attach to thread: " + res.error));
-      return;
-    }
-    threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
-    threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));
-
-    if (!threadClient.paused) {
-      deferred.reject(
-        new Error("Thread in wrong state when starting up, should be paused")
-      );
-    }
+  return new Promise((resolve, reject) => {
+    const handleResponse = ([res, threadClient]) => {
+      if (res.error) {
+        reject(new Error("Couldn't attach to thread: " + res.error));
+        return;
+      }
 
-    // These flags need to be set here because the client sends them
-    // with the `resume` request. We make sure to do this before
-    // resuming to avoid another interrupt. We can't pass it in with
-    // `threadOptions` because the resume request will override them.
-    threadClient.pauseOnExceptions(
-      Services.prefs.getBoolPref("devtools.debugger.pause-on-exceptions"),
-      Services.prefs.getBoolPref("devtools.debugger.ignore-caught-exceptions")
-    );
+      threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
+      threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));
 
-    threadClient.resume(res => {
-      if (res.error === "wrongOrder") {
-        const box = toolbox.getNotificationBox();
-        box.appendNotification(
-          L10N.getStr("toolbox.resumeOrderWarning"),
-          "wrong-resume-order",
-          "",
-          box.PRIORITY_WARNING_HIGH
-        );
+      if (!threadClient.paused) {
+        reject(new Error("Thread in wrong state when starting up, should be paused"));
       }
 
-      deferred.resolve(threadClient);
-    });
-  };
+      // These flags need to be set here because the client sends them
+      // with the `resume` request. We make sure to do this before
+      // resuming to avoid another interrupt. We can't pass it in with
+      // `threadOptions` because the resume request will override them.
+      threadClient.pauseOnExceptions(
+        Services.prefs.getBoolPref("devtools.debugger.pause-on-exceptions"),
+        Services.prefs.getBoolPref("devtools.debugger.ignore-caught-exceptions")
+      );
 
-  if (target.isBrowsingContext) {
-    // Attaching a tab, a browser process, or a WebExtensions add-on.
-    target.activeTab.attachThread(threadOptions).then(handleResponse);
-  } else if (target.isAddon) {
-    // Attaching a legacy addon.
-    target.client.attachAddon(actor).then(([res]) => {
-      target.client.attachThread(res.threadActor).then(handleResponse);
-    });
-  } else {
-    // Attaching an old browser debugger or a content process.
-    target.client.attachThread(chromeDebugger).then(handleResponse);
-  }
+      threadClient.resume(res => {
+        if (res.error === "wrongOrder") {
+          const box = toolbox.getNotificationBox();
+          box.appendNotification(
+            L10N.getStr("toolbox.resumeOrderWarning"),
+            "wrong-resume-order",
+            "",
+            box.PRIORITY_WARNING_HIGH
+          );
+        }
 
-  return deferred.promise;
+        resolve(threadClient);
+      });
+    };
+
+    if (target.isBrowsingContext) {
+      // Attaching a tab, a browser process, or a WebExtensions add-on.
+      target.activeTab.attachThread(threadOptions).then(handleResponse);
+    } else if (target.isAddon) {
+      // Attaching a legacy addon.
+      target.client.attachAddon(actor).then(([res]) => {
+        target.client.attachThread(res.threadActor).then(handleResponse);
+      });
+    } else {
+      // Attaching an old browser debugger or a content process.
+      target.client.attachThread(chromeDebugger).then(handleResponse);
+    }
+  });
 }
 
 function detachThread(threadClient) {
   threadClient.removeListener("paused");
   threadClient.removeListener("resumed");
 }
 
 module.exports = { attachThread, detachThread };
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -304,40 +304,39 @@ var gDevToolsBrowser = exports.gDevTools
     // Create a DebuggerServer in order to connect locally to it
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     DebuggerServer.allowChromeProcess = true;
 
     const transport = DebuggerServer.connectPipe();
     const client = new DebuggerClient(transport);
 
-    const deferred = defer();
-    client.connect().then(() => {
-      client.getProcess(processId)
-            .then(response => {
-              const options = {
-                form: response.form,
-                client: client,
-                chrome: true,
-                isBrowsingContext: false
-              };
-              return TargetFactory.forRemoteTab(options);
-            })
-            .then(target => {
-              // Ensure closing the connection in order to cleanup
-              // the debugger client and also the server created in the
-              // content process
-              target.on("close", () => {
-                client.close();
+    return new Promise(resolve => {
+      client.connect().then(() => {
+        client.getProcess(processId)
+              .then(response => {
+                const options = {
+                  form: response.form,
+                  client: client,
+                  chrome: true,
+                  isBrowsingContext: false
+                };
+                return TargetFactory.forRemoteTab(options);
+              })
+              .then(target => {
+                // Ensure closing the connection in order to cleanup
+                // the debugger client and also the server created in the
+                // content process
+                target.on("close", () => {
+                  client.close();
+                });
+                resolve(target);
               });
-              deferred.resolve(target);
-            });
+      });
     });
-
-    return deferred.promise;
   },
 
   /**
    * Open the Browser Content Toolbox for the provided gBrowser instance.
    * Returns a promise that resolves with a toolbox instance. If no content process is
    * available, the promise will be rejected and a message will be displayed to the user.
    *
    * Used by menus.js
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -1,16 +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";
 
 const { Ci } = require("chrome");
-const defer = require("devtools/shared/defer");
 const EventEmitter = require("devtools/shared/event-emitter");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 loader.lazyRequireGetter(this, "gDevTools",
   "devtools/client/framework/devtools", true);
@@ -183,29 +182,27 @@ TabTarget.prototype = {
    * }
    */
   getActorDescription: function(actorName) {
     if (!this.client) {
       throw new Error("TabTarget#getActorDescription() can only be called on " +
                       "remote tabs.");
     }
 
-    const deferred = defer();
-
-    if (this._protocolDescription &&
-        this._protocolDescription.types[actorName]) {
-      deferred.resolve(this._protocolDescription.types[actorName]);
-    } else {
-      this.client.mainRoot.protocolDescription(description => {
-        this._protocolDescription = description;
-        deferred.resolve(description.types[actorName]);
-      });
-    }
-
-    return deferred.promise;
+    return new Promise(resolve => {
+      if (this._protocolDescription &&
+          this._protocolDescription.types[actorName]) {
+        resolve(this._protocolDescription.types[actorName]);
+      } else {
+        this.client.mainRoot.protocolDescription(description => {
+          this._protocolDescription = description;
+          resolve(description.types[actorName]);
+        });
+      }
+    });
   },
 
   /**
    * Returns a boolean indicating whether or not the specific actor
    * type exists. Must be a remote target.
    *
    * @param {String} actorName
    * @return {Boolean}
@@ -404,21 +401,19 @@ TabTarget.prototype = {
 
   /**
    * Adds remote protocol capabilities to the target, so that it can be used
    * for tools that support the Remote Debugging Protocol even for local
    * connections.
    */
   makeRemote: async function() {
     if (this._remote) {
-      return this._remote.promise;
+      return this._remote;
     }
 
-    this._remote = defer();
-
     if (this.isLocalTab) {
       // Since a remote protocol connection will be made, let's start the
       // DebuggerServer here, once and for all tools.
       DebuggerServer.init();
 
       // When connecting to a local tab, we only need the root actor.
       // Then we are going to call DebuggerServer.connectToFrame and talk
       // directly with actors living in the child process.
@@ -447,66 +442,68 @@ TabTarget.prototype = {
 
       this._form = form;
       this._url = form.url;
       this._title = form.title;
     }
 
     this._setupRemoteListeners();
 
-    const attachTab = async () => {
-      try {
-        const [ response, tabClient ] = await this._client.attachTab(this._form.actor);
-        this.activeTab = tabClient;
-        this.threadActor = response.threadActor;
-      } catch (e) {
-        this._remote.reject("Unable to attach to the tab: " + e);
-        return;
-      }
-      attachConsole();
-    };
+    this._remote = new Promise((resolve, reject) => {
+      const attachTab = async () => {
+        try {
+          const [response, tabClient] = await this._client.attachTab(this._form.actor);
+          this.activeTab = tabClient;
+          this.threadActor = response.threadActor;
+        } catch (e) {
+          reject("Unable to attach to the tab: " + e);
+          return;
+        }
+        attachConsole();
+      };
 
-    const onConsoleAttached = ([response, consoleClient]) => {
-      this.activeConsole = consoleClient;
+      const onConsoleAttached = ([response, consoleClient]) => {
+        this.activeConsole = consoleClient;
 
-      this._onInspectObject = packet => this.emit("inspect-object", packet);
-      this.activeConsole.on("inspectObject", this._onInspectObject);
+        this._onInspectObject = packet => this.emit("inspect-object", packet);
+        this.activeConsole.on("inspectObject", this._onInspectObject);
 
-      this._remote.resolve(null);
-    };
+        resolve(null);
+      };
 
-    const attachConsole = () => {
-      this._client.attachConsole(this._form.consoleActor, [])
-        .then(onConsoleAttached, response => {
-          this._remote.reject(
-            `Unable to attach to the console [${response.error}]: ${response.message}`);
-        });
-    };
+      const attachConsole = () => {
+        this._client.attachConsole(this._form.consoleActor, [])
+          .then(onConsoleAttached, response => {
+            reject(
+              `Unable to attach to the console [${response.error}]: ${response.message}`);
+          });
+      };
 
-    if (this.isLocalTab) {
-      this._client.connect()
-        .then(() => this._client.getTab({ tab: this.tab }))
-        .then(response => {
-          this._form = response.tab;
-          this._url = this._form.url;
-          this._title = this._form.title;
+      if (this.isLocalTab) {
+        this._client.connect()
+          .then(() => this._client.getTab({tab: this.tab}))
+          .then(response => {
+            this._form = response.tab;
+            this._url = this._form.url;
+            this._title = this._form.title;
 
-          attachTab();
-        }, e => this._remote.reject(e));
-    } else if (this.isBrowsingContext) {
-      // In the remote debugging case, the protocol connection will have been
-      // already initialized in the connection screen code.
-      attachTab();
-    } else {
-      // AddonActor and chrome debugging on RootActor doesn't inherit from
-      // BrowsingContextTargetActor and doesn't need to be attached.
-      attachConsole();
-    }
+            attachTab();
+          }, e => reject(e));
+      } else if (this.isBrowsingContext) {
+        // In the remote debugging case, the protocol connection will have been
+        // already initialized in the connection screen code.
+        attachTab();
+      } else {
+        // AddonActor and chrome debugging on RootActor doesn't inherit from
+        // BrowsingContextTargetActor and doesn't need to be attached.
+        attachConsole();
+      }
+    });
 
-    return this._remote.promise;
+    return this._remote;
   },
 
   /**
    * Listen to the different events.
    */
   _setupListeners: function() {
     this.tab.addEventListener("TabClose", this);
     this.tab.parentNode.addEventListener("TabSelect", this);
@@ -645,58 +642,58 @@ TabTarget.prototype = {
 
   /**
    * Target is not alive anymore.
    */
   destroy: function() {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
-      return this._destroyer.promise;
-    }
-
-    this._destroyer = defer();
-
-    // Before taking any action, notify listeners that destruction is imminent.
-    this.emit("close");
-
-    if (this._tab) {
-      this._teardownListeners();
+      return this._destroyer;
     }
 
-    const cleanupAndResolve = () => {
-      this._cleanup();
-      this._destroyer.resolve(null);
-    };
-    // If this target was not remoted, the promise will be resolved before the
-    // function returns.
-    if (this._tab && !this._client) {
-      cleanupAndResolve();
-    } else if (this._client) {
-      // If, on the other hand, this target was remoted, the promise will be
-      // resolved after the remote connection is closed.
-      this._teardownRemoteListeners();
+    this._destroyer = new Promise(resolve => {
+      // Before taking any action, notify listeners that destruction is imminent.
+      this.emit("close");
+
+      if (this._tab) {
+        this._teardownListeners();
+      }
 
-      if (this.isLocalTab) {
-        // We started with a local tab and created the client ourselves, so we
-        // should close it.
-        this._client.close().then(cleanupAndResolve);
-      } else if (this.activeTab) {
-        // The client was handed to us, so we are not responsible for closing
-        // it. We just need to detach from the tab, if already attached.
-        // |detach| may fail if the connection is already dead, so proceed with
-        // cleanup directly after this.
-        this.activeTab.detach();
+      const cleanupAndResolve = () => {
+        this._cleanup();
+        resolve(null);
+      };
+      // If this target was not remoted, the promise will be resolved before the
+      // function returns.
+      if (this._tab && !this._client) {
         cleanupAndResolve();
-      } else {
-        cleanupAndResolve();
+      } else if (this._client) {
+        // If, on the other hand, this target was remoted, the promise will be
+        // resolved after the remote connection is closed.
+        this._teardownRemoteListeners();
+
+        if (this.isLocalTab) {
+          // We started with a local tab and created the client ourselves, so we
+          // should close it.
+          this._client.close().then(cleanupAndResolve);
+        } else if (this.activeTab) {
+          // The client was handed to us, so we are not responsible for closing
+          // it. We just need to detach from the tab, if already attached.
+          // |detach| may fail if the connection is already dead, so proceed with
+          // cleanup directly after this.
+          this.activeTab.detach();
+          cleanupAndResolve();
+        } else {
+          cleanupAndResolve();
+        }
       }
-    }
+    });
 
-    return this._destroyer.promise;
+    return this._destroyer;
   },
 
   /**
    * Clean up references to what this target points to.
    */
   _cleanup: function() {
     if (this._tab) {
       targets.delete(this._tab);
--- a/devtools/client/framework/test/browser_devtools_api_destroy.js
+++ b/devtools/client/framework/test/browser_devtools_api_destroy.js
@@ -12,26 +12,26 @@ function test() {
 function runTests(aTab) {
   const toolDefinition = {
     id: "testTool",
     visibilityswitch: "devtools.testTool.enabled",
     isTargetSupported: () => true,
     url: "about:blank",
     label: "someLabel",
     build: function(iframeWindow, toolbox) {
-      const deferred = defer();
-      executeSoon(() => {
-        deferred.resolve({
-          target: toolbox.target,
-          toolbox: toolbox,
-          isReady: true,
-          destroy: function() {},
+      return new Promise(resolve => {
+        executeSoon(() => {
+          resolve({
+            target: toolbox.target,
+            toolbox: toolbox,
+            isReady: true,
+            destroy: function() {},
+          });
         });
       });
-      return deferred.promise;
     },
   };
 
   gDevTools.registerTool(toolDefinition);
 
   const collectedEvents = [];
 
   const target = TargetFactory.forTab(aTab);
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -155,58 +155,59 @@ async function testSelect(select) {
 
   for (const option of options) {
     if (options.indexOf(option) === select.selectedIndex) {
       continue;
     }
 
     const observer = new PrefObserver("devtools.");
 
-    const deferred = defer();
     let changeSeen = false;
-    observer.once(pref, () => {
-      changeSeen = true;
-      is(GetPref(pref), option.value, "Preference been switched for " + pref);
-      deferred.resolve();
+    const changeSeenPromise = new Promise(resolve => {
+      observer.once(pref, () => {
+        changeSeen = true;
+        is(GetPref(pref), option.value, "Preference been switched for " + pref);
+        resolve();
+      });
     });
 
     select.selectedIndex = options.indexOf(option);
     const changeEvent = new Event("change");
     select.dispatchEvent(changeEvent);
 
-    await deferred.promise;
+    await changeSeenPromise;
 
     ok(changeSeen, "Correct pref was changed");
     observer.destroy();
   }
 }
 
 async function testMouseClick(node, prefValue) {
-  const deferred = defer();
-
   const observer = new PrefObserver("devtools.");
 
   const pref = node.getAttribute("data-pref");
   let changeSeen = false;
-  observer.once(pref, () => {
-    changeSeen = true;
-    is(GetPref(pref), !prefValue, "New value is correct for " + pref);
-    deferred.resolve();
+  const changeSeenPromise = new Promise(resolve => {
+    observer.once(pref, () => {
+      changeSeen = true;
+      is(GetPref(pref), !prefValue, "New value is correct for " + pref);
+      resolve();
+    });
   });
 
   node.scrollIntoView();
 
   // We use executeSoon here to ensure that the element is in view and
   // clickable.
   executeSoon(function() {
     info("Click event synthesized for pref " + pref);
     EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   });
 
-  await deferred.promise;
+  await changeSeenPromise;
 
   ok(changeSeen, "Correct pref was changed");
   observer.destroy();
 }
 
 async function testToggleWebExtensions() {
   const disabledExtensions = new Set();
   const toggleableWebExtensions = toolbox.listWebExtensions();
@@ -397,54 +398,55 @@ async function testToggleTools() {
   await toggleTool(getToolNode(lastToolId));
 }
 
 /**
  * Toggle tool node checkbox. Note: because toggling the checkbox will result in
  * re-rendering of the tool list, we must re-query the checkboxes every time.
  */
 async function toggleTool(node) {
-  const deferred = defer();
+  const toolId = node.getAttribute("id");
 
-  const toolId = node.getAttribute("id");
-  if (node.checked) {
-    gDevTools.once("tool-unregistered",
-      checkUnregistered.bind(null, toolId, deferred));
-  } else {
-    gDevTools.once("tool-registered",
-      checkRegistered.bind(null, toolId, deferred));
-  }
+  const registeredPromise = new Promise(resolve => {
+    if (node.checked) {
+      gDevTools.once("tool-unregistered",
+        checkUnregistered.bind(null, toolId, resolve));
+    } else {
+      gDevTools.once("tool-registered",
+        checkRegistered.bind(null, toolId, resolve));
+    }
+  });
   node.scrollIntoView();
   EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
 
-  await deferred.promise;
+  await registeredPromise;
 }
 
-function checkUnregistered(toolId, deferred, data) {
+function checkUnregistered(toolId, resolve, data) {
   if (data == toolId) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
     ok(!doc.getElementById("toolbox-tab-" + toolId),
       "Tab removed for " + toolId);
   } else {
     ok(false, "Something went wrong, " + toolId + " was not unregistered");
   }
-  deferred.resolve();
+  resolve();
 }
 
-async function checkRegistered(toolId, deferred, data) {
+async function checkRegistered(toolId, resolve, data) {
   if (data == toolId) {
     ok(true, "Correct tool added back");
     // checking tab on the toolbox
     const button = await lookupButtonForToolId(toolId);
     ok(button, "Tab added back for " + toolId);
   } else {
     ok(false, "Something went wrong, " + toolId + " was not registered");
   }
-  deferred.resolve();
+  resolve();
 }
 
 function GetPref(name) {
   const type = Services.prefs.getPrefType(name);
   switch (type) {
     case Services.prefs.PREF_STRING:
       return Services.prefs.getCharPref(name);
     case Services.prefs.PREF_INT:
--- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
@@ -21,48 +21,47 @@ function test() {
       .then(testSelectTool)
       .then(testToggleToolboxButtons)
       .then(testPrefsAreRespectedWhenReopeningToolbox)
       .then(cleanup, errorHandler);
   });
 }
 
 function testPrefsAreRespectedWhenReopeningToolbox() {
-  const deferred = defer();
   const target = TargetFactory.forTab(gBrowser.selectedTab);
 
-  info("Closing toolbox to test after reopening");
-  gDevTools.closeToolbox(target).then(() => {
-    const tabTarget = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.showToolbox(tabTarget)
-      .then(testSelectTool)
-      .then(() => {
-        info("Toolbox has been reopened.  Checking UI state.");
-        testPreferenceAndUIStateIsConsistent();
-        deferred.resolve();
-      });
+  return new Promise(resolve => {
+    info("Closing toolbox to test after reopening");
+    gDevTools.closeToolbox(target).then(() => {
+      const tabTarget = TargetFactory.forTab(gBrowser.selectedTab);
+      gDevTools.showToolbox(tabTarget)
+        .then(testSelectTool)
+        .then(() => {
+          info("Toolbox has been reopened.  Checking UI state.");
+          testPreferenceAndUIStateIsConsistent();
+          resolve();
+        });
+    });
   });
-
-  return deferred.promise;
 }
 
 function testSelectTool(devtoolsToolbox) {
-  const deferred = defer();
-  info("Selecting the options panel");
+  return new Promise(resolve => {
+    info("Selecting the options panel");
+
+    toolbox = devtoolsToolbox;
+    doc = toolbox.doc;
 
-  toolbox = devtoolsToolbox;
-  doc = toolbox.doc;
-  toolbox.once("options-selected", tool => {
-    ok(true, "Options panel selected via selectTool method");
-    panelWin = tool.panelWin;
-    deferred.resolve();
+    toolbox.selectTool("options");
+    toolbox.once("options-selected", tool => {
+      ok(true, "Options panel selected via selectTool method");
+      panelWin = tool.panelWin;
+      resolve();
+    });
   });
-  toolbox.selectTool("options");
-
-  return deferred.promise;
 }
 
 function testPreferenceAndUIStateIsConsistent() {
   const checkNodes = [...panelWin.document.querySelectorAll(
     "#enabled-toolbox-buttons-box input[type=checkbox]")];
   const toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
 
   for (const tool of toolbox.toolbarButtons) {
--- a/devtools/client/framework/test/browser_toolbox_sidebar.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar.js
@@ -16,27 +16,27 @@ function test() {
 
   const toolDefinition = {
     id: "fakeTool4242",
     visibilityswitch: "devtools.fakeTool4242.enabled",
     url: CHROME_URL_ROOT + "browser_toolbox_sidebar_toolURL.xul",
     label: "FAKE TOOL!!!",
     isTargetSupported: () => true,
     build: function(iframeWindow, toolbox) {
-      const deferred = defer();
-      executeSoon(() => {
-        deferred.resolve({
-          target: toolbox.target,
-          toolbox: toolbox,
-          isReady: true,
-          destroy: function() {},
-          panelDoc: iframeWindow.document,
+      return new Promise(resolve => {
+        executeSoon(() => {
+          resolve({
+            target: toolbox.target,
+            toolbox: toolbox,
+            isReady: true,
+            destroy: function() {},
+            panelDoc: iframeWindow.document,
+          });
         });
       });
-      return deferred.promise;
     },
   };
 
   gDevTools.registerTool(toolDefinition);
 
   addTab("about:blank").then(function(aTab) {
     const target = TargetFactory.forTab(aTab);
     gDevTools.showToolbox(target, toolDefinition.id).then(function(toolbox) {
--- a/devtools/client/framework/test/browser_toolbox_sidebar_events.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_events.js
@@ -12,27 +12,27 @@ function test() {
 
   const toolDefinition = {
     id: "testTool1072208",
     visibilityswitch: "devtools.testTool1072208.enabled",
     url: CHROME_URL_ROOT + "browser_toolbox_sidebar_events.xul",
     label: "Test tool",
     isTargetSupported: () => true,
     build: function(iframeWindow, toolbox) {
-      const deferred = defer();
-      executeSoon(() => {
-        deferred.resolve({
-          target: toolbox.target,
-          toolbox: toolbox,
-          isReady: true,
-          destroy: function() {},
-          panelDoc: iframeWindow.document,
+      return new Promise(resolve => {
+        executeSoon(() => {
+          resolve({
+            target: toolbox.target,
+            toolbox: toolbox,
+            isReady: true,
+            destroy: function() {},
+            panelDoc: iframeWindow.document,
+          });
         });
       });
-      return deferred.promise;
     },
   };
 
   gDevTools.registerTool(toolDefinition);
 
   addTab("about:blank").then(function(aTab) {
     const target = TargetFactory.forTab(aTab);
     gDevTools.showToolbox(target, toolDefinition.id).then(function(toolbox) {
--- a/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
+++ b/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
@@ -66,28 +66,26 @@ function getClient() {
 
   const transport = DebuggerServer.connectPipe();
   const client = new DebuggerClient(transport);
 
   return client.connect().then(() => client);
 }
 
 function getTarget(client) {
-  const deferred = defer();
-
-  client.listTabs().then(tabList => {
-    const target = TargetFactory.forRemoteTab({
-      client: client,
-      form: tabList.tabs[tabList.selected],
-      chrome: false
+  return new Promise(resolve => {
+    client.listTabs().then(tabList => {
+      const target = TargetFactory.forRemoteTab({
+        client: client,
+        form: tabList.tabs[tabList.selected],
+        chrome: false
+      });
+      resolve(target);
     });
-    deferred.resolve(target);
   });
-
-  return deferred.promise;
 }
 
 function test() {
   (async function() {
     toggleAllTools(true);
     await addTab("about:blank");
 
     const client = await getClient();
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -51,48 +51,48 @@ function getSourceActor(aSources, aURL) 
 
 /**
  * Open a Scratchpad window.
  *
  * @return nsIDOMWindow
  *         The new window object that holds Scratchpad.
  */
 async function openScratchpadWindow() {
-  const { promise: p, resolve } = defer();
   const win = ScratchpadManager.openScratchpad();
 
   await once(win, "load");
 
-  win.Scratchpad.addObserver({
-    onReady: function() {
-      win.Scratchpad.removeObserver(this);
-      resolve(win);
-    }
+  return new Promise(resolve => {
+    win.Scratchpad.addObserver({
+      onReady: function() {
+        win.Scratchpad.removeObserver(this);
+        resolve(win);
+      }
+    });
   });
-  return p;
 }
 
 /**
  * Wait for a content -> chrome message on the message manager (the window
  * messagemanager is used).
  * @param {String} name The message name
  * @return {Promise} A promise that resolves to the response data when the
  * message has been received
  */
 function waitForContentMessage(name) {
   info("Expecting message " + name + " from content");
 
   const mm = gBrowser.selectedBrowser.messageManager;
 
-  const def = defer();
-  mm.addMessageListener(name, function onMessage(msg) {
-    mm.removeMessageListener(name, onMessage);
-    def.resolve(msg.data);
+  return new Promise(resolve => {
+    mm.addMessageListener(name, function onMessage(msg) {
+      mm.removeMessageListener(name, onMessage);
+      resolve(msg.data);
+    });
   });
-  return def.promise;
 }
 
 /**
  * Send an async message to the frame script (chrome -> content) and wait for a
  * response message with the same name (content -> chrome).
  * @param {String} name The message name. Should be one of the messages defined
  * in doc_frame_script.js
  * @param {Object} data Optional data to send along
@@ -202,25 +202,23 @@ function DevToolPanel(iframeWindow, tool
   EventEmitter.decorate(this);
 
   this._toolbox = toolbox;
   this._window = iframeWindow;
 }
 
 DevToolPanel.prototype = {
   open: function() {
-    const deferred = defer();
-
-    executeSoon(() => {
-      this._isReady = true;
-      this.emit("ready");
-      deferred.resolve(this);
+    return new Promise(resolve => {
+      executeSoon(() => {
+        this._isReady = true;
+        this.emit("ready");
+        resolve(this);
+      });
     });
-
-    return deferred.promise;
   },
 
   get document() {
     return this._window.document;
   },
 
   get target() {
     return this._toolbox.target;
@@ -232,17 +230,17 @@ DevToolPanel.prototype = {
 
   get isReady() {
     return this._isReady;
   },
 
   _isReady: false,
 
   destroy: function() {
-    return defer(null);
+    return Promise.resolve(null);
   },
 };
 
 /**
  * Create a simple devtools test panel that implements the minimum API needed to be
  * registered and opened in the toolbox.
  */
 function createTestPanel(iframeWindow, toolbox) {
--- a/devtools/client/framework/test/helper_disable_cache.js
+++ b/devtools/client/framework/test/helper_disable_cache.js
@@ -89,29 +89,27 @@ async function setDisableCacheCheckboxCh
 
     // We need to wait for all checkboxes to be updated and the docshells to
     // apply the new cache settings.
     await waitForTick();
   }
 }
 
 function reloadTab(tabX) {
-  const def = defer();
   const browser = gBrowser.selectedBrowser;
 
-  BrowserTestUtils.browserLoaded(browser).then(function() {
+  const reloadTabPromise = BrowserTestUtils.browserLoaded(browser).then(function() {
     info("Reloaded tab " + tabX.title);
-    def.resolve();
   });
 
   info("Reloading tab " + tabX.title);
   const mm = loadFrameScriptUtils();
   mm.sendAsyncMessage("devtools:test:reload");
 
-  return def.promise;
+  return reloadTabPromise;
 }
 
 async function destroyTab(tabX) {
   const toolbox = gDevTools.getToolbox(tabX.target);
 
   let onceDestroyed = promise.resolve();
   if (toolbox) {
     onceDestroyed = gDevTools.once("toolbox-destroyed");
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
-const defer = require("devtools/shared/defer");
 const Services = require("Services");
 const {DOMHelpers} = require("resource://devtools/client/shared/DOMHelpers.jsm");
 
 loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 
 /* A host should always allow this much space for the page to be displayed.
  * There is also a min-height on the browser, but we still don't want to set
@@ -238,46 +237,43 @@ WindowHost.prototype = {
   type: "window",
 
   WINDOW_URL: "chrome://devtools/content/framework/toolbox-window.xul",
 
   /**
    * Create a new xul window to contain the toolbox.
    */
   create: function() {
-    const deferred = defer();
+    return new Promise(resolve => {
+      const flags = "chrome,centerscreen,resizable,dialog=no";
+      const win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
+                                      flags, null);
 
-    const flags = "chrome,centerscreen,resizable,dialog=no";
-    const win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
-                                     flags, null);
-
-    const frameLoad = () => {
-      win.removeEventListener("load", frameLoad, true);
-      win.focus();
+      const frameLoad = () => {
+        win.removeEventListener("load", frameLoad, true);
+        win.focus();
 
-      let key;
-      if (AppConstants.platform === "macosx") {
-        key = win.document.getElementById("toolbox-key-toggle-osx");
-      } else {
-        key = win.document.getElementById("toolbox-key-toggle");
-      }
-      key.removeAttribute("disabled");
+        let key;
+        if (AppConstants.platform === "macosx") {
+          key = win.document.getElementById("toolbox-key-toggle-osx");
+        } else {
+          key = win.document.getElementById("toolbox-key-toggle");
+        }
+        key.removeAttribute("disabled");
 
-      this.frame = win.document.getElementById("toolbox-iframe");
-      this.emit("ready", this.frame);
-
-      deferred.resolve(this.frame);
-    };
+        this.frame = win.document.getElementById("toolbox-iframe");
+        this.emit("ready", this.frame);
+        resolve(this.frame);
+      };
 
-    win.addEventListener("load", frameLoad, true);
-    win.addEventListener("unload", this._boundUnload);
+      win.addEventListener("load", frameLoad, true);
+      win.addEventListener("unload", this._boundUnload);
 
-    this._window = win;
-
-    return deferred.promise;
+      this._window = win;
+    });
   },
 
   /**
    * Catch the user closing the window.
    */
   _boundUnload: function(event) {
     if (event.target.location != this.WINDOW_URL) {
       return;
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -1,16 +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";
 
 const Services = require("Services");
-const defer = require("devtools/shared/defer");
 const {gDevTools} = require("devtools/client/framework/devtools");
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
 loader.lazyRequireGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true);
 
 exports.OptionsPanel = OptionsPanel;
@@ -509,34 +508,33 @@ OptionsPanel.prototype = {
     this.target.activeTab.reconfigure(options);
   },
 
   destroy: function() {
     if (this.destroyPromise) {
       return this.destroyPromise;
     }
 
-    const deferred = defer();
-    this.destroyPromise = deferred.promise;
-
     this._removeListeners();
 
-    if (this.target.activeTab) {
-      this.disableJSNode.removeEventListener("click", this._disableJSClicked);
-      // FF41+ automatically cleans up state in actor on disconnect
-      if (!this.target.activeTab.traits.noTabReconfigureOnClose) {
-        const options = {
-          "javascriptEnabled": this._origJavascriptEnabled,
-          "performReload": false
-        };
-        this.target.activeTab.reconfigure(options, deferred.resolve);
+    this.destroyPromise = new Promise(resolve => {
+      if (this.target.activeTab) {
+        this.disableJSNode.removeEventListener("click", this._disableJSClicked);
+        // FF41+ automatically cleans up state in actor on disconnect
+        if (!this.target.activeTab.traits.noTabReconfigureOnClose) {
+          const options = {
+            "javascriptEnabled": this._origJavascriptEnabled,
+            "performReload": false
+          };
+          this.target.activeTab.reconfigure(options, resolve);
+        } else {
+          resolve();
+        }
       } else {
-        deferred.resolve();
+        resolve();
       }
-    } else {
-      deferred.resolve();
-    }
+    });
 
     this.panelWin = this.panelDoc = this.disableJSNode = this.toolbox = null;
 
     return this.destroyPromise;
   }
 };
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -12,17 +12,16 @@ const SPLITCONSOLE_HEIGHT_PREF = "devtoo
 const DISABLE_AUTOHIDE_PREF = "ui.popup.disable_autohide";
 const HOST_HISTOGRAM = "DEVTOOLS_TOOLBOX_HOST";
 const CURRENT_THEME_SCALAR = "devtools.current_theme";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const REGEX_PANEL = /webconsole|inspector|jsdebugger|styleeditor|netmonitor|storage/;
 
 var {Ci, Cc} = require("chrome");
 var promise = require("promise");
-var defer = require("devtools/shared/defer");
 const { debounce } = require("devtools/shared/debounce");
 var Services = require("Services");
 var ChromeUtils = require("ChromeUtils");
 var {gDevTools} = require("devtools/client/framework/devtools");
 var EventEmitter = require("devtools/shared/event-emitter");
 var Telemetry = require("devtools/client/shared/telemetry");
 const { getUnicodeUrl } = require("devtools/client/shared/unicode-url");
 var { attachThread, detachThread } = require("./attach-thread");
@@ -167,18 +166,19 @@ function Toolbox(target, selectedTool, h
 
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   this._defaultToolId = selectedTool;
 
   this._hostType = hostType;
 
-  this._isOpenDeferred = defer();
-  this.isOpen = this._isOpenDeferred.promise;
+  this.isOpen = new Promise(function(resolve) {
+    this._resolveIsOpen = resolve;
+  }.bind(this));
 
   EventEmitter.decorate(this);
 
   this._target.on("will-navigate", this._onWillNavigate);
   this._target.on("navigate", this._refreshHostTitle);
   this._target.on("frame-update", this._updateFrames);
   this._target.on("inspect-object", this._onInspectObject);
 
@@ -298,27 +298,26 @@ Toolbox.prototype = {
    * like to select the tool right away.
    *
    * @param  {String} id
    *         The id of the panel, for example "jsdebugger".
    * @returns Promise
    *          A promise that resolves once the panel is ready.
    */
   getPanelWhenReady: function(id) {
-    const deferred = defer();
     const panel = this.getPanel(id);
-    if (panel) {
-      deferred.resolve(panel);
-    } else {
-      this.on(id + "-ready", initializedPanel => {
-        deferred.resolve(initializedPanel);
-      });
-    }
-
-    return deferred.promise;
+    return new Promise(resolve => {
+      if (panel) {
+        resolve(panel);
+      } else {
+        this.on(id + "-ready", initializedPanel => {
+          resolve(initializedPanel);
+        });
+      }
+    });
   },
 
   /**
    * This is a shortcut for getPanel(currentToolId) because it is much more
    * likely that we're going to want to get the panel that we've just made
    * visible
    */
   getCurrentPanel: function() {
@@ -431,21 +430,22 @@ Toolbox.prototype = {
         useOnlyShared: true
       }).require;
 
       if (this.win.location.href.startsWith(this._URL)) {
         // Update the URL so that onceDOMReady watch for the right url.
         this._URL = this.win.location.href;
       }
 
-      const domReady = defer();
       const domHelper = new DOMHelpers(this.win);
-      domHelper.onceDOMReady(() => {
-        domReady.resolve();
-      }, this._URL);
+      const domReady = new Promise(resolve => {
+        domHelper.onceDOMReady(() => {
+          resolve();
+        }, this._URL);
+      });
 
       // Optimization: fire up a few other things before waiting on
       // the iframe being ready (makes startup faster)
 
       // Load the toolbox-level actor fronts and utilities now
       await this._target.makeRemote();
 
       // Start tracking network activity on toolbox open for targets such as tabs.
@@ -453,17 +453,17 @@ Toolbox.prototype = {
       if (this._target.activeConsole) {
         await this._target.activeConsole.startListeners([
           "NetworkActivity",
         ]);
       }
 
       // Attach the thread
       this._threadClient = await attachThread(this);
-      await domReady.promise;
+      await domReady;
 
       this.isReady = true;
 
       const framesPromise = this._listFrames();
 
       Services.prefs.addObserver("devtools.cache.disabled", this._applyCacheSettings);
       Services.prefs.addObserver("devtools.serviceWorkers.testing.enabled",
                                  this._applyServiceWorkersTestingSettings);
@@ -561,17 +561,17 @@ Toolbox.prototype = {
       // If in testing environment, wait for performance connection to finish,
       // so we don't have to explicitly wait for this in tests; ideally, all tests
       // will handle this on their own, but each have their own tear down function.
       if (flags.testing) {
         await performanceFrontConnection;
       }
 
       this.emit("ready");
-      this._isOpenDeferred.resolve();
+      this._resolveIsOpen();
     }.bind(this))().catch(console.error);
   },
 
   /**
    * loading React modules when needed (to avoid performance penalties
    * during Firefox start up time).
    */
   get React() {
@@ -1612,140 +1612,139 @@ Toolbox.prototype = {
    * @param {string} id
    *        The id of the tool to load.
    */
   loadTool: function(id) {
     if (id === "inspector" && !this._inspector) {
       return this.initInspector().then(() => this.loadTool(id));
     }
 
-    const deferred = defer();
     let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
-
     if (iframe) {
       const panel = this._toolPanels.get(id);
-      if (panel) {
-        deferred.resolve(panel);
-      } else {
-        this.once(id + "-ready", initializedPanel => {
-          deferred.resolve(initializedPanel);
-        });
-      }
-      return deferred.promise;
-    }
-
-    // Retrieve the tool definition (from the global or the per-toolbox tool maps)
-    const definition = this.getToolDefinition(id);
-
-    if (!definition) {
-      deferred.reject(new Error("no such tool id " + id));
-      return deferred.promise;
-    }
-
-    iframe = this.doc.createElement("iframe");
-    iframe.className = "toolbox-panel-iframe";
-    iframe.id = "toolbox-panel-iframe-" + id;
-    iframe.setAttribute("flex", 1);
-    iframe.setAttribute("forceOwnRefreshDriver", "");
-    iframe.tooltip = "aHTMLTooltip";
-    iframe.style.visibility = "hidden";
-
-    gDevTools.emit(id + "-init", this, iframe);
-    this.emit(id + "-init", iframe);
-
-    // If no parent yet, append the frame into default location.
-    if (!iframe.parentNode) {
-      const vbox = this.doc.getElementById("toolbox-panel-" + id);
-      vbox.appendChild(iframe);
-      vbox.visibility = "visible";
+      return new Promise(resolve => {
+        if (panel) {
+          resolve(panel);
+        } else {
+          this.once(id + "-ready", initializedPanel => {
+            resolve(initializedPanel);
+          });
+        }
+      });
     }
 
-    const onLoad = () => {
-      // Prevent flicker while loading by waiting to make visible until now.
-      iframe.style.visibility = "visible";
-
-      // Try to set the dir attribute as early as possible.
-      this.setIframeDocumentDir(iframe);
-
-      // The build method should return a panel instance, so events can
-      // be fired with the panel as an argument. However, in order to keep
-      // backward compatibility with existing extensions do a check
-      // for a promise return value.
-      let built = definition.build(iframe.contentWindow, this);
-
-      if (!(typeof built.then == "function")) {
-        const panel = built;
-        iframe.panel = panel;
-
-        // The panel instance is expected to fire (and listen to) various
-        // framework events, so make sure it's properly decorated with
-        // appropriate API (on, off, once, emit).
-        // In this case we decorate panel instances directly returned by
-        // the tool definition 'build' method.
-        if (typeof panel.emit == "undefined") {
-          EventEmitter.decorate(panel);
-        }
-
-        gDevTools.emit(id + "-build", this, panel);
-        this.emit(id + "-build", panel);
-
-        // The panel can implement an 'open' method for asynchronous
-        // initialization sequence.
-        if (typeof panel.open == "function") {
-          built = panel.open();
-        } else {
-          const buildDeferred = defer();
-          buildDeferred.resolve(panel);
-          built = buildDeferred.promise;
-        }
+    return new Promise((resolve, reject) => {
+      // Retrieve the tool definition (from the global or the per-toolbox tool maps)
+      const definition = this.getToolDefinition(id);
+
+      if (!definition) {
+        reject(new Error("no such tool id " + id));
+        return;
+      }
+
+      iframe = this.doc.createElement("iframe");
+      iframe.className = "toolbox-panel-iframe";
+      iframe.id = "toolbox-panel-iframe-" + id;
+      iframe.setAttribute("flex", 1);
+      iframe.setAttribute("forceOwnRefreshDriver", "");
+      iframe.tooltip = "aHTMLTooltip";
+      iframe.style.visibility = "hidden";
+
+      gDevTools.emit(id + "-init", this, iframe);
+      this.emit(id + "-init", iframe);
+
+      // If no parent yet, append the frame into default location.
+      if (!iframe.parentNode) {
+        const vbox = this.doc.getElementById("toolbox-panel-" + id);
+        vbox.appendChild(iframe);
+        vbox.visibility = "visible";
       }
 
-      // Wait till the panel is fully ready and fire 'ready' events.
-      promise.resolve(built).then((panel) => {
-        this._toolPanels.set(id, panel);
-
-        // Make sure to decorate panel object with event API also in case
-        // where the tool definition 'build' method returns only a promise
-        // and the actual panel instance is available as soon as the
-        // promise is resolved.
-        if (typeof panel.emit == "undefined") {
-          EventEmitter.decorate(panel);
+      const onLoad = () => {
+        // Prevent flicker while loading by waiting to make visible until now.
+        iframe.style.visibility = "visible";
+
+        // Try to set the dir attribute as early as possible.
+        this.setIframeDocumentDir(iframe);
+
+        // The build method should return a panel instance, so events can
+        // be fired with the panel as an argument. However, in order to keep
+        // backward compatibility with existing extensions do a check
+        // for a promise return value.
+        let built = definition.build(iframe.contentWindow, this);
+
+        if (!(typeof built.then == "function")) {
+          const panel = built;
+          iframe.panel = panel;
+
+          // The panel instance is expected to fire (and listen to) various
+          // framework events, so make sure it's properly decorated with
+          // appropriate API (on, off, once, emit).
+          // In this case we decorate panel instances directly returned by
+          // the tool definition 'build' method.
+          if (typeof panel.emit == "undefined") {
+            EventEmitter.decorate(panel);
+          }
+
+          gDevTools.emit(id + "-build", this, panel);
+          this.emit(id + "-build", panel);
+
+          // The panel can implement an 'open' method for asynchronous
+          // initialization sequence.
+          if (typeof panel.open == "function") {
+            built = panel.open();
+          } else {
+            built = new Promise(resolve => {
+              resolve(panel);
+            });
+          }
         }
 
-        gDevTools.emit(id + "-ready", this, panel);
-        this.emit(id + "-ready", panel);
-
-        deferred.resolve(panel);
-      }, console.error);
-    };
-
-    iframe.setAttribute("src", definition.url);
-    if (definition.panelLabel) {
-      iframe.setAttribute("aria-label", definition.panelLabel);
-    }
-
-    // Depending on the host, iframe.contentWindow is not always
-    // defined at this moment. If it is not defined, we use an
-    // event listener on the iframe DOM node. If it's defined,
-    // we use the chromeEventHandler. We can't use a listener
-    // on the DOM node every time because this won't work
-    // if the (xul chrome) iframe is loaded in a content docshell.
-    if (iframe.contentWindow) {
-      const domHelper = new DOMHelpers(iframe.contentWindow);
-      domHelper.onceDOMReady(onLoad);
-    } else {
-      const callback = () => {
-        iframe.removeEventListener("DOMContentLoaded", callback);
-        onLoad();
+        // Wait till the panel is fully ready and fire 'ready' events.
+        promise.resolve(built).then((panel) => {
+          this._toolPanels.set(id, panel);
+
+          // Make sure to decorate panel object with event API also in case
+          // where the tool definition 'build' method returns only a promise
+          // and the actual panel instance is available as soon as the
+          // promise is resolved.
+          if (typeof panel.emit == "undefined") {
+            EventEmitter.decorate(panel);
+          }
+
+          gDevTools.emit(id + "-ready", this, panel);
+          this.emit(id + "-ready", panel);
+
+          resolve(panel);
+        }, console.error);
       };
 
-      iframe.addEventListener("DOMContentLoaded", callback);
-    }
-
-    return deferred.promise;
+      iframe.setAttribute("src", definition.url);
+      if (definition.panelLabel) {
+        iframe.setAttribute("aria-label", definition.panelLabel);
+      }
+
+      // Depending on the host, iframe.contentWindow is not always
+      // defined at this moment. If it is not defined, we use an
+      // event listener on the iframe DOM node. If it's defined,
+      // we use the chromeEventHandler. We can't use a listener
+      // on the DOM node every time because this won't work
+      // if the (xul chrome) iframe is loaded in a content docshell.
+      if (iframe.contentWindow) {
+        const domHelper = new DOMHelpers(iframe.contentWindow);
+        domHelper.onceDOMReady(onLoad);
+      } else {
+        const callback = () => {
+          iframe.removeEventListener("DOMContentLoaded", callback);
+          onLoad();
+        };
+
+        iframe.addEventListener("DOMContentLoaded", callback);
+      }
+    });
   },
 
   /**
    * Set the dir attribute on the content document element of the provided iframe.
    *
    * @param {IFrameElement} iframe
    */
   setIframeDocumentDir: function(iframe) {
@@ -2809,18 +2808,16 @@ Toolbox.prototype = {
    * Remove all UI elements, detach from target and clear up
    */
   destroy: function() {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
       return this._destroyer;
     }
-    const deferred = defer();
-    this._destroyer = deferred.promise;
 
     this.emit("destroy");
 
     this._target.off("inspect-object", this._onInspectObject);
     this._target.off("will-navigate", this._onWillNavigate);
     this._target.off("navigate", this._refreshHostTitle);
     this._target.off("frame-update", this._updateFrames);
     this.off("select", this._onToolSelected);
@@ -2946,17 +2943,18 @@ Toolbox.prototype = {
       "panel_name": this.getTelemetryPanelNameOrOther(this.currentToolId),
       "next_panel": "none",
       "reason": "toolbox_close"
     });
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
-    deferred.resolve(settleAll(outstanding)
+    this._destroyer = new Promise(resolve => {
+      resolve(settleAll(outstanding)
         .catch(console.error)
         .then(() => {
           const api = this._netMonitorAPI;
           this._netMonitorAPI = null;
           return api ? api.destroy() : null;
         }, console.error)
         .then(() => {
           this._removeHostListeners();
@@ -2998,16 +2996,17 @@ Toolbox.prototype = {
           // Force GC to prevent long GC pauses when running tests and to free up
           // memory in general when the toolbox is closed.
           if (flags.testing) {
             win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils)
               .garbageCollect();
           }
         }).catch(console.error));
+    });
 
     const leakCheckObserver = ({wrappedJSObject: barrier}) => {
       // Make the leak detector wait until this toolbox is properly destroyed.
       barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
                                 this._destroyer);
     };
 
     const topic = "shutdown-leaks-before-check";
@@ -3054,44 +3053,44 @@ Toolbox.prototype = {
   async initPerformance() {
     // If target does not have performance actor (addons), do not
     // even register the shared performance connection.
     if (!this.target.hasActor("performance")) {
       return promise.resolve();
     }
 
     if (this._performanceFrontConnection) {
-      return this._performanceFrontConnection.promise;
+      return this._performanceFrontConnection;
     }
 
-    this._performanceFrontConnection = defer();
     this._performance = createPerformanceFront(this._target);
     await this.performance.connect();
 
     // Emit an event when connected, but don't wait on startup for this.
     this.emit("profiler-connected");
 
     this.performance.on("*", this._onPerformanceFrontEvent);
-    this._performanceFrontConnection.resolve(this.performance);
-    return this._performanceFrontConnection.promise;
+    return this._performanceFrontConnection = new Promise(resolve => {
+      resolve(this.performance);
+    });
   },
 
   /**
    * Disconnects the underlying Performance actor. If the connection
    * has not finished initializing, as opening a toolbox does not wait,
    * the performance connection destroy method will wait for it on its own.
    */
   async destroyPerformance() {
     if (!this.performance) {
       return;
     }
     // If still connecting to performance actor, allow the
     // actor to resolve its connection before attempting to destroy.
     if (this._performanceFrontConnection) {
-      await this._performanceFrontConnection.promise;
+      await this._performanceFrontConnection;
     }
     this.performance.off("*", this._onPerformanceFrontEvent);
     await this.performance.destroy();
     this._performance = null;
   },
 
   /**
    * Return the style sheets front, creating it if necessary.  If the