1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/marionette/EventUtils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,673 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * EventUtils provides some utility methods for creating and sending DOM events. 1.10 + * Current methods: 1.11 + * sendMouseEvent 1.12 + * sendChar 1.13 + * sendString 1.14 + * sendKey 1.15 + * synthesizeMouse 1.16 + * synthesizeMouseAtCenter 1.17 + * synthesizeMouseScroll 1.18 + * synthesizeKey 1.19 + * synthesizeMouseExpectEvent 1.20 + * synthesizeKeyExpectEvent 1.21 + * 1.22 + * When adding methods to this file, please add a performance test for it. 1.23 + */ 1.24 + 1.25 +/** 1.26 + * Send a mouse event to the node aTarget (aTarget can be an id, or an 1.27 + * actual node) . The "event" passed in to aEvent is just a JavaScript 1.28 + * object with the properties set that the real mouse event object should 1.29 + * have. This includes the type of the mouse event. 1.30 + * E.g. to send an click event to the node with id 'node' you might do this: 1.31 + * 1.32 + * sendMouseEvent({type:'click'}, 'node'); 1.33 + */ 1.34 +function getElement(id) { 1.35 + return ((typeof(id) == "string") ? 1.36 + document.getElementById(id) : id); 1.37 +}; 1.38 + 1.39 +this.$ = this.getElement; 1.40 +const KeyEvent = Components.interfaces.nsIDOMKeyEvent; 1.41 + 1.42 +function sendMouseEvent(aEvent, aTarget, aWindow) { 1.43 + if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) { 1.44 + throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'"); 1.45 + } 1.46 + 1.47 + if (!aWindow) { 1.48 + aWindow = window; 1.49 + } 1.50 + 1.51 + if (!(aTarget instanceof Element)) { 1.52 + aTarget = aWindow.document.getElementById(aTarget); 1.53 + } 1.54 + 1.55 + var event = aWindow.document.createEvent('MouseEvent'); 1.56 + 1.57 + var typeArg = aEvent.type; 1.58 + var canBubbleArg = true; 1.59 + var cancelableArg = true; 1.60 + var viewArg = aWindow; 1.61 + var detailArg = aEvent.detail || (aEvent.type == 'click' || 1.62 + aEvent.type == 'mousedown' || 1.63 + aEvent.type == 'mouseup' ? 1 : 1.64 + aEvent.type == 'dblclick'? 2 : 0); 1.65 + var screenXArg = aEvent.screenX || 0; 1.66 + var screenYArg = aEvent.screenY || 0; 1.67 + var clientXArg = aEvent.clientX || 0; 1.68 + var clientYArg = aEvent.clientY || 0; 1.69 + var ctrlKeyArg = aEvent.ctrlKey || false; 1.70 + var altKeyArg = aEvent.altKey || false; 1.71 + var shiftKeyArg = aEvent.shiftKey || false; 1.72 + var metaKeyArg = aEvent.metaKey || false; 1.73 + var buttonArg = aEvent.button || 0; 1.74 + var relatedTargetArg = aEvent.relatedTarget || null; 1.75 + 1.76 + event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg, 1.77 + screenXArg, screenYArg, clientXArg, clientYArg, 1.78 + ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, 1.79 + buttonArg, relatedTargetArg); 1.80 + 1.81 + //removed: SpecialPowers.dispatchEvent(aWindow, aTarget, event); 1.82 +} 1.83 + 1.84 +/** 1.85 + * Send the char aChar to the focused element. This method handles casing of 1.86 + * chars (sends the right charcode, and sends a shift key for uppercase chars). 1.87 + * No other modifiers are handled at this point. 1.88 + * 1.89 + * For now this method only works for English letters (lower and upper case) 1.90 + * and the digits 0-9. 1.91 + */ 1.92 +function sendChar(aChar, aWindow) { 1.93 + // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9. 1.94 + var hasShift = (aChar == aChar.toUpperCase()); 1.95 + synthesizeKey(aChar, { shiftKey: hasShift }, aWindow); 1.96 +} 1.97 + 1.98 +/** 1.99 + * Send the string aStr to the focused element. 1.100 + * 1.101 + * For now this method only works for English letters (lower and upper case) 1.102 + * and the digits 0-9. 1.103 + */ 1.104 +function sendString(aStr, aWindow) { 1.105 + for (var i = 0; i < aStr.length; ++i) { 1.106 + sendChar(aStr.charAt(i), aWindow); 1.107 + } 1.108 +} 1.109 + 1.110 +/** 1.111 + * Send the non-character key aKey to the focused node. 1.112 + * The name of the key should be the part that comes after "DOM_VK_" in the 1.113 + * KeyEvent constant name for this key. 1.114 + * No modifiers are handled at this point. 1.115 + */ 1.116 +function sendKey(aKey, aWindow) { 1.117 + var keyName = "VK_" + aKey.toUpperCase(); 1.118 + synthesizeKey(keyName, { shiftKey: false }, aWindow); 1.119 +} 1.120 + 1.121 +/** 1.122 + * Parse the key modifier flags from aEvent. Used to share code between 1.123 + * synthesizeMouse and synthesizeKey. 1.124 + */ 1.125 +function _parseModifiers(aEvent) 1.126 +{ 1.127 + const masks = Components.interfaces.nsIDOMNSEvent; 1.128 + var mval = 0; 1.129 + if (aEvent.shiftKey) 1.130 + mval |= masks.SHIFT_MASK; 1.131 + if (aEvent.ctrlKey) 1.132 + mval |= masks.CONTROL_MASK; 1.133 + if (aEvent.altKey) 1.134 + mval |= masks.ALT_MASK; 1.135 + if (aEvent.metaKey) 1.136 + mval |= masks.META_MASK; 1.137 + if (aEvent.accelKey) 1.138 + mval |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK : 1.139 + masks.CONTROL_MASK; 1.140 + 1.141 + return mval; 1.142 +} 1.143 + 1.144 +/** 1.145 + * Synthesize a mouse event on a target. The actual client point is determined 1.146 + * by taking the aTarget's client box and offseting it by aOffsetX and 1.147 + * aOffsetY. This allows mouse clicks to be simulated by calling this method. 1.148 + * 1.149 + * aEvent is an object which may contain the properties: 1.150 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type 1.151 + * 1.152 + * If the type is specified, an mouse event of that type is fired. Otherwise, 1.153 + * a mousedown followed by a mouse up is performed. 1.154 + * 1.155 + * aWindow is optional, and defaults to the current window object. 1.156 + */ 1.157 +function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) 1.158 +{ 1.159 + var rect = aTarget.getBoundingClientRect(); 1.160 + synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, 1.161 + aEvent, aWindow); 1.162 +} 1.163 + 1.164 +/* 1.165 + * Synthesize a mouse event at a particular point in aWindow. 1.166 + * 1.167 + * aEvent is an object which may contain the properties: 1.168 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type 1.169 + * 1.170 + * If the type is specified, an mouse event of that type is fired. Otherwise, 1.171 + * a mousedown followed by a mouse up is performed. 1.172 + * 1.173 + * aWindow is optional, and defaults to the current window object. 1.174 + */ 1.175 +function synthesizeMouseAtPoint(left, top, aEvent, aWindow) 1.176 +{ 1.177 + var utils = _getDOMWindowUtils(aWindow); 1.178 + 1.179 + if (utils) { 1.180 + var button = aEvent.button || 0; 1.181 + var clickCount = aEvent.clickCount || 1; 1.182 + var modifiers = _parseModifiers(aEvent); 1.183 + 1.184 + if (("type" in aEvent) && aEvent.type) { 1.185 + utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers); 1.186 + } 1.187 + else { 1.188 + utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers); 1.189 + utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers); 1.190 + } 1.191 + } 1.192 +} 1.193 + 1.194 +// Call synthesizeMouse with coordinates at the center of aTarget. 1.195 +function synthesizeMouseAtCenter(aTarget, aEvent, aWindow) 1.196 +{ 1.197 + var rect = aTarget.getBoundingClientRect(); 1.198 + synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent, 1.199 + aWindow); 1.200 +} 1.201 + 1.202 +/** 1.203 + * Synthesize a mouse scroll event on a target. The actual client point is determined 1.204 + * by taking the aTarget's client box and offseting it by aOffsetX and 1.205 + * aOffsetY. 1.206 + * 1.207 + * aEvent is an object which may contain the properties: 1.208 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels 1.209 + * 1.210 + * If the type is specified, a mouse scroll event of that type is fired. Otherwise, 1.211 + * "DOMMouseScroll" is used. 1.212 + * 1.213 + * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified, 1.214 + * "vertical" is used. 1.215 + * 1.216 + * 'delta' is the amount to scroll by (can be positive or negative). It must 1.217 + * be specified. 1.218 + * 1.219 + * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags. 1.220 + * 1.221 + * 'isMomentum' specifies whether kIsMomentum should be set in the scrollFlags. 1.222 + * 1.223 + * aWindow is optional, and defaults to the current window object. 1.224 + */ 1.225 +function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) 1.226 +{ 1.227 + var utils = _getDOMWindowUtils(aWindow); 1.228 + 1.229 + if (utils) { 1.230 + // See nsMouseScrollFlags in nsGUIEvent.h 1.231 + const kIsVertical = 0x02; 1.232 + const kIsHorizontal = 0x04; 1.233 + const kHasPixels = 0x08; 1.234 + const kIsMomentum = 0x40; 1.235 + 1.236 + var button = aEvent.button || 0; 1.237 + var modifiers = _parseModifiers(aEvent); 1.238 + 1.239 + var rect = aTarget.getBoundingClientRect(); 1.240 + 1.241 + var left = rect.left; 1.242 + var top = rect.top; 1.243 + 1.244 + var type = (("type" in aEvent) && aEvent.type) || "DOMMouseScroll"; 1.245 + var axis = aEvent.axis || "vertical"; 1.246 + var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical; 1.247 + if (aEvent.hasPixels) { 1.248 + scrollFlags |= kHasPixels; 1.249 + } 1.250 + if (aEvent.isMomentum) { 1.251 + scrollFlags |= kIsMomentum; 1.252 + } 1.253 + utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button, 1.254 + scrollFlags, aEvent.delta, modifiers); 1.255 + } 1.256 +} 1.257 + 1.258 +function _computeKeyCodeFromChar(aChar) 1.259 +{ 1.260 + if (aChar.length != 1) { 1.261 + return 0; 1.262 + } 1.263 + const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent; 1.264 + if (aChar >= 'a' && aChar <= 'z') { 1.265 + return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0); 1.266 + } 1.267 + if (aChar >= 'A' && aChar <= 'Z') { 1.268 + return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0); 1.269 + } 1.270 + if (aChar >= '0' && aChar <= '9') { 1.271 + return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0); 1.272 + } 1.273 + // returns US keyboard layout's keycode 1.274 + switch (aChar) { 1.275 + case '~': 1.276 + case '`': 1.277 + return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE; 1.278 + case '!': 1.279 + return nsIDOMKeyEvent.DOM_VK_1; 1.280 + case '@': 1.281 + return nsIDOMKeyEvent.DOM_VK_2; 1.282 + case '#': 1.283 + return nsIDOMKeyEvent.DOM_VK_3; 1.284 + case '$': 1.285 + return nsIDOMKeyEvent.DOM_VK_4; 1.286 + case '%': 1.287 + return nsIDOMKeyEvent.DOM_VK_5; 1.288 + case '^': 1.289 + return nsIDOMKeyEvent.DOM_VK_6; 1.290 + case '&': 1.291 + return nsIDOMKeyEvent.DOM_VK_7; 1.292 + case '*': 1.293 + return nsIDOMKeyEvent.DOM_VK_8; 1.294 + case '(': 1.295 + return nsIDOMKeyEvent.DOM_VK_9; 1.296 + case ')': 1.297 + return nsIDOMKeyEvent.DOM_VK_0; 1.298 + case '-': 1.299 + case '_': 1.300 + return nsIDOMKeyEvent.DOM_VK_SUBTRACT; 1.301 + case '+': 1.302 + case '=': 1.303 + return nsIDOMKeyEvent.DOM_VK_EQUALS; 1.304 + case '{': 1.305 + case '[': 1.306 + return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET; 1.307 + case '}': 1.308 + case ']': 1.309 + return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET; 1.310 + case '|': 1.311 + case '\\': 1.312 + return nsIDOMKeyEvent.DOM_VK_BACK_SLASH; 1.313 + case ':': 1.314 + case ';': 1.315 + return nsIDOMKeyEvent.DOM_VK_SEMICOLON; 1.316 + case '\'': 1.317 + case '"': 1.318 + return nsIDOMKeyEvent.DOM_VK_QUOTE; 1.319 + case '<': 1.320 + case ',': 1.321 + return nsIDOMKeyEvent.DOM_VK_COMMA; 1.322 + case '>': 1.323 + case '.': 1.324 + return nsIDOMKeyEvent.DOM_VK_PERIOD; 1.325 + case '?': 1.326 + case '/': 1.327 + return nsIDOMKeyEvent.DOM_VK_SLASH; 1.328 + case '\n': 1.329 + return nsIDOMKeyEvent.DOM_VK_RETURN; 1.330 + default: 1.331 + return 0; 1.332 + } 1.333 +} 1.334 + 1.335 +/** 1.336 + * isKeypressFiredKey() returns TRUE if the given key should cause keypress 1.337 + * event when widget handles the native key event. Otherwise, FALSE. 1.338 + * 1.339 + * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key 1.340 + * name begins with "VK_", or a character. 1.341 + */ 1.342 +function isKeypressFiredKey(aDOMKeyCode) 1.343 +{ 1.344 + const KeyEvent = Components.interfaces.nsIDOMKeyEvent; 1.345 + if (typeof(aDOMKeyCode) == "string") { 1.346 + if (aDOMKeyCode.indexOf("VK_") == 0) { 1.347 + aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode]; 1.348 + if (!aDOMKeyCode) { 1.349 + throw "Unknown key: " + aDOMKeyCode; 1.350 + } 1.351 + } else { 1.352 + // If the key generates a character, it must cause a keypress event. 1.353 + return true; 1.354 + } 1.355 + } 1.356 + switch (aDOMKeyCode) { 1.357 + case KeyEvent.DOM_VK_SHIFT: 1.358 + case KeyEvent.DOM_VK_CONTROL: 1.359 + case KeyEvent.DOM_VK_ALT: 1.360 + case KeyEvent.DOM_VK_CAPS_LOCK: 1.361 + case KeyEvent.DOM_VK_NUM_LOCK: 1.362 + case KeyEvent.DOM_VK_SCROLL_LOCK: 1.363 + case KeyEvent.DOM_VK_META: 1.364 + return false; 1.365 + default: 1.366 + return true; 1.367 + } 1.368 +} 1.369 + 1.370 +/** 1.371 + * Synthesize a key event. It is targeted at whatever would be targeted by an 1.372 + * actual keypress by the user, typically the focused element. 1.373 + * 1.374 + * aKey should be either a character or a keycode starting with VK_ such as 1.375 + * VK_RETURN. 1.376 + * 1.377 + * aEvent is an object which may contain the properties: 1.378 + * shiftKey, ctrlKey, altKey, metaKey, accessKey, type 1.379 + * 1.380 + * If the type is specified, a key event of that type is fired. Otherwise, 1.381 + * a keydown, a keypress and then a keyup event are fired in sequence. 1.382 + * 1.383 + * aWindow is optional, and defaults to the current window object. 1.384 + */ 1.385 +function synthesizeKey(aKey, aEvent, aWindow) 1.386 +{ 1.387 + var utils = _getDOMWindowUtils(aWindow); 1.388 + if (utils) { 1.389 + var keyCode = 0, charCode = 0; 1.390 + if (aKey.indexOf("VK_") == 0) { 1.391 + keyCode = KeyEvent["DOM_" + aKey]; 1.392 + if (!keyCode) { 1.393 + throw "Unknown key: " + aKey; 1.394 + } 1.395 + } else { 1.396 + charCode = aKey.charCodeAt(0); 1.397 + keyCode = _computeKeyCodeFromChar(aKey.charAt(0)); 1.398 + } 1.399 + 1.400 + var modifiers = _parseModifiers(aEvent); 1.401 + 1.402 + if (!("type" in aEvent) || !aEvent.type) { 1.403 + // Send keydown + (optional) keypress + keyup events. 1.404 + var keyDownDefaultHappened = 1.405 + utils.sendKeyEvent("keydown", keyCode, 0, modifiers); 1.406 + if (isKeypressFiredKey(keyCode)) { 1.407 + utils.sendKeyEvent("keypress", charCode ? 0 : keyCode, charCode, 1.408 + modifiers, !keyDownDefaultHappened); 1.409 + } 1.410 + utils.sendKeyEvent("keyup", keyCode, 0, modifiers); 1.411 + } else if (aEvent.type == "keypress") { 1.412 + // Send standalone keypress event. 1.413 + utils.sendKeyEvent(aEvent.type, charCode ? 0 : keyCode, 1.414 + charCode, modifiers); 1.415 + } else { 1.416 + // Send other standalone event than keypress. 1.417 + utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers); 1.418 + } 1.419 + } 1.420 +} 1.421 + 1.422 +var _gSeenEvent = false; 1.423 + 1.424 +/** 1.425 + * Indicate that an event with an original target of aExpectedTarget and 1.426 + * a type of aExpectedEvent is expected to be fired, or not expected to 1.427 + * be fired. 1.428 + */ 1.429 +function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName) 1.430 +{ 1.431 + if (!aExpectedTarget || !aExpectedEvent) 1.432 + return null; 1.433 + 1.434 + _gSeenEvent = false; 1.435 + 1.436 + var type = (aExpectedEvent.charAt(0) == "!") ? 1.437 + aExpectedEvent.substring(1) : aExpectedEvent; 1.438 + var eventHandler = function(event) { 1.439 + var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget && 1.440 + event.type == type); 1.441 + is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : "")); 1.442 + _gSeenEvent = true; 1.443 + }; 1.444 + 1.445 + aExpectedTarget.addEventListener(type, eventHandler, false); 1.446 + return eventHandler; 1.447 +} 1.448 + 1.449 +/** 1.450 + * Check if the event was fired or not. The event handler aEventHandler 1.451 + * will be removed. 1.452 + */ 1.453 +function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName) 1.454 +{ 1.455 + if (aEventHandler) { 1.456 + var expectEvent = (aExpectedEvent.charAt(0) != "!"); 1.457 + var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1); 1.458 + aExpectedTarget.removeEventListener(type, aEventHandler, false); 1.459 + var desc = type + " event"; 1.460 + if (!expectEvent) 1.461 + desc += " not"; 1.462 + is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired"); 1.463 + } 1.464 + 1.465 + _gSeenEvent = false; 1.466 +} 1.467 + 1.468 +/** 1.469 + * Similar to synthesizeMouse except that a test is performed to see if an 1.470 + * event is fired at the right target as a result. 1.471 + * 1.472 + * aExpectedTarget - the expected originalTarget of the event. 1.473 + * aExpectedEvent - the expected type of the event, such as 'select'. 1.474 + * aTestName - the test name when outputing results 1.475 + * 1.476 + * To test that an event is not fired, use an expected type preceded by an 1.477 + * exclamation mark, such as '!select'. This might be used to test that a 1.478 + * click on a disabled element doesn't fire certain events for instance. 1.479 + * 1.480 + * aWindow is optional, and defaults to the current window object. 1.481 + */ 1.482 +function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent, 1.483 + aExpectedTarget, aExpectedEvent, aTestName, 1.484 + aWindow) 1.485 +{ 1.486 + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); 1.487 + synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow); 1.488 + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); 1.489 +} 1.490 + 1.491 +/** 1.492 + * Similar to synthesizeKey except that a test is performed to see if an 1.493 + * event is fired at the right target as a result. 1.494 + * 1.495 + * aExpectedTarget - the expected originalTarget of the event. 1.496 + * aExpectedEvent - the expected type of the event, such as 'select'. 1.497 + * aTestName - the test name when outputing results 1.498 + * 1.499 + * To test that an event is not fired, use an expected type preceded by an 1.500 + * exclamation mark, such as '!select'. 1.501 + * 1.502 + * aWindow is optional, and defaults to the current window object. 1.503 + */ 1.504 +function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent, 1.505 + aTestName, aWindow) 1.506 +{ 1.507 + var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); 1.508 + synthesizeKey(key, aEvent, aWindow); 1.509 + _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); 1.510 +} 1.511 + 1.512 +function disableNonTestMouseEvents(aDisable) 1.513 +{ 1.514 + var domutils = _getDOMWindowUtils(); 1.515 + domutils.disableNonTestMouseEvents(aDisable); 1.516 +} 1.517 + 1.518 +function _getDOMWindowUtils(aWindow) 1.519 +{ 1.520 + if (!aWindow) { 1.521 + aWindow = window; 1.522 + } 1.523 + 1.524 + //TODO: this is assuming we are in chrome space 1.525 + return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor). 1.526 + getInterface(Components.interfaces.nsIDOMWindowUtils); 1.527 +} 1.528 + 1.529 +// Must be synchronized with nsIDOMWindowUtils. 1.530 +const COMPOSITION_ATTR_RAWINPUT = 0x02; 1.531 +const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03; 1.532 +const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04; 1.533 +const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05; 1.534 + 1.535 +/** 1.536 + * Synthesize a composition event. 1.537 + * 1.538 + * @param aEvent The composition event information. This must 1.539 + * have |type| member. The value must be 1.540 + * "compositionstart", "compositionend" or 1.541 + * "compositionupdate". 1.542 + * And also this may have |data| and |locale| which 1.543 + * would be used for the value of each property of 1.544 + * the composition event. Note that the data would 1.545 + * be ignored if the event type were 1.546 + * "compositionstart". 1.547 + * @param aWindow Optional (If null, current |window| will be used) 1.548 + */ 1.549 +function synthesizeComposition(aEvent, aWindow) 1.550 +{ 1.551 + var utils = _getDOMWindowUtils(aWindow); 1.552 + if (!utils) { 1.553 + return; 1.554 + } 1.555 + 1.556 + utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "", 1.557 + aEvent.locale ? aEvent.locale : ""); 1.558 +} 1.559 +/** 1.560 + * Synthesize a text event. 1.561 + * 1.562 + * @param aEvent The text event's information, this has |composition| 1.563 + * and |caret| members. |composition| has |string| and 1.564 + * |clauses| members. |clauses| must be array object. Each 1.565 + * object has |length| and |attr|. And |caret| has |start| and 1.566 + * |length|. See the following tree image. 1.567 + * 1.568 + * aEvent 1.569 + * +-- composition 1.570 + * | +-- string 1.571 + * | +-- clauses[] 1.572 + * | +-- length 1.573 + * | +-- attr 1.574 + * +-- caret 1.575 + * +-- start 1.576 + * +-- length 1.577 + * 1.578 + * Set the composition string to |composition.string|. Set its 1.579 + * clauses information to the |clauses| array. 1.580 + * 1.581 + * When it's composing, set the each clauses' length to the 1.582 + * |composition.clauses[n].length|. The sum of the all length 1.583 + * values must be same as the length of |composition.string|. 1.584 + * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the 1.585 + * |composition.clauses[n].attr|. 1.586 + * 1.587 + * When it's not composing, set 0 to the 1.588 + * |composition.clauses[0].length| and 1.589 + * |composition.clauses[0].attr|. 1.590 + * 1.591 + * Set caret position to the |caret.start|. It's offset from 1.592 + * the start of the composition string. Set caret length to 1.593 + * |caret.length|. If it's larger than 0, it should be wide 1.594 + * caret. However, current nsEditor doesn't support wide 1.595 + * caret, therefore, you should always set 0 now. 1.596 + * 1.597 + * @param aWindow Optional (If null, current |window| will be used) 1.598 + */ 1.599 +function synthesizeText(aEvent, aWindow) 1.600 +{ 1.601 + var utils = _getDOMWindowUtils(aWindow); 1.602 + if (!utils) { 1.603 + return; 1.604 + } 1.605 + 1.606 + if (!aEvent.composition || !aEvent.composition.clauses || 1.607 + !aEvent.composition.clauses[0]) { 1.608 + return; 1.609 + } 1.610 + 1.611 + var firstClauseLength = aEvent.composition.clauses[0].length; 1.612 + var firstClauseAttr = aEvent.composition.clauses[0].attr; 1.613 + var secondClauseLength = 0; 1.614 + var secondClauseAttr = 0; 1.615 + var thirdClauseLength = 0; 1.616 + var thirdClauseAttr = 0; 1.617 + if (aEvent.composition.clauses[1]) { 1.618 + secondClauseLength = aEvent.composition.clauses[1].length; 1.619 + secondClauseAttr = aEvent.composition.clauses[1].attr; 1.620 + if (aEvent.composition.clauses[2]) { 1.621 + thirdClauseLength = aEvent.composition.clauses[2].length; 1.622 + thirdClauseAttr = aEvent.composition.clauses[2].attr; 1.623 + } 1.624 + } 1.625 + 1.626 + var caretStart = -1; 1.627 + var caretLength = 0; 1.628 + if (aEvent.caret) { 1.629 + caretStart = aEvent.caret.start; 1.630 + caretLength = aEvent.caret.length; 1.631 + } 1.632 + 1.633 + utils.sendTextEvent(aEvent.composition.string, 1.634 + firstClauseLength, firstClauseAttr, 1.635 + secondClauseLength, secondClauseAttr, 1.636 + thirdClauseLength, thirdClauseAttr, 1.637 + caretStart, caretLength); 1.638 +} 1.639 + 1.640 +/** 1.641 + * Synthesize a query selected text event. 1.642 + * 1.643 + * @param aWindow Optional (If null, current |window| will be used) 1.644 + * @return An nsIQueryContentEventResult object. If this failed, 1.645 + * the result might be null. 1.646 + */ 1.647 +function synthesizeQuerySelectedText(aWindow) 1.648 +{ 1.649 + var utils = _getDOMWindowUtils(aWindow); 1.650 + if (!utils) { 1.651 + return null; 1.652 + } 1.653 + 1.654 + return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0); 1.655 +} 1.656 + 1.657 +/** 1.658 + * Synthesize a selection set event. 1.659 + * 1.660 + * @param aOffset The character offset. 0 means the first character in the 1.661 + * selection root. 1.662 + * @param aLength The length of the text. If the length is too long, 1.663 + * the extra length is ignored. 1.664 + * @param aReverse If true, the selection is from |aOffset + aLength| to 1.665 + * |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|. 1.666 + * @param aWindow Optional (If null, current |window| will be used) 1.667 + * @return True, if succeeded. Otherwise false. 1.668 + */ 1.669 +function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow) 1.670 +{ 1.671 + var utils = _getDOMWindowUtils(aWindow); 1.672 + if (!utils) { 1.673 + return false; 1.674 + } 1.675 + return utils.sendSelectionSetEvent(aOffset, aLength, aReverse); 1.676 +}