Bug 1201595 - improving a11y checks reliability and error messaging. r=ato

This commit is contained in:
Yura Zenevich 2015-09-10 11:45:33 -04:00
Родитель 35f1f1cf1d
Коммит 47268649ef
4 изменённых файлов: 53 добавлений и 47 удалений

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

@ -97,7 +97,7 @@ class TestAccessibility(MarionetteTestCase):
lambda button: self.assertRaises(ElementNotAccessibleException,
button.tap))
self.run_element_test(self.falsy_elements,
lambda button: self.assertRaises(ElementNotAccessibleException,
lambda button: self.assertRaises(ElementNotVisibleException,
button.tap))
def test_single_tap_raises_no_exceptions(self):
@ -120,7 +120,7 @@ class TestAccessibility(MarionetteTestCase):
lambda button: self.assertRaises(ElementNotAccessibleException,
button.click))
self.run_element_test(self.falsy_elements,
lambda button: self.assertRaises(ElementNotAccessibleException,
lambda button: self.assertRaises(ElementNotVisibleException,
button.click))
def test_click_raises_no_exceptions(self):

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

@ -1803,7 +1803,7 @@ GeckoDriver.prototype.singleTap = function(cmd, resp) {
case Context.CONTENT:
this.addFrameCloseListener("tap");
yield this.listener.singleTap({id: id, corx: x, cory: y});
yield this.listener.singleTap(id, x, y);
break;
}
};

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

@ -98,7 +98,8 @@ Accessibility.prototype = {
getAccessibleObject(element, mustHaveAccessible = false) {
let acc = this.accessibleRetrieval.getAccessibleFor(element);
if (!acc && mustHaveAccessible) {
this.handleErrorMessage('Element does not have an accessible object');
this.handleErrorMessage('Element does not have an accessible object',
element);
}
return acc;
},
@ -178,11 +179,15 @@ Accessibility.prototype = {
/**
* Send an error message or log the error message in the log
* @param String message
* @param DOMElement element that caused an error
*/
handleErrorMessage(message) {
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);
}

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

@ -215,6 +215,7 @@ let isElementDisplayedFn = dispatch(isElementDisplayed);
let getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
let switchToShadowRootFn = dispatch(switchToShadowRoot);
let getCookiesFn = dispatch(getCookies);
let singleTapFn = dispatch(singleTap);
/**
* Start all message listeners
@ -225,7 +226,7 @@ function startListeners() {
addMessageListenerId("Marionette:executeScript", executeScript);
addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
addMessageListenerId("Marionette:executeJSScript", executeJSScript);
addMessageListenerId("Marionette:singleTap", singleTap);
addMessageListenerId("Marionette:singleTap", singleTapFn);
addMessageListenerId("Marionette:actionChain", actionChain);
addMessageListenerId("Marionette:multiAction", multiAction);
addMessageListenerId("Marionette:get", get);
@ -329,7 +330,7 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:executeScript", executeScript);
removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
removeMessageListenerId("Marionette:singleTap", singleTap);
removeMessageListenerId("Marionette:singleTap", singleTapFn);
removeMessageListenerId("Marionette:actionChain", actionChain);
removeMessageListenerId("Marionette:multiAction", multiAction);
removeMessageListenerId("Marionette:get", get);
@ -914,34 +915,27 @@ function checkVisible(el, x, y) {
/**
* Function that perform a single tap
*/
function singleTap(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curContainer);
let acc = accessibility.getAccessibleObject(el, true);
// after this block, the element will be scrolled into view
let visible = checkVisible(el, msg.json.corx, msg.json.cory);
checkVisibleAccessibility(acc, visible);
if (!visible) {
sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id);
return;
}
checkActionableAccessibility(acc);
if (!curContainer.frame.document.createTouch) {
actions.mouseEventsOnly = true;
}
let c = coordinates(el, msg.json.corx, msg.json.cory);
if (!actions.mouseEventsOnly) {
let touchId = actions.nextTouchId++;
let touch = createATouch(el, c.x, c.y, touchId);
emitTouchEvent('touchstart', touch);
emitTouchEvent('touchend', touch);
}
actions.mouseTap(el.ownerDocument, c.x, c.y);
sendOk(command_id);
} catch (e) {
sendError(e, command_id);
function singleTap(id, corx, cory) {
let el = elementManager.getKnownElement(id, curContainer);
// after this block, the element will be scrolled into view
let visible = checkVisible(el, corx, cory);
if (!visible) {
throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
}
let acc = accessibility.getAccessibleObject(el, true);
checkVisibleAccessibility(acc, el, visible);
checkActionableAccessibility(acc, el);
if (!curContainer.frame.document.createTouch) {
actions.mouseEventsOnly = true;
}
let c = coordinates(el, corx, cory);
if (!actions.mouseEventsOnly) {
let touchId = actions.nextTouchId++;
let touch = createATouch(el, c.x, c.y, touchId);
emitTouchEvent('touchstart', touch);
emitTouchEvent('touchend', touch);
}
actions.mouseTap(el.ownerDocument, c.x, c.y);
}
/**
@ -969,16 +963,17 @@ function checkEnabledAccessibility(accesible, element, enabled) {
} else if (!enabled && !disabledAccessibility) {
message = 'Element is disabled but enabled via the accessibility API';
}
accessibility.handleErrorMessage(message);
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, visible) {
function checkVisibleAccessibility(accesible, element, visible) {
if (!accesible) {
return;
}
@ -991,14 +986,15 @@ function checkVisibleAccessibility(accesible, visible) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it';
}
accessibility.handleErrorMessage(message);
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) {
function checkActionableAccessibility(accesible, element) {
if (!accesible) {
return;
}
@ -1013,16 +1009,17 @@ function checkActionableAccessibility(accesible) {
} else if (!accessibility.matchState(accesible, 'STATE_FOCUSABLE')) {
message = 'Element is not focusable via the accessibility API';
}
accessibility.handleErrorMessage(message);
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, selected) {
function checkSelectedAccessibility(accessible, element, selected) {
if (!accessible) {
return;
}
@ -1039,7 +1036,7 @@ function checkSelectedAccessibility(accessible, selected) {
} else if (!selected && selectedAccessibility) {
message = 'Element is not selected but selected via the accessibility API';
}
accessibility.handleErrorMessage(message);
accessibility.handleErrorMessage(message, element);
}
@ -1448,15 +1445,16 @@ function getActiveElement() {
*/
function clickElement(id) {
let el = elementManager.getKnownElement(id, curContainer);
let acc = accessibility.getAccessibleObject(el, true);
let visible = checkVisible(el);
checkVisibleAccessibility(acc, visible);
if (!visible) {
throw new ElementNotVisibleError("Element is not visible");
}
checkActionableAccessibility(acc);
let acc = accessibility.getAccessibleObject(el, true);
checkVisibleAccessibility(acc, el, visible);
if (utils.isElementEnabled(el)) {
checkEnabledAccessibility(acc, el, true);
checkActionableAccessibility(acc, el);
utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView);
} else {
throw new InvalidElementStateError("Element is not Enabled");
@ -1516,7 +1514,8 @@ function getElementTagName(id) {
function isElementDisplayed(id) {
let el = elementManager.getKnownElement(id, curContainer);
let displayed = utils.isElementDisplayed(el);
checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed);
checkVisibleAccessibility(
accessibility.getAccessibleObject(el), el, displayed);
return displayed;
}
@ -1584,7 +1583,8 @@ function isElementEnabled(id) {
function isElementSelected(id) {
let el = elementManager.getKnownElement(id, curContainer);
let selected = utils.isElementSelected(el);
checkSelectedAccessibility(accessibility.getAccessibleObject(el), selected);
checkSelectedAccessibility(
accessibility.getAccessibleObject(el), el, selected);
return selected;
}
@ -1599,7 +1599,8 @@ function sendKeysToElement(msg) {
let el = elementManager.getKnownElement(msg.json.id, curContainer);
// Element should be actionable from the accessibility standpoint to be able
// to send keys to it.
checkActionableAccessibility(accessibility.getAccessibleObject(el, true));
checkActionableAccessibility(
accessibility.getAccessibleObject(el, true), el);
if (el.type == "file") {
let p = val.join("");
fileInputElement = el;