1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,829 @@ 1.4 +// Export all available functions for Mozmill 1.5 +var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar", 1.6 + "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", 1.7 + "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", 1.8 + "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", 1.9 + "synthesizeWheel", "synthesizeKey", 1.10 + "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", 1.11 + "synthesizeText", 1.12 + "synthesizeComposition", "synthesizeQuerySelectedText"]; 1.13 + 1.14 +const Ci = Components.interfaces; 1.15 +const Cc = Components.classes; 1.16 + 1.17 +var window = Cc["@mozilla.org/appshell/appShellService;1"] 1.18 + .getService(Ci.nsIAppShellService).hiddenDOMWindow; 1.19 + 1.20 +var _EU_Ci = Ci; 1.21 +var navigator = window.navigator; 1.22 +var KeyEvent = window.KeyEvent; 1.23 +var parent = window.parent; 1.24 + 1.25 +function is(aExpression1, aExpression2, aMessage) { 1.26 + if (aExpression1 !== aExpression2) { 1.27 + throw new Error(aMessage); 1.28 + } 1.29 +} 1.30 + 1.31 +/** 1.32 + * EventUtils provides some utility methods for creating and sending DOM events. 1.33 + * Current methods: 1.34 + * sendMouseEvent 1.35 + * sendChar 1.36 + * sendString 1.37 + * sendKey 1.38 + * synthesizeMouse 1.39 + * synthesizeMouseAtCenter 1.40 + * synthesizeWheel 1.41 + * synthesizeKey 1.42 + * synthesizeMouseExpectEvent 1.43 + * synthesizeKeyExpectEvent 1.44 + * 1.45 + * When adding methods to this file, please add a performance test for it. 1.46 + */ 1.47 + 1.48 +/** 1.49 + * Send a mouse event to the node aTarget (aTarget can be an id, or an 1.50 + * actual node) . The "event" passed in to aEvent is just a JavaScript 1.51 + * object with the properties set that the real mouse event object should 1.52 + * have. This includes the type of the mouse event. 1.53 + * E.g. to send an click event to the node with id 'node' you might do this: 1.54 + * 1.55 + * sendMouseEvent({type:'click'}, 'node'); 1.56 + */ 1.57 +function getElement(id) { 1.58 + return ((typeof(id) == "string") ? 1.59 + document.getElementById(id) : id); 1.60 +}; 1.61 + 1.62 +this.$ = this.getElement; 1.63 + 1.64 +function sendMouseEvent(aEvent, aTarget, aWindow) { 1.65 + if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) { 1.66 + throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'"); 1.67 + } 1.68 + 1.69 + if (!aWindow) { 1.70 + aWindow = window; 1.71 + } 1.72 + 1.73 + if (!(aTarget instanceof aWindow.Element)) { 1.74 + aTarget = aWindow.document.getElementById(aTarget); 1.75 + } 1.76 + 1.77 + var event = aWindow.document.createEvent('MouseEvent'); 1.78 + 1.79 + var typeArg = aEvent.type; 1.80 + var canBubbleArg = true; 1.81 + var cancelableArg = true; 1.82 + var viewArg = aWindow; 1.83 + var detailArg = aEvent.detail || (aEvent.type == 'click' || 1.84 + aEvent.type == 'mousedown' || 1.85 + aEvent.type == 'mouseup' ? 1 : 1.86 + aEvent.type == 'dblclick'? 2 : 0); 1.87 + var screenXArg = aEvent.screenX || 0; 1.88 + var screenYArg = aEvent.screenY || 0; 1.89 + var clientXArg = aEvent.clientX || 0; 1.90 + var clientYArg = aEvent.clientY || 0; 1.91 + var ctrlKeyArg = aEvent.ctrlKey || false; 1.92 + var altKeyArg = aEvent.altKey || false; 1.93 + var shiftKeyArg = aEvent.shiftKey || false; 1.94 + var metaKeyArg = aEvent.metaKey || false; 1.95 + var buttonArg = aEvent.button || 0; 1.96 + var relatedTargetArg = aEvent.relatedTarget || null; 1.97 + 1.98 + event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg, 1.99 + screenXArg, screenYArg, clientXArg, clientYArg, 1.100 + ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, 1.101 + buttonArg, relatedTargetArg); 1.102 + 1.103 + SpecialPowers.dispatchEvent(aWindow, aTarget, event); 1.104 +} 1.105 + 1.106 +/** 1.107 + * Send the char aChar to the focused element. This method handles casing of 1.108 + * chars (sends the right charcode, and sends a shift key for uppercase chars). 1.109 + * No other modifiers are handled at this point. 1.110 + * 1.111 + * For now this method only works for ASCII characters and emulates the shift 1.112 + * key state on US keyboard layout. 1.113 + */ 1.114 +function sendChar(aChar, aWindow) { 1.115 + var hasShift; 1.116 + // Emulate US keyboard layout for the shiftKey state. 1.117 + switch (aChar) { 1.118 + case "!": 1.119 + case "@": 1.120 + case "#": 1.121 + case "$": 1.122 + case "%": 1.123 + case "^": 1.124 + case "&": 1.125 + case "*": 1.126 + case "(": 1.127 + case ")": 1.128 + case "_": 1.129 + case "+": 1.130 + case "{": 1.131 + case "}": 1.132 + case ":": 1.133 + case "\"": 1.134 + case "|": 1.135 + case "<": 1.136 + case ">": 1.137 + case "?": 1.138 + hasShift = true; 1.139 + break; 1.140 + default: 1.141 + hasShift = (aChar == aChar.toUpperCase()); 1.142 + break; 1.143 + } 1.144 + synthesizeKey(aChar, { shiftKey: hasShift }, aWindow); 1.145 +} 1.146 + 1.147 +/** 1.148 + * Send the string aStr to the focused element. 1.149 + * 1.150 + * For now this method only works for ASCII characters and emulates the shift 1.151 + * key state on US keyboard layout. 1.152 + */ 1.153 +function sendString(aStr, aWindow) { 1.154 + for (var i = 0; i < aStr.length; ++i) { 1.155 + sendChar(aStr.charAt(i), aWindow); 1.156 + } 1.157 +} 1.158 + 1.159 +/** 1.160 + * Send the non-character key aKey to the focused node. 1.161 + * The name of the key should be the part that comes after "DOM_VK_" in the 1.162 + * KeyEvent constant name for this key. 1.163 + * No modifiers are handled at this point. 1.164 + */ 1.165 +function sendKey(aKey, aWindow) { 1.166 + var keyName = "VK_" + aKey.toUpperCase(); 1.167 + synthesizeKey(keyName, { shiftKey: false }, aWindow); 1.168 +} 1.169 + 1.170 +/** 1.171 + * Parse the key modifier flags from aEvent. Used to share code between 1.172 + * synthesizeMouse and synthesizeKey. 1.173 + */ 1.174 +function _parseModifiers(aEvent) 1.175 +{ 1.176 + const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils; 1.177 + var mval = 0; 1.178 + if (aEvent.shiftKey) { 1.179 + mval |= nsIDOMWindowUtils.MODIFIER_SHIFT; 1.180 + } 1.181 + if (aEvent.ctrlKey) { 1.182 + mval |= nsIDOMWindowUtils.MODIFIER_CONTROL; 1.183 + } 1.184 + if (aEvent.altKey) { 1.185 + mval |= nsIDOMWindowUtils.MODIFIER_ALT; 1.186 + } 1.187 + if (aEvent.metaKey) { 1.188 + mval |= nsIDOMWindowUtils.MODIFIER_META; 1.189 + } 1.190 + if (aEvent.accelKey) { 1.191 + mval |= (navigator.platform.indexOf("Mac") >= 0) ? 1.192 + nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL; 1.193 + } 1.194 + if (aEvent.altGrKey) { 1.195 + mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH; 1.196 + } 1.197 + if (aEvent.capsLockKey) { 1.198 + mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK; 1.199 + } 1.200 + if (aEvent.fnKey) { 1.201 + mval |= nsIDOMWindowUtils.MODIFIER_FN; 1.202 + } 1.203 + if (aEvent.numLockKey) { 1.204 + mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK; 1.205 + } 1.206 + if (aEvent.scrollLockKey) { 1.207 + mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK; 1.208 + } 1.209 + if (aEvent.symbolLockKey) { 1.210 + mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK; 1.211 + } 1.212 + if (aEvent.osKey) { 1.213 + mval |= nsIDOMWindowUtils.MODIFIER_OS; 1.214 + } 1.215 + 1.216 + return mval; 1.217 +} 1.218 + 1.219 +/** 1.220 + * Synthesize a mouse event on a target. The actual client point is determined 1.221 + * by taking the aTarget's client box and offseting it by aOffsetX and 1.222 + * aOffsetY. This allows mouse clicks to be simulated by calling this method. 1.223 + * 1.224 + * aEvent is an object which may contain the properties: 1.225 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type 1.226 + * 1.227 + * If the type is specified, an mouse event of that type is fired. Otherwise, 1.228 + * a mousedown followed by a mouse up is performed. 1.229 + * 1.230 + * aWindow is optional, and defaults to the current window object. 1.231 + * 1.232 + * Returns whether the event had preventDefault() called on it. 1.233 + */ 1.234 +function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) 1.235 +{ 1.236 + var rect = aTarget.getBoundingClientRect(); 1.237 + return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, 1.238 + aEvent, aWindow); 1.239 +} 1.240 +function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) 1.241 +{ 1.242 + var rect = aTarget.getBoundingClientRect(); 1.243 + synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, 1.244 + aEvent, aWindow); 1.245 +} 1.246 + 1.247 +/* 1.248 + * Synthesize a mouse event at a particular point in aWindow. 1.249 + * 1.250 + * aEvent is an object which may contain the properties: 1.251 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type 1.252 + * 1.253 + * If the type is specified, an mouse event of that type is fired. Otherwise, 1.254 + * a mousedown followed by a mouse up is performed. 1.255 + * 1.256 + * aWindow is optional, and defaults to the current window object. 1.257 + */ 1.258 +function synthesizeMouseAtPoint(left, top, aEvent, aWindow) 1.259 +{ 1.260 + var utils = _getDOMWindowUtils(aWindow); 1.261 + var defaultPrevented = false; 1.262 + 1.263 + if (utils) { 1.264 + var button = aEvent.button || 0; 1.265 + var clickCount = aEvent.clickCount || 1; 1.266 + var modifiers = _parseModifiers(aEvent); 1.267 + var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0; 1.268 + var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0; 1.269 + 1.270 + if (("type" in aEvent) && aEvent.type) { 1.271 + defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource); 1.272 + } 1.273 + else { 1.274 + utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource); 1.275 + utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource); 1.276 + } 1.277 + } 1.278 + 1.279 + return defaultPrevented; 1.280 +} 1.281 +function synthesizeTouchAtPoint(left, top, aEvent, aWindow) 1.282 +{ 1.283 + var utils = _getDOMWindowUtils(aWindow); 1.284 + 1.285 + if (utils) { 1.286 + var id = aEvent.id || 0; 1.287 + var rx = aEvent.rx || 1; 1.288 + var ry = aEvent.rx || 1; 1.289 + var angle = aEvent.angle || 0; 1.290 + var force = aEvent.force || 1; 1.291 + var modifiers = _parseModifiers(aEvent); 1.292 + 1.293 + if (("type" in aEvent) && aEvent.type) { 1.294 + utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); 1.295 + } 1.296 + else { 1.297 + utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); 1.298 + utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers); 1.299 + } 1.300 + } 1.301 +} 1.302 +// Call synthesizeMouse with coordinates at the center of aTarget. 1.303 +function synthesizeMouseAtCenter(aTarget, aEvent, aWindow) 1.304 +{ 1.305 + var rect = aTarget.getBoundingClientRect(); 1.306 + synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent, 1.307 + aWindow); 1.308 +} 1.309 +function synthesizeTouchAtCenter(aTarget, aEvent, aWindow) 1.310 +{ 1.311 + var rect = aTarget.getBoundingClientRect(); 1.312 + synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent, 1.313 + aWindow); 1.314 +} 1.315 + 1.316 +/** 1.317 + * Synthesize a wheel event on a target. The actual client point is determined 1.318 + * by taking the aTarget's client box and offseting it by aOffsetX and 1.319 + * aOffsetY. 1.320 + * 1.321 + * aEvent is an object which may contain the properties: 1.322 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ, 1.323 + * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice, 1.324 + * isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY 1.325 + * 1.326 + * deltaMode must be defined, others are ok even if undefined. 1.327 + * 1.328 + * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The 1.329 + * value is just checked as 0 or positive or negative. 1.330 + * 1.331 + * aWindow is optional, and defaults to the current window object. 1.332 + */ 1.333 +function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) 1.334 +{ 1.335 + var utils = _getDOMWindowUtils(aWindow); 1.336 + if (!utils) { 1.337 + return; 1.338 + } 1.339 + 1.340 + var modifiers = _parseModifiers(aEvent); 1.341 + var options = 0; 1.342 + if (aEvent.isPixelOnlyDevice && 1.343 + (aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) { 1.344 + options |= utils.WHEEL_EVENT_CAUSED_BY_PIXEL_ONLY_DEVICE; 1.345 + } 1.346 + if (aEvent.isMomentum) { 1.347 + options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM; 1.348 + } 1.349 + if (aEvent.isCustomizedByPrefs) { 1.350 + options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS; 1.351 + } 1.352 + if (typeof aEvent.expectedOverflowDeltaX !== "undefined") { 1.353 + if (aEvent.expectedOverflowDeltaX === 0) { 1.354 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO; 1.355 + } else if (aEvent.expectedOverflowDeltaX > 0) { 1.356 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE; 1.357 + } else { 1.358 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE; 1.359 + } 1.360 + } 1.361 + if (typeof aEvent.expectedOverflowDeltaY !== "undefined") { 1.362 + if (aEvent.expectedOverflowDeltaY === 0) { 1.363 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO; 1.364 + } else if (aEvent.expectedOverflowDeltaY > 0) { 1.365 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE; 1.366 + } else { 1.367 + options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE; 1.368 + } 1.369 + } 1.370 + var isPixelOnlyDevice = 1.371 + aEvent.isPixelOnlyDevice && aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL; 1.372 + 1.373 + // Avoid the JS warnings "reference to undefined property" 1.374 + if (!aEvent.deltaX) { 1.375 + aEvent.deltaX = 0; 1.376 + } 1.377 + if (!aEvent.deltaY) { 1.378 + aEvent.deltaY = 0; 1.379 + } 1.380 + if (!aEvent.deltaZ) { 1.381 + aEvent.deltaZ = 0; 1.382 + } 1.383 + 1.384 + var lineOrPageDeltaX = 1.385 + aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX : 1.386 + aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) : 1.387 + Math.ceil(aEvent.deltaX); 1.388 + var lineOrPageDeltaY = 1.389 + aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY : 1.390 + aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) : 1.391 + Math.ceil(aEvent.deltaY); 1.392 + 1.393 + var rect = aTarget.getBoundingClientRect(); 1.394 + utils.sendWheelEvent(rect.left + aOffsetX, rect.top + aOffsetY, 1.395 + aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ, 1.396 + aEvent.deltaMode, modifiers, 1.397 + lineOrPageDeltaX, lineOrPageDeltaY, options); 1.398 +} 1.399 + 1.400 +function _computeKeyCodeFromChar(aChar) 1.401 +{ 1.402 + if (aChar.length != 1) { 1.403 + return 0; 1.404 + } 1.405 + const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent; 1.406 + if (aChar >= 'a' && aChar <= 'z') { 1.407 + return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0); 1.408 + } 1.409 + if (aChar >= 'A' && aChar <= 'Z') { 1.410 + return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0); 1.411 + } 1.412 + if (aChar >= '0' && aChar <= '9') { 1.413 + return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0); 1.414 + } 1.415 + // returns US keyboard layout's keycode 1.416 + switch (aChar) { 1.417 + case '~': 1.418 + case '`': 1.419 + return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE; 1.420 + case '!': 1.421 + return nsIDOMKeyEvent.DOM_VK_1; 1.422 + case '@': 1.423 + return nsIDOMKeyEvent.DOM_VK_2; 1.424 + case '#': 1.425 + return nsIDOMKeyEvent.DOM_VK_3; 1.426 + case '$': 1.427 + return nsIDOMKeyEvent.DOM_VK_4; 1.428 + case '%': 1.429 + return nsIDOMKeyEvent.DOM_VK_5; 1.430 + case '^': 1.431 + return nsIDOMKeyEvent.DOM_VK_6; 1.432 + case '&': 1.433 + return nsIDOMKeyEvent.DOM_VK_7; 1.434 + case '*': 1.435 + return nsIDOMKeyEvent.DOM_VK_8; 1.436 + case '(': 1.437 + return nsIDOMKeyEvent.DOM_VK_9; 1.438 + case ')': 1.439 + return nsIDOMKeyEvent.DOM_VK_0; 1.440 + case '-': 1.441 + case '_': 1.442 + return nsIDOMKeyEvent.DOM_VK_SUBTRACT; 1.443 + case '+': 1.444 + case '=': 1.445 + return nsIDOMKeyEvent.DOM_VK_EQUALS; 1.446 + case '{': 1.447 + case '[': 1.448 + return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET; 1.449 + case '}': 1.450 + case ']': 1.451 + return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET; 1.452 + case '|': 1.453 + case '\\': 1.454 + return nsIDOMKeyEvent.DOM_VK_BACK_SLASH; 1.455 + case ':': 1.456 + case ';': 1.457 + return nsIDOMKeyEvent.DOM_VK_SEMICOLON; 1.458 + case '\'': 1.459 + case '"': 1.460 + return nsIDOMKeyEvent.DOM_VK_QUOTE; 1.461 + case '<': 1.462 + case ',': 1.463 + return nsIDOMKeyEvent.DOM_VK_COMMA; 1.464 + case '>': 1.465 + case '.': 1.466 + return nsIDOMKeyEvent.DOM_VK_PERIOD; 1.467 + case '?': 1.468 + case '/': 1.469 + return nsIDOMKeyEvent.DOM_VK_SLASH; 1.470 + default: 1.471 + return 0; 1.472 + } 1.473 +} 1.474 + 1.475 +/** 1.476 + * isKeypressFiredKey() returns TRUE if the given key should cause keypress 1.477 + * event when widget handles the native key event. Otherwise, FALSE. 1.478 + * 1.479 + * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key 1.480 + * name begins with "VK_", or a character. 1.481 + */ 1.482 +function isKeypressFiredKey(aDOMKeyCode) 1.483 +{ 1.484 + if (typeof(aDOMKeyCode) == "string") { 1.485 + if (aDOMKeyCode.indexOf("VK_") == 0) { 1.486 + aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode]; 1.487 + if (!aDOMKeyCode) { 1.488 + throw "Unknown key: " + aDOMKeyCode; 1.489 + } 1.490 + } else { 1.491 + // If the key generates a character, it must cause a keypress event. 1.492 + return true; 1.493 + } 1.494 + } 1.495 + switch (aDOMKeyCode) { 1.496 + case KeyEvent.DOM_VK_SHIFT: 1.497 + case KeyEvent.DOM_VK_CONTROL: 1.498 + case KeyEvent.DOM_VK_ALT: 1.499 + case KeyEvent.DOM_VK_CAPS_LOCK: 1.500 + case KeyEvent.DOM_VK_NUM_LOCK: 1.501 + case KeyEvent.DOM_VK_SCROLL_LOCK: 1.502 + case KeyEvent.DOM_VK_META: 1.503 + return false; 1.504 + default: 1.505 + return true; 1.506 + } 1.507 +} 1.508 + 1.509 +/** 1.510 + * Synthesize a key event. It is targeted at whatever would be targeted by an 1.511 + * actual keypress by the user, typically the focused element. 1.512 + * 1.513 + * aKey should be either a character or a keycode starting with VK_ such as 1.514 + * VK_ENTER. 1.515 + * 1.516 + * aEvent is an object which may contain the properties: 1.517 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location 1.518 + * 1.519 + * Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location. Otherwise, 1.520 + * DOMWindowUtils will choose good location from the keycode. 1.521 + * 1.522 + * If the type is specified, a key event of that type is fired. Otherwise, 1.523 + * a keydown, a keypress and then a keyup event are fired in sequence. 1.524 + * 1.525 + * aWindow is optional, and defaults to the current window object. 1.526 + */ 1.527 +function synthesizeKey(aKey, aEvent, aWindow) 1.528 +{ 1.529 + var utils = _getDOMWindowUtils(aWindow); 1.530 + if (utils) { 1.531 + var keyCode = 0, charCode = 0; 1.532 + if (aKey.indexOf("VK_") == 0) { 1.533 + keyCode = KeyEvent["DOM_" + aKey]; 1.534 + if (!keyCode) { 1.535 + throw "Unknown key: " + aKey; 1.536 + } 1.537 + } else { 1.538 + charCode = aKey.charCodeAt(0); 1.539 + keyCode = _computeKeyCodeFromChar(aKey.charAt(0)); 1.540 + } 1.541 + 1.542 + var modifiers = _parseModifiers(aEvent); 1.543 + var flags = 0; 1.544 + if (aEvent.location != undefined) { 1.545 + switch (aEvent.location) { 1.546 + case KeyboardEvent.DOM_KEY_LOCATION_STANDARD: 1.547 + flags |= utils.KEY_FLAG_LOCATION_STANDARD; 1.548 + break; 1.549 + case KeyboardEvent.DOM_KEY_LOCATION_LEFT: 1.550 + flags |= utils.KEY_FLAG_LOCATION_LEFT; 1.551 + break; 1.552 + case KeyboardEvent.DOM_KEY_LOCATION_RIGHT: 1.553 + flags |= utils.KEY_FLAG_LOCATION_RIGHT; 1.554 + break; 1.555 + case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD: 1.556 + flags |= utils.KEY_FLAG_LOCATION_NUMPAD; 1.557 + break; 1.558 + case KeyboardEvent.DOM_KEY_LOCATION_MOBILE: 1.559 + flags |= utils.KEY_FLAG_LOCATION_MOBILE; 1.560 + break; 1.561 + case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK: 1.562 + flags |= utils.KEY_FLAG_LOCATION_JOYSTICK; 1.563 + break; 1.564 + } 1.565 + } 1.566 + 1.567 + if (!("type" in aEvent) || !aEvent.type) { 1.568 + // Send keydown + (optional) keypress + keyup events. 1.569 + var keyDownDefaultHappened = 1.570 + utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags); 1.571 + if (isKeypressFiredKey(keyCode)) { 1.572 + if (!keyDownDefaultHappened) { 1.573 + flags |= utils.KEY_FLAG_PREVENT_DEFAULT; 1.574 + } 1.575 + utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags); 1.576 + } 1.577 + utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags); 1.578 + } else if (aEvent.type == "keypress") { 1.579 + // Send standalone keypress event. 1.580 + utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags); 1.581 + } else { 1.582 + // Send other standalone event than keypress. 1.583 + utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags); 1.584 + } 1.585 + } 1.586 +} 1.587 + 1.588 +var _gSeenEvent = false; 1.589 + 1.590 +/** 1.591 + * Indicate that an event with an original target of aExpectedTarget and 1.592 + * a type of aExpectedEvent is expected to be fired, or not expected to 1.593 + * be fired. 1.594 + */ 1.595 +function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName) 1.596 +{ 1.597 + if (!aExpectedTarget || !aExpectedEvent) 1.598 + return null; 1.599 + 1.600 + _gSeenEvent = false; 1.601 + 1.602 + var type = (aExpectedEvent.charAt(0) == "!") ? 1.603 + aExpectedEvent.substring(1) : aExpectedEvent; 1.604 + var eventHandler = function(event) { 1.605 + var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget && 1.606 + event.type == type); 1.607 + is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : "")); 1.608 + _gSeenEvent = true; 1.609 + }; 1.610 + 1.611 + aExpectedTarget.addEventListener(type, eventHandler, false); 1.612 + return eventHandler; 1.613 +} 1.614 + 1.615 +/** 1.616 + * Check if the event was fired or not. The event handler aEventHandler 1.617 + * will be removed. 1.618 + */ 1.619 +function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName) 1.620 +{ 1.621 + if (aEventHandler) { 1.622 + var expectEvent = (aExpectedEvent.charAt(0) != "!"); 1.623 + var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1); 1.624 + aExpectedTarget.removeEventListener(type, aEventHandler, false); 1.625 + var desc = type + " event"; 1.626 + if (!expectEvent) 1.627 + desc += " not"; 1.628 + is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired"); 1.629 + } 1.630 + 1.631 + _gSeenEvent = false; 1.632 +} 1.633 + 1.634 +/** 1.635 + * Similar to synthesizeMouse except that a test is performed to see if an 1.636 + * event is fired at the right target as a result. 1.637 + * 1.638 + * aExpectedTarget - the expected originalTarget of the event. 1.639 + * aExpectedEvent - the expected type of the event, such as 'select'. 1.640 + * aTestName - the test name when outputing results 1.641 + * 1.642 + * To test that an event is not fired, use an expected type preceded by an 1.643 + * exclamation mark, such as '!select'. This might be used to test that a 1.644 + * click on a disabled element doesn't fire certain events for instance. 1.645 + * 1.646 + * aWindow is optional, and defaults to the current window object. 1.647 + */ 1.648 +function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent, 1.649 + aExpectedTarget, aExpectedEvent, aTestName, 1.650 + aWindow) 1.651 +{ 1.652 + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); 1.653 + synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow); 1.654 + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); 1.655 +} 1.656 + 1.657 +/** 1.658 + * Similar to synthesizeKey except that a test is performed to see if an 1.659 + * event is fired at the right target as a result. 1.660 + * 1.661 + * aExpectedTarget - the expected originalTarget of the event. 1.662 + * aExpectedEvent - the expected type of the event, such as 'select'. 1.663 + * aTestName - the test name when outputing results 1.664 + * 1.665 + * To test that an event is not fired, use an expected type preceded by an 1.666 + * exclamation mark, such as '!select'. 1.667 + * 1.668 + * aWindow is optional, and defaults to the current window object. 1.669 + */ 1.670 +function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent, 1.671 + aTestName, aWindow) 1.672 +{ 1.673 + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); 1.674 + synthesizeKey(key, aEvent, aWindow); 1.675 + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); 1.676 +} 1.677 + 1.678 +function disableNonTestMouseEvents(aDisable) 1.679 +{ 1.680 + var domutils = _getDOMWindowUtils(); 1.681 + domutils.disableNonTestMouseEvents(aDisable); 1.682 +} 1.683 + 1.684 +function _getDOMWindowUtils(aWindow) 1.685 +{ 1.686 + if (!aWindow) { 1.687 + aWindow = window; 1.688 + } 1.689 + 1.690 + // we need parent.SpecialPowers for: 1.691 + // layout/base/tests/test_reftests_with_caret.html 1.692 + // chrome: toolkit/content/tests/chrome/test_findbar.xul 1.693 + // chrome: toolkit/content/tests/chrome/test_popup_anchor.xul 1.694 + if ("SpecialPowers" in window && window.SpecialPowers != undefined) { 1.695 + return SpecialPowers.getDOMWindowUtils(aWindow); 1.696 + } 1.697 + if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) { 1.698 + return parent.SpecialPowers.getDOMWindowUtils(aWindow); 1.699 + } 1.700 + 1.701 + //TODO: this is assuming we are in chrome space 1.702 + return aWindow.QueryInterface(_EU_Ci.nsIInterfaceRequestor). 1.703 + getInterface(_EU_Ci.nsIDOMWindowUtils); 1.704 +} 1.705 + 1.706 +// Must be synchronized with nsIDOMWindowUtils. 1.707 +const COMPOSITION_ATTR_RAWINPUT = 0x02; 1.708 +const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03; 1.709 +const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04; 1.710 +const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05; 1.711 + 1.712 +/** 1.713 + * Synthesize a composition event. 1.714 + * 1.715 + * @param aEvent The composition event information. This must 1.716 + * have |type| member. The value must be 1.717 + * "compositionstart", "compositionend" or 1.718 + * "compositionupdate". 1.719 + * And also this may have |data| and |locale| which 1.720 + * would be used for the value of each property of 1.721 + * the composition event. Note that the data would 1.722 + * be ignored if the event type were 1.723 + * "compositionstart". 1.724 + * @param aWindow Optional (If null, current |window| will be used) 1.725 + */ 1.726 +function synthesizeComposition(aEvent, aWindow) 1.727 +{ 1.728 + var utils = _getDOMWindowUtils(aWindow); 1.729 + if (!utils) { 1.730 + return; 1.731 + } 1.732 + 1.733 + utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "", 1.734 + aEvent.locale ? aEvent.locale : ""); 1.735 +} 1.736 +/** 1.737 + * Synthesize a text event. 1.738 + * 1.739 + * @param aEvent The text event's information, this has |composition| 1.740 + * and |caret| members. |composition| has |string| and 1.741 + * |clauses| members. |clauses| must be array object. Each 1.742 + * object has |length| and |attr|. And |caret| has |start| and 1.743 + * |length|. See the following tree image. 1.744 + * 1.745 + * aEvent 1.746 + * +-- composition 1.747 + * | +-- string 1.748 + * | +-- clauses[] 1.749 + * | +-- length 1.750 + * | +-- attr 1.751 + * +-- caret 1.752 + * +-- start 1.753 + * +-- length 1.754 + * 1.755 + * Set the composition string to |composition.string|. Set its 1.756 + * clauses information to the |clauses| array. 1.757 + * 1.758 + * When it's composing, set the each clauses' length to the 1.759 + * |composition.clauses[n].length|. The sum of the all length 1.760 + * values must be same as the length of |composition.string|. 1.761 + * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the 1.762 + * |composition.clauses[n].attr|. 1.763 + * 1.764 + * When it's not composing, set 0 to the 1.765 + * |composition.clauses[0].length| and 1.766 + * |composition.clauses[0].attr|. 1.767 + * 1.768 + * Set caret position to the |caret.start|. It's offset from 1.769 + * the start of the composition string. Set caret length to 1.770 + * |caret.length|. If it's larger than 0, it should be wide 1.771 + * caret. However, current nsEditor doesn't support wide 1.772 + * caret, therefore, you should always set 0 now. 1.773 + * 1.774 + * @param aWindow Optional (If null, current |window| will be used) 1.775 + */ 1.776 +function synthesizeText(aEvent, aWindow) 1.777 +{ 1.778 + var utils = _getDOMWindowUtils(aWindow); 1.779 + if (!utils) { 1.780 + return; 1.781 + } 1.782 + 1.783 + if (!aEvent.composition || !aEvent.composition.clauses || 1.784 + !aEvent.composition.clauses[0]) { 1.785 + return; 1.786 + } 1.787 + 1.788 + var firstClauseLength = aEvent.composition.clauses[0].length; 1.789 + var firstClauseAttr = aEvent.composition.clauses[0].attr; 1.790 + var secondClauseLength = 0; 1.791 + var secondClauseAttr = 0; 1.792 + var thirdClauseLength = 0; 1.793 + var thirdClauseAttr = 0; 1.794 + if (aEvent.composition.clauses[1]) { 1.795 + secondClauseLength = aEvent.composition.clauses[1].length; 1.796 + secondClauseAttr = aEvent.composition.clauses[1].attr; 1.797 + if (aEvent.composition.clauses[2]) { 1.798 + thirdClauseLength = aEvent.composition.clauses[2].length; 1.799 + thirdClauseAttr = aEvent.composition.clauses[2].attr; 1.800 + } 1.801 + } 1.802 + 1.803 + var caretStart = -1; 1.804 + var caretLength = 0; 1.805 + if (aEvent.caret) { 1.806 + caretStart = aEvent.caret.start; 1.807 + caretLength = aEvent.caret.length; 1.808 + } 1.809 + 1.810 + utils.sendTextEvent(aEvent.composition.string, 1.811 + firstClauseLength, firstClauseAttr, 1.812 + secondClauseLength, secondClauseAttr, 1.813 + thirdClauseLength, thirdClauseAttr, 1.814 + caretStart, caretLength); 1.815 +} 1.816 + 1.817 +/** 1.818 + * Synthesize a query selected text event. 1.819 + * 1.820 + * @param aWindow Optional (If null, current |window| will be used) 1.821 + * @return An nsIQueryContentEventResult object. If this failed, 1.822 + * the result might be null. 1.823 + */ 1.824 +function synthesizeQuerySelectedText(aWindow) 1.825 +{ 1.826 + var utils = _getDOMWindowUtils(aWindow); 1.827 + if (!utils) { 1.828 + return null; 1.829 + } 1.830 + 1.831 + return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0); 1.832 +}