Commit b0fc027b authored by Meredith Lane's avatar Meredith Lane Committed by Commit Bot

Implement string accessors for ComputedAccessibleNode.

Adds the IDL code and accessor methods into ComputedAccessibleNode for
the intersection of properties between AccessibleNode and
AXStringAttribute. These accessors will return null strings to the
window in a few cases:
   - when the AXNode for a given matching AXID is not in the tree,
   - when the string property has not been set.

Currently, the accessors are rudimentary and require an enum
translation, although this may change in future in favour of an
individual hard coded method on the AomContentAXTree.

BUG=792783

Change-Id: Icd9cac2cb8486d549932f400b0aeeae569a1f712
Reviewed-on: https://chromium-review.googlesource.com/883863
Commit-Queue: Meredith Lane <meredithl@google.com>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533834}
parent ed7de0fe
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "content/renderer/accessibility/aom_content_ax_tree.h" #include "content/renderer/accessibility/aom_content_ax_tree.h"
#include <string>
#include "content/common/ax_content_node_data.h" #include "content/common/ax_content_node_data.h"
#include "content/renderer/accessibility/render_accessibility_impl.h" #include "content/renderer/accessibility/render_accessibility_impl.h"
#include "ui/accessibility/ax_enum_util.h" #include "ui/accessibility/ax_enum_util.h"
...@@ -38,6 +40,24 @@ ax::mojom::IntAttribute GetCorrespondingAXAttribute( ...@@ -38,6 +40,24 @@ ax::mojom::IntAttribute GetCorrespondingAXAttribute(
} }
} }
ax::mojom::StringAttribute GetCorrespondingAXAttribute(
blink::WebAOMStringAttribute attr) {
switch (attr) {
case blink::WebAOMStringAttribute::AOM_ATTR_KEY_SHORTCUTS:
return ax::mojom::StringAttribute::kKeyShortcuts;
case blink::WebAOMStringAttribute::AOM_ATTR_NAME:
return ax::mojom::StringAttribute::kName;
case blink::WebAOMStringAttribute::AOM_ATTR_PLACEHOLDER:
return ax::mojom::StringAttribute::kPlaceholder;
case blink::WebAOMStringAttribute::AOM_ATTR_ROLE_DESCRIPTION:
return ax::mojom::StringAttribute::kRoleDescription;
case blink::WebAOMStringAttribute::AOM_ATTR_VALUE_TEXT:
return ax::mojom::StringAttribute::kValue;
default:
return ax::mojom::StringAttribute::kNone;
}
}
} // namespace } // namespace
namespace content { namespace content {
...@@ -64,24 +84,35 @@ bool AomContentAxTree::ComputeAccessibilityTree() { ...@@ -64,24 +84,35 @@ bool AomContentAxTree::ComputeAccessibilityTree() {
return tree_.Unserialize(tree_update); return tree_.Unserialize(tree_update);
} }
blink::WebString AomContentAxTree::GetNameForAXNode(int32_t axID) { bool AomContentAxTree::GetRoleForAXNode(int32_t ax_id,
ui::AXNode* node = tree_.GetFromId(axID); blink::WebString* out_param) {
return (node) ? blink::WebString::FromUTF8(node->data().GetStringAttribute( ui::AXNode* node = tree_.GetFromId(ax_id);
ax::mojom::StringAttribute::kName)) if (!node)
: blink::WebString(); return false;
*out_param = blink::WebString::FromUTF8(ui::ToString(node->data().role));
return true;
} }
blink::WebString AomContentAxTree::GetRoleForAXNode(int32_t axID) { bool AomContentAxTree::GetStringAttributeForAXNode(
ui::AXNode* node = tree_.GetFromId(axID); int32_t ax_id,
// TODO(meredithl): Change to blink_ax_conversion.cc method once available. blink::WebAOMStringAttribute attr,
return (node) ? blink::WebString::FromUTF8(ui::ToString(node->data().role)) blink::WebString* out_param) {
: blink::WebString(); ui::AXNode* node = tree_.GetFromId(ax_id);
std::string out_string;
if (node && node->data().GetStringAttribute(GetCorrespondingAXAttribute(attr),
&out_string)) {
*out_param = blink::WebString::FromUTF8(out_string.c_str());
return true;
}
return false;
} }
bool AomContentAxTree::GetIntAttributeForAXNode(int32_t axID, bool AomContentAxTree::GetIntAttributeForAXNode(int32_t ax_id,
blink::WebAOMIntAttribute attr, blink::WebAOMIntAttribute attr,
int32_t* out_param) { int32_t* out_param) {
ui::AXNode* node = tree_.GetFromId(axID); ui::AXNode* node = tree_.GetFromId(ax_id);
if (!node) if (!node)
return false; return false;
ax::mojom::IntAttribute ax_attr = GetCorrespondingAXAttribute(attr); ax::mojom::IntAttribute ax_attr = GetCorrespondingAXAttribute(attr);
......
...@@ -22,13 +22,11 @@ class AomContentAxTree : public blink::WebComputedAXTree { ...@@ -22,13 +22,11 @@ class AomContentAxTree : public blink::WebComputedAXTree {
// blink::WebComputedAXTree implementation. // blink::WebComputedAXTree implementation.
bool ComputeAccessibilityTree() override; bool ComputeAccessibilityTree() override;
bool GetRoleForAXNode(int32_t axID, blink::WebString* out_param) override;
// String accessible property getters. bool GetStringAttributeForAXNode(int32_t,
// TODO(meredithl): Change these to return null rather than empty strings. blink::WebAOMStringAttribute,
blink::WebString GetNameForAXNode(int32_t) override; blink::WebString* out_param) override;
blink::WebString GetRoleForAXNode(int32_t) override; bool GetIntAttributeForAXNode(int32_t ax_id,
bool GetIntAttributeForAXNode(int32_t axID,
blink::WebAOMIntAttribute, blink::WebAOMIntAttribute,
int32_t* out_param) override; int32_t* out_param) override;
......
...@@ -11,7 +11,7 @@ Spec: https://wicg.github.io/aom/spec/ ...@@ -11,7 +11,7 @@ Spec: https://wicg.github.io/aom/spec/
--> -->
<p id="button1" role="button">Click</p> <div id="button1" role="button">Click</div>
<script> <script>
...@@ -19,70 +19,52 @@ test(function(t) { ...@@ -19,70 +19,52 @@ test(function(t) {
assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled); assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
}, "Make sure that Accessibility Object Model is enabled"); }, "Make sure that Accessibility Object Model is enabled");
promise_test(() => { async_test(async function(test) {
var element = document.getElementById("button1"); var element = document.getElementById("button1");
return window.getComputedAccessibleNode(element).then(computedAXNode => { var computedAXNode = await window.getComputedAccessibleNode(element)
assert_true(computedAXNode != null); assert_true(computedAXNode != null);
}); test.done();
}, 'Ensure that a non null value is returned from getComputedAccessibleNode'); }, 'Ensure that a non null value is returned from getComputedAccessibleNode');
promise_test(() => {
var element = document.getElementById("button1");
return window.getComputedAccessibleNode(element).then(computedAXNode => {
assert_equals(computedAXNode.role, "button");
});
}, "ComputedAccessibleNode.role");
promise_test(() => { async_test(async function(test) {
var element = document.getElementById("button1"); var element = document.getElementById("button1");
return window.getComputedAccessibleNode(element).then(computedAXNode => {
assert_equals(computedAXNode.name, "Click"); var computedAXNode1 = await window.getComputedAccessibleNode(element);
}); var computedAXNode2 = await window.getComputedAccessibleNode(element);
}, "ComputedAccessibleNode.name"); assert_true(computedAXNode1 === computedAXNode2);
test.done();
}, "Multiple calls to getComputedAccessibleNode will return the same node.");
</script> </script>
<p id="button2" role="button" aria-label="axButton">Click</p> <div id="button2" role="button" aria-label="axButton">Click</div>
<script> <script>
promise_test(() => { async_test(async function(test) {
var element = document.getElementById("button2"); var button1 = document.getElementById("button1");
return window.getComputedAccessibleNode(element).then(computedAXNode => { var button1CAXNode = await window.getComputedAccessibleNode(button1);
assert_equals(computedAXNode.name, "axButton");
});
}, "ComputedAccessibleNode.name set through ARIA.");
promise_test(() => {
var element = document.getElementById("button2");
return window.getComputedAccessibleNode(element).then(computedAXNode1 => {
window.getComputedAccessibleNode(element).then(computedAXNode2 => {
assert_true(computedAXNode1 === computedAXNode2);
});
});
}, "Multiple calls to getComputedAccessibleNode will return the same node.");
assert_equals(button1CAXNode.name, "Click");
assert_equals(button1CAXNode.role, "button");
button1.style.display = "none";
promise_test(() => {
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2"); var button2 = document.getElementById("button2");
return window.getComputedAccessibleNode(button2).then(button2AXNode => { var button2CAXNode = await window.getComputedAccessibleNode(button2);
assert_equals(button2AXNode.name, "axButton");
assert_equals(button2AXNode.role, "button");
// Set to display:none to remove from accessibility tree. assert_equals(button2CAXNode.name, "axButton");
button2.style.display = 'none'; assert_equals(button2CAXNode.role, "button");
window.getComputedAccessibleNode(button1).then(button1AXNode => {
// button1's computedAXNode should still be valid.
assert_equals(button1AXNode.name, "Click");
assert_equals(button1AXNode.role, "button");
// button2s computedAXNode should only contain empty attributes. // As button1 has no node in the accessibility tree anymore, assert that the
assert_equals(button2AXNode.name, ""); // its previously retrieved computed accessible node has had its attributes
assert_equals(button2AXNode.role, ""); // nullified.
}) assert_equals(button1CAXNode.name, null);
}); assert_equals(button1CAXNode.role, null);
test.done();
}, "Deleting nodes from the accessibility tree will not cause a crash, and set computed properties on any references to a deleted computed accessible node to empty."); }, "Deleting nodes from the accessibility tree will not cause a crash, and properties on any references to a deleted computed accessible node have been nullified.");
</script> </script>
<!DOCTYPE HTML>
<script src="../resources/gc.js"></script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<!--
Accessibility Object Model
Explainer: https://github.com/WICG/aom/blob/master/explainer.md
Spec: https://wicg.github.io/aom/spec/
-->
<div id="button1" role="button">Click</div>
<script>
test(function(t) {
assert_true(internals.runtimeFlags.accessibilityObjectModelEnabled);
}, "Make sure that Accessibility Object Model is enabled");
async_test(async function(test) {
var element = document.getElementById("button1");
var computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.role, "button");
test.done();
}, "ComputedAccessibleNode.role");
async_test(async function(test) {
var element = document.getElementById("button1");
var computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.name, "Click");
test.done();
}, "ComputedAccessibleNode.name");
</script>
<div id="button2" role="button" aria-label="axButton">Click</div>
<script>
async_test(async function(test) {
var element = document.getElementById("button2");
var computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.name, "axButton");
test.done();
}, "ComputedAccessibleNode.name set through ARIA.");
</script>
<div id="shortcut" aria-keyshortcuts="Alt+Shift+P">Use my shortcut!</div>
<script>
async_test(async function(test) {
var element = document.getElementById("shortcut");
var computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.keyShortcuts, "Alt+Shift+P");
test.done();
}, "ComputedAccessibleNode.keyShortcuts.");
</script>
<p><label>Name:<input id="fullname" type="text" name="fullname" placeholder="Jordan Doe"></label></p>
<script>
async_test(async function(test) {
var element = document.getElementById("fullname");
computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.placeholder, "Jordan Doe");
test.done();
}, "ComputedAccessibleNode.placeHolder.");
</script>
<div id="button3" role="button" aria-roledescription="submit button">Click</div>
<script>
async_test(async function(test) {
var element = document.getElementById("button3");
computedAXNode = await window.getComputedAccessibleNode(element);
assert_equals(computedAXNode.role, "button");
assert_equals(computedAXNode.roleDescription, "submit button");
test.done();
}, "ComputedAccessibleNode.roleDescription.");
</script>
...@@ -1050,14 +1050,18 @@ interface ComputedAccessibleNode ...@@ -1050,14 +1050,18 @@ interface ComputedAccessibleNode
getter colCount getter colCount
getter colIndex getter colIndex
getter colSpan getter colSpan
getter keyShortcuts
getter level getter level
getter name getter name
getter placeholder
getter posInSet getter posInSet
getter role getter role
getter roleDescription
getter rowCount getter rowCount
getter rowIndex getter rowIndex
getter rowSpan getter rowSpan
getter setSize getter setSize
getter valueText
method constructor method constructor
interface ConstantSourceNode : AudioScheduledSourceNode interface ConstantSourceNode : AudioScheduledSourceNode
attribute @@toStringTag attribute @@toStringTag
......
...@@ -60,12 +60,39 @@ int32_t ComputedAccessibleNode::GetIntAttribute(WebAOMIntAttribute attr, ...@@ -60,12 +60,39 @@ int32_t ComputedAccessibleNode::GetIntAttribute(WebAOMIntAttribute attr,
return out; return out;
} }
const String ComputedAccessibleNode::name() const { const String ComputedAccessibleNode::GetStringAttribute(
return tree_->GetNameForAXNode(cache_->GetAXID(element_)); WebAOMStringAttribute attr) const {
WebString out;
if (tree_->GetStringAttributeForAXNode(cache_->GetAXID(element_), attr,
&out)) {
return out;
}
return String();
} }
const String ComputedAccessibleNode::keyShortcuts() const {
return GetStringAttribute(WebAOMStringAttribute::AOM_ATTR_KEY_SHORTCUTS);
}
const String ComputedAccessibleNode::name() const {
return GetStringAttribute(WebAOMStringAttribute::AOM_ATTR_NAME);
}
const String ComputedAccessibleNode::placeholder() const {
return GetStringAttribute(WebAOMStringAttribute::AOM_ATTR_PLACEHOLDER);
}
const String ComputedAccessibleNode::role() const { const String ComputedAccessibleNode::role() const {
return tree_->GetRoleForAXNode(cache_->GetAXID(element_)); WebString out;
if (tree_->GetRoleForAXNode(cache_->GetAXID(element_), &out)) {
return out;
}
return String();
}
const String ComputedAccessibleNode::roleDescription() const {
return GetStringAttribute(WebAOMStringAttribute::AOM_ATTR_ROLE_DESCRIPTION);
}
const String ComputedAccessibleNode::valueText() const {
return GetStringAttribute(WebAOMStringAttribute::AOM_ATTR_VALUE_TEXT);
} }
int32_t ComputedAccessibleNode::colCount(bool& is_null) const { int32_t ComputedAccessibleNode::colCount(bool& is_null) const {
......
...@@ -30,10 +30,12 @@ class ComputedAccessibleNode : public ScriptWrappable { ...@@ -30,10 +30,12 @@ class ComputedAccessibleNode : public ScriptWrappable {
// Starts computation of the accessibility properties of the stored element. // Starts computation of the accessibility properties of the stored element.
ScriptPromise ComputeAccessibleProperties(ScriptState*); ScriptPromise ComputeAccessibleProperties(ScriptState*);
// String attribute accessors. These can return blank strings if the element const String keyShortcuts() const;
// has no node in the computed accessible tree, or property does not apply.
const String role() const;
const String name() const; const String name() const;
const String placeholder() const;
const String role() const;
const String roleDescription() const;
const String valueText() const;
int32_t colCount(bool& is_null) const; int32_t colCount(bool& is_null) const;
int32_t colIndex(bool& is_null) const; int32_t colIndex(bool& is_null) const;
...@@ -52,6 +54,7 @@ class ComputedAccessibleNode : public ScriptWrappable { ...@@ -52,6 +54,7 @@ class ComputedAccessibleNode : public ScriptWrappable {
void OnSnapshotResponse(ScriptPromiseResolver*); void OnSnapshotResponse(ScriptPromiseResolver*);
int32_t GetIntAttribute(WebAOMIntAttribute, bool& is_null) const; int32_t GetIntAttribute(WebAOMIntAttribute, bool& is_null) const;
const String GetStringAttribute(WebAOMStringAttribute) const;
Member<Element> element_; Member<Element> element_;
Member<AXObjectCache> cache_; Member<AXObjectCache> cache_;
......
...@@ -8,8 +8,12 @@ ...@@ -8,8 +8,12 @@
[ [
RuntimeEnabled=AccessibilityObjectModel RuntimeEnabled=AccessibilityObjectModel
] interface ComputedAccessibleNode { ] interface ComputedAccessibleNode {
readonly attribute DOMString? keyShortcuts;
readonly attribute DOMString? name; readonly attribute DOMString? name;
readonly attribute DOMString? placeholder;
readonly attribute DOMString? role; readonly attribute DOMString? role;
readonly attribute DOMString? roleDescription;
readonly attribute DOMString? valueText;
readonly attribute long? colCount; readonly attribute long? colCount;
readonly attribute unsigned long? colIndex; readonly attribute unsigned long? colIndex;
......
...@@ -22,20 +22,39 @@ enum WebAOMIntAttribute { ...@@ -22,20 +22,39 @@ enum WebAOMIntAttribute {
AOM_ATTR_SET_SIZE, AOM_ATTR_SET_SIZE,
}; };
enum WebAOMStringAttribute {
AOM_STRING_ATTRIBUTE_NONE,
AOM_ATTR_KEY_SHORTCUTS,
AOM_ATTR_NAME,
AOM_ATTR_PLACEHOLDER,
AOM_ATTR_ROLE_DESCRIPTION,
AOM_ATTR_VALUE_TEXT,
};
class WebComputedAXTree { class WebComputedAXTree {
public: public:
virtual ~WebComputedAXTree() {} virtual ~WebComputedAXTree() {}
// Compute the accessibility tree as a snapshot. Returns true if the snapshot
// could be unserialized into a tree successfully.
virtual bool ComputeAccessibilityTree() = 0; virtual bool ComputeAccessibilityTree() = 0;
virtual WebString GetNameForAXNode(int32_t axID) = 0;
virtual WebString GetRoleForAXNode(int32_t axID) = 0; // Accessibility Property Accessors.
// Get the specified attribute for a given AXID, returning true if the // Get the specified attribute for a given AXID, returning true if the
// node exists in the tree and contains the attribute. Stores the result in // node exists in the tree and contains the attribute, and stores the result
// |out_param|. // in |out_param|.
virtual bool GetIntAttributeForAXNode(int32_t axID, virtual bool GetIntAttributeForAXNode(int32_t axID,
WebAOMIntAttribute, WebAOMIntAttribute,
int32_t* out_param) = 0; int32_t* out_param) = 0;
virtual bool GetStringAttributeForAXNode(int32_t,
blink::WebAOMStringAttribute,
blink::WebString* out_param) = 0;
// The role is stored seperately from other attributes in the AXNode, so we
// expose a seperate method for retrieving this.
virtual bool GetRoleForAXNode(int32_t axID, blink::WebString* out_param) = 0;
}; };
} // namespace blink } // namespace blink
......
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