Bug 1349416 - Hide HTMLTooltip on mouseup rather than on click draft
authorJulian Descottes <jdescottes@mozilla.com>
Fri, 27 Jul 2018 07:27:03 +0200
changeset 823385 05b463a8cd72529b609a5c859f64091150f0cfb9
parent 823384 55dad46ddd0d8ba80d61a22894bee20ab9cc56d6
push id117656
push userjdescottes@mozilla.com
push dateFri, 27 Jul 2018 05:57:05 +0000
bugs1349416
milestone63.0a1
Bug 1349416 - Hide HTMLTooltip on mouseup rather than on click MozReview-Commit-ID: 847BlwUsMwC
devtools/client/shared/widgets/tooltip/HTMLTooltip.js
--- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -325,16 +325,17 @@ function HTMLTooltip(toolboxDoc, {
 
   // The top window is used to attach click event listeners to close the tooltip if the
   // user clicks on the content page.
   this.topWindow = this._getTopWindow();
 
   this._position = null;
 
   this._onClick = this._onClick.bind(this);
+  this._onMouseup = this._onMouseup.bind(this);
   this._onXulPanelHidden = this._onXulPanelHidden.bind(this);
 
   this._toggle = new TooltipToggle(this);
   this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
   this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
 
   this.container = this._createContainer();
 
@@ -451,16 +452,17 @@ HTMLTooltip.prototype = {
     this.doc.defaultView.clearTimeout(this.attachEventsTimer);
     this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
       if (this.autofocus) {
         this.focus();
       }
       // Update the top window reference each time in case the host changes.
       this.topWindow = this._getTopWindow();
       this.topWindow.addEventListener("click", this._onClick, true);
+      this.topWindow.addEventListener("mouseup", this._onMouseup, true);
       this.emit("shown");
     }, 0);
   },
 
   /**
    * Recalculate the dimensions and position of the tooltip in response to
    * changes to its content.
    *
@@ -696,45 +698,52 @@ HTMLTooltip.prototype = {
    */
   async hide() {
     this.doc.defaultView.clearTimeout(this.attachEventsTimer);
     if (!this.isVisible()) {
       this.emit("hidden");
       return;
     }
 
-    this.topWindow.removeEventListener("click", this._onClick, true);
+    this.topWindow.setTimeout(() => this.removeEventListeners(), 0);
+
     this.container.classList.remove("tooltip-visible");
     if (this.useXulWrapper) {
       await this._hideXulWrapper();
     }
 
     this.emit("hidden");
 
     const tooltipHasFocus = this.container.contains(this.doc.activeElement);
     if (tooltipHasFocus && this._focusedElement) {
       this._focusedElement.focus();
       this._focusedElement = null;
     }
   },
 
+  removeEventListeners: function() {
+    this.topWindow.removeEventListener("click", this._onClick, true);
+    this.topWindow.removeEventListener("mouseup", this._onMouseup, true);
+  },
+
   /**
    * Check if the tooltip is currently displayed.
    * @return {Boolean} true if the tooltip is visible
    */
   isVisible: function() {
     return this.container.classList.contains("tooltip-visible");
   },
 
   /**
    * Destroy the tooltip instance. Hide the tooltip if displayed, remove the
    * tooltip container from the document.
    */
   destroy: function() {
     this.hide();
+    this.removeEventListeners();
     this.container.remove();
     if (this.xulPanelWrapper) {
       this.xulPanelWrapper.remove();
     }
     this._toggle.destroy();
   },
 
   _createContainer: function() {
@@ -761,27 +770,40 @@ HTMLTooltip.prototype = {
     return container;
   },
 
   _onClick: function(e) {
     if (this._isInTooltipContainer(e.target)) {
       return;
     }
 
+    if (this.consumeOutsideClicks && e.button === 0) {
+      // Consume only left click events (button === 0).
+      e.preventDefault();
+      e.stopPropagation();
+    }
+  },
+
+  /**
+   * Hide the tooltip on mouseup rather than on click because the surrounding markup
+   * may change on mousedown in a way that prevents a "click" event from being fired.
+   * If the element that received the mousedown and the mouseup are different, click
+   * will not be fired.
+   */
+  _onMouseup: function(e) {
+    if (this._isInTooltipContainer(e.target)) {
+      return;
+    }
+
     // If the disable autohide setting is in effect, ignore.
     if (Services.prefs.getBoolPref("ui.popup.disable_autohide", false)) {
       return;
     }
 
     this.hide();
-    if (this.consumeOutsideClicks && e.button === 0) {
-      // Consume only left click events (button === 0).
-      e.preventDefault();
-      e.stopPropagation();
-    }
   },
 
   _isInTooltipContainer: function(node) {
     // Check if the target is the tooltip arrow.
     if (this.arrow && this.arrow === node) {
       return true;
     }