зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1238744 - centralizing interactions into its own file. r=ato
This commit is contained in:
Родитель
cb06edf308
Коммит
5132ceb11b
|
@ -0,0 +1,335 @@
|
|||
/* 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/. */
|
||||
|
||||
/* global Accessibility, Components, Log, ElementNotAccessibleError,
|
||||
XPCOMUtils */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Log.jsm');
|
||||
|
||||
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');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Accessibility'];
|
||||
|
||||
/**
|
||||
* Accessible states used to check element's state from the accessiblity API
|
||||
* perspective.
|
||||
*/
|
||||
const states = {
|
||||
unavailable: Ci.nsIAccessibleStates.STATE_UNAVAILABLE,
|
||||
focusable: Ci.nsIAccessibleStates.STATE_FOCUSABLE,
|
||||
selectable: Ci.nsIAccessibleStates.STATE_SELECTABLE,
|
||||
selected: Ci.nsIAccessibleStates.STATE_SELECTED
|
||||
};
|
||||
|
||||
var logger = Log.repository.getLogger('Marionette');
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {Function} getCapabilies
|
||||
* Session capabilities getter.
|
||||
*/
|
||||
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 = {
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
GET_ACCESSIBLE_ATTEMPTS: 100,
|
||||
|
||||
/**
|
||||
* An interval between attempts to retrieve an accessible object for an
|
||||
* element.
|
||||
* @type {Number} ms
|
||||
*/
|
||||
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 = this.retrieval.getAccessibleFor(element);
|
||||
|
||||
if (acc || !mustHaveAccessible) {
|
||||
// If accessible object is found, return it. If it is not required,
|
||||
// also resolve.
|
||||
resolve(acc);
|
||||
} else {
|
||||
// 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 = this.retrieval.getAccessibleFor(element);
|
||||
if (acc || --attempts <= 0) {
|
||||
clearInterval(intervalId);
|
||||
if (acc) { resolve(acc); }
|
||||
else { reject(); }
|
||||
}
|
||||
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
|
||||
}
|
||||
}).catch(() => this.error(
|
||||
'Element does not have an accessible object', element));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 this.ACTIONABLE_ROLES.has(
|
||||
this.retrieval.getStringRole(accessible.role));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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');
|
||||
} finally {
|
||||
// If the property is missing, exception will be thrown.
|
||||
return hidden && hidden === 'true';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (this.hasHiddenAttribute(accessible)) {
|
||||
return true;
|
||||
}
|
||||
accessible = accessible.parent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return;
|
||||
}
|
||||
if (element) {
|
||||
let {id, tagName, className} = element;
|
||||
message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
|
||||
}
|
||||
if (this.strict) {
|
||||
throw new ElementNotAccessibleError(message);
|
||||
}
|
||||
logger.error(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);
|
||||
}
|
||||
};
|
|
@ -23,6 +23,7 @@ XPCOMUtils.defineLazyServiceGetter(
|
|||
this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
|
||||
|
||||
Cu.import("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/interactions.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/modal.js");
|
||||
|
@ -164,6 +165,8 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
|
|||
"version": Services.appinfo.version,
|
||||
};
|
||||
|
||||
this.interactions = new Interactions(utils, () => this.sessionCapabilities);
|
||||
|
||||
this.mm = globalMessageManager;
|
||||
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
|
||||
|
||||
|
@ -1981,10 +1984,9 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) {
|
|||
|
||||
switch (this.context) {
|
||||
case Context.CHROME:
|
||||
// click atom fails, fall back to click() action
|
||||
let win = this.getCurrentWindow();
|
||||
let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
|
||||
el.click();
|
||||
yield this.interactions.clickElement({ frame: win },
|
||||
this.curBrowser.elementManager, id)
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
|
@ -2082,8 +2084,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 = utils.isElementDisplayed(el);
|
||||
resp.body.value = yield this.interactions.isElementDisplayed(
|
||||
{frame: win}, this.curBrowser.elementManager, id);
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
|
@ -2130,8 +2132,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 = !(!!el.disabled);
|
||||
resp.body.value = yield this.interactions.isElementEnabled(
|
||||
{frame: win}, this.curBrowser.elementManager, id);
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
|
@ -2153,14 +2155,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 });
|
||||
if (typeof el.checked != "undefined") {
|
||||
resp.body.value = !!el.checked;
|
||||
} else if (typeof el.selected != "undefined") {
|
||||
resp.body.value = !!el.selected;
|
||||
} else {
|
||||
resp.body.value = true;
|
||||
}
|
||||
resp.body.value = yield this.interactions.isElementSelected(
|
||||
{ frame: win }, this.curBrowser.elementManager, id);
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
|
@ -2209,15 +2205,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 });
|
||||
utils.sendKeysToElement(
|
||||
win,
|
||||
el,
|
||||
value,
|
||||
() => {},
|
||||
e => { throw e; },
|
||||
cmd.id,
|
||||
true /* ignore visibility check */);
|
||||
yield this.interactions.sendKeysToElement(
|
||||
{ frame: win }, this.curBrowser.elementManager, id, value, true);
|
||||
break;
|
||||
|
||||
case Context.CONTENT:
|
||||
|
@ -2806,9 +2795,6 @@ GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
|
|||
win,
|
||||
loginTextbox,
|
||||
cmd.parameters.value,
|
||||
() => {},
|
||||
e => { throw e; },
|
||||
this.command_id,
|
||||
true /* ignore visibility check */);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@
|
|||
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
|
||||
'resource://gre/modules/Timer.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
|
||||
'resource://gre/modules/Timer.jsm');
|
||||
|
||||
/**
|
||||
* The ElementManager manages DOM element references and
|
||||
|
@ -30,7 +24,6 @@ XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
|
|||
*/
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Accessibility",
|
||||
"elements",
|
||||
"ElementManager",
|
||||
"CLASS_NAME",
|
||||
|
@ -47,8 +40,8 @@ this.EXPORTED_SYMBOLS = [
|
|||
|
||||
const DOCUMENT_POSITION_DISCONNECTED = 1;
|
||||
|
||||
const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Components.interfaces.nsIUUIDGenerator);
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
this.CLASS_NAME = "class name";
|
||||
this.SELECTOR = "css selector";
|
||||
|
@ -61,192 +54,6 @@ this.XPATH = "xpath";
|
|||
this.ANON= "anon";
|
||||
this.ANON_ATTRIBUTE = "anon attribute";
|
||||
|
||||
this.Accessibility = function Accessibility() {
|
||||
// A flag indicating whether the accessibility issue should be logged or cause
|
||||
// an exception. Default: log to stdout.
|
||||
this.strict = false;
|
||||
// An interface for in-process accessibility clients
|
||||
// Note: we access it lazily to not enable accessibility when it is not needed
|
||||
Object.defineProperty(this, 'accessibleRetrieval', {
|
||||
configurable: true,
|
||||
get: function() {
|
||||
delete this.accessibleRetrieval;
|
||||
this.accessibleRetrieval = Components.classes[
|
||||
'@mozilla.org/accessibleRetrieval;1'].getService(
|
||||
Components.interfaces.nsIAccessibleRetrieval);
|
||||
return this.accessibleRetrieval;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Accessibility.prototype = {
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
GET_ACCESSIBLE_ATTEMPTS: 100,
|
||||
|
||||
/**
|
||||
* An interval between attempts to retrieve an accessible object for an
|
||||
* element.
|
||||
* @type {Number} ms
|
||||
*/
|
||||
GET_ACCESSIBLE_ATTEMPT_INTERVAL: 10,
|
||||
|
||||
/**
|
||||
* Accessible object roles that support some action
|
||||
* @type Object
|
||||
*/
|
||||
actionableRoles: 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 = this.accessibleRetrieval.getAccessibleFor(element);
|
||||
|
||||
if (acc || !mustHaveAccessible) {
|
||||
// If accessible object is found, return it. If it is not required,
|
||||
// also resolve.
|
||||
resolve(acc);
|
||||
} else {
|
||||
// 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 = this.accessibleRetrieval.getAccessibleFor(element);
|
||||
if (acc || --attempts <= 0) {
|
||||
clearInterval(intervalId);
|
||||
if (acc) { resolve(acc); }
|
||||
else { reject(); }
|
||||
}
|
||||
}, this.GET_ACCESSIBLE_ATTEMPT_INTERVAL);
|
||||
}
|
||||
}).catch(() => this.handleErrorMessage(
|
||||
'Element does not have an accessible object', element));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 this.actionableRoles.has(
|
||||
this.accessibleRetrieval.getStringRole(accessible.role));
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
try {
|
||||
hidden = accessible.attributes.getStringProperty('hidden');
|
||||
} finally {
|
||||
// If the property is missing, exception will be thrown.
|
||||
return hidden && hidden === 'true';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify if an accessible has a given state
|
||||
* @param nsIAccessible object
|
||||
* @param String stateName name of the state to match
|
||||
* @return Boolean accessible has a state
|
||||
*/
|
||||
matchState(accessible, stateName) {
|
||||
let stateToMatch = Components.interfaces.nsIAccessibleStates[stateName];
|
||||
let state = {};
|
||||
accessible.getState(state, {});
|
||||
return !!(state.value & stateToMatch);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (this.hasHiddenAttribute(accessible)) {
|
||||
return true;
|
||||
}
|
||||
accessible = accessible.parent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an error message or log the error message in the log
|
||||
* @param String message
|
||||
* @param DOMElement element that caused an error
|
||||
*/
|
||||
handleErrorMessage(message, element) {
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
if (element) {
|
||||
message += ` -> id: ${element.id}, tagName: ${element.tagName}, className: ${element.className}\n`;
|
||||
}
|
||||
if (this.strict) {
|
||||
throw new ElementNotAccessibleError(message);
|
||||
}
|
||||
dump(Date.now() + " Marionette: " + message);
|
||||
}
|
||||
};
|
||||
|
||||
this.ElementManager = function ElementManager(notSupported) {
|
||||
this.seenItems = {};
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
@ -291,7 +98,7 @@ ElementManager.prototype = {
|
|||
}
|
||||
}
|
||||
let id = elements.generateUUID();
|
||||
this.seenItems[id] = Components.utils.getWeakReference(element);
|
||||
this.seenItems[id] = Cu.getWeakReference(element);
|
||||
return id;
|
||||
},
|
||||
|
||||
|
@ -562,7 +369,7 @@ ElementManager.prototype = {
|
|||
on_success, on_error,
|
||||
command_id),
|
||||
100,
|
||||
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
} else {
|
||||
if (isArrayLike) {
|
||||
|
@ -598,7 +405,7 @@ ElementManager.prototype = {
|
|||
*/
|
||||
findByXPath: function EM_findByXPath(root, value, node) {
|
||||
return root.evaluate(value, node, null,
|
||||
Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -616,7 +423,7 @@ ElementManager.prototype = {
|
|||
*/
|
||||
findByXPathAll: function EM_findByXPathAll(root, value, node) {
|
||||
let values = root.evaluate(value, node, null,
|
||||
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
||||
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
||||
let elements = [];
|
||||
let element = values.iterateNext();
|
||||
while (element) {
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
/* 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/. */
|
||||
|
||||
/* global Components, Accessibility, ElementNotVisibleError,
|
||||
InvalidElementStateError, Interactions */
|
||||
|
||||
var {utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['Interactions'];
|
||||
|
||||
Cu.import('chrome://marionette/content/accessibility.js');
|
||||
Cu.import('chrome://marionette/content/error.js');
|
||||
|
||||
/**
|
||||
* 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'
|
||||
]);
|
||||
|
||||
/**
|
||||
* XUL elements that support checked property.
|
||||
*/
|
||||
const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([
|
||||
'BUTTON',
|
||||
'CHECKBOX',
|
||||
'LISTITEM',
|
||||
'TOOLBARBUTTON'
|
||||
]);
|
||||
|
||||
/**
|
||||
* XUL elements that support selected property.
|
||||
*/
|
||||
const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([
|
||||
'LISTITEM',
|
||||
'MENU',
|
||||
'MENUITEM',
|
||||
'MENUSEPARATOR',
|
||||
'RADIO',
|
||||
'RICHLISTITEM',
|
||||
'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.
|
||||
* @type {Object}
|
||||
*/
|
||||
this.Interactions = function(utils, getCapabilies) {
|
||||
this.utils = utils;
|
||||
this.accessibility = new Accessibility(getCapabilies);
|
||||
};
|
||||
|
||||
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 = this.checkVisible(container, 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 (this.utils.isElementEnabled(el)) {
|
||||
this.accessibility.checkEnabled(acc, el, true, container);
|
||||
this.accessibility.checkActionable(acc, el);
|
||||
if (this.isXULElement(el)) {
|
||||
el.click();
|
||||
} else {
|
||||
let rects = el.getClientRects();
|
||||
this.utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
|
||||
rects[0].top + rects[0].height/2,
|
||||
{}, el.ownerDocument.defaultView);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
this.utils.sendKeysToElement(
|
||||
container.frame, el, value, ignoreVisibility);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 = this.utils.isElementDisplayed(el);
|
||||
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 (this.isXULElement(el)) {
|
||||
// Check if XUL element supports disabled attribute
|
||||
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
|
||||
let disabled = this.utils.getElementAttribute(el, 'disabled');
|
||||
if (disabled && disabled === 'true') {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enabled = this.utils.isElementEnabled(el);
|
||||
}
|
||||
return this.accessibility.getAccessibleObject(el).then(acc => {
|
||||
this.accessibility.checkEnabled(acc, el, enabled, container);
|
||||
return enabled;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 (this.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 = this.utils.isElementSelected(el);
|
||||
}
|
||||
return this.accessibility.getAccessibleObject(el).then(acc => {
|
||||
this.accessibility.checkSelected(acc, el, 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);
|
||||
}
|
||||
};
|
|
@ -7,6 +7,8 @@ marionette.jar:
|
|||
content/server.js (server.js)
|
||||
content/driver.js (driver.js)
|
||||
content/actions.js (actions.js)
|
||||
content/interactions.js (interactions.js)
|
||||
content/accessibility.js (accessibility.js)
|
||||
content/listener.js (listener.js)
|
||||
content/elements.js (elements.js)
|
||||
content/sendkeys.js (sendkeys.js)
|
||||
|
|
|
@ -18,6 +18,7 @@ Cu.import("chrome://marionette/content/cookies.js");
|
|||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/proxy.js");
|
||||
Cu.import("chrome://marionette/content/interactions.js");
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
@ -43,7 +44,11 @@ var curContainer = { frame: content, shadowRoot: null };
|
|||
var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
|
||||
var previousContainer = null;
|
||||
var elementManager = new ElementManager([]);
|
||||
var accessibility = new Accessibility();
|
||||
|
||||
// Holds session capabilities.
|
||||
var capabilities = {};
|
||||
var interactions = new Interactions(utils, () => capabilities);
|
||||
|
||||
var actions = new ActionChain(utils, checkForInterrupted);
|
||||
var importedScripts = null;
|
||||
|
||||
|
@ -108,9 +113,8 @@ function registerSelf() {
|
|||
|
||||
if (register[0]) {
|
||||
let {id, remotenessChange} = register[0][0];
|
||||
let {B2G, raisesAccessibilityExceptions} = register[0][2];
|
||||
isB2G = B2G;
|
||||
accessibility.strict = raisesAccessibilityExceptions;
|
||||
capabilities = register[0][2];
|
||||
isB2G = capabilities.B2G;
|
||||
listenerId = id;
|
||||
if (typeof id != "undefined") {
|
||||
// check if we're the main process
|
||||
|
@ -299,8 +303,8 @@ function waitForReady() {
|
|||
* current environment, and resets all values
|
||||
*/
|
||||
function newSession(msg) {
|
||||
isB2G = msg.json.B2G;
|
||||
accessibility.strict = msg.json.raisesAccessibilityExceptions;
|
||||
capabilities = msg.json;
|
||||
isB2G = capabilities.B2G;
|
||||
resetValues();
|
||||
if (isB2G) {
|
||||
readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
|
@ -938,9 +942,9 @@ function singleTap(id, corx, cory) {
|
|||
if (!visible) {
|
||||
throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
|
||||
}
|
||||
return accessibility.getAccessibleObject(el, true).then(acc => {
|
||||
checkVisibleAccessibility(acc, el, visible);
|
||||
checkActionableAccessibility(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;
|
||||
}
|
||||
|
@ -955,108 +959,6 @@ function singleTap(id, corx, cory) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function checkEnabledAccessibility(accesible, element, enabled) {
|
||||
if (!accesible) {
|
||||
return;
|
||||
}
|
||||
let disabledAccessibility = accessibility.matchState(
|
||||
accesible, 'STATE_UNAVAILABLE');
|
||||
let explorable = curContainer.frame.document.defaultView.getComputedStyle(
|
||||
element, null).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';
|
||||
}
|
||||
accessibility.handleErrorMessage(message, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function checkVisibleAccessibility(accesible, element, visible) {
|
||||
if (!accesible) {
|
||||
return;
|
||||
}
|
||||
let hiddenAccessibility = accessibility.isHidden(accesible);
|
||||
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';
|
||||
}
|
||||
accessibility.handleErrorMessage(message, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is possible to activate an element with the accessibility API
|
||||
* @param nsIAccessible object
|
||||
* @param WebElement corresponding to nsIAccessible object
|
||||
*/
|
||||
function checkActionableAccessibility(accesible, element) {
|
||||
if (!accesible) {
|
||||
return;
|
||||
}
|
||||
let message;
|
||||
if (!accessibility.hasActionCount(accesible)) {
|
||||
message = 'Element does not support any accessible actions';
|
||||
} else if (!accessibility.isActionableRole(accesible)) {
|
||||
message = 'Element does not have a correct accessibility role ' +
|
||||
'and may not be manipulated via the accessibility API';
|
||||
} else if (!accessibility.hasValidName(accesible)) {
|
||||
message = 'Element is missing an accesible name';
|
||||
} else if (!accessibility.matchState(accesible, 'STATE_FOCUSABLE')) {
|
||||
message = 'Element is not focusable via the accessibility API';
|
||||
}
|
||||
accessibility.handleErrorMessage(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
|
||||
*/
|
||||
function checkSelectedAccessibility(accessible, element, selected) {
|
||||
if (!accessible) {
|
||||
return;
|
||||
}
|
||||
if (!accessibility.matchState(accessible, 'STATE_SELECTABLE')) {
|
||||
// Element is not selectable via the accessibility API
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedAccessibility = accessibility.matchState(
|
||||
accessible, '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';
|
||||
}
|
||||
accessibility.handleErrorMessage(message, element);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function to create a touch based on the element
|
||||
* corx and cory are relative to the viewport, id is the touchId
|
||||
|
@ -1460,24 +1362,7 @@ function getActiveElement() {
|
|||
* Reference to the web element to click.
|
||||
*/
|
||||
function clickElement(id) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
let visible = checkVisible(el);
|
||||
if (!visible) {
|
||||
throw new ElementNotVisibleError("Element is not visible");
|
||||
}
|
||||
return accessibility.getAccessibleObject(el, true).then(acc => {
|
||||
checkVisibleAccessibility(acc, el, visible);
|
||||
if (utils.isElementEnabled(el)) {
|
||||
checkEnabledAccessibility(acc, el, true);
|
||||
checkActionableAccessibility(acc, el);
|
||||
let rects = el.getClientRects();
|
||||
utils.synthesizeMouseAtPoint(rects[0].left + rects[0].width/2,
|
||||
rects[0].top + rects[0].height/2,
|
||||
{}, el.ownerDocument.defaultView);
|
||||
} else {
|
||||
throw new InvalidElementStateError("Element is not Enabled");
|
||||
}
|
||||
});
|
||||
return interactions.clickElement(curContainer, elementManager, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1531,12 +1416,7 @@ function getElementTagName(id) {
|
|||
* capability.
|
||||
*/
|
||||
function isElementDisplayed(id) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
let displayed = utils.isElementDisplayed(el);
|
||||
return accessibility.getAccessibleObject(el).then(acc => {
|
||||
checkVisibleAccessibility(acc, el, displayed);
|
||||
return displayed;
|
||||
});
|
||||
return interactions.isElementDisplayed(curContainer, elementManager, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1587,12 +1467,7 @@ function getElementRect(id) {
|
|||
* True if enabled, false otherwise.
|
||||
*/
|
||||
function isElementEnabled(id) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
let enabled = utils.isElementEnabled(el);
|
||||
return accessibility.getAccessibleObject(el).then(acc => {
|
||||
checkEnabledAccessibility(acc, el, enabled);
|
||||
return enabled;
|
||||
});
|
||||
return interactions.isElementEnabled(curContainer, elementManager, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1602,12 +1477,7 @@ function isElementEnabled(id) {
|
|||
* and Radio Button states, or option elements.
|
||||
*/
|
||||
function isElementSelected(id) {
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
let selected = utils.isElementSelected(el);
|
||||
return accessibility.getAccessibleObject(el).then(acc => {
|
||||
checkSelectedAccessibility(acc, el, selected);
|
||||
return selected;
|
||||
});
|
||||
return interactions.isElementSelected(curContainer, elementManager, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1616,16 +1486,9 @@ function isElementSelected(id) {
|
|||
function sendKeysToElement(msg) {
|
||||
let command_id = msg.json.command_id;
|
||||
let val = msg.json.value;
|
||||
let el;
|
||||
let id = msg.json.id;
|
||||
let el = elementManager.getKnownElement(id, curContainer);
|
||||
|
||||
return Promise.resolve(elementManager.getKnownElement(msg.json.id, curContainer))
|
||||
.then(knownEl => {
|
||||
el = knownEl;
|
||||
// Element should be actionable from the accessibility standpoint to be able
|
||||
// to send keys to it.
|
||||
return accessibility.getAccessibleObject(el, true)
|
||||
}).then(acc => {
|
||||
checkActionableAccessibility(acc, el);
|
||||
if (el.type == "file") {
|
||||
let p = val.join("");
|
||||
fileInputElement = el;
|
||||
|
@ -1635,9 +1498,10 @@ function sendKeysToElement(msg) {
|
|||
sendSyncMessage("Marionette:getFiles",
|
||||
{value: p, command_id: command_id});
|
||||
} else {
|
||||
utils.sendKeysToElement(curContainer.frame, el, val, sendOk, sendError, command_id);
|
||||
interactions.sendKeysToElement(curContainer, elementManager, id, val)
|
||||
.then(() => sendOk(command_id))
|
||||
.catch(e => sendError(e, command_id));
|
||||
}
|
||||
}).catch(e => sendError(e, command_id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -144,7 +144,7 @@ function focusElement(el) {
|
|||
el.focus();
|
||||
}
|
||||
|
||||
function sendKeysToElement(document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
|
||||
function sendKeysToElement(document, element, keysToSend, ignoreVisibility) {
|
||||
if (ignoreVisibility || checkVisible(element)) {
|
||||
focusElement(element);
|
||||
|
||||
|
@ -159,9 +159,7 @@ function sendKeysToElement(document, element, keysToSend, successCallback, error
|
|||
var c = value.charAt(i);
|
||||
sendSingleKey(c, modifiers, document);
|
||||
}
|
||||
|
||||
successCallback(command_id);
|
||||
} else {
|
||||
errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
|
||||
throw new ElementNotVisibleError("Element is not visible");
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче