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

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
     5 var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
     6                         "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
     7                         "MozMillTextBox", "subclasses"
     8                        ];
    10 const NAMESPACE_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    12 const Cc = Components.classes;
    13 const Ci = Components.interfaces;
    14 const Cu = Components.utils;
    16 var EventUtils = {};  Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
    18 var assertions = {};  Cu.import('resource://mozmill/modules/assertions.js', assertions);
    19 var broker = {};      Cu.import('resource://mozmill/driver/msgbroker.js', broker);
    20 var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
    21 var utils = {};       Cu.import('resource://mozmill/stdlib/utils.js', utils);
    23 var assert = new assertions.Assert();
    25 // A list of all the subclasses available.  Shared modules can push their own subclasses onto this list
    26 var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
    28 /**
    29  * createInstance()
    30  *
    31  * Returns an new instance of a MozMillElement
    32  * The type of the element is automatically determined
    33  */
    34 function createInstance(locatorType, locator, elem, document) {
    35   var args = { "document": document, "element": elem };
    37   // If we already have an element lets determine the best MozMillElement type
    38   if (elem) {
    39     for (var i = 0; i < subclasses.length; ++i) {
    40       if (subclasses[i].isType(elem)) {
    41         return new subclasses[i](locatorType, locator, args);
    42       }
    43     }
    44   }
    46   // By default we create a base MozMillElement
    47   if (MozMillElement.isType(elem)) {
    48     return new MozMillElement(locatorType, locator, args);
    49   }
    51   throw new Error("Unsupported element type " + locatorType + ": " + locator);
    52 }
    54 var Elem = function (node) {
    55   return createInstance("Elem", node, node);
    56 };
    58 var Selector = function (document, selector, index) {
    59   return createInstance("Selector", selector, elementslib.Selector(document, selector, index), document);
    60 };
    62 var ID = function (document, nodeID) {
    63   return createInstance("ID", nodeID, elementslib.ID(document, nodeID), document);
    64 };
    66 var Link = function (document, linkName) {
    67   return createInstance("Link", linkName, elementslib.Link(document, linkName), document);
    68 };
    70 var XPath = function (document, expr) {
    71   return createInstance("XPath", expr, elementslib.XPath(document, expr), document);
    72 };
    74 var Name = function (document, nName) {
    75   return createInstance("Name", nName, elementslib.Name(document, nName), document);
    76 };
    78 var Lookup = function (document, expression) {
    79   var elem = createInstance("Lookup", expression, elementslib.Lookup(document, expression), document);
    81   // Bug 864268 - Expose the expression property to maintain backwards compatibility
    82   elem.expression = elem._locator;
    84   return elem;
    85 };
    87 /**
    88  * MozMillElement
    89  * The base class for all mozmill elements
    90  */
    91 function MozMillElement(locatorType, locator, args) {
    92   args = args || {};
    93   this._locatorType = locatorType;
    94   this._locator = locator;
    95   this._element = args["element"];
    96   this._owner = args["owner"];
    98   this._document = this._element ? this._element.ownerDocument : args["document"];
    99   this._defaultView = this._document ? this._document.defaultView : null;
   101   // Used to maintain backwards compatibility with controller.js
   102   this.isElement = true;
   103 }
   105 // Static method that returns true if node is of this element type
   106 MozMillElement.isType = function (node) {
   107   return true;
   108 };
   110 // This getter is the magic behind lazy loading (note distinction between _element and element)
   111 MozMillElement.prototype.__defineGetter__("element", function () {
   112   // If the document is invalid (e.g. reload of the page), invalidate the cached
   113   // element and update the document cache
   114   if (this._defaultView && this._defaultView.document !== this._document) {
   115     this._document = this._defaultView.document;
   116     this._element = undefined;
   117   }
   119   if (this._element == undefined) {
   120     if (elementslib[this._locatorType]) {
   121       this._element = elementslib[this._locatorType](this._document, this._locator);
   122     } else if (this._locatorType == "Elem") {
   123       this._element = this._locator;
   124     } else {
   125       throw new Error("Unknown locator type: " + this._locatorType);
   126     }
   127   }
   129   return this._element;
   130 });
   132 /**
   133  * Drag an element to the specified offset on another element, firing mouse and
   134  * drag events. Adapted from ChromeUtils.js synthesizeDrop()
   135  *
   136  * By default it will drag the source element over the destination's element
   137  * center with a "move" dropEffect.
   138  *
   139  * @param {MozElement} aElement
   140  *        Destination element over which the drop occurs
   141  * @param {Number} [aOffsetX=aElement.width/2]
   142  *        Relative x offset for dropping on aElement
   143  * @param {Number} [aOffsetY=aElement.height/2]
   144  *        Relative y offset for dropping on aElement
   145  * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
   146  *        Custom source Window to be used.
   147  * @param {DOMWindow} [aDestWindow=aElement.getNode().ownerDocument.defaultView]
   148  *        Custom destination Window to be used.
   149  * @param {String} [aDropEffect="move"]
   150  *        Possible values: copy, move, link, none
   151  * @param {Object[]} [aDragData]
   152  *        An array holding custom drag data to be used during the drag event
   153  *        Format: [{ type: "text/plain", "Text to drag"}, ...]
   154  *
   155  * @returns {String} the captured dropEffect
   156  */
   157 MozMillElement.prototype.dragToElement = function(aElement, aOffsetX, aOffsetY,
   158                                                   aSourceWindow, aDestWindow,
   159                                                   aDropEffect, aDragData) {
   160   if (!this.element) {
   161     throw new Error("Could not find element " + this.getInfo());
   162   }
   163   if (!aElement) {
   164     throw new Error("Missing destination element");
   165   }
   167   var srcNode = this.element;
   168   var destNode = aElement.getNode();
   169   var srcWindow = aSourceWindow ||
   170                   (srcNode.ownerDocument ? srcNode.ownerDocument.defaultView
   171                                          : srcNode);
   172   var destWindow = aDestWindow ||
   173                   (destNode.ownerDocument ? destNode.ownerDocument.defaultView
   174                                           : destNode);
   176   var srcRect = srcNode.getBoundingClientRect();
   177   var srcCoords = {
   178     x: srcRect.width / 2,
   179     y: srcRect.height / 2
   180   };
   181   var destRect = destNode.getBoundingClientRect();
   182   var destCoords = {
   183     x: (!aOffsetX || isNaN(aOffsetX)) ? (destRect.width / 2) : aOffsetX,
   184     y: (!aOffsetY || isNaN(aOffsetY)) ? (destRect.height / 2) : aOffsetY
   185   };
   187   var windowUtils = destWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   188                               .getInterface(Ci.nsIDOMWindowUtils);
   189   var ds = Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService);
   191   var dataTransfer;
   192   var trapDrag = function (event) {
   193     srcWindow.removeEventListener("dragstart", trapDrag, true);
   194     dataTransfer = event.dataTransfer;
   196     if (!aDragData) {
   197       return;
   198     }
   200     for (var i = 0; i < aDragData.length; i++) {
   201       var item = aDragData[i];
   202       for (var j = 0; j < item.length; j++) {
   203         dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
   204       }
   205     }
   207     dataTransfer.dropEffect = aDropEffect || "move";
   208     event.preventDefault();
   209     event.stopPropagation();
   210   }
   212   ds.startDragSession();
   214   try {
   215     srcWindow.addEventListener("dragstart", trapDrag, true);
   216     EventUtils.synthesizeMouse(srcNode, srcCoords.x, srcCoords.y,
   217                                { type: "mousedown" }, srcWindow);
   218     EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
   219                                { type: "mousemove" }, destWindow);
   221     var event = destWindow.document.createEvent("DragEvents");
   222     event.initDragEvent("dragenter", true, true, destWindow, 0, 0, 0, 0, 0,
   223                         false, false, false, false, 0, null, dataTransfer);
   224     event.initDragEvent("dragover", true, true, destWindow, 0, 0, 0, 0, 0,
   225                         false, false, false, false, 0, null, dataTransfer);
   226     event.initDragEvent("drop", true, true, destWindow, 0, 0, 0, 0, 0,
   227                         false, false, false, false, 0, null, dataTransfer);
   228     windowUtils.dispatchDOMEventViaPresShell(destNode, event, true);
   230     EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
   231                                { type: "mouseup" }, destWindow);
   233     return dataTransfer.dropEffect;
   234   } finally {
   235     ds.endDragSession(true);
   236   }
   238 };
   240 // Returns the actual wrapped DOM node
   241 MozMillElement.prototype.getNode = function () {
   242   return this.element;
   243 };
   245 MozMillElement.prototype.getInfo = function () {
   246   return this._locatorType + ": " + this._locator;
   247 };
   249 /**
   250  * Sometimes an element which once existed will no longer exist in the DOM
   251  * This function re-searches for the element
   252  */
   253 MozMillElement.prototype.exists = function () {
   254   this._element = undefined;
   255   if (this.element) {
   256     return true;
   257   }
   259   return false;
   260 };
   262 /**
   263  * Synthesize a keypress event on the given element
   264  *
   265  * @param {string} aKey
   266  *        Key to use for synthesizing the keypress event. It can be a simple
   267  *        character like "k" or a string like "VK_ESCAPE" for command keys
   268  * @param {object} aModifiers
   269  *        Information about the modifier keys to send
   270  *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
   271  *                               [optional - default: false]
   272  *                  altKey     - Hold down the alt key
   273  *                              [optional - default: false]
   274  *                  ctrlKey    - Hold down the ctrl key
   275  *                               [optional - default: false]
   276  *                  metaKey    - Hold down the meta key (command key on Mac)
   277  *                               [optional - default: false]
   278  *                  shiftKey   - Hold down the shift key
   279  *                               [optional - default: false]
   280  * @param {object} aExpectedEvent
   281  *        Information about the expected event to occur
   282  *        Elements: target     - Element which should receive the event
   283  *                               [optional - default: current element]
   284  *                  type       - Type of the expected key event
   285  */
   286 MozMillElement.prototype.keypress = function (aKey, aModifiers, aExpectedEvent) {
   287   if (!this.element) {
   288     throw new Error("Could not find element " + this.getInfo());
   289   }
   291   var win = this.element.ownerDocument ? this.element.ownerDocument.defaultView
   292                                        : this.element;
   293   this.element.focus();
   295   if (aExpectedEvent) {
   296     if (!aExpectedEvent.type) {
   297       throw new Error(arguments.callee.name + ": Expected event type not specified");
   298     }
   300     var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
   301                                        : this.element;
   302     EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
   303                                         "MozMillElement.keypress()", win);
   304   } else {
   305     EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
   306   }
   308   broker.pass({'function':'MozMillElement.keypress()'});
   310   return true;
   311 };
   314 /**
   315  * Synthesize a general mouse event on the given element
   316  *
   317  * @param {number} aOffsetX
   318  *        Relative x offset in the elements bounds to click on
   319  * @param {number} aOffsetY
   320  *        Relative y offset in the elements bounds to click on
   321  * @param {object} aEvent
   322  *        Information about the event to send
   323  *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
   324  *                               [optional - default: false]
   325  *                  altKey     - Hold down the alt key
   326  *                               [optional - default: false]
   327  *                  button     - Mouse button to use
   328  *                               [optional - default: 0]
   329  *                  clickCount - Number of counts to click
   330  *                               [optional - default: 1]
   331  *                  ctrlKey    - Hold down the ctrl key
   332  *                               [optional - default: false]
   333  *                  metaKey    - Hold down the meta key (command key on Mac)
   334  *                               [optional - default: false]
   335  *                  shiftKey   - Hold down the shift key
   336  *                               [optional - default: false]
   337  *                  type       - Type of the mouse event ('click', 'mousedown',
   338  *                               'mouseup', 'mouseover', 'mouseout')
   339  *                               [optional - default: 'mousedown' + 'mouseup']
   340  * @param {object} aExpectedEvent
   341  *        Information about the expected event to occur
   342  *        Elements: target     - Element which should receive the event
   343  *                               [optional - default: current element]
   344  *                  type       - Type of the expected mouse event
   345  */
   346 MozMillElement.prototype.mouseEvent = function (aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
   347   if (!this.element) {
   348     throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
   349   }
   351   if ("document" in this.element) {
   352     throw new Error("A window cannot be a target for mouse events.");
   353   }
   355   var rect = this.element.getBoundingClientRect();
   357   if (!aOffsetX || isNaN(aOffsetX)) {
   358     aOffsetX = rect.width / 2;
   359   }
   361   if (!aOffsetY || isNaN(aOffsetY)) {
   362     aOffsetY = rect.height / 2;
   363   }
   365   // Scroll element into view otherwise the click will fail
   366   if ("scrollIntoView" in this.element)
   367     this.element.scrollIntoView();
   369   if (aExpectedEvent) {
   370     // The expected event type has to be set
   371     if (!aExpectedEvent.type) {
   372       throw new Error(arguments.callee.name + ": Expected event type not specified");
   373     }
   375     // If no target has been specified use the specified element
   376     var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
   377                                        : this.element;
   378     if (!target) {
   379       throw new Error(arguments.callee.name + ": could not find element " +
   380                       aExpectedEvent.target.getInfo());
   381     }
   383     EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
   384                                           target, aExpectedEvent.type,
   385                                           "MozMillElement.mouseEvent()",
   386                                           this.element.ownerDocument.defaultView);
   387   } else {
   388     EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
   389                                this.element.ownerDocument.defaultView);
   390   }
   392   // Bug 555347
   393   // We don't know why this sleep is necessary but more investigation is needed
   394   // before it can be removed
   395   utils.sleep(0);
   397   return true;
   398 };
   400 /**
   401  * Synthesize a mouse click event on the given element
   402  */
   403 MozMillElement.prototype.click = function (aOffsetX, aOffsetY, aExpectedEvent) {
   404   // Handle menu items differently
   405   if (this.element && this.element.tagName == "menuitem") {
   406     this.element.click();
   407   } else {
   408     this.mouseEvent(aOffsetX, aOffsetY, {}, aExpectedEvent);
   409   }
   411   broker.pass({'function':'MozMillElement.click()'});
   413   return true;
   414 };
   416 /**
   417  * Synthesize a double click on the given element
   418  */
   419 MozMillElement.prototype.doubleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
   420   this.mouseEvent(aOffsetX, aOffsetY, {clickCount: 2}, aExpectedEvent);
   422   broker.pass({'function':'MozMillElement.doubleClick()'});
   424   return true;
   425 };
   427 /**
   428  * Synthesize a mouse down event on the given element
   429  */
   430 MozMillElement.prototype.mouseDown = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
   431   this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mousedown"}, aExpectedEvent);
   433   broker.pass({'function':'MozMillElement.mouseDown()'});
   435   return true;
   436 };
   438 /**
   439  * Synthesize a mouse out event on the given element
   440  */
   441 MozMillElement.prototype.mouseOut = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
   442   this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseout"}, aExpectedEvent);
   444   broker.pass({'function':'MozMillElement.mouseOut()'});
   446   return true;
   447 };
   449 /**
   450  * Synthesize a mouse over event on the given element
   451  */
   452 MozMillElement.prototype.mouseOver = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
   453   this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseover"}, aExpectedEvent);
   455   broker.pass({'function':'MozMillElement.mouseOver()'});
   457   return true;
   458 };
   460 /**
   461  * Synthesize a mouse up event on the given element
   462  */
   463 MozMillElement.prototype.mouseUp = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
   464   this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseup"}, aExpectedEvent);
   466   broker.pass({'function':'MozMillElement.mouseUp()'});
   468   return true;
   469 };
   471 /**
   472  * Synthesize a mouse middle click event on the given element
   473  */
   474 MozMillElement.prototype.middleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
   475   this.mouseEvent(aOffsetX, aOffsetY, {button: 1}, aExpectedEvent);
   477   broker.pass({'function':'MozMillElement.middleClick()'});
   479   return true;
   480 };
   482 /**
   483  * Synthesize a mouse right click event on the given element
   484  */
   485 MozMillElement.prototype.rightClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
   486   this.mouseEvent(aOffsetX, aOffsetY, {type : "contextmenu", button: 2 }, aExpectedEvent);
   488   broker.pass({'function':'MozMillElement.rightClick()'});
   490   return true;
   491 };
   493 /**
   494  * Synthesize a general touch event on the given element
   495  *
   496  * @param {Number} [aOffsetX=aElement.width / 2]
   497  *        Relative x offset in the elements bounds to click on
   498  * @param {Number} [aOffsetY=aElement.height / 2]
   499  *        Relative y offset in the elements bounds to click on
   500  * @param {Object} [aEvent]
   501  *        Information about the event to send
   502  * @param {Boolean} [aEvent.altKey=false]
   503  *        A Boolean value indicating whether or not the alt key was down when
   504  *        the touch event was fired
   505  * @param {Number} [aEvent.angle=0]
   506  *        The angle (in degrees) that the ellipse described by rx and
   507  *        ry must be rotated, clockwise, to most accurately cover the area
   508  *        of contact between the user and the surface.
   509  * @param {Touch[]} [aEvent.changedTouches]
   510  *        A TouchList of all the Touch objects representing individual points of
   511  *        contact whose states changed between the previous touch event and
   512  *        this one
   513  * @param {Boolean} [aEvent.ctrlKey]
   514  *        A Boolean value indicating whether or not the control key was down
   515  *        when the touch event was fired
   516  * @param {Number} [aEvent.force=1]
   517  *        The amount of pressure being applied to the surface by the user, as a
   518  *        float between 0.0 (no pressure) and 1.0 (maximum pressure)
   519  * @param {Number} [aEvent.id=0]
   520  *        A unique identifier for this Touch object. A given touch (say, by a
   521  *        finger) will have the same identifier for the duration of its movement
   522  *        around the surface. This lets you ensure that you're tracking the same
   523  *        touch all the time
   524  * @param {Boolean} [aEvent.metaKey]
   525  *        A Boolean value indicating whether or not the meta key was down when
   526  *        the touch event was fired.
   527  * @param {Number} [aEvent.rx=1]
   528  *        The X radius of the ellipse that most closely circumscribes the area
   529  *        of contact with the screen.
   530  * @param {Number} [aEvent.ry=1]
   531  *        The Y radius of the ellipse that most closely circumscribes the area
   532  *        of contact with the screen.
   533  * @param {Boolean} [aEvent.shiftKey]
   534  *        A Boolean value indicating whether or not the shift key was down when
   535  *        the touch event was fired
   536  * @param {Touch[]} [aEvent.targetTouches]
   537  *        A TouchList of all the Touch objects that are both currently in
   538  *        contact with the touch surface and were also started on the same
   539  *        element that is the target of the event
   540  * @param {Touch[]} [aEvent.touches]
   541  *        A TouchList of all the Touch objects representing all current points
   542  *        of contact with the surface, regardless of target or changed status
   543  * @param {Number} [aEvent.type=*|touchstart|touchend|touchmove|touchenter|touchleave|touchcancel]
   544  *        The type of touch event that occurred
   545  * @param {Element} [aEvent.target]
   546  *        The target of the touches associated with this event. This target
   547  *        corresponds to the target of all the touches in the targetTouches
   548  *        attribute, but note that other touches in this event may have a
   549  *        different target. To be careful, you should use the target associated
   550  *        with individual touches
   551  */
   552 MozMillElement.prototype.touchEvent = function (aOffsetX, aOffsetY, aEvent) {
   553   if (!this.element) {
   554     throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
   555   }
   557   if ("document" in this.element) {
   558     throw new Error("A window cannot be a target for touch events.");
   559   }
   561   var rect = this.element.getBoundingClientRect();
   563   if (!aOffsetX || isNaN(aOffsetX)) {
   564     aOffsetX = rect.width / 2;
   565   }
   567   if (!aOffsetY || isNaN(aOffsetY)) {
   568     aOffsetY = rect.height / 2;
   569   }
   571   // Scroll element into view otherwise the click will fail
   572   if ("scrollIntoView" in this.element) {
   573     this.element.scrollIntoView();
   574   }
   576   EventUtils.synthesizeTouch(this.element, aOffsetX, aOffsetY, aEvent,
   577                              this.element.ownerDocument.defaultView);
   579   return true;
   580 };
   582 /**
   583  * Synthesize a touch tap event on the given element
   584  *
   585  * @param {Number} [aOffsetX=aElement.width / 2]
   586  *        Left offset in px where the event is triggered
   587  * @param {Number} [aOffsetY=aElement.height / 2]
   588  *        Top offset in px where the event is triggered
   589  * @param {Object} [aExpectedEvent]
   590  *        Information about the expected event to occur
   591  * @param {MozMillElement} [aExpectedEvent.target=this.element]
   592  *        Element which should receive the event
   593  * @param {MozMillElement} [aExpectedEvent.type]
   594  *        Type of the expected mouse event
   595  */
   596 MozMillElement.prototype.tap = function (aOffsetX, aOffsetY, aExpectedEvent) {
   597   this.mouseEvent(aOffsetX, aOffsetY, {
   598     clickCount: 1,
   599     inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
   600   }, aExpectedEvent);
   602   broker.pass({'function':'MozMillElement.tap()'});
   604   return true;
   605 };
   607 /**
   608  * Synthesize a double tap on the given element
   609  *
   610  * @param {Number} [aOffsetX=aElement.width / 2]
   611  *        Left offset in px where the event is triggered
   612  * @param {Number} [aOffsetY=aElement.height / 2]
   613  *        Top offset in px where the event is triggered
   614  * @param {Object} [aExpectedEvent]
   615  *        Information about the expected event to occur
   616  * @param {MozMillElement} [aExpectedEvent.target=this.element]
   617  *        Element which should receive the event
   618  * @param {MozMillElement} [aExpectedEvent.type]
   619  *        Type of the expected mouse event
   620  */
   621 MozMillElement.prototype.doubleTap = function (aOffsetX, aOffsetY, aExpectedEvent) {
   622   this.mouseEvent(aOffsetX, aOffsetY, {
   623     clickCount: 2,
   624     inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
   625   }, aExpectedEvent);
   627   broker.pass({'function':'MozMillElement.doubleTap()'});
   629   return true;
   630 };
   632 /**
   633  * Synthesize a long press
   634  *
   635  * @param {Number} aOffsetX
   636  *        Left offset in px where the event is triggered
   637  * @param {Number} aOffsetY
   638  *        Top offset in px where the event is triggered
   639  * @param {Number} [aTime=1000]
   640  *        Duration of the "press" event in ms
   641  */
   642 MozMillElement.prototype.longPress = function (aOffsetX, aOffsetY, aTime) {
   643   var time = aTime || 1000;
   645   this.touchStart(aOffsetX, aOffsetY);
   646   utils.sleep(time);
   647   this.touchEnd(aOffsetX, aOffsetY);
   649   broker.pass({'function':'MozMillElement.longPress()'});
   651   return true;
   652 };
   654 /**
   655  * Synthesize a touch & drag event on the given element
   656  *
   657  * @param {Number} aOffsetX1
   658  *        Left offset of the start position
   659  * @param {Number} aOffsetY1
   660  *        Top offset of the start position
   661  * @param {Number} aOffsetX2
   662  *        Left offset of the end position
   663  * @param {Number} aOffsetY2
   664  *        Top offset of the end position
   665  */
   666 MozMillElement.prototype.touchDrag = function (aOffsetX1, aOffsetY1, aOffsetX2, aOffsetY2) {
   667   this.touchStart(aOffsetX1, aOffsetY1);
   668   this.touchMove(aOffsetX2, aOffsetY2);
   669   this.touchEnd(aOffsetX2, aOffsetY2);
   671   broker.pass({'function':'MozMillElement.move()'});
   673   return true;
   674 };
   676 /**
   677  * Synthesize a press / touchstart event on the given element
   678  *
   679  * @param {Number} aOffsetX
   680  *        Left offset where the event is triggered
   681  * @param {Number} aOffsetY
   682  *        Top offset where the event is triggered
   683  */
   684 MozMillElement.prototype.touchStart = function (aOffsetX, aOffsetY) {
   685   this.touchEvent(aOffsetX, aOffsetY, { type: "touchstart" });
   687   broker.pass({'function':'MozMillElement.touchStart()'});
   689   return true;
   690 };
   692 /**
   693  * Synthesize a release / touchend event on the given element
   694  *
   695  * @param {Number} aOffsetX
   696  *        Left offset where the event is triggered
   697  * @param {Number} aOffsetY
   698  *        Top offset where the event is triggered
   699  */
   700 MozMillElement.prototype.touchEnd = function (aOffsetX, aOffsetY) {
   701   this.touchEvent(aOffsetX, aOffsetY, { type: "touchend" });
   703   broker.pass({'function':'MozMillElement.touchEnd()'});
   705   return true;
   706 };
   708 /**
   709  * Synthesize a touchMove event on the given element
   710  *
   711  * @param {Number} aOffsetX
   712  *        Left offset where the event is triggered
   713  * @param {Number} aOffsetY
   714  *        Top offset where the event is triggered
   715  */
   716 MozMillElement.prototype.touchMove = function (aOffsetX, aOffsetY) {
   717   this.touchEvent(aOffsetX, aOffsetY, { type: "touchmove" });
   719   broker.pass({'function':'MozMillElement.touchMove()'});
   721   return true;
   722 };
   724 MozMillElement.prototype.waitForElement = function (timeout, interval) {
   725   var elem = this;
   727   assert.waitFor(function () {
   728     return elem.exists();
   729   }, "Element.waitForElement(): Element '" + this.getInfo() +
   730      "' has been found", timeout, interval);
   732   broker.pass({'function':'MozMillElement.waitForElement()'});
   733 };
   735 MozMillElement.prototype.waitForElementNotPresent = function (timeout, interval) {
   736   var elem = this;
   738   assert.waitFor(function () {
   739     return !elem.exists();
   740   }, "Element.waitForElementNotPresent(): Element '" + this.getInfo() +
   741      "' has not been found", timeout, interval);
   743   broker.pass({'function':'MozMillElement.waitForElementNotPresent()'});
   744 };
   746 MozMillElement.prototype.waitThenClick = function (timeout, interval,
   747                                                    aOffsetX, aOffsetY, aExpectedEvent) {
   748   this.waitForElement(timeout, interval);
   749   this.click(aOffsetX, aOffsetY, aExpectedEvent);
   750 };
   752 /**
   753  * Waits for the element to be available in the DOM, then trigger a tap event
   754  *
   755  * @param {Number} [aTimeout=5000]
   756  *        Time to wait for the element to be available
   757  * @param {Number} [aInterval=100]
   758  *        Interval to check for availability
   759  * @param {Number} [aOffsetX=aElement.width / 2]
   760  *        Left offset where the event is triggered
   761  * @param {Number} [aOffsetY=aElement.height / 2]
   762  *        Top offset where the event is triggered
   763  * @param {Object} [aExpectedEvent]
   764  *        Information about the expected event to occur
   765  * @param {MozMillElement} [aExpectedEvent.target=this.element]
   766  *        Element which should receive the event
   767  * @param {MozMillElement} [aExpectedEvent.type]
   768  *        Type of the expected mouse event
   769  */
   770 MozMillElement.prototype.waitThenTap = function (aTimeout, aInterval,
   771                                                  aOffsetX, aOffsetY, aExpectedEvent) {
   772   this.waitForElement(aTimeout, aInterval);
   773   this.tap(aOffsetX, aOffsetY, aExpectedEvent);
   774 };
   776 // Dispatches an HTMLEvent
   777 MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
   778   canBubble = canBubble || true;
   779   modifiers = modifiers || { };
   781   let document = 'ownerDocument' in this.element ? this.element.ownerDocument
   782                                                  : this.element.document;
   784   let evt = document.createEvent('HTMLEvents');
   785   evt.shiftKey = modifiers["shift"];
   786   evt.metaKey = modifiers["meta"];
   787   evt.altKey = modifiers["alt"];
   788   evt.ctrlKey = modifiers["ctrl"];
   789   evt.initEvent(eventType, canBubble, true);
   791   this.element.dispatchEvent(evt);
   792 };
   795 /**
   796  * MozMillCheckBox, which inherits from MozMillElement
   797  */
   798 function MozMillCheckBox(locatorType, locator, args) {
   799   MozMillElement.call(this, locatorType, locator, args);
   800 }
   803 MozMillCheckBox.prototype = Object.create(MozMillElement.prototype, {
   804   check : {
   805     /**
   806      * Enable/Disable a checkbox depending on the target state
   807      *
   808      * @param {boolean} state State to set
   809      * @return {boolean} Success state
   810      */
   811     value : function MMCB_check(state) {
   812       var result = false;
   814       if (!this.element) {
   815         throw new Error("could not find element " + this.getInfo());
   816       }
   818       // If we have a XUL element, unwrap its XPCNativeWrapper
   819       if (this.element.namespaceURI == NAMESPACE_XUL) {
   820         this.element = utils.unwrapNode(this.element);
   821       }
   823       state = (typeof(state) == "boolean") ? state : false;
   824       if (state != this.element.checked) {
   825         this.click();
   826         var element = this.element;
   828         assert.waitFor(function () {
   829           return element.checked == state;
   830         }, "CheckBox.check(): Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
   832         result = true;
   833       }
   835       broker.pass({'function':'MozMillCheckBox.check(' + this.getInfo() +
   836                    ', state: ' + state + ')'});
   838       return result;
   839     }
   840   }
   841 });
   844 /**
   845  * Returns true if node is of type MozMillCheckBox
   846  *
   847  * @static
   848  * @param {DOMNode} node Node to check for its type
   849  * @return {boolean} True if node is of type checkbox
   850  */
   851 MozMillCheckBox.isType = function MMCB_isType(node) {
   852   return ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
   853     (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
   854     (node.localName.toLowerCase() == 'checkbox'));
   855 };
   858 /**
   859  * MozMillRadio, which inherits from MozMillElement
   860  */
   861 function MozMillRadio(locatorType, locator, args) {
   862   MozMillElement.call(this, locatorType, locator, args);
   863 }
   866 MozMillRadio.prototype = Object.create(MozMillElement.prototype, {
   867   select : {
   868     /**
   869      * Select the given radio button
   870      *
   871      * @param {number} [index=0]
   872      *        Specifies which radio button in the group to select (only
   873      *        applicable to radiogroup elements)
   874      * @return {boolean} Success state
   875      */
   876     value : function MMR_select(index) {
   877       if (!this.element) {
   878         throw new Error("could not find element " + this.getInfo());
   879       }
   881       if (this.element.localName.toLowerCase() == "radiogroup") {
   882         var element = this.element.getElementsByTagName("radio")[index || 0];
   883         new MozMillRadio("Elem", element).click();
   884       } else {
   885         var element = this.element;
   886         this.click();
   887       }
   889       assert.waitFor(function () {
   890         // If we have a XUL element, unwrap its XPCNativeWrapper
   891         if (element.namespaceURI == NAMESPACE_XUL) {
   892           element = utils.unwrapNode(element);
   893           return element.selected == true;
   894         }
   896         return element.checked == true;
   897       }, "Radio.select(): Radio button " + this.getInfo() + " has been selected", 500);
   899       broker.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
   901       return true;
   902     }
   903   }
   904 });
   907 /**
   908  * Returns true if node is of type MozMillRadio
   909  *
   910  * @static
   911  * @param {DOMNode} node Node to check for its type
   912  * @return {boolean} True if node is of type radio
   913  */
   914 MozMillRadio.isType = function MMR_isType(node) {
   915   return ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
   916     (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
   917     (node.localName.toLowerCase() == 'radio') ||
   918     (node.localName.toLowerCase() == 'radiogroup'));
   919 };
   922 /**
   923  * MozMillDropList, which inherits from MozMillElement
   924  */
   925 function MozMillDropList(locatorType, locator, args) {
   926   MozMillElement.call(this, locatorType, locator, args);
   927 }
   930 MozMillDropList.prototype = Object.create(MozMillElement.prototype, {
   931   select : {
   932     /**
   933      * Select the specified option and trigger the relevant events of the element
   934      * @return {boolean}
   935      */
   936     value : function MMDL_select(index, option, value) {
   937       if (!this.element){
   938         throw new Error("Could not find element " + this.getInfo());
   939       }
   941       //if we have a select drop down
   942       if (this.element.localName.toLowerCase() == "select"){
   943         var item = null;
   945         // The selected item should be set via its index
   946         if (index != undefined) {
   947           // Resetting a menulist has to be handled separately
   948           if (index == -1) {
   949             this.dispatchEvent('focus', false);
   950             this.element.selectedIndex = index;
   951             this.dispatchEvent('change', true);
   953             broker.pass({'function':'MozMillDropList.select()'});
   955             return true;
   956           } else {
   957             item = this.element.options.item(index);
   958           }
   959         } else {
   960           for (var i = 0; i < this.element.options.length; i++) {
   961             var entry = this.element.options.item(i);
   962             if (option != undefined && entry.innerHTML == option ||
   963               value != undefined && entry.value == value) {
   964               item = entry;
   965               break;
   966             }
   967           }
   968         }
   970         // Click the item
   971         try {
   972           // EventUtils.synthesizeMouse doesn't work.
   973           this.dispatchEvent('focus', false);
   974           item.selected = true;
   975           this.dispatchEvent('change', true);
   977           var self = this;
   978           var selected = index || option || value;
   979           assert.waitFor(function () {
   980             switch (selected) {
   981               case index:
   982                 return selected === self.element.selectedIndex;
   983                 break;
   984               case option:
   985                 return selected === item.label;
   986                 break;
   987               case value:
   988                 return selected === item.value;
   989                 break;
   990             }
   991           }, "DropList.select(): The correct item has been selected");
   993           broker.pass({'function':'MozMillDropList.select()'});
   995           return true;
   996         } catch (e) {
   997           throw new Error("No item selected for element " + this.getInfo());
   998         }
   999       }
  1000       //if we have a xul menupopup select accordingly
  1001       else if (this.element.namespaceURI.toLowerCase() == NAMESPACE_XUL) {
  1002         var ownerDoc = this.element.ownerDocument;
  1003         // Unwrap the XUL element's XPCNativeWrapper
  1004         this.element = utils.unwrapNode(this.element);
  1005         // Get the list of menuitems
  1006         var menuitems = this.element.
  1007                         getElementsByTagNameNS(NAMESPACE_XUL, "menupopup")[0].
  1008                         getElementsByTagNameNS(NAMESPACE_XUL, "menuitem");
  1010         var item = null;
  1012         if (index != undefined) {
  1013           if (index == -1) {
  1014             this.dispatchEvent('focus', false);
  1015             this.element.boxObject.QueryInterface(Ci.nsIMenuBoxObject).activeChild = null;
  1016             this.dispatchEvent('change', true);
  1018             broker.pass({'function':'MozMillDropList.select()'});
  1020             return true;
  1021           } else {
  1022             item = menuitems[index];
  1024         } else {
  1025           for (var i = 0; i < menuitems.length; i++) {
  1026             var entry = menuitems[i];
  1027             if (option != undefined && entry.label == option ||
  1028               value != undefined && entry.value == value) {
  1029               item = entry;
  1030               break;
  1035         // Click the item
  1036         try {
  1037           item.click();
  1039           var self = this;
  1040           var selected = index || option || value;
  1041           assert.waitFor(function () {
  1042             switch (selected) {
  1043               case index:
  1044                 return selected === self.element.selectedIndex;
  1045                 break;
  1046               case option:
  1047                 return selected === self.element.label;
  1048                 break;
  1049               case value:
  1050                 return selected === self.element.value;
  1051                 break;
  1053           }, "DropList.select(): The correct item has been selected");
  1055           broker.pass({'function':'MozMillDropList.select()'});
  1057           return true;
  1058         } catch (e) {
  1059           throw new Error('No item selected for element ' + this.getInfo());
  1064 });
  1067 /**
  1068  * Returns true if node is of type MozMillDropList
  1070  * @static
  1071  * @param {DOMNode} node Node to check for its type
  1072  * @return {boolean} True if node is of type dropdown list
  1073  */
  1074 MozMillDropList.isType = function MMR_isType(node) {
  1075   return ((node.localName.toLowerCase() == 'toolbarbutton' &&
  1076     (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
  1077     (node.localName.toLowerCase() == 'menu') ||
  1078     (node.localName.toLowerCase() == 'menulist') ||
  1079     (node.localName.toLowerCase() == 'select' ));
  1080 };
  1083 /**
  1084  * MozMillTextBox, which inherits from MozMillElement
  1085  */
  1086 function MozMillTextBox(locatorType, locator, args) {
  1087   MozMillElement.call(this, locatorType, locator, args);
  1091 MozMillTextBox.prototype = Object.create(MozMillElement.prototype, {
  1092   sendKeys : {
  1093     /**
  1094      * Synthesize keypress events for each character on the given element
  1096      * @param {string} aText
  1097      *        The text to send as single keypress events
  1098      * @param {object} aModifiers
  1099      *        Information about the modifier keys to send
  1100      *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
  1101      *                               [optional - default: false]
  1102      *                  altKey     - Hold down the alt key
  1103      *                              [optional - default: false]
  1104      *                  ctrlKey    - Hold down the ctrl key
  1105      *                               [optional - default: false]
  1106      *                  metaKey    - Hold down the meta key (command key on Mac)
  1107      *                               [optional - default: false]
  1108      *                  shiftKey   - Hold down the shift key
  1109      *                               [optional - default: false]
  1110      * @param {object} aExpectedEvent
  1111      *        Information about the expected event to occur
  1112      *        Elements: target     - Element which should receive the event
  1113      *                               [optional - default: current element]
  1114      *                  type       - Type of the expected key event
  1115      * @return {boolean} Success state
  1116      */
  1117     value : function MMTB_sendKeys(aText, aModifiers, aExpectedEvent) {
  1118       if (!this.element) {
  1119         throw new Error("could not find element " + this.getInfo());
  1122       var element = this.element;
  1123       Array.forEach(aText, function (letter) {
  1124         var win = element.ownerDocument ? element.ownerDocument.defaultView
  1125           : element;
  1126         element.focus();
  1128         if (aExpectedEvent) {
  1129           if (!aExpectedEvent.type) {
  1130             throw new Error(arguments.callee.name + ": Expected event type not specified");
  1133           var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
  1134             : element;
  1135           EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target,
  1136             aExpectedEvent.type,
  1137             "MozMillTextBox.sendKeys()", win);
  1138         } else {
  1139           EventUtils.synthesizeKey(letter, aModifiers || {}, win);
  1141       });
  1143       broker.pass({'function':'MozMillTextBox.type()'});
  1145       return true;
  1148 });
  1151 /**
  1152  * Returns true if node is of type MozMillTextBox
  1154  * @static
  1155  * @param {DOMNode} node Node to check for its type
  1156  * @return {boolean} True if node is of type textbox
  1157  */
  1158 MozMillTextBox.isType = function MMR_isType(node) {
  1159   return ((node.localName.toLowerCase() == 'input' &&
  1160     (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
  1161     (node.localName.toLowerCase() == 'textarea') ||
  1162     (node.localName.toLowerCase() == 'textbox'));
  1163 };

mercurial