services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 // Export all available functions for Mozmill
michael@0 2 var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar",
michael@0 3 "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch",
michael@0 4 "synthesizeMouseAtPoint", "synthesizeTouchAtPoint",
michael@0 5 "synthesizeMouseAtCenter", "synthesizeTouchAtCenter",
michael@0 6 "synthesizeWheel", "synthesizeKey",
michael@0 7 "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
michael@0 8 "synthesizeText",
michael@0 9 "synthesizeComposition", "synthesizeQuerySelectedText"];
michael@0 10
michael@0 11 const Ci = Components.interfaces;
michael@0 12 const Cc = Components.classes;
michael@0 13
michael@0 14 var window = Cc["@mozilla.org/appshell/appShellService;1"]
michael@0 15 .getService(Ci.nsIAppShellService).hiddenDOMWindow;
michael@0 16
michael@0 17 var _EU_Ci = Ci;
michael@0 18 var navigator = window.navigator;
michael@0 19 var KeyEvent = window.KeyEvent;
michael@0 20 var parent = window.parent;
michael@0 21
michael@0 22 function is(aExpression1, aExpression2, aMessage) {
michael@0 23 if (aExpression1 !== aExpression2) {
michael@0 24 throw new Error(aMessage);
michael@0 25 }
michael@0 26 }
michael@0 27
michael@0 28 /**
michael@0 29 * EventUtils provides some utility methods for creating and sending DOM events.
michael@0 30 * Current methods:
michael@0 31 * sendMouseEvent
michael@0 32 * sendChar
michael@0 33 * sendString
michael@0 34 * sendKey
michael@0 35 * synthesizeMouse
michael@0 36 * synthesizeMouseAtCenter
michael@0 37 * synthesizeWheel
michael@0 38 * synthesizeKey
michael@0 39 * synthesizeMouseExpectEvent
michael@0 40 * synthesizeKeyExpectEvent
michael@0 41 *
michael@0 42 * When adding methods to this file, please add a performance test for it.
michael@0 43 */
michael@0 44
michael@0 45 /**
michael@0 46 * Send a mouse event to the node aTarget (aTarget can be an id, or an
michael@0 47 * actual node) . The "event" passed in to aEvent is just a JavaScript
michael@0 48 * object with the properties set that the real mouse event object should
michael@0 49 * have. This includes the type of the mouse event.
michael@0 50 * E.g. to send an click event to the node with id 'node' you might do this:
michael@0 51 *
michael@0 52 * sendMouseEvent({type:'click'}, 'node');
michael@0 53 */
michael@0 54 function getElement(id) {
michael@0 55 return ((typeof(id) == "string") ?
michael@0 56 document.getElementById(id) : id);
michael@0 57 };
michael@0 58
michael@0 59 this.$ = this.getElement;
michael@0 60
michael@0 61 function sendMouseEvent(aEvent, aTarget, aWindow) {
michael@0 62 if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
michael@0 63 throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
michael@0 64 }
michael@0 65
michael@0 66 if (!aWindow) {
michael@0 67 aWindow = window;
michael@0 68 }
michael@0 69
michael@0 70 if (!(aTarget instanceof aWindow.Element)) {
michael@0 71 aTarget = aWindow.document.getElementById(aTarget);
michael@0 72 }
michael@0 73
michael@0 74 var event = aWindow.document.createEvent('MouseEvent');
michael@0 75
michael@0 76 var typeArg = aEvent.type;
michael@0 77 var canBubbleArg = true;
michael@0 78 var cancelableArg = true;
michael@0 79 var viewArg = aWindow;
michael@0 80 var detailArg = aEvent.detail || (aEvent.type == 'click' ||
michael@0 81 aEvent.type == 'mousedown' ||
michael@0 82 aEvent.type == 'mouseup' ? 1 :
michael@0 83 aEvent.type == 'dblclick'? 2 : 0);
michael@0 84 var screenXArg = aEvent.screenX || 0;
michael@0 85 var screenYArg = aEvent.screenY || 0;
michael@0 86 var clientXArg = aEvent.clientX || 0;
michael@0 87 var clientYArg = aEvent.clientY || 0;
michael@0 88 var ctrlKeyArg = aEvent.ctrlKey || false;
michael@0 89 var altKeyArg = aEvent.altKey || false;
michael@0 90 var shiftKeyArg = aEvent.shiftKey || false;
michael@0 91 var metaKeyArg = aEvent.metaKey || false;
michael@0 92 var buttonArg = aEvent.button || 0;
michael@0 93 var relatedTargetArg = aEvent.relatedTarget || null;
michael@0 94
michael@0 95 event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
michael@0 96 screenXArg, screenYArg, clientXArg, clientYArg,
michael@0 97 ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
michael@0 98 buttonArg, relatedTargetArg);
michael@0 99
michael@0 100 SpecialPowers.dispatchEvent(aWindow, aTarget, event);
michael@0 101 }
michael@0 102
michael@0 103 /**
michael@0 104 * Send the char aChar to the focused element. This method handles casing of
michael@0 105 * chars (sends the right charcode, and sends a shift key for uppercase chars).
michael@0 106 * No other modifiers are handled at this point.
michael@0 107 *
michael@0 108 * For now this method only works for ASCII characters and emulates the shift
michael@0 109 * key state on US keyboard layout.
michael@0 110 */
michael@0 111 function sendChar(aChar, aWindow) {
michael@0 112 var hasShift;
michael@0 113 // Emulate US keyboard layout for the shiftKey state.
michael@0 114 switch (aChar) {
michael@0 115 case "!":
michael@0 116 case "@":
michael@0 117 case "#":
michael@0 118 case "$":
michael@0 119 case "%":
michael@0 120 case "^":
michael@0 121 case "&":
michael@0 122 case "*":
michael@0 123 case "(":
michael@0 124 case ")":
michael@0 125 case "_":
michael@0 126 case "+":
michael@0 127 case "{":
michael@0 128 case "}":
michael@0 129 case ":":
michael@0 130 case "\"":
michael@0 131 case "|":
michael@0 132 case "<":
michael@0 133 case ">":
michael@0 134 case "?":
michael@0 135 hasShift = true;
michael@0 136 break;
michael@0 137 default:
michael@0 138 hasShift = (aChar == aChar.toUpperCase());
michael@0 139 break;
michael@0 140 }
michael@0 141 synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
michael@0 142 }
michael@0 143
michael@0 144 /**
michael@0 145 * Send the string aStr to the focused element.
michael@0 146 *
michael@0 147 * For now this method only works for ASCII characters and emulates the shift
michael@0 148 * key state on US keyboard layout.
michael@0 149 */
michael@0 150 function sendString(aStr, aWindow) {
michael@0 151 for (var i = 0; i < aStr.length; ++i) {
michael@0 152 sendChar(aStr.charAt(i), aWindow);
michael@0 153 }
michael@0 154 }
michael@0 155
michael@0 156 /**
michael@0 157 * Send the non-character key aKey to the focused node.
michael@0 158 * The name of the key should be the part that comes after "DOM_VK_" in the
michael@0 159 * KeyEvent constant name for this key.
michael@0 160 * No modifiers are handled at this point.
michael@0 161 */
michael@0 162 function sendKey(aKey, aWindow) {
michael@0 163 var keyName = "VK_" + aKey.toUpperCase();
michael@0 164 synthesizeKey(keyName, { shiftKey: false }, aWindow);
michael@0 165 }
michael@0 166
michael@0 167 /**
michael@0 168 * Parse the key modifier flags from aEvent. Used to share code between
michael@0 169 * synthesizeMouse and synthesizeKey.
michael@0 170 */
michael@0 171 function _parseModifiers(aEvent)
michael@0 172 {
michael@0 173 const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
michael@0 174 var mval = 0;
michael@0 175 if (aEvent.shiftKey) {
michael@0 176 mval |= nsIDOMWindowUtils.MODIFIER_SHIFT;
michael@0 177 }
michael@0 178 if (aEvent.ctrlKey) {
michael@0 179 mval |= nsIDOMWindowUtils.MODIFIER_CONTROL;
michael@0 180 }
michael@0 181 if (aEvent.altKey) {
michael@0 182 mval |= nsIDOMWindowUtils.MODIFIER_ALT;
michael@0 183 }
michael@0 184 if (aEvent.metaKey) {
michael@0 185 mval |= nsIDOMWindowUtils.MODIFIER_META;
michael@0 186 }
michael@0 187 if (aEvent.accelKey) {
michael@0 188 mval |= (navigator.platform.indexOf("Mac") >= 0) ?
michael@0 189 nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL;
michael@0 190 }
michael@0 191 if (aEvent.altGrKey) {
michael@0 192 mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH;
michael@0 193 }
michael@0 194 if (aEvent.capsLockKey) {
michael@0 195 mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK;
michael@0 196 }
michael@0 197 if (aEvent.fnKey) {
michael@0 198 mval |= nsIDOMWindowUtils.MODIFIER_FN;
michael@0 199 }
michael@0 200 if (aEvent.numLockKey) {
michael@0 201 mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK;
michael@0 202 }
michael@0 203 if (aEvent.scrollLockKey) {
michael@0 204 mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK;
michael@0 205 }
michael@0 206 if (aEvent.symbolLockKey) {
michael@0 207 mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK;
michael@0 208 }
michael@0 209 if (aEvent.osKey) {
michael@0 210 mval |= nsIDOMWindowUtils.MODIFIER_OS;
michael@0 211 }
michael@0 212
michael@0 213 return mval;
michael@0 214 }
michael@0 215
michael@0 216 /**
michael@0 217 * Synthesize a mouse event on a target. The actual client point is determined
michael@0 218 * by taking the aTarget's client box and offseting it by aOffsetX and
michael@0 219 * aOffsetY. This allows mouse clicks to be simulated by calling this method.
michael@0 220 *
michael@0 221 * aEvent is an object which may contain the properties:
michael@0 222 * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
michael@0 223 *
michael@0 224 * If the type is specified, an mouse event of that type is fired. Otherwise,
michael@0 225 * a mousedown followed by a mouse up is performed.
michael@0 226 *
michael@0 227 * aWindow is optional, and defaults to the current window object.
michael@0 228 *
michael@0 229 * Returns whether the event had preventDefault() called on it.
michael@0 230 */
michael@0 231 function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
michael@0 232 {
michael@0 233 var rect = aTarget.getBoundingClientRect();
michael@0 234 return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
michael@0 235 aEvent, aWindow);
michael@0 236 }
michael@0 237 function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
michael@0 238 {
michael@0 239 var rect = aTarget.getBoundingClientRect();
michael@0 240 synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
michael@0 241 aEvent, aWindow);
michael@0 242 }
michael@0 243
michael@0 244 /*
michael@0 245 * Synthesize a mouse event at a particular point in aWindow.
michael@0 246 *
michael@0 247 * aEvent is an object which may contain the properties:
michael@0 248 * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
michael@0 249 *
michael@0 250 * If the type is specified, an mouse event of that type is fired. Otherwise,
michael@0 251 * a mousedown followed by a mouse up is performed.
michael@0 252 *
michael@0 253 * aWindow is optional, and defaults to the current window object.
michael@0 254 */
michael@0 255 function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
michael@0 256 {
michael@0 257 var utils = _getDOMWindowUtils(aWindow);
michael@0 258 var defaultPrevented = false;
michael@0 259
michael@0 260 if (utils) {
michael@0 261 var button = aEvent.button || 0;
michael@0 262 var clickCount = aEvent.clickCount || 1;
michael@0 263 var modifiers = _parseModifiers(aEvent);
michael@0 264 var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
michael@0 265 var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
michael@0 266
michael@0 267 if (("type" in aEvent) && aEvent.type) {
michael@0 268 defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
michael@0 269 }
michael@0 270 else {
michael@0 271 utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
michael@0 272 utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
michael@0 273 }
michael@0 274 }
michael@0 275
michael@0 276 return defaultPrevented;
michael@0 277 }
michael@0 278 function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
michael@0 279 {
michael@0 280 var utils = _getDOMWindowUtils(aWindow);
michael@0 281
michael@0 282 if (utils) {
michael@0 283 var id = aEvent.id || 0;
michael@0 284 var rx = aEvent.rx || 1;
michael@0 285 var ry = aEvent.rx || 1;
michael@0 286 var angle = aEvent.angle || 0;
michael@0 287 var force = aEvent.force || 1;
michael@0 288 var modifiers = _parseModifiers(aEvent);
michael@0 289
michael@0 290 if (("type" in aEvent) && aEvent.type) {
michael@0 291 utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
michael@0 292 }
michael@0 293 else {
michael@0 294 utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
michael@0 295 utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
michael@0 296 }
michael@0 297 }
michael@0 298 }
michael@0 299 // Call synthesizeMouse with coordinates at the center of aTarget.
michael@0 300 function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
michael@0 301 {
michael@0 302 var rect = aTarget.getBoundingClientRect();
michael@0 303 synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
michael@0 304 aWindow);
michael@0 305 }
michael@0 306 function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)
michael@0 307 {
michael@0 308 var rect = aTarget.getBoundingClientRect();
michael@0 309 synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent,
michael@0 310 aWindow);
michael@0 311 }
michael@0 312
michael@0 313 /**
michael@0 314 * Synthesize a wheel event on a target. The actual client point is determined
michael@0 315 * by taking the aTarget's client box and offseting it by aOffsetX and
michael@0 316 * aOffsetY.
michael@0 317 *
michael@0 318 * aEvent is an object which may contain the properties:
michael@0 319 * shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
michael@0 320 * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice,
michael@0 321 * isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY
michael@0 322 *
michael@0 323 * deltaMode must be defined, others are ok even if undefined.
michael@0 324 *
michael@0 325 * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The
michael@0 326 * value is just checked as 0 or positive or negative.
michael@0 327 *
michael@0 328 * aWindow is optional, and defaults to the current window object.
michael@0 329 */
michael@0 330 function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
michael@0 331 {
michael@0 332 var utils = _getDOMWindowUtils(aWindow);
michael@0 333 if (!utils) {
michael@0 334 return;
michael@0 335 }
michael@0 336
michael@0 337 var modifiers = _parseModifiers(aEvent);
michael@0 338 var options = 0;
michael@0 339 if (aEvent.isPixelOnlyDevice &&
michael@0 340 (aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) {
michael@0 341 options |= utils.WHEEL_EVENT_CAUSED_BY_PIXEL_ONLY_DEVICE;
michael@0 342 }
michael@0 343 if (aEvent.isMomentum) {
michael@0 344 options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM;
michael@0 345 }
michael@0 346 if (aEvent.isCustomizedByPrefs) {
michael@0 347 options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS;
michael@0 348 }
michael@0 349 if (typeof aEvent.expectedOverflowDeltaX !== "undefined") {
michael@0 350 if (aEvent.expectedOverflowDeltaX === 0) {
michael@0 351 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO;
michael@0 352 } else if (aEvent.expectedOverflowDeltaX > 0) {
michael@0 353 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE;
michael@0 354 } else {
michael@0 355 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE;
michael@0 356 }
michael@0 357 }
michael@0 358 if (typeof aEvent.expectedOverflowDeltaY !== "undefined") {
michael@0 359 if (aEvent.expectedOverflowDeltaY === 0) {
michael@0 360 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO;
michael@0 361 } else if (aEvent.expectedOverflowDeltaY > 0) {
michael@0 362 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE;
michael@0 363 } else {
michael@0 364 options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE;
michael@0 365 }
michael@0 366 }
michael@0 367 var isPixelOnlyDevice =
michael@0 368 aEvent.isPixelOnlyDevice && aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL;
michael@0 369
michael@0 370 // Avoid the JS warnings "reference to undefined property"
michael@0 371 if (!aEvent.deltaX) {
michael@0 372 aEvent.deltaX = 0;
michael@0 373 }
michael@0 374 if (!aEvent.deltaY) {
michael@0 375 aEvent.deltaY = 0;
michael@0 376 }
michael@0 377 if (!aEvent.deltaZ) {
michael@0 378 aEvent.deltaZ = 0;
michael@0 379 }
michael@0 380
michael@0 381 var lineOrPageDeltaX =
michael@0 382 aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX :
michael@0 383 aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) :
michael@0 384 Math.ceil(aEvent.deltaX);
michael@0 385 var lineOrPageDeltaY =
michael@0 386 aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY :
michael@0 387 aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) :
michael@0 388 Math.ceil(aEvent.deltaY);
michael@0 389
michael@0 390 var rect = aTarget.getBoundingClientRect();
michael@0 391 utils.sendWheelEvent(rect.left + aOffsetX, rect.top + aOffsetY,
michael@0 392 aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ,
michael@0 393 aEvent.deltaMode, modifiers,
michael@0 394 lineOrPageDeltaX, lineOrPageDeltaY, options);
michael@0 395 }
michael@0 396
michael@0 397 function _computeKeyCodeFromChar(aChar)
michael@0 398 {
michael@0 399 if (aChar.length != 1) {
michael@0 400 return 0;
michael@0 401 }
michael@0 402 const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent;
michael@0 403 if (aChar >= 'a' && aChar <= 'z') {
michael@0 404 return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
michael@0 405 }
michael@0 406 if (aChar >= 'A' && aChar <= 'Z') {
michael@0 407 return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
michael@0 408 }
michael@0 409 if (aChar >= '0' && aChar <= '9') {
michael@0 410 return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
michael@0 411 }
michael@0 412 // returns US keyboard layout's keycode
michael@0 413 switch (aChar) {
michael@0 414 case '~':
michael@0 415 case '`':
michael@0 416 return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
michael@0 417 case '!':
michael@0 418 return nsIDOMKeyEvent.DOM_VK_1;
michael@0 419 case '@':
michael@0 420 return nsIDOMKeyEvent.DOM_VK_2;
michael@0 421 case '#':
michael@0 422 return nsIDOMKeyEvent.DOM_VK_3;
michael@0 423 case '$':
michael@0 424 return nsIDOMKeyEvent.DOM_VK_4;
michael@0 425 case '%':
michael@0 426 return nsIDOMKeyEvent.DOM_VK_5;
michael@0 427 case '^':
michael@0 428 return nsIDOMKeyEvent.DOM_VK_6;
michael@0 429 case '&':
michael@0 430 return nsIDOMKeyEvent.DOM_VK_7;
michael@0 431 case '*':
michael@0 432 return nsIDOMKeyEvent.DOM_VK_8;
michael@0 433 case '(':
michael@0 434 return nsIDOMKeyEvent.DOM_VK_9;
michael@0 435 case ')':
michael@0 436 return nsIDOMKeyEvent.DOM_VK_0;
michael@0 437 case '-':
michael@0 438 case '_':
michael@0 439 return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
michael@0 440 case '+':
michael@0 441 case '=':
michael@0 442 return nsIDOMKeyEvent.DOM_VK_EQUALS;
michael@0 443 case '{':
michael@0 444 case '[':
michael@0 445 return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
michael@0 446 case '}':
michael@0 447 case ']':
michael@0 448 return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
michael@0 449 case '|':
michael@0 450 case '\\':
michael@0 451 return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
michael@0 452 case ':':
michael@0 453 case ';':
michael@0 454 return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
michael@0 455 case '\'':
michael@0 456 case '"':
michael@0 457 return nsIDOMKeyEvent.DOM_VK_QUOTE;
michael@0 458 case '<':
michael@0 459 case ',':
michael@0 460 return nsIDOMKeyEvent.DOM_VK_COMMA;
michael@0 461 case '>':
michael@0 462 case '.':
michael@0 463 return nsIDOMKeyEvent.DOM_VK_PERIOD;
michael@0 464 case '?':
michael@0 465 case '/':
michael@0 466 return nsIDOMKeyEvent.DOM_VK_SLASH;
michael@0 467 default:
michael@0 468 return 0;
michael@0 469 }
michael@0 470 }
michael@0 471
michael@0 472 /**
michael@0 473 * isKeypressFiredKey() returns TRUE if the given key should cause keypress
michael@0 474 * event when widget handles the native key event. Otherwise, FALSE.
michael@0 475 *
michael@0 476 * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
michael@0 477 * name begins with "VK_", or a character.
michael@0 478 */
michael@0 479 function isKeypressFiredKey(aDOMKeyCode)
michael@0 480 {
michael@0 481 if (typeof(aDOMKeyCode) == "string") {
michael@0 482 if (aDOMKeyCode.indexOf("VK_") == 0) {
michael@0 483 aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
michael@0 484 if (!aDOMKeyCode) {
michael@0 485 throw "Unknown key: " + aDOMKeyCode;
michael@0 486 }
michael@0 487 } else {
michael@0 488 // If the key generates a character, it must cause a keypress event.
michael@0 489 return true;
michael@0 490 }
michael@0 491 }
michael@0 492 switch (aDOMKeyCode) {
michael@0 493 case KeyEvent.DOM_VK_SHIFT:
michael@0 494 case KeyEvent.DOM_VK_CONTROL:
michael@0 495 case KeyEvent.DOM_VK_ALT:
michael@0 496 case KeyEvent.DOM_VK_CAPS_LOCK:
michael@0 497 case KeyEvent.DOM_VK_NUM_LOCK:
michael@0 498 case KeyEvent.DOM_VK_SCROLL_LOCK:
michael@0 499 case KeyEvent.DOM_VK_META:
michael@0 500 return false;
michael@0 501 default:
michael@0 502 return true;
michael@0 503 }
michael@0 504 }
michael@0 505
michael@0 506 /**
michael@0 507 * Synthesize a key event. It is targeted at whatever would be targeted by an
michael@0 508 * actual keypress by the user, typically the focused element.
michael@0 509 *
michael@0 510 * aKey should be either a character or a keycode starting with VK_ such as
michael@0 511 * VK_ENTER.
michael@0 512 *
michael@0 513 * aEvent is an object which may contain the properties:
michael@0 514 * shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location
michael@0 515 *
michael@0 516 * Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location. Otherwise,
michael@0 517 * DOMWindowUtils will choose good location from the keycode.
michael@0 518 *
michael@0 519 * If the type is specified, a key event of that type is fired. Otherwise,
michael@0 520 * a keydown, a keypress and then a keyup event are fired in sequence.
michael@0 521 *
michael@0 522 * aWindow is optional, and defaults to the current window object.
michael@0 523 */
michael@0 524 function synthesizeKey(aKey, aEvent, aWindow)
michael@0 525 {
michael@0 526 var utils = _getDOMWindowUtils(aWindow);
michael@0 527 if (utils) {
michael@0 528 var keyCode = 0, charCode = 0;
michael@0 529 if (aKey.indexOf("VK_") == 0) {
michael@0 530 keyCode = KeyEvent["DOM_" + aKey];
michael@0 531 if (!keyCode) {
michael@0 532 throw "Unknown key: " + aKey;
michael@0 533 }
michael@0 534 } else {
michael@0 535 charCode = aKey.charCodeAt(0);
michael@0 536 keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
michael@0 537 }
michael@0 538
michael@0 539 var modifiers = _parseModifiers(aEvent);
michael@0 540 var flags = 0;
michael@0 541 if (aEvent.location != undefined) {
michael@0 542 switch (aEvent.location) {
michael@0 543 case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
michael@0 544 flags |= utils.KEY_FLAG_LOCATION_STANDARD;
michael@0 545 break;
michael@0 546 case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
michael@0 547 flags |= utils.KEY_FLAG_LOCATION_LEFT;
michael@0 548 break;
michael@0 549 case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
michael@0 550 flags |= utils.KEY_FLAG_LOCATION_RIGHT;
michael@0 551 break;
michael@0 552 case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
michael@0 553 flags |= utils.KEY_FLAG_LOCATION_NUMPAD;
michael@0 554 break;
michael@0 555 case KeyboardEvent.DOM_KEY_LOCATION_MOBILE:
michael@0 556 flags |= utils.KEY_FLAG_LOCATION_MOBILE;
michael@0 557 break;
michael@0 558 case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK:
michael@0 559 flags |= utils.KEY_FLAG_LOCATION_JOYSTICK;
michael@0 560 break;
michael@0 561 }
michael@0 562 }
michael@0 563
michael@0 564 if (!("type" in aEvent) || !aEvent.type) {
michael@0 565 // Send keydown + (optional) keypress + keyup events.
michael@0 566 var keyDownDefaultHappened =
michael@0 567 utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags);
michael@0 568 if (isKeypressFiredKey(keyCode)) {
michael@0 569 if (!keyDownDefaultHappened) {
michael@0 570 flags |= utils.KEY_FLAG_PREVENT_DEFAULT;
michael@0 571 }
michael@0 572 utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags);
michael@0 573 }
michael@0 574 utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags);
michael@0 575 } else if (aEvent.type == "keypress") {
michael@0 576 // Send standalone keypress event.
michael@0 577 utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags);
michael@0 578 } else {
michael@0 579 // Send other standalone event than keypress.
michael@0 580 utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags);
michael@0 581 }
michael@0 582 }
michael@0 583 }
michael@0 584
michael@0 585 var _gSeenEvent = false;
michael@0 586
michael@0 587 /**
michael@0 588 * Indicate that an event with an original target of aExpectedTarget and
michael@0 589 * a type of aExpectedEvent is expected to be fired, or not expected to
michael@0 590 * be fired.
michael@0 591 */
michael@0 592 function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
michael@0 593 {
michael@0 594 if (!aExpectedTarget || !aExpectedEvent)
michael@0 595 return null;
michael@0 596
michael@0 597 _gSeenEvent = false;
michael@0 598
michael@0 599 var type = (aExpectedEvent.charAt(0) == "!") ?
michael@0 600 aExpectedEvent.substring(1) : aExpectedEvent;
michael@0 601 var eventHandler = function(event) {
michael@0 602 var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
michael@0 603 event.type == type);
michael@0 604 is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
michael@0 605 _gSeenEvent = true;
michael@0 606 };
michael@0 607
michael@0 608 aExpectedTarget.addEventListener(type, eventHandler, false);
michael@0 609 return eventHandler;
michael@0 610 }
michael@0 611
michael@0 612 /**
michael@0 613 * Check if the event was fired or not. The event handler aEventHandler
michael@0 614 * will be removed.
michael@0 615 */
michael@0 616 function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
michael@0 617 {
michael@0 618 if (aEventHandler) {
michael@0 619 var expectEvent = (aExpectedEvent.charAt(0) != "!");
michael@0 620 var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
michael@0 621 aExpectedTarget.removeEventListener(type, aEventHandler, false);
michael@0 622 var desc = type + " event";
michael@0 623 if (!expectEvent)
michael@0 624 desc += " not";
michael@0 625 is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
michael@0 626 }
michael@0 627
michael@0 628 _gSeenEvent = false;
michael@0 629 }
michael@0 630
michael@0 631 /**
michael@0 632 * Similar to synthesizeMouse except that a test is performed to see if an
michael@0 633 * event is fired at the right target as a result.
michael@0 634 *
michael@0 635 * aExpectedTarget - the expected originalTarget of the event.
michael@0 636 * aExpectedEvent - the expected type of the event, such as 'select'.
michael@0 637 * aTestName - the test name when outputing results
michael@0 638 *
michael@0 639 * To test that an event is not fired, use an expected type preceded by an
michael@0 640 * exclamation mark, such as '!select'. This might be used to test that a
michael@0 641 * click on a disabled element doesn't fire certain events for instance.
michael@0 642 *
michael@0 643 * aWindow is optional, and defaults to the current window object.
michael@0 644 */
michael@0 645 function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
michael@0 646 aExpectedTarget, aExpectedEvent, aTestName,
michael@0 647 aWindow)
michael@0 648 {
michael@0 649 var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
michael@0 650 synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
michael@0 651 _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
michael@0 652 }
michael@0 653
michael@0 654 /**
michael@0 655 * Similar to synthesizeKey except that a test is performed to see if an
michael@0 656 * event is fired at the right target as a result.
michael@0 657 *
michael@0 658 * aExpectedTarget - the expected originalTarget of the event.
michael@0 659 * aExpectedEvent - the expected type of the event, such as 'select'.
michael@0 660 * aTestName - the test name when outputing results
michael@0 661 *
michael@0 662 * To test that an event is not fired, use an expected type preceded by an
michael@0 663 * exclamation mark, such as '!select'.
michael@0 664 *
michael@0 665 * aWindow is optional, and defaults to the current window object.
michael@0 666 */
michael@0 667 function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
michael@0 668 aTestName, aWindow)
michael@0 669 {
michael@0 670 var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
michael@0 671 synthesizeKey(key, aEvent, aWindow);
michael@0 672 _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
michael@0 673 }
michael@0 674
michael@0 675 function disableNonTestMouseEvents(aDisable)
michael@0 676 {
michael@0 677 var domutils = _getDOMWindowUtils();
michael@0 678 domutils.disableNonTestMouseEvents(aDisable);
michael@0 679 }
michael@0 680
michael@0 681 function _getDOMWindowUtils(aWindow)
michael@0 682 {
michael@0 683 if (!aWindow) {
michael@0 684 aWindow = window;
michael@0 685 }
michael@0 686
michael@0 687 // we need parent.SpecialPowers for:
michael@0 688 // layout/base/tests/test_reftests_with_caret.html
michael@0 689 // chrome: toolkit/content/tests/chrome/test_findbar.xul
michael@0 690 // chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
michael@0 691 if ("SpecialPowers" in window && window.SpecialPowers != undefined) {
michael@0 692 return SpecialPowers.getDOMWindowUtils(aWindow);
michael@0 693 }
michael@0 694 if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) {
michael@0 695 return parent.SpecialPowers.getDOMWindowUtils(aWindow);
michael@0 696 }
michael@0 697
michael@0 698 //TODO: this is assuming we are in chrome space
michael@0 699 return aWindow.QueryInterface(_EU_Ci.nsIInterfaceRequestor).
michael@0 700 getInterface(_EU_Ci.nsIDOMWindowUtils);
michael@0 701 }
michael@0 702
michael@0 703 // Must be synchronized with nsIDOMWindowUtils.
michael@0 704 const COMPOSITION_ATTR_RAWINPUT = 0x02;
michael@0 705 const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
michael@0 706 const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
michael@0 707 const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
michael@0 708
michael@0 709 /**
michael@0 710 * Synthesize a composition event.
michael@0 711 *
michael@0 712 * @param aEvent The composition event information. This must
michael@0 713 * have |type| member. The value must be
michael@0 714 * "compositionstart", "compositionend" or
michael@0 715 * "compositionupdate".
michael@0 716 * And also this may have |data| and |locale| which
michael@0 717 * would be used for the value of each property of
michael@0 718 * the composition event. Note that the data would
michael@0 719 * be ignored if the event type were
michael@0 720 * "compositionstart".
michael@0 721 * @param aWindow Optional (If null, current |window| will be used)
michael@0 722 */
michael@0 723 function synthesizeComposition(aEvent, aWindow)
michael@0 724 {
michael@0 725 var utils = _getDOMWindowUtils(aWindow);
michael@0 726 if (!utils) {
michael@0 727 return;
michael@0 728 }
michael@0 729
michael@0 730 utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
michael@0 731 aEvent.locale ? aEvent.locale : "");
michael@0 732 }
michael@0 733 /**
michael@0 734 * Synthesize a text event.
michael@0 735 *
michael@0 736 * @param aEvent The text event's information, this has |composition|
michael@0 737 * and |caret| members. |composition| has |string| and
michael@0 738 * |clauses| members. |clauses| must be array object. Each
michael@0 739 * object has |length| and |attr|. And |caret| has |start| and
michael@0 740 * |length|. See the following tree image.
michael@0 741 *
michael@0 742 * aEvent
michael@0 743 * +-- composition
michael@0 744 * | +-- string
michael@0 745 * | +-- clauses[]
michael@0 746 * | +-- length
michael@0 747 * | +-- attr
michael@0 748 * +-- caret
michael@0 749 * +-- start
michael@0 750 * +-- length
michael@0 751 *
michael@0 752 * Set the composition string to |composition.string|. Set its
michael@0 753 * clauses information to the |clauses| array.
michael@0 754 *
michael@0 755 * When it's composing, set the each clauses' length to the
michael@0 756 * |composition.clauses[n].length|. The sum of the all length
michael@0 757 * values must be same as the length of |composition.string|.
michael@0 758 * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
michael@0 759 * |composition.clauses[n].attr|.
michael@0 760 *
michael@0 761 * When it's not composing, set 0 to the
michael@0 762 * |composition.clauses[0].length| and
michael@0 763 * |composition.clauses[0].attr|.
michael@0 764 *
michael@0 765 * Set caret position to the |caret.start|. It's offset from
michael@0 766 * the start of the composition string. Set caret length to
michael@0 767 * |caret.length|. If it's larger than 0, it should be wide
michael@0 768 * caret. However, current nsEditor doesn't support wide
michael@0 769 * caret, therefore, you should always set 0 now.
michael@0 770 *
michael@0 771 * @param aWindow Optional (If null, current |window| will be used)
michael@0 772 */
michael@0 773 function synthesizeText(aEvent, aWindow)
michael@0 774 {
michael@0 775 var utils = _getDOMWindowUtils(aWindow);
michael@0 776 if (!utils) {
michael@0 777 return;
michael@0 778 }
michael@0 779
michael@0 780 if (!aEvent.composition || !aEvent.composition.clauses ||
michael@0 781 !aEvent.composition.clauses[0]) {
michael@0 782 return;
michael@0 783 }
michael@0 784
michael@0 785 var firstClauseLength = aEvent.composition.clauses[0].length;
michael@0 786 var firstClauseAttr = aEvent.composition.clauses[0].attr;
michael@0 787 var secondClauseLength = 0;
michael@0 788 var secondClauseAttr = 0;
michael@0 789 var thirdClauseLength = 0;
michael@0 790 var thirdClauseAttr = 0;
michael@0 791 if (aEvent.composition.clauses[1]) {
michael@0 792 secondClauseLength = aEvent.composition.clauses[1].length;
michael@0 793 secondClauseAttr = aEvent.composition.clauses[1].attr;
michael@0 794 if (aEvent.composition.clauses[2]) {
michael@0 795 thirdClauseLength = aEvent.composition.clauses[2].length;
michael@0 796 thirdClauseAttr = aEvent.composition.clauses[2].attr;
michael@0 797 }
michael@0 798 }
michael@0 799
michael@0 800 var caretStart = -1;
michael@0 801 var caretLength = 0;
michael@0 802 if (aEvent.caret) {
michael@0 803 caretStart = aEvent.caret.start;
michael@0 804 caretLength = aEvent.caret.length;
michael@0 805 }
michael@0 806
michael@0 807 utils.sendTextEvent(aEvent.composition.string,
michael@0 808 firstClauseLength, firstClauseAttr,
michael@0 809 secondClauseLength, secondClauseAttr,
michael@0 810 thirdClauseLength, thirdClauseAttr,
michael@0 811 caretStart, caretLength);
michael@0 812 }
michael@0 813
michael@0 814 /**
michael@0 815 * Synthesize a query selected text event.
michael@0 816 *
michael@0 817 * @param aWindow Optional (If null, current |window| will be used)
michael@0 818 * @return An nsIQueryContentEventResult object. If this failed,
michael@0 819 * the result might be null.
michael@0 820 */
michael@0 821 function synthesizeQuerySelectedText(aWindow)
michael@0 822 {
michael@0 823 var utils = _getDOMWindowUtils(aWindow);
michael@0 824 if (!utils) {
michael@0 825 return null;
michael@0 826 }
michael@0 827
michael@0 828 return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
michael@0 829 }

mercurial