|
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 } |