Backed out changeset 4dec8d87c105 (bug 1245064) for marionette bustage

This commit is contained in:
Carsten "Tomcat" Book 2016-03-09 14:03:28 +01:00
Родитель 38c337c81b
Коммит 84efe80138
4 изменённых файлов: 466 добавлений и 553 удалений

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

@ -2,244 +2,209 @@
* 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/. */
"use strict";
/* global Accessibility, Components, Log, ElementNotAccessibleError,
XPCOMUtils */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Log.jsm');
Cu.import("chrome://marionette/content/error.js");
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ElementNotAccessibleError',
'chrome://marionette/content/error.js');
XPCOMUtils.defineLazyModuleGetter(
this, "setInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(
this, "clearInterval", "resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyGetter(this, "retrieval",
() => Cc["@mozilla.org/accessibleRetrieval;1"].getService(Ci.nsIAccessibleRetrieval));
this.EXPORTED_SYMBOLS = ["accessibility"];
const logger = Log.repository.getLogger("Marionette");
this.EXPORTED_SYMBOLS = ['Accessibility'];
/**
* Number of attempts to get an accessible object for an element.
* We attempt more than once because accessible tree can be out of sync
* with the DOM tree for a short period of time.
*/
const GET_ACCESSIBLE_ATTEMPTS = 100;
/**
* An interval between attempts to retrieve an accessible object for an
* element.
*/
const GET_ACCESSIBLE_ATTEMPT_INTERVAL = 10;
this.accessibility = {};
/**
* Accessible states used to check element"s state from the accessiblity API
* Accessible states used to check element's state from the accessiblity API
* perspective.
*/
accessibility.State = {
Unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
Focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
Selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
Selected: Ci.nsIAccessibleStates.STATE_SELECTED,
const states = {
unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
selected: Ci.nsIAccessibleStates.STATE_SELECTED
};
/**
* Accessible object roles that support some action.
*/
accessibility.ActionableRoles = new Set([
"checkbutton",
"check menu item",
"check rich option",
"combobox",
"combobox option",
"entry",
"key",
"link",
"listbox option",
"listbox rich option",
"menuitem",
"option",
"outlineitem",
"pagetab",
"pushbutton",
"radiobutton",
"radio menu item",
"rowheader",
"slider",
"spinbutton",
"switch",
]);
var logger = Log.repository.getLogger('Marionette');
/**
* Factory function that constructs a new {@code accessibility.Checks}
* object with enforced strictness or not.
*/
accessibility.get = function(strict = false) {
return new accessibility.Checks(!!strict);
};
/**
* Component responsible for interacting with platform accessibility
* API.
* Component responsible for interacting with platform accessibility API. Its
* methods serve as wrappers for testing content and chrome accessibility as
* well as accessibility of user interactions.
*
* Its methods serve as wrappers for testing content and chrome
* accessibility as well as accessibility of user interactions.
* @param {Function} getCapabilies
* Session capabilities getter.
*/
accessibility.Checks = class {
this.Accessibility = function Accessibility(getCapabilies = () => {}) {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
Object.defineProperty(this, 'strict', {
configurable: true,
get: function() {
let capabilies = getCapabilies();
return !!capabilies.raisesAccessibilityExceptions;
}
});
// An interface for in-process accessibility clients
// Note: we access it lazily to not enable accessibility when it is not needed
Object.defineProperty(this, 'retrieval', {
configurable: true,
get: function() {
delete this.retrieval;
this.retrieval = Cc[
'@mozilla.org/accessibleRetrieval;1'].getService(
Ci.nsIAccessibleRetrieval);
return this.retrieval;
}
});
};
Accessibility.prototype = {
/**
* @param {boolean} strict
* Flag indicating whether the accessibility issue should be logged
* or cause an error to be thrown. Default is to log to stdout.
* Number of attempts to get an accessible object for an element. We attempt
* more than once because accessible tree can be out of sync with the DOM tree
* for a short period of time.
* @type {Number}
*/
constructor(strict) {
this.strict = strict;
}
GET_ACCESSIBLE_ATTEMPTS: 100,
/**
* Get an accessible object for an element.
*
* @param {DOMElement|XULElement} element
* Element to get the accessible object for.
* @param {boolean=} mustHaveAccessible
* Flag indicating that the element must have an accessible object.
* Defaults to not require this.
*
* @return {nsIAccessible}
* Accessibility object for the given element.
* An interval between attempts to retrieve an accessible object for an
* element.
* @type {Number} ms
*/
getAccessible(element, mustHaveAccessible = false) {
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
/**
* Accessible object roles that support some action
* @type Object
*/
ACTIONABLE_ROLES: new Set([
'pushbutton',
'checkbutton',
'combobox',
'key',
'link',
'menuitem',
'check menu item',
'radio menu item',
'option',
'listbox option',
'listbox rich option',
'check rich option',
'combobox option',
'radiobutton',
'rowheader',
'switch',
'slider',
'spinbutton',
'pagetab',
'entry',
'outlineitem'
]),
/**
* Get an accessible object for a DOM element
* @param nsIDOMElement element
* @param Boolean mustHaveAccessible a flag indicating that the element must
* have an accessible object
* @return nsIAccessible object for the element
*/
getAccessibleObject(element, mustHaveAccessible = false) {
return new Promise((resolve, reject) => {
let acc = retrieval.getAccessibleFor(element);
let acc = this.retrieval.getAccessibleFor(element);
// if accessible object is found, return it;
// if it is not required, also resolve
if (acc || !mustHaveAccessible) {
// If accessible object is found, return it. If it is not required,
// also resolve.
resolve(acc);
// if we must have an accessible but are strict,
// reject now and avoid polling for an accessible object
} else if (mustHaveAccessible && !this.strict) {
// If we must have an accessible but are not raising accessibility
// exceptions, reject now and avoid polling for an accessible object.
reject();
// if we require an accessible object, we need to poll for it
// because accessible tree might be
// out of sync with DOM tree for a short time
} else {
let attempts = GET_ACCESSIBLE_ATTEMPTS;
// If we require an accessible object, we need to poll for it because
// accessible tree might be out of sync with DOM tree for a short time.
let attempts = this.GET_ACCESSIBLE_ATTEMPTS;
let intervalId = setInterval(() => {
let acc = retrieval.getAccessibleFor(element);
let acc = this.retrieval.getAccessibleFor(element);
if (acc || --attempts <= 0) {
clearInterval(intervalId);
if (acc) {
resolve(acc);
} else {
reject();
}
if (acc) { resolve(acc); }
else { reject(); }
}
}, GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
}
}).catch(() => this.error(
"Element does not have an accessible object", element));
};
'Element does not have an accessible object', element));
},
/**
* Test if the accessible has a role that supports some arbitrary
* action.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if an actionable role is found on the accessible, false
* otherwise.
* Check if the accessible has a role that supports some action
* @param nsIAccessible object
* @return Boolean an indicator of role being actionable
*/
isActionableRole(accessible) {
return accessibility.ActionableRoles.has(
retrieval.getStringRole(accessible.role));
}
return this.ACTIONABLE_ROLES.has(
this.retrieval.getStringRole(accessible.role));
},
/**
* Test if an accessible has at least one action that it supports.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accessible has at least one supported action,
* false otherwise.
* Determine if an accessible has at least one action that it supports
* @param nsIAccessible object
* @return Boolean an indicator of supporting at least one accessible action
*/
hasActionCount(accessible) {
return accessible.actionCount > 0;
}
},
/**
* Test if an accessible has a valid name.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accessible has a non-empty valid name, or false if
* this is not the case.
* Determine if an accessible has a valid name
* @param nsIAccessible object
* @return Boolean an indicator that the element has a non empty valid name
*/
hasValidName(accessible) {
return accessible.name && accessible.name.trim();
}
},
/**
* Test if an accessible has a {@code hidden} attribute.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if the accesible object has a {@code hidden} attribute,
* false otherwise.
* Check if an accessible has a set hidden attribute
* @param nsIAccessible object
* @return Boolean an indicator that the element has a hidden accessible
* attribute set to true
*/
hasHiddenAttribute(accessible) {
let hidden = false;
try {
hidden = accessible.attributes.getStringProperty("hidden");
hidden = accessible.attributes.getStringProperty('hidden');
} finally {
// if the property is missing, error will be thrown
return hidden && hidden === "true";
// If the property is missing, exception will be thrown.
return hidden && hidden === 'true';
}
}
},
/**
* Verify if an accessible has a given state.
* Test if an accessible has a given state.
*
* @param {nsIAccessible} accessible
* Accessible object to test.
* @param {number} stateToMatch
* State to match.
*
* @return {boolean}
* True if |accessible| has |stateToMatch|, false otherwise.
* Verify if an accessible has a given state
* @param nsIAccessible object
* @param Number stateToMatch the state to match
* @return Boolean accessible has a state
*/
matchState(accessible, stateToMatch) {
let state = {};
accessible.getState(state, {});
return !!(state.value & stateToMatch);
}
},
/**
* Test if an accessible is hidden from the user.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
* @return {boolean}
* True if element is hidden from user, false otherwise.
* Check if an accessible is hidden from the user of the accessibility API
* @param nsIAccessible object
* @return Boolean an indicator that the element is hidden from the user
*/
isHidden(accessible) {
while (accessible) {
@ -249,156 +214,12 @@ accessibility.Checks = class {
accessible = accessible.parent;
}
return false;
}
},
/**
* Test if the element's visible state corresponds to its accessibility
* API visibility.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
* @param {boolean} visible
* Visibility state of |element|.
*
* @throws ElementNotAccessibleError
* If |element|'s visibility state does not correspond to
* |accessible|'s.
*/
checkVisible(accessible, element, visible) {
if (!accessible) {
return;
}
let hiddenAccessibility = this.isHidden(accessible);
let message;
if (visible && hiddenAccessibility) {
message = "Element is not currently visible via the accessibility API " +
"and may not be manipulated by it";
} else if (!visible && !hiddenAccessibility) {
message = "Element is currently only visible via the accessibility API " +
"and can be manipulated by it";
}
this.error(message, element);
}
/**
* Test if the element's unavailable accessibility state matches the
* enabled state.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
* @param {boolean} enabled
* Enabled state of |element|.
*
* @throws ElementNotAccessibleError
* If |element|'s enabled state does not match |accessible|'s.
*/
checkEnabled(accessible, element, enabled) {
if (!accessible) {
return;
}
let win = element.ownerDocument.defaultView;
let disabledAccessibility = this.matchState(
accessible, accessibility.State.Unavailable);
let explorable = win.getComputedStyle(element)
.getPropertyValue("pointer-events") !== "none";
let message;
if (!explorable && !disabledAccessibility) {
message = "Element is enabled but is not explorable via the " +
"accessibility API";
} else if (enabled && disabledAccessibility) {
message = "Element is enabled but disabled via the accessibility API";
} else if (!enabled && !disabledAccessibility) {
message = "Element is disabled but enabled via the accessibility API";
}
this.error(message, element);
}
/**
* Test if it is possible to activate an element with the accessibility
* API.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement} element
* Element associated with |accessible|.
*
* @throws ElementNotAccessibleError
* If it is impossible to activate |element| with |accessible|.
*/
checkActionable(accessible, element) {
if (!accessible) {
return;
}
let message;
if (!this.hasActionCount(accessible)) {
message = "Element does not support any accessible actions";
} else if (!this.isActionableRole(accessible)) {
message = "Element does not have a correct accessibility role " +
"and may not be manipulated via the accessibility API";
} else if (!this.hasValidName(accessible)) {
message = "Element is missing an accessible name";
} else if (!this.matchState(accessible, accessibility.State.Focusable)) {
message = "Element is not focusable via the accessibility API";
}
this.error(message, element);
}
/**
* Test that an element's selected state corresponds to its
* accessibility API selected state.
*
* @param {nsIAccessible} accessible
* Accessible object.
* @param {DOMElement|XULElement}
* Element associated with |accessible|.
* @param {boolean} selected
* The |element|s selected state.
*
* @throws ElementNotAccessibleError
* If |element|'s selected state does not correspond to
* |accessible|'s.
*/
checkSelected(accessible, element, selected) {
if (!accessible) {
return;
}
// element is not selectable via the accessibility API
if (!this.matchState(accessible, accessibility.State.Selectable)) {
return;
}
let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected);
let message;
if (selected && !selectedAccessibility) {
message = "Element is selected but not selected via the accessibility API";
} else if (!selected && selectedAccessibility) {
message = "Element is not selected but selected via the accessibility API";
}
this.error(message, element);
}
/**
* Throw an error if strict accessibility checks are enforced and log
* the error to the log.
*
* @param {string} message
* @param {DOMElement|XULElement} element
* Element that caused an error.
*
* @throws ElementNotAccessibleError
* If |strict| is true.
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
error(message, element) {
if (!message) {
@ -412,6 +233,107 @@ accessibility.Checks = class {
throw new ElementNotAccessibleError(message);
}
logger.debug(message);
}
},
/**
* Check if the element's visible state corresponds to its accessibility API
* visibility
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean visible element's visibility state
*/
checkVisible(accessible, element, visible) {
if (!accessible) {
return;
}
let hiddenAccessibility = this.isHidden(accessible);
let message;
if (visible && hiddenAccessibility) {
message = 'Element is not currently visible via the accessibility API ' +
'and may not be manipulated by it';
} else if (!visible && !hiddenAccessibility) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it';
}
this.error(message, element);
},
/**
* Check if the element's unavailable accessibility state matches the enabled
* state
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean enabled element's enabled state
* @param Object container frame and optional ShadowDOM
*/
checkEnabled(accessible, element, enabled, container) {
if (!accessible) {
return;
}
let disabledAccessibility = this.matchState(accessible, states.unavailable);
let explorable = container.frame.document.defaultView.getComputedStyle(
element).getPropertyValue('pointer-events') !== 'none';
let message;
if (!explorable && !disabledAccessibility) {
message = 'Element is enabled but is not explorable via the ' +
'accessibility API';
} else if (enabled && disabledAccessibility) {
message = 'Element is enabled but disabled via the accessibility API';
} else if (!enabled && !disabledAccessibility) {
message = 'Element is disabled but enabled via the accessibility API';
}
this.error(message, element);
},
/**
* Check if it is possible to activate an element with the accessibility API
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
*/
checkActionable(accessible, element) {
if (!accessible) {
return;
}
let message;
if (!this.hasActionCount(accessible)) {
message = 'Element does not support any accessible actions';
} else if (!this.isActionableRole(accessible)) {
message = 'Element does not have a correct accessibility role ' +
'and may not be manipulated via the accessibility API';
} else if (!this.hasValidName(accessible)) {
message = 'Element is missing an accessible name';
} else if (!this.matchState(accessible, states.focusable)) {
message = 'Element is not focusable via the accessibility API';
}
this.error(message, element);
},
/**
* Check if element's selected state corresponds to its accessibility API
* selected state.
* @param nsIAccessible object
* @param WebElement corresponding to nsIAccessible object
* @param Boolean selected element's selected state
*/
checkSelected(accessible, element, selected) {
if (!accessible) {
return;
}
if (!this.matchState(accessible, states.selectable)) {
// Element is not selectable via the accessibility API
return;
}
let selectedAccessibility = this.matchState(accessible, states.selected);
let message;
if (selected && !selectedAccessibility) {
message =
'Element is selected but not selected via the accessibility API';
} else if (!selected && selectedAccessibility) {
message =
'Element is not selected but selected via the accessibility API';
}
this.error(message, element);
}
};

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

@ -158,6 +158,8 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
"version": Services.appinfo.version,
};
this.interactions = new Interactions(() => this.sessionCapabilities);
this.mm = globalMessageManager;
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
@ -1964,9 +1966,8 @@ GeckoDriver.prototype.clickElement = function*(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
yield interaction.clickElement(
el, this.sessionCapabilities.raisesAccessibilityExceptions);
yield this.interactions.clickElement({ frame: win },
this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2064,10 +2065,8 @@ GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(
id, {frame: win});
resp.body.value = yield interaction.isElementDisplayed(
el, this.sessionCapabilities.raisesAccessibilityExceptions);
resp.body.value = yield this.interactions.isElementDisplayed(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2114,10 +2113,8 @@ GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(
id, {frame: win});
resp.body.value = yield interaction.isElementEnabled(
el, this.sessionCapabilities.raisesAccessibilityExceptions);
resp.body.value = yield this.interactions.isElementEnabled(
{frame: win}, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2139,10 +2136,8 @@ GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
case Context.CHROME:
// Selenium atom doesn't quite work here
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(
id, {frame: win});
resp.body.value = yield interaction.isElementSelected(
el, this.sessionCapabilities.raisesAccessibilityExceptions);
resp.body.value = yield this.interactions.isElementSelected(
{ frame: win }, this.curBrowser.elementManager, id);
break;
case Context.CONTENT:
@ -2191,10 +2186,8 @@ GeckoDriver.prototype.sendKeysToElement = function*(cmd, resp) {
switch (this.context) {
case Context.CHROME:
let win = this.getCurrentWindow();
let el = this.curBrowser.elementManager.getKnownElement(
id, {frame: win});
yield interaction.sendKeysToElement(
el, value, true, this.sessionCapabilities.raisesAccessibilityExceptions);
yield this.interactions.sendKeysToElement(
{ frame: win }, this.curBrowser.elementManager, id, value, true);
break;
case Context.CONTENT:

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

@ -12,222 +12,230 @@ Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/element.js");
Cu.import("chrome://marionette/content/event.js");
this.EXPORTED_SYMBOLS = ["interaction"];
this.EXPORTED_SYMBOLS = ["Interactions"];
/**
* XUL elements that support disabled attribute.
* XUL elements that support disabled attribtue.
*/
const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
"ARROWSCROLLBOX",
"BUTTON",
"CHECKBOX",
"COLORPICKER",
"COMMAND",
"DATEPICKER",
"DESCRIPTION",
"KEY",
"KEYSET",
"LABEL",
"LISTBOX",
"LISTCELL",
"LISTHEAD",
"LISTHEADER",
"LISTITEM",
"MENU",
"MENUITEM",
"MENULIST",
"MENUSEPARATOR",
"PREFERENCE",
"RADIO",
"RADIOGROUP",
"RICHLISTBOX",
"RICHLISTITEM",
"SCALE",
"TAB",
"TABS",
"TEXTBOX",
"TIMEPICKER",
"TOOLBARBUTTON",
"TREE",
'ARROWSCROLLBOX',
'BUTTON',
'CHECKBOX',
'COLORPICKER',
'COMMAND',
'DATEPICKER',
'DESCRIPTION',
'KEY',
'KEYSET',
'LABEL',
'LISTBOX',
'LISTCELL',
'LISTHEAD',
'LISTHEADER',
'LISTITEM',
'MENU',
'MENUITEM',
'MENULIST',
'MENUSEPARATOR',
'PREFERENCE',
'RADIO',
'RADIOGROUP',
'RICHLISTBOX',
'RICHLISTITEM',
'SCALE',
'TAB',
'TABS',
'TEXTBOX',
'TIMEPICKER',
'TOOLBARBUTTON',
'TREE'
]);
/**
* XUL elements that support checked property.
*/
const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([
"BUTTON",
"CHECKBOX",
"LISTITEM",
"TOOLBARBUTTON",
'BUTTON',
'CHECKBOX',
'LISTITEM',
'TOOLBARBUTTON'
]);
/**
* XUL elements that support selected property.
*/
const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
"LISTITEM",
"MENU",
"MENUITEM",
"MENUSEPARATOR",
"RADIO",
"RICHLISTITEM",
"TAB",
'LISTITEM',
'MENU',
'MENUITEM',
'MENUSEPARATOR',
'RADIO',
'RICHLISTITEM',
'TAB'
]);
this.interaction = {};
/**
* Interact with an element by clicking it.
*
* @param {DOMElement|XULElement} el
* Element to click.
* @param {boolean=} strict
* Enforce strict accessibility tests.
* A collection of interactions available in marionette.
* @type {Object}
*/
interaction.clickElement = function(el, strict = false) {
let win = getWindow(el);
let visible = element.isVisible(el, win);
if (!visible) {
throw new ElementNotVisibleError("Element is not visible");
}
this.Interactions = function(getCapabilies) {
this.accessibility = new Accessibility(getCapabilies);
};
let a11y = accessibility.get(strict);
return a11y.getAccessible(el, true).then(acc => {
a11y.checkVisible(acc, el, visible);
if (atom.isElementEnabled(el)) {
a11y.checkEnabled(acc, el, true);
a11y.checkActionable(acc, el);
if (element.isXULElement(el)) {
el.click();
Interactions.prototype = {
/**
* Send click event to element.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param String id
* The DOM reference ID
*/
clickElement(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let visible = element.isVisible(el);
if (!visible) {
throw new ElementNotVisibleError('Element is not visible');
}
return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkVisible(acc, el, visible);
if (atom.isElementEnabled(el)) {
this.accessibility.checkEnabled(acc, el, true, container);
this.accessibility.checkActionable(acc, el);
if (element.isXULElement(el)) {
el.click();
} else {
let rects = el.getClientRects();
let win = el.ownerDocument.defaultView;
event.synthesizeMouseAtPoint(
rects[0].left + rects[0].width / 2,
rects[0].top + rects[0].height / 2,
{} /* opts */,
win);
}
} else {
let rects = el.getClientRects();
event.synthesizeMouseAtPoint(
rects[0].left + rects[0].width / 2,
rects[0].top + rects[0].height / 2,
{} /* opts */,
win);
throw new InvalidElementStateError('Element is not enabled');
}
});
},
/**
* Send keys to element
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param String id
* The DOM reference ID
*
* @param String?Array value
* Value to send to an element
*
* @param Boolean ignoreVisibility
* A flag to check element visibility
*/
sendKeysToElement(container, elementManager, id, value, ignoreVisibility) {
let el = elementManager.getKnownElement(id, container);
return this.accessibility.getAccessibleObject(el, true).then(acc => {
this.accessibility.checkActionable(acc, el);
event.sendKeysToElement(
value, el, {ignoreVisibility: false}, container.frame);
});
},
/**
* Determine the element displayedness of the given web element.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*
* Also performs additional accessibility checks if enabled by session
* capability.
*/
isElementDisplayed(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let displayed = atom.isElementDisplayed(el, container.frame);
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkVisible(acc, el, displayed);
return displayed;
});
},
/**
* Check if element is enabled.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*
* @return {boolean}
* True if enabled, false otherwise.
*/
isElementEnabled(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let enabled = true;
if (element.isXULElement(el)) {
// Check if XUL element supports disabled attribute
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
let disabled = atom.getElementAttribute(el, 'disabled', container.frame);
if (disabled && disabled === 'true') {
enabled = false;
}
}
} else {
throw new InvalidElementStateError("Element is not enabled");
enabled = atom.isElementEnabled(el, container.frame);
}
});
};
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkEnabled(acc, el, enabled, container);
return enabled;
});
},
/**
* Send keys to element.
*
* @param {DOMElement|XULElement} el
* Element to send key events to.
* @param {Array.<string>} value
* Sequence of keystrokes to send to the element.
* @param {boolean} ignoreVisibility
* Flag to enable or disable element visibility tests.
* @param {boolean=} strict
* Enforce strict accessibility tests.
*/
interaction.sendKeysToElement = function(el, value, ignoreVisibility, strict = false) {
let win = getWindow(el);
let a11y = accessibility.get(strict);
return a11y.getAccessible(el, true).then(acc => {
a11y.checkActionable(acc, el);
event.sendKeysToElement(value, el, {ignoreVisibility: false}, win);
});
};
/**
* Determine the element displayedness of an element.
*
* @param {DOMElement|XULElement} el
* Element to determine displayedness of.
* @param {boolean=} strict
* Enforce strict accessibility tests.
*
* @return {boolean}
* True if element is displayed, false otherwise.
*/
interaction.isElementDisplayed = function(el, strict = false) {
let win = getWindow(el);
let displayed = atom.isElementDisplayed(el, win);
let a11y = accessibility.get(strict);
return a11y.getAccessible(el).then(acc => {
a11y.checkVisible(acc, el, displayed);
return displayed;
});
};
/**
* Check if element is enabled.
*
* @param {DOMElement|XULElement} el
* Element to test if is enabled.
*
* @return {boolean}
* True if enabled, false otherwise.
*/
interaction.isElementEnabled = function(el, strict = false) {
let enabled = true;
let win = getWindow(el);
if (element.isXULElement(el)) {
// check if XUL element supports disabled attribute
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
let disabled = atom.getElementAttribute(el, "disabled", win);
if (disabled && disabled === "true") {
enabled = false;
/**
* Determines if the referenced element is selected or not.
*
* This operation only makes sense on input elements of the Checkbox-
* and Radio Button states, or option elements.
*
* @param nsIDOMWindow, ShadowRoot container
* The window and an optional shadow root that contains the element
*
* @param ElementManager elementManager
*
* @param {WebElement} id
* Reference to web element.
*/
isElementSelected(container, elementManager, id) {
let el = elementManager.getKnownElement(id, container);
let selected = true;
if (element.isXULElement(el)) {
let tagName = el.tagName.toUpperCase();
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.checked;
}
if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.selected;
}
} else {
selected = atom.isElementSelected(el, container.frame);
}
} else {
enabled = atom.isElementEnabled(el, {frame: win});
}
let a11y = accessibility.get(strict);
return a11y.getAccessible(el).then(acc => {
a11y.checkEnabled(acc, el, enabled);
return enabled;
});
return this.accessibility.getAccessibleObject(el).then(acc => {
this.accessibility.checkSelected(acc, el, selected);
return selected;
});
},
};
/**
* Determines if the referenced element is selected or not.
*
* This operation only makes sense on input elements of the Checkbox-
* and Radio Button states, or option elements.
*
* @param {DOMElement|XULElement} el
* Element to test if is selected.
* @param {boolean=} strict
* Enforce strict accessibility tests.
*
* @return {boolean}
* True if element is selected, false otherwise.
*/
interaction.isElementSelected = function(el, strict = false) {
let selected = true;
let win = getWindow(el);
if (element.isXULElement(el)) {
let tagName = el.tagName.toUpperCase();
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.checked;
}
if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.selected;
}
} else {
selected = atom.isElementSelected(el, win);
}
let a11y = accessibility.get(strict);
return a11y.getAccessible(el).then(acc => {
a11y.checkSelected(acc, el, selected);
return selected;
});
};
function getWindow(el) {
return el.ownerDocument.defaultView;
}

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

@ -13,7 +13,6 @@ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
loader.loadSubScript("chrome://marionette/content/simpletest.js");
loader.loadSubScript("chrome://marionette/content/common.js");
Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/action.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/capture.js");
@ -42,7 +41,9 @@ var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
var previousContainer = null;
var elementManager = new ElementManager();
// Holds session capabilities.
var capabilities = {};
var interactions = new Interactions(() => capabilities);
var actions = new action.Chain(checkForInterrupted);
@ -857,11 +858,9 @@ function singleTap(id, corx, cory) {
if (!visible) {
throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
}
let a11y = accessibility.get(capabilities.raisesAccessibilityExceptions);
return a11y.getAccessible(el, true).then(acc => {
a11y.checkVisible(acc, el, visible);
a11y.checkActionable(acc, el);
return interactions.accessibility.getAccessibleObject(el, true).then(acc => {
interactions.accessibility.checkVisible(acc, el, visible);
interactions.accessibility.checkActionable(acc, el);
if (!curContainer.frame.document.createTouch) {
actions.mouseEventsOnly = true;
}
@ -1270,9 +1269,7 @@ function getActiveElement() {
* Reference to the web element to click.
*/
function clickElement(id) {
let el = elementManager.getKnownElement(id, curContainer);
return interaction.clickElement(
el, capabilities.raisesAccessibilityExceptions);
return interactions.clickElement(curContainer, elementManager, id);
}
/**
@ -1326,9 +1323,7 @@ function getElementTagName(id) {
* capability.
*/
function isElementDisplayed(id) {
let el = elementManager.getKnownElement(id, curContainer);
return interaction.isElementDisplayed(
el, capabilities.raisesAccessibilityExceptions);
return interactions.isElementDisplayed(curContainer, elementManager, id);
}
/**
@ -1379,9 +1374,7 @@ function getElementRect(id) {
* True if enabled, false otherwise.
*/
function isElementEnabled(id) {
let el = elementManager.getKnownElement(id, curContainer);
return interaction.isElementEnabled(
el, capabilities.raisesAccessibilityExceptions);
return interactions.isElementEnabled(curContainer, elementManager, id);
}
/**
@ -1391,9 +1384,7 @@ function isElementEnabled(id) {
* and Radio Button states, or option elements.
*/
function isElementSelected(id) {
let el = elementManager.getKnownElement(id, curContainer);
return interaction.isElementSelected(
el, capabilities.raisesAccessibilityExceptions);
return interactions.isElementSelected(curContainer, elementManager, id);
}
/**
@ -1414,10 +1405,9 @@ function sendKeysToElement(msg) {
sendSyncMessage("Marionette:getFiles",
{value: p, command_id: command_id});
} else {
let promise = interaction.sendKeysToElement(
el, val, false, capabilities.raisesAccessibilityExceptions)
.then(() => sendOk(command_id))
.catch(e => sendError(e, command_id));
interactions.sendKeysToElement(curContainer, elementManager, id, val)
.then(() => sendOk(command_id))
.catch(e => sendError(e, command_id));
}
}