Bug 1245153 - Convert interactions.js and elements.js to modules; r=automatedtester

To simplify the dependency chain and reduce the number of duplicate
functions in Marionette, a number of functions have been removed from
interactions.js and added to elements.js.  This makes them more easily
re-usable and works around a circular dependency issue.

MozReview-Commit-ID: TZc3VZzHqM

--HG--
extra : histedit_source : 06b82234371ca8cd6bb0ede9cbf03f848fc66d82
extra : rebase_source : da99b4329baba3fa8a604b9b2952692568b96a75
extra : commitid : BTgnO71SGbB
extra : source : 5da7628c376765842c013f90566d4264f9e9312a
extra : intermediate-source : 68a6dda373d2efffd8cf76103a80f1003c969093
This commit is contained in:
Andreas Tolfsen 2016-02-03 18:54:23 +00:00
Родитель 3ce634a949
Коммит f363202bb1
2 изменённых файлов: 135 добавлений и 105 удалений

Просмотреть файл

@ -2,8 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * 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/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
let {classes: Cc, interfaces: Ci, utils: Cu} = Components; "use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("chrome://marionette/content/atoms.js");
Cu.import("chrome://marionette/content/error.js"); Cu.import("chrome://marionette/content/error.js");
/** /**
@ -299,7 +302,7 @@ ElementManager.prototype = {
* as its members. * as its members.
*/ */
applyNamedArgs: function EM_applyNamedArgs(args) { applyNamedArgs: function EM_applyNamedArgs(args) {
namedArgs = {}; let namedArgs = {};
args.forEach(function(arg) { args.forEach(function(arg) {
if (arg && typeof(arg['__marionetteArgs']) === 'object') { if (arg && typeof(arg['__marionetteArgs']) === 'object') {
for (let prop in arg['__marionetteArgs']) { for (let prop in arg['__marionetteArgs']) {
@ -592,10 +595,113 @@ ElementManager.prototype = {
} }
return elements; return elements;
}, },
} };
this.elements = {}; this.elements = {};
elements.generateUUID = function() { elements.generateUUID = function() {
let uuid = uuidGen.generateUUID().toString(); let uuid = uuidGen.generateUUID().toString();
return uuid.substring(1, uuid.length - 1); return uuid.substring(1, uuid.length - 1);
}; };
/**
* This function generates a pair of coordinates relative to the viewport
* given a target element and coordinates relative to that element's
* top-left corner.
*
* @param {Node} node
* Target node.
* @param {number=} x
* Horizontal offset relative to target. Defaults to the centre of
* the target's bounding box.
* @param {number=} y
* Vertical offset relative to target. Defaults to the centre of
* the target's bounding box.
*/
// TODO(ato): Replicated from listener.js for the time being
elements.coordinates = function(node, x = undefined, y = undefined) {
let box = node.getBoundingClientRect();
if (!x) {
x = box.width / 2.0;
}
if (!y) {
y = box.height / 2.0;
}
return {
x: box.left + x,
y: box.top + y,
};
}
/**
* This function returns true if the node is in the viewport.
*
* @param {Element} element
* Target element.
* @param {number=} x
* Horizontal offset relative to target. Defaults to the centre of
* the target's bounding box.
* @param {number=} y
* Vertical offset relative to target. Defaults to the centre of
* the target's bounding box.
*/
elements.inViewport = function(el, x = undefined, y = undefined) {
let win = el.ownerDocument.defaultView;
let c = elements.coordinates(el, x, y);
let vp = {
top: win.pageYOffset,
left: win.pageXOffset,
bottom: (win.pageYOffset + win.innerHeight),
right: (win.pageXOffset + win.innerWidth)
};
return (vp.left <= c.x + win.pageXOffset &&
c.x + win.pageXOffset <= vp.right &&
vp.top <= c.y + win.pageYOffset &&
c.y + win.pageYOffset <= vp.bottom);
};
/**
* This function throws the visibility of the element error if the element is
* not displayed or the given coordinates are not within the viewport.
*
* @param {Element} element
* Element to check if visible.
* @param {Window} window
* Window object.
* @param {number=} x
* Horizontal offset relative to target. Defaults to the centre of
* the target's bounding box.
* @param {number=} y
* Vertical offset relative to target. Defaults to the centre of
* the target's bounding box.
*/
elements.checkVisible = function(el, win, x = undefined, y = undefined) {
// Bug 1094246: Webdriver's isShown doesn't work with content xul
let ns = atom.getElementAttribute(el, "namespaceURI", win);
if (ns.indexOf("there.is.only.xul") < 0 &&
!atom.isElementDisplayed(el, win)) {
return false;
}
if (el.tagName.toLowerCase() == "body") {
return true;
}
if (!elements.inViewport(el, x, y)) {
if (el.scrollIntoView) {
el.scrollIntoView(false);
if (!elements.inViewport(el)) {
return false;
}
} else {
return false;
}
}
return true;
};
elements.isXULElement = function(el) {
let ns = atom.getElementAttribute(el, "namespaceURI");
return ns.indexOf("there.is.only.xul") >= 0;
};

Просмотреть файл

@ -2,15 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * 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/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global Components, Accessibility, ElementNotVisibleError, "use strict";
InvalidElementStateError, Interactions */
var {utils: Cu} = Components; const {utils: Cu} = Components;
this.EXPORTED_SYMBOLS = ['Interactions']; Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/atoms.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/event.js");
Cu.import('chrome://marionette/content/accessibility.js'); this.EXPORTED_SYMBOLS = ["Interactions"];
Cu.import('chrome://marionette/content/error.js');
/** /**
* XUL elements that support disabled attribtue. * XUL elements that support disabled attribtue.
@ -72,32 +74,11 @@ const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
'TAB' 'TAB'
]); ]);
/**
* This function generates a pair of coordinates relative to the viewport given
* a target element and coordinates relative to that element's top-left corner.
* @param 'x', and 'y' are the relative to the target.
* If they are not specified, then the center of the target is used.
*/
function coordinates(target, x, y) {
let box = target.getBoundingClientRect();
if (typeof x === 'undefined') {
x = box.width / 2;
}
if (typeof y === 'undefined') {
y = box.height / 2;
}
return {
x: box.left + x,
y: box.top + y
};
}
/** /**
* A collection of interactions available in marionette. * A collection of interactions available in marionette.
* @type {Object} * @type {Object}
*/ */
this.Interactions = function(utils, getCapabilies) { this.Interactions = function(getCapabilies) {
this.utils = utils;
this.accessibility = new Accessibility(getCapabilies); this.accessibility = new Accessibility(getCapabilies);
}; };
@ -115,22 +96,25 @@ Interactions.prototype = {
*/ */
clickElement(container, elementManager, id) { clickElement(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container); let el = elementManager.getKnownElement(id, container);
let visible = this.checkVisible(container, el); let visible = elements.checkVisible(el, container.frame);
if (!visible) { if (!visible) {
throw new ElementNotVisibleError('Element is not visible'); throw new ElementNotVisibleError('Element is not visible');
} }
return this.accessibility.getAccessibleObject(el, true).then(acc => { return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkVisible(acc, el, visible); this.accessibility.checkVisible(acc, el, visible);
if (this.utils.isElementEnabled(el)) { if (atom.isElementEnabled(el)) {
this.accessibility.checkEnabled(acc, el, true, container); this.accessibility.checkEnabled(acc, el, true, container);
this.accessibility.checkActionable(acc, el); this.accessibility.checkActionable(acc, el);
if (this.isXULElement(el)) { if (elements.isXULElement(el)) {
el.click(); el.click();
} else { } else {
let rects = el.getClientRects(); let rects = el.getClientRects();
this.utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2, let win = el.ownerDocument.defaultView;
rects[0].top + rects[0].height/2, event.synthesizeMouseAtPoint(
{}, el.ownerDocument.defaultView); rects[0].left + rects[0].width / 2,
rects[0].top + rects[0].height / 2,
{} /* opts */,
win);
} }
} else { } else {
throw new InvalidElementStateError('Element is not enabled'); throw new InvalidElementStateError('Element is not enabled');
@ -159,8 +143,8 @@ Interactions.prototype = {
let el = elementManager.getKnownElement(id, container); let el = elementManager.getKnownElement(id, container);
return this.accessibility.getAccessibleObject(el, true).then(acc => { return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkActionable(acc, el); this.accessibility.checkActionable(acc, el);
this.utils.sendKeysToElement( event.sendKeysToElement(
container.frame, el, value, ignoreVisibility); value, el, {ignoreVisibility: false}, container.frame);
}); });
}, },
@ -180,7 +164,7 @@ Interactions.prototype = {
*/ */
isElementDisplayed(container, elementManager, id) { isElementDisplayed(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container); let el = elementManager.getKnownElement(id, container);
let displayed = this.utils.isElementDisplayed(el); let displayed = atom.isElementDisplayed(el, container.frame);
return this.accessibility.getAccessibleObject(el).then(acc => { return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkVisible(acc, el, displayed); this.accessibility.checkVisible(acc, el, displayed);
return displayed; return displayed;
@ -204,16 +188,16 @@ Interactions.prototype = {
isElementEnabled(container, elementManager, id) { isElementEnabled(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container); let el = elementManager.getKnownElement(id, container);
let enabled = true; let enabled = true;
if (this.isXULElement(el)) { if (elements.isXULElement(el)) {
// Check if XUL element supports disabled attribute // Check if XUL element supports disabled attribute
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) { if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
let disabled = this.utils.getElementAttribute(el, 'disabled'); let disabled = atom.getElementAttribute(el, 'disabled', container.frame);
if (disabled && disabled === 'true') { if (disabled && disabled === 'true') {
enabled = false; enabled = false;
} }
} }
} else { } else {
enabled = this.utils.isElementEnabled(el); enabled = atom.isElementEnabled(el, container.frame);
} }
return this.accessibility.getAccessibleObject(el).then(acc => { return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkEnabled(acc, el, enabled, container); this.accessibility.checkEnabled(acc, el, enabled, container);
@ -238,7 +222,7 @@ Interactions.prototype = {
isElementSelected(container, elementManager, id) { isElementSelected(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container); let el = elementManager.getKnownElement(id, container);
let selected = true; let selected = true;
if (this.isXULElement(el)) { if (elements.isXULElement(el)) {
let tagName = el.tagName.toUpperCase(); let tagName = el.tagName.toUpperCase();
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) { if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.checked; selected = el.checked;
@ -247,71 +231,11 @@ Interactions.prototype = {
selected = el.selected; selected = el.selected;
} }
} else { } else {
selected = this.utils.isElementSelected(el); selected = atom.isElementSelected(el, container.frame);
} }
return this.accessibility.getAccessibleObject(el).then(acc => { return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkSelected(acc, el, selected); this.accessibility.checkSelected(acc, el, selected);
return selected; return selected;
}); });
}, },
/**
* This function throws the visibility of the element error if the element is
* not displayed or the given coordinates are not within the viewport.
*
* @param 'x', and 'y' are the coordinates relative to the target.
* If they are not specified, then the center of the target is used.
*/
checkVisible(container, el, x, y) {
// Bug 1094246 - Webdriver's isShown doesn't work with content xul
if (!this.isXULElement(el)) {
//check if the element is visible
let visible = this.utils.isElementDisplayed(el);
if (!visible) {
return false;
}
}
if (el.tagName.toLowerCase() === 'body') {
return true;
}
if (!this.elementInViewport(container, el, x, y)) {
//check if scroll function exist. If so, call it.
if (el.scrollIntoView) {
el.scrollIntoView(false);
if (!this.elementInViewport(container, el)) {
return false;
}
}
else {
return false;
}
}
return true;
},
isXULElement(el) {
return this.utils.getElementAttribute(el, 'namespaceURI').indexOf(
'there.is.only.xul') >= 0;
},
/**
* This function returns true if the given coordinates are in the viewport.
* @param 'x', and 'y' are the coordinates relative to the target.
* If they are not specified, then the center of the target is used.
*/
elementInViewport(container, el, x, y) {
let c = coordinates(el, x, y);
let win = container.frame;
let viewPort = {
top: win.pageYOffset,
left: win.pageXOffset,
bottom: win.pageYOffset + win.innerHeight,
right: win.pageXOffset + win.innerWidth
};
return (viewPort.left <= c.x + win.pageXOffset &&
c.x + win.pageXOffset <= viewPort.right &&
viewPort.top <= c.y + win.pageYOffset &&
c.y + win.pageYOffset <= viewPort.bottom);
}
}; };