Commit 32e424c9 authored by dtseng's avatar dtseng Committed by Commit bot

Expose images to accessibility

Blink has some loose heuristics to ignore images, even ones that are important for interaction.

Specifically, web authors do use horizontal images (i.e. 1d images) to convey information to screen readers that is not purely presentational.

At the very least, it should be up to each client to make an intelligent determination.

This cl also adds missing graphics predicates and key bindings to ChromeVox.

BUG=641848
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2479593007
Cr-Commit-Position: refs/heads/master@{#430199}
parent 79c1b5fe
...@@ -173,6 +173,25 @@ ...@@ -173,6 +173,25 @@
} }
} }
}, },
{
"command": "previousGraphic",
"sequence": {
"cvoxModifier": true,
"keys": {
"keyCode": [71],
"shiftKey": [true]
}
}
},
{
"command": "nextGraphic",
"sequence": {
"cvoxModifier": true,
"keys": {
"keyCode": [71]
}
}
},
{ {
"command": "nextHeading", "command": "nextHeading",
"sequence": { "sequence": {
......
...@@ -124,6 +124,14 @@ AutomationPredicate.control = AutomationPredicate.match({ ...@@ -124,6 +124,14 @@ AutomationPredicate.control = AutomationPredicate.match({
] ]
}); });
/**
* @param {!AutomationNode} node
* @return {boolean}
*/
AutomationPredicate.image = function(node) {
return node.role == Role.image && !!(node.name || node.url);
};
/** @type {AutomationPredicate.Unary} */ /** @type {AutomationPredicate.Unary} */
AutomationPredicate.linkOrControl = AutomationPredicate.match({ AutomationPredicate.linkOrControl = AutomationPredicate.match({
anyPredicate: [ anyPredicate: [
......
...@@ -276,6 +276,16 @@ CommandHandler.onCommand = function(command) { ...@@ -276,6 +276,16 @@ CommandHandler.onCommand = function(command) {
pred = AutomationPredicate.formField; pred = AutomationPredicate.formField;
predErrorMsg = 'no_previous_form_field'; predErrorMsg = 'no_previous_form_field';
break; break;
case 'previousGraphic':
dir = Dir.BACKWARD;
pred = AutomationPredicate.image;
predErrorMsg = 'no_previous_graphic';
break;
case 'nextGraphic':
dir = Dir.FORWARD;
pred = AutomationPredicate.image;
predErrorMsg = 'no_next_graphic';
break;
case 'nextHeading': case 'nextHeading':
dir = Dir.FORWARD; dir = Dir.FORWARD;
pred = AutomationPredicate.heading; pred = AutomationPredicate.heading;
......
...@@ -446,6 +446,10 @@ Output.RULES = { ...@@ -446,6 +446,10 @@ Output.RULES = {
'$nameOrDescendants= ' + '$nameOrDescendants= ' +
'$if($hierarchicalLevel, @tag_h+$hierarchicalLevel, $role) $state' '$if($hierarchicalLevel, @tag_h+$hierarchicalLevel, $role) $state'
}, },
image: {
speak: '$if($name, $name, $urlFilename) ' +
'$value $state $role $description',
},
inlineTextBox: { inlineTextBox: {
speak: '$name=' speak: '$name='
}, },
...@@ -1073,6 +1077,19 @@ Output.prototype = { ...@@ -1073,6 +1077,19 @@ Output.prototype = {
if (earcon) if (earcon)
options.annotation.push(earcon); options.annotation.push(earcon);
this.append_(buff, node.name, options); this.append_(buff, node.name, options);
} else if (token == 'urlFilename') {
options.annotation.push('name');
var url = node.url;
var filename = '';
if (url.substring(0, 4) != 'data') {
filename =
url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
// Hack to not speak the filename if it's ridiculously long.
if (filename.length >= 30)
filename = filename.substring(0, 16) + '...';
}
this.append_(buff, filename, options);
} else if (token == 'nameFromNode') { } else if (token == 'nameFromNode') {
if (chrome.automation.NameFromType[node.nameFrom] == if (chrome.automation.NameFromType[node.nameFrom] ==
'contents') 'contents')
......
...@@ -4,5 +4,6 @@ rootWebArea ...@@ -4,5 +4,6 @@ rootWebArea
++++++staticText name='unread ' ++++++staticText name='unread '
++++++++inlineTextBox name='unread ' ++++++++inlineTextBox name='unread '
++++link name='read' ++++link name='read'
++++++image
++++++staticText name='read' ++++++staticText name='read'
++++++++inlineTextBox name='read' ++++++++inlineTextBox name='read'
...@@ -3,4 +3,5 @@ AXWebArea ...@@ -3,4 +3,5 @@ AXWebArea
++++AXLink AXTitle='unread ' ++++AXLink AXTitle='unread '
++++++AXStaticText AXValue='unread ' ++++++AXStaticText AXValue='unread '
++++AXLink AXTitle='read' ++++AXLink AXTitle='read'
++++++AXImage AXDescription='/read.jpg'
++++++AXStaticText AXValue='read' ++++++AXStaticText AXValue='read'
android.webkit.WebView focusable focused scrollable android.webkit.WebView focusable focused scrollable
++android.view.View ++android.view.View
++++android.widget.Image role_description='graphic' name='pipe' ++++android.widget.Image role_description='graphic' name='pipe'
\ No newline at end of file ++++android.widget.Image role_description='graphic' name='pipe'
++++android.widget.Image role_description='graphic' name=' '
AXWebArea AXWebArea
++AXGroup ++AXGroup
++++AXImage AXRoleDescription='image' AXDescription='pipe' ++++AXImage AXRoleDescription='image' AXDescription='pipe'
++++AXImage AXRoleDescription='image' AXDescription='/pipe.jpg'
++++AXImage AXRoleDescription='image' AXDescription=' '
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
++IA2_ROLE_SECTION ++IA2_ROLE_SECTION
++++ROLE_SYSTEM_GRAPHIC name='pipe' READONLY xml-roles:img ++++ROLE_SYSTEM_GRAPHIC name='pipe' READONLY xml-roles:img
++++ROLE_SYSTEM_GRAPHIC READONLY xml-roles:img
++++ROLE_SYSTEM_GRAPHIC name=' ' READONLY xml-roles:img
...@@ -2,12 +2,12 @@ CONSOLE MESSAGE: line 26: Image description: Image ...@@ -2,12 +2,12 @@ CONSOLE MESSAGE: line 26: Image description: Image
This tests that images with alt tags that only have white space are ignored. This tests that images with alt tags that only have white space are not ignored.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS imagesGroup.childrenCount is 4 PASS imagesGroup.childrenCount is 6
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -16,14 +16,14 @@ ...@@ -16,14 +16,14 @@
<script> <script>
description("This tests that images with alt tags that only have white space are ignored."); description("This tests that images with alt tags that only have white space are not ignored.");
if (window.accessibilityController) { if (window.accessibilityController) {
document.getElementById("images").focus(); document.getElementById("images").focus();
var imagesGroup = accessibilityController.focusedElement; var imagesGroup = accessibilityController.focusedElement;
shouldBe("imagesGroup.childrenCount", "4"); shouldBe("imagesGroup.childrenCount", "6");
console.log("Image description: " + imagesGroup.childAtIndex(2).name); console.log("Image description: " + imagesGroup.childAtIndex(4).name);
} }
</script> </script>
......
test test
test test
This tests that images will fallback to using the title attribute if no other descriptive text is present. This tests that images will fallback to using the title attribute if no other descriptive text is present.
...@@ -18,7 +18,6 @@ Image3 description: ...@@ -18,7 +18,6 @@ Image3 description:
Image4 name: test4 Image4 name: test4
Image4 description: Image4 description:
PASS accessibilityController.accessibleElementById('img-ignored') is undefined
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
</head> </head>
<body id="body"> <body id="body">
<img id="img-ignored" alt="" height="100" width="100">
<img id="img1" title="test1" height="100" width="100"> <img id="img1" title="test1" height="100" width="100">
<img id="img2" alt="alt" title="test2" height="100" width="100"> <img id="img2" alt="alt" title="test2" height="100" width="100">
<div id="img3" role="img" title="test3" width="100" height="100">test</div> <div id="img3" role="img" title="test3" width="100" height="100">test</div>
...@@ -41,9 +40,6 @@ ...@@ -41,9 +40,6 @@
var image4 = accessibilityController.accessibleElementById("img4"); var image4 = accessibilityController.accessibleElementById("img4");
debug("Image4 name: " + image4.name); debug("Image4 name: " + image4.name);
debug("Image4 description: " + image4.description + "<br>"); debug("Image4 description: " + image4.description + "<br>");
// Verify that the first image (with an empty alt tag) is ignored
shouldBe("accessibilityController.accessibleElementById('img-ignored')", "undefined");
} }
</script> </script>
......
...@@ -247,20 +247,43 @@ Content within label refers to label container ...@@ -247,20 +247,43 @@ Content within label refers to label container
"nodes": [ "nodes": [
{ {
"nodeId": "<string>", "nodeId": "<string>",
"ignored": true, "ignored": false,
"ignoredReasons": [
{
"name": "emptyAlt",
"value": {
"type": "boolean",
"value": true
}
}
],
"role": { "role": {
"type": "role", "type": "role",
"value": "img" "value": "img"
} },
"name": {
"type": "computedString",
"value": "",
"sources": [
{
"type": "relatedElement",
"attribute": "aria-labelledby"
},
{
"type": "attribute",
"attribute": "aria-label"
},
{
"type": "attribute",
"value": {
"type": "computedString",
"value": ""
},
"attribute": "alt",
"attributeValue": {
"type": "string",
"value": ""
}
},
{
"type": "attribute",
"attribute": "title",
"superseded": true
}
]
},
"properties": []
} }
] ]
} }
......
...@@ -697,52 +697,8 @@ bool AXLayoutObject::computeAccessibilityIsIgnored( ...@@ -697,52 +697,8 @@ bool AXLayoutObject::computeAccessibilityIsIgnored(
return true; return true;
} }
// ignore images seemingly used as spacers if (isImage())
if (isImage()) {
// If the image can take focus, it should not be ignored, lest the user not
// be able to interact with something important.
if (canSetFocusAttribute())
return false;
if (node && node->isElementNode()) {
Element* elt = toElement(node);
const AtomicString& alt = elt->getAttribute(altAttr);
// don't ignore an image that has an alt tag
if (!alt.getString().containsOnlyWhitespace())
return false;
// informal standard is to ignore images with zero-length alt strings
if (!alt.isNull()) {
if (ignoredReasons)
ignoredReasons->append(IgnoredReason(AXEmptyAlt));
return true;
}
}
if (isNativeImage() && m_layoutObject->isImage()) {
// check for one-dimensional image
LayoutImage* image = toLayoutImage(m_layoutObject);
if (image->size().height() <= 1 || image->size().width() <= 1) {
if (ignoredReasons)
ignoredReasons->append(IgnoredReason(AXProbablyPresentational));
return true;
}
// Check whether laid out image was stretched from one-dimensional file
// image.
if (image->cachedImage()) {
LayoutSize imageSize = image->cachedImage()->imageSize(
LayoutObject::shouldRespectImageOrientation(m_layoutObject),
image->view()->zoomFactor());
if (imageSize.height() <= 1 || imageSize.width() <= 1) {
if (ignoredReasons)
ignoredReasons->append(IgnoredReason(AXProbablyPresentational));
return true;
}
return false;
}
}
return false; return false;
}
if (isCanvas()) { if (isCanvas()) {
if (canvasHasFallbackContent()) if (canvasHasFallbackContent())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment