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";
//
// 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;
Parse(&root, property.begin(), property.end());
Parse(&root, filter.property_str.begin(), filter.property_str.end());
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);
}
PropertyNode::PropertyNode() = default;
PropertyNode::PropertyNode(PropertyNode&& o)
: value(std::move(o.value)),
: name_or_value(std::move(o.name_or_value)),
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::operator=(PropertyNode&& o) {
value = std::move(o.value);
name_or_value = std::move(o.name_or_value);
parameters = std::move(o.parameters);
original_property = std::move(o.original_property);
line_indexes = std::move(o.line_indexes);
return *this;
}
PropertyNode::operator bool() const {
return !value.empty();
return !name_or_value.empty();
}
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()) {
out += '(';
for (size_t i = 0; i < parameters.size(); i++) {
......@@ -81,10 +108,11 @@ std::string PropertyNode::ToString() const {
}
// 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::iterator end)
: value(begin, end) {}
: name_or_value(begin, end) {}
// private static
PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
......@@ -139,7 +167,26 @@ PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
return iter;
}
//
// 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(
size_t index) {
......@@ -299,24 +346,25 @@ AccessibilityTreeFormatterBase::GetVersionSpecificExpectedFileSuffix() {
}
PropertyNode AccessibilityTreeFormatterBase::GetMatchingPropertyNode(
const base::string16& text) {
// Find the first allow-filter matching the property name.
// The filters have form of name(args)=value. Here we match the name part.
const base::string16 filter_delim = base::ASCIIToUTF16("=");
const base::string16& line_index,
const base::string16& property_name) {
// Find the first allow-filter matching the line index and the property name.
for (const auto& filter : property_filters_) {
base::String16Tokenizer filter_tokenizer(filter.match_str, filter_delim);
if (!filter_tokenizer.GetNext()) {
PropertyNode property_node = PropertyNode::FromPropertyFilter(filter);
// 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;
}
base::string16 property = filter_tokenizer.token();
PropertyNode property_node = PropertyNode::FromProperty(property);
// The filter should be either an exact property match or a wildcard
// matching to support filter collections like AXRole* which matches
// AXRoleDescription.
if (text == property_node.value ||
base::MatchPattern(text, property_node.value)) {
if (property_name == property_node.name_or_value ||
base::MatchPattern(property_name, property_node.name_or_value)) {
switch (filter.type) {
case PropertyFilter::ALLOW_EMPTY:
case PropertyFilter::ALLOW:
......
......@@ -37,7 +37,8 @@ namespace content {
class CONTENT_EXPORT PropertyNode final {
public:
// Parses a property node from a string.
static PropertyNode FromProperty(const base::string16&);
static PropertyNode FromPropertyFilter(
const AccessibilityTreeFormatter::PropertyFilter& filter);
PropertyNode();
PropertyNode(PropertyNode&&);
......@@ -46,13 +47,17 @@ class CONTENT_EXPORT PropertyNode final {
PropertyNode& operator=(PropertyNode&& other);
explicit operator bool() const;
base::string16 value;
base::string16 name_or_value;
std::vector<PropertyNode> parameters;
// Used to store the origianl unparsed property including invocation
// parameters if any.
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;
private:
......@@ -128,7 +133,8 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBase
// Returns a property node struct built for a matching property filter,
// which includes a property name and invocation parameters if any.
// 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.
// Given a dictionary that contains a platform-specific dictionary
......
......@@ -10,8 +10,6 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include <iostream>
namespace content {
class AccessibilityTreeFormatterBaseTest : public testing::Test {
......@@ -33,11 +31,15 @@ class AccessibilityTreeFormatterBaseTest : public testing::Test {
};
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);
}
TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
// Properties and methods.
ParseAndCheck("Role", "Role");
ParseAndCheck("ChildAt(3)", "ChildAt(3)");
ParseAndCheck("Cell(3, 4)", "Cell(3, 4)");
......@@ -48,6 +50,10 @@ TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
ParseAndCheck("[3, 4]", "[](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("TableFor(CellBy(id", "TableFor(CellBy(id))");
ParseAndCheck("[3, 4", "[](3, 4)");
......
......@@ -50,9 +50,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) {
}
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
<< " to IntArray: " << arraynode.value << " is not array";
<< " to IntArray: " << arraynode.name_or_value
<< " is not array";
return nil;
}
......@@ -60,9 +61,10 @@ NSArray* PropNodeToIntArray(const PropertyNode& propnode) {
[[NSMutableArray alloc] initWithCapacity:arraynode.parameters.size()];
for (const auto& paramnode : arraynode.parameters) {
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
<< " to IntArray: " << paramnode.value << " is not a number";
<< " to IntArray: " << paramnode.name_or_value
<< " is not a number";
return nil;
}
[array addObject:@(param)];
......@@ -91,7 +93,8 @@ class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBase {
const base::StringPiece& pattern) override;
private:
using LineIndexesMap = std::map<const gfx::NativeViewAccessible, int>;
using LineIndexesMap =
std::map<const gfx::NativeViewAccessible, base::string16>;
void RecursiveBuildAccessibilityTree(const BrowserAccessibilityCocoa* node,
const LineIndexesMap& line_indexes_map,
......@@ -216,7 +219,9 @@ void AccessibilityTreeFormatterMac::RecursiveBuildLineIndexesMap(
const BrowserAccessibilityCocoa* cocoa_node,
LineIndexesMap* line_indexes_map,
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]) {
RecursiveBuildLineIndexesMap(cocoa_child, line_indexes_map, counter);
}
......@@ -230,10 +235,16 @@ void AccessibilityTreeFormatterMac::AddProperties(
BrowserAccessibility* node = [cocoa_node owner];
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
for (NSString* supportedAttribute in
[cocoa_node accessibilityAttributeNames]) {
if (GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute))) {
if (GetMatchingPropertyNode(line_index,
SysNSStringToUTF16(supportedAttribute))) {
id value = [cocoa_node accessibilityAttributeValue:supportedAttribute];
if (value != nil) {
dict->SetPath(SysNSStringToUTF8(supportedAttribute),
......@@ -246,9 +257,9 @@ void AccessibilityTreeFormatterMac::AddProperties(
for (NSString* supportedAttribute in
[cocoa_node accessibilityParameterizedAttributeNames]) {
id param = nil;
auto propnode =
GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute));
if (propnode.value == base::ASCIIToUTF16("AXCellForColumnAndRow")) {
auto propnode = GetMatchingPropertyNode(
line_index, SysNSStringToUTF16(supportedAttribute));
if (propnode.name_or_value == base::ASCIIToUTF16("AXCellForColumnAndRow")) {
param = PropNodeToIntArray(propnode);
if (param == nil) {
dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
......@@ -324,11 +335,11 @@ base::Value AccessibilityTreeFormatterMac::PopulateObject(
// Accessible object.
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()) {
line_index = line_indexes_map.at(value);
}
return base::Value(":" + base::NumberToString(line_index));
return base::Value(line_index);
}
// Scalar value.
......
......@@ -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,
ParameterizedAttributes) {
TestAndCheck(R"~~(data:text/html,
......
......@@ -56,15 +56,31 @@ class AccessibilityTestExpectationsLocator {
class CONTENT_EXPORT AccessibilityTreeFormatter
: public AccessibilityTestExpectationsLocator {
public:
// A single property filter specification. See GetAllowString() and
// GetDenyString() for more information.
struct PropertyFilter {
// A single property filter specification. Represents a parsed string of the
// filter_str;match_str format, where `filter_str` has
// :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 };
base::string16 match_str;
base::string16 property_str;
base::string16 filter_str;
Type type;
PropertyFilter(base::string16 match_str, Type type)
: match_str(match_str), type(type) {}
PropertyFilter(const base::string16& str, Type type);
PropertyFilter(const PropertyFilter&);
};
// 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