Commit 8ddf0a69 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Be more strict when performing rect-based sorting

On further scrutiny of the Android codebase, the following constraints get used to identify |IsAccessibilityFocusable|, which gets used to decide whether a node contributes to the computed bounds of a node or not.

Any of these conditions makes a node "accessibility focusable", used here to mean "should use its rect" when sorting for child ordering
- actionable nodes
- top level scrollables with text

In ARC++, we cannot seem to rely entirely upon replicating the exact logic, but keep the spirit of what is meant. To that end, we want to sort nodes by layout.

This change gets much closer to what we want by:
- allowing computed bounds to be empty (meaning it has no meaningful content). In these cases, we simply rely upon tree ordering
- be very conservative when using a node's bounds. Only actionable nodes should matter. This potentially misses non-focusable nodes with text, or non-clickable nodes with text. However, mis-sorting a node is much much worse.

This change does not consider virtual hierarchies, which should likely not trigger reodering, since an author has explicit contorl over child ordering in those cases.

Change-Id: Iacffc5f2d88b6d52b732dadda8a01fd735665018
Reviewed-on: https://chromium-review.googlesource.com/989657
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547790}
parent ddea0efc
...@@ -306,6 +306,8 @@ source_set("chromeos") { ...@@ -306,6 +306,8 @@ source_set("chromeos") {
"apps/intent_helper/apps_navigation_throttle.h", "apps/intent_helper/apps_navigation_throttle.h",
"arc/accessibility/arc_accessibility_helper_bridge.cc", "arc/accessibility/arc_accessibility_helper_bridge.cc",
"arc/accessibility/arc_accessibility_helper_bridge.h", "arc/accessibility/arc_accessibility_helper_bridge.h",
"arc/accessibility/arc_accessibility_util.cc",
"arc/accessibility/arc_accessibility_util.h",
"arc/accessibility/ax_tree_source_arc.cc", "arc/accessibility/ax_tree_source_arc.cc",
"arc/accessibility/ax_tree_source_arc.h", "arc/accessibility/ax_tree_source_arc.h",
"arc/arc_migration_constants.h", "arc/arc_migration_constants.h",
......
dtseng@chromium.org dtseng@chromium.org
yawano@chromium.org yawano@chromium.org
file://ui/accessibility/OWNERS
# TEAM: chromium-accessibility@chromium.org
# COMPONENT: UI>Accessibility
// Copyright 2018 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 "chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.h"
#include "components/arc/common/accessibility_helper.mojom.h"
namespace arc {
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityBooleanProperty prop) {
if (!node->boolean_properties)
return false;
auto it = node->boolean_properties->find(prop);
if (it == node->boolean_properties->end())
return false;
return it->second;
}
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityIntProperty prop,
int32_t* out_value) {
if (!node->int_properties)
return false;
auto it = node->int_properties->find(prop);
if (it == node->int_properties->end())
return false;
*out_value = it->second;
return true;
}
bool HasProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringProperty prop) {
if (!node->string_properties)
return false;
auto it = node->string_properties->find(prop);
return it != node->string_properties->end();
}
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringProperty prop,
std::string* out_value) {
if (!HasProperty(node, prop))
return false;
auto it = node->string_properties->find(prop);
*out_value = it->second;
return true;
}
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityIntListProperty prop,
std::vector<int32_t>* out_value) {
if (!node->int_list_properties)
return false;
auto it = node->int_list_properties->find(prop);
if (it == node->int_list_properties->end())
return false;
*out_value = it->second;
return true;
}
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringListProperty prop,
std::vector<std::string>* out_value) {
if (!node->string_list_properties)
return false;
auto it = node->string_list_properties->find(prop);
if (it == node->string_list_properties->end())
return false;
*out_value = it->second;
return true;
}
} // namespace arc
// Copyright 2018 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 CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_ARC_ACCESSIBILITY_UTIL_H_
#define CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_ARC_ACCESSIBILITY_UTIL_H_
#include <stdint.h>
#include <string>
#include <vector>
namespace arc {
namespace mojom {
class AccessibilityNodeInfoData;
enum class AccessibilityBooleanProperty;
enum class AccessibilityIntProperty;
enum class AccessibilityStringProperty;
enum class AccessibilityIntListProperty;
enum class AccessibilityStringListProperty;
} // namespace mojom
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityBooleanProperty prop);
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityIntProperty prop,
int32_t* out_value);
bool HasProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringProperty prop);
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringProperty prop,
std::string* out_value);
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityIntListProperty prop,
std::vector<int32_t>* out_value);
bool GetProperty(mojom::AccessibilityNodeInfoData* node,
mojom::AccessibilityStringListProperty prop,
std::vector<std::string>* out_value);
} // namespace arc
#endif // CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_ARC_ACCESSIBILITY_UTIL_H_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <string> #include <string>
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.h"
#include "chrome/browser/extensions/api/automation_internal/automation_event_router.h" #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "chrome/common/extensions/chrome_extension_messages.h" #include "chrome/common/extensions/chrome_extension_messages.h"
...@@ -18,43 +19,54 @@ ...@@ -18,43 +19,54 @@
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
namespace { namespace arc {
ax::mojom::Event ToAXEvent(arc::mojom::AccessibilityEventType arc_event_type) { using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXCollectionInfoData = mojom::AccessibilityCollectionInfoData;
using AXCollectionItemInfoData = mojom::AccessibilityCollectionItemInfoData;
using AXEventData = mojom::AccessibilityEventData;
using AXEventType = mojom::AccessibilityEventType;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
using AXIntProperty = mojom::AccessibilityIntProperty;
using AXNodeInfoData = mojom::AccessibilityNodeInfoData;
using AXRangeInfoData = mojom::AccessibilityRangeInfoData;
using AXStringListProperty = mojom::AccessibilityStringListProperty;
using AXStringProperty = mojom::AccessibilityStringProperty;
ax::mojom::Event ToAXEvent(AXEventType arc_event_type) {
switch (arc_event_type) { switch (arc_event_type) {
case arc::mojom::AccessibilityEventType::VIEW_FOCUSED: case AXEventType::VIEW_FOCUSED:
case arc::mojom::AccessibilityEventType::VIEW_ACCESSIBILITY_FOCUSED: case AXEventType::VIEW_ACCESSIBILITY_FOCUSED:
return ax::mojom::Event::kFocus; return ax::mojom::Event::kFocus;
case arc::mojom::AccessibilityEventType::VIEW_CLICKED: case AXEventType::VIEW_CLICKED:
case arc::mojom::AccessibilityEventType::VIEW_LONG_CLICKED: case AXEventType::VIEW_LONG_CLICKED:
return ax::mojom::Event::kClicked; return ax::mojom::Event::kClicked;
case arc::mojom::AccessibilityEventType::VIEW_TEXT_CHANGED: case AXEventType::VIEW_TEXT_CHANGED:
return ax::mojom::Event::kTextChanged; return ax::mojom::Event::kTextChanged;
case arc::mojom::AccessibilityEventType::VIEW_TEXT_SELECTION_CHANGED: case AXEventType::VIEW_TEXT_SELECTION_CHANGED:
return ax::mojom::Event::kTextSelectionChanged; return ax::mojom::Event::kTextSelectionChanged;
case arc::mojom::AccessibilityEventType::WINDOW_STATE_CHANGED: case AXEventType::WINDOW_STATE_CHANGED:
case arc::mojom::AccessibilityEventType::NOTIFICATION_STATE_CHANGED: case AXEventType::NOTIFICATION_STATE_CHANGED:
case arc::mojom::AccessibilityEventType::WINDOW_CONTENT_CHANGED: case AXEventType::WINDOW_CONTENT_CHANGED:
case arc::mojom::AccessibilityEventType::WINDOWS_CHANGED: case AXEventType::WINDOWS_CHANGED:
return ax::mojom::Event::kLayoutComplete; return ax::mojom::Event::kLayoutComplete;
case arc::mojom::AccessibilityEventType::VIEW_HOVER_ENTER: case AXEventType::VIEW_HOVER_ENTER:
return ax::mojom::Event::kHover; return ax::mojom::Event::kHover;
case arc::mojom::AccessibilityEventType::ANNOUNCEMENT: case AXEventType::ANNOUNCEMENT:
return ax::mojom::Event::kAlert; return ax::mojom::Event::kAlert;
case arc::mojom::AccessibilityEventType::VIEW_SCROLLED: case AXEventType::VIEW_SCROLLED:
return ax::mojom::Event::kScrollPositionChanged; return ax::mojom::Event::kScrollPositionChanged;
case arc::mojom::AccessibilityEventType::VIEW_SELECTED: case AXEventType::VIEW_SELECTED:
case arc::mojom::AccessibilityEventType::VIEW_HOVER_EXIT: case AXEventType::VIEW_HOVER_EXIT:
case arc::mojom::AccessibilityEventType::TOUCH_EXPLORATION_GESTURE_START: case AXEventType::TOUCH_EXPLORATION_GESTURE_START:
case arc::mojom::AccessibilityEventType::TOUCH_EXPLORATION_GESTURE_END: case AXEventType::TOUCH_EXPLORATION_GESTURE_END:
case arc::mojom::AccessibilityEventType:: case AXEventType::VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: case AXEventType::GESTURE_DETECTION_START:
case arc::mojom::AccessibilityEventType::GESTURE_DETECTION_START: case AXEventType::GESTURE_DETECTION_END:
case arc::mojom::AccessibilityEventType::GESTURE_DETECTION_END: case AXEventType::TOUCH_INTERACTION_START:
case arc::mojom::AccessibilityEventType::TOUCH_INTERACTION_START: case AXEventType::TOUCH_INTERACTION_END:
case arc::mojom::AccessibilityEventType::TOUCH_INTERACTION_END: case AXEventType::VIEW_CONTEXT_CLICKED:
case arc::mojom::AccessibilityEventType::VIEW_CONTEXT_CLICKED: case AXEventType::ASSIST_READING_CONTEXT:
case arc::mojom::AccessibilityEventType::ASSIST_READING_CONTEXT:
return ax::mojom::Event::kChildrenChanged; return ax::mojom::Event::kChildrenChanged;
default: default:
return ax::mojom::Event::kChildrenChanged; return ax::mojom::Event::kChildrenChanged;
...@@ -62,82 +74,14 @@ ax::mojom::Event ToAXEvent(arc::mojom::AccessibilityEventType arc_event_type) { ...@@ -62,82 +74,14 @@ ax::mojom::Event ToAXEvent(arc::mojom::AccessibilityEventType arc_event_type) {
return ax::mojom::Event::kChildrenChanged; return ax::mojom::Event::kChildrenChanged;
} }
bool GetBooleanProperty(arc::mojom::AccessibilityNodeInfoData* node, bool HasCoveringSpan(AXNodeInfoData* data,
arc::mojom::AccessibilityBooleanProperty prop) { AXStringProperty prop,
if (!node->boolean_properties) mojom::SpanType span_type) {
return false;
auto it = node->boolean_properties->find(prop);
if (it == node->boolean_properties->end())
return false;
return it->second;
}
bool GetIntProperty(arc::mojom::AccessibilityNodeInfoData* node,
arc::mojom::AccessibilityIntProperty prop,
int32_t* out_value) {
if (!node->int_properties)
return false;
auto it = node->int_properties->find(prop);
if (it == node->int_properties->end())
return false;
*out_value = it->second;
return true;
}
bool GetStringProperty(arc::mojom::AccessibilityNodeInfoData* node,
arc::mojom::AccessibilityStringProperty prop,
std::string* out_value) {
if (!node->string_properties)
return false;
auto it = node->string_properties->find(prop);
if (it == node->string_properties->end())
return false;
*out_value = it->second;
return true;
}
bool GetIntListProperty(arc::mojom::AccessibilityNodeInfoData* node,
arc::mojom::AccessibilityIntListProperty prop,
std::vector<int32_t>* out_value) {
if (!node->int_list_properties)
return false;
auto it = node->int_list_properties->find(prop);
if (it == node->int_list_properties->end())
return false;
*out_value = it->second;
return true;
}
bool GetStringListProperty(arc::mojom::AccessibilityNodeInfoData* node,
arc::mojom::AccessibilityStringListProperty prop,
std::vector<std::string>* out_value) {
if (!node->string_list_properties)
return false;
auto it = node->string_list_properties->find(prop);
if (it == node->string_list_properties->end())
return false;
*out_value = it->second;
return true;
}
bool HasCoveringSpan(arc::mojom::AccessibilityNodeInfoData* data,
arc::mojom::AccessibilityStringProperty prop,
arc::mojom::SpanType span_type) {
if (!data->spannable_string_properties) if (!data->spannable_string_properties)
return false; return false;
std::string text; std::string text;
GetStringProperty(data, prop, &text); GetProperty(data, prop, &text);
if (text.empty()) if (text.empty())
return false; return false;
...@@ -157,32 +101,26 @@ bool HasCoveringSpan(arc::mojom::AccessibilityNodeInfoData* data, ...@@ -157,32 +101,26 @@ bool HasCoveringSpan(arc::mojom::AccessibilityNodeInfoData* data,
return false; return false;
} }
void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node, void PopulateAXRole(AXNodeInfoData* node, ui::AXNodeData* out_data) {
ui::AXNodeData* out_data) {
std::string class_name; std::string class_name;
if (GetStringProperty(node, if (GetProperty(node, AXStringProperty::CLASS_NAME, &class_name)) {
arc::mojom::AccessibilityStringProperty::CLASS_NAME,
&class_name)) {
out_data->AddStringAttribute(ax::mojom::StringAttribute::kClassName, out_data->AddStringAttribute(ax::mojom::StringAttribute::kClassName,
class_name); class_name);
} }
if (GetBooleanProperty(node, if (GetProperty(node, AXBooleanProperty::EDITABLE)) {
arc::mojom::AccessibilityBooleanProperty::EDITABLE)) {
out_data->role = ax::mojom::Role::kTextField; out_data->role = ax::mojom::Role::kTextField;
return; return;
} }
if (HasCoveringSpan(node, arc::mojom::AccessibilityStringProperty::TEXT, if (HasCoveringSpan(node, AXStringProperty::TEXT, mojom::SpanType::URL) ||
arc::mojom::SpanType::URL) || HasCoveringSpan(node, AXStringProperty::CONTENT_DESCRIPTION,
HasCoveringSpan( mojom::SpanType::URL)) {
node, arc::mojom::AccessibilityStringProperty::CONTENT_DESCRIPTION,
arc::mojom::SpanType::URL)) {
out_data->role = ax::mojom::Role::kLink; out_data->role = ax::mojom::Role::kLink;
return; return;
} }
arc::mojom::AccessibilityCollectionItemInfoData* collection_item_info = AXCollectionItemInfoData* collection_item_info =
node->collection_item_info.get(); node->collection_item_info.get();
if (collection_item_info) { if (collection_item_info) {
if (collection_item_info->is_heading) { if (collection_item_info->is_heading) {
...@@ -196,9 +134,7 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node, ...@@ -196,9 +134,7 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node,
} }
std::string chrome_role; std::string chrome_role;
if (GetStringProperty(node, if (GetProperty(node, AXStringProperty::CHROME_ROLE, &chrome_role)) {
arc::mojom::AccessibilityStringProperty::CHROME_ROLE,
&chrome_role)) {
ax::mojom::Role role_value = ui::ParseRole(chrome_role.c_str()); ax::mojom::Role role_value = ui::ParseRole(chrome_role.c_str());
if (role_value != ax::mojom::Role::kNone) { if (role_value != ax::mojom::Role::kNone) {
// The webView and rootWebArea roles differ between Android and Chrome. In // The webView and rootWebArea roles differ between Android and Chrome. In
...@@ -231,8 +167,7 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node, ...@@ -231,8 +167,7 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node,
MAP_ROLE(ui::kAXGridViewClassname, ax::mojom::Role::kTable); MAP_ROLE(ui::kAXGridViewClassname, ax::mojom::Role::kTable);
MAP_ROLE(ui::kAXImageClassname, ax::mojom::Role::kImage); MAP_ROLE(ui::kAXImageClassname, ax::mojom::Role::kImage);
MAP_ROLE(ui::kAXImageButtonClassname, ax::mojom::Role::kButton); MAP_ROLE(ui::kAXImageButtonClassname, ax::mojom::Role::kButton);
if (GetBooleanProperty(node, if (GetProperty(node, AXBooleanProperty::CLICKABLE)) {
arc::mojom::AccessibilityBooleanProperty::CLICKABLE)) {
MAP_ROLE(ui::kAXImageViewClassname, ax::mojom::Role::kButton); MAP_ROLE(ui::kAXImageViewClassname, ax::mojom::Role::kButton);
} else { } else {
MAP_ROLE(ui::kAXImageViewClassname, ax::mojom::Role::kImage); MAP_ROLE(ui::kAXImageViewClassname, ax::mojom::Role::kImage);
...@@ -253,21 +188,18 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node, ...@@ -253,21 +188,18 @@ void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node,
#undef MAP_ROLE #undef MAP_ROLE
std::string text; std::string text;
GetStringProperty(node, arc::mojom::AccessibilityStringProperty::TEXT, &text); GetProperty(node, AXStringProperty::TEXT, &text);
if (!text.empty()) if (!text.empty())
out_data->role = ax::mojom::Role::kStaticText; out_data->role = ax::mojom::Role::kStaticText;
else else
out_data->role = ax::mojom::Role::kGenericContainer; out_data->role = ax::mojom::Role::kGenericContainer;
} }
void PopulateAXState(arc::mojom::AccessibilityNodeInfoData* node, void PopulateAXState(AXNodeInfoData* node, ui::AXNodeData* out_data) {
ui::AXNodeData* out_data) {
#define MAP_STATE(android_boolean_property, chrome_state) \ #define MAP_STATE(android_boolean_property, chrome_state) \
if (GetBooleanProperty(node, android_boolean_property)) \ if (GetProperty(node, android_boolean_property)) \
out_data->AddState(chrome_state); out_data->AddState(chrome_state);
using AXBooleanProperty = arc::mojom::AccessibilityBooleanProperty;
// These mappings were taken from accessibility utils (Android -> Chrome) and // These mappings were taken from accessibility utils (Android -> Chrome) and
// BrowserAccessibilityAndroid. They do not completely match the above two // BrowserAccessibilityAndroid. They do not completely match the above two
// sources. // sources.
...@@ -279,26 +211,21 @@ void PopulateAXState(arc::mojom::AccessibilityNodeInfoData* node, ...@@ -279,26 +211,21 @@ void PopulateAXState(arc::mojom::AccessibilityNodeInfoData* node,
#undef MAP_STATE #undef MAP_STATE
if (GetBooleanProperty(node, AXBooleanProperty::CHECKABLE)) { if (GetProperty(node, AXBooleanProperty::CHECKABLE)) {
const bool is_checked = const bool is_checked = GetProperty(node, AXBooleanProperty::CHECKED);
GetBooleanProperty(node, AXBooleanProperty::CHECKED);
out_data->SetCheckedState(is_checked ? ax::mojom::CheckedState::kTrue out_data->SetCheckedState(is_checked ? ax::mojom::CheckedState::kTrue
: ax::mojom::CheckedState::kFalse); : ax::mojom::CheckedState::kFalse);
} }
if (!GetBooleanProperty(node, AXBooleanProperty::ENABLED)) { if (!GetProperty(node, AXBooleanProperty::ENABLED)) {
out_data->SetRestriction(ax::mojom::Restriction::kDisabled); out_data->SetRestriction(ax::mojom::Restriction::kDisabled);
} }
if (!GetBooleanProperty(node, AXBooleanProperty::VISIBLE_TO_USER)) { if (!GetProperty(node, AXBooleanProperty::VISIBLE_TO_USER)) {
out_data->AddState(ax::mojom::State::kInvisible); out_data->AddState(ax::mojom::State::kInvisible);
} }
} }
} // namespace
namespace arc {
// This class keeps focus on a |ShellSurface| without interfering with default // This class keeps focus on a |ShellSurface| without interfering with default
// focus management in |ShellSurface|. For example, touch causes the // focus management in |ShellSurface|. For example, touch causes the
// |ShellSurface| to lose focus to its ancestor containing View. // |ShellSurface| to lose focus to its ancestor containing View.
...@@ -335,8 +262,7 @@ AXTreeSourceArc::~AXTreeSourceArc() { ...@@ -335,8 +262,7 @@ AXTreeSourceArc::~AXTreeSourceArc() {
Reset(); Reset();
} }
void AXTreeSourceArc::NotifyAccessibilityEvent( void AXTreeSourceArc::NotifyAccessibilityEvent(AXEventData* event_data) {
mojom::AccessibilityEventData* event_data) {
tree_map_.clear(); tree_map_.clear();
parent_map_.clear(); parent_map_.clear();
cached_computed_bounds_.clear(); cached_computed_bounds_.clear();
...@@ -355,7 +281,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent( ...@@ -355,7 +281,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent(
if (!event_data->node_data[i]->int_list_properties) if (!event_data->node_data[i]->int_list_properties)
continue; continue;
auto it = event_data->node_data[i]->int_list_properties->find( auto it = event_data->node_data[i]->int_list_properties->find(
arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS); AXIntListProperty::CHILD_NODE_IDS);
if (it != event_data->node_data[i]->int_list_properties->end()) { if (it != event_data->node_data[i]->int_list_properties->end()) {
for (size_t j = 0; j < it->second.size(); ++j) for (size_t j = 0; j < it->second.size(); ++j)
parent_map_[it->second[j]] = event_data->node_data[i]->id; parent_map_[it->second[j]] = event_data->node_data[i]->id;
...@@ -364,15 +290,14 @@ void AXTreeSourceArc::NotifyAccessibilityEvent( ...@@ -364,15 +290,14 @@ void AXTreeSourceArc::NotifyAccessibilityEvent(
for (size_t i = 0; i < event_data->node_data.size(); ++i) { for (size_t i = 0; i < event_data->node_data.size(); ++i) {
int32_t id = event_data->node_data[i]->id; int32_t id = event_data->node_data[i]->id;
mojom::AccessibilityNodeInfoData* node = event_data->node_data[i].get(); AXNodeInfoData* node = event_data->node_data[i].get();
tree_map_[id] = node; tree_map_[id] = node;
if (parent_map_.find(id) == parent_map_.end()) { if (parent_map_.find(id) == parent_map_.end()) {
CHECK_EQ(-1, root_id_) << "Duplicated root"; CHECK_EQ(-1, root_id_) << "Duplicated root";
root_id_ = id; root_id_ = id;
} }
if (GetBooleanProperty(node, if (GetProperty(node, AXBooleanProperty::FOCUSED)) {
arc::mojom::AccessibilityBooleanProperty::FOCUSED)) {
focused_node_id_ = id; focused_node_id_ = id;
} }
} }
...@@ -381,7 +306,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent( ...@@ -381,7 +306,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent(
// avoid an O(n^2) amount of work as the computed bounds uses descendant // avoid an O(n^2) amount of work as the computed bounds uses descendant
// bounds. // bounds.
for (int i = event_data->node_data.size() - 1; i >= 0; --i) { for (int i = event_data->node_data.size() - 1; i >= 0; --i) {
mojom::AccessibilityNodeInfoData* node = event_data->node_data[i].get(); AXNodeInfoData* node = event_data->node_data[i].get();
cached_computed_bounds_[node] = ComputeEnclosingBounds(node); cached_computed_bounds_[node] = ComputeEnclosingBounds(node);
} }
...@@ -395,8 +320,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent( ...@@ -395,8 +320,7 @@ void AXTreeSourceArc::NotifyAccessibilityEvent(
ui::AXTreeUpdate update; ui::AXTreeUpdate update;
if (event_data->event_type == if (event_data->event_type == AXEventType::WINDOW_CONTENT_CHANGED) {
arc::mojom::AccessibilityEventType::WINDOW_CONTENT_CHANGED) {
current_tree_serializer_->DeleteClientSubtree( current_tree_serializer_->DeleteClientSubtree(
GetFromId(event_data->source_id)); GetFromId(event_data->source_id));
} }
...@@ -438,45 +362,51 @@ bool AXTreeSourceArc::GetTreeData(ui::AXTreeData* data) const { ...@@ -438,45 +362,51 @@ bool AXTreeSourceArc::GetTreeData(ui::AXTreeData* data) const {
return true; return true;
} }
mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetRoot() const { AXNodeInfoData* AXTreeSourceArc::GetRoot() const {
mojom::AccessibilityNodeInfoData* root = GetFromId(root_id_); AXNodeInfoData* root = GetFromId(root_id_);
return root; return root;
} }
mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetFromId(int32_t id) const { AXNodeInfoData* AXTreeSourceArc::GetFromId(int32_t id) const {
auto it = tree_map_.find(id); auto it = tree_map_.find(id);
if (it == tree_map_.end()) if (it == tree_map_.end())
return nullptr; return nullptr;
return it->second; return it->second;
} }
int32_t AXTreeSourceArc::GetId(mojom::AccessibilityNodeInfoData* node) const { int32_t AXTreeSourceArc::GetId(AXNodeInfoData* node) const {
if (!node) if (!node)
return -1; return -1;
return node->id; return node->id;
} }
void AXTreeSourceArc::GetChildren( void AXTreeSourceArc::GetChildren(
mojom::AccessibilityNodeInfoData* node, AXNodeInfoData* node,
std::vector<mojom::AccessibilityNodeInfoData*>* out_children) const { std::vector<AXNodeInfoData*>* out_children) const {
if (!node || !node->int_list_properties) if (!node || !node->int_list_properties)
return; return;
auto it = node->int_list_properties->find( auto it = node->int_list_properties->find(AXIntListProperty::CHILD_NODE_IDS);
arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS);
if (it == node->int_list_properties->end()) if (it == node->int_list_properties->end())
return; return;
for (size_t i = 0; i < it->second.size(); ++i) std::map<int32_t, size_t> id_to_index;
out_children->push_back(GetFromId(it->second[i])); for (size_t i = 0; i < it->second.size(); ++i) {
AXNodeInfoData* child = GetFromId(it->second[i]);
out_children->push_back(child);
id_to_index[child->id] = i;
}
// Sort children based on their enclosing bounding rectangles, based on their // Sort children based on their enclosing bounding rectangles, based on their
// descendants. // descendants.
std::sort(out_children->begin(), out_children->end(), std::sort(out_children->begin(), out_children->end(),
[this](auto left, auto right) { [this, id_to_index](auto left, auto right) {
auto left_bounds = ComputeEnclosingBounds(left); auto left_bounds = ComputeEnclosingBounds(left);
auto right_bounds = ComputeEnclosingBounds(right); auto right_bounds = ComputeEnclosingBounds(right);
if (left_bounds.IsEmpty() || right_bounds.IsEmpty())
return id_to_index.at(left->id) < id_to_index.at(right->id);
// Top to bottom sort (non-overlapping). // Top to bottom sort (non-overlapping).
if (!left_bounds.Intersects(right_bounds)) if (!left_bounds.Intersects(right_bounds))
return left_bounds.y() < right_bounds.y(); return left_bounds.y() < right_bounds.y();
...@@ -503,12 +433,11 @@ void AXTreeSourceArc::GetChildren( ...@@ -503,12 +433,11 @@ void AXTreeSourceArc::GetChildren(
return width_difference > 0; return width_difference > 0;
// The rects are equal. // The rects are equal.
return false; return id_to_index.at(left->id) < id_to_index.at(right->id);
}); });
} }
mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetParent( AXNodeInfoData* AXTreeSourceArc::GetParent(AXNodeInfoData* node) const {
mojom::AccessibilityNodeInfoData* node) const {
if (!node) if (!node)
return nullptr; return nullptr;
auto it = parent_map_.find(node->id); auto it = parent_map_.find(node->id);
...@@ -517,22 +446,22 @@ mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetParent( ...@@ -517,22 +446,22 @@ mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetParent(
return nullptr; return nullptr;
} }
bool AXTreeSourceArc::IsValid(mojom::AccessibilityNodeInfoData* node) const { bool AXTreeSourceArc::IsValid(AXNodeInfoData* node) const {
return node; return node;
} }
bool AXTreeSourceArc::IsEqual(mojom::AccessibilityNodeInfoData* node1, bool AXTreeSourceArc::IsEqual(AXNodeInfoData* node1,
mojom::AccessibilityNodeInfoData* node2) const { AXNodeInfoData* node2) const {
if (!node1 || !node2) if (!node1 || !node2)
return false; return false;
return node1->id == node2->id; return node1->id == node2->id;
} }
mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetNull() const { AXNodeInfoData* AXTreeSourceArc::GetNull() const {
return nullptr; return nullptr;
} }
void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, void AXTreeSourceArc::SerializeNode(AXNodeInfoData* node,
ui::AXNodeData* out_data) const { ui::AXNodeData* out_data) const {
if (!node) if (!node)
return; return;
...@@ -544,10 +473,10 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -544,10 +473,10 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
else else
PopulateAXRole(node, out_data); PopulateAXRole(node, out_data);
using AXIntListProperty = arc::mojom::AccessibilityIntListProperty; using AXIntListProperty = AXIntListProperty;
using AXIntProperty = arc::mojom::AccessibilityIntProperty; using AXIntProperty = AXIntProperty;
using AXStringListProperty = arc::mojom::AccessibilityStringListProperty; using AXStringListProperty = AXStringListProperty;
using AXStringProperty = arc::mojom::AccessibilityStringProperty; using AXStringProperty = AXStringProperty;
// String properties. // String properties.
int labelled_by = -1; int labelled_by = -1;
...@@ -556,13 +485,12 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -556,13 +485,12 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
// description, text, or labelled by text. // description, text, or labelled by text.
std::string name; std::string name;
bool has_name = bool has_name =
GetStringProperty(node, AXStringProperty::CONTENT_DESCRIPTION, &name); GetProperty(node, AXStringProperty::CONTENT_DESCRIPTION, &name);
if (name.empty()) if (name.empty())
has_name |= GetStringProperty(node, AXStringProperty::TEXT, &name); has_name |= GetProperty(node, AXStringProperty::TEXT, &name);
if (name.empty() && if (name.empty() &&
GetIntProperty(node, arc::mojom::AccessibilityIntProperty::LABELED_BY, GetProperty(node, AXIntProperty::LABELED_BY, &labelled_by)) {
&labelled_by)) { AXNodeInfoData* labelled_by_node = GetFromId(labelled_by);
mojom::AccessibilityNodeInfoData* labelled_by_node = GetFromId(labelled_by);
if (labelled_by_node) { if (labelled_by_node) {
ui::AXNodeData labelled_by_data; ui::AXNodeData labelled_by_data;
SerializeNode(labelled_by_node, &labelled_by_data); SerializeNode(labelled_by_node, &labelled_by_data);
...@@ -579,7 +507,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -579,7 +507,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
} }
std::string role_description; std::string role_description;
if (GetStringProperty(node, AXStringProperty::ROLE_DESCRIPTION, if (GetProperty(node, AXStringProperty::ROLE_DESCRIPTION,
&role_description)) { &role_description)) {
out_data->AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription, out_data->AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
role_description); role_description);
...@@ -587,8 +515,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -587,8 +515,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
if (out_data->role == ax::mojom::Role::kRootWebArea) { if (out_data->role == ax::mojom::Role::kRootWebArea) {
std::string package_name; std::string package_name;
if (GetStringProperty(node, AXStringProperty::PACKAGE_NAME, if (GetProperty(node, AXStringProperty::PACKAGE_NAME, &package_name)) {
&package_name)) {
const std::string& url = const std::string& url =
base::StringPrintf("%s/%d", package_name.c_str(), tree_id()); base::StringPrintf("%s/%d", package_name.c_str(), tree_id());
out_data->AddStringAttribute(ax::mojom::StringAttribute::kUrl, url); out_data->AddStringAttribute(ax::mojom::StringAttribute::kUrl, url);
...@@ -597,33 +524,27 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -597,33 +524,27 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
// Int properties. // Int properties.
int traversal_before = -1, traversal_after = -1; int traversal_before = -1, traversal_after = -1;
if (GetIntProperty(node, if (GetProperty(node, AXIntProperty::TRAVERSAL_BEFORE, &traversal_before)) {
arc::mojom::AccessibilityIntProperty::TRAVERSAL_BEFORE,
&traversal_before)) {
out_data->AddIntAttribute(ax::mojom::IntAttribute::kPreviousFocusId, out_data->AddIntAttribute(ax::mojom::IntAttribute::kPreviousFocusId,
traversal_before); traversal_before);
} }
if (GetIntProperty(node, if (GetProperty(node, AXIntProperty::TRAVERSAL_AFTER, &traversal_after)) {
arc::mojom::AccessibilityIntProperty::TRAVERSAL_AFTER,
&traversal_after)) {
out_data->AddIntAttribute(ax::mojom::IntAttribute::kNextFocusId, out_data->AddIntAttribute(ax::mojom::IntAttribute::kNextFocusId,
traversal_after); traversal_after);
} }
// Boolean properties. // Boolean properties.
PopulateAXState(node, out_data); PopulateAXState(node, out_data);
if (GetBooleanProperty( if (GetProperty(node, AXBooleanProperty::SCROLLABLE)) {
node, arc::mojom::AccessibilityBooleanProperty::SCROLLABLE)) {
out_data->AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, true); out_data->AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, true);
} }
if (GetBooleanProperty(node, if (GetProperty(node, AXBooleanProperty::CLICKABLE)) {
arc::mojom::AccessibilityBooleanProperty::CLICKABLE)) {
out_data->AddBoolAttribute(ax::mojom::BoolAttribute::kClickable, true); out_data->AddBoolAttribute(ax::mojom::BoolAttribute::kClickable, true);
} }
// Range info. // Range info.
arc::mojom::AccessibilityRangeInfoData* range_info = node->range_info.get(); AXRangeInfoData* range_info = node->range_info.get();
if (range_info) { if (range_info) {
out_data->AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, out_data->AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
range_info->current); range_info->current);
...@@ -649,21 +570,19 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -649,21 +570,19 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
// Integer properties. // Integer properties.
int32_t val; int32_t val;
if (GetIntProperty(node, AXIntProperty::TEXT_SELECTION_START, &val) && if (GetProperty(node, AXIntProperty::TEXT_SELECTION_START, &val) && val >= 0)
val >= 0)
out_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, val); out_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, val);
if (GetIntProperty(node, AXIntProperty::TEXT_SELECTION_END, &val) && val >= 0) if (GetProperty(node, AXIntProperty::TEXT_SELECTION_END, &val) && val >= 0)
out_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, val); out_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, val);
// Custom actions. // Custom actions.
std::vector<int32_t> custom_action_ids; std::vector<int32_t> custom_action_ids;
if (GetIntListProperty(node, AXIntListProperty::CUSTOM_ACTION_IDS, if (GetProperty(node, AXIntListProperty::CUSTOM_ACTION_IDS,
&custom_action_ids)) { &custom_action_ids)) {
std::vector<std::string> custom_action_descriptions; std::vector<std::string> custom_action_descriptions;
CHECK(GetStringListProperty( CHECK(GetProperty(node, AXStringListProperty::CUSTOM_ACTION_DESCRIPTIONS,
node, AXStringListProperty::CUSTOM_ACTION_DESCRIPTIONS,
&custom_action_descriptions)); &custom_action_descriptions));
CHECK(!custom_action_ids.empty()); CHECK(!custom_action_ids.empty());
CHECK_EQ(custom_action_ids.size(), custom_action_descriptions.size()); CHECK_EQ(custom_action_ids.size(), custom_action_descriptions.size());
...@@ -677,8 +596,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, ...@@ -677,8 +596,7 @@ void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node,
} }
} }
const gfx::Rect AXTreeSourceArc::GetBounds( const gfx::Rect AXTreeSourceArc::GetBounds(AXNodeInfoData* node,
mojom::AccessibilityNodeInfoData* node,
aura::Window* focused_window) const { aura::Window* focused_window) const {
DCHECK_NE(root_id_, -1); DCHECK_NE(root_id_, -1);
...@@ -705,15 +623,19 @@ const gfx::Rect AXTreeSourceArc::GetBounds( ...@@ -705,15 +623,19 @@ const gfx::Rect AXTreeSourceArc::GetBounds(
return node_bounds; return node_bounds;
} }
gfx::Rect AXTreeSourceArc::ComputeEnclosingBounds( gfx::Rect AXTreeSourceArc::ComputeEnclosingBounds(AXNodeInfoData* node) const {
mojom::AccessibilityNodeInfoData* node) const {
gfx::Rect computed_bounds; gfx::Rect computed_bounds;
// Exit early if |node| is invisible.
if (!GetProperty(node, AXBooleanProperty::VISIBLE_TO_USER))
return computed_bounds;
ComputeEnclosingBoundsInternal(node, computed_bounds); ComputeEnclosingBoundsInternal(node, computed_bounds);
return computed_bounds.IsEmpty() ? node->bounds_in_screen : computed_bounds; return computed_bounds;
} }
void AXTreeSourceArc::ComputeEnclosingBoundsInternal( void AXTreeSourceArc::ComputeEnclosingBoundsInternal(
mojom::AccessibilityNodeInfoData* node, AXNodeInfoData* node,
gfx::Rect& computed_bounds) const { gfx::Rect& computed_bounds) const {
auto cached_bounds = cached_computed_bounds_.find(node); auto cached_bounds = cached_computed_bounds_.find(node);
if (cached_bounds != cached_computed_bounds_.end()) { if (cached_bounds != cached_computed_bounds_.end()) {
...@@ -721,24 +643,32 @@ void AXTreeSourceArc::ComputeEnclosingBoundsInternal( ...@@ -721,24 +643,32 @@ void AXTreeSourceArc::ComputeEnclosingBoundsInternal(
return; return;
} }
if (!GetProperty(node, AXBooleanProperty::VISIBLE_TO_USER))
return;
// Only consider nodes that can possibly be accessibility focused. In Chrome, // Only consider nodes that can possibly be accessibility focused. In Chrome,
// this amounts to nodes with a non-generic container role. // this means:
// a node with a non-generic role and
// actionable nodes, or
// top level scrollables with a name
ui::AXNodeData data; ui::AXNodeData data;
PopulateAXRole(node, &data); PopulateAXRole(node, &data);
const gfx::Rect& bounds = node->bounds_in_screen; if ((data.role != ax::mojom::Role::kGenericContainer &&
if (data.role != ax::mojom::Role::kGenericContainer && data.role != ax::mojom::Role::kGroup) &&
data.role != ax::mojom::Role::kGroup && !bounds.IsEmpty() && (GetProperty(node, AXBooleanProperty::FOCUSABLE) ||
GetBooleanProperty( GetProperty(node, AXBooleanProperty::CLICKABLE) ||
node, arc::mojom::AccessibilityBooleanProperty::VISIBLE_TO_USER)) { GetProperty(node, AXBooleanProperty::FOCUSABLE) ||
computed_bounds.Union(bounds); GetProperty(node, AXBooleanProperty::CHECKABLE) ||
(HasProperty(node, AXStringProperty::TEXT) &&
GetProperty(node, AXBooleanProperty::SCROLLABLE)))) {
computed_bounds.Union(node->bounds_in_screen);
return; return;
} }
if (!node->int_list_properties) if (!node->int_list_properties)
return; return;
auto it = node->int_list_properties->find( auto it = node->int_list_properties->find(AXIntListProperty::CHILD_NODE_IDS);
arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS);
if (it == node->int_list_properties->end()) if (it == node->int_list_properties->end())
return; return;
......
...@@ -10,23 +10,68 @@ ...@@ -10,23 +10,68 @@
namespace arc { namespace arc {
using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXCollectionInfoData = mojom::AccessibilityCollectionInfoData;
using AXCollectionItemInfoData = mojom::AccessibilityCollectionItemInfoData;
using AXEventData = mojom::AccessibilityEventData;
using AXEventType = mojom::AccessibilityEventType;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
using AXIntProperty = mojom::AccessibilityIntProperty;
using AXNodeInfoData = mojom::AccessibilityNodeInfoData;
using AXRangeInfoData = mojom::AccessibilityRangeInfoData;
using AXStringListProperty = mojom::AccessibilityStringListProperty;
using AXStringProperty = mojom::AccessibilityStringProperty;
void SetProperty(AXNodeInfoData* node, AXBooleanProperty prop, bool value) {
if (!node->boolean_properties) {
node->boolean_properties = std::unordered_map<AXBooleanProperty, bool>();
}
node->boolean_properties.value().insert(std::make_pair(prop, value));
}
void SetProperty(AXNodeInfoData* node,
AXStringProperty prop,
const std::string& value) {
if (!node->string_properties) {
node->string_properties =
std::unordered_map<AXStringProperty, std::string>();
}
node->string_properties.value().insert(std::make_pair(prop, value));
}
void SetProperty(AXNodeInfoData* node, AXIntProperty prop, int32_t value) {
if (!node->int_properties) {
node->int_properties = std::unordered_map<AXIntProperty, int>();
}
node->int_properties.value().insert(std::make_pair(prop, value));
}
void SetProperty(AXNodeInfoData* node,
AXIntListProperty prop,
const std::vector<int>& value) {
if (!node->int_list_properties) {
node->int_list_properties =
std::unordered_map<AXIntListProperty, std::vector<int>>();
}
node->int_list_properties.value().insert(std::make_pair(prop, value));
}
class AXTreeSourceArcTest : public testing::Test, class AXTreeSourceArcTest : public testing::Test,
public AXTreeSourceArc::Delegate { public AXTreeSourceArc::Delegate {
public: public:
AXTreeSourceArcTest() : tree_(new AXTreeSourceArc(this)) {} AXTreeSourceArcTest() : tree_(new AXTreeSourceArc(this)) {}
protected: protected:
void CallNotifyAccessibilityEvent(mojom::AccessibilityEventData* event_data) { void CallNotifyAccessibilityEvent(AXEventData* event_data) {
tree_->NotifyAccessibilityEvent(event_data); tree_->NotifyAccessibilityEvent(event_data);
} }
void CallGetChildren( void CallGetChildren(AXNodeInfoData* node,
mojom::AccessibilityNodeInfoData* node, std::vector<AXNodeInfoData*>* out_children) const {
std::vector<mojom::AccessibilityNodeInfoData*>* out_children) const {
tree_->GetChildren(node, out_children); tree_->GetChildren(node, out_children);
} }
void CallSerializeNode(mojom::AccessibilityNodeInfoData* node, void CallSerializeNode(AXNodeInfoData* node,
std::unique_ptr<ui::AXNodeData>* out_data) const { std::unique_ptr<ui::AXNodeData>* out_data) const {
ASSERT_TRUE(out_data); ASSERT_TRUE(out_data);
*out_data = std::make_unique<ui::AXNodeData>(); *out_data = std::make_unique<ui::AXNodeData>();
...@@ -42,152 +87,143 @@ class AXTreeSourceArcTest : public testing::Test, ...@@ -42,152 +87,143 @@ class AXTreeSourceArcTest : public testing::Test,
}; };
TEST_F(AXTreeSourceArcTest, ReorderChildrenByLayout) { TEST_F(AXTreeSourceArcTest, ReorderChildrenByLayout) {
auto event = AXEventData::New();
event->source_id = 0;
event->task_id = 1;
event->event_type = AXEventType::VIEW_FOCUSED;
auto event1 = arc::mojom::AccessibilityEventData::New(); event->node_data.push_back(AXNodeInfoData::New());
event1->source_id = 0; AXNodeInfoData* root = event->node_data.back().get();
event1->task_id = 1; root->id = 0;
event1->event_type = arc::mojom::AccessibilityEventType::VIEW_FOCUSED; SetProperty(root, AXIntListProperty::CHILD_NODE_IDS,
event1->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New()); std::vector<int>({1, 2}));
event1->node_data[0]->id = 0;
event1->node_data[0]->int_list_properties =
std::unordered_map<arc::mojom::AccessibilityIntListProperty,
std::vector<int>>();
event1->node_data[0]->int_list_properties.value().insert(
std::make_pair(arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS,
std::vector<int>({1, 2})));
// Child button. // Child button.
event1->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New()); event->node_data.push_back(AXNodeInfoData::New());
event1->node_data[1]->id = 1; AXNodeInfoData* button1 = event->node_data.back().get();
event1->node_data[1]->string_properties = button1->id = 1;
std::unordered_map<arc::mojom::AccessibilityStringProperty, SetProperty(button1, AXStringProperty::CLASS_NAME, ui::kAXButtonClassname);
std::string>(); SetProperty(button1, AXBooleanProperty::VISIBLE_TO_USER, true);
event1->node_data[1]->string_properties.value().insert( SetProperty(button1, AXBooleanProperty::FOCUSABLE, true);
std::make_pair(arc::mojom::AccessibilityStringProperty::CLASS_NAME,
ui::kAXButtonClassname));
event1->node_data[1]->boolean_properties =
std::unordered_map<arc::mojom::AccessibilityBooleanProperty, bool>();
event1->node_data[1]->boolean_properties.value().insert(std::make_pair(
arc::mojom::AccessibilityBooleanProperty::VISIBLE_TO_USER, true));
// Another child button. // Another child button.
event1->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New()); event->node_data.push_back(AXNodeInfoData::New());
event1->node_data[2]->id = 2; AXNodeInfoData* button2 = event->node_data.back().get();
event1->node_data[2]->string_properties = button2->id = 2;
std::unordered_map<arc::mojom::AccessibilityStringProperty, SetProperty(button2, AXStringProperty::CLASS_NAME, ui::kAXButtonClassname);
std::string>(); SetProperty(button2, AXBooleanProperty::VISIBLE_TO_USER, true);
event1->node_data[2]->string_properties.value().insert( SetProperty(button2, AXBooleanProperty::FOCUSABLE, true);
std::make_pair(arc::mojom::AccessibilityStringProperty::CLASS_NAME,
ui::kAXButtonClassname));
event1->node_data[2]->boolean_properties =
std::unordered_map<arc::mojom::AccessibilityBooleanProperty, bool>();
event1->node_data[2]->boolean_properties.value().insert(std::make_pair(
arc::mojom::AccessibilityBooleanProperty::VISIBLE_TO_USER, true));
// Populate the tree source with the data.
CallNotifyAccessibilityEvent(event1.get());
// Live edit the data sources to exercise each layout.
// Non-overlapping, bottom to top. // Non-overlapping, bottom to top.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(0, 0, 50, 50); button2->bounds_in_screen = gfx::Rect(0, 0, 50, 50);
std::vector<mojom::AccessibilityNodeInfoData*> top_to_bottom;
CallGetChildren(event1->node_data[0].get(), &top_to_bottom); // Trigger an update which refreshes the computed bounds used for reordering.
CallNotifyAccessibilityEvent(event.get());
std::vector<AXNodeInfoData*> top_to_bottom;
CallGetChildren(root, &top_to_bottom);
ASSERT_EQ(2U, top_to_bottom.size()); ASSERT_EQ(2U, top_to_bottom.size());
ASSERT_EQ(2, top_to_bottom[0]->id); ASSERT_EQ(2, top_to_bottom[0]->id);
ASSERT_EQ(1, top_to_bottom[1]->id); ASSERT_EQ(1, top_to_bottom[1]->id);
// Non-overlapping, top to bottom. // Non-overlapping, top to bottom.
event1->node_data[1]->bounds_in_screen = gfx::Rect(0, 0, 50, 50); button1->bounds_in_screen = gfx::Rect(0, 0, 50, 50);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
CallNotifyAccessibilityEvent(event.get());
top_to_bottom.clear(); top_to_bottom.clear();
CallGetChildren(event1->node_data[0].get(), &top_to_bottom); CallGetChildren(event->node_data[0].get(), &top_to_bottom);
ASSERT_EQ(2U, top_to_bottom.size()); ASSERT_EQ(2U, top_to_bottom.size());
ASSERT_EQ(1, top_to_bottom[0]->id); ASSERT_EQ(1, top_to_bottom[0]->id);
ASSERT_EQ(2, top_to_bottom[1]->id); ASSERT_EQ(2, top_to_bottom[1]->id);
// Overlapping; right to left. // Overlapping; right to left.
event1->node_data[1]->bounds_in_screen = gfx::Rect(101, 100, 99, 100); button1->bounds_in_screen = gfx::Rect(101, 100, 99, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
std::vector<mojom::AccessibilityNodeInfoData*> left_to_right; CallNotifyAccessibilityEvent(event.get());
CallGetChildren(event1->node_data[0].get(), &left_to_right); std::vector<AXNodeInfoData*> left_to_right;
CallGetChildren(root, &left_to_right);
ASSERT_EQ(2U, left_to_right.size()); ASSERT_EQ(2U, left_to_right.size());
ASSERT_EQ(2, left_to_right[0]->id); ASSERT_EQ(2, left_to_right[0]->id);
ASSERT_EQ(1, left_to_right[1]->id); ASSERT_EQ(1, left_to_right[1]->id);
// Overlapping; left to right. // Overlapping; left to right.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(101, 100, 99, 100); button2->bounds_in_screen = gfx::Rect(101, 100, 99, 100);
CallNotifyAccessibilityEvent(event.get());
left_to_right.clear(); left_to_right.clear();
CallGetChildren(event1->node_data[0].get(), &left_to_right); CallGetChildren(event->node_data[0].get(), &left_to_right);
ASSERT_EQ(2U, left_to_right.size()); ASSERT_EQ(2U, left_to_right.size());
ASSERT_EQ(1, left_to_right[0]->id); ASSERT_EQ(1, left_to_right[0]->id);
ASSERT_EQ(2, left_to_right[1]->id); ASSERT_EQ(2, left_to_right[1]->id);
// Overlapping, bottom to top. // Overlapping, bottom to top.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 99, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 99, 100, 100);
CallNotifyAccessibilityEvent(event.get());
top_to_bottom.clear(); top_to_bottom.clear();
CallGetChildren(event1->node_data[0].get(), &top_to_bottom); CallGetChildren(event->node_data[0].get(), &top_to_bottom);
ASSERT_EQ(2U, top_to_bottom.size()); ASSERT_EQ(2U, top_to_bottom.size());
ASSERT_EQ(2, top_to_bottom[0]->id); ASSERT_EQ(2, top_to_bottom[0]->id);
ASSERT_EQ(1, top_to_bottom[1]->id); ASSERT_EQ(1, top_to_bottom[1]->id);
// Overlapping, top to bottom. // Overlapping, top to bottom.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 99, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 99, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
CallNotifyAccessibilityEvent(event.get());
top_to_bottom.clear(); top_to_bottom.clear();
CallGetChildren(event1->node_data[0].get(), &top_to_bottom); CallGetChildren(event->node_data[0].get(), &top_to_bottom);
ASSERT_EQ(2U, top_to_bottom.size()); ASSERT_EQ(2U, top_to_bottom.size());
ASSERT_EQ(1, top_to_bottom[0]->id); ASSERT_EQ(1, top_to_bottom[0]->id);
ASSERT_EQ(2, top_to_bottom[1]->id); ASSERT_EQ(2, top_to_bottom[1]->id);
// Identical. smaller to larger. // Identical. smaller to larger.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 10); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 10);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
std::vector<mojom::AccessibilityNodeInfoData*> dimension; CallNotifyAccessibilityEvent(event.get());
CallGetChildren(event1->node_data[0].get(), &dimension); std::vector<AXNodeInfoData*> dimension;
CallGetChildren(event->node_data[0].get(), &dimension);
ASSERT_EQ(2U, dimension.size()); ASSERT_EQ(2U, dimension.size());
ASSERT_EQ(2, dimension[0]->id); ASSERT_EQ(2, dimension[0]->id);
ASSERT_EQ(1, dimension[1]->id); ASSERT_EQ(1, dimension[1]->id);
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 10, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 10, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
CallNotifyAccessibilityEvent(event.get());
dimension.clear(); dimension.clear();
CallGetChildren(event1->node_data[0].get(), &dimension); CallGetChildren(event->node_data[0].get(), &dimension);
ASSERT_EQ(2U, dimension.size()); ASSERT_EQ(2U, dimension.size());
ASSERT_EQ(2, dimension[0]->id); ASSERT_EQ(2, dimension[0]->id);
ASSERT_EQ(1, dimension[1]->id); ASSERT_EQ(1, dimension[1]->id);
// Identical. Larger to smaller. // Identical. Larger to smaller.
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 100, 10); button2->bounds_in_screen = gfx::Rect(100, 100, 100, 10);
CallNotifyAccessibilityEvent(event.get());
dimension.clear(); dimension.clear();
CallGetChildren(event1->node_data[0].get(), &dimension); CallGetChildren(event->node_data[0].get(), &dimension);
ASSERT_EQ(2U, dimension.size()); ASSERT_EQ(2U, dimension.size());
ASSERT_EQ(1, dimension[0]->id); ASSERT_EQ(1, dimension[0]->id);
ASSERT_EQ(2, dimension[1]->id); ASSERT_EQ(2, dimension[1]->id);
event1->node_data[1]->bounds_in_screen = gfx::Rect(100, 100, 100, 100); button1->bounds_in_screen = gfx::Rect(100, 100, 100, 100);
event1->node_data[2]->bounds_in_screen = gfx::Rect(100, 100, 10, 100); button2->bounds_in_screen = gfx::Rect(100, 100, 10, 100);
CallNotifyAccessibilityEvent(event.get());
dimension.clear(); dimension.clear();
CallGetChildren(event1->node_data[0].get(), &dimension); CallGetChildren(event->node_data[0].get(), &dimension);
ASSERT_EQ(2U, dimension.size()); ASSERT_EQ(2U, dimension.size());
ASSERT_EQ(1, dimension[0]->id); ASSERT_EQ(1, dimension[0]->id);
ASSERT_EQ(2, dimension[1]->id); ASSERT_EQ(2, dimension[1]->id);
} }
TEST_F(AXTreeSourceArcTest, AccessibleNameComputation) { TEST_F(AXTreeSourceArcTest, AccessibleNameComputation) {
auto event = arc::mojom::AccessibilityEventData::New(); auto event = AXEventData::New();
event->source_id = 0; event->source_id = 0;
event->task_id = 1; event->task_id = 1;
event->event_type = arc::mojom::AccessibilityEventType::VIEW_FOCUSED; event->event_type = AXEventType::VIEW_FOCUSED;
event->node_data.push_back(arc::mojom::AccessibilityNodeInfoData::New()); event->node_data.push_back(AXNodeInfoData::New());
event->node_data[0]->id = 0; AXNodeInfoData* root = event->node_data.back().get();
event->node_data[0]->string_properties = root->id = 0;
std::unordered_map<arc::mojom::AccessibilityStringProperty, SetProperty(root, AXStringProperty::CLASS_NAME, "");
std::string>();
// Populate the tree source with the data. // Populate the tree source with the data.
CallNotifyAccessibilityEvent(event.get()); CallNotifyAccessibilityEvent(event.get());
...@@ -196,46 +232,41 @@ TEST_F(AXTreeSourceArcTest, AccessibleNameComputation) { ...@@ -196,46 +232,41 @@ TEST_F(AXTreeSourceArcTest, AccessibleNameComputation) {
// No attributes. // No attributes.
std::unique_ptr<ui::AXNodeData> data; std::unique_ptr<ui::AXNodeData> data;
CallSerializeNode(event->node_data[0].get(), &data); CallSerializeNode(root, &data);
std::string name; std::string name;
ASSERT_FALSE( ASSERT_FALSE(
data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name)); data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
// Text (empty). // Text (empty).
event->node_data[0]->string_properties.value().insert( SetProperty(root, AXStringProperty::TEXT, "");
std::make_pair(arc::mojom::AccessibilityStringProperty::TEXT, ""));
CallSerializeNode(event->node_data[0].get(), &data); CallSerializeNode(root, &data);
ASSERT_TRUE( ASSERT_TRUE(
data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name)); data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
ASSERT_EQ("", name); ASSERT_EQ("", name);
// Text (non-empty). // Text (non-empty).
event->node_data[0]->string_properties->clear(); root->string_properties->clear();
event->node_data[0]->string_properties.value().insert(std::make_pair( SetProperty(root, AXStringProperty::TEXT, "label text");
arc::mojom::AccessibilityStringProperty::TEXT, "label text"));
CallSerializeNode(event->node_data[0].get(), &data); CallSerializeNode(root, &data);
ASSERT_TRUE( ASSERT_TRUE(
data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name)); data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
ASSERT_EQ("label text", name); ASSERT_EQ("label text", name);
// Content description (empty), text (non-empty). // Content description (empty), text (non-empty).
event->node_data[0]->string_properties.value().insert(std::make_pair( SetProperty(root, AXStringProperty::CONTENT_DESCRIPTION, "");
arc::mojom::AccessibilityStringProperty::CONTENT_DESCRIPTION, ""));
CallSerializeNode(event->node_data[0].get(), &data); CallSerializeNode(root, &data);
ASSERT_TRUE( ASSERT_TRUE(
data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name)); data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
ASSERT_EQ("label text", name); ASSERT_EQ("label text", name);
// Content description (non-empty), text (non-empty). // Content description (non-empty), text (non-empty).
event->node_data[0] root->string_properties.value()[AXStringProperty::CONTENT_DESCRIPTION] =
->string_properties
.value()[arc::mojom::AccessibilityStringProperty::CONTENT_DESCRIPTION] =
"label content description"; "label content description";
CallSerializeNode(event->node_data[0].get(), &data); CallSerializeNode(root, &data);
ASSERT_TRUE( ASSERT_TRUE(
data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name)); data->GetStringAttribute(ax::mojom::StringAttribute::kName, &name));
ASSERT_EQ("label content description", name); ASSERT_EQ("label content description", name);
......
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