testing/marionette/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 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * EventUtils provides some utility methods for creating and sending DOM events.
michael@0 7 * Current methods:
michael@0 8 * sendMouseEvent
michael@0 9 * sendChar
michael@0 10 * sendString
michael@0 11 * sendKey
michael@0 12 * synthesizeMouse
michael@0 13 * synthesizeMouseAtCenter
michael@0 14 * synthesizeMouseScroll
michael@0 15 * synthesizeKey
michael@0 16 * synthesizeMouseExpectEvent
michael@0 17 * synthesizeKeyExpectEvent
michael@0 18 *
michael@0 19 * When adding methods to this file, please add a performance test for it.
michael@0 20 */
michael@0 21
michael@0 22 /**
michael@0 23 * Send a mouse event to the node aTarget (aTarget can be an id, or an
michael@0 24 * actual node) . The "event" passed in to aEvent is just a JavaScript
michael@0 25 * object with the properties set that the real mouse event object should
michael@0 26 * have. This includes the type of the mouse event.
michael@0 27 * E.g. to send an click event to the node with id 'node' you might do this:
michael@0 28 *
michael@0 29 * sendMouseEvent({type:'click'}, 'node');
michael@0 30 */
michael@0 31 function getElement(id) {
michael@0 32 return ((typeof(id) == "string") ?
michael@0 33 document.getElementById(id) : id);
michael@0 34 };
michael@0 35
michael@0 36 this.$ = this.getElement;
michael@0 37 const KeyEvent = Components.interfaces.nsIDOMKeyEvent;
michael@0 38
michael@0 39 function sendMouseEvent(aEvent, aTarget, aWindow) {
michael@0 40 if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
michael@0 41 throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
michael@0 42 }
michael@0 43
michael@0 44 if (!aWindow) {
michael@0 45 aWindow = window;
michael@0 46 }
michael@0 47
michael@0 48 if (!(aTarget instanceof Element)) {
michael@0 49 aTarget = aWindow.document.getElementById(aTarget);
michael@0 50 }
michael@0 51
michael@0 52 var event = aWindow.document.createEvent('MouseEvent');
michael@0 53
michael@0 54 var typeArg = aEvent.type;
michael@0 55 var canBubbleArg = true;
michael@0 56 var cancelableArg = true;
michael@0 57 var viewArg = aWindow;
michael@0 58 var detailArg = aEvent.detail || (aEvent.type == 'click' ||
michael@0 59 aEvent.type == 'mousedown' ||
michael@0 60 aEvent.type == 'mouseup' ? 1 :
michael@0 61 aEvent.type == 'dblclick'? 2 : 0);
michael@0 62 var screenXArg = aEvent.screenX || 0;
michael@0 63 var screenYArg = aEvent.screenY || 0;
michael@0 64 var clientXArg = aEvent.clientX || 0;
michael@0 65 var clientYArg = aEvent.clientY || 0;
michael@0 66 var ctrlKeyArg = aEvent.ctrlKey || false;
michael@0 67 var altKeyArg = aEvent.altKey || false;
michael@0 68 var shiftKeyArg = aEvent.shiftKey || false;
michael@0 69 var metaKeyArg = aEvent.metaKey || false;
michael@0 70 var buttonArg = aEvent.button || 0;
michael@0 71 var relatedTargetArg = aEvent.relatedTarget || null;
michael@0 72
michael@0 73 event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
michael@0 74 screenXArg, screenYArg, clientXArg, clientYArg,
michael@0 75 ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
michael@0 76 buttonArg, relatedTargetArg);
michael@0 77
michael@0 78 //removed: SpecialPowers.dispatchEvent(aWindow, aTarget, event);
michael@0 79 }
michael@0 80
michael@0 81 /**
michael@0 82 * Send the char aChar to the focused element. This method handles casing of
michael@0 83 * chars (sends the right charcode, and sends a shift key for uppercase chars).
michael@0 84 * No other modifiers are handled at this point.
michael@0 85 *
michael@0 86 * For now this method only works for English letters (lower and upper case)
michael@0 87 * and the digits 0-9.
michael@0 88 */
michael@0 89 function sendChar(aChar, aWindow) {
michael@0 90 // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
michael@0 91 var hasShift = (aChar == aChar.toUpperCase());
michael@0 92 synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
michael@0 93 }
michael@0 94
michael@0 95 /**
michael@0 96 * Send the string aStr to the focused element.
michael@0 97 *
michael@0 98 * For now this method only works for English letters (lower and upper case)
michael@0 99 * and the digits 0-9.
michael@0 100 */
michael@0 101 function sendString(aStr, aWindow) {
michael@0 102 for (var i = 0; i < aStr.length; ++i) {
michael@0 103 sendChar(aStr.charAt(i), aWindow);
michael@0 104 }
michael@0 105 }
michael@0 106
michael@0 107 /**
michael@0 108 * Send the non-character key aKey to the focused node.
michael@0 109 * The name of the key should be the part that comes after "DOM_VK_" in the
michael@0 110 * KeyEvent constant name for this key.
michael@0 111 * No modifiers are handled at this point.
michael@0 112 */
michael@0 113 function sendKey(aKey, aWindow) {
michael@0 114 var keyName = "VK_" + aKey.toUpperCase();
michael@0 115 synthesizeKey(keyName, { shiftKey: false }, aWindow);
michael@0 116 }
michael@0 117
michael@0 118 /**
michael@0 119 * Parse the key modifier flags from aEvent. Used to share code between
michael@0 120 * synthesizeMouse and synthesizeKey.
michael@0 121 */
michael@0 122 function _parseModifiers(aEvent)
michael@0 123 {
michael@0 124 const masks = Components.interfaces.nsIDOMNSEvent;
michael@0 125 var mval = 0;
michael@0 126 if (aEvent.shiftKey)
michael@0 127 mval |= masks.SHIFT_MASK;
michael@0 128 if (aEvent.ctrlKey)
michael@0 129 mval |= masks.CONTROL_MASK;
michael@0 130 if (aEvent.altKey)
michael@0 131 mval |= masks.ALT_MASK;
michael@0 132 if (aEvent.metaKey)
michael@0 133 mval |= masks.META_MASK;
michael@0 134 if (aEvent.accelKey)
michael@0 135 mval |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
michael@0 136 masks.CONTROL_MASK;
michael@0 137
michael@0 138 return mval;
michael@0 139 }
michael@0 140
michael@0 141 /**
michael@0 142 * Synthesize a mouse event on a target. The actual client point is determined
michael@0 143 * by taking the aTarget's client box and offseting it by aOffsetX and
michael@0 144 * aOffsetY. This allows mouse clicks to be simulated by calling this method.
michael@0 145 *
michael@0 146 * aEvent is an object which may contain the properties:
michael@0 147 * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
michael@0 148 *
michael@0 149 * If the type is specified, an mouse event of that type is fired. Otherwise,
michael@0 150 * a mousedown followed by a mouse up is performed.
michael@0 151 *
michael@0 152 * aWindow is optional, and defaults to the current window object.
michael@0 153 */
michael@0 154 function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
michael@0 155 {
michael@0 156 var rect = aTarget.getBoundingClientRect();
michael@0 157 synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
michael@0 158 aEvent, aWindow);
michael@0 159 }
michael@0 160
michael@0 161 /*
michael@0 162 * Synthesize a mouse event at a particular point in aWindow.
michael@0 163 *
michael@0 164 * aEvent is an object which may contain the properties:
michael@0 165 * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
michael@0 166 *
michael@0 167 * If the type is specified, an mouse event of that type is fired. Otherwise,
michael@0 168 * a mousedown followed by a mouse up is performed.
michael@0 169 *
michael@0 170 * aWindow is optional, and defaults to the current window object.
michael@0 171 */
michael@0 172 function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
michael@0 173 {
michael@0 174 var utils = _getDOMWindowUtils(aWindow);
michael@0 175
michael@0 176 if (utils) {
michael@0 177 var button = aEvent.button || 0;
michael@0 178 var clickCount = aEvent.clickCount || 1;
michael@0 179 var modifiers = _parseModifiers(aEvent);
michael@0 180
michael@0 181 if (("type" in aEvent) && aEvent.type) {
michael@0 182 utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
michael@0 183 }
michael@0 184 else {
michael@0 185 utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
michael@0 186 utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
michael@0 187 }
michael@0 188 }
michael@0 189 }
michael@0 190
michael@0 191 // Call synthesizeMouse with coordinates at the center of aTarget.
michael@0 192 function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
michael@0 193 {
michael@0 194 var rect = aTarget.getBoundingClientRect();
michael@0 195 synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
michael@0 196 aWindow);
michael@0 197 }
michael@0 198
michael@0 199 /**
michael@0 200 * Synthesize a mouse scroll event on a target. The actual client point is determined
michael@0 201 * by taking the aTarget's client box and offseting it by aOffsetX and
michael@0 202 * aOffsetY.
michael@0 203 *
michael@0 204 * aEvent is an object which may contain the properties:
michael@0 205 * shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
michael@0 206 *
michael@0 207 * If the type is specified, a mouse scroll event of that type is fired. Otherwise,
michael@0 208 * "DOMMouseScroll" is used.
michael@0 209 *
michael@0 210 * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
michael@0 211 * "vertical" is used.
michael@0 212 *
michael@0 213 * 'delta' is the amount to scroll by (can be positive or negative). It must
michael@0 214 * be specified.
michael@0 215 *
michael@0 216 * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
michael@0 217 *
michael@0 218 * 'isMomentum' specifies whether kIsMomentum should be set in the scrollFlags.
michael@0 219 *
michael@0 220 * aWindow is optional, and defaults to the current window object.
michael@0 221 */
michael@0 222 function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
michael@0 223 {
michael@0 224 var utils = _getDOMWindowUtils(aWindow);
michael@0 225
michael@0 226 if (utils) {
michael@0 227 // See nsMouseScrollFlags in nsGUIEvent.h
michael@0 228 const kIsVertical = 0x02;
michael@0 229 const kIsHorizontal = 0x04;
michael@0 230 const kHasPixels = 0x08;
michael@0 231 const kIsMomentum = 0x40;
michael@0 232
michael@0 233 var button = aEvent.button || 0;
michael@0 234 var modifiers = _parseModifiers(aEvent);
michael@0 235
michael@0 236 var rect = aTarget.getBoundingClientRect();
michael@0 237
michael@0 238 var left = rect.left;
michael@0 239 var top = rect.top;
michael@0 240
michael@0 241 var type = (("type" in aEvent) && aEvent.type) || "DOMMouseScroll";
michael@0 242 var axis = aEvent.axis || "vertical";
michael@0 243 var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
michael@0 244 if (aEvent.hasPixels) {
michael@0 245 scrollFlags |= kHasPixels;
michael@0 246 }
michael@0 247 if (aEvent.isMomentum) {
michael@0 248 scrollFlags |= kIsMomentum;
michael@0 249 }
michael@0 250 utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
michael@0 251 scrollFlags, aEvent.delta, modifiers);
michael@0 252 }
michael@0 253 }
michael@0 254
michael@0 255 function _computeKeyCodeFromChar(aChar)
michael@0 256 {
michael@0 257 if (aChar.length != 1) {
michael@0 258 return 0;
michael@0 259 }
michael@0 260 const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
michael@0 261 if (aChar >= 'a' && aChar <= 'z') {
michael@0 262 return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
michael@0 263 }
michael@0 264 if (aChar >= 'A' && aChar <= 'Z') {
michael@0 265 return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
michael@0 266 }
michael@0 267 if (aChar >= '0' && aChar <= '9') {
michael@0 268 return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
michael@0 269 }
michael@0 270 // returns US keyboard layout's keycode
michael@0 271 switch (aChar) {
michael@0 272 case '~':
michael@0 273 case '`':
michael@0 274 return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
michael@0 275 case '!':
michael@0 276 return nsIDOMKeyEvent.DOM_VK_1;
michael@0 277 case '@':
michael@0 278 return nsIDOMKeyEvent.DOM_VK_2;
michael@0 279 case '#':
michael@0 280 return nsIDOMKeyEvent.DOM_VK_3;
michael@0 281 case '$':
michael@0 282 return nsIDOMKeyEvent.DOM_VK_4;
michael@0 283 case '%':
michael@0 284 return nsIDOMKeyEvent.DOM_VK_5;
michael@0 285 case '^':
michael@0 286 return nsIDOMKeyEvent.DOM_VK_6;
michael@0 287 case '&':
michael@0 288 return nsIDOMKeyEvent.DOM_VK_7;
michael@0 289 case '*':
michael@0 290 return nsIDOMKeyEvent.DOM_VK_8;
michael@0 291 case '(':
michael@0 292 return nsIDOMKeyEvent.DOM_VK_9;
michael@0 293 case ')':
michael@0 294 return nsIDOMKeyEvent.DOM_VK_0;
michael@0 295 case '-':
michael@0 296 case '_':
michael@0 297 return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
michael@0 298 case '+':
michael@0 299 case '=':
michael@0 300 return nsIDOMKeyEvent.DOM_VK_EQUALS;
michael@0 301 case '{':
michael@0 302 case '[':
michael@0 303 return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
michael@0 304 case '}':
michael@0 305 case ']':
michael@0 306 return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
michael@0 307 case '|':
michael@0 308 case '\\':
michael@0 309 return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
michael@0 310 case ':':
michael@0 311 case ';':
michael@0 312 return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
michael@0 313 case '\'':
michael@0 314 case '"':
michael@0 315 return nsIDOMKeyEvent.DOM_VK_QUOTE;
michael@0 316 case '<':
michael@0 317 case ',':
michael@0 318 return nsIDOMKeyEvent.DOM_VK_COMMA;
michael@0 319 case '>':
michael@0 320 case '.':
michael@0 321 return nsIDOMKeyEvent.DOM_VK_PERIOD;
michael@0 322 case '?':
michael@0 323 case '/':
michael@0 324 return nsIDOMKeyEvent.DOM_VK_SLASH;
michael@0 325 case '\n':
michael@0 326 return nsIDOMKeyEvent.DOM_VK_RETURN;
michael@0 327 default:
michael@0 328 return 0;
michael@0 329 }
michael@0 330 }
michael@0 331
michael@0 332 /**
michael@0 333 * isKeypressFiredKey() returns TRUE if the given key should cause keypress
michael@0 334 * event when widget handles the native key event. Otherwise, FALSE.
michael@0 335 *
michael@0 336 * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
michael@0 337 * name begins with "VK_", or a character.
michael@0 338 */
michael@0 339 function isKeypressFiredKey(aDOMKeyCode)
michael@0 340 {
michael@0 341 const KeyEvent = Components.interfaces.nsIDOMKeyEvent;
michael@0 342 if (typeof(aDOMKeyCode) == "string") {
michael@0 343 if (aDOMKeyCode.indexOf("VK_") == 0) {
michael@0 344 aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
michael@0 345 if (!aDOMKeyCode) {
michael@0 346 throw "Unknown key: " + aDOMKeyCode;
michael@0 347 }
michael@0 348 } else {
michael@0 349 // If the key generates a character, it must cause a keypress event.
michael@0 350 return true;
michael@0 351 }
michael@0 352 }
michael@0 353 switch (aDOMKeyCode) {
michael@0 354 case KeyEvent.DOM_VK_SHIFT:
michael@0 355 case KeyEvent.DOM_VK_CONTROL:
michael@0 356 case KeyEvent.DOM_VK_ALT:
michael@0 357 case KeyEvent.DOM_VK_CAPS_LOCK:
michael@0 358 case KeyEvent.DOM_VK_NUM_LOCK:
michael@0 359 case KeyEvent.DOM_VK_SCROLL_LOCK:
michael@0 360 case KeyEvent.DOM_VK_META:
michael@0 361 return false;
michael@0 362 default:
michael@0 363 return true;
michael@0 364 }
michael@0 365 }
michael@0 366
michael@0 367 /**
michael@0 368 * Synthesize a key event. It is targeted at whatever would be targeted by an
michael@0 369 * actual keypress by the user, typically the focused element.
michael@0 370 *
michael@0 371 * aKey should be either a character or a keycode starting with VK_ such as
michael@0 372 * VK_RETURN.
michael@0 373 *
michael@0 374 * aEvent is an object which may contain the properties:
michael@0 375 * shiftKey, ctrlKey, altKey, metaKey, accessKey, type
michael@0 376 *
michael@0 377 * If the type is specified, a key event of that type is fired. Otherwise,
michael@0 378 * a keydown, a keypress and then a keyup event are fired in sequence.
michael@0 379 *
michael@0 380 * aWindow is optional, and defaults to the current window object.
michael@0 381 */
michael@0 382 function synthesizeKey(aKey, aEvent, aWindow)
michael@0 383 {
michael@0 384 var utils = _getDOMWindowUtils(aWindow);
michael@0 385 if (utils) {
michael@0 386 var keyCode = 0, charCode = 0;
michael@0 387 if (aKey.indexOf("VK_") == 0) {
michael@0 388 keyCode = KeyEvent["DOM_" + aKey];
michael@0 389 if (!keyCode) {
michael@0 390 throw "Unknown key: " + aKey;
michael@0 391 }
michael@0 392 } else {
michael@0 393 charCode = aKey.charCodeAt(0);
michael@0 394 keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
michael@0 395 }
michael@0 396
michael@0 397 var modifiers = _parseModifiers(aEvent);
michael@0 398
michael@0 399 if (!("type" in aEvent) || !aEvent.type) {
michael@0 400 // Send keydown + (optional) keypress + keyup events.
michael@0 401 var keyDownDefaultHappened =
michael@0 402 utils.sendKeyEvent("keydown", keyCode, 0, modifiers);
michael@0 403 if (isKeypressFiredKey(keyCode)) {
michael@0 404 utils.sendKeyEvent("keypress", charCode ? 0 : keyCode, charCode,
michael@0 405 modifiers, !keyDownDefaultHappened);
michael@0 406 }
michael@0 407 utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
michael@0 408 } else if (aEvent.type == "keypress") {
michael@0 409 // Send standalone keypress event.
michael@0 410 utils.sendKeyEvent(aEvent.type, charCode ? 0 : keyCode,
michael@0 411 charCode, modifiers);
michael@0 412 } else {
michael@0 413 // Send other standalone event than keypress.
michael@0 414 utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers);
michael@0 415 }
michael@0 416 }
michael@0 417 }
michael@0 418
michael@0 419 var _gSeenEvent = false;
michael@0 420
michael@0 421 /**
michael@0 422 * Indicate that an event with an original target of aExpectedTarget and
michael@0 423 * a type of aExpectedEvent is expected to be fired, or not expected to
michael@0 424 * be fired.
michael@0 425 */
michael@0 426 function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
michael@0 427 {
michael@0 428 if (!aExpectedTarget || !aExpectedEvent)
michael@0 429 return null;
michael@0 430
michael@0 431 _gSeenEvent = false;
michael@0 432
michael@0 433 var type = (aExpectedEvent.charAt(0) == "!") ?
michael@0 434 aExpectedEvent.substring(1) : aExpectedEvent;
michael@0 435 var eventHandler = function(event) {
michael@0 436 var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
michael@0 437 event.type == type);
michael@0 438 is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
michael@0 439 _gSeenEvent = true;
michael@0 440 };
michael@0 441
michael@0 442 aExpectedTarget.addEventListener(type, eventHandler, false);
michael@0 443 return eventHandler;
michael@0 444 }
michael@0 445
michael@0 446 /**
michael@0 447 * Check if the event was fired or not. The event handler aEventHandler
michael@0 448 * will be removed.
michael@0 449 */
michael@0 450 function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
michael@0 451 {
michael@0 452 if (aEventHandler) {
michael@0 453 var expectEvent = (aExpectedEvent.charAt(0) != "!");
michael@0 454 var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
michael@0 455 aExpectedTarget.removeEventListener(type, aEventHandler, false);
michael@0 456 var desc = type + " event";
michael@0 457 if (!expectEvent)
michael@0 458 desc += " not";
michael@0 459 is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
michael@0 460 }
michael@0 461
michael@0 462 _gSeenEvent = false;
michael@0 463 }
michael@0 464
michael@0 465 /**
michael@0 466 * Similar to synthesizeMouse except that a test is performed to see if an
michael@0 467 * event is fired at the right target as a result.
michael@0 468 *
michael@0 469 * aExpectedTarget - the expected originalTarget of the event.
michael@0 470 * aExpectedEvent - the expected type of the event, such as 'select'.
michael@0 471 * aTestName - the test name when outputing results
michael@0 472 *
michael@0 473 * To test that an event is not fired, use an expected type preceded by an
michael@0 474 * exclamation mark, such as '!select'. This might be used to test that a
michael@0 475 * click on a disabled element doesn't fire certain events for instance.
michael@0 476 *
michael@0 477 * aWindow is optional, and defaults to the current window object.
michael@0 478 */
michael@0 479 function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
michael@0 480 aExpectedTarget, aExpectedEvent, aTestName,
michael@0 481 aWindow)
michael@0 482 {
michael@0 483 var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
michael@0 484 synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
michael@0 485 _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
michael@0 486 }
michael@0 487
michael@0 488 /**
michael@0 489 * Similar to synthesizeKey except that a test is performed to see if an
michael@0 490 * event is fired at the right target as a result.
michael@0 491 *
michael@0 492 * aExpectedTarget - the expected originalTarget of the event.
michael@0 493 * aExpectedEvent - the expected type of the event, such as 'select'.
michael@0 494 * aTestName - the test name when outputing results
michael@0 495 *
michael@0 496 * To test that an event is not fired, use an expected type preceded by an
michael@0 497 * exclamation mark, such as '!select'.
michael@0 498 *
michael@0 499 * aWindow is optional, and defaults to the current window object.
michael@0 500 */
michael@0 501 function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
michael@0 502 aTestName, aWindow)
michael@0 503 {
michael@0 504 var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
michael@0 505 synthesizeKey(key, aEvent, aWindow);
michael@0 506 _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
michael@0 507 }
michael@0 508
michael@0 509 function disableNonTestMouseEvents(aDisable)
michael@0 510 {
michael@0 511 var domutils = _getDOMWindowUtils();
michael@0 512 domutils.disableNonTestMouseEvents(aDisable);
michael@0 513 }
michael@0 514
michael@0 515 function _getDOMWindowUtils(aWindow)
michael@0 516 {
michael@0 517 if (!aWindow) {
michael@0 518 aWindow = window;
michael@0 519 }
michael@0 520
michael@0 521 //TODO: this is assuming we are in chrome space
michael@0 522 return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
michael@0 523 getInterface(Components.interfaces.nsIDOMWindowUtils);
michael@0 524 }
michael@0 525
michael@0 526 // Must be synchronized with nsIDOMWindowUtils.
michael@0 527 const COMPOSITION_ATTR_RAWINPUT = 0x02;
michael@0 528 const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
michael@0 529 const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
michael@0 530 const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
michael@0 531
michael@0 532 /**
michael@0 533 * Synthesize a composition event.
michael@0 534 *
michael@0 535 * @param aEvent The composition event information. This must
michael@0 536 * have |type| member. The value must be
michael@0 537 * "compositionstart", "compositionend" or
michael@0 538 * "compositionupdate".
michael@0 539 * And also this may have |data| and |locale| which
michael@0 540 * would be used for the value of each property of
michael@0 541 * the composition event. Note that the data would
michael@0 542 * be ignored if the event type were
michael@0 543 * "compositionstart".
michael@0 544 * @param aWindow Optional (If null, current |window| will be used)
michael@0 545 */
michael@0 546 function synthesizeComposition(aEvent, aWindow)
michael@0 547 {
michael@0 548 var utils = _getDOMWindowUtils(aWindow);
michael@0 549 if (!utils) {
michael@0 550 return;
michael@0 551 }
michael@0 552
michael@0 553 utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
michael@0 554 aEvent.locale ? aEvent.locale : "");
michael@0 555 }
michael@0 556 /**
michael@0 557 * Synthesize a text event.
michael@0 558 *
michael@0 559 * @param aEvent The text event's information, this has |composition|
michael@0 560 * and |caret| members. |composition| has |string| and
michael@0 561 * |clauses| members. |clauses| must be array object. Each
michael@0 562 * object has |length| and |attr|. And |caret| has |start| and
michael@0 563 * |length|. See the following tree image.
michael@0 564 *
michael@0 565 * aEvent
michael@0 566 * +-- composition
michael@0 567 * | +-- string
michael@0 568 * | +-- clauses[]
michael@0 569 * | +-- length
michael@0 570 * | +-- attr
michael@0 571 * +-- caret
michael@0 572 * +-- start
michael@0 573 * +-- length
michael@0 574 *
michael@0 575 * Set the composition string to |composition.string|. Set its
michael@0 576 * clauses information to the |clauses| array.
michael@0 577 *
michael@0 578 * When it's composing, set the each clauses' length to the
michael@0 579 * |composition.clauses[n].length|. The sum of the all length
michael@0 580 * values must be same as the length of |composition.string|.
michael@0 581 * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
michael@0 582 * |composition.clauses[n].attr|.
michael@0 583 *
michael@0 584 * When it's not composing, set 0 to the
michael@0 585 * |composition.clauses[0].length| and
michael@0 586 * |composition.clauses[0].attr|.
michael@0 587 *
michael@0 588 * Set caret position to the |caret.start|. It's offset from
michael@0 589 * the start of the composition string. Set caret length to
michael@0 590 * |caret.length|. If it's larger than 0, it should be wide
michael@0 591 * caret. However, current nsEditor doesn't support wide
michael@0 592 * caret, therefore, you should always set 0 now.
michael@0 593 *
michael@0 594 * @param aWindow Optional (If null, current |window| will be used)
michael@0 595 */
michael@0 596 function synthesizeText(aEvent, aWindow)
michael@0 597 {
michael@0 598 var utils = _getDOMWindowUtils(aWindow);
michael@0 599 if (!utils) {
michael@0 600 return;
michael@0 601 }
michael@0 602
michael@0 603 if (!aEvent.composition || !aEvent.composition.clauses ||
michael@0 604 !aEvent.composition.clauses[0]) {
michael@0 605 return;
michael@0 606 }
michael@0 607
michael@0 608 var firstClauseLength = aEvent.composition.clauses[0].length;
michael@0 609 var firstClauseAttr = aEvent.composition.clauses[0].attr;
michael@0 610 var secondClauseLength = 0;
michael@0 611 var secondClauseAttr = 0;
michael@0 612 var thirdClauseLength = 0;
michael@0 613 var thirdClauseAttr = 0;
michael@0 614 if (aEvent.composition.clauses[1]) {
michael@0 615 secondClauseLength = aEvent.composition.clauses[1].length;
michael@0 616 secondClauseAttr = aEvent.composition.clauses[1].attr;
michael@0 617 if (aEvent.composition.clauses[2]) {
michael@0 618 thirdClauseLength = aEvent.composition.clauses[2].length;
michael@0 619 thirdClauseAttr = aEvent.composition.clauses[2].attr;
michael@0 620 }
michael@0 621 }
michael@0 622
michael@0 623 var caretStart = -1;
michael@0 624 var caretLength = 0;
michael@0 625 if (aEvent.caret) {
michael@0 626 caretStart = aEvent.caret.start;
michael@0 627 caretLength = aEvent.caret.length;
michael@0 628 }
michael@0 629
michael@0 630 utils.sendTextEvent(aEvent.composition.string,
michael@0 631 firstClauseLength, firstClauseAttr,
michael@0 632 secondClauseLength, secondClauseAttr,
michael@0 633 thirdClauseLength, thirdClauseAttr,
michael@0 634 caretStart, caretLength);
michael@0 635 }
michael@0 636
michael@0 637 /**
michael@0 638 * Synthesize a query selected text event.
michael@0 639 *
michael@0 640 * @param aWindow Optional (If null, current |window| will be used)
michael@0 641 * @return An nsIQueryContentEventResult object. If this failed,
michael@0 642 * the result might be null.
michael@0 643 */
michael@0 644 function synthesizeQuerySelectedText(aWindow)
michael@0 645 {
michael@0 646 var utils = _getDOMWindowUtils(aWindow);
michael@0 647 if (!utils) {
michael@0 648 return null;
michael@0 649 }
michael@0 650
michael@0 651 return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
michael@0 652 }
michael@0 653
michael@0 654 /**
michael@0 655 * Synthesize a selection set event.
michael@0 656 *
michael@0 657 * @param aOffset The character offset. 0 means the first character in the
michael@0 658 * selection root.
michael@0 659 * @param aLength The length of the text. If the length is too long,
michael@0 660 * the extra length is ignored.
michael@0 661 * @param aReverse If true, the selection is from |aOffset + aLength| to
michael@0 662 * |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|.
michael@0 663 * @param aWindow Optional (If null, current |window| will be used)
michael@0 664 * @return True, if succeeded. Otherwise false.
michael@0 665 */
michael@0 666 function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
michael@0 667 {
michael@0 668 var utils = _getDOMWindowUtils(aWindow);
michael@0 669 if (!utils) {
michael@0 670 return false;
michael@0 671 }
michael@0 672 return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
michael@0 673 }

mercurial