Commit 8a54c74b authored by Aaron Leventhal's avatar Aaron Leventhal Committed by Commit Bot

ax_dump_tree

Standalone executable tool that helps developers view IAccessible2 object trees from any application.
This tool is an alternative to AccProbe, which is buggy, and AViewer, which is not feature rich.
The first version of ax_dump_tree is barebones and simply displays events on console, but has
a lot of room for future expansion (e.g., serve info to a GUI, dump the tree structure etc.).
The tool is similar is use to ax_dump_events.

As an example of how this makes easier, when there is a bug with NVDA/JAWS in Chrome that doesn't
occur with Firefox, we can look at the object tree emitted by Firefox for the same content.

The ax_dump_tree tool shares code and formatting with the DumpAccessibilityTree browsertests.

Use as follows:
ax_dump_tree --pid=[processid]
ax_dump_tree --window=[hwnd]   (start with 0x to use hex value)

TBR=nektar,dpranke

Bug: 771747
Change-Id: Idfc17ca1a1d44006ad32dcca07303ce1d93f7669
Reviewed-on: https://chromium-review.googlesource.com/724239Reviewed-by: default avatarAaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarDirk Pranke <dpranke@chromium.org>
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509597}
parent 8a79c9bb
...@@ -392,8 +392,10 @@ group("gn_all") { ...@@ -392,8 +392,10 @@ group("gn_all") {
] ]
} }
if (is_win || is_mac) { # Accessibility API debugging tools.
if (is_win) {
deps += [ "//tools/accessibility/inspect:ax_dump_events" ] deps += [ "//tools/accessibility/inspect:ax_dump_events" ]
deps += [ "//tools/accessibility/inspect:ax_dump_tree" ]
} }
if (is_linux) { if (is_linux) {
......
...@@ -109,6 +109,8 @@ class AccessibilityEventRecorderWin : public AccessibilityEventRecorder { ...@@ -109,6 +109,8 @@ class AccessibilityEventRecorderWin : public AccessibilityEventRecorder {
HWINEVENTHOOK win_event_hook_handle_; HWINEVENTHOOK win_event_hook_handle_;
static AccessibilityEventRecorderWin* instance_; static AccessibilityEventRecorderWin* instance_;
// Initializes COM services when standalone dump events tool is used.
base::win::ScopedCOMInitializer com_initializer; base::win::ScopedCOMInitializer com_initializer;
}; };
......
...@@ -45,6 +45,12 @@ void AccessibilityTreeFormatter::FormatAccessibilityTree( ...@@ -45,6 +45,12 @@ void AccessibilityTreeFormatter::FormatAccessibilityTree(
RecursiveFormatAccessibilityTree(*(dict.get()), contents); RecursiveFormatAccessibilityTree(*(dict.get()), contents);
} }
void AccessibilityTreeFormatter::FormatAccessibilityTree(
const base::DictionaryValue& dict,
base::string16* contents) {
RecursiveFormatAccessibilityTree(dict, contents);
}
void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree( void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree(
const base::DictionaryValue& dict, base::string16* contents, int depth) { const base::DictionaryValue& dict, base::string16* contents, int depth) {
base::string16 indent = base::string16(depth * kIndentSymbolCount, base::string16 indent = base::string16(depth * kIndentSymbolCount,
...@@ -64,7 +70,8 @@ void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree( ...@@ -64,7 +70,8 @@ void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree(
return; return;
const base::ListValue* children; const base::ListValue* children;
dict.GetList(kChildrenDictAttr, &children); if (!dict.GetList(kChildrenDictAttr, &children))
return;
const base::DictionaryValue* child_dict; const base::DictionaryValue* child_dict;
for (size_t i = 0; i < children->GetSize(); i++) { for (size_t i = 0; i < children->GetSize(); i++) {
children->GetDictionary(i, &child_dict); children->GetDictionary(i, &child_dict);
......
...@@ -11,11 +11,13 @@ ...@@ -11,11 +11,13 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/process/process_handle.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/values.h" #include "base/values.h"
#include "content/browser/accessibility/browser_accessibility.h" #include "content/browser/accessibility/browser_accessibility.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "ui/gfx/native_widget_types.h"
namespace { namespace {
const char kChildrenDictAttr[] = "children"; const char kChildrenDictAttr[] = "children";
...@@ -75,12 +77,23 @@ class CONTENT_EXPORT AccessibilityTreeFormatter { ...@@ -75,12 +77,23 @@ class CONTENT_EXPORT AccessibilityTreeFormatter {
// "children": [ ] // "children": [ ]
// } ] // } ]
// } // }
// Build an accessibility tree for the current Chrome app.
virtual std::unique_ptr<base::DictionaryValue> BuildAccessibilityTree( virtual std::unique_ptr<base::DictionaryValue> BuildAccessibilityTree(
BrowserAccessibility* root) = 0; BrowserAccessibility* root) = 0;
// Build an accessibility tree for any process with a window.
virtual std::unique_ptr<base::DictionaryValue>
BuildAccessibilityTreeForProcess(base::ProcessId pid) = 0;
// Build an accessibility tree for any window.
virtual std::unique_ptr<base::DictionaryValue>
BuildAccessibilityTreeForWindow(gfx::AcceleratedWidget widget) = 0;
// Dumps a BrowserAccessibility tree into a string. // Dumps a BrowserAccessibility tree into a string.
void FormatAccessibilityTree( void FormatAccessibilityTree(
BrowserAccessibility* root, base::string16* contents); BrowserAccessibility* root, base::string16* contents);
void FormatAccessibilityTree(const base::DictionaryValue& tree_node,
base::string16* contents);
// Set regular expression filters that apply to each component of every // Set regular expression filters that apply to each component of every
// line before it's output. // line before it's output.
......
...@@ -17,6 +17,20 @@ AccessibilityTreeFormatterBrowser::BuildAccessibilityTree( ...@@ -17,6 +17,20 @@ AccessibilityTreeFormatterBrowser::BuildAccessibilityTree(
return dict; return dict;
} }
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterBrowser::BuildAccessibilityTreeForProcess(
base::ProcessId pid) {
NOTREACHED();
return nullptr;
}
std::unique_ptr<base::DictionaryValue>
AccessibilityTreeFormatterBrowser::BuildAccessibilityTreeForWindow(
gfx::AcceleratedWidget widget) {
NOTREACHED();
return nullptr;
}
void AccessibilityTreeFormatterBrowser::RecursiveBuildAccessibilityTree( void AccessibilityTreeFormatterBrowser::RecursiveBuildAccessibilityTree(
const BrowserAccessibility& node, const BrowserAccessibility& node,
base::DictionaryValue* dict) { base::DictionaryValue* dict) {
......
...@@ -25,6 +25,12 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBrowser ...@@ -25,6 +25,12 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBrowser
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTree( std::unique_ptr<base::DictionaryValue> BuildAccessibilityTree(
BrowserAccessibility* root) override; BrowserAccessibility* root) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForProcess(
base::ProcessId pid) override;
std::unique_ptr<base::DictionaryValue> BuildAccessibilityTreeForWindow(
gfx::AcceleratedWidget widget) override;
protected: protected:
void RecursiveBuildAccessibilityTree(const BrowserAccessibility& node, void RecursiveBuildAccessibilityTree(const BrowserAccessibility& node,
base::DictionaryValue* dict); base::DictionaryValue* dict);
......
...@@ -154,7 +154,7 @@ void DumpAccessibilityTreeTest::AddDefaultFilters( ...@@ -154,7 +154,7 @@ void DumpAccessibilityTreeTest::AddDefaultFilters(
AddFilter(filters, "DEFAULT"); AddFilter(filters, "DEFAULT");
AddFilter(filters, "EXPANDED"); AddFilter(filters, "EXPANDED");
AddFilter(filters, "FLOATING"); AddFilter(filters, "FLOATING");
AddFilter(filters, "FOCUS*"); AddFilter(filters, "FOCUSABLE");
AddFilter(filters, "HASPOPUP"); AddFilter(filters, "HASPOPUP");
AddFilter(filters, "INVISIBLE"); AddFilter(filters, "INVISIBLE");
AddFilter(filters, "MARQUEED"); AddFilter(filters, "MARQUEED");
...@@ -181,6 +181,10 @@ void DumpAccessibilityTreeTest::AddDefaultFilters( ...@@ -181,6 +181,10 @@ void DumpAccessibilityTreeTest::AddDefaultFilters(
AddFilter(filters, "IA2_STATE_REQUIRED"); AddFilter(filters, "IA2_STATE_REQUIRED");
AddFilter(filters, "IA2_STATE_STALE"); AddFilter(filters, "IA2_STATE_STALE");
AddFilter(filters, "IA2_STATE_TRANSIENT"); AddFilter(filters, "IA2_STATE_TRANSIENT");
// Reduce flakiness.
AddFilter(filters, "FOCUSED", Filter::DENY);
AddFilter(filters, "HOTTRACKED", Filter::DENY);
AddFilter(filters, "OFFSCREEN", Filter::DENY);
// //
// Blink // Blink
......
...@@ -2,21 +2,42 @@ ...@@ -2,21 +2,42 @@
# Copyright 2017 The Chromium Authors. All rights reserved. # Copyright 2017 The Chromium Authors. All rights reserved.
# found in the LICENSE file. # found in the LICENSE file.
executable("ax_dump_events") { if (is_win) {
testonly = true executable("ax_dump_events") {
testonly = true
sources = [
"ax_dump_events.cc", sources = [
"ax_event_server.cc", "ax_dump_events.cc",
] "ax_event_server.cc",
]
deps = [
"//base", deps = [
"//base/test:test_support", "//base",
"//content/test:test_support", "//base/test:test_support",
] "//content/test:test_support",
]
if (is_win) {
libs = [ "oleacc.lib" ] if (is_win) {
libs = [ "oleacc.lib" ]
}
}
executable("ax_dump_tree") {
testonly = true
sources = [
"ax_dump_tree.cc",
"ax_tree_server.cc",
]
deps = [
"//base",
"//base/test:test_support",
"//content/test:test_support",
]
if (is_win) {
libs = [ "oleacc.lib" ]
}
} }
} }
...@@ -11,6 +11,16 @@ ...@@ -11,6 +11,16 @@
char kPidSwitch[] = "pid"; char kPidSwitch[] = "pid";
// Convert from string to int, whether in 0x hex format or decimal format.
bool StringToInt(std::string str, int* result) {
if (str.empty())
return false;
bool is_hex =
str.size() > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X');
return is_hex ? base::HexStringToInt(str, result)
: base::StringToInt(str, result);
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
base::AtExitManager at_exit_manager; base::AtExitManager at_exit_manager;
// TODO(aleventhal) Want callback after Ctrl+C or some global keystroke: // TODO(aleventhal) Want callback after Ctrl+C or some global keystroke:
...@@ -19,11 +29,13 @@ int main(int argc, char** argv) { ...@@ -19,11 +29,13 @@ int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv); base::CommandLine::Init(argc, argv);
std::string pid_str = std::string pid_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kPidSwitch); base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kPidSwitch);
int pid = 0; int pid;
if (!pid_str.empty()) if (!pid_str.empty()) {
base::StringToInt(pid_str, &pid); if (StringToInt(pid_str, &pid)) {
std::unique_ptr<content::AXEventServer> server(
new content::AXEventServer(pid));
}
}
std::unique_ptr<content::AXEventServer> server(
new content::AXEventServer(pid));
return 0; return 0;
} }
// Copyright 2017 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 <string>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "tools/accessibility/inspect/ax_tree_server.h"
char kPidSwitch[] = "pid";
char kWindowSwitch[] = "window";
// Convert from string to int, whether in 0x hex format or decimal format.
bool StringToInt(std::string str, int* result) {
if (str.empty())
return false;
bool is_hex =
str.size() > 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X');
return is_hex ? base::HexStringToInt(str, result)
: base::StringToInt(str, result);
}
int main(int argc, char** argv) {
base::AtExitManager at_exit_manager;
base::CommandLine::Init(argc, argv);
std::string window_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kWindowSwitch);
if (!window_str.empty()) {
int window;
if (StringToInt(window_str, &window)) {
gfx::AcceleratedWidget widget(
reinterpret_cast<gfx::AcceleratedWidget>(window));
std::unique_ptr<content::AXTreeServer> server(
new content::AXTreeServer(widget));
return 0;
}
}
std::string pid_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kPidSwitch);
if (!pid_str.empty()) {
int pid;
if (StringToInt(pid_str, &pid)) {
base::ProcessId process_id = static_cast<base::ProcessId>(pid);
std::unique_ptr<content::AXTreeServer> server(
new content::AXTreeServer(process_id));
}
}
return 0;
}
// Copyright 2017 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 "tools/accessibility/inspect/ax_tree_server.h"
#include <iostream>
#include <string>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
namespace content {
AXTreeServer::AXTreeServer(base::ProcessId pid) {
std::unique_ptr<AccessibilityTreeFormatter> formatter(
AccessibilityTreeFormatter::Create());
// Get accessibility tree as nested dictionary.
base::string16 accessibility_contents_utf16;
std::unique_ptr<base::DictionaryValue> dict =
formatter->BuildAccessibilityTreeForProcess(pid);
if (!dict) {
std::cout << "Failed to get accessibility tree";
return;
}
Format(*formatter, *dict);
}
AXTreeServer::AXTreeServer(gfx::AcceleratedWidget widget) {
std::unique_ptr<AccessibilityTreeFormatter> formatter(
AccessibilityTreeFormatter::Create());
// Get accessibility tree as nested dictionary.
std::unique_ptr<base::DictionaryValue> dict =
formatter->BuildAccessibilityTreeForWindow(widget);
if (!dict) {
std::cout << "Failed to get accessibility tree";
return;
}
Format(*formatter, *dict);
}
void AXTreeServer::Format(AccessibilityTreeFormatter& formatter,
base::DictionaryValue& dict) {
// Set filters.
std::vector<AccessibilityTreeFormatter::Filter> filters;
filters.push_back(AccessibilityTreeFormatter::Filter(
base::ASCIIToUTF16("*"), AccessibilityTreeFormatter::Filter::ALLOW));
formatter.SetFilters(filters);
// Format accessibility tree as text.
base::string16 accessibility_contents_utf16;
formatter.FormatAccessibilityTree(dict, &accessibility_contents_utf16);
// Write to console.
std::cout << accessibility_contents_utf16;
}
} // namespace content
// Copyright 2017 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.
#ifndef AX_TREE_SERVER_H_
#define AX_TREE_SERVER_H_
#include <string>
#include "content/browser/accessibility/accessibility_tree_formatter.h"
namespace content {
class AXTreeServer {
public:
explicit AXTreeServer(base::ProcessId pid);
explicit AXTreeServer(gfx::AcceleratedWidget widget);
private:
DISALLOW_COPY_AND_ASSIGN(AXTreeServer);
void Format(AccessibilityTreeFormatter& formatter,
base::DictionaryValue& dict);
};
} // namespace content
#endif // AX_TREE_SERVER_H_
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