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

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

mercurial