PageRenderTime 47ms CodeModel.GetById 15ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/services/sync/tps/extensions/mozmill/resource/modules/mozelement.js

http://github.com/zpao/v8monkey
JavaScript | 702 lines | 393 code | 96 blank | 213 comment | 124 complexity | bad47cde540c028086579e8882e04e78 MD5 | raw file
  1/* ***** BEGIN LICENSE BLOCK *****
  2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3 *
  4 * The contents of this file are subject to the Mozilla Public License Version
  5 * 1.1 (the "License"); you may not use this file except in compliance with
  6 * the License. You may obtain a copy of the License at
  7 * http://www.mozilla.org/MPL/
  8 *
  9 * Software distributed under the License is distributed on an "AS IS" basis,
 10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11 * for the specific language governing rights and limitations under the
 12 * License.
 13 *
 14 * The Original Code is Mozmill Elements.
 15 *
 16 * The Initial Developer of the Original Code is
 17 * Mozilla Corporation
 18 *
 19 * Portions created by the Initial Developer are Copyright (C) 2011
 20 * the Initial Developer. All Rights Reserved.
 21 *
 22 * Contributor(s):
 23 * Andrew Halberstadt <halbersa@gmail.com>
 24 *
 25 * Alternatively, the contents of this file may be used under the terms of
 26 * either the GNU General Public License Version 2 or later (the "GPL"), or
 27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 28 * in which case the provisions of the GPL or the LGPL are applicable instead
 29 * of those above. If you wish to allow use of your version of this file only
 30 * under the terms of either the GPL or the LGPL, and not to allow others to
 31 * use your version of this file under the terms of the MPL, indicate your
 32 * decision by deleting the provisions above and replace them with the notice
 33 * and other provisions required by the GPL or the LGPL. If you do not delete
 34 * the provisions above, a recipient may use your version of this file under
 35 * the terms of any one of the MPL, the GPL or the LGPL.
 36 *
 37 * ***** END LICENSE BLOCK ***** */
 38
 39var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
 40                        "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
 41                        "MozMillTextBox", "subclasses",
 42                       ];
 43
 44var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
 45var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
 46var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
 47var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
 48
 49// A list of all the subclasses available.  Shared modules can push their own subclasses onto this list
 50var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
 51
 52/**
 53 * createInstance()
 54 *
 55 * Returns an new instance of a MozMillElement
 56 * The type of the element is automatically determined
 57 */
 58function createInstance(locatorType, locator, elem) {
 59  if (elem) {
 60    var args = {"element":elem};
 61    for (var i = 0; i < subclasses.length; ++i) {
 62      if (subclasses[i].isType(elem)) {
 63        return new subclasses[i](locatorType, locator, args);
 64      }
 65    }
 66    if (MozMillElement.isType(elem)) return new MozMillElement(locatorType, locator, args);
 67  }
 68  throw new Error("could not find element " + locatorType + ": " + locator);
 69};
 70
 71var Elem = function(node) {
 72  return createInstance("Elem", node, node);
 73};
 74
 75var Selector = function(_document, selector, index) {
 76  return createInstance("Selector", selector, elementslib.Selector(_document, selector, index));
 77};
 78
 79var ID = function(_document, nodeID) {
 80  return createInstance("ID", nodeID, elementslib.ID(_document, nodeID));
 81};
 82
 83var Link = function(_document, linkName) {
 84  return createInstance("Link", linkName, elementslib.Link(_document, linkName));
 85};
 86
 87var XPath = function(_document, expr) {
 88  return createInstance("XPath", expr, elementslib.XPath(_document, expr));
 89};
 90
 91var Name = function(_document, nName) {
 92  return createInstance("Name", nName, elementslib.Name(_document, nName));
 93};
 94
 95var Lookup = function(_document, expression) {
 96  return createInstance("Lookup", expression, elementslib.Lookup(_document, expression));
 97};
 98
 99
100/**
101 * MozMillElement
102 * The base class for all mozmill elements
103 */
104function MozMillElement(locatorType, locator, args) {
105  args = args || {};
106  this._locatorType = locatorType;
107  this._locator = locator;
108  this._element = args["element"];
109  this._document = args["document"];
110  this._owner = args["owner"];
111  // Used to maintain backwards compatibility with controller.js
112  this.isElement = true;
113}
114
115// Static method that returns true if node is of this element type
116MozMillElement.isType = function(node) {
117  return true;
118};
119
120// This getter is the magic behind lazy loading (note distinction between _element and element)
121MozMillElement.prototype.__defineGetter__("element", function() {
122  if (this._element == undefined) {
123    if (elementslib[this._locatorType]) {
124      this._element = elementslib[this._locatorType](this._document, this._locator);
125    } else if (this._locatorType == "Elem") {
126      this._element = this._locator;
127    } else {
128      throw new Error("Unknown locator type: " + this._locatorType);
129    }
130  }
131  return this._element;
132});
133
134// Returns the actual wrapped DOM node
135MozMillElement.prototype.getNode = function() {
136  return this.element;
137};
138
139MozMillElement.prototype.getInfo = function() {
140  return this._locatorType + ": " + this._locator;
141};
142
143/**
144 * Sometimes an element which once existed will no longer exist in the DOM
145 * This function re-searches for the element
146 */
147MozMillElement.prototype.exists = function() {
148  this._element = undefined;
149  if (this.element) return true;
150  return false;
151};
152
153/**
154 * Synthesize a keypress event on the given element
155 *
156 * @param {string} aKey
157 *        Key to use for synthesizing the keypress event. It can be a simple
158 *        character like "k" or a string like "VK_ESCAPE" for command keys
159 * @param {object} aModifiers
160 *        Information about the modifier keys to send
161 *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
162 *                               [optional - default: false]
163 *                  altKey     - Hold down the alt key
164 *                              [optional - default: false]
165 *                  ctrlKey    - Hold down the ctrl key
166 *                               [optional - default: false]
167 *                  metaKey    - Hold down the meta key (command key on Mac)
168 *                               [optional - default: false]
169 *                  shiftKey   - Hold down the shift key
170 *                               [optional - default: false]
171 * @param {object} aExpectedEvent
172 *        Information about the expected event to occur
173 *        Elements: target     - Element which should receive the event
174 *                               [optional - default: current element]
175 *                  type       - Type of the expected key event
176 */
177MozMillElement.prototype.keypress = function(aKey, aModifiers, aExpectedEvent) {
178  if (!this.element) {
179    throw new Error("Could not find element " + this.getInfo());
180  }
181
182  var win = this.element.ownerDocument? this.element.ownerDocument.defaultView : this.element;
183  this.element.focus();
184
185  if (aExpectedEvent) {
186    var target = aExpectedEvent.target? aExpectedEvent.target.getNode() : this.element;
187    EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
188                                                            "MozMillElement.keypress()", win);
189  } else {
190    EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
191  }
192
193  frame.events.pass({'function':'MozMillElement.keypress()'});
194  return true;
195};
196
197
198/**
199 * Synthesize a general mouse event on the given element
200 *
201 * @param {ElemBase} aTarget
202 *        Element which will receive the mouse event
203 * @param {number} aOffsetX
204 *        Relative x offset in the elements bounds to click on
205 * @param {number} aOffsetY
206 *        Relative y offset in the elements bounds to click on
207 * @param {object} aEvent
208 *        Information about the event to send
209 *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
210 *                               [optional - default: false]
211 *                  altKey     - Hold down the alt key
212 *                               [optional - default: false]
213 *                  button     - Mouse button to use
214 *                               [optional - default: 0]
215 *                  clickCount - Number of counts to click
216 *                               [optional - default: 1]
217 *                  ctrlKey    - Hold down the ctrl key
218 *                               [optional - default: false]
219 *                  metaKey    - Hold down the meta key (command key on Mac)
220 *                               [optional - default: false]
221 *                  shiftKey   - Hold down the shift key
222 *                               [optional - default: false]
223 *                  type       - Type of the mouse event ('click', 'mousedown',
224 *                               'mouseup', 'mouseover', 'mouseout')
225 *                               [optional - default: 'mousedown' + 'mouseup']
226 * @param {object} aExpectedEvent
227 *        Information about the expected event to occur
228 *        Elements: target     - Element which should receive the event
229 *                               [optional - default: current element]
230 *                  type       - Type of the expected mouse event
231 */
232MozMillElement.prototype.mouseEvent = function(aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
233  if (!this.element) {
234    throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
235  }
236
237  // If no offset is given we will use the center of the element to click on.
238  var rect = this.element.getBoundingClientRect();
239  if (isNaN(aOffsetX)) {
240    aOffsetX = rect.width / 2;
241  }
242  if (isNaN(aOffsetY)) {
243    aOffsetY = rect.height / 2;
244  }
245
246  // Scroll element into view otherwise the click will fail
247  if (this.element.scrollIntoView) {
248    this.element.scrollIntoView();
249  }
250
251  if (aExpectedEvent) {
252    // The expected event type has to be set
253    if (!aExpectedEvent.type)
254      throw new Error(arguments.callee.name + ": Expected event type not specified");
255
256    // If no target has been specified use the specified element
257    var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : this.element;
258    if (!target) {
259      throw new Error(arguments.callee.name + ": could not find element " + aExpectedEvent.target.getInfo());
260    }
261
262    EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
263                                          target, aExpectedEvent.event,
264                                          "MozMillElement.mouseEvent()",
265                                          this.element.ownerDocument.defaultView);
266  } else {
267    EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
268                               this.element.ownerDocument.defaultView);
269  }
270};
271
272/**
273 * Synthesize a mouse click event on the given element
274 */
275MozMillElement.prototype.click = function(left, top, expectedEvent) {
276  // Handle menu items differently
277  if (this.element && this.element.tagName == "menuitem") {
278    this.element.click();
279  } else {
280    this.mouseEvent(left, top, {}, expectedEvent);
281  }
282
283  frame.events.pass({'function':'MozMillElement.click()'});
284};
285
286/**
287 * Synthesize a double click on the given element
288 */
289MozMillElement.prototype.doubleClick = function(left, top, expectedEvent) {
290  this.mouseEvent(left, top, {clickCount: 2}, expectedEvent);
291
292  frame.events.pass({'function':'MozMillElement.doubleClick()'});
293  return true;
294};
295
296/**
297 * Synthesize a mouse down event on the given element
298 */
299MozMillElement.prototype.mouseDown = function (button, left, top, expectedEvent) {
300  this.mouseEvent(left, top, {button: button, type: "mousedown"}, expectedEvent);
301
302  frame.events.pass({'function':'MozMillElement.mouseDown()'});
303  return true;
304};
305
306/**
307 * Synthesize a mouse out event on the given element
308 */
309MozMillElement.prototype.mouseOut = function (button, left, top, expectedEvent) {
310  this.mouseEvent(left, top, {button: button, type: "mouseout"}, expectedEvent);
311
312  frame.events.pass({'function':'MozMillElement.mouseOut()'});
313  return true;
314};
315
316/**
317 * Synthesize a mouse over event on the given element
318 */
319MozMillElement.prototype.mouseOver = function (button, left, top, expectedEvent) {
320  this.mouseEvent(left, top, {button: button, type: "mouseover"}, expectedEvent);
321
322  frame.events.pass({'function':'MozMillElement.mouseOver()'});
323  return true;
324};
325
326/**
327 * Synthesize a mouse up event on the given element
328 */
329MozMillElement.prototype.mouseUp = function (button, left, top, expectedEvent) {
330  this.mouseEvent(left, top, {button: button, type: "mouseup"}, expectedEvent);
331
332  frame.events.pass({'function':'MozMillElement.mouseUp()'});
333  return true;
334};
335
336/**
337 * Synthesize a mouse middle click event on the given element
338 */
339MozMillElement.prototype.middleClick = function(left, top, expectedEvent) {
340  this.mouseEvent(left, top, {button: 1}, expectedEvent);
341
342  frame.events.pass({'function':'MozMillElement.middleClick()'});
343  return true;
344};
345
346/**
347 * Synthesize a mouse right click event on the given element
348 */
349MozMillElement.prototype.rightClick = function(left, top, expectedEvent) {
350  this.mouseEvent(left, top, {type : "contextmenu", button: 2 }, expectedEvent);
351
352  frame.events.pass({'function':'MozMillElement.rightClick()'});
353  return true;
354};
355
356MozMillElement.prototype.waitForElement = function(timeout, interval) {
357  var elem = this;
358  utils.waitFor(function() {
359    return elem.exists();
360  }, "Timeout exceeded for waitForElement " + this.getInfo(), timeout, interval);
361
362  frame.events.pass({'function':'MozMillElement.waitForElement()'});
363};
364
365MozMillElement.prototype.waitForElementNotPresent = function(timeout, interval) {
366  var elem = this;
367  utils.waitFor(function() {
368    return !elem.exists();
369  }, "Timeout exceeded for waitForElementNotPresent " + this.getInfo(), timeout, interval);
370
371  frame.events.pass({'function':'MozMillElement.waitForElementNotPresent()'});
372};
373
374MozMillElement.prototype.waitThenClick = function (timeout, interval, left, top, expectedEvent) {
375  this.waitForElement(timeout, interval);
376  this.click(left, top, expectedEvent);
377};
378
379// Dispatches an HTMLEvent
380MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
381  canBubble = canBubble || true;
382  var evt = this.element.ownerDocument.createEvent('HTMLEvents');
383  evt.shiftKey = modifiers["shift"];
384  evt.metaKey = modifiers["meta"];
385  evt.altKey = modifiers["alt"];
386  evt.ctrlKey = modifiers["ctrl"];
387  evt.initEvent(eventType, canBubble, true);
388  this.element.dispatchEvent(evt);
389};
390
391
392//---------------------------------------------------------------------------------------------------------------------------------------
393
394
395/**
396 * MozMillCheckBox
397 * Checkbox element, inherits from MozMillElement
398 */
399MozMillCheckBox.prototype = new MozMillElement();
400MozMillCheckBox.prototype.parent = MozMillElement.prototype;
401MozMillCheckBox.prototype.constructor = MozMillCheckBox;
402function MozMillCheckBox(locatorType, locator, args) {
403  this.parent.constructor.call(this, locatorType, locator, args);
404}
405
406// Static method returns true if node is this type of element
407MozMillCheckBox.isType = function(node) {
408  if ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
409      (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
410      (node.localName.toLowerCase() == 'checkbox')) {
411    return true;
412  }
413  return false;
414};
415
416/**
417 * Enable/Disable a checkbox depending on the target state
418 */
419MozMillCheckBox.prototype.check = function(state) {
420  var result = false;
421
422  if (!this.element) {
423    throw new Error("could not find element " + this.getInfo());
424    return false;
425  }
426
427  // If we have a XUL element, unwrap its XPCNativeWrapper
428  if (this.element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
429    this.element = utils.unwrapNode(this.element);
430  }
431
432  state = (typeof(state) == "boolean") ? state : false;
433  if (state != this.element.checked) {
434    this.click();
435    var element = this.element;
436    utils.waitFor(function() {
437      return element.checked == state;
438    }, "Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
439
440    result = true;
441  }
442
443  frame.events.pass({'function':'MozMillCheckBox.check(' + this.getInfo() + ', state: ' + state + ')'});
444  return result;
445};
446
447//----------------------------------------------------------------------------------------------------------------------------------------
448
449
450/**
451 * MozMillRadio
452 * Radio button inherits from MozMillElement
453 */
454MozMillRadio.prototype = new MozMillElement();
455MozMillRadio.prototype.parent = MozMillElement.prototype;
456MozMillRadio.prototype.constructor = MozMillRadio;
457function MozMillRadio(locatorType, locator, args) {
458  this.parent.constructor.call(this, locatorType, locator, args);
459}
460
461// Static method returns true if node is this type of element
462MozMillRadio.isType = function(node) {
463  if ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
464      (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
465      (node.localName.toLowerCase() == 'radio') ||
466      (node.localName.toLowerCase() == 'radiogroup')) {
467    return true;
468  }
469  return false;
470};
471
472/**
473 * Select the given radio button
474 *
475 * index - Specifies which radio button in the group to select (only applicable to radiogroup elements)
476 *         Defaults to the first radio button in the group
477 */
478MozMillRadio.prototype.select = function(index) {
479  if (!this.element) {
480    throw new Error("could not find element " + this.getInfo());
481  }
482
483  if (this.element.localName.toLowerCase() == "radiogroup") {
484    var element = this.element.getElementsByTagName("radio")[index || 0];
485    new MozMillRadio("Elem", element).click();
486  } else {
487    var element = this.element;
488    this.click();
489  }
490
491  utils.waitFor(function() {
492    // If we have a XUL element, unwrap its XPCNativeWrapper
493    if (element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
494      element = utils.unwrapNode(element);
495      return element.selected == true;
496    }
497    return element.checked == true;
498  }, "Radio button " + this.getInfo() + " could not be selected", 500);
499
500  frame.events.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
501  return true;
502};
503
504//----------------------------------------------------------------------------------------------------------------------------------------
505
506
507/**
508 * MozMillDropList
509 * DropList inherits from MozMillElement
510 */
511MozMillDropList.prototype = new MozMillElement();
512MozMillDropList.prototype.parent = MozMillElement.prototype;
513MozMillDropList.prototype.constructor = MozMillDropList;
514function MozMillDropList(locatorType, locator, args) {
515  this.parent.constructor.call(this, locatorType, locator, args);
516};
517
518// Static method returns true if node is this type of element
519MozMillDropList.isType = function(node) {
520  if ((node.localName.toLowerCase() == 'toolbarbutton' && (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
521      (node.localName.toLowerCase() == 'menu') ||
522      (node.localName.toLowerCase() == 'menulist') ||
523      (node.localName.toLowerCase() == 'select' )) {
524    return true;
525  }
526  return false;
527};
528
529/* Select the specified option and trigger the relevant events of the element */
530MozMillDropList.prototype.select = function (indx, option, value) {
531  if (!this.element){
532    throw new Error("Could not find element " + this.getInfo());
533  }
534
535  //if we have a select drop down
536  if (this.element.localName.toLowerCase() == "select"){
537    var item = null;
538
539    // The selected item should be set via its index
540    if (indx != undefined) {
541      // Resetting a menulist has to be handled separately
542      if (indx == -1) {
543        this.dispatchEvent('focus', false);
544        this.element.selectedIndex = indx;
545        this.dispatchEvent('change', true);
546
547        frame.events.pass({'function':'MozMillDropList.select()'});
548        return true;
549      } else {
550        item = this.element.options.item(indx);
551      }
552    } else {
553      for (var i = 0; i < this.element.options.length; i++) {
554        var entry = this.element.options.item(i);
555        if (option != undefined && entry.innerHTML == option ||
556            value != undefined && entry.value == value) {
557          item = entry;
558          break;
559        }
560      }
561    }
562
563    // Click the item
564    try {
565      // EventUtils.synthesizeMouse doesn't work.
566      this.dispatchEvent('focus', false);
567      item.selected = true;
568      this.dispatchEvent('change', true);
569
570      frame.events.pass({'function':'MozMillDropList.select()'});
571      return true;
572    } catch (ex) {
573      throw new Error("No item selected for element " + this.getInfo());
574      return false;
575    }
576  }
577  //if we have a xul menupopup select accordingly
578  else if (this.element.namespaceURI.toLowerCase() == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
579    var ownerDoc = this.element.ownerDocument;
580    // Unwrap the XUL element's XPCNativeWrapper
581    this.element = utils.unwrapNode(this.element);
582    // Get the list of menuitems
583    menuitems = this.element.getElementsByTagName("menupopup")[0].getElementsByTagName("menuitem");
584
585    var item = null;
586
587    if (indx != undefined) {
588      if (indx == -1) {
589        this.dispatchEvent('focus', false);
590        this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild = null;
591        this.dispatchEvent('change', true);
592
593        frame.events.pass({'function':'MozMillDropList.select()'});
594        return true;
595      } else {
596        item = menuitems[indx];
597      }
598    } else {
599      for (var i = 0; i < menuitems.length; i++) {
600        var entry = menuitems[i];
601        if (option != undefined && entry.label == option ||
602            value != undefined && entry.value == value) {
603          item = entry;
604          break;
605        }
606      }
607    }
608
609    // Click the item
610    try {
611      EventUtils.synthesizeMouse(this.element, 1, 1, {}, ownerDoc.defaultView);
612
613      // Scroll down until item is visible
614      for (var i = 0; i <= menuitems.length; ++i) {
615        var selected = this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild;
616        if (item == selected) {
617          break;
618        }
619        EventUtils.synthesizeKey("VK_DOWN", {}, ownerDoc.defaultView);
620      }
621
622      EventUtils.synthesizeMouse(item, 1, 1, {}, ownerDoc.defaultView);
623
624      frame.events.pass({'function':'MozMillDropList.select()'});
625      return true;
626    } catch (ex) {
627      throw new Error('No item selected for element ' + this.getInfo());
628      return false;
629    }
630  }
631};
632
633
634//----------------------------------------------------------------------------------------------------------------------------------------
635
636
637/**
638 * MozMillTextBox
639 * TextBox inherits from MozMillElement
640 */
641MozMillTextBox.prototype = new MozMillElement();
642MozMillTextBox.prototype.parent = MozMillElement.prototype;
643MozMillTextBox.prototype.constructor = MozMillTextBox;
644function MozMillTextBox(locatorType, locator, args) {
645  this.parent.constructor.call(this, locatorType, locator, args);
646};
647
648// Static method returns true if node is this type of element
649MozMillTextBox.isType = function(node) {
650  if ((node.localName.toLowerCase() == 'input' && (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
651      (node.localName.toLowerCase() == 'textarea') ||
652      (node.localName.toLowerCase() == 'textbox')) {
653    return true;
654  }
655  return false;
656};
657
658/**
659 * Synthesize keypress events for each character on the given element
660 *
661 * @param {string} aText
662 *        The text to send as single keypress events
663 * @param {object} aModifiers
664 *        Information about the modifier keys to send
665 *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
666 *                               [optional - default: false]
667 *                  altKey     - Hold down the alt key
668 *                              [optional - default: false]
669 *                  ctrlKey    - Hold down the ctrl key
670 *                               [optional - default: false]
671 *                  metaKey    - Hold down the meta key (command key on Mac)
672 *                               [optional - default: false]
673 *                  shiftKey   - Hold down the shift key
674 *                               [optional - default: false]
675 * @param {object} aExpectedEvent
676 *        Information about the expected event to occur
677 *        Elements: target     - Element which should receive the event
678 *                               [optional - default: current element]
679 *                  type       - Type of the expected key event
680 */
681MozMillTextBox.prototype.sendKeys = function (aText, aModifiers, aExpectedEvent) {
682  if (!this.element) {
683    throw new Error("could not find element " + this.getInfo());
684  }
685
686  var element = this.element;
687  Array.forEach(aText, function(letter) {
688    var win = element.ownerDocument? element.ownerDocument.defaultView : element;
689    element.focus();
690
691    if (aExpectedEvent) {
692      var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : element;
693      EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target, aExpectedEvent.type,
694                                                              "MozMillTextBox.sendKeys()", win);
695    } else {
696      EventUtils.synthesizeKey(letter, aModifiers || {}, win);
697    }
698  });
699
700  frame.events.pass({'function':'MozMillTextBox.type()'});
701  return true;
702};