Commit 74c69531 authored by Alexander Surkov's avatar Alexander Surkov Committed by Commit Bot

DumpAccTree testing: prototype parameterized attributes call on Mac.

Extend property filter format to name(args)=value to specify arguments
for a method call.

AX-Relnotes: n/a
Bug: 1076955
Change-Id: I0c862b257808368e93c41e61a9b5525d35bb80d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2190175
Commit-Queue: Alexander Surkov <asurkov@igalia.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#771366}
parent c6d15649
......@@ -8,6 +8,7 @@
#include <memory>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/strings/pattern.h"
......@@ -32,6 +33,114 @@ const char kSkipChildren[] = "@NO_CHILDREN_DUMP";
} // namespace
//
// PropertyNode
//
// static
PropertyNode PropertyNode::FromProperty(const base::string16& property) {
PropertyNode root;
Parse(&root, property.begin(), property.end());
PropertyNode* node = &root.parameters[0];
node->original_property = property;
return std::move(*node);
}
PropertyNode::PropertyNode() = default;
PropertyNode::PropertyNode(PropertyNode&& o)
: value(std::move(o.value)),
parameters(std::move(o.parameters)),
original_property(std::move(o.original_property)) {}
PropertyNode::~PropertyNode() = default;
PropertyNode& PropertyNode::operator=(PropertyNode&& o) {
value = std::move(o.value);
parameters = std::move(o.parameters);
original_property = std::move(o.original_property);
return *this;
}
PropertyNode::operator bool() const {
return !value.empty();
}
std::string PropertyNode::ToString() const {
std::string out = base::UTF16ToUTF8(value);
if (parameters.size()) {
out += '(';
for (size_t i = 0; i < parameters.size(); i++) {
if (i != 0) {
out += ", ";
}
out += parameters[i].ToString();
}
out += ')';
}
return out;
}
// private
PropertyNode::PropertyNode(const base::string16& v) : value(v) {}
PropertyNode::PropertyNode(PropertyNode::iterator begin,
PropertyNode::iterator end)
: value(begin, end) {}
// private static
PropertyNode::iterator PropertyNode::Parse(PropertyNode* node,
PropertyNode::iterator begin,
PropertyNode::iterator end) {
auto iter = begin;
while (iter != end) {
// Subnode begins: create a new node, record its name and parse its
// arguments.
if (*iter == '(') {
node->parameters.push_back(PropertyNode(begin, iter));
begin = iter = Parse(&node->parameters.back(), ++iter, end);
continue;
}
// Subnode begins: a special case for arrays, which have [arg1, ..., argN]
// form.
if (*iter == '[') {
node->parameters.push_back(PropertyNode(base::UTF8ToUTF16("[]")));
begin = iter = Parse(&node->parameters.back(), ++iter, end);
continue;
}
// Subnode ends.
if (*iter == ')' || *iter == ']') {
if (begin != iter) {
node->parameters.push_back(PropertyNode(begin, iter));
}
return ++iter;
}
// Skip spaces, adjust new node start.
if (*iter == ' ') {
begin = ++iter;
}
// Subsequent scalar param case.
if (*iter == ',' && begin != iter) {
node->parameters.push_back(PropertyNode(begin, iter));
iter++;
begin = iter;
continue;
}
iter++;
}
// Single scalar param case.
if (begin != iter) {
node->parameters.push_back(PropertyNode(begin, iter));
}
return iter;
}
// AccessibilityTreeFormatter
AccessibilityTreeFormatter::TestPass AccessibilityTreeFormatter::GetTestPass(
size_t index) {
std::vector<content::AccessibilityTreeFormatter::TestPass> passes =
......@@ -189,26 +298,37 @@ AccessibilityTreeFormatterBase::GetVersionSpecificExpectedFileSuffix() {
return FILE_PATH_LITERAL("");
}
bool AccessibilityTreeFormatterBase::FilterPropertyName(
PropertyNode AccessibilityTreeFormatterBase::GetMatchingPropertyNode(
const base::string16& text) {
// Find the first allow-filter matching the property name. The filter should
// be either an exact property match or a wildcard matching to support filter
// collections like AXRole* which matches AXRoleDescription.
const base::string16 delim = base::ASCIIToUTF16("=");
// 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("=");
for (const auto& filter : property_filters_) {
base::String16Tokenizer tokenizer(filter.match_str, delim);
if (tokenizer.GetNext() && (text == tokenizer.token() ||
base::MatchPattern(text, tokenizer.token()))) {
base::String16Tokenizer filter_tokenizer(filter.match_str, filter_delim);
if (!filter_tokenizer.GetNext()) {
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)) {
switch (filter.type) {
case PropertyFilter::ALLOW_EMPTY:
case PropertyFilter::ALLOW:
return true;
return property_node;
case PropertyFilter::DENY:
break;
default:
break;
}
}
}
return false;
return PropertyNode();
}
bool AccessibilityTreeFormatterBase::MatchesPropertyFilters(
......@@ -282,4 +402,5 @@ void AccessibilityTreeFormatterBase::AddPropertyFilter(
void AccessibilityTreeFormatterBase::AddDefaultFilters(
std::vector<PropertyFilter>* property_filters) {}
} // namespace content
......@@ -27,6 +27,45 @@ const char kChildrenDictAttr[] = "children";
namespace content {
// Property node is a tree-like structure, representing a property or collection
// of properties and its invocation parameters. A collection of properties is
// specified by putting a wildcard into a property name, for exampe, AXRole*
// will match both AXRole and AXRoleDescription properties. Parameters of a
// property are given in parentheses like a conventional function call, for
// example, AXCellForColumnAndRow([0, 0]) will call AXCellForColumnAndRow
// parameterized property for column/row 0 indexes.
class CONTENT_EXPORT PropertyNode final {
public:
// Parses a property node from a string.
static PropertyNode FromProperty(const base::string16&);
PropertyNode();
PropertyNode(PropertyNode&&);
~PropertyNode();
PropertyNode& operator=(PropertyNode&& other);
explicit operator bool() const;
base::string16 value;
std::vector<PropertyNode> parameters;
// Used to store the origianl unparsed property including invocation
// parameters if any.
base::string16 original_property;
std::string ToString() const;
private:
using iterator = base::string16::const_iterator;
explicit PropertyNode(const base::string16&);
PropertyNode(iterator begin, iterator end);
// Builds a property node struct for a string of NAME(ARG1, ..., ARGN) format,
// where each ARG is a scalar value or a string of the same format.
static iterator Parse(PropertyNode* node, iterator begin, iterator end);
};
// A utility class for formatting platform-specific accessibility information,
// for use in testing, debugging, and developer tools.
// This is extended by a subclass for each platform where accessibility is
......@@ -86,8 +125,10 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBase
// Overridden by platform subclasses.
//
// Returns true if the property name matches a property filter.
bool FilterPropertyName(const base::string16& text);
// 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);
// Process accessibility tree with filters for output.
// Given a dictionary that contains a platform-specific dictionary
......
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/accessibility/accessibility_tree_formatter_base.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/test_browser_accessibility_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include <iostream>
namespace content {
class AccessibilityTreeFormatterBaseTest : public testing::Test {
public:
AccessibilityTreeFormatterBaseTest() = default;
~AccessibilityTreeFormatterBaseTest() override = default;
protected:
std::unique_ptr<TestBrowserAccessibilityDelegate>
test_browser_accessibility_delegate_;
private:
void SetUp() override {
test_browser_accessibility_delegate_ =
std::make_unique<TestBrowserAccessibilityDelegate>();
}
DISALLOW_COPY_AND_ASSIGN(AccessibilityTreeFormatterBaseTest);
};
void ParseAndCheck(const char* input, const char* expected) {
auto got = PropertyNode::FromProperty(base::UTF8ToUTF16(input)).ToString();
EXPECT_EQ(got, expected);
}
TEST_F(AccessibilityTreeFormatterBaseTest, ParseProperty) {
ParseAndCheck("Role", "Role");
ParseAndCheck("ChildAt(3)", "ChildAt(3)");
ParseAndCheck("Cell(3, 4)", "Cell(3, 4)");
ParseAndCheck("Volume(3, 4, 5)", "Volume(3, 4, 5)");
ParseAndCheck("TableFor(CellBy(id))", "TableFor(CellBy(id))");
ParseAndCheck("A(B(1), 2)", "A(B(1), 2)");
ParseAndCheck("A(B(1), 2, C(3, 4))", "A(B(1), 2, C(3, 4))");
ParseAndCheck("[3, 4]", "[](3, 4)");
ParseAndCheck("Cell([3, 4])", "Cell([](3, 4))");
ParseAndCheck("Role(3", "Role(3)");
ParseAndCheck("TableFor(CellBy(id", "TableFor(CellBy(id))");
ParseAndCheck("[3, 4", "[](3, 4)");
}
} // namespace content
......@@ -42,6 +42,34 @@ const char kHeightDictAttr[] = "height";
const char kRangeLocDictAttr[] = "loc";
const char kRangeLenDictAttr[] = "len";
NSArray* PropNodeToIntArray(const PropertyNode& propnode) {
if (propnode.parameters.size() != 1) {
LOG(ERROR) << "Failed to parse " << propnode.original_property
<< " to IntArray: single argument is expected";
return nil;
}
const auto& arraynode = propnode.parameters[0];
if (arraynode.value != base::ASCIIToUTF16("[]")) {
LOG(ERROR) << "Failed to parse " << propnode.original_property
<< " to IntArray: " << arraynode.value << " is not array";
return nil;
}
NSMutableArray* array =
[[NSMutableArray alloc] initWithCapacity:arraynode.parameters.size()];
for (const auto& paramnode : arraynode.parameters) {
int param = 0;
if (!base::StringToInt(paramnode.value, &param)) {
LOG(ERROR) << "Failed to parse " << propnode.original_property
<< " to IntArray: " << paramnode.value << " is not a number";
return nil;
}
[array addObject:@(param)];
}
return array;
}
} // namespace
class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBase {
......@@ -202,9 +230,10 @@ void AccessibilityTreeFormatterMac::AddProperties(
BrowserAccessibility* node = [cocoa_node owner];
dict->SetKey("id", base::Value(base::NumberToString16(node->GetId())));
// Attributes
for (NSString* supportedAttribute in
[cocoa_node accessibilityAttributeNames]) {
if (FilterPropertyName(SysNSStringToUTF16(supportedAttribute))) {
if (GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute))) {
id value = [cocoa_node accessibilityAttributeValue:supportedAttribute];
if (value != nil) {
dict->SetPath(SysNSStringToUTF8(supportedAttribute),
......@@ -212,6 +241,31 @@ void AccessibilityTreeFormatterMac::AddProperties(
}
}
}
// Parameterized attributes
for (NSString* supportedAttribute in
[cocoa_node accessibilityParameterizedAttributeNames]) {
id param = nil;
auto propnode =
GetMatchingPropertyNode(SysNSStringToUTF16(supportedAttribute));
if (propnode.value == base::ASCIIToUTF16("AXCellForColumnAndRow")) {
param = PropNodeToIntArray(propnode);
if (param == nil) {
dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
base::Value("ERROR:FAILED_TO_PARSE_ARGS"));
continue;
}
}
if (param != nil) {
id value = [cocoa_node accessibilityAttributeValue:supportedAttribute
forParameter:param];
dict->SetPath(base::UTF16ToUTF8(propnode.original_property),
PopulateObject(value, line_indexes_map));
}
}
// Position and size
dict->SetPath(kPositionDictAttr, PopulatePosition(cocoa_node));
dict->SetPath(kSizeDictAttr, PopulateSize(cocoa_node));
}
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/accessibility/accessibility_tree_formatter_base.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/accessibility_notification_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/base/data_url.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "url/gurl.h"
namespace content {
namespace {
class AccessibilityTreeFormatterMacBrowserTest : public ContentBrowserTest {
public:
AccessibilityTreeFormatterMacBrowserTest() {}
~AccessibilityTreeFormatterMacBrowserTest() override {}
// Checks the formatted accessible tree for the given data URL.
void TestAndCheck(const char* url,
const std::vector<const char*>& filters,
const char* expected) const;
protected:
BrowserAccessibilityManager* GetManager() const {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
return web_contents->GetRootBrowserAccessibilityManager();
}
};
void AccessibilityTreeFormatterMacBrowserTest::TestAndCheck(
const char* url,
const std::vector<const char*>& filters,
const char* expected) const {
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kLoadComplete);
EXPECT_TRUE(NavigateToURL(shell(), GURL(url)));
waiter.WaitForNotification();
// Set property filters
std::unique_ptr<AccessibilityTreeFormatter> formatter =
AccessibilityTreeFormatter::Create();
std::vector<AccessibilityTreeFormatter::PropertyFilter> property_filters;
for (const char* filter : filters) {
property_filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
base::UTF8ToUTF16(filter),
AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY));
}
formatter->AddDefaultFilters(&property_filters);
formatter->SetPropertyFilters(property_filters);
// Format the tree
BrowserAccessibility* root = GetManager()->GetRoot();
CHECK(root);
base::string16 contents;
formatter->FormatAccessibilityTreeForTesting(root, &contents);
auto got = base::UTF16ToUTF8(contents);
EXPECT_EQ(got, expected);
}
} // namespace
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
DefaultAttributes) {
TestAndCheck(R"~~(data:text/html,
<input aria-label='input'>)~~",
{},
R"~~(AXWebArea
++AXGroup
++++AXTextField AXDescription='input'
)~~");
}
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
ParameterizedAttributes) {
TestAndCheck(R"~~(data:text/html,
<table role="grid"><tr><td>CELL</td></tr></table>)~~",
{"AXCellForColumnAndRow([0, 0])=*"}, R"~~(AXWebArea
++AXTable AXCellForColumnAndRow([0, 0])=:4
++++AXRow
++++++AXCell
++++++++AXStaticText AXValue='CELL'
++++AXColumn
++++++AXCell
++++++++AXStaticText AXValue='CELL'
++++AXGroup
)~~");
}
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
ParameterizedAttributes_WrongArgs) {
TestAndCheck(R"~~(data:text/html,
<table role="grid"><tr><td>CELL</td></tr></table>)~~",
{"AXCellForColumnAndRow(0, 0)=*"}, R"~~(AXWebArea
++AXTable AXCellForColumnAndRow(0, 0)='ERROR:FAILED_TO_PARSE_ARGS'
++++AXRow
++++++AXCell
++++++++AXStaticText AXValue='CELL'
++++AXColumn
++++++AXCell
++++++++AXStaticText AXValue='CELL'
++++AXGroup
)~~");
}
IN_PROC_BROWSER_TEST_F(AccessibilityTreeFormatterMacBrowserTest,
ParameterizedAttributes_NilValue) {
TestAndCheck(R"~~(data:text/html,
<table role="grid"></table>)~~",
{"AXCellForColumnAndRow([0, 0])=*"}, R"~~(AXWebArea
++AXTable AXCellForColumnAndRow([0, 0])='(null)'
++++AXGroup
)~~");
}
} // namespace content
......@@ -1465,6 +1465,7 @@ test("content_browsertests") {
if (is_mac) {
sources += [
"../browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm",
"../browser/accessibility/browser_accessibility_cocoa_browsertest.mm",
]
deps += [
......@@ -1584,6 +1585,7 @@ test("content_unittests") {
defines = []
sources = [
"../browser/accessibility/accessibility_tree_formatter_base_unittest.cc",
"../browser/accessibility/browser_accessibility_mac_unittest.mm",
"../browser/accessibility/browser_accessibility_manager_unittest.cc",
"../browser/accessibility/browser_accessibility_manager_win_unittest.cc",
......
AXWebArea
++AXTable AXARIAColumnCount='5'
++AXTable AXARIAColumnCount='5' AXCellForColumnAndRow([0, 0])=:4
++++AXRow
++++++AXCell AXARIAColumnIndex='2'
++++++++AXStaticText AXValue='cell 2'
......
......@@ -3,6 +3,7 @@
@WIN-ALLOW:colindex*
@WIN-ALLOW:colspan*
@MAC-ALLOW:AXARIAColumn*
@MAC-ALLOW:AXCellForColumnAndRow([0, 0])=*
@BLINK-ALLOW:ariaColumn*
@BLINK-ALLOW:ariaCellColumn*
@AURALINUX-ALLOW:colspan*
......
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