Backed out 5 changesets (bug 1384527, bug 1734786, bug 1734782, bug 1734592, bug 1734785) for causing mochitest failures. CLOSED TREE
authorCristian Tuns <ctuns@mozilla.com>
Mon, 11 Oct 2021 11:06:16 -0400
changeset 595373 712a0c6e4fc22e84ae74e96fc588ad41a088258f
parent 595372 759d9b550cca9061128e98b24909c9547e02bc89
child 595374 c15b48000571a810d69f3c8e066af579428e7691
push id151231
push userctuns@mozilla.com
push dateMon, 11 Oct 2021 15:07:07 +0000
treeherderautoland@712a0c6e4fc2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1384527, 1734786, 1734782, 1734592, 1734785
milestone95.0a1
backs outfd5088801f6d8fc24833c33704aadd128ed094a5
0752e08d7ec9c841d6b3163fe2c22d04c9e2d932
038d15951e1a6c5a8f9fe70a02a16e05a4ebcb1f
a6a1a9b835f4fef1eb31d539a09c4094394b685d
aca421f634ea2c8edcd3cf3d24376ec3f83e5d86
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
Backed out 5 changesets (bug 1384527, bug 1734786, bug 1734782, bug 1734592, bug 1734785) for causing mochitest failures. CLOSED TREE Backed out changeset fd5088801f6d (bug 1384527) Backed out changeset 0752e08d7ec9 (bug 1734786) Backed out changeset 038d15951e1a (bug 1734782) Backed out changeset a6a1a9b835f4 (bug 1734785) Backed out changeset aca421f634ea (bug 1734592)
devtools/client/framework/toolbox.js
devtools/client/inspector/computed/computed.js
devtools/client/inspector/inspector-search.js
devtools/client/inspector/inspector.js
devtools/client/inspector/markup/markup-context-menu.js
devtools/client/inspector/markup/views/element-container.js
devtools/client/inspector/rules/models/element-style.js
devtools/client/inspector/rules/models/rule.js
devtools/client/inspector/rules/rules.js
devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
devtools/client/inspector/rules/views/rule-editor.js
devtools/client/inspector/test/browser_inspector_search-01.js
devtools/client/inspector/test/browser_inspector_search-02.js
devtools/client/inspector/test/browser_inspector_search-03.js
devtools/client/inspector/test/browser_inspector_search-04.js
devtools/client/inspector/test/browser_inspector_search-05.js
devtools/client/inspector/test/browser_inspector_search-06.js
devtools/client/inspector/test/browser_inspector_search-07.js
devtools/client/inspector/test/browser_inspector_search-08.js
devtools/client/inspector/test/browser_inspector_search-09.js
devtools/client/inspector/test/browser_inspector_search-clear.js
devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
devtools/client/inspector/test/browser_inspector_search-navigation.js
devtools/client/inspector/test/browser_inspector_search-reserved.js
devtools/client/inspector/test/browser_inspector_search-selection.js
devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js
devtools/shared/Loader.jsm
devtools/shared/builtin-modules.js
devtools/shared/tests/xpcshell/test_invisible_loader.js
devtools/shared/worker/worker.js
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -14,16 +14,17 @@ const SPLITCONSOLE_ENABLED_PREF = "devto
 const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
 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_4XX_5XX = /^[4,5]\d\d$/;
 
 var { Ci, Cc } = require("chrome");
+var promise = require("promise");
 const { debounce } = require("devtools/shared/debounce");
 const { throttle } = require("devtools/shared/throttle");
 const { safeAsyncMethod } = require("devtools/shared/async-utils");
 var Services = require("Services");
 var ChromeUtils = require("ChromeUtils");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var EventEmitter = require("devtools/shared/event-emitter");
 const Selection = require("devtools/client/framework/selection");
@@ -903,17 +904,17 @@ Toolbox.prototype = {
         },
         { timeout: 16 }
       );
 
       await this.selectTool(this._defaultToolId, "initial_panel");
 
       // Wait until the original tool is selected so that the split
       // console input will receive focus.
-      let splitConsolePromise = Promise.resolve();
+      let splitConsolePromise = promise.resolve();
       if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
         splitConsolePromise = this.openSplitConsole();
         this.telemetry.addEventProperty(
           this.topWindow,
           "open",
           "tools",
           null,
           "splitconsole",
@@ -925,17 +926,17 @@ Toolbox.prototype = {
           "open",
           "tools",
           null,
           "splitconsole",
           false
         );
       }
 
-      await Promise.all([
+      await promise.all([
         splitConsolePromise,
         framesPromise,
         onResourcesWatched,
       ]);
 
       // We do not expect the focus to be restored when using about:debugging toolboxes
       // Otherwise, when reloading the toolbox, the debugged tab will be focused.
       if (this.hostType !== Toolbox.HostType.PAGE) {
@@ -2536,17 +2537,17 @@ Toolbox.prototype = {
           } else {
             built = new Promise(resolve => {
               resolve(panel);
             });
           }
         }
 
         // Wait till the panel is fully ready and fire 'ready' events.
-        Promise.resolve(built).then(panel => {
+        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);
@@ -2677,22 +2678,22 @@ Toolbox.prototype = {
       const panel = this._toolPanels.get(id);
       if (panel) {
         // We have a panel instance, so the tool is already fully loaded.
 
         // re-focus tool to get key events again
         this.focusTool(id);
 
         // Return the existing panel in order to have a consistent return value.
-        return Promise.resolve(panel);
+        return promise.resolve(panel);
       }
       // Otherwise, if there is no panel instance, it is still loading,
       // so we are racing another call to selectTool with the same id.
       return this.once("select").then(() =>
-        Promise.resolve(this._toolPanels.get(id))
+        promise.resolve(this._toolPanels.get(id))
       );
     }
 
     if (!this.isReady) {
       throw new Error("Can't select tool, wait for toolbox 'ready' event");
     }
 
     // Check if the tool exists.
@@ -2900,34 +2901,34 @@ Toolbox.prototype = {
       session_id: this.sessionId,
     });
 
     this.emit("split-console");
 
     if (this._lastFocusedElement) {
       this._lastFocusedElement.focus();
     }
-    return Promise.resolve();
+    return promise.resolve();
   },
 
   /**
    * Toggles the split state of the webconsole.  If the webconsole panel
    * is already selected then this command is ignored.
    *
    * @returns {Promise} a promise that resolves once the tool has been
    *          opened or closed.
    */
   toggleSplitConsole: function() {
     if (this.currentToolId !== "webconsole") {
       return this.splitConsole
         ? this.closeSplitConsole()
         : this.openSplitConsole();
     }
 
-    return Promise.resolve();
+    return promise.resolve();
   },
 
   /**
    * Toggles the options panel.
    * If the option panel is already selected then select the last selected panel.
    */
   toggleOptions: function(event) {
     // Flip back to the last used panel if we are already
@@ -3145,17 +3146,17 @@ Toolbox.prototype = {
     const prefFront = await this.preferenceFront;
     return prefFront.getBoolPref(DISABLE_AUTOHIDE_PREF);
   },
 
   _listFrames: async function(event) {
     if (!this.target.getTrait("frames")) {
       // We are not targetting a regular WindowGlobalTargetActor
       // it can be either an addon or browser toolbox actor
-      return Promise.resolve();
+      return promise.resolve();
     }
 
     try {
       const { frames } = await this.target.listFrames();
       this._updateFrames({ frames });
     } catch (e) {
       console.error("Error while listing frames", e);
     }
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const promise = require("promise");
 const flags = require("devtools/shared/flags");
 const ToolDefinitions = require("devtools/client/definitions").Tools;
 const CssLogic = require("devtools/shared/inspector/css-logic");
 const {
   style: { ELEMENT_STYLE },
 } = require("devtools/shared/constants");
 const OutputParser = require("devtools/client/shared/output-parser");
 const { PrefObserver } = require("devtools/client/shared/prefs");
@@ -327,17 +328,17 @@ CssComputedView.prototype = {
   /**
    * Update the view with a new selected element. The CssComputedView panel
    * will show the style information for the given element.
    *
    * @param {NodeFront} element
    *        The highlighted node to get styles for.
    * @returns a promise that will be resolved when highlighting is complete.
    */
-  selectElement: function(element) {
+  selectElement: async function(element) {
     if (!element) {
       if (this.viewedElementPageStyle) {
         this.viewedElementPageStyle.off(
           "stylesheet-updated",
           this.refreshPanel
         );
         this.viewedElementPageStyle = null;
       }
@@ -346,21 +347,21 @@ CssComputedView.prototype = {
 
       if (this._refreshProcess) {
         this._refreshProcess.cancel();
       }
       // Hiding all properties
       for (const propView of this.propertyViews) {
         propView.refresh();
       }
-      return Promise.resolve(undefined);
+      return promise.resolve(undefined);
     }
 
     if (element === this._viewedElement) {
-      return Promise.resolve(undefined);
+      return promise.resolve(undefined);
     }
 
     if (this.viewedElementPageStyle) {
       this.viewedElementPageStyle.off("stylesheet-updated", this.refreshPanel);
     }
     this.viewedElementPageStyle = element.inspectorFront.pageStyle;
     this.viewedElementPageStyle.on("stylesheet-updated", this.refreshPanel);
 
@@ -554,34 +555,35 @@ CssComputedView.prototype = {
   },
 
   /**
    * Refresh the panel content. This could be called by a "ruleview-changed" event, but
    * we avoid the extra processing unless the panel is visible.
    */
   refreshPanel: function() {
     if (!this._viewedElement || !this.isPanelVisible()) {
-      return Promise.resolve();
+      return promise.resolve();
     }
 
     // Capture the current viewed element to return from the promise handler
     // early if it changed
     const viewedElement = this._viewedElement;
 
-    return Promise.all([
-      this._createPropertyViews(),
-      this.viewedElementPageStyle.getComputed(this._viewedElement, {
-        filter: this._sourceFilter,
-        onlyMatched: !this.includeBrowserStyles,
-        markMatched: true,
-      }),
-    ])
+    return promise
+      .all([
+        this._createPropertyViews(),
+        this.viewedElementPageStyle.getComputed(this._viewedElement, {
+          filter: this._sourceFilter,
+          onlyMatched: !this.includeBrowserStyles,
+          markMatched: true,
+        }),
+      ])
       .then(([, computed]) => {
         if (viewedElement !== this._viewedElement) {
-          return Promise.resolve();
+          return promise.resolve();
         }
 
         this._matchedProperties = new Set();
         for (const name in computed) {
           if (computed[name].matched) {
             this._matchedProperties.add(name);
           }
         }
@@ -1240,17 +1242,17 @@ PropertyView.prototype = {
 
     this.matchedSelectorsContainer.innerHTML = "";
     this.matchedExpander.removeAttribute("open");
     this.matchedExpander.setAttribute(
       "aria-label",
       STYLE_INSPECTOR_L10N.getStr("rule.twistyExpand.label")
     );
     this.tree.inspector.emit("computed-view-property-collapsed");
-    return Promise.resolve(undefined);
+    return promise.resolve(undefined);
   },
 
   get matchedSelectors() {
     return this._matchedSelectorResponse;
   },
 
   _buildMatchedSelectors: function() {
     const frag = this.element.ownerDocument.createDocumentFragment();
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const promise = require("promise");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 const Services = require("Services");
 
 // Maximum number of selector suggestions shown in the panel.
 const MAX_SUGGESTIONS = 15;
@@ -38,16 +39,20 @@ function InspectorSearch(inspector, inpu
   this._onKeyDown = this._onKeyDown.bind(this);
   this._onInput = this._onInput.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
 
   this.searchBox.addEventListener("keydown", this._onKeyDown, true);
   this.searchBox.addEventListener("input", this._onInput, true);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
 
+  // For testing, we need to be able to wait for the most recent node request
+  // to finish.  Tests can watch this promise for that.
+  this._lastQuery = promise.resolve(null);
+
   this.autocompleter = new SelectorAutocompleter(inspector, input);
   EventEmitter.decorate(this);
 }
 
 exports.InspectorSearch = InspectorSearch;
 
 InspectorSearch.prototype = {
   destroy: function() {
@@ -167,16 +172,19 @@ function SelectorAutocompleter(inspector
 
   // The popup will be attached to the toolbox document.
   this.searchPopup = new AutocompletePopup(inspector._toolbox.doc, options);
 
   this.searchBox.addEventListener("input", this.showSuggestions, true);
   this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
   this.inspector.on("markupmutation", this._onMarkupMutation);
 
+  // For testing, we need to be able to wait for the most recent node request
+  // to finish.  Tests can watch this promise for that.
+  this._lastQuery = promise.resolve(null);
   EventEmitter.decorate(this);
 }
 
 exports.SelectorAutocompleter = SelectorAutocompleter;
 
 SelectorAutocompleter.prototype = {
   get walker() {
     return this.inspector.walker;
@@ -330,17 +338,17 @@ SelectorAutocompleter.prototype = {
           if (popup.selectedItem) {
             this.searchBox.value = popup.selectedItem.label;
           }
           this.hidePopup();
         } else if (!popup.isOpen) {
           // When tab is pressed with focus on searchbox and closed popup,
           // do not prevent the default to avoid a keyboard trap and move focus
           // to next/previous element.
-          this.emitForTests("processing-done");
+          this.emit("processing-done");
           return;
         }
         break;
 
       case KeyCodes.DOM_VK_UP:
         if (popup.isOpen && popup.itemCount > 0) {
           popup.selectPreviousItem();
           this.searchBox.value = popup.selectedItem.label;
@@ -353,28 +361,28 @@ SelectorAutocompleter.prototype = {
           this.searchBox.value = popup.selectedItem.label;
         }
         break;
 
       case KeyCodes.DOM_VK_ESCAPE:
         if (popup.isOpen) {
           this.hidePopup();
         } else {
-          this.emitForTests("processing-done");
+          this.emit("processing-done");
           return;
         }
         break;
 
       default:
         return;
     }
 
     event.preventDefault();
     event.stopPropagation();
-    this.emitForTests("processing-done");
+    this.emit("processing-done");
   },
 
   /**
    * Handles click events from the autocomplete popup.
    */
   _onSearchPopupClick: function(event) {
     const selectedItem = this.searchPopup.selectedItem;
     if (selectedItem) {
@@ -469,27 +477,24 @@ SelectorAutocompleter.prototype = {
   },
 
   /**
    * Suggests classes,ids and tags based on the user input as user types in the
    * searchbox.
    */
   showSuggestions: async function() {
     let query = this.searchBox.value;
-    const originalQuery = this.searchBox.value;
-
     const state = this.state;
     let firstPart = "";
 
     if (query.endsWith("*") || state === this.States.ATTRIBUTE) {
       // Hide the popup if the query ends with * (because we don't want to
       // suggest all nodes) or if it is an attribute selector (because
       // it would give a lot of useless results).
       this.hidePopup();
-      this.emitForTests("processing-done", { query: originalQuery });
       return;
     }
 
     if (state === this.States.TAG) {
       // gets the tag that is being completed. For ex. 'div.foo > s' returns
       // 's', 'di' returns 'di' and likewise.
       firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
       query = query.slice(0, query.length - firstPart.length);
@@ -503,32 +508,31 @@ SelectorAutocompleter.prototype = {
       query = query.slice(0, query.length - firstPart.length - 1);
     }
     // TODO: implement some caching so that over the wire request is not made
     // everytime.
     if (/[\s+>~]$/.test(query)) {
       query += "*";
     }
 
-    let suggestions = await this.inspector.commands.inspectorCommand.getSuggestionsForQuery(
-      query,
-      firstPart,
-      state
-    );
+    this._lastQuery = this.inspector.commands.inspectorCommand
+      .getSuggestionsForQuery(query, firstPart, state)
+      .then(suggestions => {
+        this.emit("processing-done");
+
+        if (state === this.States.CLASS) {
+          firstPart = "." + firstPart;
+        } else if (state === this.States.ID) {
+          firstPart = "#" + firstPart;
+        }
 
-    if (state === this.States.CLASS) {
-      firstPart = "." + firstPart;
-    } else if (state === this.States.ID) {
-      firstPart = "#" + firstPart;
-    }
+        // If there is a single tag match and it's what the user typed, then
+        // don't need to show a popup.
+        if (suggestions.length === 1 && suggestions[0][0] === firstPart) {
+          suggestions = [];
+        }
 
-    // If there is a single tag match and it's what the user typed, then
-    // don't need to show a popup.
-    if (suggestions.length === 1 && suggestions[0][0] === firstPart) {
-      suggestions = [];
-    }
-
-    // Wait for the autocomplete-popup to fire its popup-opened event, to make sure
-    // the autoSelect item has been selected.
-    await this._showPopup(suggestions, state);
-    this.emitForTests("processing-done", { query: originalQuery });
+        // Wait for the autocomplete-popup to fire its popup-opened event, to make sure
+        // the autoSelect item has been selected.
+        return this._showPopup(suggestions, state);
+      });
   },
 };
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
+const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 const flags = require("devtools/shared/flags");
 const { executeSoon } = require("devtools/shared/DevToolsUtils");
 const { Toolbox } = require("devtools/client/framework/toolbox");
 const createStore = require("devtools/client/inspector/store");
 const InspectorStyleChangeTracker = require("devtools/client/inspector/shared/style-change-tracker");
 
 // Use privileged promise in panel documents to prevent having them to freeze
@@ -1868,17 +1869,17 @@ Inspector.prototype = {
         });
       }
 
       const hierarchical = pseudo == ":hover" || pseudo == ":active";
       return node.walkerFront.addPseudoClassLock(node, pseudo, {
         parents: hierarchical,
       });
     }
-    return Promise.resolve();
+    return promise.resolve();
   },
 
   /**
    * Initiate screenshot command on selected node.
    */
   async screenshotNode() {
     // Bug 1332936 - it's possible to call `screenshotNode` while the BoxModel highlighter
     // is still visible, therefore showing it in the picture.
--- a/devtools/client/inspector/markup/markup-context-menu.js
+++ b/devtools/client/inspector/markup/markup-context-menu.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
+const promise = require("promise");
 const { PSEUDO_CLASSES } = require("devtools/shared/css/constants");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(
   this,
   "MenuItem",
   "devtools/client/framework/menu-item"
@@ -281,45 +282,45 @@ class MarkupContextMenu {
    *
    * @param  {String} position
    *         The position as specified for Element.insertAdjacentHTML
    *         (i.e. "beforeBegin", "afterBegin", "beforeEnd", "afterEnd").
    */
   _pasteAdjacentHTML(position) {
     const content = this._getClipboardContentForPaste();
     if (!content) {
-      return Promise.reject("No clipboard content for paste");
+      return promise.reject("No clipboard content for paste");
     }
 
     const node = this.selection.nodeFront;
     return this.markup.insertAdjacentHTMLToNode(node, position, content);
   }
 
   /**
    * Paste the contents of the clipboard into the selected Node's inner HTML.
    */
   _pasteInnerHTML() {
     const content = this._getClipboardContentForPaste();
     if (!content) {
-      return Promise.reject("No clipboard content for paste");
+      return promise.reject("No clipboard content for paste");
     }
 
     const node = this.selection.nodeFront;
     return this.markup.getNodeInnerHTML(node).then(oldContent => {
       this.markup.updateNodeInnerHTML(node, content, oldContent);
     });
   }
 
   /**
    * Paste the contents of the clipboard into the selected Node's outer HTML.
    */
   _pasteOuterHTML() {
     const content = this._getClipboardContentForPaste();
     if (!content) {
-      return Promise.reject("No clipboard content for paste");
+      return promise.reject("No clipboard content for paste");
     }
 
     const node = this.selection.nodeFront;
     return this.markup.getNodeOuterHTML(node).then(oldContent => {
       this.markup.updateNodeOuterHTML(node, content, oldContent);
     });
   }
 
--- a/devtools/client/inspector/markup/views/element-container.js
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const promise = require("promise");
 const Services = require("Services");
 const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
 const ElementEditor = require("devtools/client/inspector/markup/views/element-editor");
 const { ELEMENT_NODE } = require("devtools/shared/dom-node-constants");
 const { extend } = require("devtools/shared/extend");
 
 loader.lazyRequireGetter(
   this,
@@ -107,17 +108,17 @@ MarkupElementContainer.prototype = exten
    *         - size contains information about the original image size and if
    *         the preview has been resized.
    *
    * If this element is not previewable or the preview cannot be generated for
    * some reason, the Promise is rejected.
    */
   _getPreview: function() {
     if (!this.isPreviewable()) {
-      return Promise.reject("_getPreview called on a non-previewable element.");
+      return promise.reject("_getPreview called on a non-previewable element.");
     }
 
     if (this.tooltipDataPromise) {
       // A preview request is already pending. Re-use that request.
       return this.tooltipDataPromise;
     }
 
     // Fetch the preview from the server.
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
+const promise = require("promise");
 const Rule = require("devtools/client/inspector/rules/models/rule");
 const UserProperties = require("devtools/client/inspector/rules/models/user-properties");
 const {
   style: { ELEMENT_STYLE },
 } = require("devtools/shared/constants");
 
 loader.lazyRequireGetter(
   this,
@@ -123,17 +124,17 @@ class ElementStyle {
     const populated = this.pageStyle
       .getApplied(this.element, {
         inherited: true,
         matchedSelectors: true,
         filter: this.showUserAgentStyles ? "ua" : undefined,
       })
       .then(entries => {
         if (this.destroyed || this.populated !== populated) {
-          return Promise.resolve(undefined);
+          return promise.resolve(undefined);
         }
 
         // Store the current list of rules (if any) during the population
         // process. They will be reused if possible.
         const existingRules = this.rules;
 
         this.rules = [];
 
@@ -161,17 +162,17 @@ class ElementStyle {
         }
 
         return undefined;
       })
       .catch(e => {
         // populate is often called after a setTimeout,
         // the connection may already be closed.
         if (this.destroyed) {
-          return Promise.resolve(undefined);
+          return promise.resolve(undefined);
         }
         return promiseWarn(e);
       });
     this.populated = populated;
     return this.populated;
   }
 
   /**
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const promise = require("promise");
 const {
   style: { ELEMENT_STYLE },
 } = require("devtools/shared/constants");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 const TextProperty = require("devtools/client/inspector/rules/models/text-property");
 const Services = require("Services");
 
 loader.lazyRequireGetter(
@@ -468,17 +469,18 @@ class Rule {
    *        (or RuleRewriter) as an argument and that modifies it
    *        to apply the desired edit
    * @return {Promise} a promise which will resolve when the edit
    *        is complete
    */
   applyProperties(modifier) {
     // If there is already a pending modification, we have to wait
     // until it settles before applying the next modification.
-    const resultPromise = Promise.resolve(this._applyingModifications)
+    const resultPromise = promise
+      .resolve(this._applyingModifications)
       .then(() => {
         const modifications = this.domRule.startModifyingProperties(
           this.cssProperties
         );
         modifier(modifications);
         if (this.domRule.canSetRuleText) {
           return this._applyPropertiesAuthored(modifications);
         }
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const promise = require("promise");
 const Services = require("Services");
 const flags = require("devtools/shared/flags");
 const { l10n } = require("devtools/shared/inspector/css-logic");
 const {
   style: { ELEMENT_STYLE },
 } = require("devtools/shared/constants");
 const { PSEUDO_CLASSES } = require("devtools/shared/css/constants");
 const OutputParser = require("devtools/client/shared/output-parser");
@@ -642,17 +643,17 @@ CssRuleView.prototype = {
     const pseudoClasses = element.pseudoClassLocks;
 
     // Adding a new rule with authored styles will cause the actor to
     // emit an event, which will in turn cause the rule view to be
     // updated.  So, we wait for this update and for the rule creation
     // request to complete, and then focus the new rule's selector.
     const eventPromise = this.once("ruleview-refreshed");
     const newRulePromise = this.pageStyle.addNewRule(element, pseudoClasses);
-    Promise.all([eventPromise, newRulePromise]).then(values => {
+    promise.all([eventPromise, newRulePromise]).then(values => {
       const options = values[1];
       // Be sure the reference the correct |rules| here.
       for (const rule of this._elementStyle.rules) {
         if (options.rule === rule.domRule) {
           rule.editor.selectorText.click();
           elementStyle._changed();
           break;
         }
@@ -923,17 +924,17 @@ CssRuleView.prototype = {
    * @param {NodeActor} element
    *        The node whose style rules we'll inspect.
    * @param {Boolean} allowRefresh
    *        Update the view even if the element is the same as last time.
    */
   selectElement: function(element, allowRefresh = false) {
     const refresh = this._viewedElement === element;
     if (refresh && !allowRefresh) {
-      return Promise.resolve(undefined);
+      return promise.resolve(undefined);
     }
 
     if (this._popup && this.popup.isOpen) {
       this.popup.hidePopup();
     }
 
     this.clear(false);
     this._viewedElement = element;
@@ -945,26 +946,27 @@ CssRuleView.prototype = {
       this._stopSelectingElement();
       this._clearRules();
       this._showEmpty();
       this.refreshPseudoClassPanel();
       if (this.pageStyle) {
         this.pageStyle.off("stylesheet-updated", this.refreshPanel);
         this.pageStyle = null;
       }
-      return Promise.resolve(undefined);
+      return promise.resolve(undefined);
     }
 
     this.pageStyle = element.inspectorFront.pageStyle;
     this.pageStyle.on("stylesheet-updated", this.refreshPanel);
 
     // To figure out how shorthand properties are interpreted by the
     // engine, we will set properties on a dummy element and observe
     // how their .style attribute reflects them as computed values.
-    const dummyElementPromise = Promise.resolve(this.styleDocument)
+    const dummyElementPromise = promise
+      .resolve(this.styleDocument)
       .then(document => {
         // ::before and ::after do not have a namespaceURI
         const namespaceURI =
           this.element.namespaceURI || document.documentElement.namespaceURI;
         this._dummyElement = document.createElementNS(
           namespaceURI,
           this.element.tagName
         );
@@ -1010,28 +1012,28 @@ CssRuleView.prototype = {
   },
 
   /**
    * Update the rules for the currently highlighted element.
    */
   refreshPanel: function() {
     // Ignore refreshes when the panel is hidden, or during editing or when no element is selected.
     if (!this.isPanelVisible() || this.isEditing || !this._elementStyle) {
-      return Promise.resolve(undefined);
+      return promise.resolve(undefined);
     }
 
     // Repopulate the element style once the current modifications are done.
     const promises = [];
     for (const rule of this._elementStyle.rules) {
       if (rule._applyingModifications) {
         promises.push(rule._applyingModifications);
       }
     }
 
-    return Promise.all(promises).then(() => {
+    return promise.all(promises).then(() => {
       return this._populate();
     });
   },
 
   /**
    * Clear the pseudo class options panel by removing the checked and disabled
    * attributes for each checkbox.
    */
@@ -1303,17 +1305,17 @@ CssRuleView.prototype = {
     let lastInheritedSource = "";
     let lastKeyframes = null;
     let seenPseudoElement = false;
     let seenNormalElement = false;
     let seenSearchTerm = false;
     let container = null;
 
     if (!this._elementStyle.rules) {
-      return Promise.resolve();
+      return promise.resolve();
     }
 
     const editorReadyPromises = [];
     for (const rule of this._elementStyle.rules) {
       if (rule.domRule.system) {
         continue;
       }
 
@@ -1376,17 +1378,17 @@ CssRuleView.prototype = {
     }
 
     const searchBox = this.searchField.parentNode;
     searchBox.classList.toggle(
       "devtools-searchbox-no-match",
       this.searchValue && !seenSearchTerm
     );
 
-    return Promise.all(editorReadyPromises);
+    return promise.all(editorReadyPromises);
   },
 
   /**
    * Highlight rules that matches the filter search value and returns a
    * boolean indicating whether or not rules were highlighted.
    *
    * @param  {Rule} rule
    *         The rule object we're highlighting if its rule selectors or
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
@@ -40,20 +40,17 @@ async function testImageTooltipAfterColo
     selector: "body",
     name: "background-image",
     value:
       'url("chrome://branding/content/icon64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)',
   });
 
   const spectrum = picker.spectrum;
   const onHidden = picker.tooltip.once("hidden");
-
-  // On "RETURN", `ruleview-changed` is triggered when the SwatchBasedEditorTooltip calls
-  // its `commit` method, and then another event is emitted when the editor is hidden.
-  const onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
+  const onModifications = ruleView.once("ruleview-changed");
   focusAndSendKey(spectrum.element.ownerDocument.defaultView, "RETURN");
   await onHidden;
   await onModifications;
 
   info("Verify again that the image preview tooltip works");
   // After a color change, the property is re-populated, we need to get the new
   // dom node
   url = getRuleViewProperty(
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -23,16 +23,17 @@ const {
 } = require("devtools/client/inspector/shared/utils");
 const {
   parseNamedDeclarations,
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS,
 } = require("devtools/shared/css/parsing-utils");
+const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 
 loader.lazyRequireGetter(this, "Tools", "devtools/client/definitions", true);
 
 const STYLE_INSPECTOR_PROPERTIES =
   "devtools/shared/locales/styleinspector.properties";
@@ -358,17 +359,17 @@ RuleEditor.prototype = {
       this._onToolChanged();
     } else if (this.rule.domRule.type === ELEMENT_STYLE) {
       this.source.setAttribute("unselectable", "permanent");
     } else {
       // Set "unselectable" appropriately.
       this._onToolChanged();
     }
 
-    Promise.resolve().then(() => {
+    promise.resolve().then(() => {
       this.emit("source-link-updated");
     });
   },
 
   /**
    * Update the rule editor with the contents of the rule.
    *
    * @param {Boolean} reset
--- a/devtools/client/inspector/test/browser_inspector_search-01.js
+++ b/devtools/client/inspector/test/browser_inspector_search-01.js
@@ -73,17 +73,17 @@ add_task(async function() {
     promises.push(inspector.searchSuggestions.once("processing-done"));
 
     if (key === "VK_RETURN") {
       info("Waiting for " + (isValid ? "NO " : "") + "results");
       promises.push(inspector.search.once("search-result"));
     }
 
     info("Waiting for search query to complete");
-    promises.push(inspector.searchSuggestions.once("processing-done"));
+    promises.push(inspector.searchSuggestions._lastQuery);
 
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
 
     await Promise.all(promises);
     info(
       "The keypress press process, any possible search results and the search query are complete."
     );
 
--- a/devtools/client/inspector/test/browser_inspector_search-02.js
+++ b/devtools/client/inspector/test/browser_inspector_search-02.js
@@ -113,24 +113,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + formatSuggestions(suggestions));
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     info(
       "Query completed. Performing checks for input '" +
         searchBox.value +
         "' - key pressed: " +
         key
     );
     const actualSuggestions = popup.getItems();
--- a/devtools/client/inspector/test/browser_inspector_search-03.js
+++ b/devtools/client/inspector/test/browser_inspector_search-03.js
@@ -189,24 +189,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + formatSuggestions(suggestions));
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     info(
       "Query completed. Performing checks for input '" + searchBox.value + "'"
     );
     const actualSuggestions = popup.getItems();
 
     is(
       popup.isOpen ? actualSuggestions.length : 0,
--- a/devtools/client/inspector/test/browser_inspector_search-04.js
+++ b/devtools/client/inspector/test/browser_inspector_search-04.js
@@ -76,24 +76,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + formatSuggestions(suggestions));
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     info(
       "Query completed. Performing checks for input '" + searchBox.value + "'"
     );
     const actualSuggestions = popup.getItems();
 
     is(
       popup.isOpen ? actualSuggestions.length : 0,
--- a/devtools/client/inspector/test/browser_inspector_search-05.js
+++ b/devtools/client/inspector/test/browser_inspector_search-05.js
@@ -13,19 +13,20 @@ add_task(async function() {
   );
 
   info("Focus the search box");
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   info("Enter # to search for all ids");
   let processingDone = once(inspector.searchSuggestions, "processing-done");
   EventUtils.synthesizeKey("#", {}, inspector.panelWin);
+  await processingDone;
 
   info("Wait for search query to complete");
-  await processingDone;
+  await inspector.searchSuggestions._lastQuery;
 
   info("Press tab to fill the search input with the first suggestion");
   processingDone = once(inspector.searchSuggestions, "processing-done");
   EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin);
   await processingDone;
 
   info("Press enter and expect a new selection");
   let onSelect = inspector.once("inspector-updated");
--- a/devtools/client/inspector/test/browser_inspector_search-06.js
+++ b/devtools/client/inspector/test/browser_inspector_search-06.js
@@ -72,23 +72,20 @@ add_task(async function() {
 async function synthesizeKeys(keys, inspector) {
   if (typeof keys === "string") {
     keys = [keys];
   }
 
   for (const key of keys) {
     info("Synthesizing key " + key + " in the search box");
     const eventHandled = once(inspector.searchBox, "keypress", true);
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await eventHandled;
     info("Waiting for the search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
   }
 }
 
 function assertHasResult(inspector, expectResult) {
   is(
     inspector.searchBox.parentNode.classList.contains(
       "devtools-searchbox-no-match"
     ),
--- a/devtools/client/inspector/test/browser_inspector_search-07.js
+++ b/devtools/client/inspector/test/browser_inspector_search-07.js
@@ -29,24 +29,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + suggestions);
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete and getting the suggestions");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
     const actualSuggestions = popup.getItems();
 
     is(
       popup.isOpen ? actualSuggestions.length : 0,
       suggestions.length,
       "There are expected number of suggestions."
     );
 
--- a/devtools/client/inspector/test/browser_inspector_search-08.js
+++ b/devtools/client/inspector/test/browser_inspector_search-08.js
@@ -44,24 +44,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + suggestions.join(", "));
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete and getting the suggestions");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
     const actualSuggestions = popup.getItems();
 
     is(
       popup.isOpen ? actualSuggestions.length : 0,
       suggestions.length,
       "There are expected number of suggestions."
     );
 
--- a/devtools/client/inspector/test/browser_inspector_search-09.js
+++ b/devtools/client/inspector/test/browser_inspector_search-09.js
@@ -51,33 +51,30 @@ add_task(async function() {
   const { searchBox } = inspector;
 
   await selectNode("#b1", inspector);
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   let index = 0;
   for (const [key, id, isTextNode, isValid] of KEY_STATES) {
     info(index + ": Pressing key " + key + " to get id " + id + ".");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
-    const onSearchResult = inspector.search.once("search-result");
     EventUtils.synthesizeKey(
       key,
       { shiftKey: key === "*" },
       inspector.panelWin
     );
+    info("Got processing-done event");
 
     if (key === "VK_RETURN") {
       info("Waiting for " + (isValid ? "NO " : "") + "results");
-      await onSearchResult;
+      await inspector.search.once("search-result");
     }
 
     info("Waiting for search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     if (isTextNode) {
       info(
         "Text node of " +
           inspector.selection.nodeFront.parentNode.id +
           " is selected with text " +
           searchBox.value
       );
--- a/devtools/client/inspector/test/browser_inspector_search-clear.js
+++ b/devtools/client/inspector/test/browser_inspector_search-clear.js
@@ -27,34 +27,30 @@ add_task(async function() {
   const { inspector } = await openInspectorForURL(TEST_URI);
   const { searchBox, searchClearButton } = inspector;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   info("Type d and the clear button will be shown");
 
   const command = once(searchBox, "input");
-  let onSearchProcessingDone = inspector.searchSuggestions.once(
-    "processing-done"
-  );
   EventUtils.synthesizeKey("c", {}, inspector.panelWin);
   await command;
 
   info("Waiting for search query to complete and getting the suggestions");
-  await onSearchProcessingDone;
+  await inspector.searchSuggestions._lastQuery;
 
   ok(
     !searchClearButton.hidden,
     "The clear button is shown when some word is in searchBox"
   );
 
-  onSearchProcessingDone = inspector.searchSuggestions.once("processing-done");
   EventUtils.synthesizeKey("VK_BACK_SPACE", {}, inspector.panelWin);
   await command;
 
   info("Waiting for search query to complete and getting the suggestions");
-  await onSearchProcessingDone;
+  await inspector.searchSuggestions._lastQuery;
 
   ok(
     searchClearButton.hidden,
     "The clear button is hidden when no word is in searchBox"
   );
 });
--- a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
+++ b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
@@ -49,27 +49,20 @@ add_task(async function() {
   }
 
   info("Closing context menu");
   let onContextMenuClose = toolbox.once("menu-close");
   searchContextMenu.hidePopup();
   await onContextMenuClose;
 
   info("Copy text in search field using the context menu");
-  const onSearchProcessingDone = inspector.searchSuggestions.once(
-    "processing-done"
-  );
   searchBox.setUserInput(TEST_INPUT);
   searchBox.select();
   searchBox.focus();
 
-  // We have to wait for search query to avoid test failure.
-  info("Waiting for search query to complete and getting the suggestions");
-  await onSearchProcessingDone;
-
   onContextMenuOpen = toolbox.once("menu-open");
   synthesizeContextMenuEvent(searchBox);
   await onContextMenuOpen;
 
   searchContextMenu = toolbox.getTextBoxContextMenu();
 
   // Simulating a click on cmdCopy will also close the context menu.
   onContextMenuClose = toolbox.once("menu-close");
@@ -102,9 +95,13 @@ add_task(async function() {
   is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
   is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
   is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
   is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
 
   const onContextMenuHidden = toolbox.once("menu-close");
   searchContextMenu.hidePopup();
   await onContextMenuHidden;
+
+  // We have to wait for search query to avoid test failure.
+  info("Waiting for search query to complete and getting the suggestions");
+  await inspector.searchSuggestions._lastQuery;
 });
--- a/devtools/client/inspector/test/browser_inspector_search-navigation.js
+++ b/devtools/client/inspector/test/browser_inspector_search-navigation.js
@@ -59,15 +59,16 @@ add_task(async function() {
   const { inspector } = await openInspectorForURL(TEST_URL);
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const [key, query] of KEY_STATES) {
     info("Pressing key " + key + " to get searchbox value as " + query);
 
     const done = inspector.searchSuggestions.once("processing-done");
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+    await done;
 
     info("Waiting for search query to complete");
-    await done;
+    await inspector.searchSuggestions._lastQuery;
 
     is(inspector.searchBox.value, query, "The searchbox value is correct");
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_search-reserved.js
+++ b/devtools/client/inspector/test/browser_inspector_search-reserved.js
@@ -98,24 +98,21 @@ add_task(async function() {
   const popup = inspector.searchSuggestions.searchPopup;
 
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   for (const { key, suggestions } of TEST_DATA) {
     info("Pressing " + key + " to get " + formatSuggestions(suggestions));
 
     const command = once(searchBox, "input");
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await command;
 
     info("Waiting for search query to complete");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     info(
       "Query completed. Performing checks for input '" + searchBox.value + "'"
     );
     const actualSuggestions = popup.getItems();
 
     is(
       popup.isOpen ? actualSuggestions.length : 0,
--- a/devtools/client/inspector/test/browser_inspector_search-selection.js
+++ b/devtools/client/inspector/test/browser_inspector_search-selection.js
@@ -9,30 +9,22 @@ const TEST_URL = URL_ROOT + "doc_inspect
 
 add_task(async function() {
   const { inspector } = await openInspectorForURL(TEST_URL);
 
   info("Focus the search box");
   await focusSearchBoxUsingShortcut(inspector.panelWin);
 
   info("Enter body > p to search");
-  const searchText = "body > p";
-  // EventUtils.sendString will trigger multiple updates, so wait until the final one.
-  const processingDone = new Promise(resolve => {
-    const off = inspector.searchSuggestions.on("processing-done", data => {
-      if (data.query == searchText) {
-        resolve();
-        off();
-      }
-    });
-  });
-  EventUtils.sendString(searchText, inspector.panelWin);
+  const processingDone = once(inspector.searchSuggestions, "processing-done");
+  EventUtils.sendString("body > p", inspector.panelWin);
+  await processingDone;
 
   info("Wait for search query to complete");
-  await processingDone;
+  await inspector.searchSuggestions._lastQuery;
 
   let msg = "Press enter and expect a new selection";
   await sendKeyAndCheck(inspector, msg, "VK_RETURN", {}, "#p1");
 
   msg = "Press enter to cycle through multiple nodes";
   await sendKeyAndCheck(inspector, msg, "VK_RETURN", {}, "#p2");
 
   msg = "Press shift-enter to select the previous node";
--- a/devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js
+++ b/devtools/client/inspector/test/browser_inspector_search-suggests-ids-and-classes.js
@@ -123,24 +123,21 @@ add_task(async function() {
     info(
       "pressing key " +
         key +
         " to get suggestions " +
         JSON.stringify(expectedSuggestions)
     );
 
     const onCommand = once(searchBox, "input", true);
-    const onSearchProcessingDone = inspector.searchSuggestions.once(
-      "processing-done"
-    );
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
     await onCommand;
 
     info("Waiting for the suggestions to be retrieved");
-    await onSearchProcessingDone;
+    await inspector.searchSuggestions._lastQuery;
 
     const actualSuggestions = popup.getItems();
     is(
       popup.isOpen ? actualSuggestions.length : 0,
       expectedSuggestions.length,
       "There are expected number of suggestions"
     );
 
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -60,16 +60,25 @@ function DevToolsLoader({
     // Allow access to locale data using paths closer to what is
     // used in the source tree.
     "devtools/client/locales": "chrome://devtools/locale",
     "devtools/shared/locales": "chrome://devtools-shared/locale",
     "devtools/startup/locales": "chrome://devtools-startup/locale",
     "toolkit/locales": "chrome://global/locale",
   };
 
+  // When creating a Loader invisible to the Debugger, we have to ensure
+  // using only modules and not depend on any JSM. As everything that is
+  // not loaded with Loader isn't going to respect `invisibleToDebugger`.
+  // But we have to keep using Promise.jsm for other loader to prevent
+  // breaking unhandled promise rejection in tests.
+  if (invisibleToDebugger) {
+    paths.promise = "resource://gre/modules/Promise-backend.js";
+  }
+
   // DAMP tests use a dynamic path. If DEBUG_DEVTOOLS_DAMP_TEST_PATH was set as
   // a custom preference, add a corresponding path mapping entry.
   // DAMP runner and tests are under testing/talos/talos/tests/devtools
   const dampTestPath = Services.prefs.getCharPref(
     "devtools.damp.test-path",
     ""
   );
   if (dampTestPath) {
@@ -104,16 +113,23 @@ function DevToolsLoader({
     },
   });
 
   this.require = Require(this.loader, { id: "devtools" });
 
   // Fetch custom pseudo modules and globals
   const { modules, globals } = this.require("devtools/shared/builtin-modules");
 
+  // When creating a Loader for the browser toolbox, we have to use
+  // Promise-backend.js, as a Loader module. Instead of Promise.jsm which
+  // can't be flagged as invisible to debugger.
+  if (invisibleToDebugger) {
+    delete modules.promise;
+  }
+
   // Register custom pseudo modules to the current loader instance
   for (const id in modules) {
     const uri = resolveURI(id, this.loader.mapping);
     this.loader.modules[uri] = {
       get exports() {
         return modules[id];
       },
     };
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -9,16 +9,17 @@
  * pseudo modules that aren't separate files but just dynamically set values.
  *
  * As it does so, the module itself doesn't have access to these globals,
  * nor the pseudo modules. Be careful to avoid loading any other js module as
  * they would also miss them.
  */
 
 const { Cu, Cc, Ci } = require("chrome");
+const promise = require("resource://gre/modules/Promise.jsm").Promise;
 const jsmScope = require("resource://devtools/shared/Loader.jsm");
 const { Services } = require("resource://gre/modules/Services.jsm");
 
 const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const {
   BrowsingContext,
@@ -204,16 +205,17 @@ function lazyRequireGetter(obj, properti
 }
 
 // List of pseudo modules exposed to all devtools modules.
 exports.modules = {
   ChromeUtils,
   DebuggerNotificationObserver,
   HeapSnapshot,
   InspectorUtils,
+  promise,
   // Expose "chrome" Promise, which aren't related to any document
   // and so are never frozen, even if the browser loader module which
   // pull it is destroyed. See bug 1402779.
   Promise,
   Services: Object.create(Services),
   TelemetryStopwatch,
 };
 
--- a/devtools/shared/tests/xpcshell/test_invisible_loader.js
+++ b/devtools/shared/tests/xpcshell/test_invisible_loader.js
@@ -27,16 +27,22 @@ function visible_loader() {
   const sandbox = loader.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     Assert.ok(true);
   } catch (e) {
     do_throw("debugger could not add visible value");
   }
+
+  // Check that for common loader used for tabs, promise modules is Promise.jsm
+  // Which is required to support unhandled promises rejection in mochitests
+  const promise = ChromeUtils.import("resource://gre/modules/Promise.jsm")
+    .Promise;
+  Assert.equal(loader.require("promise"), promise);
 }
 
 function invisible_loader() {
   const loader = new DevToolsLoader({
     invisibleToDebugger: true,
   });
   loader.require("devtools/shared/indentation");
 
@@ -44,9 +50,17 @@ function invisible_loader() {
   const sandbox = loader.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_throw("debugger added invisible value");
   } catch (e) {
     Assert.ok(true);
   }
+
+  // But for browser toolbox loader, promise is loaded as a regular modules out
+  // of Promise-backend.js, that to be invisible to the debugger and not step
+  // into it.
+  const promise = loader.require("promise");
+  const promiseModule =
+    loader.loader.modules["resource://gre/modules/Promise-backend.js"];
+  Assert.equal(promise, promiseModule.exports);
 }
--- a/devtools/shared/worker/worker.js
+++ b/devtools/shared/worker/worker.js
@@ -21,16 +21,17 @@
       dumpn
     );
   } else {
     // Cu.import
     const { require } = ChromeUtils.import(
       "resource://devtools/shared/Loader.jsm"
     );
     this.isWorker = false;
+    this.Promise = require("resource://gre/modules/Promise.jsm").Promise;
     this.console = console;
     factory.call(
       this,
       require,
       this,
       { exports: this },
       { Cc, Ci, Cu },
       ChromeWorker,