Commit 5755004e authored by Kurt Catti-Schmidt (SCHMIDT)'s avatar Kurt Catti-Schmidt (SCHMIDT) Committed by Chromium LUCI CQ

Adding ViewsAXTreeManager output to chrome://accessibility

This change creates a ViewsAXTreeManager for each top-level Widget
behind the runtime flag "--enable-features=AccessibilityTreeForViews".
This change also displays the contents of the entire tree built by
ViewsAXTreeManager under chrome://accessibility under a new "Top
Level Windows" section that is only visible when the runtime flag
is enabled.

Much of the code changes here are converting methods that serialize
BrowserAccessibility nodes to instead take AXNode's. This was fairly
straightforward for most of these, however
AccessibilityTreeFormatterBlink::AddProperties had to be forked due to
all of the properties that depend on the delegate.

WidgetAXTreeIDMap is added to map between Widget objects and their
root AXTreeID.

In order to suppress build errors on ChromeOS and macOS, this change is
limited to non-ChromeOS Aura builds (Windows and Linux).

AX-Relnotes: Displays internal UI tree in chrome://accessibility.

Bug: 1049261
Change-Id: Id3918e177df360b284d186638a04d8be494508c7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2402398
Commit-Queue: Kurt Catti-Schmidt <kschmi@microsoft.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarAllen Bauer <kylixrd@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836275}
parent 37a2f4f0
......@@ -26,6 +26,7 @@
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/ax_inspect_factory.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/favicon_status.h"
......@@ -38,15 +39,20 @@
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_ui_data_source.h"
#include "net/base/escape.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/inspect/ax_tree_formatter.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#if !defined(OS_ANDROID)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "ui/views/accessibility/widget_ax_tree_id_map.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#endif
static const char kTargetsDataFile[] = "targets-data.json";
......@@ -71,6 +77,12 @@ static const char kStartField[] = "start";
static const char kTreeField[] = "tree";
static const char kTypeField[] = "type";
static const char kUrlField[] = "url";
static const char kWidgetsField[] = "widgets";
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
static const char kWidgetIdField[] = "widgetId";
static const char kWidget[] = "widget";
#endif
// Global flags
static const char kBrowser[] = "browser";
......@@ -84,6 +96,7 @@ static const char kPDF[] = "pdf";
static const char kScreenReader[] = "screenreader";
static const char kShowOrRefreshTree[] = "showOrRefreshTree";
static const char kText[] = "text";
static const char kViewsAccessibility[] = "viewsAccessibility";
static const char kWeb[] = "web";
// Possible global flag values
......@@ -159,6 +172,22 @@ std::unique_ptr<base::DictionaryValue> BuildTargetDescriptor(Browser* browser) {
}
#endif // !defined(OS_ANDROID)
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
std::unique_ptr<base::DictionaryValue> BuildTargetDescriptor(
views::Widget* widget) {
std::unique_ptr<base::DictionaryValue> widget_data(
new base::DictionaryValue());
widget_data->SetString(kNameField,
widget->widget_delegate()->GetWindowTitle());
widget_data->SetString(kTypeField, kWidget);
// Use the Widget's root view ViewAccessibility's unique ID for lookup.
int id = widget->GetRootView()->GetViewAccessibility().GetUniqueId().Get();
widget_data->SetInteger(kWidgetIdField, id);
return widget_data;
}
#endif // defined(USE_AURA) && !defined(OS_CHROMEOS)
bool ShouldHandleAccessibilityRequestCallback(const std::string& path) {
return path == kTargetsDataFile;
}
......@@ -210,6 +239,11 @@ void HandleAccessibilityRequestCallback(
// The "pdf" flag is independent of the others.
data.SetString(kPDF, pdf ? kOn : kOff);
// The "Top Level Widgets" section is only relevant if views accessibility is
// enabled.
data.SetBoolean(kViewsAccessibility,
features::IsAccessibilityTreeForViewsEnabled());
bool show_internal = pref->GetBoolean(prefs::kShowInternalAccessibilityTree);
data.SetString(kInternal, show_internal ? kOn : kOff);
......@@ -254,6 +288,19 @@ void HandleAccessibilityRequestCallback(
#endif // !defined(OS_ANDROID)
data.Set(kBrowsersField, std::move(browser_list));
std::unique_ptr<base::ListValue> widgets_list(new base::ListValue());
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
if (features::IsAccessibilityTreeForViewsEnabled()) {
views::WidgetAXTreeIDMap& manager_map =
views::WidgetAXTreeIDMap::GetInstance();
const std::vector<views::Widget*> widgets = manager_map.GetWidgets();
for (views::Widget* widget : widgets) {
widgets_list->Append(BuildTargetDescriptor(widget));
}
}
#endif // defined(USE_AURA) && !defined(OS_CHROMEOS)
data.Set(kWidgetsField, std::move(widgets_list));
std::string json_string;
base::JSONWriter::Write(data, &json_string);
......@@ -375,6 +422,14 @@ void AccessibilityUIMessageHandler::RegisterMessages() {
"requestNativeUITree",
base::BindRepeating(&AccessibilityUIMessageHandler::RequestNativeUITree,
base::Unretained(this)));
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
web_ui()->RegisterMessageCallback(
"requestWidgetsTree",
base::BindRepeating(&AccessibilityUIMessageHandler::RequestWidgetsTree,
base::Unretained(this)));
#endif
web_ui()->RegisterMessageCallback(
"requestAccessibilityEvents",
base::BindRepeating(
......@@ -497,28 +552,39 @@ void AccessibilityUIMessageHandler::SetGlobalFlag(const base::ListValue* args) {
state->RemoveAccessibilityModeFlags(new_mode);
}
void AccessibilityUIMessageHandler::RequestWebContentsTree(
const base::ListValue* args) {
const base::DictionaryValue* data;
CHECK(args->GetDictionary(0, &data));
int process_id = *data->FindIntPath(kProcessIdField);
int routing_id = *data->FindIntPath(kRoutingIdField);
void AccessibilityUIMessageHandler::GetRequestTypeAndFilters(
const base::DictionaryValue* data,
std::string& request_type,
std::string& allow,
std::string& allow_empty,
std::string& deny) {
DCHECK(data);
const std::string* request_type_p = data->FindStringPath(kRequestTypeField);
CHECK(IsValidJSValue(request_type_p));
std::string request_type = *request_type_p;
request_type = *request_type_p;
CHECK(request_type == kShowOrRefreshTree || request_type == kCopyTree);
const std::string* allow_p = data->FindStringPath("filters.allow");
CHECK(IsValidJSValue(allow_p));
std::string allow = *allow_p;
allow = *allow_p;
const std::string* allow_empty_p = data->FindStringPath("filters.allowEmpty");
CHECK(IsValidJSValue(allow_empty_p));
std::string allow_empty = *allow_empty_p;
allow_empty = *allow_empty_p;
const std::string* deny_p = data->FindStringPath("filters.deny");
CHECK(IsValidJSValue(deny_p));
std::string deny = *deny_p;
deny = *deny_p;
}
void AccessibilityUIMessageHandler::RequestWebContentsTree(
const base::ListValue* args) {
const base::DictionaryValue* data;
CHECK(args->GetDictionary(0, &data));
std::string request_type, allow, allow_empty, deny;
GetRequestTypeAndFilters(data, request_type, allow, allow_empty, deny);
int process_id = *data->FindIntPath(kProcessIdField);
int routing_id = *data->FindIntPath(kRoutingIdField);
AllowJavascript();
content::RenderViewHost* rvh =
......@@ -561,21 +627,10 @@ void AccessibilityUIMessageHandler::RequestNativeUITree(
const base::DictionaryValue* data;
CHECK(args->GetDictionary(0, &data));
int session_id = *data->FindIntPath(kSessionIdField);
const std::string* request_type_p = data->FindStringPath(kRequestTypeField);
CHECK(IsValidJSValue(request_type_p));
std::string request_type = *request_type_p;
CHECK(request_type == kShowOrRefreshTree || request_type == kCopyTree);
std::string request_type, allow, allow_empty, deny;
GetRequestTypeAndFilters(data, request_type, allow, allow_empty, deny);
const std::string* allow_p = data->FindStringPath("filters.allow");
CHECK(IsValidJSValue(allow_p));
std::string allow = *allow_p;
const std::string* allow_empty_p = data->FindStringPath("filters.allowEmpty");
CHECK(IsValidJSValue(allow_empty_p));
std::string allow_empty = *allow_empty_p;
const std::string* deny_p = data->FindStringPath("filters.deny");
CHECK(IsValidJSValue(deny_p));
std::string deny = *deny_p;
int session_id = *data->FindIntPath(kSessionIdField);
AllowJavascript();
......@@ -609,6 +664,55 @@ void AccessibilityUIMessageHandler::RequestNativeUITree(
FireWebUIListener(request_type, *(result.get()));
}
void AccessibilityUIMessageHandler::RequestWidgetsTree(
const base::ListValue* args) {
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
const base::DictionaryValue* data;
CHECK(args->GetDictionary(0, &data));
std::string request_type, allow, allow_empty, deny;
GetRequestTypeAndFilters(data, request_type, allow, allow_empty, deny);
std::vector<AXPropertyFilter> property_filters;
AddPropertyFilters(property_filters, allow, AXPropertyFilter::ALLOW);
AddPropertyFilters(property_filters, allow_empty,
AXPropertyFilter::ALLOW_EMPTY);
AddPropertyFilters(property_filters, deny, AXPropertyFilter::DENY);
if (features::IsAccessibilityTreeForViewsEnabled()) {
int widget_id = *data->FindIntPath(kWidgetIdField);
views::WidgetAXTreeIDMap& manager_map =
views::WidgetAXTreeIDMap::GetInstance();
const std::vector<views::Widget*> widgets = manager_map.GetWidgets();
for (views::Widget* widget : widgets) {
int current_id =
widget->GetRootView()->GetViewAccessibility().GetUniqueId().Get();
if (current_id == widget_id) {
ui::AXTreeID tree_id = manager_map.GetWidgetTreeID(widget);
DCHECK_NE(tree_id, ui::AXTreeIDUnknown());
std::unique_ptr<ui::AXTreeFormatter> formatter(
content::AXInspectFactory::CreateBlinkFormatter());
std::string tree_dump =
formatter->DumpInternalAccessibilityTree(tree_id, property_filters);
std::unique_ptr<base::DictionaryValue> result(
BuildTargetDescriptor(widget));
result->SetKey(kTreeField, base::Value(tree_dump));
AllowJavascript();
FireWebUIListener(request_type, *(result.get()));
return;
}
}
}
std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
result->SetString(kTypeField, kWidget);
result->SetString(kErrorField, "Window no longer exists.");
AllowJavascript();
FireWebUIListener(request_type, *(result.get()));
#endif // defined(USE_AURA) && !defined(OS_CHROMEOS)
}
void AccessibilityUIMessageHandler::Callback(const std::string& str) {
event_logs_.push_back(str);
}
......
......@@ -65,8 +65,14 @@ class AccessibilityUIMessageHandler : public content::WebUIMessageHandler {
void ToggleAccessibility(const base::ListValue* args);
void SetGlobalFlag(const base::ListValue* args);
void GetRequestTypeAndFilters(const base::DictionaryValue* data,
std::string& request_type,
std::string& allow,
std::string& allow_empty,
std::string& deny);
void RequestWebContentsTree(const base::ListValue* args);
void RequestNativeUITree(const base::ListValue* args);
void RequestWidgetsTree(const base::ListValue* args);
void RequestAccessibilityEvents(const base::ListValue* args);
void Callback(const std::string&);
void StopRecording(content::WebContents* web_contents);
......
......@@ -9,13 +9,13 @@ body {
font-size: 12px;
margin: 10px 20px;
min-width: 47em;
padding-bottom: 65px;
padding-block-end: 65px;
}
img {
float: left;
height: 16px;
padding-right: 5px;
padding-inline-end: 5px;
width: 16px;
}
......@@ -57,10 +57,14 @@ p {
margin: 0 0 12px 32px;
}
.hidden {
display: none;
}
a {
margin-top: 10px;
margin-bottom: 10px;
margin-left: 20px;
margin-block-end: 10px;
margin-block-start: 10px;
margin-inline-start: 20px;
}
label {
......@@ -74,5 +78,5 @@ label.disabled {
}
label input[type='checkbox'] {
margin-right: 6px;
margin-inline-end: 6px;
}
......@@ -195,5 +195,11 @@ found in the LICENSE file.
<h2>Pages:</h2>
<div id="pages" class="list"></div>
<h2 id="widgets-header">Top Level Windows:</h2>
<p id="widgets-not-supported" class="hidden">
Top Level Window support is not available on this platform.
</p>
<div id="widgets" class="list"></div>
</body>
</html>
......@@ -32,6 +32,12 @@ class BrowserProxy {
]);
}
requestWidgetsTree(widgetId, requestType, allow, allowEmpty, deny) {
chrome.send(
'requestWidgetsTree',
[{widgetId, requestType, filters: {allow, allowEmpty, deny}}]);
}
requestAccessibilityEvents(processId, routingId, start) {
chrome.send('requestAccessibilityEvents', [{processId, routingId, start}]);
}
......@@ -81,6 +87,8 @@ function getIdFromData(data) {
return data.processId + '.' + data.routingId;
} else if (data.type == 'browser') {
return 'browser.' + data.sessionId;
} else if (data.type == 'widget') {
return 'widget.' + data.widgetId;
} else {
console.error('Unknown data type.', data);
return '';
......@@ -119,6 +127,9 @@ function requestTree(data, element) {
browserProxy.requestNativeUITree(
data.sessionId, requestType, allow, allowEmpty, deny);
}, delay);
} else if (data.type == 'widget') {
browserProxy.requestWidgetsTree(
data.widgetId, requestType, allow, allowEmpty, deny);
} else {
browserProxy.requestWebContentsTree(
data.processId, data.routingId, requestType, allow, allowEmpty, deny);
......@@ -179,6 +190,25 @@ function initialize() {
addToBrowsersList(browsers[i]);
}
if (data['viewsAccessibility']) {
const widgets = data['widgets'];
if (widgets.length === 0) {
// There should always be at least 1 Widget displayed (for the current
// window). If this is not the case, and Views Accessibility is enabled,
// the only possibility is that Views Accessibility is not enabled for
// the current platform. Display a message to the user to indicate this.
$('widgets-not-supported').style.display = 'block';
} else {
for (let i = 0; i < widgets.length; i++) {
addToWidgetsList(widgets[i]);
}
}
} else {
$('widgets').style.display = 'none';
$('widgets-header').style.display = 'none';
}
// Cache filters so they're easily accessible on page refresh.
const allow = window.localStorage['chrome-accessibility-filter-allow'];
const allowEmpty =
......@@ -233,6 +263,17 @@ function addToBrowsersList(data) {
browsers.appendChild(row);
}
function addToWidgetsList(data) {
const id = getIdFromData(data);
const row = document.createElement('div');
row.className = 'row';
row.id = id;
formatRow(row, data, null);
const widgets = $('widgets');
widgets.appendChild(row);
}
function formatRow(row, data, requestType) {
if (!('url' in data)) {
if ('error' in data) {
......@@ -528,4 +569,4 @@ function createAccessibilityOutputElement(data, id, type) {
return treeElement;
}
document.addEventListener('DOMContentLoaded', initialize);
document.addEventListener('DOMContentLoaded', initialize);
\ No newline at end of file
......@@ -18,6 +18,7 @@
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/compute_attributes.h"
#include "ui/gfx/geometry/rect_conversions.h"
......@@ -27,12 +28,12 @@ namespace content {
namespace {
base::Optional<std::string> GetStringAttribute(
const BrowserAccessibility& node,
const ui::AXNode& node,
ax::mojom::StringAttribute attr) {
// Language is different from other string attributes as it inherits and has
// a method to compute it.
if (attr == ax::mojom::StringAttribute::kLanguage) {
std::string value = node.node()->GetLanguage();
std::string value = node.GetLanguage();
if (value.empty()) {
return base::nullopt;
}
......@@ -57,13 +58,16 @@ base::Optional<std::string> GetStringAttribute(
return base::nullopt;
}
std::string IntAttrToString(const BrowserAccessibility& node,
std::string IntAttrToString(const ui::AXNode& node,
ax::mojom::IntAttribute attr,
int32_t value) {
if (ui::IsNodeIdIntAttribute(attr)) {
// Relation
BrowserAccessibility* target = node.manager()->GetFromID(value);
return target ? ui::ToString(target->GetData().role) : std::string("null");
ui::AXTreeID tree_id = node.tree()->GetAXTreeID();
ui::AXNode* target = ui::AXTreeManagerMap::GetInstance()
.GetManager(tree_id)
->GetNodeFromTree(tree_id, value);
return target ? ui::ToString(target->data().role) : std::string("null");
}
switch (attr) {
......@@ -76,7 +80,7 @@ std::string IntAttrToString(const BrowserAccessibility& node,
case ax::mojom::IntAttribute::kDescriptionFrom:
return ui::ToString(static_cast<ax::mojom::DescriptionFrom>(value));
case ax::mojom::IntAttribute::kDropeffect:
return node.GetData().DropeffectBitfieldToString();
return node.data().DropeffectBitfieldToString();
case ax::mojom::IntAttribute::kHasPopup:
return ui::ToString(static_cast<ax::mojom::HasPopup>(value));
case ax::mojom::IntAttribute::kInvalidState:
......@@ -207,7 +211,6 @@ base::Value AccessibilityTreeFormatterBlink::BuildTree(
RecursiveBuildTree(*root_internal, &dict);
return dict;
}
base::Value AccessibilityTreeFormatterBlink::BuildTreeForWindow(
gfx::AcceleratedWidget widget) const {
NOTREACHED();
......@@ -220,6 +223,25 @@ base::Value AccessibilityTreeFormatterBlink::BuildTreeForSelector(
return base::Value(base::Value::Type::DICTIONARY);
}
base::Value AccessibilityTreeFormatterBlink::BuildTreeForNode(
ui::AXNode* node) const {
CHECK(node);
base::Value dict(base::Value::Type::DICTIONARY);
RecursiveBuildTree(*node, &dict);
return dict;
}
std::string AccessibilityTreeFormatterBlink::DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) {
ui::AXTreeManager* ax_mgr =
ui::AXTreeManagerMap::GetInstance().GetManager(tree_id);
DCHECK(ax_mgr);
SetPropertyFilters(property_filters, kFiltersDefaultSet);
base::Value dict = BuildTreeForNode(ax_mgr->GetRootAsAXNode());
return FormatTree(dict);
}
void AccessibilityTreeFormatterBlink::RecursiveBuildTree(
const BrowserAccessibility& node,
base::Value* dict) const {
......@@ -235,6 +257,20 @@ void AccessibilityTreeFormatterBlink::RecursiveBuildTree(
dict->SetKey(kChildrenDictAttr, std::move(children));
}
void AccessibilityTreeFormatterBlink::RecursiveBuildTree(
const ui::AXNode& node,
base::Value* dict) const {
AddProperties(node, static_cast<base::DictionaryValue*>(dict));
base::Value children(base::Value::Type::LIST);
for (ui::AXNode* child_node : node.children()) {
base::Value child_dict(base::Value::Type::DICTIONARY);
RecursiveBuildTree(*child_node, &child_dict);
children.Append(std::move(child_dict));
}
dict->SetKey(kChildrenDictAttr, std::move(children));
}
uint32_t AccessibilityTreeFormatterBlink::ChildCount(
const BrowserAccessibility& node) const {
if (node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId))
......@@ -308,7 +344,7 @@ void AccessibilityTreeFormatterBlink::AddProperties(
static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::StringAttribute>(attr_index);
auto maybe_value = GetStringAttribute(node, attr);
auto maybe_value = GetStringAttribute(*node.node(), attr);
if (maybe_value.has_value())
dict->SetString(ui::ToString(attr), maybe_value.value());
}
......@@ -321,7 +357,7 @@ void AccessibilityTreeFormatterBlink::AddProperties(
auto maybe_value = ui::ComputeAttribute(&node, attr);
if (maybe_value.has_value()) {
dict->SetString(ui::ToString(attr),
IntAttrToString(node, attr, maybe_value.value()));
IntAttrToString(*node.node(), attr, maybe_value.value()));
}
}
......@@ -396,6 +432,156 @@ void AccessibilityTreeFormatterBlink::AddProperties(
dict->SetString("actions", base::JoinString(actions_strings, ","));
}
void AccessibilityTreeFormatterBlink::AddProperties(
const ui::AXNode& node,
base::DictionaryValue* dict) const {
int id = node.id();
dict->SetInteger("id", id);
dict->SetString("internalRole", ui::ToString(node.data().role));
gfx::Rect bounds = gfx::ToEnclosingRect(node.data().relative_bounds.bounds);
dict->SetInteger("boundsX", bounds.x());
dict->SetInteger("boundsY", bounds.y());
dict->SetInteger("boundsWidth", bounds.width());
dict->SetInteger("boundsHeight", bounds.height());
// TODO(kschmi): Add support for the following (potentially via AXTree):
// GetClippedRootFrameBoundsRect
// pageBoundsX
// pageBoundsY
// pageBoundsWidth
// pageBoundsHeight
// GetUnclippedRootFrameBoundsRect
// unclippedBoundsX
// unclippedBoundsY
// unclippedBoundsWidth
// unclippedBoundsHeight
// STATE_OFFSCREEN
// ComputeAttribute
// TableCellAriaColIndex
// TableCellAriaRowIndex
// TableCellColIndex
// TableCellRowIndex
// TableCellColSpan
// TableCellRowSpan
// TableRowRowIndex
// TableColCount
// TableRowCount
// TableAriaColCount
// TableAriaRowCount
// PosInSet
// SetSize
// SetSize
dict->SetBoolean("transform",
node.data().relative_bounds.transform &&
!node.data().relative_bounds.transform->IsIdentity());
for (int32_t state_index = static_cast<int32_t>(ax::mojom::State::kNone);
state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue);
++state_index) {
auto state = static_cast<ax::mojom::State>(state_index);
if (node.data().HasState(state))
dict->SetBoolean(ui::ToString(state), true);
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::StringAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::StringAttribute>(attr_index);
auto maybe_value = GetStringAttribute(node, attr);
if (maybe_value.has_value())
dict->SetString(ui::ToString(attr), maybe_value.value());
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntAttribute>(attr_index);
int32_t value;
if (node.data().GetIntAttribute(attr, &value)) {
dict->SetString(ui::ToString(attr), IntAttrToString(node, attr, value));
}
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::FloatAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index);
if (node.HasFloatAttribute(attr) &&
std::isfinite(node.GetFloatAttribute(attr)))
dict->SetDouble(ui::ToString(attr), node.GetFloatAttribute(attr));
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::BoolAttribute::kNone);
attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index);
if (node.HasBoolAttribute(attr))
dict->SetBoolean(ui::ToString(attr), node.GetBoolAttribute(attr));
}
for (int32_t attr_index =
static_cast<int32_t>(ax::mojom::IntListAttribute::kNone);
attr_index <=
static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue);
++attr_index) {
auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index);
if (node.HasIntListAttribute(attr)) {
std::vector<int32_t> values;
node.GetIntListAttribute(attr, &values);
auto value_list = std::make_unique<base::ListValue>();
for (auto value : values) {
if (ui::IsNodeIdIntListAttribute(attr)) {
ui::AXTreeID tree_id = node.tree()->GetAXTreeID();
ui::AXNode* target = ui::AXTreeManagerMap::GetInstance()
.GetManager(tree_id)
->GetNodeFromTree(tree_id, node.id());
if (target)
value_list->AppendString(ui::ToString(target->data().role));
else
value_list->AppendString("null");
} else {
value_list->AppendInteger(value);
}
}
dict->Set(ui::ToString(attr), std::move(value_list));
}
}
// Check for relevant rich text selection info in AXTreeData
ui::AXTree::Selection unignored_selection =
node.tree()->GetUnignoredSelection();
int anchor_id = unignored_selection.anchor_object_id;
if (id == anchor_id) {
int anchor_offset = unignored_selection.anchor_offset;
dict->SetInteger("TreeData.textSelStartOffset", anchor_offset);
}
int focus_id = unignored_selection.focus_object_id;
if (id == focus_id) {
int focus_offset = unignored_selection.focus_offset;
dict->SetInteger("TreeData.textSelEndOffset", focus_offset);
}
std::vector<std::string> actions_strings;
for (int32_t action_index =
static_cast<int32_t>(ax::mojom::Action::kNone) + 1;
action_index <= static_cast<int32_t>(ax::mojom::Action::kMaxValue);
++action_index) {
auto action = static_cast<ax::mojom::Action>(action_index);
if (node.data().HasAction(action))
actions_strings.push_back(ui::ToString(action));
}
if (!actions_strings.empty())
dict->SetString("actions", base::JoinString(actions_strings, ","));
}
std::string AccessibilityTreeFormatterBlink::ProcessTreeForOutput(
const base::DictionaryValue& dict) const {
std::string error_value;
......@@ -435,34 +621,49 @@ std::string AccessibilityTreeFormatterBlink::ProcessTreeForOutput(
if (focused)
WriteAttribute(false, STATE_FOCUSED, &line);
WriteAttribute(
false, FormatCoordinates(dict, "location", "boundsX", "boundsY"), &line);
WriteAttribute(false,
FormatCoordinates(dict, "size", "boundsWidth", "boundsHeight"),
&line);
bool ignored = false;
dict.GetBoolean("ignored", &ignored);
if (!ignored) {
WriteAttribute(
false,
FormatCoordinates(dict, "pageLocation", "pageBoundsX", "pageBoundsY"),
&line);
if (dict.FindKey("boundsX") && dict.FindKey("boundsY")) {
WriteAttribute(false,
FormatCoordinates(dict, "pageSize", "pageBoundsWidth",
"pageBoundsHeight"),
&line);
WriteAttribute(false,
FormatCoordinates(dict, "unclippedLocation",
"unclippedBoundsX", "unclippedBoundsY"),
FormatCoordinates(dict, "location", "boundsX", "boundsY"),
&line);
}
if (dict.FindKey("boundsWidth") && dict.FindKey("boundsHeight")) {
WriteAttribute(
false,
FormatCoordinates(dict, "unclippedSize", "unclippedBoundsWidth",
"unclippedBoundsHeight"),
false, FormatCoordinates(dict, "size", "boundsWidth", "boundsHeight"),
&line);
}
bool ignored = false;
dict.GetBoolean("ignored", &ignored);
if (!ignored) {
if (dict.FindKey("pageBoundsX") && dict.FindKey("pageBoundsY")) {
WriteAttribute(
false,
FormatCoordinates(dict, "pageLocation", "pageBoundsX", "pageBoundsY"),
&line);
}
if (dict.FindKey("pageBoundsWidth") && dict.FindKey("pageBoundsHeight")) {
WriteAttribute(false,
FormatCoordinates(dict, "pageSize", "pageBoundsWidth",
"pageBoundsHeight"),
&line);
}
if (dict.FindKey("unclippedBoundsX") && dict.FindKey("unclippedBoundsY")) {
WriteAttribute(false,
FormatCoordinates(dict, "unclippedLocation",
"unclippedBoundsX", "unclippedBoundsY"),
&line);
}
if (dict.FindKey("unclippedBoundsWidth") &&
dict.FindKey("unclippedBoundsHeight")) {
WriteAttribute(
false,
FormatCoordinates(dict, "unclippedSize", "unclippedBoundsWidth",
"unclippedBoundsHeight"),
&line);
}
}
bool transform;
if (dict.GetBoolean("transform", &transform) && transform)
WriteAttribute(false, "transform", &line);
......
......@@ -24,6 +24,10 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBlink
base::Value BuildTreeForWindow(gfx::AcceleratedWidget widget) const override;
base::Value BuildTreeForSelector(
const AXTreeSelector& selector) const override;
base::Value BuildTreeForNode(ui::AXNode* node) const override;
std::string DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) override;
protected:
void AddDefaultFilters(
......@@ -33,6 +37,8 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBlink
void RecursiveBuildTree(const BrowserAccessibility& node,
base::Value* dict) const;
void RecursiveBuildTree(const ui::AXNode& node, base::Value* dict) const;
uint32_t ChildCount(const BrowserAccessibility& node) const;
BrowserAccessibility* GetChild(const BrowserAccessibility& node,
uint32_t i) const;
......@@ -40,6 +46,8 @@ class CONTENT_EXPORT AccessibilityTreeFormatterBlink
void AddProperties(const BrowserAccessibility& node,
base::DictionaryValue* dict) const;
void AddProperties(const ui::AXNode& node, base::DictionaryValue* dict) const;
std::string ProcessTreeForOutput(
const base::DictionaryValue& node) const override;
};
......
......@@ -13,6 +13,7 @@ AXTreeManagerMap::AXTreeManagerMap() {}
AXTreeManagerMap::~AXTreeManagerMap() {}
// static
AXTreeManagerMap& AXTreeManagerMap::GetInstance() {
static base::NoDestructor<AXTreeManagerMap> instance;
return *instance;
......
......@@ -16,6 +16,8 @@ class Value;
namespace ui {
class AXNode;
class AXTreeID;
class AXPlatformNodeDelegate;
// A utility class for formatting platform-specific accessibility information,
......@@ -75,6 +77,14 @@ class AX_EXPORT AXTreeFormatter {
// given pattern.
virtual base::Value BuildTreeForSelector(const AXTreeSelector&) const = 0;
// Build an accessibility tree for an application with |node| as the root.
virtual base::Value BuildTreeForNode(ui::AXNode* node) const = 0;
// Returns a string representing the internal tree represented by |tree_id|.
virtual std::string DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) = 0;
// Dumps accessibility tree.
virtual std::string FormatTree(const base::Value& tree_node) const = 0;
......
......@@ -4,8 +4,10 @@
#include "ui/accessibility/platform/inspect/ax_tree_formatter_base.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/platform/inspect/ax_property_node.h"
namespace ui {
......@@ -51,6 +53,12 @@ std::string AXTreeFormatterBase::FormatTree(const base::Value& dict) const {
return contents;
}
base::Value AXTreeFormatterBase::BuildTreeForNode(ui::AXNode* root) const {
NOTREACHED()
<< "Only supported when called on AccessibilityTreeFormatterBlink.";
return base::Value();
}
void AXTreeFormatterBase::RecursiveFormatTree(const base::Value& dict,
std::string* contents,
int depth) const {
......@@ -103,6 +111,14 @@ void AXTreeFormatterBase::set_show_ids(bool show_ids) {
show_ids_ = show_ids;
}
std::string AXTreeFormatterBase::DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) {
NOTREACHED()
<< "Only supported when called on AccessibilityTreeFormatterBlink.";
return std::string("");
}
std::vector<AXPropertyNode> AXTreeFormatterBase::PropertyFilterNodesFor(
const std::string& line_index) const {
std::vector<AXPropertyNode> list;
......
......@@ -31,10 +31,14 @@ class AX_EXPORT AXTreeFormatterBase : public AXTreeFormatter {
// AXTreeFormatter overrides.
std::string FormatTree(const base::Value& tree_node) const override;
base::Value BuildTreeForNode(ui::AXNode* root) const override;
void SetPropertyFilters(const std::vector<AXPropertyFilter>& property_filters,
PropertyFilterSet default_filters_set) override;
void SetNodeFilters(const std::vector<AXNodeFilter>& node_filters) override;
void set_show_ids(bool show_ids) override;
std::string DumpInternalAccessibilityTree(
ui::AXTreeID tree_id,
const std::vector<AXPropertyFilter>& property_filters) override;
protected:
static const char kChildrenDictAttr[];
......
......@@ -62,6 +62,8 @@ component("views") {
"accessibility/ax_virtual_view.h",
"accessibility/view_accessibility.h",
"accessibility/view_accessibility_utils.h",
"accessibility/views_ax_tree_manager.h",
"accessibility/widget_ax_tree_id_map.h",
"accessible_pane_view.h",
"animation/animation_delegate_views.h",
"animation/bounds_animator.h",
......@@ -731,6 +733,12 @@ component("views") {
"corewm/tooltip_win.cc",
]
}
if (!is_chromeos) {
sources += [
"accessibility/views_ax_tree_manager.cc",
"accessibility/widget_ax_tree_id_map.cc",
]
}
deps += [
"//ui/aura",
"//ui/events",
......@@ -830,8 +838,6 @@ component("views") {
if (use_aura) {
sources += [
"accessibility/views_ax_tree_manager.cc",
"accessibility/views_ax_tree_manager.h",
"accessibility/views_utilities_aura.cc",
"accessibility/views_utilities_aura.h",
]
......
......@@ -10,10 +10,14 @@
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/buildflags.h"
#include "ui/views/accessibility/views_ax_tree_manager.h"
#include "ui/views/accessibility/widget_ax_tree_id_map.h"
#include "ui/views/view.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
......@@ -57,7 +61,20 @@ ViewAccessibility::ViewAccessibility(View* view)
: view_(view),
focused_virtual_child_(nullptr),
is_leaf_(false),
is_ignored_(false) {}
is_ignored_(false) {
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
if (features::IsAccessibilityTreeForViewsEnabled()) {
Widget* widget = view_->GetWidget();
if (widget && widget->is_top_level() &&
!WidgetAXTreeIDMap::GetInstance().HasWidget(widget)) {
View* root_view = static_cast<View*>(widget->GetRootView());
if (root_view && root_view == view) {
ax_tree_manager_ = std::make_unique<views::ViewsAXTreeManager>(widget);
}
}
}
#endif
}
ViewAccessibility::~ViewAccessibility() = default;
......@@ -139,6 +156,30 @@ bool ViewAccessibility::IsLeaf() const {
return is_leaf_;
}
ViewsAXTreeManager* ViewAccessibility::AXTreeManager() const {
ViewsAXTreeManager* manager = nullptr;
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
Widget* widget = view_->GetWidget();
// Don't return managers for closing Widgets.
if (widget->IsClosed())
return nullptr;
manager = ax_tree_manager_.get();
// ViewsAXTreeManagers are only created for top-level windows (Widgets). For
// non top-level Views, look up the Widget's tree ID to retrieve the manager.
if (!manager) {
ui::AXTreeID tree_id =
WidgetAXTreeIDMap::GetInstance().GetWidgetTreeID(widget);
DCHECK_NE(tree_id, ui::AXTreeIDUnknown());
manager = static_cast<views::ViewsAXTreeManager*>(
ui::AXTreeManagerMap::GetInstance().GetManager(tree_id));
}
#endif
return manager;
}
bool ViewAccessibility::IsIgnored() const {
return is_ignored_;
}
......
......@@ -28,6 +28,7 @@ class AXPlatformNodeDelegate;
namespace views {
class View;
class ViewsAXTreeManager;
class Widget;
// An object that manages the accessibility interface for a View.
......@@ -117,6 +118,7 @@ class VIEWS_EXPORT ViewAccessibility {
View* view() const { return view_; }
AXVirtualView* FocusedVirtualChild() const { return focused_virtual_child_; }
virtual bool IsLeaf() const;
ViewsAXTreeManager* AXTreeManager() const;
virtual bool IsIgnored() const;
//
......@@ -219,6 +221,12 @@ class VIEWS_EXPORT ViewAccessibility {
// screen readers, transition focus from one widget to another.
Widget* next_focus_ = nullptr;
Widget* previous_focus_ = nullptr;
#if defined(USE_AURA) && !defined(OS_CHROMEOS)
// Each instance of ViewAccessibility that's associated with a root View
// owns an ViewsAXTreeManager. For other Views, this should be nullptr.
std::unique_ptr<views::ViewsAXTreeManager> ax_tree_manager_;
#endif
};
} // namespace views
......
......@@ -12,12 +12,14 @@
#include "base/location.h"
#include "base/notreached.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/accessibility/ax_tree_source_checker.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/widget_ax_tree_id_map.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
......@@ -25,14 +27,15 @@ namespace views {
ViewsAXTreeManager::ViewsAXTreeManager(Widget* widget)
: widget_(widget),
tree_source_(cache_.GetOrCreate(widget),
ui::AXTreeID::CreateNewAXTreeID(),
&cache_),
tree_id_(ui::AXTreeID::CreateNewAXTreeID()),
tree_source_(cache_.GetOrCreate(widget), tree_id_, &cache_),
tree_serializer_(&tree_source_),
event_generator_(&ax_tree_) {
DCHECK(widget);
ui::AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
views_event_observer_.Add(AXEventManager::Get());
ui::AXTreeManagerMap::GetInstance().AddTreeManager(tree_id_, this);
views::WidgetAXTreeIDMap::GetInstance().AddWidget(tree_id_, widget);
views_event_observer_.Observe(AXEventManager::Get());
widget_observer_.Observe(widget);
View* root_view = widget->GetRootView();
if (root_view)
root_view->NotifyAccessibilityEvent(ax::mojom::Event::kLoadComplete, true);
......@@ -40,8 +43,9 @@ ViewsAXTreeManager::ViewsAXTreeManager(Widget* widget)
ViewsAXTreeManager::~ViewsAXTreeManager() {
event_generator_.ReleaseTree();
views_event_observer_.RemoveAll();
ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
views_event_observer_.Reset();
widget_observer_.Reset();
ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(tree_id_);
}
void ViewsAXTreeManager::SetGeneratedEventCallbackForTesting(
......@@ -56,6 +60,9 @@ void ViewsAXTreeManager::UnsetGeneratedEventCallbackForTesting() {
ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
const ui::AXTreeID tree_id,
const ui::AXNode::AXID node_id) const {
if (!widget_)
return nullptr;
const ui::AXTreeManager* manager =
ui::AXTreeManagerMap::GetInstance().GetManager(tree_id);
return manager ? manager->GetNodeFromTree(node_id) : nullptr;
......@@ -63,11 +70,14 @@ ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
const ui::AXNode::AXID node_id) const {
if (!widget_)
return nullptr;
return ax_tree_.GetFromId(node_id);
}
ui::AXTreeID ViewsAXTreeManager::GetTreeID() const {
return ax_tree_.GetAXTreeID();
return tree_id_;
}
ui::AXTreeID ViewsAXTreeManager::GetParentTreeID() const {
......@@ -77,6 +87,9 @@ ui::AXTreeID ViewsAXTreeManager::GetParentTreeID() const {
}
ui::AXNode* ViewsAXTreeManager::GetRootAsAXNode() const {
if (!widget_)
return nullptr;
return ax_tree_.root();
}
......@@ -101,11 +114,31 @@ void ViewsAXTreeManager::OnViewEvent(View* view, ax::mojom::Event event) {
weak_factory_.GetWeakPtr()));
}
void ViewsAXTreeManager::OnWidgetDestroyed(Widget* widget) {
if (widget->is_top_level())
views::WidgetAXTreeIDMap::GetInstance().RemoveWidget(widget);
widget_ = nullptr;
}
void ViewsAXTreeManager::OnWidgetClosing(Widget* widget) {
if (widget->is_top_level())
views::WidgetAXTreeIDMap::GetInstance().RemoveWidget(widget);
widget_ = nullptr;
}
void ViewsAXTreeManager::PerformAction(const ui::AXActionData& data) {
if (!widget_)
return;
tree_source_.HandleAccessibleAction(data);
}
void ViewsAXTreeManager::SerializeTreeUpdates() {
if (!widget_)
return;
// Better to set this flag to false early in case this method, or any method
// it calls, causes an event to get fired.
waiting_to_serialize_ = false;
......@@ -139,6 +172,9 @@ void ViewsAXTreeManager::SerializeTreeUpdates() {
void ViewsAXTreeManager::UnserializeTreeUpdates(
const std::vector<ui::AXTreeUpdate>& updates) {
if (!widget_)
return;
for (const ui::AXTreeUpdate& update : updates) {
if (!ax_tree_.Unserialize(update)) {
NOTREACHED() << ax_tree_.error();
......
......@@ -11,7 +11,7 @@
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/scoped_observation.h"
#include "ui/accessibility/ax_action_handler.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/accessibility/ax_event_generator.h"
......@@ -27,6 +27,8 @@
#include "ui/views/accessibility/ax_event_observer.h"
#include "ui/views/accessibility/ax_tree_source_views.h"
#include "ui/views/views_export.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace ui {
......@@ -38,7 +40,6 @@ namespace views {
class AXAuraObjWrapper;
class View;
class Widget;
// Manages an accessibility tree that mirrors the Views tree for a particular
// widget.
......@@ -51,7 +52,8 @@ class Widget;
// deserialized into an AXTree, both in the same process.
class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
public ui::AXActionHandler,
public AXEventObserver {
public AXEventObserver,
public views::WidgetObserver {
public:
using GeneratedEventCallbackForTesting = base::RepeatingCallback<
void(Widget*, ui::AXEventGenerator::Event, ui::AXNode::AXID)>;
......@@ -93,6 +95,10 @@ class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
// AXEventObserver implementation.
void OnViewEvent(views::View* view, ax::mojom::Event event) override;
// WidgetObserver implementation.
void OnWidgetDestroyed(Widget* widget) override;
void OnWidgetClosing(Widget* widget) override;
private:
using ViewsAXTreeSerializer =
ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>;
......@@ -110,7 +116,7 @@ class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
// The Widget for which this class manages an AXTree.
//
// Weak, a Widget doesn't own this class.
Widget* const widget_;
Widget* widget_;
// Set to true if we are still waiting for a task to serialize all previously
// modified nodes.
......@@ -124,6 +130,13 @@ class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
// that are used to serialize the Views tree.
AXAuraObjCache cache_;
// The ID for this AXTree.
ui::AXTreeID tree_id_;
// The AXTree that mirrors the Views tree and which is created by
// deserializing the updates from |tree_source_|.
ui::AXTree ax_tree_;
// The tree source that enables us to serialize the Views tree.
AXTreeSourceViews tree_source_;
......@@ -131,10 +144,6 @@ class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
// AXTreeUpdate.
ViewsAXTreeSerializer tree_serializer_;
// The AXTree that mirrors the Views tree and which is created by
// deserializing the updates from |tree_source_|.
ui::AXTree ax_tree_;
// For automatically generating events based on changes to |tree_|.
ui::AXEventGenerator event_generator_;
......@@ -143,7 +152,9 @@ class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
// To prevent any use-after-free, members below this line should be declared
// last.
ScopedObserver<AXEventManager, AXEventObserver> views_event_observer_{this};
base::ScopedObservation<AXEventManager, AXEventObserver>
views_event_observer_{this};
base::ScopedObservation<Widget, views::WidgetObserver> widget_observer_{this};
base::WeakPtrFactory<ViewsAXTreeManager> weak_factory_{this};
};
......
......@@ -13,6 +13,8 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_event_generator.h"
#include "ui/accessibility/ax_node.h"
......@@ -41,7 +43,8 @@ class TestButton : public Button {
~TestButton() override = default;
};
class ViewsAXTreeManagerTest : public ViewsTestBase {
class ViewsAXTreeManagerTest : public ViewsTestBase,
public ::testing::WithParamInterface<bool> {
public:
ViewsAXTreeManagerTest() = default;
~ViewsAXTreeManagerTest() override = default;
......@@ -51,6 +54,7 @@ class ViewsAXTreeManagerTest : public ViewsTestBase {
protected:
void SetUp() override;
void TearDown() override;
void CloseWidget();
ui::AXNode* FindNode(const ax::mojom::Role role,
const std::string& name_or_value) const;
void WaitFor(const ui::AXEventGenerator::Event event);
......@@ -58,7 +62,7 @@ class ViewsAXTreeManagerTest : public ViewsTestBase {
Widget* widget() const { return widget_; }
Button* button() const { return button_; }
Label* label() const { return label_; }
const ViewsAXTreeManager& manager() const { return *manager_; }
ViewsAXTreeManager* manager() const { return manager_.get(); }
private:
ui::AXNode* FindNodeInSubtree(ui::AXNode* root,
......@@ -74,13 +78,20 @@ class ViewsAXTreeManagerTest : public ViewsTestBase {
std::unique_ptr<ViewsAXTreeManager> manager_;
ui::AXEventGenerator::Event event_to_wait_for_;
std::unique_ptr<base::RunLoop> loop_runner_;
base::test::ScopedFeatureList scoped_feature_list_;
};
void ViewsAXTreeManagerTest::SetUp() {
ViewsTestBase::SetUp();
if (GetParam()) {
scoped_feature_list_.InitWithFeatures(
{features::kEnableAccessibilityTreeForViews}, {});
}
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(std::move(params));
......@@ -93,26 +104,45 @@ void ViewsAXTreeManagerTest::SetUp() {
widget_->GetContentsView()->AddChildView(button_);
widget_->Show();
manager_ = std::make_unique<ViewsAXTreeManager>(widget_);
ASSERT_NE(nullptr, manager_.get());
manager_->SetGeneratedEventCallbackForTesting(base::BindRepeating(
// AccessibilityTreeForViewsEnabled will create and manage its own
// ViewsAXTreeManager, so we don't need to create one for testing.
if (features::IsAccessibilityTreeForViewsEnabled()) {
manager_.reset(
widget_->GetRootView()->GetViewAccessibility().AXTreeManager());
} else {
manager_ = std::make_unique<ViewsAXTreeManager>(widget_);
}
ASSERT_NE(nullptr, manager_);
manager()->SetGeneratedEventCallbackForTesting(base::BindRepeating(
&ViewsAXTreeManagerTest::OnGeneratedEvent, base::Unretained(this)));
WaitFor(ui::AXEventGenerator::Event::LOAD_COMPLETE);
}
void ViewsAXTreeManagerTest::TearDown() {
manager_->UnsetGeneratedEventCallbackForTesting();
if (manager())
manager()->UnsetGeneratedEventCallbackForTesting();
manager_.reset();
if (!widget_->IsClosed())
widget_->Close();
CloseWidget();
ViewsTestBase::TearDown();
}
void ViewsAXTreeManagerTest::CloseWidget() {
if (!widget_->IsClosed())
widget_->CloseNow();
RunPendingMessages();
}
ui::AXNode* ViewsAXTreeManagerTest::FindNode(
const ax::mojom::Role role,
const std::string& name_or_value) const {
ui::AXNode* root = manager_->GetRootAsAXNode();
EXPECT_NE(nullptr, root);
ui::AXNode* root = manager()->GetRootAsAXNode();
// If the manager has been closed, it will return nullptr as root.
if (!root)
return nullptr;
return FindNodeInSubtree(root, role, name_or_value);
}
......@@ -149,15 +179,16 @@ ui::AXNode* ViewsAXTreeManagerTest::FindNodeInSubtree(
void ViewsAXTreeManagerTest::OnGeneratedEvent(Widget* widget,
ui::AXEventGenerator::Event event,
ui::AXNode::AXID node_id) {
ASSERT_NE(nullptr, manager_.get())
<< "Should not be called after TearDown().";
ASSERT_NE(nullptr, manager()) << "Should not be called after TearDown().";
if (loop_runner_ && event == event_to_wait_for_)
loop_runner_->Quit();
}
} // namespace
TEST_F(ViewsAXTreeManagerTest, MirrorInitialTree) {
INSTANTIATE_TEST_SUITE_P(All, ViewsAXTreeManagerTest, testing::Bool());
TEST_P(ViewsAXTreeManagerTest, MirrorInitialTree) {
ui::AXNodeData button_data;
button()->GetViewAccessibility().GetAccessibleNodeData(&button_data);
ui::AXNode* ax_button = FindNode(ax::mojom::Role::kButton, "");
......@@ -174,7 +205,7 @@ TEST_F(ViewsAXTreeManagerTest, MirrorInitialTree) {
EXPECT_TRUE(ax_button->data().HasState(ax::mojom::State::kFocusable));
}
TEST_F(ViewsAXTreeManagerTest, PerformAction) {
TEST_P(ViewsAXTreeManagerTest, PerformAction) {
ui::AXNode* ax_button = FindNode(ax::mojom::Role::kButton, "");
ASSERT_NE(nullptr, ax_button);
ASSERT_FALSE(ax_button->data().HasIntAttribute(
......@@ -185,5 +216,22 @@ TEST_F(ViewsAXTreeManagerTest, PerformAction) {
WaitFor(ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED);
}
TEST_P(ViewsAXTreeManagerTest, CloseWidget) {
// This test is only relevant when IsAccessibilityTreeForViewsEnabled is set,
// as it tests the lifetime management of ViewsAXTreeManager when a Widget is
// closed.
if (!features::IsAccessibilityTreeForViewsEnabled())
return;
ui::AXNode* ax_button = FindNode(ax::mojom::Role::kButton, "");
ASSERT_NE(nullptr, ax_button);
CloseWidget();
// Looking up a node after its Widget has been closed should return nullptr.
ax_button = FindNode(ax::mojom::Role::kButton, "");
EXPECT_EQ(nullptr, ax_button);
}
} // namespace test
} // namespace views
// 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 "ui/views/accessibility/widget_ax_tree_id_map.h"
#include "base/stl_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
namespace views {
WidgetAXTreeIDMap::WidgetAXTreeIDMap() {}
WidgetAXTreeIDMap::~WidgetAXTreeIDMap() {}
// static
WidgetAXTreeIDMap& WidgetAXTreeIDMap::GetInstance() {
static base::NoDestructor<WidgetAXTreeIDMap> instance;
return *instance;
}
bool WidgetAXTreeIDMap::HasWidget(Widget* widget) {
return base::Contains(widget_map_, widget);
}
void WidgetAXTreeIDMap::AddWidget(ui::AXTreeID tree_id, Widget* widget) {
DCHECK_NE(tree_id, ui::AXTreeIDUnknown());
DCHECK(widget);
DCHECK(!HasWidget(widget));
widget_map_[widget] = tree_id;
}
void WidgetAXTreeIDMap::RemoveWidget(Widget* widget) {
widget_map_.erase(widget);
}
ui::AXTreeID WidgetAXTreeIDMap::GetWidgetTreeID(Widget* widget) {
DCHECK(widget);
if (!base::Contains(widget_map_, widget))
return ui::AXTreeIDUnknown();
return widget_map_.at(widget);
}
const std::vector<Widget*> WidgetAXTreeIDMap::GetWidgets() const {
std::vector<Widget*> widgets;
widgets.reserve(widget_map_.size());
for (auto iter : widget_map_)
widgets.push_back(iter.first);
return widgets;
}
} // namespace views
// 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.
#ifndef UI_VIEWS_ACCESSIBILITY_WIDGET_AX_TREE_ID_MAP_H_
#define UI_VIEWS_ACCESSIBILITY_WIDGET_AX_TREE_ID_MAP_H_
#include <map>
#include <vector>
#include "base/macros.h"
#include "base/no_destructor.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
#include "ui/views/views_export.h"
namespace views {
class Widget;
// This class manages mapping between Widgets and their associated AXTreeIDs.
// It is a singleton wrapper around a std::map. Widget pointers are used as the
// key for the map and AXTreeID's are used as the value returned.
class VIEWS_EXPORT WidgetAXTreeIDMap {
public:
WidgetAXTreeIDMap();
~WidgetAXTreeIDMap();
static WidgetAXTreeIDMap& GetInstance();
bool HasWidget(Widget* widget);
void AddWidget(ui::AXTreeID tree_id, Widget* widget);
void RemoveWidget(Widget* widget);
ui::AXTreeID GetWidgetTreeID(views::Widget* widget);
const std::vector<Widget*> GetWidgets() const;
private:
std::map<Widget*, ui::AXTreeID> widget_map_;
};
} // namespace views
#endif // UI_VIEWS_ACCESSIBILITY_WIDGET_AX_TREE_ID_MAP_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