Bug 1289528, don't fire click event when option selected with keyboard, r=mconley
authorNeil Deakin <neil@mozilla.com>
Wed, 03 Aug 2016 07:45:45 -0400
changeset 307859 7e93e11713c4c46016c136601cbb26ac9439edb7
parent 307858 82cf261d9fea83f9d8fc2818c34f99b6ff2b3655
child 307860 8df572afbb5dd8c060eafefcb901d476b2862b69
push id80203
push userneil@mozilla.com
push dateWed, 03 Aug 2016 11:47:00 +0000
treeherdermozilla-inbound@7e96b394d5f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1289528
milestone51.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 1289528, don't fire click event when option selected with keyboard, r=mconley
toolkit/modules/SelectContentHelper.jsm
toolkit/modules/SelectParentHelper.jsm
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -29,16 +29,17 @@ var gOpen = false;
 this.EXPORTED_SYMBOLS = [
   "SelectContentHelper"
 ];
 
 this.SelectContentHelper = function (aElement, aGlobal) {
   this.element = aElement;
   this.initialSelection = aElement[aElement.selectedIndex] || null;
   this.global = aGlobal;
+  this.closedWithEnter = false;
   this.init();
   this.showDropDown();
   this._updateTimer = new DeferredTask(this._update.bind(this), 0);
 }
 
 Object.defineProperty(SelectContentHelper, "open", {
   get: function() {
     return gOpen;
@@ -105,47 +106,50 @@ this.SelectContentHelper.prototype = {
       selectedIndex: this.element.selectedIndex,
     });
   },
 
   receiveMessage: function(message) {
     switch (message.name) {
       case "Forms:SelectDropDownItem":
         this.element.selectedIndex = message.data.value;
+        this.closedWithEnter = message.data.closedWithEnter;
         break;
 
       case "Forms:DismissedDropDown":
         let selectedOption = this.element.item(this.element.selectedIndex);
         if (this.initialSelection != selectedOption) {
           let win = this.element.ownerDocument.defaultView;
           let inputEvent = new win.UIEvent("input", {
             bubbles: true,
           });
           this.element.dispatchEvent(inputEvent);
 
           let changeEvent = new win.Event("change", {
             bubbles: true,
           });
           this.element.dispatchEvent(changeEvent);
 
-          // Going for mostly-Blink parity here, which (at least on Windows)
-          // fires a mouseup and click event after each selection -
-          // even by keyboard. We're firing a mousedown too, since that
-          // seems to make more sense. Unfortunately, the spec on form
-          // control behaviours for these events is really not clear.
-          const MOUSE_EVENTS = ["mousedown", "mouseup", "click"];
-          for (let eventName of MOUSE_EVENTS) {
-            let mouseEvent = new win.MouseEvent(eventName, {
-              view: win,
-              bubbles: true,
-              cancelable: true,
-            });
-            selectedOption.dispatchEvent(mouseEvent);
-            if (eventName == "mouseup") {
-              DOMUtils.removeContentState(this.element, kStateActive);
+          if (!this.closedWithEnter) {
+            // Going for mostly-Blink parity here, which (at least on Windows)
+            // fires a mouseup and click event after each selection -
+            // even by keyboard. We're firing a mousedown too, since that
+            // seems to make more sense. Unfortunately, the spec on form
+            // control behaviours for these events is really not clear.
+            const MOUSE_EVENTS = ["mousedown", "mouseup", "click"];
+            for (let eventName of MOUSE_EVENTS) {
+              let mouseEvent = new win.MouseEvent(eventName, {
+                view: win,
+                bubbles: true,
+                cancelable: true,
+              });
+              selectedOption.dispatchEvent(mouseEvent);
+              if (eventName == "mouseup") {
+                DOMUtils.removeContentState(this.element, kStateActive);
+              }
             }
           }
         }
 
         this.uninit();
         break;
 
       case "Forms:MouseOver":
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -9,29 +9,31 @@ this.EXPORTED_SYMBOLS = [
 ];
 
 // Maximum number of rows to display in the select dropdown.
 const MAX_ROWS = 20;
 
 var currentBrowser = null;
 var currentMenulist = null;
 var currentZoom = 1;
+var closedWithEnter = false;
 
 this.SelectParentHelper = {
   populate: function(menulist, items, selectedIndex, zoom) {
     // Clear the current contents of the popup
     menulist.menupopup.textContent = "";
     currentZoom = zoom;
     currentMenulist = menulist;
     populateChildren(menulist, items, selectedIndex, zoom);
   },
 
   open: function(browser, menulist, rect) {
     menulist.hidden = false;
     currentBrowser = browser;
+    closedWithEnter = false;
     this._registerListeners(browser, menulist.menupopup);
 
     let win = browser.ownerDocument.defaultView;
 
     // Set the maximum height to show exactly MAX_ROWS items.
     let firstItem = menulist.getItemAtIndex(0);
     if (firstItem) {
       let itemHeight = firstItem.getBoundingClientRect().height;
@@ -62,20 +64,29 @@ this.SelectParentHelper = {
       case "mouseover":
         currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOver", {});
         break;
 
       case "mouseout":
         currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOut", {});
         break;
 
+      case "keydown":
+        if (event.keyCode == event.DOM_VK_RETURN) {
+          closedWithEnter = true;
+        }
+        break;
+
       case "command":
         if (event.target.hasAttribute("value")) {
+          let win = currentBrowser.ownerDocument.defaultView;
+
           currentBrowser.messageManager.sendAsyncMessage("Forms:SelectDropDownItem", {
-            value: event.target.value
+            value: event.target.value,
+            closedWithEnter: closedWithEnter
           });
         }
         break;
 
       case "popuphidden":
         currentBrowser.messageManager.sendAsyncMessage("Forms:DismissedDropDown", {});
         let popup = event.target;
         this._unregisterListeners(currentBrowser, popup);
@@ -101,24 +112,26 @@ this.SelectParentHelper = {
     }
   },
 
   _registerListeners: function(browser, popup) {
     popup.addEventListener("command", this);
     popup.addEventListener("popuphidden", this);
     popup.addEventListener("mouseover", this);
     popup.addEventListener("mouseout", this);
+    browser.ownerDocument.defaultView.addEventListener("keydown", this, true);
     browser.messageManager.addMessageListener("Forms:UpdateDropDown", this);
   },
 
   _unregisterListeners: function(browser, popup) {
     popup.removeEventListener("command", this);
     popup.removeEventListener("popuphidden", this);
     popup.removeEventListener("mouseover", this);
     popup.removeEventListener("mouseout", this);
+    browser.ownerDocument.defaultView.removeEventListener("keydown", this, true);
     browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
   },
 
 };
 
 function populateChildren(menulist, options, selectedIndex, zoom,
                           isInGroup = false, isGroupDisabled = false, adjustedTextSize = -1) {
   let element = menulist.menupopup;