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