Dactyloidae/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
2019-02-16 00:21:27 +08:00

1163 lines
39 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
"MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
"MozMillTextBox", "subclasses"
];
const NAMESPACE_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
var assert = new assertions.Assert();
// A list of all the subclasses available. Shared modules can push their own subclasses onto this list
var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
/**
* createInstance()
*
* Returns an new instance of a MozMillElement
* The type of the element is automatically determined
*/
function createInstance(locatorType, locator, elem, document) {
var args = { "document": document, "element": elem };
// If we already have an element lets determine the best MozMillElement type
if (elem) {
for (var i = 0; i < subclasses.length; ++i) {
if (subclasses[i].isType(elem)) {
return new subclasses[i](locatorType, locator, args);
}
}
}
// By default we create a base MozMillElement
if (MozMillElement.isType(elem)) {
return new MozMillElement(locatorType, locator, args);
}
throw new Error("Unsupported element type " + locatorType + ": " + locator);
}
var Elem = function (node) {
return createInstance("Elem", node, node);
};
var Selector = function (document, selector, index) {
return createInstance("Selector", selector, elementslib.Selector(document, selector, index), document);
};
var ID = function (document, nodeID) {
return createInstance("ID", nodeID, elementslib.ID(document, nodeID), document);
};
var Link = function (document, linkName) {
return createInstance("Link", linkName, elementslib.Link(document, linkName), document);
};
var XPath = function (document, expr) {
return createInstance("XPath", expr, elementslib.XPath(document, expr), document);
};
var Name = function (document, nName) {
return createInstance("Name", nName, elementslib.Name(document, nName), document);
};
var Lookup = function (document, expression) {
var elem = createInstance("Lookup", expression, elementslib.Lookup(document, expression), document);
// Bug 864268 - Expose the expression property to maintain backwards compatibility
elem.expression = elem._locator;
return elem;
};
/**
* MozMillElement
* The base class for all mozmill elements
*/
function MozMillElement(locatorType, locator, args) {
args = args || {};
this._locatorType = locatorType;
this._locator = locator;
this._element = args["element"];
this._owner = args["owner"];
this._document = this._element ? this._element.ownerDocument : args["document"];
this._defaultView = this._document ? this._document.defaultView : null;
// Used to maintain backwards compatibility with controller.js
this.isElement = true;
}
// Static method that returns true if node is of this element type
MozMillElement.isType = function (node) {
return true;
};
// This getter is the magic behind lazy loading (note distinction between _element and element)
MozMillElement.prototype.__defineGetter__("element", function () {
// If the document is invalid (e.g. reload of the page), invalidate the cached
// element and update the document cache
if (this._defaultView && this._defaultView.document !== this._document) {
this._document = this._defaultView.document;
this._element = undefined;
}
if (this._element == undefined) {
if (elementslib[this._locatorType]) {
this._element = elementslib[this._locatorType](this._document, this._locator);
} else if (this._locatorType == "Elem") {
this._element = this._locator;
} else {
throw new Error("Unknown locator type: " + this._locatorType);
}
}
return this._element;
});
/**
* Drag an element to the specified offset on another element, firing mouse and
* drag events. Adapted from ChromeUtils.js synthesizeDrop()
*
* By default it will drag the source element over the destination's element
* center with a "move" dropEffect.
*
* @param {MozElement} aElement
* Destination element over which the drop occurs
* @param {Number} [aOffsetX=aElement.width/2]
* Relative x offset for dropping on aElement
* @param {Number} [aOffsetY=aElement.height/2]
* Relative y offset for dropping on aElement
* @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
* Custom source Window to be used.
* @param {DOMWindow} [aDestWindow=aElement.getNode().ownerDocument.defaultView]
* Custom destination Window to be used.
* @param {String} [aDropEffect="move"]
* Possible values: copy, move, link, none
* @param {Object[]} [aDragData]
* An array holding custom drag data to be used during the drag event
* Format: [{ type: "text/plain", "Text to drag"}, ...]
*
* @returns {String} the captured dropEffect
*/
MozMillElement.prototype.dragToElement = function(aElement, aOffsetX, aOffsetY,
aSourceWindow, aDestWindow,
aDropEffect, aDragData) {
if (!this.element) {
throw new Error("Could not find element " + this.getInfo());
}
if (!aElement) {
throw new Error("Missing destination element");
}
var srcNode = this.element;
var destNode = aElement.getNode();
var srcWindow = aSourceWindow ||
(srcNode.ownerDocument ? srcNode.ownerDocument.defaultView
: srcNode);
var destWindow = aDestWindow ||
(destNode.ownerDocument ? destNode.ownerDocument.defaultView
: destNode);
var srcRect = srcNode.getBoundingClientRect();
var srcCoords = {
x: srcRect.width / 2,
y: srcRect.height / 2
};
var destRect = destNode.getBoundingClientRect();
var destCoords = {
x: (!aOffsetX || isNaN(aOffsetX)) ? (destRect.width / 2) : aOffsetX,
y: (!aOffsetY || isNaN(aOffsetY)) ? (destRect.height / 2) : aOffsetY
};
var windowUtils = destWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
var ds = Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService);
var dataTransfer;
var trapDrag = function (event) {
srcWindow.removeEventListener("dragstart", trapDrag, true);
dataTransfer = event.dataTransfer;
if (!aDragData) {
return;
}
for (var i = 0; i < aDragData.length; i++) {
var item = aDragData[i];
for (var j = 0; j < item.length; j++) {
dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
}
}
dataTransfer.dropEffect = aDropEffect || "move";
event.preventDefault();
event.stopPropagation();
}
ds.startDragSession();
try {
srcWindow.addEventListener("dragstart", trapDrag, true);
EventUtils.synthesizeMouse(srcNode, srcCoords.x, srcCoords.y,
{ type: "mousedown" }, srcWindow);
EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
{ type: "mousemove" }, destWindow);
var event = destWindow.document.createEvent("DragEvents");
event.initDragEvent("dragenter", true, true, destWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
event.initDragEvent("dragover", true, true, destWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
event.initDragEvent("drop", true, true, destWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
windowUtils.dispatchDOMEventViaPresShell(destNode, event, true);
EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
{ type: "mouseup" }, destWindow);
return dataTransfer.dropEffect;
} finally {
ds.endDragSession(true);
}
};
// Returns the actual wrapped DOM node
MozMillElement.prototype.getNode = function () {
return this.element;
};
MozMillElement.prototype.getInfo = function () {
return this._locatorType + ": " + this._locator;
};
/**
* Sometimes an element which once existed will no longer exist in the DOM
* This function re-searches for the element
*/
MozMillElement.prototype.exists = function () {
this._element = undefined;
if (this.element) {
return true;
}
return false;
};
/**
* Synthesize a keypress event on the given element
*
* @param {string} aKey
* Key to use for synthesizing the keypress event. It can be a simple
* character like "k" or a string like "VK_ESCAPE" for command keys
* @param {object} aModifiers
* Information about the modifier keys to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected key event
*/
MozMillElement.prototype.keypress = function (aKey, aModifiers, aExpectedEvent) {
if (!this.element) {
throw new Error("Could not find element " + this.getInfo());
}
var win = this.element.ownerDocument ? this.element.ownerDocument.defaultView
: this.element;
this.element.focus();
if (aExpectedEvent) {
if (!aExpectedEvent.type) {
throw new Error(arguments.callee.name + ": Expected event type not specified");
}
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
: this.element;
EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
"MozMillElement.keypress()", win);
} else {
EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
}
broker.pass({'function':'MozMillElement.keypress()'});
return true;
};
/**
* Synthesize a general mouse event on the given element
*
* @param {number} aOffsetX
* Relative x offset in the elements bounds to click on
* @param {number} aOffsetY
* Relative y offset in the elements bounds to click on
* @param {object} aEvent
* Information about the event to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* button - Mouse button to use
* [optional - default: 0]
* clickCount - Number of counts to click
* [optional - default: 1]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* type - Type of the mouse event ('click', 'mousedown',
* 'mouseup', 'mouseover', 'mouseout')
* [optional - default: 'mousedown' + 'mouseup']
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected mouse event
*/
MozMillElement.prototype.mouseEvent = function (aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
if (!this.element) {
throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
}
if ("document" in this.element) {
throw new Error("A window cannot be a target for mouse events.");
}
var rect = this.element.getBoundingClientRect();
if (!aOffsetX || isNaN(aOffsetX)) {
aOffsetX = rect.width / 2;
}
if (!aOffsetY || isNaN(aOffsetY)) {
aOffsetY = rect.height / 2;
}
// Scroll element into view otherwise the click will fail
if ("scrollIntoView" in this.element)
this.element.scrollIntoView();
if (aExpectedEvent) {
// The expected event type has to be set
if (!aExpectedEvent.type) {
throw new Error(arguments.callee.name + ": Expected event type not specified");
}
// If no target has been specified use the specified element
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
: this.element;
if (!target) {
throw new Error(arguments.callee.name + ": could not find element " +
aExpectedEvent.target.getInfo());
}
EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
target, aExpectedEvent.type,
"MozMillElement.mouseEvent()",
this.element.ownerDocument.defaultView);
} else {
EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
this.element.ownerDocument.defaultView);
}
// Bug 555347
// We don't know why this sleep is necessary but more investigation is needed
// before it can be removed
utils.sleep(0);
return true;
};
/**
* Synthesize a mouse click event on the given element
*/
MozMillElement.prototype.click = function (aOffsetX, aOffsetY, aExpectedEvent) {
// Handle menu items differently
if (this.element && this.element.tagName == "menuitem") {
this.element.click();
} else {
this.mouseEvent(aOffsetX, aOffsetY, {}, aExpectedEvent);
}
broker.pass({'function':'MozMillElement.click()'});
return true;
};
/**
* Synthesize a double click on the given element
*/
MozMillElement.prototype.doubleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {clickCount: 2}, aExpectedEvent);
broker.pass({'function':'MozMillElement.doubleClick()'});
return true;
};
/**
* Synthesize a mouse down event on the given element
*/
MozMillElement.prototype.mouseDown = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mousedown"}, aExpectedEvent);
broker.pass({'function':'MozMillElement.mouseDown()'});
return true;
};
/**
* Synthesize a mouse out event on the given element
*/
MozMillElement.prototype.mouseOut = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseout"}, aExpectedEvent);
broker.pass({'function':'MozMillElement.mouseOut()'});
return true;
};
/**
* Synthesize a mouse over event on the given element
*/
MozMillElement.prototype.mouseOver = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseover"}, aExpectedEvent);
broker.pass({'function':'MozMillElement.mouseOver()'});
return true;
};
/**
* Synthesize a mouse up event on the given element
*/
MozMillElement.prototype.mouseUp = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseup"}, aExpectedEvent);
broker.pass({'function':'MozMillElement.mouseUp()'});
return true;
};
/**
* Synthesize a mouse middle click event on the given element
*/
MozMillElement.prototype.middleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {button: 1}, aExpectedEvent);
broker.pass({'function':'MozMillElement.middleClick()'});
return true;
};
/**
* Synthesize a mouse right click event on the given element
*/
MozMillElement.prototype.rightClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {type : "contextmenu", button: 2 }, aExpectedEvent);
broker.pass({'function':'MozMillElement.rightClick()'});
return true;
};
/**
* Synthesize a general touch event on the given element
*
* @param {Number} [aOffsetX=aElement.width / 2]
* Relative x offset in the elements bounds to click on
* @param {Number} [aOffsetY=aElement.height / 2]
* Relative y offset in the elements bounds to click on
* @param {Object} [aEvent]
* Information about the event to send
* @param {Boolean} [aEvent.altKey=false]
* A Boolean value indicating whether or not the alt key was down when
* the touch event was fired
* @param {Number} [aEvent.angle=0]
* The angle (in degrees) that the ellipse described by rx and
* ry must be rotated, clockwise, to most accurately cover the area
* of contact between the user and the surface.
* @param {Touch[]} [aEvent.changedTouches]
* A TouchList of all the Touch objects representing individual points of
* contact whose states changed between the previous touch event and
* this one
* @param {Boolean} [aEvent.ctrlKey]
* A Boolean value indicating whether or not the control key was down
* when the touch event was fired
* @param {Number} [aEvent.force=1]
* The amount of pressure being applied to the surface by the user, as a
* float between 0.0 (no pressure) and 1.0 (maximum pressure)
* @param {Number} [aEvent.id=0]
* A unique identifier for this Touch object. A given touch (say, by a
* finger) will have the same identifier for the duration of its movement
* around the surface. This lets you ensure that you're tracking the same
* touch all the time
* @param {Boolean} [aEvent.metaKey]
* A Boolean value indicating whether or not the meta key was down when
* the touch event was fired.
* @param {Number} [aEvent.rx=1]
* The X radius of the ellipse that most closely circumscribes the area
* of contact with the screen.
* @param {Number} [aEvent.ry=1]
* The Y radius of the ellipse that most closely circumscribes the area
* of contact with the screen.
* @param {Boolean} [aEvent.shiftKey]
* A Boolean value indicating whether or not the shift key was down when
* the touch event was fired
* @param {Touch[]} [aEvent.targetTouches]
* A TouchList of all the Touch objects that are both currently in
* contact with the touch surface and were also started on the same
* element that is the target of the event
* @param {Touch[]} [aEvent.touches]
* A TouchList of all the Touch objects representing all current points
* of contact with the surface, regardless of target or changed status
* @param {Number} [aEvent.type=*|touchstart|touchend|touchmove|touchenter|touchleave|touchcancel]
* The type of touch event that occurred
* @param {Element} [aEvent.target]
* The target of the touches associated with this event. This target
* corresponds to the target of all the touches in the targetTouches
* attribute, but note that other touches in this event may have a
* different target. To be careful, you should use the target associated
* with individual touches
*/
MozMillElement.prototype.touchEvent = function (aOffsetX, aOffsetY, aEvent) {
if (!this.element) {
throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
}
if ("document" in this.element) {
throw new Error("A window cannot be a target for touch events.");
}
var rect = this.element.getBoundingClientRect();
if (!aOffsetX || isNaN(aOffsetX)) {
aOffsetX = rect.width / 2;
}
if (!aOffsetY || isNaN(aOffsetY)) {
aOffsetY = rect.height / 2;
}
// Scroll element into view otherwise the click will fail
if ("scrollIntoView" in this.element) {
this.element.scrollIntoView();
}
EventUtils.synthesizeTouch(this.element, aOffsetX, aOffsetY, aEvent,
this.element.ownerDocument.defaultView);
return true;
};
/**
* Synthesize a touch tap event on the given element
*
* @param {Number} [aOffsetX=aElement.width / 2]
* Left offset in px where the event is triggered
* @param {Number} [aOffsetY=aElement.height / 2]
* Top offset in px where the event is triggered
* @param {Object} [aExpectedEvent]
* Information about the expected event to occur
* @param {MozMillElement} [aExpectedEvent.target=this.element]
* Element which should receive the event
* @param {MozMillElement} [aExpectedEvent.type]
* Type of the expected mouse event
*/
MozMillElement.prototype.tap = function (aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {
clickCount: 1,
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
}, aExpectedEvent);
broker.pass({'function':'MozMillElement.tap()'});
return true;
};
/**
* Synthesize a double tap on the given element
*
* @param {Number} [aOffsetX=aElement.width / 2]
* Left offset in px where the event is triggered
* @param {Number} [aOffsetY=aElement.height / 2]
* Top offset in px where the event is triggered
* @param {Object} [aExpectedEvent]
* Information about the expected event to occur
* @param {MozMillElement} [aExpectedEvent.target=this.element]
* Element which should receive the event
* @param {MozMillElement} [aExpectedEvent.type]
* Type of the expected mouse event
*/
MozMillElement.prototype.doubleTap = function (aOffsetX, aOffsetY, aExpectedEvent) {
this.mouseEvent(aOffsetX, aOffsetY, {
clickCount: 2,
inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
}, aExpectedEvent);
broker.pass({'function':'MozMillElement.doubleTap()'});
return true;
};
/**
* Synthesize a long press
*
* @param {Number} aOffsetX
* Left offset in px where the event is triggered
* @param {Number} aOffsetY
* Top offset in px where the event is triggered
* @param {Number} [aTime=1000]
* Duration of the "press" event in ms
*/
MozMillElement.prototype.longPress = function (aOffsetX, aOffsetY, aTime) {
var time = aTime || 1000;
this.touchStart(aOffsetX, aOffsetY);
utils.sleep(time);
this.touchEnd(aOffsetX, aOffsetY);
broker.pass({'function':'MozMillElement.longPress()'});
return true;
};
/**
* Synthesize a touch & drag event on the given element
*
* @param {Number} aOffsetX1
* Left offset of the start position
* @param {Number} aOffsetY1
* Top offset of the start position
* @param {Number} aOffsetX2
* Left offset of the end position
* @param {Number} aOffsetY2
* Top offset of the end position
*/
MozMillElement.prototype.touchDrag = function (aOffsetX1, aOffsetY1, aOffsetX2, aOffsetY2) {
this.touchStart(aOffsetX1, aOffsetY1);
this.touchMove(aOffsetX2, aOffsetY2);
this.touchEnd(aOffsetX2, aOffsetY2);
broker.pass({'function':'MozMillElement.move()'});
return true;
};
/**
* Synthesize a press / touchstart event on the given element
*
* @param {Number} aOffsetX
* Left offset where the event is triggered
* @param {Number} aOffsetY
* Top offset where the event is triggered
*/
MozMillElement.prototype.touchStart = function (aOffsetX, aOffsetY) {
this.touchEvent(aOffsetX, aOffsetY, { type: "touchstart" });
broker.pass({'function':'MozMillElement.touchStart()'});
return true;
};
/**
* Synthesize a release / touchend event on the given element
*
* @param {Number} aOffsetX
* Left offset where the event is triggered
* @param {Number} aOffsetY
* Top offset where the event is triggered
*/
MozMillElement.prototype.touchEnd = function (aOffsetX, aOffsetY) {
this.touchEvent(aOffsetX, aOffsetY, { type: "touchend" });
broker.pass({'function':'MozMillElement.touchEnd()'});
return true;
};
/**
* Synthesize a touchMove event on the given element
*
* @param {Number} aOffsetX
* Left offset where the event is triggered
* @param {Number} aOffsetY
* Top offset where the event is triggered
*/
MozMillElement.prototype.touchMove = function (aOffsetX, aOffsetY) {
this.touchEvent(aOffsetX, aOffsetY, { type: "touchmove" });
broker.pass({'function':'MozMillElement.touchMove()'});
return true;
};
MozMillElement.prototype.waitForElement = function (timeout, interval) {
var elem = this;
assert.waitFor(function () {
return elem.exists();
}, "Element.waitForElement(): Element '" + this.getInfo() +
"' has been found", timeout, interval);
broker.pass({'function':'MozMillElement.waitForElement()'});
};
MozMillElement.prototype.waitForElementNotPresent = function (timeout, interval) {
var elem = this;
assert.waitFor(function () {
return !elem.exists();
}, "Element.waitForElementNotPresent(): Element '" + this.getInfo() +
"' has not been found", timeout, interval);
broker.pass({'function':'MozMillElement.waitForElementNotPresent()'});
};
MozMillElement.prototype.waitThenClick = function (timeout, interval,
aOffsetX, aOffsetY, aExpectedEvent) {
this.waitForElement(timeout, interval);
this.click(aOffsetX, aOffsetY, aExpectedEvent);
};
/**
* Waits for the element to be available in the DOM, then trigger a tap event
*
* @param {Number} [aTimeout=5000]
* Time to wait for the element to be available
* @param {Number} [aInterval=100]
* Interval to check for availability
* @param {Number} [aOffsetX=aElement.width / 2]
* Left offset where the event is triggered
* @param {Number} [aOffsetY=aElement.height / 2]
* Top offset where the event is triggered
* @param {Object} [aExpectedEvent]
* Information about the expected event to occur
* @param {MozMillElement} [aExpectedEvent.target=this.element]
* Element which should receive the event
* @param {MozMillElement} [aExpectedEvent.type]
* Type of the expected mouse event
*/
MozMillElement.prototype.waitThenTap = function (aTimeout, aInterval,
aOffsetX, aOffsetY, aExpectedEvent) {
this.waitForElement(aTimeout, aInterval);
this.tap(aOffsetX, aOffsetY, aExpectedEvent);
};
// Dispatches an HTMLEvent
MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
canBubble = canBubble || true;
modifiers = modifiers || { };
let document = 'ownerDocument' in this.element ? this.element.ownerDocument
: this.element.document;
let evt = document.createEvent('HTMLEvents');
evt.shiftKey = modifiers["shift"];
evt.metaKey = modifiers["meta"];
evt.altKey = modifiers["alt"];
evt.ctrlKey = modifiers["ctrl"];
evt.initEvent(eventType, canBubble, true);
this.element.dispatchEvent(evt);
};
/**
* MozMillCheckBox, which inherits from MozMillElement
*/
function MozMillCheckBox(locatorType, locator, args) {
MozMillElement.call(this, locatorType, locator, args);
}
MozMillCheckBox.prototype = Object.create(MozMillElement.prototype, {
check : {
/**
* Enable/Disable a checkbox depending on the target state
*
* @param {boolean} state State to set
* @return {boolean} Success state
*/
value : function MMCB_check(state) {
var result = false;
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
}
// If we have a XUL element, unwrap its XPCNativeWrapper
if (this.element.namespaceURI == NAMESPACE_XUL) {
this.element = utils.unwrapNode(this.element);
}
state = (typeof(state) == "boolean") ? state : false;
if (state != this.element.checked) {
this.click();
var element = this.element;
assert.waitFor(function () {
return element.checked == state;
}, "CheckBox.check(): Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
result = true;
}
broker.pass({'function':'MozMillCheckBox.check(' + this.getInfo() +
', state: ' + state + ')'});
return result;
}
}
});
/**
* Returns true if node is of type MozMillCheckBox
*
* @static
* @param {DOMNode} node Node to check for its type
* @return {boolean} True if node is of type checkbox
*/
MozMillCheckBox.isType = function MMCB_isType(node) {
return ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
(node.localName.toLowerCase() == 'checkbox'));
};
/**
* MozMillRadio, which inherits from MozMillElement
*/
function MozMillRadio(locatorType, locator, args) {
MozMillElement.call(this, locatorType, locator, args);
}
MozMillRadio.prototype = Object.create(MozMillElement.prototype, {
select : {
/**
* Select the given radio button
*
* @param {number} [index=0]
* Specifies which radio button in the group to select (only
* applicable to radiogroup elements)
* @return {boolean} Success state
*/
value : function MMR_select(index) {
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
}
if (this.element.localName.toLowerCase() == "radiogroup") {
var element = this.element.getElementsByTagName("radio")[index || 0];
new MozMillRadio("Elem", element).click();
} else {
var element = this.element;
this.click();
}
assert.waitFor(function () {
// If we have a XUL element, unwrap its XPCNativeWrapper
if (element.namespaceURI == NAMESPACE_XUL) {
element = utils.unwrapNode(element);
return element.selected == true;
}
return element.checked == true;
}, "Radio.select(): Radio button " + this.getInfo() + " has been selected", 500);
broker.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
return true;
}
}
});
/**
* Returns true if node is of type MozMillRadio
*
* @static
* @param {DOMNode} node Node to check for its type
* @return {boolean} True if node is of type radio
*/
MozMillRadio.isType = function MMR_isType(node) {
return ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
(node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
(node.localName.toLowerCase() == 'radio') ||
(node.localName.toLowerCase() == 'radiogroup'));
};
/**
* MozMillDropList, which inherits from MozMillElement
*/
function MozMillDropList(locatorType, locator, args) {
MozMillElement.call(this, locatorType, locator, args);
}
MozMillDropList.prototype = Object.create(MozMillElement.prototype, {
select : {
/**
* Select the specified option and trigger the relevant events of the element
* @return {boolean}
*/
value : function MMDL_select(index, option, value) {
if (!this.element){
throw new Error("Could not find element " + this.getInfo());
}
//if we have a select drop down
if (this.element.localName.toLowerCase() == "select"){
var item = null;
// The selected item should be set via its index
if (index != undefined) {
// Resetting a menulist has to be handled separately
if (index == -1) {
this.dispatchEvent('focus', false);
this.element.selectedIndex = index;
this.dispatchEvent('change', true);
broker.pass({'function':'MozMillDropList.select()'});
return true;
} else {
item = this.element.options.item(index);
}
} else {
for (var i = 0; i < this.element.options.length; i++) {
var entry = this.element.options.item(i);
if (option != undefined && entry.innerHTML == option ||
value != undefined && entry.value == value) {
item = entry;
break;
}
}
}
// Click the item
try {
// EventUtils.synthesizeMouse doesn't work.
this.dispatchEvent('focus', false);
item.selected = true;
this.dispatchEvent('change', true);
var self = this;
var selected = index || option || value;
assert.waitFor(function () {
switch (selected) {
case index:
return selected === self.element.selectedIndex;
break;
case option:
return selected === item.label;
break;
case value:
return selected === item.value;
break;
}
}, "DropList.select(): The correct item has been selected");
broker.pass({'function':'MozMillDropList.select()'});
return true;
} catch (e) {
throw new Error("No item selected for element " + this.getInfo());
}
}
//if we have a xul menupopup select accordingly
else if (this.element.namespaceURI.toLowerCase() == NAMESPACE_XUL) {
var ownerDoc = this.element.ownerDocument;
// Unwrap the XUL element's XPCNativeWrapper
this.element = utils.unwrapNode(this.element);
// Get the list of menuitems
var menuitems = this.element.
getElementsByTagNameNS(NAMESPACE_XUL, "menupopup")[0].
getElementsByTagNameNS(NAMESPACE_XUL, "menuitem");
var item = null;
if (index != undefined) {
if (index == -1) {
this.dispatchEvent('focus', false);
this.element.boxObject.activeChild = null;
this.dispatchEvent('change', true);
broker.pass({'function':'MozMillDropList.select()'});
return true;
} else {
item = menuitems[index];
}
} else {
for (var i = 0; i < menuitems.length; i++) {
var entry = menuitems[i];
if (option != undefined && entry.label == option ||
value != undefined && entry.value == value) {
item = entry;
break;
}
}
}
// Click the item
try {
item.click();
var self = this;
var selected = index || option || value;
assert.waitFor(function () {
switch (selected) {
case index:
return selected === self.element.selectedIndex;
break;
case option:
return selected === self.element.label;
break;
case value:
return selected === self.element.value;
break;
}
}, "DropList.select(): The correct item has been selected");
broker.pass({'function':'MozMillDropList.select()'});
return true;
} catch (e) {
throw new Error('No item selected for element ' + this.getInfo());
}
}
}
}
});
/**
* Returns true if node is of type MozMillDropList
*
* @static
* @param {DOMNode} node Node to check for its type
* @return {boolean} True if node is of type dropdown list
*/
MozMillDropList.isType = function MMR_isType(node) {
return ((node.localName.toLowerCase() == 'toolbarbutton' &&
(node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
(node.localName.toLowerCase() == 'menu') ||
(node.localName.toLowerCase() == 'menulist') ||
(node.localName.toLowerCase() == 'select' ));
};
/**
* MozMillTextBox, which inherits from MozMillElement
*/
function MozMillTextBox(locatorType, locator, args) {
MozMillElement.call(this, locatorType, locator, args);
}
MozMillTextBox.prototype = Object.create(MozMillElement.prototype, {
sendKeys : {
/**
* Synthesize keypress events for each character on the given element
*
* @param {string} aText
* The text to send as single keypress events
* @param {object} aModifiers
* Information about the modifier keys to send
* Elements: accelKey - Hold down the accelerator key (ctrl/meta)
* [optional - default: false]
* altKey - Hold down the alt key
* [optional - default: false]
* ctrlKey - Hold down the ctrl key
* [optional - default: false]
* metaKey - Hold down the meta key (command key on Mac)
* [optional - default: false]
* shiftKey - Hold down the shift key
* [optional - default: false]
* @param {object} aExpectedEvent
* Information about the expected event to occur
* Elements: target - Element which should receive the event
* [optional - default: current element]
* type - Type of the expected key event
* @return {boolean} Success state
*/
value : function MMTB_sendKeys(aText, aModifiers, aExpectedEvent) {
if (!this.element) {
throw new Error("could not find element " + this.getInfo());
}
var element = this.element;
Array.forEach(aText, function (letter) {
var win = element.ownerDocument ? element.ownerDocument.defaultView
: element;
element.focus();
if (aExpectedEvent) {
if (!aExpectedEvent.type) {
throw new Error(arguments.callee.name + ": Expected event type not specified");
}
var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
: element;
EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target,
aExpectedEvent.type,
"MozMillTextBox.sendKeys()", win);
} else {
EventUtils.synthesizeKey(letter, aModifiers || {}, win);
}
});
broker.pass({'function':'MozMillTextBox.type()'});
return true;
}
}
});
/**
* Returns true if node is of type MozMillTextBox
*
* @static
* @param {DOMNode} node Node to check for its type
* @return {boolean} True if node is of type textbox
*/
MozMillTextBox.isType = function MMR_isType(node) {
return ((node.localName.toLowerCase() == 'input' &&
(node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
(node.localName.toLowerCase() == 'textarea') ||
(node.localName.toLowerCase() == 'textbox'));
};