Bug 166240 part.8 Add tests for KeyboardEvent.location (synthesized events) r=smaug, sr=jst
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 03 May 2012 17:35:02 +0900
changeset 92962 de5745bce8bcd5c6fd391a77f2ad53c35d4d7d5b
parent 92961 691e7e02395e751fcdc50cbf5636cf5c3acded48
child 92963 074c8fb332a8bf7642c5a770b01d7dc784404c8f
push id8914
push usermasayuki@d-toybox.com
push dateThu, 03 May 2012 08:35:15 +0000
treeherdermozilla-inbound@de5745bce8bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jst
bugs166240
milestone15.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 166240 part.8 Add tests for KeyboardEvent.location (synthesized events) r=smaug, sr=jst
content/events/test/test_dom_keyboard_event.html
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/forms/test/test_bug348236.html
testing/mochitest/tests/SimpleTest/EventUtils.js
--- a/content/events/test/test_dom_keyboard_event.html
+++ b/content/events/test/test_dom_keyboard_event.html
@@ -1,13 +1,14 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for DOM KeyboardEvent</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
@@ -134,18 +135,174 @@ function testInitializingUntrustedEvent(
     }
     for (var k = 0; k < kInvalidModifierName.length; k++) {
       ok(!e.getModifierState(kInvalidModifierName[k]),
          description + "getModifierState(\"" + kInvalidModifierName[k] + "\") returns wrong value");
     }
   }
 }
 
+function testSynthesizedKeyLocation()
+{
+  const kTests = [
+    { key: "a", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_SHIFT", isModifier: true,
+      event: { shiftKey: true, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+    },
+    { key: "VK_SHIFT", isModifier: true,
+      event: { shiftKey: true, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+    },
+    { key: "VK_CONTROL", isModifier: true,
+      event: { shiftKey: false, ctrlKey: true, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+    },
+    { key: "VK_CONTROL", isModifier: true,
+      event: { shiftKey: false, ctrlKey: true, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+    },
+/* XXX Alt key activates menubar even if we consume the key events.
+    { key: "VK_ALT", isModifier: true,
+      event: { shiftKey: false, ctrlKey: false, altKey: true, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+    },
+    { key: "VK_ALT", isModifier: true,
+      event: { shiftKey: false, ctrlKey: false, altKey: true, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+    },
+*/
+    { key: "VK_META", isModifier: true,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: true,
+               location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+    },
+    { key: "VK_META", isModifier: true,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: true,
+               location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT },
+    },
+    { key: "VK_DOWN", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_DOWN", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+    },
+    { key: "VK_DOWN", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK },
+    },
+    { key: "5", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_NUMPAD5", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+    },
+    { key: "5", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_MOBILE },
+    },
+    { key: "VK_NUMPAD5", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_MOBILE },
+    },
+    { key: "+", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_ADD", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+    },
+    { key: "VK_ENTER", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_ENTER", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+    },
+    { key: "VK_NUM_LOCK", isModifier: true,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_INSERT", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
+    },
+    { key: "VK_INSERT", isModifier: false,
+      event: { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false,
+               location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD },
+    },
+  ];
+
+  function getLocationName(aLocation)
+  {
+    switch (aLocation) {
+      case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
+        return "DOM_KEY_LOCATION_STANDARD";
+      case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
+        return "DOM_KEY_LOCATION_LEFT";
+      case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
+        return "DOM_KEY_LOCATION_RIGHT";
+      case KeyboardEvent.DOM_KEY_LOCATION_MOBILE:
+        return "DOM_KEY_LOCATION_MOBILE";
+      case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
+        return "DOM_KEY_LOCATION_NUMPAD";
+      case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK:
+        return "DOM_KEY_LOCATION_JOYSTICK";
+      default:
+        return "Invalid value (" + aLocation + ")";
+    }
+  }
+
+  var currentTest, description;
+  var events = { keydown: false, keypress: false, keyup: false };
+
+  function handler(aEvent)
+  {
+    is(aEvent.location, currentTest.event.location,
+       description + "location of " + aEvent.type + " was invalid");
+    events[aEvent.type] = true;
+    aEvent.preventDefault();
+  }
+
+  window.addEventListener("keydown", handler, true);
+  window.addEventListener("keypress", handler, true);
+  window.addEventListener("keyup", handler, true);
+
+  for (var i = 0; i < kTests.length; i++) {
+    currentTest = kTests[i];
+    events = { keydown: false, keypress: false, keyup: false };
+    description = "testSynthesizedKeyLocation, " + i + ", key: " +
+      currentTest.key + ", location: " +
+      getLocationName(currentTest.event.location) + ": ";
+    synthesizeKey(currentTest.key, currentTest.event);
+    ok(events.keydown, description + "keydown event wasn't fired");
+    if (currentTest.isModifier) {
+      todo(events.keypress, description + "keypress event was fired for modifier key");
+    } else {
+      ok(events.keypress, description + "keypress event wasn't fired");
+    }
+    ok(events.keyup, description + "keyup event wasn't fired");
+  }
+
+  window.removeEventListener("keydown", handler, true);
+  window.removeEventListener("keypress", handler, true);
+  window.removeEventListener("keyup", handler, true);
+}
+
 function runTests()
 {
   testInitializingUntrustedEvent();
+  testSynthesizedKeyLocation();
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -693,17 +693,17 @@ nsDOMWindowUtils::SendTouchEvent(const n
   return rv;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendKeyEvent(const nsAString& aType,
                                PRInt32 aKeyCode,
                                PRInt32 aCharCode,
                                PRInt32 aModifiers,
-                               bool aPreventDefault,
+                               PRUint32 aAdditionalFlags,
                                bool* aDefaultActionTaken)
 {
   if (!IsUniversalXPConnectCapable()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
@@ -718,22 +718,88 @@ nsDOMWindowUtils::SendKeyEvent(const nsA
   else if (aType.EqualsLiteral("keypress"))
     msg = NS_KEY_PRESS;
   else
     return NS_ERROR_FAILURE;
 
   nsKeyEvent event(true, msg, widget);
   event.modifiers = GetWidgetModifiers(aModifiers);
 
-  event.keyCode = aKeyCode;
-  event.charCode = aCharCode;
+  if (msg == NS_KEY_PRESS) {
+    event.keyCode = aCharCode ? 0 : aKeyCode;
+    event.charCode = aCharCode;
+  } else {
+    event.keyCode = aKeyCode;
+    event.charCode = 0;
+  }
+
+  PRUint32 locationFlag = (aAdditionalFlags &
+    (KEY_FLAG_LOCATION_STANDARD | KEY_FLAG_LOCATION_LEFT |
+     KEY_FLAG_LOCATION_RIGHT | KEY_FLAG_LOCATION_NUMPAD |
+     KEY_FLAG_LOCATION_MOBILE | KEY_FLAG_LOCATION_JOYSTICK));
+  switch (locationFlag) {
+    case KEY_FLAG_LOCATION_STANDARD:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+      break;
+    case KEY_FLAG_LOCATION_LEFT:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+      break;
+    case KEY_FLAG_LOCATION_RIGHT:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+      break;
+    case KEY_FLAG_LOCATION_NUMPAD:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+      break;
+    case KEY_FLAG_LOCATION_MOBILE:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE;
+      break;
+    case KEY_FLAG_LOCATION_JOYSTICK:
+      event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_JOYSTICK;
+      break;
+    default:
+      if (locationFlag != 0) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      // If location flag isn't set, choose the location from keycode.
+      switch (aKeyCode) {
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD0:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD1:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD2:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD3:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD4:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD5:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD6:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD7:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD8:
+        case nsIDOMKeyEvent::DOM_VK_NUMPAD9:
+        case nsIDOMKeyEvent::DOM_VK_MULTIPLY:
+        case nsIDOMKeyEvent::DOM_VK_ADD:
+        case nsIDOMKeyEvent::DOM_VK_SEPARATOR:
+        case nsIDOMKeyEvent::DOM_VK_SUBTRACT:
+        case nsIDOMKeyEvent::DOM_VK_DECIMAL:
+        case nsIDOMKeyEvent::DOM_VK_DIVIDE:
+          event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+          break;
+        case nsIDOMKeyEvent::DOM_VK_SHIFT:
+        case nsIDOMKeyEvent::DOM_VK_CONTROL:
+        case nsIDOMKeyEvent::DOM_VK_ALT:
+        case nsIDOMKeyEvent::DOM_VK_META:
+          event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+          break;
+        default:
+          event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+          break;
+      }
+      break;
+  }
+
   event.refPoint.x = event.refPoint.y = 0;
   event.time = PR_IntervalNow();
 
-  if (aPreventDefault) {
+  if (aAdditionalFlags & KEY_FLAG_PREVENT_DEFAULT) {
     event.flags |= NS_EVENT_FLAG_NO_DEFAULT;
   }
 
   nsEventStatus status;
   nsresult rv = widget->DispatchEvent(&event, status);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aDefaultActionTaken = (status != nsEventStatus_eConsumeNoDefault);
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -65,17 +65,17 @@ interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 
-[scriptable, uuid(66a68858-df38-40e1-a792-fda04ebcc084)]
+[scriptable, uuid(f75d0a14-e278-4716-a151-637862451a2f)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -342,27 +342,40 @@ interface nsIDOMWindowUtils : nsISupport
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges.
    *
    * @param aType event type
    * @param aKeyCode key code
    * @param aCharCode character code
    * @param aModifiers modifiers pressed, using constants defined as MODIFIER_*
-   * @param aPreventDefault if true, preventDefault() the event before dispatch
+   * @param aAdditionalFlags special flags for the key event, see KEY_FLAG_*.
    *
    * @return false if the event had preventDefault() called on it,
    *               true otherwise.  In other words, true if and only if the
    *               default action was taken.
    */
+
+  // If this is set, preventDefault() the event before dispatch.
+  const unsigned long KEY_FLAG_PREVENT_DEFAULT   = 0x0001;
+
+  // if one of these flags is set, the KeyboardEvent.location will be the value.
+  // Otherwise, it will be computed from aKeyCode.
+  const unsigned long KEY_FLAG_LOCATION_STANDARD = 0x0010;
+  const unsigned long KEY_FLAG_LOCATION_LEFT     = 0x0020;
+  const unsigned long KEY_FLAG_LOCATION_RIGHT    = 0x0040;
+  const unsigned long KEY_FLAG_LOCATION_NUMPAD   = 0x0080;
+  const unsigned long KEY_FLAG_LOCATION_MOBILE   = 0x0100;
+  const unsigned long KEY_FLAG_LOCATION_JOYSTICK = 0x0200;
+
   boolean sendKeyEvent(in AString aType,
                        in long aKeyCode,
                        in long aCharCode,
                        in long aModifiers,
-                       [optional] in boolean aPreventDefault);
+                       [optional] in unsigned long aAdditionalFlags);
 
   /**
    * See nsIWidget::SynthesizeNativeKeyEvent
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges.
    */
--- a/layout/forms/test/test_bug348236.html
+++ b/layout/forms/test/test_bug348236.html
@@ -45,19 +45,19 @@ addLoadEvent(function test() {
         eSelect = $("eSelect"),
         IDOMNSEvent = CI.nsIDOMNSEvent,
         IDOMKeyEvent = CI.nsIDOMKeyEvent,
         timeout = 0 // Choose a larger value like 500 ms if you want to see what's happening.
 
     function keypressOnSelect(key, modifiers) {
         sec.PrivilegeManager.enablePrivilege("UniversalXPConnect")
         WinUtils.focus(eSelect)
-        WinUtils.sendKeyEvent("keyup", key, 0, modifiers, true)
-        WinUtils.sendKeyEvent("keypress", key, 0, modifiers, true)
-        WinUtils.sendKeyEvent("keydown", key, 0, modifiers, true)
+        WinUtils.sendKeyEvent("keyup", key, 0, modifiers, WinUtils.KEY_FLAG_PREVENT_DEFAULT)
+        WinUtils.sendKeyEvent("keypress", key, 0, modifiers, WinUtils.KEY_FLAG_PREVENT_DEFAULT)
+        WinUtils.sendKeyEvent("keydown", key, 0, modifiers, WinUtils.KEY_FLAG_PREVENT_DEFAULT)
     }
 
     function testKey(key, modifiers, keyString, functionToContinue) {
         var selectGotClick
         function clickListener() { selectGotClick = true }
         eSelect.selectedIndex = 0
         eSelect.onchangeCount = 0
 
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -385,17 +385,20 @@ function isKeypressFiredKey(aDOMKeyCode)
 /**
  * Synthesize a key event. It is targeted at whatever would be targeted by an
  * actual keypress by the user, typically the focused element.
  *
  * aKey should be either a character or a keycode starting with VK_ such as
  * VK_ENTER.
  *
  * aEvent is an object which may contain the properties:
- *   shiftKey, ctrlKey, altKey, metaKey, accessKey, type
+ *   shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location
+ *
+ * Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location.  Otherwise,
+ * DOMWindowUtils will choose good location from the keycode.
  *
  * If the type is specified, a key event of that type is fired. Otherwise,
  * a keydown, a keypress and then a keyup event are fired in sequence.
  *
  * aWindow is optional, and defaults to the current window object.
  */
 function synthesizeKey(aKey, aEvent, aWindow)
 {
@@ -408,33 +411,57 @@ function synthesizeKey(aKey, aEvent, aWi
         throw "Unknown key: " + aKey;
       }
     } else {
       charCode = aKey.charCodeAt(0);
       keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
     }
 
     var modifiers = _parseModifiers(aEvent);
+    var flags = 0;
+    if (aEvent.location != undefined) {
+      switch (aEvent.location) {
+        case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
+          flags |= utils.KEY_FLAG_LOCATION_STANDARD;
+          break;
+        case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
+          flags |= utils.KEY_FLAG_LOCATION_LEFT;
+          break;
+        case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
+          flags |= utils.KEY_FLAG_LOCATION_RIGHT;
+          break;
+        case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
+          flags |= utils.KEY_FLAG_LOCATION_NUMPAD;
+          break;
+        case KeyboardEvent.DOM_KEY_LOCATION_MOBILE:
+          flags |= utils.KEY_FLAG_LOCATION_MOBILE;
+          break;
+        case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK:
+          flags |= utils.KEY_FLAG_LOCATION_JOYSTICK;
+          break;
+      }
+    }
 
     if (!("type" in aEvent) || !aEvent.type) {
       // Send keydown + (optional) keypress + keyup events.
       var keyDownDefaultHappened =
-          utils.sendKeyEvent("keydown", keyCode, 0, modifiers);
+        utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags);
       if (isKeypressFiredKey(keyCode)) {
-        utils.sendKeyEvent("keypress", charCode ? 0 : keyCode, charCode,
-                           modifiers, !keyDownDefaultHappened);
+        if (!keyDownDefaultHappened) {
+          flags |= utils.KEY_FLAG_PREVENT_DEFAULT;
+        }
+        utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags);
       }
-      utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
+      utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags);
     } else if (aEvent.type == "keypress") {
       // Send standalone keypress event.
-      utils.sendKeyEvent(aEvent.type, charCode ? 0 : keyCode,
-                         charCode, modifiers);
+      utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags);
     } else {
       // Send other standalone event than keypress.
-      utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers);
+      utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags);
     }
   }
 }
 
 var _gSeenEvent = false;
 
 /**
  * Indicate that an event with an original target of aExpectedTarget and