Commit d77baf89 authored by dmazzoni@chromium.org's avatar dmazzoni@chromium.org

Correctly update the bounds of objects in the accessibility tree.

Fixes regression that happened when moving to the new AXTree code.
Improves on it by only sending location changes when there's an
AXLayoutChanged event, since that will always be fired when there's a
layout and objects might have moved.

To make it testable, adds a new feature to DumpAccessibilityTree tests
where you can make it wait until a certain magic string appears in the
dump before the dump is diffed with the expectation. In this test, we
start a CSS transition that animates changing a button's size, and make
sure the final dump reflects the final size.

BUG=268296

Review URL: https://codereview.chromium.org/401643003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285174 0039d316-1c4b-4281-b951-d872f2087c98
parent 8329185f
...@@ -93,8 +93,27 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest { ...@@ -93,8 +93,27 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest {
filters->push_back(Filter(base::ASCIIToUTF16("*=''"), Filter::DENY)); filters->push_back(Filter(base::ASCIIToUTF16("*=''"), Filter::DENY));
} }
void ParseFilters(const std::string& test_html, // Parse the test html file and parse special directives, usually
std::vector<Filter>* filters) { // beginning with an '@' and inside an HTML comment, that control how the
// test is run and how the results are interpreted.
//
// When the accessibility tree is dumped as text, each attribute is
// run through filters before being appended to the string. An "allow"
// filter specifies attribute strings that should be dumped, and a "deny"
// filter specifies strings that should be suppressed. As an example,
// @MAC-ALLOW:AXSubrole=* means that the AXSubrole attribute should be
// printed, while @MAC-ALLOW:AXSubrole=AXList* means that any subrole
// beginning with the text "AXList" should be printed.
//
// The @WAIT-FOR:text directive allows the test to specify that the document
// may dynamically change after initial load, and the test is to wait
// until the given string (e.g., "text") appears in the resulting dump.
// A test can make some changes to the document, then append a magic string
// indicating that the test is done, and this framework will wait for that
// string to appear before comparing the results.
void ParseHtmlForExtraDirectives(const std::string& test_html,
std::vector<Filter>* filters,
std::string* wait_for) {
std::vector<std::string> lines; std::vector<std::string> lines;
base::SplitString(test_html, '\n', &lines); base::SplitString(test_html, '\n', &lines);
for (std::vector<std::string>::const_iterator iter = lines.begin(); for (std::vector<std::string>::const_iterator iter = lines.begin();
...@@ -107,6 +126,7 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest { ...@@ -107,6 +126,7 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest {
AccessibilityTreeFormatter::GetAllowString(); AccessibilityTreeFormatter::GetAllowString();
const std::string& deny_str = const std::string& deny_str =
AccessibilityTreeFormatter::GetDenyString(); AccessibilityTreeFormatter::GetDenyString();
const std::string& wait_str = "@WAIT-FOR:";
if (StartsWithASCII(line, allow_empty_str, true)) { if (StartsWithASCII(line, allow_empty_str, true)) {
filters->push_back( filters->push_back(
Filter(base::UTF8ToUTF16(line.substr(allow_empty_str.size())), Filter(base::UTF8ToUTF16(line.substr(allow_empty_str.size())),
...@@ -119,6 +139,8 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest { ...@@ -119,6 +139,8 @@ class DumpAccessibilityTreeTest : public ContentBrowserTest {
filters->push_back(Filter(base::UTF8ToUTF16( filters->push_back(Filter(base::UTF8ToUTF16(
line.substr(deny_str.size())), line.substr(deny_str.size())),
Filter::DENY)); Filter::DENY));
} else if (StartsWithASCII(line, wait_str, true)) {
*wait_for = line.substr(wait_str.size());
} }
} }
} }
...@@ -170,32 +192,52 @@ void DumpAccessibilityTreeTest::RunTest( ...@@ -170,32 +192,52 @@ void DumpAccessibilityTreeTest::RunTest(
return; return;
} }
// Parse filters and other directives in the test file.
std::vector<Filter> filters;
std::string wait_for;
AddDefaultFilters(&filters);
ParseHtmlForExtraDirectives(html_contents, &filters, &wait_for);
// Load the page. // Load the page.
base::string16 html_contents16; base::string16 html_contents16;
html_contents16 = base::UTF8ToUTF16(html_contents); html_contents16 = base::UTF8ToUTF16(html_contents);
GURL url = GetTestUrl("accessibility", GURL url = GetTestUrl("accessibility",
html_file.BaseName().MaybeAsASCII().c_str()); html_file.BaseName().MaybeAsASCII().c_str());
AccessibilityNotificationWaiter waiter(
shell(), AccessibilityModeComplete, // If there's a @WAIT-FOR directive, set up an accessibility notification
ui::AX_EVENT_LOAD_COMPLETE); // waiter that returns on any event; we'll stop when we get the text we're
// waiting for, or time out. Otherwise just wait specifically for
// the "load complete" event.
scoped_ptr<AccessibilityNotificationWaiter> waiter;
if (!wait_for.empty()) {
waiter.reset(new AccessibilityNotificationWaiter(
shell(), AccessibilityModeComplete, ui::AX_EVENT_NONE));
} else {
waiter.reset(new AccessibilityNotificationWaiter(
shell(), AccessibilityModeComplete, ui::AX_EVENT_LOAD_COMPLETE));
}
// Load the test html.
NavigateToURL(shell(), url); NavigateToURL(shell(), url);
waiter.WaitForNotification();
// Wait for notifications. If there's a @WAIT-FOR directive, break when
// the text we're waiting for appears in the dump, otherwise break after
// the first notification, which will be a load complete.
RenderWidgetHostViewBase* host_view = static_cast<RenderWidgetHostViewBase*>( RenderWidgetHostViewBase* host_view = static_cast<RenderWidgetHostViewBase*>(
shell()->web_contents()->GetRenderWidgetHostView()); shell()->web_contents()->GetRenderWidgetHostView());
AccessibilityTreeFormatter formatter( std::string actual_contents;
host_view->GetBrowserAccessibilityManager()->GetRoot()); do {
waiter->WaitForNotification();
// Parse filters in the test file. base::string16 actual_contents_utf16;
std::vector<Filter> filters; AccessibilityTreeFormatter formatter(
AddDefaultFilters(&filters); host_view->GetBrowserAccessibilityManager()->GetRoot());
ParseFilters(html_contents, &filters); formatter.SetFilters(filters);
formatter.SetFilters(filters); formatter.FormatAccessibilityTree(&actual_contents_utf16);
actual_contents = base::UTF16ToUTF8(actual_contents_utf16);
} while (!wait_for.empty() &&
actual_contents.find(wait_for) == std::string::npos);
// Perform a diff (or write the initial baseline). // Perform a diff (or write the initial baseline).
base::string16 actual_contents_utf16;
formatter.FormatAccessibilityTree(&actual_contents_utf16);
std::string actual_contents = base::UTF16ToUTF8(actual_contents_utf16);
std::vector<std::string> actual_lines, expected_lines; std::vector<std::string> actual_lines, expected_lines;
Tokenize(actual_contents, "\n", &actual_lines); Tokenize(actual_contents, "\n", &actual_lines);
Tokenize(expected_contents, "\n", &expected_lines); Tokenize(expected_contents, "\n", &expected_lines);
...@@ -530,6 +572,10 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSpans) { ...@@ -530,6 +572,10 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSpans) {
RunTest(FILE_PATH_LITERAL("table-spans.html")); RunTest(FILE_PATH_LITERAL("table-spans.html"));
} }
IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTransition) {
RunTest(FILE_PATH_LITERAL("transition.html"));
}
IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
AccessibilityToggleButton) { AccessibilityToggleButton) {
RunTest(FILE_PATH_LITERAL("togglebutton.html")); RunTest(FILE_PATH_LITERAL("togglebutton.html"));
......
...@@ -177,16 +177,21 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() { ...@@ -177,16 +177,21 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
// Generate an event message from each Blink event. // Generate an event message from each Blink event.
std::vector<AccessibilityHostMsg_EventParams> event_msgs; std::vector<AccessibilityHostMsg_EventParams> event_msgs;
// If there's a layout complete message, we need to send location changes.
bool had_layout_complete_messages = false;
// Loop over each event and generate an updated event message. // Loop over each event and generate an updated event message.
for (size_t i = 0; i < src_events.size(); ++i) { for (size_t i = 0; i < src_events.size(); ++i) {
AccessibilityHostMsg_EventParams& event = AccessibilityHostMsg_EventParams& event = src_events[i];
src_events[i]; if (event.event_type == ui::AX_EVENT_LAYOUT_COMPLETE)
had_layout_complete_messages = true;
WebAXObject obj = document.accessibilityObjectFromID(event.id);
WebAXObject obj = document.accessibilityObjectFromID(
event.id);
// Make sure the object still exists. // Make sure the object still exists.
if (!obj.updateBackingStoreAndCheckValidity()) if (!obj.updateBackingStoreAndCheckValidity())
continue; continue;
// Make sure it's a descendant of our root node - exceptions include the // Make sure it's a descendant of our root node - exceptions include the
// scroll area that's the parent of the main document (we ignore it), and // scroll area that's the parent of the main document (we ignore it), and
// possibly nodes attached to a different document. // possibly nodes attached to a different document.
...@@ -207,6 +212,13 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() { ...@@ -207,6 +212,13 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
serializer_.SerializeChanges(obj, &event_msg.update); serializer_.SerializeChanges(obj, &event_msg.update);
event_msgs.push_back(event_msg); event_msgs.push_back(event_msg);
// For each node in the update, set the location in our map from
// ids to locations.
for (size_t i = 0; i < event_msg.update.nodes.size(); ++i) {
locations_[event_msg.update.nodes[i].id] =
event_msg.update.nodes[i].location;
}
VLOG(0) << "Accessibility event: " << ui::ToString(event.event_type) VLOG(0) << "Accessibility event: " << ui::ToString(event.event_type)
<< " on node id " << event_msg.id << " on node id " << event_msg.id
<< "\n" << event_msg.update.ToString(); << "\n" << event_msg.update.ToString();
...@@ -214,7 +226,8 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() { ...@@ -214,7 +226,8 @@ void RendererAccessibilityComplete::SendPendingAccessibilityEvents() {
Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs)); Send(new AccessibilityHostMsg_Events(routing_id(), event_msgs));
SendLocationChanges(); if (had_layout_complete_messages)
SendLocationChanges();
} }
void RendererAccessibilityComplete::SendLocationChanges() { void RendererAccessibilityComplete::SendLocationChanges() {
...@@ -246,6 +259,12 @@ void RendererAccessibilityComplete::SendLocationChanges() { ...@@ -246,6 +259,12 @@ void RendererAccessibilityComplete::SendLocationChanges() {
// Save the new location. // Save the new location.
new_locations[id] = new_location; new_locations[id] = new_location;
// Explore children of this object.
std::vector<blink::WebAXObject> children;
tree_source_.GetChildren(obj, &children);
for (size_t i = 0; i < children.size(); ++i)
objs_to_explore.push(children[i]);
} }
locations_.swap(new_locations); locations_.swap(new_locations);
......
...@@ -56,6 +56,10 @@ AccessibilityNotificationWaiter::~AccessibilityNotificationWaiter() { ...@@ -56,6 +56,10 @@ AccessibilityNotificationWaiter::~AccessibilityNotificationWaiter() {
void AccessibilityNotificationWaiter::WaitForNotification() { void AccessibilityNotificationWaiter::WaitForNotification() {
loop_runner_->Run(); loop_runner_->Run();
// Each loop runner can only be called once. Create a new one in case
// the caller wants to call this again to wait for the next notification.
loop_runner_ = new MessageLoopRunner();
} }
const ui::AXTree& AccessibilityNotificationWaiter::GetAXTree() const { const ui::AXTree& AccessibilityNotificationWaiter::GetAXTree() const {
...@@ -64,7 +68,7 @@ const ui::AXTree& AccessibilityNotificationWaiter::GetAXTree() const { ...@@ -64,7 +68,7 @@ const ui::AXTree& AccessibilityNotificationWaiter::GetAXTree() const {
void AccessibilityNotificationWaiter::OnAccessibilityEvent( void AccessibilityNotificationWaiter::OnAccessibilityEvent(
ui::AXEvent event_type, int event_target_id) { ui::AXEvent event_type, int event_target_id) {
if (!IsAboutBlank() && (event_to_wait_for_ == ui::AX_EVENT_NONE || if (!IsAboutBlank() && (event_to_wait_for_ == ui::AX_EVENT_NONE ||
event_to_wait_for_ == event_type)) { event_to_wait_for_ == event_type)) {
event_target_id_ = event_target_id; event_target_id_ = event_target_id;
loop_runner_->Quit(); loop_runner_->Quit();
......
...@@ -24,7 +24,6 @@ class Shell; ...@@ -24,7 +24,6 @@ class Shell;
class AccessibilityNotificationWaiter { class AccessibilityNotificationWaiter {
public: public:
explicit AccessibilityNotificationWaiter(Shell* shell); explicit AccessibilityNotificationWaiter(Shell* shell);
AccessibilityNotificationWaiter( AccessibilityNotificationWaiter(
Shell* shell, Shell* shell,
AccessibilityMode accessibility_mode, AccessibilityMode accessibility_mode,
......
#<skip - Android doesn't output the location and size of objects.>
android.webkit.WebView focusable scrollable
android.view.View
android.widget.Button clickable focusable focused name='GrowButton'
android.view.View clickable name='Done'
AXWebArea
AXGroup
AXButton AXTitle='GrowButton' size=(600, 300)
AXStaticText AXValue='Done'
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
IA2_ROLE_SECTION READONLY
ROLE_SYSTEM_PUSHBUTTON name='GrowButton' FOCUSABLE size=(600, 300)
ROLE_SYSTEM_STATICTEXT name='Done'
<!doctype html>
<!--
This tests that location changes are sent when an element animates
using CSS transitions. The test animates the size of a button when
focused, then adds the magic text "Done" to the document when
the transition finishes. The WAIT-FOR directive below instructs
the test framework to keep waiting for accessibility events and
not diff the dump against the expectations until the text "Done"
appears in the dump.
@MAC-ALLOW:size=(400, 200)
@MAC-ALLOW:size=(600, 300)
@WIN-ALLOW:size=(400, 200)
@WIN-ALLOW:size=(600, 300)
@WAIT-FOR:Done
-->
<html>
<head>
<style>
body {
width: 800px;
height: 600px;
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
}
#growbutton {
width: 400px;
height: 200px;
margin: 0;
padding: 0;
}
#growbutton:focus {
width: 600px;
height: 300px;
transition: all 0.1s ease-in-out;
}
</style>
</head>
<body>
<button id="growbutton">GrowButton</button>
<script>
var growButton = document.getElementById('growbutton');
var done = false;
growButton.addEventListener('webkitTransitionEnd', function() {
if (!done) {
document.body.appendChild(document.createTextNode('Done'));
done = true;
}
}, false);
growButton.focus();
</script>
</body>
</html>
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