testing/mochitest/tests/SimpleTest/EventUtils.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /**
     2  * EventUtils provides some utility methods for creating and sending DOM events.
     3  * Current methods:
     4  *  sendMouseEvent
     5  *  sendChar
     6  *  sendString
     7  *  sendKey
     8  *  synthesizeMouse
     9  *  synthesizeMouseAtCenter
    10  *  synthesizePointer
    11  *  synthesizeWheel
    12  *  synthesizeKey
    13  *  synthesizeNativeKey
    14  *  synthesizeMouseExpectEvent
    15  *  synthesizeKeyExpectEvent
    16  *
    17  *  When adding methods to this file, please add a performance test for it.
    18  */
    20 // This file is used both in privileged and unprivileged contexts, so we have to
    21 // be careful about our access to Components.interfaces. We also want to avoid
    22 // naming collisions with anything that might be defined in the scope that imports
    23 // this script.
    24 window.__defineGetter__('_EU_Ci', function() {
    25   // Even if the real |Components| doesn't exist, we might shim in a simple JS
    26   // placebo for compat. An easy way to differentiate this from the real thing
    27   // is whether the property is read-only or not.
    28   var c = Object.getOwnPropertyDescriptor(window, 'Components');
    29   return c && c.value && !c.writable ? Components.interfaces : SpecialPowers.Ci;
    30 });
    32 /**
    33  * Send a mouse event to the node aTarget (aTarget can be an id, or an
    34  * actual node) . The "event" passed in to aEvent is just a JavaScript
    35  * object with the properties set that the real mouse event object should
    36  * have. This includes the type of the mouse event.
    37  * E.g. to send an click event to the node with id 'node' you might do this:
    38  *
    39  * sendMouseEvent({type:'click'}, 'node');
    40  */
    41 function getElement(id) {
    42   return ((typeof(id) == "string") ?
    43     document.getElementById(id) : id); 
    44 };   
    46 this.$ = this.getElement;
    48 function sendMouseEvent(aEvent, aTarget, aWindow) {
    49   if (['click', 'contextmenu', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
    50     throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
    51   }
    53   if (!aWindow) {
    54     aWindow = window;
    55   }
    57   if (!(aTarget instanceof aWindow.Element)) {
    58     aTarget = aWindow.document.getElementById(aTarget);
    59   }
    61   var event = aWindow.document.createEvent('MouseEvent');
    63   var typeArg          = aEvent.type;
    64   var canBubbleArg     = true;
    65   var cancelableArg    = true;
    66   var viewArg          = aWindow;
    67   var detailArg        = aEvent.detail        || (aEvent.type == 'click'     ||
    68                                                   aEvent.type == 'mousedown' ||
    69                                                   aEvent.type == 'mouseup' ? 1 :
    70                                                   aEvent.type == 'dblclick'? 2 : 0);
    71   var screenXArg       = aEvent.screenX       || 0;
    72   var screenYArg       = aEvent.screenY       || 0;
    73   var clientXArg       = aEvent.clientX       || 0;
    74   var clientYArg       = aEvent.clientY       || 0;
    75   var ctrlKeyArg       = aEvent.ctrlKey       || false;
    76   var altKeyArg        = aEvent.altKey        || false;
    77   var shiftKeyArg      = aEvent.shiftKey      || false;
    78   var metaKeyArg       = aEvent.metaKey       || false;
    79   var buttonArg        = aEvent.button        || (aEvent.type == 'contextmenu' ? 2 : 0);
    80   var relatedTargetArg = aEvent.relatedTarget || null;
    82   event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
    83                        screenXArg, screenYArg, clientXArg, clientYArg,
    84                        ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
    85                        buttonArg, relatedTargetArg);
    87   return SpecialPowers.dispatchEvent(aWindow, aTarget, event);
    88 }
    90 /**
    91  * Send the char aChar to the focused element.  This method handles casing of
    92  * chars (sends the right charcode, and sends a shift key for uppercase chars).
    93  * No other modifiers are handled at this point.
    94  *
    95  * For now this method only works for ASCII characters and emulates the shift
    96  * key state on US keyboard layout.
    97  */
    98 function sendChar(aChar, aWindow) {
    99   var hasShift;
   100   // Emulate US keyboard layout for the shiftKey state.
   101   switch (aChar) {
   102     case "!":
   103     case "@":
   104     case "#":
   105     case "$":
   106     case "%":
   107     case "^":
   108     case "&":
   109     case "*":
   110     case "(":
   111     case ")":
   112     case "_":
   113     case "+":
   114     case "{":
   115     case "}":
   116     case ":":
   117     case "\"":
   118     case "|":
   119     case "<":
   120     case ">":
   121     case "?":
   122       hasShift = true;
   123       break;
   124     default:
   125       hasShift = (aChar == aChar.toUpperCase());
   126       break;
   127   }
   128   synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
   129 }
   131 /**
   132  * Send the string aStr to the focused element.
   133  *
   134  * For now this method only works for ASCII characters and emulates the shift
   135  * key state on US keyboard layout.
   136  */
   137 function sendString(aStr, aWindow) {
   138   for (var i = 0; i < aStr.length; ++i) {
   139     sendChar(aStr.charAt(i), aWindow);
   140   }
   141 }
   143 /**
   144  * Send the non-character key aKey to the focused node.
   145  * The name of the key should be the part that comes after "DOM_VK_" in the
   146  *   KeyEvent constant name for this key.
   147  * No modifiers are handled at this point.
   148  */
   149 function sendKey(aKey, aWindow) {
   150   var keyName = "VK_" + aKey.toUpperCase();
   151   synthesizeKey(keyName, { shiftKey: false }, aWindow);
   152 }
   154 /**
   155  * Parse the key modifier flags from aEvent. Used to share code between
   156  * synthesizeMouse and synthesizeKey.
   157  */
   158 function _parseModifiers(aEvent)
   159 {
   160   const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
   161   var mval = 0;
   162   if (aEvent.shiftKey) {
   163     mval |= nsIDOMWindowUtils.MODIFIER_SHIFT;
   164   }
   165   if (aEvent.ctrlKey) {
   166     mval |= nsIDOMWindowUtils.MODIFIER_CONTROL;
   167   }
   168   if (aEvent.altKey) {
   169     mval |= nsIDOMWindowUtils.MODIFIER_ALT;
   170   }
   171   if (aEvent.metaKey) {
   172     mval |= nsIDOMWindowUtils.MODIFIER_META;
   173   }
   174   if (aEvent.accelKey) {
   175     mval |= (navigator.platform.indexOf("Mac") >= 0) ?
   176       nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL;
   177   }
   178   if (aEvent.altGrKey) {
   179     mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH;
   180   }
   181   if (aEvent.capsLockKey) {
   182     mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK;
   183   }
   184   if (aEvent.fnKey) {
   185     mval |= nsIDOMWindowUtils.MODIFIER_FN;
   186   }
   187   if (aEvent.numLockKey) {
   188     mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK;
   189   }
   190   if (aEvent.scrollLockKey) {
   191     mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK;
   192   }
   193   if (aEvent.symbolLockKey) {
   194     mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK;
   195   }
   196   if (aEvent.osKey) {
   197     mval |= nsIDOMWindowUtils.MODIFIER_OS;
   198   }
   200   return mval;
   201 }
   203 /**
   204  * Synthesize a mouse event on a target. The actual client point is determined
   205  * by taking the aTarget's client box and offseting it by aOffsetX and
   206  * aOffsetY. This allows mouse clicks to be simulated by calling this method.
   207  *
   208  * aEvent is an object which may contain the properties:
   209  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
   210  *
   211  * If the type is specified, an mouse event of that type is fired. Otherwise,
   212  * a mousedown followed by a mouse up is performed.
   213  *
   214  * aWindow is optional, and defaults to the current window object.
   215  *
   216  * Returns whether the event had preventDefault() called on it.
   217  */
   218 function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
   219 {
   220   var rect = aTarget.getBoundingClientRect();
   221   return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
   222        aEvent, aWindow);
   223 }
   224 function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
   225 {
   226   var rect = aTarget.getBoundingClientRect();
   227   synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
   228        aEvent, aWindow);
   229 }
   230 function synthesizePointer(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
   231 {
   232   var rect = aTarget.getBoundingClientRect();
   233   return synthesizePointerAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
   234        aEvent, aWindow);
   235 }
   237 /*
   238  * Synthesize a mouse event at a particular point in aWindow.
   239  *
   240  * aEvent is an object which may contain the properties:
   241  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
   242  *
   243  * If the type is specified, an mouse event of that type is fired. Otherwise,
   244  * a mousedown followed by a mouse up is performed.
   245  *
   246  * aWindow is optional, and defaults to the current window object.
   247  */
   248 function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
   249 {
   250   var utils = _getDOMWindowUtils(aWindow);
   251   var defaultPrevented = false;
   253   if (utils) {
   254     var button = aEvent.button || 0;
   255     var clickCount = aEvent.clickCount || 1;
   256     var modifiers = _parseModifiers(aEvent);
   257     var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
   258     var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
   259     var synthesized = ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true;
   261     if (("type" in aEvent) && aEvent.type) {
   262       defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button,
   263                                               clickCount, modifiers, false,
   264                                               pressure, inputSource,
   265                                               synthesized);
   266     }
   267     else {
   268       utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
   269       utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
   270     }
   271   }
   273   return defaultPrevented;
   274 }
   275 function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
   276 {
   277   var utils = _getDOMWindowUtils(aWindow);
   279   if (utils) {
   280     var id = aEvent.id || 0;
   281     var rx = aEvent.rx || 1;
   282     var ry = aEvent.rx || 1;
   283     var angle = aEvent.angle || 0;
   284     var force = aEvent.force || 1;
   285     var modifiers = _parseModifiers(aEvent);
   287     if (("type" in aEvent) && aEvent.type) {
   288       utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
   289     }
   290     else {
   291       utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
   292       utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
   293     }
   294   }
   295 }
   296 function synthesizePointerAtPoint(left, top, aEvent, aWindow)
   297 {
   298   var utils = _getDOMWindowUtils(aWindow);
   299   var defaultPrevented = false;
   301   if (utils) {
   302     var button = aEvent.button || 0;
   303     var clickCount = aEvent.clickCount || 1;
   304     var modifiers = _parseModifiers(aEvent);
   305     var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
   306     var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
   307     var synthesized = ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true;
   309     if (("type" in aEvent) && aEvent.type) {
   310       defaultPrevented = utils.sendPointerEvent(aEvent.type, left, top, button,
   311                                                 clickCount, modifiers, false,
   312                                                 pressure, inputSource,
   313                                                 synthesized);
   314     }
   315     else {
   316       utils.sendPointerEvent("pointerdown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
   317       utils.sendPointerEvent("pointerup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
   318     }
   319   }
   321   return defaultPrevented;
   322 }
   324 // Call synthesizeMouse with coordinates at the center of aTarget.
   325 function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
   326 {
   327   var rect = aTarget.getBoundingClientRect();
   328   synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
   329                   aWindow);
   330 }
   331 function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)
   332 {
   333   var rect = aTarget.getBoundingClientRect();
   334   synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent,
   335                   aWindow);
   336 }
   338 /**
   339  * Synthesize a wheel event on a target. The actual client point is determined
   340  * by taking the aTarget's client box and offseting it by aOffsetX and
   341  * aOffsetY.
   342  *
   343  * aEvent is an object which may contain the properties:
   344  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
   345  *   deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice,
   346  *   isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY
   347  *
   348  * deltaMode must be defined, others are ok even if undefined.
   349  *
   350  * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value.  The
   351  * value is just checked as 0 or positive or negative.
   352  *
   353  * aWindow is optional, and defaults to the current window object.
   354  */
   355 function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
   356 {
   357   var utils = _getDOMWindowUtils(aWindow);
   358   if (!utils) {
   359     return;
   360   }
   362   var modifiers = _parseModifiers(aEvent);
   363   var options = 0;
   364   if (aEvent.isPixelOnlyDevice &&
   365       (aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) {
   366     options |= utils.WHEEL_EVENT_CAUSED_BY_PIXEL_ONLY_DEVICE;
   367   }
   368   if (aEvent.isMomentum) {
   369     options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM;
   370   }
   371   if (aEvent.isCustomizedByPrefs) {
   372     options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS;
   373   }
   374   if (typeof aEvent.expectedOverflowDeltaX !== "undefined") {
   375     if (aEvent.expectedOverflowDeltaX === 0) {
   376       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO;
   377     } else if (aEvent.expectedOverflowDeltaX > 0) {
   378       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE;
   379     } else {
   380       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE;
   381     }
   382   }
   383   if (typeof aEvent.expectedOverflowDeltaY !== "undefined") {
   384     if (aEvent.expectedOverflowDeltaY === 0) {
   385       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO;
   386     } else if (aEvent.expectedOverflowDeltaY > 0) {
   387       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE;
   388     } else {
   389       options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE;
   390     }
   391   }
   392   var isPixelOnlyDevice =
   393     aEvent.isPixelOnlyDevice && aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL;
   395   // Avoid the JS warnings "reference to undefined property"
   396   if (!aEvent.deltaX) {
   397     aEvent.deltaX = 0;
   398   }
   399   if (!aEvent.deltaY) {
   400     aEvent.deltaY = 0;
   401   }
   402   if (!aEvent.deltaZ) {
   403     aEvent.deltaZ = 0;
   404   }
   406   var lineOrPageDeltaX =
   407     aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX :
   408                   aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) :
   409                                       Math.ceil(aEvent.deltaX);
   410   var lineOrPageDeltaY =
   411     aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY :
   412                   aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) :
   413                                       Math.ceil(aEvent.deltaY);
   415   var rect = aTarget.getBoundingClientRect();
   416   utils.sendWheelEvent(rect.left + aOffsetX, rect.top + aOffsetY,
   417                        aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ,
   418                        aEvent.deltaMode, modifiers,
   419                        lineOrPageDeltaX, lineOrPageDeltaY, options);
   420 }
   422 function _computeKeyCodeFromChar(aChar)
   423 {
   424   if (aChar.length != 1) {
   425     return 0;
   426   }
   427   const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent;
   428   if (aChar >= 'a' && aChar <= 'z') {
   429     return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
   430   }
   431   if (aChar >= 'A' && aChar <= 'Z') {
   432     return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
   433   }
   434   if (aChar >= '0' && aChar <= '9') {
   435     return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
   436   }
   437   // returns US keyboard layout's keycode
   438   switch (aChar) {
   439     case '~':
   440     case '`':
   441       return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
   442     case '!':
   443       return nsIDOMKeyEvent.DOM_VK_1;
   444     case '@':
   445       return nsIDOMKeyEvent.DOM_VK_2;
   446     case '#':
   447       return nsIDOMKeyEvent.DOM_VK_3;
   448     case '$':
   449       return nsIDOMKeyEvent.DOM_VK_4;
   450     case '%':
   451       return nsIDOMKeyEvent.DOM_VK_5;
   452     case '^':
   453       return nsIDOMKeyEvent.DOM_VK_6;
   454     case '&':
   455       return nsIDOMKeyEvent.DOM_VK_7;
   456     case '*':
   457       return nsIDOMKeyEvent.DOM_VK_8;
   458     case '(':
   459       return nsIDOMKeyEvent.DOM_VK_9;
   460     case ')':
   461       return nsIDOMKeyEvent.DOM_VK_0;
   462     case '-':
   463     case '_':
   464       return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
   465     case '+':
   466     case '=':
   467       return nsIDOMKeyEvent.DOM_VK_EQUALS;
   468     case '{':
   469     case '[':
   470       return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
   471     case '}':
   472     case ']':
   473       return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
   474     case '|':
   475     case '\\':
   476       return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
   477     case ':':
   478     case ';':
   479       return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
   480     case '\'':
   481     case '"':
   482       return nsIDOMKeyEvent.DOM_VK_QUOTE;
   483     case '<':
   484     case ',':
   485       return nsIDOMKeyEvent.DOM_VK_COMMA;
   486     case '>':
   487     case '.':
   488       return nsIDOMKeyEvent.DOM_VK_PERIOD;
   489     case '?':
   490     case '/':
   491       return nsIDOMKeyEvent.DOM_VK_SLASH;
   492     case '\n':
   493       return nsIDOMKeyEvent.DOM_VK_RETURN;
   494     case ' ':
   495       return nsIDOMKeyEvent.DOM_VK_SPACE;
   496     default:
   497       return 0;
   498   }
   499 }
   501 /**
   502  * isKeypressFiredKey() returns TRUE if the given key should cause keypress
   503  * event when widget handles the native key event.  Otherwise, FALSE.
   504  *
   505  * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
   506  * name begins with "VK_", or a character.
   507  */
   508 function isKeypressFiredKey(aDOMKeyCode)
   509 {
   510   if (typeof(aDOMKeyCode) == "string") {
   511     if (aDOMKeyCode.indexOf("VK_") == 0) {
   512       aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
   513       if (!aDOMKeyCode) {
   514         throw "Unknown key: " + aDOMKeyCode;
   515       }
   516     } else {
   517       // If the key generates a character, it must cause a keypress event.
   518       return true;
   519     }
   520   }
   521   switch (aDOMKeyCode) {
   522     case KeyEvent.DOM_VK_SHIFT:
   523     case KeyEvent.DOM_VK_CONTROL:
   524     case KeyEvent.DOM_VK_ALT:
   525     case KeyEvent.DOM_VK_CAPS_LOCK:
   526     case KeyEvent.DOM_VK_NUM_LOCK:
   527     case KeyEvent.DOM_VK_SCROLL_LOCK:
   528     case KeyEvent.DOM_VK_META:
   529       return false;
   530     default:
   531       return true;
   532   }
   533 }
   535 /**
   536  * Synthesize a key event. It is targeted at whatever would be targeted by an
   537  * actual keypress by the user, typically the focused element.
   538  *
   539  * aKey should be either a character or a keycode starting with VK_ such as
   540  * VK_RETURN.
   541  *
   542  * aEvent is an object which may contain the properties:
   543  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location
   544  *
   545  * Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location.  Otherwise,
   546  * DOMWindowUtils will choose good location from the keycode.
   547  *
   548  * If the type is specified, a key event of that type is fired. Otherwise,
   549  * a keydown, a keypress and then a keyup event are fired in sequence.
   550  *
   551  * aWindow is optional, and defaults to the current window object.
   552  */
   553 function synthesizeKey(aKey, aEvent, aWindow)
   554 {
   555   var utils = _getDOMWindowUtils(aWindow);
   556   if (utils) {
   557     var keyCode = 0, charCode = 0;
   558     if (aKey.indexOf("VK_") == 0) {
   559       keyCode = KeyEvent["DOM_" + aKey];
   560       if (!keyCode) {
   561         throw "Unknown key: " + aKey;
   562       }
   563     } else {
   564       charCode = aKey.charCodeAt(0);
   565       keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
   566     }
   568     var modifiers = _parseModifiers(aEvent);
   569     var flags = 0;
   570     if (aEvent.location != undefined) {
   571       switch (aEvent.location) {
   572         case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
   573           flags |= utils.KEY_FLAG_LOCATION_STANDARD;
   574           break;
   575         case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
   576           flags |= utils.KEY_FLAG_LOCATION_LEFT;
   577           break;
   578         case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
   579           flags |= utils.KEY_FLAG_LOCATION_RIGHT;
   580           break;
   581         case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
   582           flags |= utils.KEY_FLAG_LOCATION_NUMPAD;
   583           break;
   584         case KeyboardEvent.DOM_KEY_LOCATION_MOBILE:
   585           flags |= utils.KEY_FLAG_LOCATION_MOBILE;
   586           break;
   587         case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK:
   588           flags |= utils.KEY_FLAG_LOCATION_JOYSTICK;
   589           break;
   590       }
   591     }
   593     if (!("type" in aEvent) || !aEvent.type) {
   594       // Send keydown + (optional) keypress + keyup events.
   595       var keyDownDefaultHappened =
   596         utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags);
   597       if (isKeypressFiredKey(keyCode) && keyDownDefaultHappened) {
   598         utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags);
   599       }
   600       utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags);
   601     } else if (aEvent.type == "keypress") {
   602       // Send standalone keypress event.
   603       utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags);
   604     } else {
   605       // Send other standalone event than keypress.
   606       utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags);
   607     }
   608   }
   609 }
   611 function _parseNativeModifiers(aModifiers)
   612 {
   613   var modifiers;
   614   if (aModifiers.capsLockKey) {
   615     modifiers |= 0x00000001;
   616   }
   617   if (aModifiers.numLockKey) {
   618     modifiers |= 0x00000002;
   619   }
   620   if (aModifiers.shiftKey) {
   621     modifiers |= 0x00000100;
   622   }
   623   if (aModifiers.shiftRightKey) {
   624     modifiers |= 0x00000200;
   625   }
   626   if (aModifiers.ctrlKey) {
   627     modifiers |= 0x00000400;
   628   }
   629   if (aModifiers.ctrlRightKey) {
   630     modifiers |= 0x00000800;
   631   }
   632   if (aModifiers.altKey) {
   633     modifiers |= 0x00001000;
   634   }
   635   if (aModifiers.altRightKey) {
   636     modifiers |= 0x00002000;
   637   }
   638   if (aModifiers.metaKey) {
   639     modifiers |= 0x00004000;
   640   }
   641   if (aModifiers.metaRightKey) {
   642     modifiers |= 0x00008000;
   643   }
   644   if (aModifiers.helpKey) {
   645     modifiers |= 0x00010000;
   646   }
   647   if (aModifiers.fnKey) {
   648     modifiers |= 0x00100000;
   649   }
   650   if (aModifiers.numericKeyPadKey) {
   651     modifiers |= 0x01000000;
   652   }
   654   if (aModifiers.accelKey) {
   655     modifiers |=
   656       (navigator.platform.indexOf("Mac") == 0) ? 0x00004000 : 0x00000400;
   657   }
   658   if (aModifiers.accelRightKey) {
   659     modifiers |=
   660       (navigator.platform.indexOf("Mac") == 0) ? 0x00008000 : 0x00000800;
   661   }
   662   if (aModifiers.altGrKey) {
   663     modifiers |=
   664       (navigator.platform.indexOf("Win") == 0) ? 0x00002800 : 0x00001000;
   665   }
   666   return modifiers;
   667 }
   669 // Mac: Any unused number is okay for adding new keyboard layout.
   670 //      When you add new keyboard layout here, you need to modify
   671 //      TISInputSourceWrapper::InitByLayoutID().
   672 // Win: These constants can be found by inspecting registry keys under
   673 //      HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts
   675 const KEYBOARD_LAYOUT_ARABIC =
   676   { name: "Arabic",             Mac: 6,    Win: 0x00000401 };
   677 const KEYBOARD_LAYOUT_BRAZILIAN_ABNT =
   678   { name: "Brazilian ABNT",     Mac: null, Win: 0x00000416 };
   679 const KEYBOARD_LAYOUT_DVORAK_QWERTY =
   680   { name: "Dvorak-QWERTY",      Mac: 4,    Win: null       };
   681 const KEYBOARD_LAYOUT_EN_US =
   682   { name: "US",                 Mac: 0,    Win: 0x00000409 };
   683 const KEYBOARD_LAYOUT_FRENCH =
   684   { name: "French",             Mac: 7,    Win: 0x0000040C };
   685 const KEYBOARD_LAYOUT_GREEK =
   686   { name: "Greek",              Mac: 1,    Win: 0x00000408 };
   687 const KEYBOARD_LAYOUT_GERMAN =
   688   { name: "German",             Mac: 2,    Win: 0x00000407 };
   689 const KEYBOARD_LAYOUT_HEBREW =
   690   { name: "Hebrew",             Mac: 8,    Win: 0x0000040D };
   691 const KEYBOARD_LAYOUT_JAPANESE =
   692   { name: "Japanese",           Mac: null, Win: 0x00000411 };
   693 const KEYBOARD_LAYOUT_LITHUANIAN =
   694   { name: "Lithuanian",         Mac: 9,    Win: 0x00010427 };
   695 const KEYBOARD_LAYOUT_NORWEGIAN =
   696   { name: "Norwegian",          Mac: 10,   Win: 0x00000414 };
   697 const KEYBOARD_LAYOUT_SPANISH =
   698   { name: "Spanish",            Mac: 11,   Win: 0x0000040A };
   699 const KEYBOARD_LAYOUT_SWEDISH =
   700   { name: "Swedish",            Mac: 3,    Win: 0x0000041D };
   701 const KEYBOARD_LAYOUT_THAI =
   702   { name: "Thai",               Mac: 5,    Win: 0x0002041E };
   704 /**
   705  * synthesizeNativeKey() dispatches native key event on active window.
   706  * This is implemented only on Windows and Mac.
   707  *
   708  * @param aKeyboardLayout       One of KEYBOARD_LAYOUT_* defined above.
   709  * @param aNativeKeyCode        A native keycode value defined in
   710  *                              NativeKeyCodes.js.
   711  * @param aModifiers            Modifier keys.  If no modifire key is pressed,
   712  *                              this must be {}.  Otherwise, one or more items
   713  *                              referred in _parseNativeModifiers() must be
   714  *                              true.
   715  * @param aChars                Specify characters which should be generated
   716  *                              by the key event.
   717  * @param aUnmodifiedChars      Specify characters of unmodified (except Shift)
   718  *                              aChar value.
   719  * @return                      True if this function succeed dispatching
   720  *                              native key event.  Otherwise, false.
   721  */
   723 function synthesizeNativeKey(aKeyboardLayout, aNativeKeyCode, aModifiers,
   724                              aChars, aUnmodifiedChars)
   725 {
   726   var utils = _getDOMWindowUtils(window);
   727   if (!utils) {
   728     return false;
   729   }
   730   var nativeKeyboardLayout = null;
   731   if (navigator.platform.indexOf("Mac") == 0) {
   732     nativeKeyboardLayout = aKeyboardLayout.Mac;
   733   } else if (navigator.platform.indexOf("Win") == 0) {
   734     nativeKeyboardLayout = aKeyboardLayout.Win;
   735   }
   736   if (nativeKeyboardLayout === null) {
   737     return false;
   738   }
   739   utils.sendNativeKeyEvent(nativeKeyboardLayout, aNativeKeyCode,
   740                            _parseNativeModifiers(aModifiers),
   741                            aChars, aUnmodifiedChars);
   742   return true;
   743 }
   745 var _gSeenEvent = false;
   747 /**
   748  * Indicate that an event with an original target of aExpectedTarget and
   749  * a type of aExpectedEvent is expected to be fired, or not expected to
   750  * be fired.
   751  */
   752 function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
   753 {
   754   if (!aExpectedTarget || !aExpectedEvent)
   755     return null;
   757   _gSeenEvent = false;
   759   var type = (aExpectedEvent.charAt(0) == "!") ?
   760              aExpectedEvent.substring(1) : aExpectedEvent;
   761   var eventHandler = function(event) {
   762     var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
   763                    event.type == type);
   764     is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
   765     _gSeenEvent = true;
   766   };
   768   aExpectedTarget.addEventListener(type, eventHandler, false);
   769   return eventHandler;
   770 }
   772 /**
   773  * Check if the event was fired or not. The event handler aEventHandler
   774  * will be removed.
   775  */
   776 function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
   777 {
   778   if (aEventHandler) {
   779     var expectEvent = (aExpectedEvent.charAt(0) != "!");
   780     var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
   781     aExpectedTarget.removeEventListener(type, aEventHandler, false);
   782     var desc = type + " event";
   783     if (!expectEvent)
   784       desc += " not";
   785     is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
   786   }
   788   _gSeenEvent = false;
   789 }
   791 /**
   792  * Similar to synthesizeMouse except that a test is performed to see if an
   793  * event is fired at the right target as a result.
   794  *
   795  * aExpectedTarget - the expected originalTarget of the event.
   796  * aExpectedEvent - the expected type of the event, such as 'select'.
   797  * aTestName - the test name when outputing results
   798  *
   799  * To test that an event is not fired, use an expected type preceded by an
   800  * exclamation mark, such as '!select'. This might be used to test that a
   801  * click on a disabled element doesn't fire certain events for instance.
   802  *
   803  * aWindow is optional, and defaults to the current window object.
   804  */
   805 function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
   806                                     aExpectedTarget, aExpectedEvent, aTestName,
   807                                     aWindow)
   808 {
   809   var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
   810   synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
   811   _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
   812 }
   814 /**
   815  * Similar to synthesizeKey except that a test is performed to see if an
   816  * event is fired at the right target as a result.
   817  *
   818  * aExpectedTarget - the expected originalTarget of the event.
   819  * aExpectedEvent - the expected type of the event, such as 'select'.
   820  * aTestName - the test name when outputing results
   821  *
   822  * To test that an event is not fired, use an expected type preceded by an
   823  * exclamation mark, such as '!select'.
   824  *
   825  * aWindow is optional, and defaults to the current window object.
   826  */
   827 function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
   828                                   aTestName, aWindow)
   829 {
   830   var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
   831   synthesizeKey(key, aEvent, aWindow);
   832   _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
   833 }
   835 function disableNonTestMouseEvents(aDisable)
   836 {
   837   var domutils = _getDOMWindowUtils();
   838   domutils.disableNonTestMouseEvents(aDisable);
   839 }
   841 function _getDOMWindowUtils(aWindow)
   842 {
   843   if (!aWindow) {
   844     aWindow = window;
   845   }
   847   // we need parent.SpecialPowers for:
   848   //  layout/base/tests/test_reftests_with_caret.html
   849   //  chrome: toolkit/content/tests/chrome/test_findbar.xul
   850   //  chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
   851   if ("SpecialPowers" in window && window.SpecialPowers != undefined) {
   852     return SpecialPowers.getDOMWindowUtils(aWindow);
   853   }
   854   if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) {
   855     return parent.SpecialPowers.getDOMWindowUtils(aWindow);
   856   }
   858   //TODO: this is assuming we are in chrome space
   859   return aWindow.QueryInterface(_EU_Ci.nsIInterfaceRequestor).
   860                                getInterface(_EU_Ci.nsIDOMWindowUtils);
   861 }
   863 // Must be synchronized with nsICompositionStringSynthesizer.
   864 const COMPOSITION_ATTR_RAWINPUT              = 0x02;
   865 const COMPOSITION_ATTR_SELECTEDRAWTEXT       = 0x03;
   866 const COMPOSITION_ATTR_CONVERTEDTEXT         = 0x04;
   867 const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
   869 /**
   870  * Synthesize a composition event.
   871  *
   872  * @param aEvent               The composition event information.  This must
   873  *                             have |type| member.  The value must be
   874  *                             "compositionstart", "compositionend" or
   875  *                             "compositionupdate".
   876  *                             And also this may have |data| and |locale| which
   877  *                             would be used for the value of each property of
   878  *                             the composition event.  Note that the data would
   879  *                             be ignored if the event type were
   880  *                             "compositionstart".
   881  * @param aWindow              Optional (If null, current |window| will be used)
   882  */
   883 function synthesizeComposition(aEvent, aWindow)
   884 {
   885   var utils = _getDOMWindowUtils(aWindow);
   886   if (!utils) {
   887     return;
   888   }
   890   utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
   891                              aEvent.locale ? aEvent.locale : "");
   892 }
   893 /**
   894  * Synthesize a text event.
   895  *
   896  * @param aEvent   The text event's information, this has |composition|
   897  *                 and |caret| members.  |composition| has |string| and
   898  *                 |clauses| members.  |clauses| must be array object.  Each
   899  *                 object has |length| and |attr|.  And |caret| has |start| and
   900  *                 |length|.  See the following tree image.
   901  *
   902  *                 aEvent
   903  *                   +-- composition
   904  *                   |     +-- string
   905  *                   |     +-- clauses[]
   906  *                   |           +-- length
   907  *                   |           +-- attr
   908  *                   +-- caret
   909  *                         +-- start
   910  *                         +-- length
   911  *
   912  *                 Set the composition string to |composition.string|.  Set its
   913  *                 clauses information to the |clauses| array.
   914  *
   915  *                 When it's composing, set the each clauses' length to the
   916  *                 |composition.clauses[n].length|.  The sum of the all length
   917  *                 values must be same as the length of |composition.string|.
   918  *                 Set nsICompositionStringSynthesizer.ATTR_* to the
   919  *                 |composition.clauses[n].attr|.
   920  *
   921  *                 When it's not composing, set 0 to the
   922  *                 |composition.clauses[0].length| and
   923  *                 |composition.clauses[0].attr|.
   924  *
   925  *                 Set caret position to the |caret.start|. It's offset from
   926  *                 the start of the composition string.  Set caret length to
   927  *                 |caret.length|.  If it's larger than 0, it should be wide
   928  *                 caret.  However, current nsEditor doesn't support wide
   929  *                 caret, therefore, you should always set 0 now.
   930  *
   931  * @param aWindow  Optional (If null, current |window| will be used)
   932  */
   933 function synthesizeText(aEvent, aWindow)
   934 {
   935   var utils = _getDOMWindowUtils(aWindow);
   936   if (!utils) {
   937     return;
   938   }
   940   if (!aEvent.composition || !aEvent.composition.clauses ||
   941       !aEvent.composition.clauses[0]) {
   942     return;
   943   }
   945   var compositionString = utils.createCompositionStringSynthesizer();
   946   compositionString.setString(aEvent.composition.string);
   947   if (aEvent.composition.clauses[0].length) {
   948     for (var i = 0; i < aEvent.composition.clauses.length; i++) {
   949       switch (aEvent.composition.clauses[i].attr) {
   950         case compositionString.ATTR_RAWINPUT:
   951         case compositionString.ATTR_SELECTEDRAWTEXT:
   952         case compositionString.ATTR_CONVERTEDTEXT:
   953         case compositionString.ATTR_SELECTEDCONVERTEDTEXT:
   954           compositionString.appendClause(aEvent.composition.clauses[i].length,
   955                                          aEvent.composition.clauses[i].attr);
   956           break;
   957         case 0:
   958           // Ignore dummy clause for the argument.
   959           break;
   960         default:
   961           throw new Error("invalid clause attribute specified");
   962           break;
   963       }
   964     }
   965   }
   967   if (aEvent.caret) {
   968     compositionString.setCaret(aEvent.caret.start, aEvent.caret.length);
   969   }
   971   compositionString.dispatchEvent();
   972 }
   974 // Must be synchronized with nsIDOMWindowUtils.
   975 const QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
   976 const QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK              = 0x0001;
   978 const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
   979 const SELECTION_SET_FLAG_USE_XP_LINE_BREAK              = 0x0001;
   980 const SELECTION_SET_FLAG_REVERSE                        = 0x0002;
   982 /**
   983  * Synthesize a query selected text event.
   984  *
   985  * @param aWindow  Optional (If null, current |window| will be used)
   986  * @return         An nsIQueryContentEventResult object.  If this failed,
   987  *                 the result might be null.
   988  */
   989 function synthesizeQuerySelectedText(aWindow)
   990 {
   991   var utils = _getDOMWindowUtils(aWindow);
   992   if (!utils) {
   993     return null;
   994   }
   996   return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0,
   997                                      QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
   998 }
  1000 /**
  1001  * Synthesize a query caret rect event.
  1003  * @param aOffset  The caret offset.  0 means left side of the first character
  1004  *                 in the selection root.
  1005  * @param aWindow  Optional (If null, current |window| will be used)
  1006  * @return         An nsIQueryContentEventResult object.  If this failed,
  1007  *                 the result might be null.
  1008  */
  1009 function synthesizeQueryCaretRect(aOffset, aWindow)
  1011   var utils = _getDOMWindowUtils(aWindow);
  1012   if (!utils) {
  1013     return null;
  1015   return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
  1016                                      aOffset, 0, 0, 0,
  1017                                      QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK);
  1020 /**
  1021  * Synthesize a selection set event.
  1023  * @param aOffset  The character offset.  0 means the first character in the
  1024  *                 selection root.
  1025  * @param aLength  The length of the text.  If the length is too long,
  1026  *                 the extra length is ignored.
  1027  * @param aReverse If true, the selection is from |aOffset + aLength| to
  1028  *                 |aOffset|.  Otherwise, from |aOffset| to |aOffset + aLength|.
  1029  * @param aWindow  Optional (If null, current |window| will be used)
  1030  * @return         True, if succeeded.  Otherwise false.
  1031  */
  1032 function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
  1034   var utils = _getDOMWindowUtils(aWindow);
  1035   if (!utils) {
  1036     return false;
  1038   var flags = aReverse ? SELECTION_SET_FLAG_REVERSE : 0;
  1039   return utils.sendSelectionSetEvent(aOffset, aLength, flags);

mercurial