Bug 939705 - Arrow keys not working in context menu r=mbrubeck
authorRodrigo Silveira <rsilveira@mozilla.com>
Tue, 26 Nov 2013 17:07:51 -0800
changeset 159127 3ef4df94f3dd8cadc4643417847ec4e1f960c26e
parent 159032 2831368a5e052002621ae52c314c2c37ec346650
child 159128 96af68a913f468febf09cf1e031a39ee8dce439d
push id25772
push usercbook@mozilla.com
push dateFri, 06 Dec 2013 12:31:37 +0000
treeherdermozilla-central@538e52a54db9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs939705
milestone28.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 939705 - Arrow keys not working in context menu r=mbrubeck
browser/metro/base/content/contenthandlers/FormHelper.js
browser/metro/base/content/helperui/MenuUI.js
browser/metro/modules/ContentUtil.jsm
--- a/browser/metro/base/content/contenthandlers/FormHelper.js
+++ b/browser/metro/base/content/contenthandlers/FormHelper.js
@@ -124,17 +124,17 @@ FormAssistant.prototype = {
 
     // We only work with choice lists or elements with autocomplete suggestions
     if (!this._isSelectElement(aElement) &&
         !this._isAutocomplete(aElement)) {
       return this.close();
     }
 
     // Don't re-open when navigating to avoid repopulating list when changing selection.
-    if (this._isAutocomplete(aElement) && this._open && this._isNavigationKey(aEvent)) {
+    if (this._isAutocomplete(aElement) && this._open && Util.isNavigationKey(aEvent.keyCode)) {
       return false;
     }
 
     // Enable the assistant
     this.currentElement = aElement;
     return this._open = true;
   },
 
@@ -288,32 +288,16 @@ FormAssistant.prototype = {
       case "text":
         if (this._isAutocomplete(aEvent.target)) {
           this._sendJsonMsgWrapper("FormAssist:AutoComplete");
         }
         break;
     }
   },
 
-  _isNavigationKey: function (aEvent) {
-    // Ignore navigation keys
-    if (aEvent.keyCode) {
-      let navigationKeys = [
-        aEvent.DOM_VK_DOWN,
-        aEvent.DOM_VK_UP,
-        aEvent.DOM_VK_LEFT,
-        aEvent.DOM_VK_RIGHT,
-        aEvent.DOM_VK_PAGE_UP,
-        aEvent.DOM_VK_PAGE_DOWN];
-
-      return navigationKeys.indexOf(aEvent.keyCode) != -1;
-    }
-    return false;
-  },
-
   _executeDelayed: function formHelperExecuteSoon(aCallback) {
     let self = this;
     let timer = new Util.Timeout(function() {
       aCallback(self);
     });
     timer.once(0);
   },
 
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -45,17 +45,16 @@ var AutofillMenuUI = {
       maxWidth: this._anchorRect.width,
       maxHeight: 350,
       source: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
     };
   },
 
   show: function show(aAnchorRect, aSuggestionsList) {
     this.commands.addEventListener("select", this, true);
-    window.addEventListener("keypress", this, true);
 
     this._anchorRect = aAnchorRect;
     this._emptyCommands();
     for (let idx = 0; idx < aSuggestionsList.length; idx++) {
       let item = document.createElement("richlistitem");
       let label = document.createElement("label");
       label.setAttribute("value", aSuggestionsList[idx].label);
       item.setAttribute("value", aSuggestionsList[idx].value);
@@ -67,48 +66,23 @@ var AutofillMenuUI = {
   },
 
   selectByIndex: function mn_selectByIndex(aIndex) {
     this._menuPopup.hide();
     FormHelperUI.doAutoComplete(this.commands.childNodes[aIndex].getAttribute("data"));
   },
 
   hide: function hide () {
-    window.removeEventListener("keypress", this, true);
     this.commands.removeEventListener("select", this, true);
 
     this._menuPopup.hide();
   },
 
   handleEvent: function (aEvent) {
     switch (aEvent.type) {
-      case "keypress":
-        switch (aEvent.keyCode) {
-          case aEvent.DOM_VK_ESCAPE:
-            this.hide();
-            break;
-
-          case aEvent.DOM_VK_DOWN:
-            this.commands.moveByOffset(1, true, false);
-            break;
-
-          case aEvent.DOM_VK_UP:
-            this.commands.moveByOffset(-1, true, false);
-            break;
-
-          case aEvent.DOM_VK_PAGE_DOWN:
-            this.commands.moveByOffset(this.commands.scrollOnePage(1), true, false);
-            break;
-
-          case aEvent.DOM_VK_PAGE_UP:
-            this.commands.moveByOffset(this.commands.scrollOnePage(-1), true, false);
-            break;
-        }
-        break;
-
       case "select":
         FormHelperUI.doAutoComplete(this.commands.value);
         break;
     }
   }
 };
 
 var ContextMenuUI = {
@@ -522,22 +496,57 @@ MenuPopup.prototype = {
     let event = document.createEvent("Events");
     event.initEvent(aName, true, false);
     this._panel.dispatchEvent(event);
   },
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "keypress":
-        if (!this._wantTypeBehind) {
+        // this.commands is not holding focus and not processing key events.
+        // Proxying events so that they're handled properly.
+
+        // Avoid recursion
+        if (aEvent.mine)
+          break;
+
+        let ev = document.createEvent("KeyboardEvent");
+        ev.initKeyEvent(
+          "keypress",        //  in DOMString typeArg,
+          false,             //  in boolean canBubbleArg,
+          true,              //  in boolean cancelableArg,
+          null,              //  in nsIDOMAbstractView viewArg,  Specifies UIEvent.view. This value may be null.
+          aEvent.ctrlKey,    //  in boolean ctrlKeyArg,
+          aEvent.altKey,     //  in boolean altKeyArg,
+          aEvent.shiftKey,   //  in boolean shiftKeyArg,
+          aEvent.metaKey,    //  in boolean metaKeyArg,
+          aEvent.keyCode,    //  in unsigned long keyCodeArg,
+          aEvent.charCode);  //  in unsigned long charCodeArg);
+
+        ev.mine = true;
+        this.commands.dispatchEvent(ev);
+
+        switch (aEvent.keyCode) {
+          case aEvent.DOM_VK_ESCAPE:
+            this.hide();
+            break;
+
+          case aEvent.DOM_VK_RETURN:
+            this.commands.currentItem.click();
+            break;
+        }
+
+        if (Util.isNavigationKey(aEvent.keyCode)) {
+          aEvent.stopPropagation();
+          aEvent.preventDefault();
+        } else if (!this._wantTypeBehind) {
           // Hide the context menu so you can't type behind it.
           aEvent.stopPropagation();
           aEvent.preventDefault();
-          if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE)
-            this.hide();
+          this.hide();
         }
         break;
       case "blur":
       case "mousedown":
       case "touchstart":
       case "scroll":
         if (!this._popup.contains(aEvent.target)) {
           aEvent.stopPropagation();
--- a/browser/metro/modules/ContentUtil.jsm
+++ b/browser/metro/modules/ContentUtil.jsm
@@ -1,16 +1,17 @@
 /* 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";
 
 this.EXPORTED_SYMBOLS = ["ContentUtil"];
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
 
 this.ContentUtil = {
   populateFragmentFromString: function populateFragmentFromString(fragment, str) {
     let re = /^([^#]*)#(\d+)\b([^#]*)/,
         document = fragment.ownerDocument,
         // the remaining arguments are our {text, className} values
         replacements = Array.slice(arguments, 2),
         match;
@@ -82,11 +83,24 @@ this.ContentUtil = {
           if (copy !== undefined)
             target[name] = copy;
         }
       }
     }
 
     // Return the modified object
     return target;
-  }
+  },
 
+  // Checks if a keycode is used for list navigation.
+  isNavigationKey: function (keyCode) {
+    let navigationKeys = [
+      nsIDOMKeyEvent.DOM_VK_DOWN,
+      nsIDOMKeyEvent.DOM_VK_UP,
+      nsIDOMKeyEvent.DOM_VK_LEFT,
+      nsIDOMKeyEvent.DOM_VK_RIGHT,
+      nsIDOMKeyEvent.DOM_VK_PAGE_UP,
+      nsIDOMKeyEvent.DOM_VK_PAGE_DOWN,
+      nsIDOMKeyEvent.DOM_VK_ESCAPE];
+
+    return navigationKeys.indexOf(keyCode) != -1;
+  }
 };