Commit c58a4ab2 authored by Alexander Surkov's avatar Alexander Surkov Committed by Commit Bot

Support property filtering by line indexes.

Extend property filtering capabilities by allowing to whitelist
accessible objects the property should be called for. Format is:
:LINE_NUM_0,...,LINE_NUM_N

Bug: 1086610
AX-Relnotes: n/a
Change-Id: Iea938c3ea1a6f766d40bec20a6e54be8d2bf5520
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2218610Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Alexander Surkov <asurkov@igalia.com>
Cr-Commit-Position: refs/heads/master@{#774849}
parent aa8eab76
...@@ -38,35 +38,62 @@ const char kSkipChildren[] = "@NO_CHILDREN_DUMP"; ...@@ -38,35 +38,62 @@ const char kSkipChildren[] = "@NO_CHILDREN_DUMP";
// //
// static // static
PropertyNode PropertyNode::FromProperty(const base::string16& property) { PropertyNode PropertyNode::FromPropertyFilter(
const AccessibilityTreeFormatter::PropertyFilter& filter) {
// Property invocation: property_str expected format is
// prop_name or prop_name(arg1, ... argN).
PropertyNode root; PropertyNode root;
Parse(&root, property.begin(), property.end()); Parse(&root, filter.property_str.begin(), filter.property_str.end());
PropertyNode* node = &root.parameters[0]; PropertyNode* node = &root.parameters[0];
node->original_property = property; node->original_property = filter.property_str;
// Line indexes filter: filter_str expected format is
// :line_num_1, ... :line_num_N, a comma separated list of line indexes
// the property should be queried for. For example, ":1,:5,:7" indicates that
// the property should called for objects placed on 1, 5 and 7 lines only.
if (!filter.filter_str.empty()) {
node->line_indexes =
base::SplitString(filter.filter_str, base::string16(1, ','),
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}
return std::move(*node); return std::move(*node);
} }
PropertyNode::PropertyNode() = default; PropertyNode::PropertyNode() = default;
PropertyNode::PropertyNode(PropertyNode&& o) PropertyNode::PropertyNode(PropertyNode&& o)
: value(std::move(o.value)), : name_or_value(std::move(o.name_or_value)),
parameters(std::move(o.parameters)), parameters(std::move(o.parameters)),
original_property(std::move(o.original_property)) {} original_property(std::move(o.original_property)),
line_indexes(std::move(o.line_indexes)) {}
PropertyNode::~PropertyNode() = default; PropertyNode::~PropertyNode() = default;
PropertyNode& PropertyNode::operator=(PropertyNode&& o) { PropertyNode& PropertyNode::operator=(PropertyNode&& o) {
value = std::move(o.value); name_or_value = std::move(o.name_or_value);
parameters = std::move(o.parameters); parameters = std::move(o.parameters);
original_property = std::move(o.original_property); original_property = std::move(o.original_property);
line_indexes = std::move(o.line_indexes);
return *this; return *this;
} }
PropertyNode::operator bool() const { PropertyNode::operator bool() const {
return !value.empty(); return !name_or_value.empty();
} }
std::string PropertyNode::ToString() const { std::string PropertyNode::ToString() const {
std::string out = base::UTF16ToUTF8(value); std::string out;
for (const auto& index : line_indexes) {
if (!out.empty()) {
out += ',';
}
out += base::UTF16ToUTF8(index);
}
if (!out.empty()) {
out += ';';
}
out += base::UTF16ToUTF8(name_or_value);
if (parameters.size()) { if (parameters.size()) {
out += '('; out += '(';
for (size_t i = 0; i < parameters.size(); i++) { for (size_t i = 0; i < parameters.size(); i++) {
...@@ -81,10 +108,11 @@ std::string PropertyNode::ToString() const { ...@@ -81,10 +108,11 @@ std::string PropertyNode::ToString() const {
} }
// private // private
PropertyNode::PropertyNode(const base::string16& v) : value(v) {} PropertyNode::PropertyNode(const base::string16& name_or_value)
: name_or_value(name_or_value) {}
PropertyNode::PropertyNode(PropertyNode::iterator begin, PropertyNode::PropertyNode(PropertyNode::iterator begin,
PropertyNode::iterator end) PropertyNode::iterator end)
: value(begin, end) {} : name_or_value(begin, end) {}
// private static // private static
PropertyNode::iterator PropertyNode::Parse(PropertyNode* node, PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
...@@ -139,7 +167,26 @@ PropertyNode::iterator PropertyNode::Parse(PropertyNode* node, ...@@ -139,7 +167,26 @@ PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
return iter; return iter;
} }
//
// AccessibilityTreeFormatter // AccessibilityTreeFormatter
//
AccessibilityTreeFormatter::PropertyFilter::PropertyFilter(
const PropertyFilter&) = default;
AccessibilityTreeFormatter::PropertyFilter::PropertyFilter(
const base::string16& str,
Type type)
: match_str(str), type(type) {
size_t index = str.find(';');
if (index != std::string::npos) {
filter_str = str.substr(0, index);
if (index + 1 < str.length()) {
match_str = str.substr(index + 1, std::string::npos);
}
}
property_str = match_str.substr(0, match_str.find('='));
}
AccessibilityTreeFormatter::TestPass AccessibilityTreeFormatter::GetTestPass( AccessibilityTreeFormatter::TestPass AccessibilityTreeFormatter::GetTestPass(
size_t index) { size_t index) {
...@@ -299,24 +346,25 @@ AccessibilityTreeFormatterBase::GetVersionSpecificExpectedFileSuffix() { ...@@ -299,24 +346,25 @@ AccessibilityTreeFormatterBase::GetVersionSpecificExpectedFileSuffix() {
} }
PropertyNode AccessibilityTreeFormatterBase::GetMatchingPropertyNode( PropertyNode AccessibilityTreeFormatterBase::GetMatchingPropertyNode(
const base::string16& text) { const base::string16& line_index,
// Find the first allow-filter matching the property name. const base::string16& property_name) {
// The filters have form of name(args)=value. Here we match the name part. // Find the first allow-filter matching the line index and the property name.
const base::string16 filter_delim = base::ASCIIToUTF16("=");
for (const auto& filter : property_filters_) { for (const auto& filter : property_filters_) {
base::String16Tokenizer filter_tokenizer(filter.match_str, filter_delim); PropertyNode property_node = PropertyNode::FromPropertyFilter(filter);
if (!filter_tokenizer.GetNext()) {
// Skip if the line index filter doesn't matched (if specified).
if (!property_node.line_indexes.empty() &&
std::find(property_node.line_indexes.begin(),
property_node.line_indexes.end(),
line_index) == property_node.line_indexes.end()) {
continue; continue;
} }
base::string16 property = filter_tokenizer.token();
PropertyNode property_node = PropertyNode::FromProperty(property);
// The filter should be either an exact property match or a wildcard // The filter should be either an exact property match or a wildcard
// matching to support filter collections like AXRole* which matches // matching to support filter collections like AXRole* which matches
// AXRoleDescription. // AXRoleDescription.
if (text == property_node.value || if (property_name == property_node.name_or_value ||
base::MatchPattern(text, property_node.value)) { base::MatchPattern(property_name, property_node.name_or_value)) {
switch (filter.type) { switch (filter.type) {
case PropertyFilter::ALLOW_EMPTY: case PropertyFilter::ALLOW_EMPTY:
case PropertyFilter::ALLOW: case PropertyFilter::ALLOW:
......
...@@ -37,7 +37,8 @@ namespace content { ...@@ -37,7 +37,8 @@ namespace content {
class CONTENT_EXPORT PropertyNode final { class CONTENT_EXPORT PropertyNode final {
public: public:
// Parses a property node from a string. // Parses a property node from a string.
static PropertyNode FromProperty(const base::string16&); static PropertyNode FromPropertyFilter(
const AccessibilityTreeFormatter::PropertyFilter& filter);
PropertyNode(); PropertyNode();
PropertyNode(PropertyNode&&); PropertyNode(PropertyNode&&);
...@@ -46,13 +47,17 @@ class CONTENT_EXPORT PropertyNode final { ...@@ -46,13 +47,17 @@ class CONTENT_EXPORT PropertyNode final {
PropertyNode& operator=(PropertyNode&& other); PropertyNode& operator=(PropertyNode&& other);
explicit operator bool() const; explicit operator bool() const;
base::string16 value; base::string16 name_or_value;
std::vector<PropertyNode> parameters; std::vector<PropertyNode> parameters;
// Used to store the origianl unparsed property including invocation // Used to store the origianl unparsed property including invocation
// parameters if any. // parameters if any.
base::string16 original_property; base::string16 original_property;
// The list of line indexes of accessible objects the property is allowed to
// be called for.
std::vector<base::string16> line_indexes;
std::string ToString() const; std::string ToString() const;
private: private:
...@@ -128,7 +133,8 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBase ...@@ -128,7 +133,8 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBase
// Returns a property node struct built for a matching property filter, // Returns a property node struct built for a matching property filter,
// which includes a property name and invocation parameters if any. // which includes a property name and invocation parameters if any.
// If no matching property filter, then empty property node is returned. // If no matching property filter, then empty property node is returned.
PropertyNode GetMatchingPropertyNode(const base::string16& text); PropertyNode GetMatchingPropertyNode(const base::string16& line_index,
const base::string16& property_name);
// Process accessibility tree with filters for output. // Process accessibility tree with filters for output.
// Given a dictionary that contains a platform-specific dictionary // Given a dictionary that contains a platform-specific dictionary
......
...@@ -10,8 +10,6 @@ ...@@ -10,8 +10,6 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_enums.mojom.h"
#include <iostream>
namespace content { namespace content {
class AccessibilityTreeFormatterBaseTest : public testing::Test { class AccessibilityTreeFormatterBaseTest : public testing::Test {
...@@ -33,11 +31,15 @@ class AccessibilityTreeFormatterBaseTest : public testing::Test { ...@@ -33,11 +31,15 @@ class AccessibilityTreeFormatterBaseTest : public testing::Test {
}; };
void ParseAndCheck(const char* input, const char* expected) { void ParseAndCheck(const char* input, const char* expected) {
auto got = PropertyNode::FromProperty(base::UTF8ToUTF16(input)).ToString(); AccessibilityTreeFormatter::PropertyFilter filter(
base::UTF8ToUTF16(input),
AccessibilityTreeFormatter::PropertyFilter::ALLOW);
auto got = PropertyNode::FromPropertyFilter(filter).ToString();
EXPECT_EQ(got, expected); EXPECT_EQ(got, expected);
} }
TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) { TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
// Properties and methods.
ParseAndCheck("Role", "Role"); ParseAndCheck("Role", "Role");
ParseAndCheck("ChildAt(3)", "ChildAt(3)"); ParseAndCheck("ChildAt(3)", "ChildAt(3)");
ParseAndCheck("Cell(3, 4)", "Cell(3, 4)"); ParseAndCheck("Cell(3, 4)", "Cell(3, 4)");
...@@ -48,6 +50,10 @@ TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) { ...@@ -48,6 +50,10 @@ TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
ParseAndCheck("[3, 4]", "[](3, 4)"); ParseAndCheck("[3, 4]", "[](3, 4)");
ParseAndCheck("Cell([3, 4])", "Cell([](3, 4))"); ParseAndCheck("Cell([3, 4])", "Cell([](3, 4))");
// Line indexes filter.
ParseAndCheck(":3,:5;AXDOMClassList", ":3,:5;AXDOMClassList");
// Wrong format.
ParseAndCheck("Role(3", "Role(3)"); ParseAndCheck("Role(3", "Role(3)");
ParseAndCheck("TableFor(CellBy(id", "TableFor(CellBy(id))"); ParseAndCheck("TableFor(CellBy(id", "TableFor(CellBy(id))");
ParseAndCheck("[3, 4", "[](3, 4)"); ParseAndCheck("[3, 4", "[](3, 4)");
......
...@@ -50,9 +50,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) { ...@@ -50,9 +50,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) {
} }
const auto& arraynode = propnode.parameters[0]; const auto& arraynode = propnode.parameters[0];
if (arraynode.value != base::ASCIIToUTF16("[]")) { if (arraynode.name_or_value != base::ASCIIToUTF16("[]")) {
LOG(ERROR) << "Failed to parse " << propnode.original_property LOG(ERROR) << "Failed to parse " << propnode.original_property
<< " to IntArray: " << arraynode.value << " is not array"; << " to IntArray: " << arraynode.name_or_value
<< " is not array";
return nil; return nil;
} }
...@@ -60,9 +61,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) { ...@@ -60,9 +61,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) {
[[NSMutableArray alloc] initWithCapacity:arraynode.parameters.size()]; [[NSMutableArray alloc] initWithCapacity:arraynode.parameters.size()];
for (const auto& paramnode : arraynode.parameters) { for (const auto& paramnode : arraynode.parameters) {
int param = 0; int param = 0;
if (!base::StringToInt(paramnode.value, &param)) { if (!base::StringToInt(paramnode.name_or_value, &param)) {
LOG(ERROR) << "Failed to parse " << propnode.original_property LOG(ERROR) << "Failed to parse " << propnode.original_property
<< " to IntArray: " << paramnode.value << " is not a number"; << " to IntArray: " << paramnode.name_or_value
<< " is not a number";
return nil; return nil;
} }
[array addObject:@(param)]; [array addObject:@(param)];
...@@ -91,7 +93,8 @@ class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBase { ...@@ -91,7 +93,8 @@ class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBase {
const base::StringPiece& pattern) override; const base::StringPiece& pattern) override;
private: private:
using LineIndexesMap = std::map<const gfx::NativeViewAccessible, int>; using LineIndexesMap =
std::map<const gfx::NativeViewAccessible, base::string16>;
void RecursiveBuildAccessibilityTree(const BrowserAccessibilityCocoa* node, void RecursiveBuildAccessibilityTree(const BrowserAccessibilityCocoa* node,
const LineIndexesMap& line_indexes_map, const LineIndexesMap& line_indexes_map,
...@@ -216,7 +219,9 @@ void AccessibilityTreeFormatterMac::RecursiveBuildLineIndexesMap( ...@@ -216,7 +219,9 @@ void AccessibilityTreeFormatterMac::RecursiveBuildLineIndexesMap(
const BrowserAccessibilityCocoa* cocoa_node, const BrowserAccessibilityCocoa* cocoa_node,
LineIndexesMap* line_indexes_map, LineIndexesMap* line_indexes_map,
int* counter) { int* counter) {
line_indexes_map->insert({cocoa_node, ++(*counter)}); const base::string16 line_index =
base::string16(1, ':') + base::NumberToString16(++(*counter));
line_indexes_map->insert({cocoa_node, line_index});
for (BrowserAccessibilityCocoa* cocoa_child in [cocoa_node children]) { for (BrowserAccessibilityCocoa* cocoa_child in [cocoa_node children]) {
RecursiveBuildLineIndexesMap(cocoa_child, line_indexes_map, counter); RecursiveBuildLineIndexesMap(cocoa_child, line_indexes_map, counter);
} }
...@@ -230,10 +235,16 @@ void AccessibilityTreeFormatterMac::AddProperties( ...@@ -230,10 +235,16 @@ void AccessibilityTreeFormatterMac::AddProperties(
BrowserAccessibility* node = [cocoa_node owner]; BrowserAccessibility* node = [cocoa_node owner];
dict->SetKey("id", base::Value(base::NumberToString16(node->GetId()))); dict->SetKey("id", base::Value(base::NumberToString16(node->GetId())));
base::string16 line_index = base::ASCIIToUTF16("-1");
if (line_indexes_map.find(cocoa_node) != line_indexes_map.end()) {
line_index = line_indexes_map.at(cocoa_node);
}
// Attributes // Attributes
for (NSString* supportedAttribute in for (NSString* supportedAttribute in
[cocoa_node accessibilityAttributeNames]) { [cocoa_node accessibilityAttributeNames]) {
if (GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute))) { if (GetMatchingPropertyNode(line_index,
SysNSStringToUTF16(supportedAttribute))) {
id value = [cocoa_node accessibilityAttributeValue:supportedAttribute]; id value = [cocoa_node accessibilityAttributeValue:supportedAttribute];
if (value != nil) { if (value != nil) {
dict->SetPath(SysNSStringToUTF8(supportedAttribute), dict->SetPath(SysNSStringToUTF8(supportedAttribute),
...@@ -246,9 +257,9 @@ void AccessibilityTreeFormatterMac::AddProperties( ...@@ -246,9 +257,9 @@ void AccessibilityTreeFormatterMac::AddProperties(
for (NSString* supportedAttribute in for (NSString* supportedAttribute in
[cocoa_node accessibilityParameterizedAttributeNames]) { [cocoa_node accessibilityParameterizedAttributeNames]) {
id param = nil; id param = nil;
auto propnode = auto propnode = GetMatchingPropertyNode(
GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute)); line_index, SysNSStringToUTF16(supportedAttribute));
if (propnode.value == base::ASCIIToUTF16("AXCellForColumnAndRow")) { if (propnode.name_or_value == base::ASCIIToUTF16("AXCellForColumnAndRow")) {
param = PropNodeToIntArray(propnode); param = PropNodeToIntArray(propnode);
if (param == nil) { if (param == nil) {
dict->SetPath(base::UTF16ToUTF8(propnode.original_property), dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
...@@ -324,11 +335,11 @@ base::Value AccessibilityTreeFormatterMac::PopulateObject( ...@@ -324,11 +335,11 @@ base::Value AccessibilityTreeFormatterMac::PopulateObject(
// Accessible object. // Accessible object.
if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) { if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
int line_index = -1; base::string16 line_index = base::ASCIIToUTF16("-1");
if (line_indexes_map.find(value) != line_indexes_map.end()) { if (line_indexes_map.find(value) != line_indexes_map.end()) {
line_index = line_indexes_map.at(value); line_index = line_indexes_map.at(value);
} }
return base::Value(":" + base::NumberToString(line_index)); return base::Value(line_index);
} }
// Scalar value. // Scalar value.
......
...@@ -92,6 +92,20 @@ IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest, ...@@ -92,6 +92,20 @@ IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
)~~"); )~~");
} }
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
Attributes_LineIndexFilter) {
TestAndCheck(R"~~(data:text/html,
<input class='input_at_3rd_line'>
<input class='input_at_4th_line'>
<input class='input_at_5th_line'>)~~",
{":3,:5;AXDOMClassList=*"}, R"~~(AXWebArea
++AXGroup
++++AXTextField AXDOMClassList=['input_at_3rd_line']
++++AXTextField
++++AXTextField AXDOMClassList=['input_at_5th_line']
)~~");
}
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest, IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
ParameterizedAttributes) { ParameterizedAttributes) {
TestAndCheck(R"~~(data:text/html, TestAndCheck(R"~~(data:text/html,
......
...@@ -56,15 +56,31 @@ class AccessibilityTestExpectationsLocator { ...@@ -56,15 +56,31 @@ class AccessibilityTestExpectationsLocator {
class CONTENT_EXPORT AccessibilityTreeFormatter class CONTENT_EXPORT AccessibilityTreeFormatter
: public AccessibilityTestExpectationsLocator { : public AccessibilityTestExpectationsLocator {
public: public:
// A single property filter specification. See GetAllowString() and // A single property filter specification. Represents a parsed string of the
// GetDenyString() for more information. // filter_str;match_str format, where `filter_str` has
struct PropertyFilter { // :line_num_0,...:line_num_N format, `match_str` has format of
// property_str=value_str. For example, :1,:3;AXDOMClassList=*.
//
// Longer version: `filter_str` is a comma separated list of the line
// indexes from the output accessible tree, and serves to narrow down the
// property calls to the accessible object placed on those line indexes only;
// `match_str` is used to match properties by property name and value.
// For example, :1,:3;AXDOMClassList=*
// will query a AXDOMClassList attribute on accessible objects placed at 1st
// and 3rd lines in the output accessible tree.
// Also see
// DumpAccessibilityTestBase::ParseHtmlForExtraDirectives() for more
// information.
struct CONTENT_EXPORT PropertyFilter {
enum Type { ALLOW, ALLOW_EMPTY, DENY }; enum Type { ALLOW, ALLOW_EMPTY, DENY };
base::string16 match_str; base::string16 match_str;
base::string16 property_str;
base::string16 filter_str;
Type type; Type type;
PropertyFilter(base::string16 match_str, Type type) PropertyFilter(const base::string16& str, Type type);
: match_str(match_str), type(type) {} PropertyFilter(const PropertyFilter&);
}; };
// A single node filter specification which will exclude any node where the // A single node filter specification which will exclude any node where the
......
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