Bug 966424 - Fix intermittent failures in browser_bug765105_background_image_tooltip.js. r=jwalker
☠☠ backed out by 844d226e29cd ☠ ☠
authorPatrick Brosset <pbrosset@mozilla.com>
Wed, 12 Mar 2014 13:14:18 -0400
changeset 191506 20e22a0b38f6c688d6bb6068ced12b6db527b6b5
parent 191505 b07f0ec10d226e32b8c89b465b71cf4600f61539
child 191507 1c3076666191df729260a9fd37f440f2490ad575
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker
bugs966424
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 966424 - Fix intermittent failures in browser_bug765105_background_image_tooltip.js. r=jwalker
browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
browser/devtools/markupview/markup-view.js
browser/devtools/markupview/test/browser_inspector_markup_765105_tooltip.js
browser/devtools/netmonitor/test/browser_net_image-tooltip.js
browser/devtools/shared/widgets/Tooltip.js
browser/devtools/styleinspector/rule-view.js
browser/devtools/styleinspector/test/browser_bug726427_csstransform_tooltip.js
browser/devtools/styleinspector/test/browser_bug765105_background_image_tooltip.js
browser/devtools/styleinspector/test/browser_bug889638_rule_view_color_picker.js
browser/devtools/styleinspector/test/head.js
--- a/browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
@@ -70,100 +70,127 @@ function selectDiv() {
   inspector.once("inspector-updated", () => {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function testTransformDimension() {
-  let deferred = promise.defer();
-  info("Testing css transform tooltip dimensions");
+  return Task.spawn(function*() {
+    let tooltip = ruleView.previewTooltip;
+    let panel = tooltip.panel;
 
-  let {valueSpan} = getRuleViewProperty("transform");
-  showTooltipOn(ruleView.previewTooltip, valueSpan, () => {
-    let panel = ruleView.previewTooltip.panel;
+    info("Testing css transform tooltip dimensions");
+    let {valueSpan} = getRuleViewProperty("transform");
+
+    yield assertTooltipShownOn(tooltip, valueSpan);
 
     // Let's not test for a specific size, but instead let's make sure it's at
     // least as big as the preview canvas
     let canvas = panel.querySelector("canvas");
     let w = canvas.width;
     let h = canvas.height;
     let panelRect = panel.getBoundingClientRect();
 
     ok(panelRect.width >= w, "The panel is wide enough to show the canvas");
     ok(panelRect.height >= h, "The panel is high enough to show the canvas");
 
-    ruleView.previewTooltip.hide();
-    deferred.resolve();
+    let onHidden = tooltip.once("hidden");
+    tooltip.hide();
+    yield onHidden;
   });
-
-  return deferred.promise;
 }
 
 function testImageDimension() {
-  let deferred = promise.defer();
-  info("Testing background-image tooltip dimensions");
+  return Task.spawn(function*() {
+    info("Testing background-image tooltip dimensions");
 
-  let {valueSpan} = getRuleViewProperty("background");
-  let uriSpan = valueSpan.querySelector(".theme-link");
+    let tooltip = ruleView.previewTooltip;
+    let panel = tooltip.panel;
 
-  showTooltipOn(ruleView.previewTooltip, uriSpan, () => {
-    let panel = ruleView.previewTooltip.panel;
+    let {valueSpan} = getRuleViewProperty("background");
+    let uriSpan = valueSpan.querySelector(".theme-link");
+
+    yield assertTooltipShownOn(tooltip, uriSpan);
 
     // Let's not test for a specific size, but instead let's make sure it's at
     // least as big as the image
     let imageRect = panel.querySelector("image").getBoundingClientRect();
     let panelRect = panel.getBoundingClientRect();
 
     ok(panelRect.width >= imageRect.width,
       "The panel is wide enough to show the image");
     ok(panelRect.height >= imageRect.height,
       "The panel is high enough to show the image");
 
-    ruleView.previewTooltip.hide();
-    deferred.resolve();
+    let onHidden = tooltip.once("hidden");
+    tooltip.hide();
+    yield onHidden;
   });
-
-  return deferred.promise;
 }
 
 function testPickerDimension() {
-  let deferred = promise.defer();
-  info("Testing color-picker tooltip dimensions");
+  return Task.spawn(function*() {
+    info("Testing color-picker tooltip dimensions");
 
-  let {valueSpan} = getRuleViewProperty("background");
-  let swatch = valueSpan.querySelector(".ruleview-colorswatch");
-  let cPicker = ruleView.colorPicker;
+    let {valueSpan} = getRuleViewProperty("background");
+    let swatch = valueSpan.querySelector(".ruleview-colorswatch");
+    let cPicker = ruleView.colorPicker;
 
-  cPicker.tooltip.once("shown", () => {
+    let onShown = cPicker.tooltip.once("shown");
+    swatch.click();
+    yield onShown;
+
     // The colorpicker spectrum's iframe has a fixed width height, so let's
     // make sure the tooltip is at least as big as that
     let w = cPicker.tooltip.panel.querySelector("iframe").width;
     let h = cPicker.tooltip.panel.querySelector("iframe").height;
     let panelRect = cPicker.tooltip.panel.getBoundingClientRect();
 
     ok(panelRect.width >= w, "The panel is wide enough to show the picker");
     ok(panelRect.height >= h, "The panel is high enough to show the picker");
 
+    let onHidden = cPicker.tooltip.once("hidden");
     cPicker.hide();
-    deferred.resolve();
+    yield onHidden;
   });
-  swatch.click();
-
-  return deferred.promise;
 }
 
-function showTooltipOn(tooltip, element, cb) {
-  // If there is indeed a show-on-hover on element, the xul panel will be shown
-  tooltip.panel.addEventListener("popupshown", function shown() {
-    tooltip.panel.removeEventListener("popupshown", shown, true);
-    cb();
-  }, true);
-  tooltip._showOnHover(element);
+/**
+ * @return a promise that resolves when the tooltip is shown
+ */
+function assertTooltipShownOn(tooltip, element) {
+  return Task.spawn(function*() {
+    let isTarget = yield isHoverTooltipTarget(tooltip, element);
+    ok(isTarget, "The element is a tooltip target and content has been inserted");
+
+    info("Showing the tooltip now that content has been inserted by isValidHoverTarget");
+    let onShown = tooltip.once("shown");
+    tooltip.show();
+    yield onShown;
+  });
+}
+
+/**
+ * Given a tooltip object instance (see Tooltip.js), checks if it is set to
+ * toggle and hover and if so, checks if the given target is a valid hover target.
+ * This won't actually show the tooltip (the less we interact with XUL panels
+ * during test runs, the better).
+ * @return a promise that resolves when the answer is known. Also, this will
+ * delete to a function in the rule-view which will insert content into the
+ * tooltip
+ */
+function isHoverTooltipTarget(tooltip, target) {
+  if (!tooltip._basedNode || !tooltip.panel) {
+    return promise.reject(new Error("The tooltip passed isn't set to toggle on hover or is not a tooltip"));
+  }
+  // The tooltip delegates to a user defined cb that inserts content in the tooltip
+  // when calling isValidHoverTarget
+  return tooltip.isValidHoverTarget(target);
 }
 
 function getRuleViewProperty(name) {
   let prop = null;
   [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
     let nameSpan = property.querySelector(".ruleview-propertyname");
     let valueSpan = property.querySelector(".ruleview-propertyvalue");
 
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -105,17 +105,17 @@ function MarkupView(aInspector, aFrame, 
 exports.MarkupView = MarkupView;
 
 MarkupView.prototype = {
   _selectedContainer: null,
 
   _initTooltips: function() {
     this.tooltip = new Tooltip(this._inspector.panelDoc);
     this.tooltip.startTogglingOnHover(this._elt,
-      this._buildTooltipContent.bind(this));
+      this._isImagePreviewTarget.bind(this));
   },
 
   _initHighlighter: function() {
     // Show the box model on markup-view mousemove
     this._onMouseMove = this._onMouseMove.bind(this);
     this._elt.addEventListener("mousemove", this._onMouseMove, false);
     this._onMouseLeave = this._onMouseLeave.bind(this);
     this._elt.addEventListener("mouseleave", this._onMouseLeave, false);
@@ -227,32 +227,40 @@ MarkupView.prototype = {
         break;
       }
     }
 
     // Recursively update each node starting with documentElement.
     updateChildren(documentElement);
   },
 
-  _buildTooltipContent: function(target) {
+  /**
+   * Executed when the mouse hovers over a target in the markup-view and is used
+   * to decide whether this target should be used to display an image preview
+   * tooltip.
+   * Delegates the actual decision to the corresponding MarkupContainer instance
+   * if one is found.
+   * @return the promise returned by MarkupContainer._isImagePreviewTarget
+   */
+  _isImagePreviewTarget: function(target) {
     // From the target passed here, let's find the parent MarkupContainer
     // and ask it if the tooltip should be shown
     let parent = target, container;
     while (parent !== this.doc.body) {
       if (parent.container) {
         container = parent.container;
         break;
       }
       parent = parent.parentNode;
     }
 
     if (container) {
       // With the newly found container, delegate the tooltip content creation
       // and decision to show or not the tooltip
-      return container._buildTooltipContent(target, this.tooltip);
+      return container._isImagePreviewTarget(target, this.tooltip);
     }
   },
 
   /**
    * Given the known reason, should the current selection be briefly highlighted
    * In a few cases, we don't want to highlight the node:
    * - If the reason is null (used to reset the selection),
    * - if it's "inspector-open" (when the inspector opens up, let's not highlight
@@ -1260,16 +1268,23 @@ MarkupContainer.prototype = {
       let isCanvas = tagName === "canvas";
 
       return isImage || isCanvas;
     } else {
       return false;
     }
   },
 
+  /**
+   * If the node is an image or canvas (@see isPreviewable), then get the
+   * image data uri from the server so that it can then later be previewed in
+   * a tooltip if needed.
+   * Stores a promise in this.tooltipData.data that resolves when the data has
+   * been retrieved
+   */
   _prepareImagePreview: function() {
     if (this.isPreviewable()) {
       // Get the image data for later so that when the user actually hovers over
       // the element, the tooltip does contain the image
       let def = promise.defer();
 
       this.tooltipData = {
         target: this.editor.getAttributeElement("src") || this.editor.tag,
@@ -1286,37 +1301,47 @@ MarkupContainer.prototype = {
           this.tooltipData.data = promise.resolve(res);
         });
       }, () => {
         this.tooltipData.data = promise.reject();
       });
     }
   },
 
+  /**
+   * Executed by MarkupView._isImagePreviewTarget which is itself called when the
+   * mouse hovers over a target in the markup-view.
+   * Checks if the target is indeed something we want to have an image tooltip
+   * preview over and, if so, inserts content into the tooltip.
+   * @return a promise that resolves when the content has been inserted or
+   * rejects if no preview is required. This promise is then used by Tooltip.js
+   * to decide if/when to show the tooltip
+   */
+  _isImagePreviewTarget: function(target, tooltip) {
+    if (!this.tooltipData || this.tooltipData.target !== target) {
+      return promise.reject();
+    }
+
+    return this.tooltipData.data.then(({data, size}) => {
+      tooltip.setImageContent(data, size);
+    }, () => {
+      tooltip.setBrokenImageContent();
+    });
+  },
+
   copyImageDataUri: function() {
     // We need to send again a request to gettooltipData even if one was sent for
     // the tooltip, because we want the full-size image
     this.node.getImageData().then(data => {
       data.data.string().then(str => {
         clipboardHelper.copyString(str, this.markup.doc);
       });
     });
   },
 
-  _buildTooltipContent: function(target, tooltip) {
-    if (this.tooltipData && target === this.tooltipData.target) {
-      this.tooltipData.data.then(({data, size}) => {
-        tooltip.setImageContent(data, size);
-      }, () => {
-        tooltip.setBrokenImageContent();
-      });
-      return true;
-    }
-  },
-
   /**
    * True if the current node has children.  The MarkupView
    * will set this attribute for the MarkupContainer.
    */
   _hasChildren: false,
 
   get hasChildren() {
     return this._hasChildren;
--- a/browser/devtools/markupview/test/browser_inspector_markup_765105_tooltip.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_765105_tooltip.js
@@ -83,26 +83,25 @@ function testImageTooltip(index) {
 
   let container = getContainerForRawNode(markup, node);
 
   let target = container.editor.tag;
   if (isImg) {
     target = container.editor.getAttributeElement("src");
   }
 
-  assertTooltipShownOn(target, () => {
+  assertTooltipShownOn(target).then(() => {
     let images = markup.tooltip.panel.getElementsByTagName("image");
     is(images.length, 1,
       "Tooltip for [" + TEST_NODES[index].selector + "] contains an image");
 
     let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
     is(label.textContent, TEST_NODES[index].size,
       "Tooltip label for [" + TEST_NODES[index].selector + "] displays the right image size")
 
-    markup.tooltip.hide();
     testImageTooltip(index + 1);
   });
 }
 
 function compareImageData(img, imgData) {
   let canvas = content.document.createElement("canvas");
   canvas.width = img.naturalWidth;
   canvas.height = img.naturalHeight;
@@ -112,27 +111,15 @@ function compareImageData(img, imgData) 
     ctx.drawImage(img, 0, 0);
     data = canvas.toDataURL("image/png");
   } catch (e) {}
 
   is(data, imgData, "Tooltip image has the right content");
 }
 
 function assertTooltipShownOn(element, cb) {
-  // If there is indeed a show-on-hover on element, the xul panel will be shown
-  markup.tooltip.panel.addEventListener("popupshown", function shown() {
-    markup.tooltip.panel.removeEventListener("popupshown", shown, true);
+  return Task.spawn(function*() {
+    info("Is the element a valid hover target");
 
-    // Poll until the image gets loaded in the tooltip. This is required because
-    // markup containers only load images in their associated tooltips when
-    // the image data comes back from the server. However, this test is executed
-    // synchronously as soon as "inspector-updated" is fired, which is before
-    // the data for images is known.
-    let hasImage = () => markup.tooltip.panel.getElementsByTagName("image").length;
-    let poll = setInterval(() => {
-      if (hasImage()) {
-        clearInterval(poll);
-        cb();
-      }
-    }, 200);
-  }, true);
-  markup.tooltip._showOnHover(element);
+    let isValid = yield markup.tooltip.isValidHoverTarget(element);
+    ok(isValid, "The element is a valid hover target for the image tooltip");
+  });
 }
--- a/browser/devtools/netmonitor/test/browser_net_image-tooltip.js
+++ b/browser/devtools/netmonitor/test/browser_net_image-tooltip.js
@@ -54,23 +54,24 @@ function test() {
     });
 
     function reloadAndPerformRequests() {
       NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED).then(() => {
         aDebuggee.performRequests();
       });
     }
 
-    function showTooltipOn(aTooltip, aTarget) {
-      let deferred = promise.defer();
-
-      aTooltip.panel.addEventListener("popupshown", function onEvent() {
-        aTooltip.panel.removeEventListener("popupshown", onEvent, true);
-        deferred.resolve(aTooltip);
-      }, true);
-
-      aTooltip._showOnHover(aTarget);
-      return deferred.promise;
+    /**
+     * @return a promise that resolves when the tooltip is shown
+     */
+    function showTooltipOn(tooltip, element) {
+      return Task.spawn(function*() {
+        let isTarget = yield tooltip.isValidHoverTarget(element);
+        let onShown = tooltip.once("shown");
+        tooltip.show();
+        yield onShown;
+        return tooltip;
+      });
     }
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -387,29 +387,44 @@ Tooltip.prototype = {
     this._lastHovered = null;
   },
 
   _onBaseNodeMouseMove: function(event) {
     if (event.target !== this._lastHovered) {
       this.hide();
       this._lastHovered = event.target;
       setNamedTimeout(this.uid, this._showDelay, () => {
-        this._showOnHover(event.target);
+        this.isValidHoverTarget(event.target).then(target => {
+          this.show(target);
+        });
       });
     }
   },
 
-  _showOnHover: function(target) {
+  /**
+   * Is the given target DOMNode a valid node for toggling the tooltip on hover.
+   * This delegates to the user-defined _targetNodeCb callback.
+   * @return a promise that resolves or rejects depending if the tooltip should
+   * be shown or not. If it resolves, it does to the actual anchor to be used
+   */
+  isValidHoverTarget: function(target) {
+    // Execute the user-defined callback which should return either true/false
+    // or a promise that resolves or rejects
     let res = this._targetNodeCb(target, this);
-    let show = arg => this.show(arg instanceof Ci.nsIDOMNode ? arg : target);
 
+    // The callback can additionally return a DOMNode to replace the anchor of
+    // the tooltip when shown
     if (res && res.then) {
-      res.then(show);
-    } else if (res) {
-      show(res);
+      return res.then(arg => {
+        return arg instanceof Ci.nsIDOMNode ? arg : target;
+      }, () => {
+        return false;
+      });
+    } else {
+      return res ? promise.resolve(res) : promise.reject(false);
     }
   },
 
   _onBaseNodeMouseLeave: function() {
     clearNamedTimeout(this.uid);
     this._lastHovered = null;
     this.hide();
   },
@@ -615,52 +630,54 @@ Tooltip.prototype = {
    *        resized before this function was called.
    *        - naturalWidth/naturalHeight : the original size of the image before
    *        it was resized, if if was resized before this function was called.
    *        If not provided, will be measured on the loaded image.
    *        - maxDim : if the image should be resized before being shown, pass
    *        a number here
    */
   setImageContent: function(imageUrl, options={}) {
-    if (imageUrl) {
-      // Main container
-      let vbox = this.doc.createElement("vbox");
-      vbox.setAttribute("align", "center");
+    if (!imageUrl) {
+      return;
+    }
+
+    // Main container
+    let vbox = this.doc.createElement("vbox");
+    vbox.setAttribute("align", "center");
 
-      // Display the image
-      let image = this.doc.createElement("image");
-      image.setAttribute("src", imageUrl);
-      if (options.maxDim) {
-        image.style.maxWidth = options.maxDim + "px";
-        image.style.maxHeight = options.maxDim + "px";
-      }
-      vbox.appendChild(image);
+    // Display the image
+    let image = this.doc.createElement("image");
+    image.setAttribute("src", imageUrl);
+    if (options.maxDim) {
+      image.style.maxWidth = options.maxDim + "px";
+      image.style.maxHeight = options.maxDim + "px";
+    }
+    vbox.appendChild(image);
 
-      // Dimension label
-      let label = this.doc.createElement("label");
-      label.classList.add("devtools-tooltip-caption");
-      label.classList.add("theme-comment");
-      if (options.naturalWidth && options.naturalHeight) {
-        label.textContent = this._getImageDimensionLabel(options.naturalWidth,
-          options.naturalHeight);
-      } else {
-        // If no dimensions were provided, load the image to get them
-        label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
-        let imgObj = new this.doc.defaultView.Image();
-        imgObj.src = imageUrl;
-        imgObj.onload = () => {
-          imgObj.onload = null;
-          label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
-            imgObj.naturalHeight);
-        }
+    // Dimension label
+    let label = this.doc.createElement("label");
+    label.classList.add("devtools-tooltip-caption");
+    label.classList.add("theme-comment");
+    if (options.naturalWidth && options.naturalHeight) {
+      label.textContent = this._getImageDimensionLabel(options.naturalWidth,
+        options.naturalHeight);
+    } else {
+      // If no dimensions were provided, load the image to get them
+      label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
+      let imgObj = new this.doc.defaultView.Image();
+      imgObj.src = imageUrl;
+      imgObj.onload = () => {
+        imgObj.onload = null;
+        label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
+          imgObj.naturalHeight);
       }
-      vbox.appendChild(label);
+    }
+    vbox.appendChild(label);
 
-      this.content = vbox;
-    }
+    this.content = vbox;
   },
 
   _getImageDimensionLabel: (w, h) => w + " x " + h,
 
   /**
    * Fill the tooltip with a new instance of the spectrum color picker widget
    * initialized with the given color, and return a promise that resolves to
    * the instance of spectrum
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1117,17 +1117,17 @@ CssRuleView.prototype = {
    * Checks if the hovered target is a css value we support tooltips for.
    */
   _onTooltipTargetHover: function(target) {
     let property = target.textProperty, def = promise.defer(), hasTooltip = false;
 
     // Test for css transform
     if (property && property.name === "transform") {
       this.previewTooltip.setCssTransformContent(property.value, this.pageStyle,
-        this._viewedElement).then(def.resolve);
+        this._viewedElement).then(def.resolve, def.reject);
       hasTooltip = true;
     }
 
     // Test for image
     if (this.inspector.hasUrlToImageDataResolver) {
       let isImageHref = target.classList.contains("theme-link") &&
         target.parentNode.classList.contains("ruleview-propertyvalue");
       if (isImageHref) {
@@ -1159,18 +1159,20 @@ CssRuleView.prototype = {
 
     if (propertyName === "font-family" &&
         target.classList.contains("ruleview-propertyvalue")) {
       this.previewTooltip.setFontFamilyContent(target.textContent).then(def.resolve);
       hasTooltip = true;
     }
 
     if (hasTooltip) {
-      this.colorPicker.revert();
-      this.colorPicker.hide();
+      if (this.colorPicker.tooltip.isShown()) {
+        this.colorPicker.revert();
+        this.colorPicker.hide();
+      }
     } else {
       def.reject();
     }
 
     return def.promise;
   },
 
   /**
--- a/browser/devtools/styleinspector/test/browser_bug726427_csstransform_tooltip.js
+++ b/browser/devtools/styleinspector/test/browser_bug726427_csstransform_tooltip.js
@@ -59,116 +59,92 @@ function startTests() {
 
 function endTests() {
   contentDoc = inspector = ruleView = computedView = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function testTransformTooltipOnIDSelector() {
-  info("Testing that a transform tooltip appears on the #ID rule");
+  Task.spawn(function*() {
+    info("Testing that a transform tooltip appears on the #ID rule");
 
-  let panel = ruleView.previewTooltip.panel;
-  ok(panel, "The XUL panel exists for the rule-view preview tooltips");
+    let panel = ruleView.previewTooltip.panel;
+    ok(panel, "The XUL panel exists for the rule-view preview tooltips");
 
-  let {valueSpan} = getRuleViewProperty("#testElement", "transform");
-  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    let {valueSpan} = getRuleViewProperty("#testElement", "transform");
+    yield assertTooltipShownOn(ruleView.previewTooltip, valueSpan);
+
     // The transform preview is canvas, so there's not much we can test, so for
     // now, let's just be happy with the fact that the tooltips is shown!
     ok(true, "Tooltip shown on the transform property of the #ID rule");
-    ruleView.previewTooltip.hide();
-    executeSoon(testTransformTooltipOnClassSelector);
-  });
+  }).then(testTransformTooltipOnClassSelector);
 }
 
 function testTransformTooltipOnClassSelector() {
-  info("Testing that a transform tooltip appears on the .class rule");
+  Task.spawn(function*() {
+    info("Testing that a transform tooltip appears on the .class rule");
 
-  let {valueSpan} = getRuleViewProperty(".test-element", "transform");
-  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    let {valueSpan} = getRuleViewProperty(".test-element", "transform");
+    yield assertTooltipShownOn(ruleView.previewTooltip, valueSpan);
+
     // The transform preview is canvas, so there's not much we can test, so for
     // now, let's just be happy with the fact that the tooltips is shown!
     ok(true, "Tooltip shown on the transform property of the .class rule");
-    ruleView.previewTooltip.hide();
-    executeSoon(testTransformTooltipOnTagSelector);
-  });
+  }).then(testTransformTooltipOnTagSelector);
 }
 
 function testTransformTooltipOnTagSelector() {
-  info("Testing that a transform tooltip appears on the tag rule");
+  Task.spawn(function*() {
+    info("Testing that a transform tooltip appears on the tag rule");
 
-  let {valueSpan} = getRuleViewProperty("div", "transform");
-  assertTooltipShownOn(ruleView.previewTooltip, valueSpan, () => {
+    let {valueSpan} = getRuleViewProperty("div", "transform");
+    yield assertTooltipShownOn(ruleView.previewTooltip, valueSpan);
+
     // The transform preview is canvas, so there's not much we can test, so for
     // now, let's just be happy with the fact that the tooltips is shown!
     ok(true, "Tooltip shown on the transform property of the tag rule");
-    ruleView.previewTooltip.hide();
-    executeSoon(testTransformTooltipNotShownOnInvalidTransform);
-  });
+  }).then(testTransformTooltipNotShownOnInvalidTransform);
 }
 
 function testTransformTooltipNotShownOnInvalidTransform() {
-  info("Testing that a transform tooltip does not appear for invalid values");
+  Task.spawn(function*() {
+    info("Testing that a transform tooltip does not appear for invalid values");
 
-  let ruleEditor;
-  for (let rule of ruleView._elementStyle.rules) {
-    if (rule.matchedSelectors[0] === "[attr]") {
-      ruleEditor = rule.editor;
+    let ruleEditor;
+    for (let rule of ruleView._elementStyle.rules) {
+      if (rule.matchedSelectors[0] === "[attr]") {
+        ruleEditor = rule.editor;
+      }
     }
-  }
-  ruleEditor.addProperty("transform", "muchTransform(suchAngle)", "");
+    ruleEditor.addProperty("transform", "muchTransform(suchAngle)", "");
 
-  let {valueSpan} = getRuleViewProperty("[attr]", "transform");
-  assertTooltipNotShownOn(ruleView.previewTooltip, valueSpan, () => {
-    executeSoon(testTransformTooltipOnComputedView);
-  });
+    let {valueSpan} = getRuleViewProperty("[attr]", "transform");
+    let isValid = yield isHoverTooltipTarget(ruleView.previewTooltip, valueSpan);
+    ok(!isValid, "The tooltip did not appear on hover of an invalid transform value");
+  }).then(testTransformTooltipOnComputedView);
 }
 
 function testTransformTooltipOnComputedView() {
-  info("Testing that a transform tooltip appears in the computed view too");
+  Task.spawn(function*() {
+    info("Testing that a transform tooltip appears in the computed view too");
 
-  inspector.sidebar.select("computedview");
-  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
-  let doc = computedView.styleDocument;
+    inspector.sidebar.select("computedview");
+    computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+    let doc = computedView.styleDocument;
 
-  let panel = computedView.tooltip.panel;
-  let {valueSpan} = getComputedViewProperty("transform");
+    let panel = computedView.tooltip.panel;
+    let {valueSpan} = getComputedViewProperty("transform");
 
-  assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
+    yield assertTooltipShownOn(computedView.tooltip, valueSpan);
+
     // The transform preview is canvas, so there's not much we can test, so for
     // now, let's just be happy with the fact that the tooltips is shown!
     ok(true, "Tooltip shown on the computed transform property");
-    computedView.tooltip.hide();
-    executeSoon(endTests);
-  });
-}
-
-function assertTooltipShownOn(tooltip, element, cb) {
-  // If there is indeed a show-on-hover on element, the xul panel will be shown
-  tooltip.panel.addEventListener("popupshown", function shown() {
-    tooltip.panel.removeEventListener("popupshown", shown, true);
-    cb();
-  }, true);
-
-  // Run _showOnHover at stable state after the next refresh driver tick.
-  // This way nothing during reflow or painting should be able to
-  // cancel showing the popup.
-  element.ownerDocument.defaultView.requestAnimationFrame(() => {
-      executeSoon(() => { tooltip._showOnHover(element); });
-    });
-}
-
-function assertTooltipNotShownOn(tooltip, element, cb) {
-  // The only way to make sure the tooltip is not shown is try and show it, wait
-  // for a given amount of time, and then check if it's shown or not
-  tooltip._showOnHover(element);
-  setTimeout(() => {
-    ok(!tooltip.isShown(), "The tooltip did not appear on hover of the element");
-    cb();
-  }, tooltip.defaultShowDelay + 100);
+  }).then(endTests);
 }
 
 function getRule(selectorText) {
   let rule;
 
   [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-rule"), aRule => {
     let selector = aRule.querySelector(".ruleview-selector-matched");
     if (selector && selector.textContent === selectorText) {
--- a/browser/devtools/styleinspector/test/browser_bug765105_background_image_tooltip.js
+++ b/browser/devtools/styleinspector/test/browser_bug765105_background_image_tooltip.js
@@ -54,110 +54,102 @@ function startTests() {
 }
 
 function endTests() {
   contentDoc = inspector = ruleView = computedView = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
-function assertTooltipShownOn(tooltip, element, cb) {
-  // If there is indeed a show-on-hover on element, the xul panel will be shown
-  tooltip.panel.addEventListener("popupshown", function shown() {
-    tooltip.panel.removeEventListener("popupshown", shown, true);
-    cb();
-  }, true);
-  tooltip._showOnHover(element);
-}
-
 function testBodyRuleView() {
-  info("Testing tooltips in the rule view");
+  Task.spawn(function*() {
+    info("Testing tooltips in the rule view");
+    let panel = ruleView.previewTooltip.panel;
 
-  let panel = ruleView.previewTooltip.panel;
-
-  // Check that the rule view has a tooltip and that a XUL panel has been created
-  ok(ruleView.previewTooltip, "Tooltip instance exists");
-  ok(panel, "XUL panel exists");
+    // Check that the rule view has a tooltip and that a XUL panel has been created
+    ok(ruleView.previewTooltip, "Tooltip instance exists");
+    ok(panel, "XUL panel exists");
 
-  // Get the background-image property inside the rule view
-  let {valueSpan} = getRuleViewProperty("background-image");
-  let uriSpan = valueSpan.querySelector(".theme-link");
-  // And verify that the tooltip gets shown on this property
-  assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
+    // Get the background-image property inside the rule view
+    let {valueSpan} = getRuleViewProperty("background-image");
+    let uriSpan = valueSpan.querySelector(".theme-link");
+
+    yield assertTooltipShownOn(ruleView.previewTooltip, uriSpan);
+
     let images = panel.getElementsByTagName("image");
     is(images.length, 1, "Tooltip contains an image");
-    ok(images[0].src.indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1, "The image URL seems fine");
-
-    ruleView.previewTooltip.hide();
+    ok(images[0].getAttribute("src").indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1,
+      "The image URL seems fine");
 
+    let onUpdated = inspector.once("inspector-updated");
     inspector.selection.setNode(contentDoc.querySelector(".test-element"));
-    inspector.once("inspector-updated", testDivRuleView);
-  });
+    yield onUpdated;
+  }).then(testDivRuleView);
 }
 
 function testDivRuleView() {
-  let panel = ruleView.previewTooltip.panel;
+  Task.spawn(function*() {
+    let panel = ruleView.previewTooltip.panel;
 
-  // Get the background property inside the rule view
-  let {valueSpan} = getRuleViewProperty("background");
-  let uriSpan = valueSpan.querySelector(".theme-link");
+    // Get the background property inside the rule view
+    let {valueSpan} = getRuleViewProperty("background");
+    let uriSpan = valueSpan.querySelector(".theme-link");
 
-  // And verify that the tooltip gets shown on this property
-  assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
+    yield assertTooltipShownOn(ruleView.previewTooltip, uriSpan);
+
     let images = panel.getElementsByTagName("image");
     is(images.length, 1, "Tooltip contains an image");
-    ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri image as expected");
-
-    ruleView.previewTooltip.hide();
-
-    testTooltipAppearsEvenInEditMode();
-  });
+    ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri image as expected");
+  }).then(testTooltipAppearsEvenInEditMode);
 }
 
 function testTooltipAppearsEvenInEditMode() {
-  let panel = ruleView.previewTooltip.panel;
+  Task.spawn(function*() {
+    let panel = ruleView.previewTooltip.panel;
 
-  // Switch one field to edit mode
-  let brace = ruleView.doc.querySelector(".ruleview-ruleclose");
-  waitForEditorFocus(brace.parentNode, editor => {
-    // Now try to show the tooltip
+    info("Switching to edit mode in the rule view");
+    let editor = yield turnToEditMode(ruleView);
+
+    info("Now trying to show the preview tooltip");
     let {valueSpan} = getRuleViewProperty("background");
     let uriSpan = valueSpan.querySelector(".theme-link");
-    assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
-      is(ruleView.doc.activeElement, editor.input,
-        "Tooltip was shown in edit mode, and inplace-editor still focused");
+    yield assertTooltipShownOn(ruleView.previewTooltip, uriSpan);
+
+    is(ruleView.doc.activeElement, editor.input,
+      "Tooltip was shown in edit mode, and inplace-editor still focused");
+  }).then(testComputedView);
+}
 
-      ruleView.previewTooltip.hide();
-
-      testComputedView();
-    });
-  });
+function turnToEditMode(ruleView) {
+  let def = promise.defer();
+  let brace = ruleView.doc.querySelector(".ruleview-ruleclose");
+  waitForEditorFocus(brace.parentNode, def.resolve);
   brace.click();
+  return def.promise;
 }
 
 function testComputedView() {
-  info("Testing tooltips in the computed view");
+  Task.spawn(function*() {
+    info("Testing tooltips in the computed view");
 
-  inspector.sidebar.select("computedview");
-  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
-  let doc = computedView.styleDocument;
+    inspector.sidebar.select("computedview");
+    computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+    let doc = computedView.styleDocument;
 
-  let panel = computedView.tooltip.panel;
-  let {valueSpan} = getComputedViewProperty("background-image");
-  let uriSpan = valueSpan.querySelector(".theme-link");
+    let panel = computedView.tooltip.panel;
+    let {valueSpan} = getComputedViewProperty("background-image");
+    let uriSpan = valueSpan.querySelector(".theme-link");
 
-  assertTooltipShownOn(computedView.tooltip, uriSpan, () => {
+    yield assertTooltipShownOn(computedView.tooltip, uriSpan);
+
     let images = panel.getElementsByTagName("image");
     is(images.length, 1, "Tooltip contains an image");
-    ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri in the computed-view too");
 
-    computedView.tooltip.hide();
-
-    endTests();
-  });
+    ok(images[0].getAttribute("src").startsWith("data:"), "Tooltip contains a data-uri in the computed-view too");
+  }).then(endTests);
 }
 
 function getRuleViewProperty(name) {
   let prop = null;
   [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
     let nameSpan = property.querySelector(".ruleview-propertyname");
     let valueSpan = property.querySelector(".ruleview-propertyvalue");
 
--- a/browser/devtools/styleinspector/test/browser_bug889638_rule_view_color_picker.js
+++ b/browser/devtools/styleinspector/test/browser_bug889638_rule_view_color_picker.js
@@ -100,34 +100,34 @@ function testColorPickerAppearsOnColorSw
     });
     swatch.click();
   }
 
   clickOnSwatch(0, testColorPickerHidesWhenImageTooltipAppears);
 }
 
 function testColorPickerHidesWhenImageTooltipAppears() {
-  let swatch = swatches[0];
-  let bgImageSpan = getRuleViewProperty("background-image").valueSpan;
-  let uriSpan = bgImageSpan.querySelector(".theme-link");
+  Task.spawn(function*() {
+    let swatch = swatches[0];
+    let bgImageSpan = getRuleViewProperty("background-image").valueSpan;
+    let uriSpan = bgImageSpan.querySelector(".theme-link");
+    let tooltip = ruleView.colorPicker.tooltip;
 
-  ruleView.colorPicker.tooltip.once("shown", () => {
-    info("The color picker is shown, now display an image tooltip to hide it");
-    ruleView.previewTooltip._showOnHover(uriSpan);
-  });
-  ruleView.colorPicker.tooltip.once("hidden", () => {
-    ok(true, "The color picker closed when the image preview tooltip appeared");
+    info("Showing the color picker tooltip by clicking on the color swatch");
+    let onShown = tooltip.once("shown");
+    swatch.click();
+    yield onShown;
 
-    executeSoon(() => {
-      ruleView.previewTooltip.hide();
-      testPressingEscapeRevertsChanges();
-    });
-  });
+    info("Now showing the image preview tooltip to hide the color picker");
+    let onHidden = tooltip.once("hidden");
+    yield assertTooltipShownOn(ruleView.previewTooltip, uriSpan);
+    yield onHidden;
 
-  swatch.click();
+    ok(true, "The color picker closed when the image preview tooltip appeared");
+  }).then(testPressingEscapeRevertsChanges);
 }
 
 function testPressingEscapeRevertsChanges() {
   let cPicker = ruleView.colorPicker;
   let swatch = swatches[1];
 
   cPicker.tooltip.once("shown", () => {
     simulateColorChange(cPicker, [0, 0, 0, 1]);
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -275,8 +275,35 @@ function waitForSuccess(aOptions)
 
   wait(aOptions.validatorFn, aOptions.successFn, aOptions.failureFn);
 }
 
 registerCleanupFunction(tearDown);
 
 waitForExplicitFinish();
 
+/**
+ * @return a promise that resolves when the tooltip is shown
+ */
+function assertTooltipShownOn(tooltip, element) {
+  return Task.spawn(function*() {
+    let isTarget = yield isHoverTooltipTarget(tooltip, element);
+    ok(isTarget, "The element is a tooltip target");
+  });
+}
+
+/**
+ * Given a tooltip object instance (see Tooltip.js), checks if it is set to
+ * toggle and hover and if so, checks if the given target is a valid hover target.
+ * This won't actually show the tooltip (the less we interact with XUL panels
+ * during test runs, the better).
+ * @return a promise that resolves when the answer is known. Also, this will
+ * delete to a function in the rule-view which will insert content into the
+ * tooltip
+ */
+function isHoverTooltipTarget(tooltip, target) {
+  if (!tooltip._basedNode || !tooltip.panel) {
+    return promise.reject(new Error("The tooltip passed isn't set to toggle on hover or is not a tooltip"));
+  }
+  // The tooltip delegates to a user defined cb that inserts content in the tooltip
+  // when calling isValidHoverTarget
+  return tooltip.isValidHoverTarget(target);
+}