PageRenderTime 54ms CodeModel.GetById 14ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://github.com/zpao/v8monkey
JavaScript | 820 lines | 449 code | 72 blank | 299 comment | 104 complexity | ad6f329c296915b072080873b94c9186 MD5 | raw file
  1// Export all available functions for Mozmill
  2var EXPORTED_SYMBOLS = ["sendMouseEvent", "sendChar", "sendString", "sendKey",
  3                        "synthesizeMouse", "synthesizeMouseScroll", "synthesizeKey",
  4                        "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
  5                        "synthesizeDragStart", "synthesizeDrop", "synthesizeText",
  6                        "disableNonTestMouseEvents", "synthesizeComposition",
  7                        "synthesizeQuerySelectedText", "synthesizeQueryTextContent",
  8                        "synthesizeQueryCaretRect", "synthesizeQueryTextRect",
  9                        "synthesizeQueryEditorRect", "synthesizeCharAtPoint",
 10                        "synthesizeSelectionSet"];
 11
 12/**
 13 * Get the array with available key events
 14 */
 15function getKeyEvent(aWindow) {
 16  var win = aWindow.wrappedJSObject ? aWindow.wrappedJSObject : aWindow;
 17  return win.KeyEvent;
 18}
 19
 20/**
 21 * EventUtils provides some utility methods for creating and sending DOM events.
 22 * Current methods:
 23 *  sendMouseEvent
 24 *  sendChar
 25 *  sendString
 26 *  sendKey
 27 */
 28
 29/**
 30 * Send a mouse event to the node aTarget (aTarget can be an id, or an
 31 * actual node) . The "event" passed in to aEvent is just a JavaScript
 32 * object with the properties set that the real mouse event object should
 33 * have. This includes the type of the mouse event.
 34 * E.g. to send an click event to the node with id 'node' you might do this:
 35 *
 36 * sendMouseEvent({type:'click'}, 'node');
 37 */
 38function sendMouseEvent(aEvent, aTarget, aWindow) {
 39  if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
 40    throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'");
 41  }
 42
 43  if (!aWindow) {
 44    aWindow = window;
 45  }
 46
 47  if (!(aTarget instanceof Element)) {
 48    aTarget = aWindow.document.getElementById(aTarget);
 49  }
 50
 51  var event = aWindow.document.createEvent('MouseEvent');
 52
 53  var typeArg          = aEvent.type;
 54  var canBubbleArg     = true;
 55  var cancelableArg    = true;
 56  var viewArg          = aWindow;
 57  var detailArg        = aEvent.detail        || (aEvent.type == 'click'     ||
 58                                                  aEvent.type == 'mousedown' ||
 59                                                  aEvent.type == 'mouseup' ? 1 : 0);
 60  var screenXArg       = aEvent.screenX       || 0;
 61  var screenYArg       = aEvent.screenY       || 0;
 62  var clientXArg       = aEvent.clientX       || 0;
 63  var clientYArg       = aEvent.clientY       || 0;
 64  var ctrlKeyArg       = aEvent.ctrlKey       || false;
 65  var altKeyArg        = aEvent.altKey        || false;
 66  var shiftKeyArg      = aEvent.shiftKey      || false;
 67  var metaKeyArg       = aEvent.metaKey       || false;
 68  var buttonArg        = aEvent.button        || 0;
 69  var relatedTargetArg = aEvent.relatedTarget || null;
 70
 71  event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
 72                       screenXArg, screenYArg, clientXArg, clientYArg,
 73                       ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
 74                       buttonArg, relatedTargetArg);
 75
 76  aTarget.dispatchEvent(event);
 77}
 78
 79/**
 80 * Send the char aChar to the node with id aTarget.  If aTarget is not
 81 * provided, use "target".  This method handles casing of chars (sends the
 82 * right charcode, and sends a shift key for uppercase chars).  No other
 83 * modifiers are handled at this point.
 84 *
 85 * For now this method only works for English letters (lower and upper case)
 86 * and the digits 0-9.
 87 *
 88 * Returns true if the keypress event was accepted (no calls to preventDefault
 89 * or anything like that), false otherwise.
 90 */
 91function sendChar(aChar, aTarget) {
 92  // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
 93  var hasShift = (aChar == aChar.toUpperCase());
 94  var charCode = aChar.charCodeAt(0);
 95  var keyCode = charCode;
 96  if (!hasShift) {
 97    // For lowercase letters, the keyCode is actually 32 less than the charCode
 98    keyCode -= 0x20;
 99  }
100
101  return __doEventDispatch(aTarget, charCode, keyCode, hasShift);
102}
103
104/**
105 * Send the string aStr to the node with id aTarget.  If aTarget is not
106 * provided, use "target".
107 *
108 * For now this method only works for English letters (lower and upper case)
109 * and the digits 0-9.
110 */
111function sendString(aStr, aTarget) {
112  for (var i = 0; i < aStr.length; ++i) {
113    sendChar(aStr.charAt(i), aTarget);
114  }
115}
116
117/**
118 * Send the non-character key aKey to the node with id aTarget. If aTarget is
119 * not provided, use "target".  The name of the key should be a lowercase
120 * version of the part that comes after "DOM_VK_" in the KeyEvent constant
121 * name for this key.  No modifiers are handled at this point.
122 *
123 * Returns true if the keypress event was accepted (no calls to preventDefault
124 * or anything like that), false otherwise.
125 */
126function sendKey(aKey, aTarget, aWindow) {
127  if (!aWindow)
128    aWindow = window;
129
130  keyName = "DOM_VK_" + aKey.toUpperCase();
131
132  if (!getKeyEvent(aWindow)[keyName]) {
133    throw "Unknown key: " + keyName;
134  }
135
136  return __doEventDispatch(aTarget, 0, getKeyEvent(aWindow)[keyName], false);
137}
138
139/**
140 * Actually perform event dispatch given a charCode, keyCode, and boolean for
141 * whether "shift" was pressed.  Send the event to the node with id aTarget.  If
142 * aTarget is not provided, use "target".
143 *
144 * Returns true if the keypress event was accepted (no calls to preventDefault
145 * or anything like that), false otherwise.
146 */
147function __doEventDispatch(aTarget, aCharCode, aKeyCode, aHasShift) {
148  if (aTarget === undefined) {
149    aTarget = "target";
150  }
151
152  var event = document.createEvent("KeyEvents");
153  event.initKeyEvent("keydown", true, true, document.defaultView,
154                     false, false, aHasShift, false,
155                     aKeyCode, 0);
156  var accepted = $(aTarget).dispatchEvent(event);
157
158  // Preventing the default keydown action also prevents the default
159  // keypress action.
160  event = document.createEvent("KeyEvents");
161  if (aCharCode) {
162    event.initKeyEvent("keypress", true, true, document.defaultView,
163                       false, false, aHasShift, false,
164                       0, aCharCode);
165  } else {
166    event.initKeyEvent("keypress", true, true, document.defaultView,
167                       false, false, aHasShift, false,
168                       aKeyCode, 0);
169  }
170  if (!accepted) {
171    event.preventDefault();
172  }
173  accepted = $(aTarget).dispatchEvent(event);
174
175  // Always send keyup
176  var event = document.createEvent("KeyEvents");
177  event.initKeyEvent("keyup", true, true, document.defaultView,
178                     false, false, aHasShift, false,
179                     aKeyCode, 0);
180  $(aTarget).dispatchEvent(event);
181  return accepted;
182}
183
184/**
185 * Parse the key modifier flags from aEvent. Used to share code between
186 * synthesizeMouse and synthesizeKey.
187 */
188function _parseModifiers(aEvent)
189{
190  var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
191                          .getService(Components.interfaces.nsIAppShellService)
192                          .hiddenDOMWindow;
193
194  const masks = Components.interfaces.nsIDOMNSEvent;
195  var mval = 0;
196  if (aEvent.shiftKey)
197    mval |= masks.SHIFT_MASK;
198  if (aEvent.ctrlKey)
199    mval |= masks.CONTROL_MASK;
200  if (aEvent.altKey)
201    mval |= masks.ALT_MASK;
202  if (aEvent.metaKey)
203    mval |= masks.META_MASK;
204  if (aEvent.accelKey)
205    mval |= (hwindow.navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
206                                                               masks.CONTROL_MASK;
207
208  return mval;
209}
210
211/**
212 * Synthesize a mouse event on a target. The actual client point is determined
213 * by taking the aTarget's client box and offseting it by aOffsetX and
214 * aOffsetY. This allows mouse clicks to be simulated by calling this method.
215 *
216 * aEvent is an object which may contain the properties:
217 *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
218 *
219 * If the type is specified, an mouse event of that type is fired. Otherwise,
220 * a mousedown followed by a mouse up is performed.
221 *
222 * aWindow is optional, and defaults to the current window object.
223 */
224function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
225{
226  if (!aWindow)
227    aWindow = window;
228
229  var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
230                      getInterface(Components.interfaces.nsIDOMWindowUtils);
231  if (utils) {
232    var button = aEvent.button || 0;
233    var clickCount = aEvent.clickCount || 1;
234    var modifiers = _parseModifiers(aEvent);
235
236    var rect = aTarget.getBoundingClientRect();
237
238    var left = rect.left + aOffsetX;
239    var top = rect.top + aOffsetY;
240
241    if (aEvent.type) {
242      utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
243    }
244    else {
245      utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
246      utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
247    }
248  }
249}
250
251/**
252 * Synthesize a mouse scroll event on a target. The actual client point is determined
253 * by taking the aTarget's client box and offseting it by aOffsetX and
254 * aOffsetY.
255 *
256 * aEvent is an object which may contain the properties:
257 *   shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
258 *
259 * If the type is specified, a mouse scroll event of that type is fired. Otherwise,
260 * "DOMMouseScroll" is used.
261 *
262 * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
263 * "vertical" is used.
264 *
265 * 'delta' is the amount to scroll by (can be positive or negative). It must
266 * be specified.
267 *
268 * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
269 *
270 * aWindow is optional, and defaults to the current window object.
271 */
272function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
273{
274  if (!aWindow)
275    aWindow = window;
276
277  var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
278                      getInterface(Components.interfaces.nsIDOMWindowUtils);
279  if (utils) {
280    // See nsMouseScrollFlags in nsGUIEvent.h
281    const kIsVertical = 0x02;
282    const kIsHorizontal = 0x04;
283    const kHasPixels = 0x08;
284
285    var button = aEvent.button || 0;
286    var modifiers = _parseModifiers(aEvent);
287
288    var rect = aTarget.getBoundingClientRect();
289
290    var left = rect.left;
291    var top = rect.top;
292
293    var type = aEvent.type || "DOMMouseScroll";
294    var axis = aEvent.axis || "vertical";
295    var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
296    if (aEvent.hasPixels) {
297      scrollFlags |= kHasPixels;
298    }
299    utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
300                               scrollFlags, aEvent.delta, modifiers);
301  }
302}
303
304/**
305 * Synthesize a key event. It is targeted at whatever would be targeted by an
306 * actual keypress by the user, typically the focused element.
307 *
308 * aKey should be either a character or a keycode starting with VK_ such as
309 * VK_ENTER.
310 *
311 * aEvent is an object which may contain the properties:
312 *   shiftKey, ctrlKey, altKey, metaKey, accessKey, type
313 *
314 * If the type is specified, a key event of that type is fired. Otherwise,
315 * a keydown, a keypress and then a keyup event are fired in sequence.
316 *
317 * aWindow is optional, and defaults to the current window object.
318 */
319function synthesizeKey(aKey, aEvent, aWindow)
320{
321  if (!aWindow)
322    aWindow = window;
323
324  var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
325                      getInterface(Components.interfaces.nsIDOMWindowUtils);
326  if (utils) {
327    var keyCode = 0, charCode = 0;
328    if (aKey.indexOf("VK_") == 0)
329      keyCode = getKeyEvent(aWindow)["DOM_" + aKey];
330    else
331      charCode = aKey.charCodeAt(0);
332
333    var modifiers = _parseModifiers(aEvent);
334
335    if (aEvent.type) {
336      utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers);
337    }
338    else {
339      var keyDownDefaultHappened =
340          utils.sendKeyEvent("keydown", keyCode, charCode, modifiers);
341      utils.sendKeyEvent("keypress", keyCode, charCode, modifiers,
342                         !keyDownDefaultHappened);
343      utils.sendKeyEvent("keyup", keyCode, charCode, modifiers);
344    }
345  }
346}
347
348var _gSeenEvent = false;
349
350/**
351 * Indicate that an event with an original target of aExpectedTarget and
352 * a type of aExpectedEvent is expected to be fired, or not expected to
353 * be fired.
354 */
355function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
356{
357  if (!aExpectedTarget || !aExpectedEvent)
358    return null;
359
360  _gSeenEvent = false;
361
362  var type = (aExpectedEvent.charAt(0) == "!") ?
363             aExpectedEvent.substring(1) : aExpectedEvent;
364  var eventHandler = function(event) {
365    var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
366                   event.type == type);
367    if (!epassed)
368      throw new Error(aTestName + " " + type + " event target " +
369                      (_gSeenEvent ? "twice" : ""));
370    _gSeenEvent = true;
371  };
372
373  aExpectedTarget.addEventListener(type, eventHandler, false);
374  return eventHandler;
375}
376
377/**
378 * Check if the event was fired or not. The event handler aEventHandler
379 * will be removed.
380 */
381function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
382{
383  if (aEventHandler) {
384    var expectEvent = (aExpectedEvent.charAt(0) != "!");
385    var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
386    aExpectedTarget.removeEventListener(type, aEventHandler, false);
387    var desc = type + " event";
388    if (expectEvent)
389      desc += " not";
390    if (_gSeenEvent != expectEvent)
391      throw new Error(aTestName + ": " + desc + " fired.");
392  }
393
394  _gSeenEvent = false;
395}
396
397/**
398 * Similar to synthesizeMouse except that a test is performed to see if an
399 * event is fired at the right target as a result.
400 *
401 * aExpectedTarget - the expected originalTarget of the event.
402 * aExpectedEvent - the expected type of the event, such as 'select'.
403 * aTestName - the test name when outputing results
404 *
405 * To test that an event is not fired, use an expected type preceded by an
406 * exclamation mark, such as '!select'. This might be used to test that a
407 * click on a disabled element doesn't fire certain events for instance.
408 *
409 * aWindow is optional, and defaults to the current window object.
410 */
411function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
412                                    aExpectedTarget, aExpectedEvent, aTestName,
413                                    aWindow)
414{
415  var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
416  synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
417  _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
418}
419
420/**
421 * Similar to synthesizeKey except that a test is performed to see if an
422 * event is fired at the right target as a result.
423 *
424 * aExpectedTarget - the expected originalTarget of the event.
425 * aExpectedEvent - the expected type of the event, such as 'select'.
426 * aTestName - the test name when outputing results
427 *
428 * To test that an event is not fired, use an expected type preceded by an
429 * exclamation mark, such as '!select'.
430 *
431 * aWindow is optional, and defaults to the current window object.
432 */
433function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
434                                  aTestName, aWindow)
435{
436  var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
437  synthesizeKey(key, aEvent, aWindow);
438  _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
439}
440
441/**
442 * Emulate a dragstart event.
443 *  element - element to fire the dragstart event on
444 *  expectedDragData - the data you expect the data transfer to contain afterwards
445 *                      This data is in the format:
446 *                         [ [ {type: value, data: value, test: function}, ... ], ... ]
447 *                     can be null
448 *  aWindow - optional; defaults to the current window object.
449 *  x - optional; initial x coordinate
450 *  y - optional; initial y coordinate
451 * Returns null if data matches.
452 * Returns the event.dataTransfer if data does not match
453 *
454 * eqTest is an optional function if comparison can't be done with x == y;
455 *   function (actualData, expectedData) {return boolean}
456 *   @param actualData from dataTransfer
457 *   @param expectedData from expectedDragData
458 * see bug 462172 for example of use
459 *
460 */
461function synthesizeDragStart(element, expectedDragData, aWindow, x, y)
462{
463  if (!aWindow)
464    aWindow = window;
465  x = x || 2;
466  y = y || 2;
467  const step = 9;
468
469  var result = "trapDrag was not called";
470  var trapDrag = function(event) {
471    try {
472      var dataTransfer = event.dataTransfer;
473      result = null;
474      if (!dataTransfer)
475        throw "no dataTransfer";
476      if (expectedDragData == null ||
477          dataTransfer.mozItemCount != expectedDragData.length)
478        throw dataTransfer;
479      for (var i = 0; i < dataTransfer.mozItemCount; i++) {
480        var dtTypes = dataTransfer.mozTypesAt(i);
481        if (dtTypes.length != expectedDragData[i].length)
482          throw dataTransfer;
483        for (var j = 0; j < dtTypes.length; j++) {
484          if (dtTypes[j] != expectedDragData[i][j].type)
485            throw dataTransfer;
486          var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i);
487          if (expectedDragData[i][j].eqTest) {
488            if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data))
489              throw dataTransfer;
490          }
491          else if (expectedDragData[i][j].data != dtData)
492            throw dataTransfer;
493        }
494      }
495    } catch(ex) {
496      result = ex;
497    }
498    event.preventDefault();
499    event.stopPropagation();
500  }
501  aWindow.addEventListener("dragstart", trapDrag, false);
502  synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow);
503  x += step; y += step;
504  synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
505  x += step; y += step;
506  synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
507  aWindow.removeEventListener("dragstart", trapDrag, false);
508  synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow);
509  return result;
510}
511
512/**
513 * Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop.
514 *  srcElement - the element to use to start the drag, usually the same as destElement
515 *               but if destElement isn't suitable to start a drag on pass a suitable
516 *               element for srcElement
517 *  destElement - the element to fire the dragover, dragleave and drop events
518 *  dragData - the data to supply for the data transfer
519 *                     This data is in the format:
520 *                       [ [ {type: value, data: value}, ...], ... ]
521 *  dropEffect - the drop effect to set during the dragstart event, or 'move' if null
522 *  aWindow - optional; defaults to the current window object.
523 *
524 * Returns the drop effect that was desired.
525 */
526function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow)
527{
528  if (!aWindow)
529    aWindow = window;
530
531  var dataTransfer;
532  var trapDrag = function(event) {
533    dataTransfer = event.dataTransfer;
534    for (var i = 0; i < dragData.length; i++) {
535      var item = dragData[i];
536      for (var j = 0; j < item.length; j++) {
537        dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
538      }
539    }
540    dataTransfer.dropEffect = dropEffect || "move";
541    event.preventDefault();
542    event.stopPropagation();
543  }
544
545  // need to use real mouse action
546  aWindow.addEventListener("dragstart", trapDrag, true);
547  synthesizeMouse(srcElement, 2, 2, { type: "mousedown" }, aWindow);
548  synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
549  synthesizeMouse(srcElement, 20, 20, { type: "mousemove" }, aWindow);
550  aWindow.removeEventListener("dragstart", trapDrag, true);
551
552  event = aWindow.document.createEvent("DragEvents");
553  event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
554  destElement.dispatchEvent(event);
555
556  var event = aWindow.document.createEvent("DragEvents");
557  event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
558  if (destElement.dispatchEvent(event)) {
559    synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
560    return "none";
561  }
562
563  if (dataTransfer.dropEffect != "none") {
564    event = aWindow.document.createEvent("DragEvents");
565    event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
566    destElement.dispatchEvent(event);
567  }
568  synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
569
570  return dataTransfer.dropEffect;
571}
572
573function disableNonTestMouseEvents(aDisable)
574{
575  var utils =
576    window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
577           getInterface(Components.interfaces.nsIDOMWindowUtils);
578  if (utils)
579    utils.disableNonTestMouseEvents(aDisable);
580}
581
582function _getDOMWindowUtils(aWindow)
583{
584  if (!aWindow) {
585    aWindow = window;
586  }
587  return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
588                 getInterface(Components.interfaces.nsIDOMWindowUtils);
589}
590
591/**
592 * Synthesize a composition event.
593 *
594 * @param aIsCompositionStart  If true, this synthesize compositionstart event.
595 *                             Otherwise, compositionend event.
596 * @param aWindow              Optional (If null, current |window| will be used)
597 */
598function synthesizeComposition(aIsCompositionStart, aWindow)
599{
600  var utils = _getDOMWindowUtils(aWindow);
601  if (!utils) {
602    return;
603  }
604
605  utils.sendCompositionEvent(aIsCompositionStart ?
606                               "compositionstart" : "compositionend");
607}
608
609/**
610 * Synthesize a text event.
611 *
612 * @param aEvent   The text event's information, this has |composition|
613 *                 and |caret| members.  |composition| has |string| and
614 *                 |clauses| members.  |clauses| must be array object.  Each
615 *                 object has |length| and |attr|.  And |caret| has |start| and
616 *                 |length|.  See the following tree image.
617 *
618 *                 aEvent
619 *                   +-- composition
620 *                   |     +-- string
621 *                   |     +-- clauses[]
622 *                   |           +-- length
623 *                   |           +-- attr
624 *                   +-- caret
625 *                         +-- start
626 *                         +-- length
627 *
628 *                 Set the composition string to |composition.string|.  Set its
629 *                 clauses information to the |clauses| array.
630 *
631 *                 When it's composing, set the each clauses' length to the
632 *                 |composition.clauses[n].length|.  The sum of the all length
633 *                 values must be same as the length of |composition.string|.
634 *                 Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
635 *                 |composition.clauses[n].attr|.
636 *
637 *                 When it's not composing, set 0 to the
638 *                 |composition.clauses[0].length| and
639 *                 |composition.clauses[0].attr|.
640 *
641 *                 Set caret position to the |caret.start|. It's offset from
642 *                 the start of the composition string.  Set caret length to
643 *                 |caret.length|.  If it's larger than 0, it should be wide
644 *                 caret.  However, current nsEditor doesn't support wide
645 *                 caret, therefore, you should always set 0 now.
646 *
647 * @param aWindow  Optional (If null, current |window| will be used)
648 */
649function synthesizeText(aEvent, aWindow)
650{
651  var utils = _getDOMWindowUtils(aWindow);
652  if (!utils) {
653    return;
654  }
655
656  if (!aEvent.composition || !aEvent.composition.clauses ||
657      !aEvent.composition.clauses[0]) {
658    return;
659  }
660
661  var firstClauseLength = aEvent.composition.clauses[0].length;
662  var firstClauseAttr   = aEvent.composition.clauses[0].attr;
663  var secondClauseLength = 0;
664  var secondClauseAttr = 0;
665  var thirdClauseLength = 0;
666  var thirdClauseAttr = 0;
667  if (aEvent.composition.clauses[1]) {
668    secondClauseLength = aEvent.composition.clauses[1].length;
669    secondClauseAttr   = aEvent.composition.clauses[1].attr;
670    if (aEvent.composition.clauses[2]) {
671      thirdClauseLength = aEvent.composition.clauses[2].length;
672      thirdClauseAttr   = aEvent.composition.clauses[2].attr;
673    }
674  }
675
676  var caretStart = -1;
677  var caretLength = 0;
678  if (aEvent.caret) {
679    caretStart = aEvent.caret.start;
680    caretLength = aEvent.caret.length;
681  }
682
683  utils.sendTextEvent(aEvent.composition.string,
684                      firstClauseLength, firstClauseAttr,
685                      secondClauseLength, secondClauseAttr,
686                      thirdClauseLength, thirdClauseAttr,
687                      caretStart, caretLength);
688}
689
690/**
691 * Synthesize a query selected text event.
692 *
693 * @param aWindow  Optional (If null, current |window| will be used)
694 * @return         An nsIQueryContentEventResult object.  If this failed,
695 *                 the result might be null.
696 */
697function synthesizeQuerySelectedText(aWindow)
698{
699  var utils = _getDOMWindowUtils(aWindow);
700  if (!utils) {
701    return nsnull;
702  }
703  return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
704}
705
706/**
707 * Synthesize a query text content event.
708 *
709 * @param aOffset  The character offset.  0 means the first character in the
710 *                 selection root.
711 * @param aLength  The length of getting text.  If the length is too long,
712 *                 the extra length is ignored.
713 * @param aWindow  Optional (If null, current |window| will be used)
714 * @return         An nsIQueryContentEventResult object.  If this failed,
715 *                 the result might be null.
716 */
717function synthesizeQueryTextContent(aOffset, aLength, aWindow)
718{
719  var utils = _getDOMWindowUtils(aWindow);
720  if (!utils) {
721    return nsnull;
722  }
723  return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
724                                     aOffset, aLength, 0, 0);
725}
726
727/**
728 * Synthesize a query caret rect event.
729 *
730 * @param aOffset  The caret offset.  0 means left side of the first character
731 *                 in the selection root.
732 * @param aWindow  Optional (If null, current |window| will be used)
733 * @return         An nsIQueryContentEventResult object.  If this failed,
734 *                 the result might be null.
735 */
736function synthesizeQueryCaretRect(aOffset, aWindow)
737{
738  var utils = _getDOMWindowUtils(aWindow);
739  if (!utils) {
740    return nsnull;
741  }
742  return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
743                                     aOffset, 0, 0, 0);
744}
745
746/**
747 * Synthesize a query text rect event.
748 *
749 * @param aOffset  The character offset.  0 means the first character in the
750 *                 selection root.
751 * @param aLength  The length of the text.  If the length is too long,
752 *                 the extra length is ignored.
753 * @param aWindow  Optional (If null, current |window| will be used)
754 * @return         An nsIQueryContentEventResult object.  If this failed,
755 *                 the result might be null.
756 */
757function synthesizeQueryTextRect(aOffset, aLength, aWindow)
758{
759  var utils = _getDOMWindowUtils(aWindow);
760  if (!utils) {
761    return nsnull;
762  }
763  return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT,
764                                     aOffset, aLength, 0, 0);
765}
766
767/**
768 * Synthesize a query editor rect event.
769 *
770 * @param aWindow  Optional (If null, current |window| will be used)
771 * @return         An nsIQueryContentEventResult object.  If this failed,
772 *                 the result might be null.
773 */
774function synthesizeQueryEditorRect(aWindow)
775{
776  var utils = _getDOMWindowUtils(aWindow);
777  if (!utils) {
778    return nsnull;
779  }
780  return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0);
781}
782
783/**
784 * Synthesize a character at point event.
785 *
786 * @param aX, aY   The offset in the client area of the DOM window.
787 * @param aWindow  Optional (If null, current |window| will be used)
788 * @return         An nsIQueryContentEventResult object.  If this failed,
789 *                 the result might be null.
790 */
791function synthesizeCharAtPoint(aX, aY, aWindow)
792{
793  var utils = _getDOMWindowUtils(aWindow);
794  if (!utils) {
795    return nsnull;
796  }
797  return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT,
798                                     0, 0, aX, aY);
799}
800
801/**
802 * Synthesize a selection set event.
803 *
804 * @param aOffset  The character offset.  0 means the first character in the
805 *                 selection root.
806 * @param aLength  The length of the text.  If the length is too long,
807 *                 the extra length is ignored.
808 * @param aReverse If true, the selection is from |aOffset + aLength| to
809 *                 |aOffset|.  Otherwise, from |aOffset| to |aOffset + aLength|.
810 * @param aWindow  Optional (If null, current |window| will be used)
811 * @return         True, if succeeded.  Otherwise false.
812 */
813function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
814{
815  var utils = _getDOMWindowUtils(aWindow);
816  if (!utils) {
817    return false;
818  }
819  return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
820}