Commit ccb83d4a authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Implemented AXVirtualView

Some widgets in Views, such as table and tree view controls,  make use of lightweight objects to display their contents.
These lightweight objects are not View classes and hence have no native accessibility support.
To make them accessible, we introduce a virtual accessibility view that will be used to provide the accessibility support for those objects.
In addition, we could make use of virtual accessibility views to override the children of a real view and create a completely new accessibility tree hanging off that real view.
This creates more flexibility as it allows us to construct a virtual accessibility tree that is not always a mirror image of the hierarchy of the real views.
R=dmazzoni@chromium.org

Bug: 101762
Tested: unit tests
Change-Id: Iab8fea36ef44373e06d9408c407a27caa7c4219c
Reviewed-on: https://chromium-review.googlesource.com/c/1229234
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#597330}
parent 23cfafad
...@@ -22,18 +22,18 @@ namespace ui { ...@@ -22,18 +22,18 @@ namespace ui {
namespace { namespace {
bool IsFlagSet(uint32_t bitfield, uint32_t flag) { bool IsFlagSet(uint32_t bitfield, uint32_t flag) {
return 0 != (bitfield & (1 << flag)); return (bitfield & (1 << flag)) != 0;
} }
uint32_t ModifyFlag(uint32_t bitfield, uint32_t flag, bool set) { uint32_t ModifyFlag(uint32_t bitfield, uint32_t flag, bool set) {
return set ? (bitfield |= (1 << flag)) : (bitfield &= ~(1 << flag)); return set ? (bitfield |= (1 << flag)) : (bitfield &= ~(1 << flag));
} }
std::string StateBitfieldToString(uint32_t state) { std::string StateBitfieldToString(uint32_t state_enum) {
std::string str; std::string str;
for (uint32_t i = static_cast<uint32_t>(ax::mojom::State::kNone) + 1; for (uint32_t i = static_cast<uint32_t>(ax::mojom::State::kNone) + 1;
i <= static_cast<uint32_t>(ax::mojom::State::kMaxValue); ++i) { i <= static_cast<uint32_t>(ax::mojom::State::kMaxValue); ++i) {
if (IsFlagSet(state, i)) if (IsFlagSet(state_enum, i))
str += " " + str += " " +
base::ToUpperASCII(ui::ToString(static_cast<ax::mojom::State>(i))); base::ToUpperASCII(ui::ToString(static_cast<ax::mojom::State>(i)));
} }
...@@ -510,16 +510,23 @@ bool AXNodeData::HasState(ax::mojom::State state_enum) const { ...@@ -510,16 +510,23 @@ bool AXNodeData::HasState(ax::mojom::State state_enum) const {
return IsFlagSet(state, static_cast<uint32_t>(state_enum)); return IsFlagSet(state, static_cast<uint32_t>(state_enum));
} }
bool AXNodeData::HasAction(ax::mojom::Action action_enum) const { bool AXNodeData::HasAction(ax::mojom::Action action) const {
return IsFlagSet(actions, static_cast<uint32_t>(action_enum)); return IsFlagSet(actions, static_cast<uint32_t>(action));
} }
void AXNodeData::AddState(ax::mojom::State state_enum) { ax::mojom::State AXNodeData::AddState(ax::mojom::State state_enum) {
DCHECK_NE(state_enum, ax::mojom::State::kNone); DCHECK_NE(state_enum, ax::mojom::State::kNone);
state = ModifyFlag(state, static_cast<uint32_t>(state_enum), true); state = ModifyFlag(state, static_cast<uint32_t>(state_enum), true);
return static_cast<ax::mojom::State>(state);
} }
void AXNodeData::AddAction(ax::mojom::Action action_enum) { ax::mojom::State AXNodeData::RemoveState(ax::mojom::State state_enum) {
DCHECK_NE(state_enum, ax::mojom::State::kNone);
state = ModifyFlag(state, static_cast<uint32_t>(state_enum), false);
return static_cast<ax::mojom::State>(state);
}
ax::mojom::Action AXNodeData::AddAction(ax::mojom::Action action_enum) {
switch (action_enum) { switch (action_enum) {
case ax::mojom::Action::kNone: case ax::mojom::Action::kNone:
NOTREACHED(); NOTREACHED();
...@@ -563,11 +570,7 @@ void AXNodeData::AddAction(ax::mojom::Action action_enum) { ...@@ -563,11 +570,7 @@ void AXNodeData::AddAction(ax::mojom::Action action_enum) {
} }
actions = ModifyFlag(actions, static_cast<uint32_t>(action_enum), true); actions = ModifyFlag(actions, static_cast<uint32_t>(action_enum), true);
} return static_cast<ax::mojom::Action>(actions);
void AXNodeData::RemoveState(ax::mojom::State state_enum) {
DCHECK_NE(state_enum, ax::mojom::State::kNone);
state = ModifyFlag(state, static_cast<uint32_t>(state_enum), false);
} }
std::string AXNodeData::ToString() const { std::string AXNodeData::ToString() const {
......
...@@ -127,15 +127,13 @@ struct AX_EXPORT AXNodeData { ...@@ -127,15 +127,13 @@ struct AX_EXPORT AXNodeData {
void SetValue(const base::string16& value); void SetValue(const base::string16& value);
// Returns true if the given enum bit is 1. // Returns true if the given enum bit is 1.
bool HasState(ax::mojom::State state_enum) const; bool HasState(ax::mojom::State state) const;
bool HasAction(ax::mojom::Action state_enum) const; bool HasAction(ax::mojom::Action action) const;
// Set bits in the given enum's corresponding bitfield. // Set or remove bits in the given enum's corresponding bitfield.
void AddState(ax::mojom::State state_enum); ax::mojom::State AddState(ax::mojom::State state);
void AddAction(ax::mojom::Action action_enum); ax::mojom::State RemoveState(ax::mojom::State state);
ax::mojom::Action AddAction(ax::mojom::Action action);
// Remove bits in the given enum's corresponding bitfield.
void RemoveState(ax::mojom::State state_enum);
// Helper functions to get some common int attributes with some specific // Helper functions to get some common int attributes with some specific
// enum types: // enum types:
...@@ -153,18 +151,22 @@ struct AX_EXPORT AXNodeData { ...@@ -153,18 +151,22 @@ struct AX_EXPORT AXNodeData {
return static_cast<ax::mojom::DefaultActionVerb>( return static_cast<ax::mojom::DefaultActionVerb>(
GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb)); GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb));
} }
ax::mojom::InvalidState GetInvalidState() const { ax::mojom::InvalidState GetInvalidState() const {
return static_cast<ax::mojom::InvalidState>( return static_cast<ax::mojom::InvalidState>(
GetIntAttribute(ax::mojom::IntAttribute::kInvalidState)); GetIntAttribute(ax::mojom::IntAttribute::kInvalidState));
} }
ax::mojom::NameFrom GetNameFrom() const { ax::mojom::NameFrom GetNameFrom() const {
return static_cast<ax::mojom::NameFrom>( return static_cast<ax::mojom::NameFrom>(
GetIntAttribute(ax::mojom::IntAttribute::kNameFrom)); GetIntAttribute(ax::mojom::IntAttribute::kNameFrom));
} }
ax::mojom::Restriction GetRestriction() const { ax::mojom::Restriction GetRestriction() const {
return static_cast<ax::mojom::Restriction>( return static_cast<ax::mojom::Restriction>(
GetIntAttribute(ax::mojom::IntAttribute::kRestriction)); GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
} }
ax::mojom::TextDirection GetTextDirection() const { ax::mojom::TextDirection GetTextDirection() const {
return static_cast<ax::mojom::TextDirection>( return static_cast<ax::mojom::TextDirection>(
GetIntAttribute(ax::mojom::IntAttribute::kTextDirection)); GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
......
...@@ -61,7 +61,7 @@ void AXPlatformNode::Destroy() { ...@@ -61,7 +61,7 @@ void AXPlatformNode::Destroy() {
} }
int32_t AXPlatformNode::GetUniqueId() const { int32_t AXPlatformNode::GetUniqueId() const {
DCHECK(GetDelegate()); // Must be called after Init() DCHECK(GetDelegate()) << "|GetUniqueId| must be called after |Init|.";
return GetDelegate() ? GetDelegate()->GetUniqueId().Get() : -1; return GetDelegate() ? GetDelegate()->GetUniqueId().Get() : -1;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <unordered_set> #include <unordered_set>
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/stl_util.h"
namespace ui { namespace ui {
...@@ -18,6 +19,8 @@ base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_assigned_ids = ...@@ -18,6 +19,8 @@ base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_assigned_ids =
} // namespace } // namespace
AXUniqueId::AXUniqueId() : AXUniqueId(INT32_MAX) {}
AXUniqueId::AXUniqueId(const int32_t max_id) : id_(GetNextAXUniqueId(max_id)) {} AXUniqueId::AXUniqueId(const int32_t max_id) : id_(GetNextAXUniqueId(max_id)) {}
AXUniqueId::~AXUniqueId() { AXUniqueId::~AXUniqueId() {
...@@ -32,9 +35,8 @@ bool AXUniqueId::operator!=(const AXUniqueId& other) const { ...@@ -32,9 +35,8 @@ bool AXUniqueId::operator!=(const AXUniqueId& other) const {
return !(*this == other); return !(*this == other);
} }
bool AXUniqueId::IsAssigned(int32_t id) const { bool AXUniqueId::IsAssigned(const int32_t id) const {
auto id_map = g_assigned_ids.Get(); return base::ContainsKey(g_assigned_ids.Get(), id);
return id_map.find(id) != id_map.end();
} }
int32_t AXUniqueId::GetNextAXUniqueId(const int32_t max_id) { int32_t AXUniqueId::GetNextAXUniqueId(const int32_t max_id) {
...@@ -42,22 +44,23 @@ int32_t AXUniqueId::GetNextAXUniqueId(const int32_t max_id) { ...@@ -42,22 +44,23 @@ int32_t AXUniqueId::GetNextAXUniqueId(const int32_t max_id) {
static bool has_wrapped = false; static bool has_wrapped = false;
const int32_t prev_id = current_id; const int32_t prev_id = current_id;
do {
while (true) {
if (current_id == max_id) { if (current_id == max_id) {
current_id = 1; current_id = 1;
has_wrapped = true; has_wrapped = true;
} else { } else {
current_id++; ++current_id;
} }
if (current_id == prev_id) if (current_id == prev_id) {
LOG(FATAL) << "Over 2 billion active ids, something is wrong."; LOG(FATAL) << "There are over 2 billion available IDs, so the newly "
if (!has_wrapped || !IsAssigned(current_id)) "created ID cannot be equal to the most recently created "
break; "ID.";
} }
// If it |has_wrapped| then we need to continue until we find the first
// unassigned ID.
} while (has_wrapped && IsAssigned(current_id));
g_assigned_ids.Get().insert(current_id); g_assigned_ids.Get().insert(current_id);
return current_id; return current_id;
} }
......
...@@ -17,17 +17,18 @@ namespace ui { ...@@ -17,17 +17,18 @@ namespace ui {
// is required to generate the ID, and the ID is freed when the AXUniqueID is // is required to generate the ID, and the ID is freed when the AXUniqueID is
// destroyed. // destroyed.
// //
// The unique id that's guaranteed to be a positive number. Becase some // The unique id that's guaranteed to be a positive number. Because some
// platforms want to negate it, we ensure the range is below the signed int max. // platforms want to negate it, we ensure the range is below the signed int max.
// //
// These ids must not be conflated with the int id, that comes with web node // These ids must not be conflated with the int id, that comes with web node
// data, which are only unique within their source frame. // data, which are only unique within their source frame.
class AX_EXPORT AXUniqueId { class AX_EXPORT AXUniqueId {
public: public:
AXUniqueId() : AXUniqueId(INT32_MAX) {} AXUniqueId();
~AXUniqueId(); virtual ~AXUniqueId();
int32_t Get() const { return id_; } int32_t Get() const { return id_; }
operator int32_t() const { return id_; }
bool operator==(const AXUniqueId& other) const; bool operator==(const AXUniqueId& other) const;
bool operator!=(const AXUniqueId& other) const; bool operator!=(const AXUniqueId& other) const;
......
...@@ -21,11 +21,15 @@ static const int32_t kMaxId = 100; ...@@ -21,11 +21,15 @@ static const int32_t kMaxId = 100;
class AXTestSmallBankUniqueId : public AXUniqueId { class AXTestSmallBankUniqueId : public AXUniqueId {
public: public:
AXTestSmallBankUniqueId(); AXTestSmallBankUniqueId();
~AXTestSmallBankUniqueId() override;
private:
friend class AXUniqueId; friend class AXUniqueId;
DISALLOW_COPY_AND_ASSIGN(AXTestSmallBankUniqueId);
}; };
AXTestSmallBankUniqueId::AXTestSmallBankUniqueId() : AXUniqueId(kMaxId) {} AXTestSmallBankUniqueId::AXTestSmallBankUniqueId() : AXUniqueId(kMaxId) {}
AXTestSmallBankUniqueId::~AXTestSmallBankUniqueId() = default;
TEST(AXPlatformUniqueIdTest, UnassignedIdsAreReused) { TEST(AXPlatformUniqueIdTest, UnassignedIdsAreReused) {
// Create a bank of ids that uses up all available ids. // Create a bank of ids that uses up all available ids.
......
...@@ -59,6 +59,7 @@ jumbo_component("views") { ...@@ -59,6 +59,7 @@ jumbo_component("views") {
"../views_bridge_mac/bridged_native_widget_impl.h", "../views_bridge_mac/bridged_native_widget_impl.h",
"../views_bridge_mac/native_widget_mac_nswindow.h", "../views_bridge_mac/native_widget_mac_nswindow.h",
"../views_bridge_mac/window_touch_bar_delegate.h", "../views_bridge_mac/window_touch_bar_delegate.h",
"accessibility/ax_virtual_view.h",
"accessibility/view_accessibility.h", "accessibility/view_accessibility.h",
"accessibility/view_accessibility_utils.h", "accessibility/view_accessibility_utils.h",
"accessible_pane_view.h", "accessible_pane_view.h",
...@@ -264,6 +265,7 @@ jumbo_component("views") { ...@@ -264,6 +265,7 @@ jumbo_component("views") {
# TODO(ccameron): Move these sources to the views_bridge_mac component # TODO(ccameron): Move these sources to the views_bridge_mac component
"../views_bridge_mac/bridged_native_widget_impl.mm", "../views_bridge_mac/bridged_native_widget_impl.mm",
"../views_bridge_mac/native_widget_mac_nswindow.mm", "../views_bridge_mac/native_widget_mac_nswindow.mm",
"accessibility/ax_virtual_view.cc",
"accessibility/view_accessibility.cc", "accessibility/view_accessibility.cc",
"accessibility/view_accessibility_utils.cc", "accessibility/view_accessibility_utils.cc",
"accessible_pane_view.cc", "accessible_pane_view.cc",
...@@ -516,6 +518,7 @@ jumbo_component("views") { ...@@ -516,6 +518,7 @@ jumbo_component("views") {
if (use_x11) { if (use_x11) {
deps += [ "//ui/display/util" ] deps += [ "//ui/display/util" ]
} }
if (is_linux && !is_chromeos) { if (is_linux && !is_chromeos) {
sources -= [ "window/window_button_order_provider.cc" ] sources -= [ "window/window_button_order_provider.cc" ]
deps += [ "//ui/shell_dialogs" ] deps += [ "//ui/shell_dialogs" ]
...@@ -575,6 +578,7 @@ jumbo_component("views") { ...@@ -575,6 +578,7 @@ jumbo_component("views") {
if (use_ozone) { if (use_ozone) {
deps += [ "//ui/ozone" ] deps += [ "//ui/ozone" ]
} }
if (use_x11) { if (use_x11) {
configs += [ configs += [
"//build/config/linux:x11", "//build/config/linux:x11",
...@@ -621,6 +625,7 @@ jumbo_component("views") { ...@@ -621,6 +625,7 @@ jumbo_component("views") {
"widget/tooltip_manager_aura.h", "widget/tooltip_manager_aura.h",
"widget/window_reorderer.h", "widget/window_reorderer.h",
] ]
sources += [ sources += [
"accessibility/ax_aura_obj_cache.cc", "accessibility/ax_aura_obj_cache.cc",
"accessibility/ax_aura_obj_wrapper.cc", "accessibility/ax_aura_obj_wrapper.cc",
...@@ -1081,7 +1086,10 @@ source_set("views_unittests_sources") { ...@@ -1081,7 +1086,10 @@ source_set("views_unittests_sources") {
} }
if (has_native_accessibility) { if (has_native_accessibility) {
sources += [ "accessibility/view_ax_platform_node_delegate_unittest.cc" ] sources += [
"accessibility/ax_virtual_view_unittest.cc",
"accessibility/view_ax_platform_node_delegate_unittest.cc",
]
} }
if (use_x11) { if (use_x11) {
......
// 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 "ui/views/accessibility/ax_virtual_view.h"
#include <stdint.h>
#include <algorithm>
#include <utility>
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"
namespace views {
AXVirtualView::AXVirtualView()
: parent_view_(nullptr), virtual_parent_view_(nullptr) {
ax_platform_node_ = ui::AXPlatformNode::Create(this);
DCHECK(ax_platform_node_);
}
AXVirtualView::~AXVirtualView() {
DCHECK(!parent_view_ || !virtual_parent_view_)
<< "Either |parent_view_| or |virtual_parent_view_| could be set but "
"not both.";
if (ax_platform_node_) {
ax_platform_node_->Destroy();
ax_platform_node_ = nullptr;
}
}
void AXVirtualView::AddChildView(std::unique_ptr<AXVirtualView> view) {
DCHECK(view);
if (view->virtual_parent_view_ == this)
return;
AddChildViewAt(std::move(view), GetChildCount());
}
void AXVirtualView::AddChildViewAt(std::unique_ptr<AXVirtualView> view,
int index) {
DCHECK(view);
CHECK_NE(view.get(), this)
<< "You cannot add an AXVirtualView as its own child.";
DCHECK(!view->parent_view_) << "This |view| already has an AXVirtualView "
"parent. Call RemoveVirtualChildView first.";
DCHECK(!view->virtual_parent_view_)
<< "This |view| already has a View parent. Call RemoveChildView first.";
DCHECK_GE(index, 0);
DCHECK_LE(index, GetChildCount());
view->virtual_parent_view_ = this;
children_.insert(children_.begin() + index, std::move(view));
}
void AXVirtualView::ReorderChildView(AXVirtualView* view, int index) {
DCHECK(view);
if (index >= GetChildCount())
return;
if (index < 0)
index = GetChildCount() - 1;
DCHECK_EQ(view->virtual_parent_view_, this);
if (children_[index].get() == view)
return;
int cur_index = GetIndexOf(view);
if (cur_index < 0)
return;
std::unique_ptr<AXVirtualView> child = std::move(children_[cur_index]);
children_.erase(children_.begin() + cur_index);
children_.insert(children_.begin() + index, std::move(child));
}
std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
AXVirtualView* view) {
DCHECK(view);
int cur_index = GetIndexOf(view);
if (cur_index < 0)
return {};
std::unique_ptr<AXVirtualView> child = std::move(children_[cur_index]);
children_.erase(children_.begin() + cur_index);
child->virtual_parent_view_ = nullptr;
return child;
}
void AXVirtualView::RemoveAllChildViews() {
while (!children_.empty())
RemoveChildView(children_.back().get());
}
const AXVirtualView* AXVirtualView::child_at(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, static_cast<int>(children_.size()));
return children_[index].get();
}
AXVirtualView* AXVirtualView::child_at(int index) {
return const_cast<AXVirtualView*>(
const_cast<const AXVirtualView*>(this)->child_at(index));
}
bool AXVirtualView::Contains(const AXVirtualView* view) const {
DCHECK(view);
for (const AXVirtualView* v = view; v; v = v->virtual_parent_view_) {
if (v == this)
return true;
}
return false;
}
int AXVirtualView::GetIndexOf(const AXVirtualView* view) const {
DCHECK(view);
const auto iter =
std::find_if(children_.begin(), children_.end(),
[view](const auto& child) { return child.get() == view; });
return iter != children_.end() ? static_cast<int>(iter - children_.begin())
: -1;
}
gfx::NativeViewAccessible AXVirtualView::GetNativeObject() const {
DCHECK(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
void AXVirtualView::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
ax_platform_node_->NotifyAccessibilityEvent(event_type);
}
void AXVirtualView::OverrideRole(const ax::mojom::Role role) {
custom_data_.role = role;
}
void AXVirtualView::OverrideState(ax::mojom::State state) {
custom_data_.state = static_cast<int32_t>(state);
}
void AXVirtualView::OverrideName(const std::string& name) {
custom_data_.SetName(name);
}
void AXVirtualView::OverrideName(const base::string16& name) {
custom_data_.SetName(name);
}
void AXVirtualView::OverrideDescription(const std::string& description) {
custom_data_.SetDescription(description);
}
void AXVirtualView::OverrideDescription(const base::string16& description) {
custom_data_.SetDescription(description);
}
void AXVirtualView::OverrideBoundsRect(const gfx::RectF& location) {
custom_data_.location = location;
}
// ui::AXPlatformNodeDelegate
const ui::AXNodeData& AXVirtualView::GetData() const {
if (!IsParentVisible()) {
custom_data_.AddState(ax::mojom::State::kInvisible);
} else {
custom_data_.RemoveState(ax::mojom::State::kInvisible);
}
return custom_data_;
}
int AXVirtualView::GetChildCount() {
return static_cast<int>(children_.size());
}
gfx::NativeViewAccessible AXVirtualView::ChildAtIndex(int index) {
DCHECK_GE(index, 0) << "Child indices should be greater or equal to 0.";
DCHECK_LT(index, GetChildCount())
<< "Child indices should be less than the child count.";
if (index >= 0 && index < GetChildCount())
return children_[index]->GetNativeObject();
return nullptr;
}
gfx::NativeWindow AXVirtualView::GetTopLevelWidget() {
// TODO(nektar): Implement.
return nullptr;
}
gfx::NativeViewAccessible AXVirtualView::GetParent() {
if (parent_view_)
return parent_view_->GetNativeViewAccessible();
if (virtual_parent_view_)
return virtual_parent_view_->GetNativeObject();
return nullptr;
}
gfx::Rect AXVirtualView::GetClippedScreenBoundsRect() const {
// We could optionally add clipping here if ever needed.
// TODO(nektar): Implement.
return {};
}
gfx::Rect AXVirtualView::GetUnclippedScreenBoundsRect() const {
// TODO(nektar): Implement.
return {};
}
gfx::NativeViewAccessible AXVirtualView::HitTestSync(int x, int y) {
// TODO(nektar): Implement.
return GetNativeObject();
}
gfx::NativeViewAccessible AXVirtualView::GetFocus() {
// TODO(nektar): Implement.
return nullptr;
}
ui::AXPlatformNode* AXVirtualView::GetFromNodeID(int32_t id) {
// TODO(nektar): Implement.
return nullptr;
}
bool AXVirtualView::AccessibilityPerformAction(const ui::AXActionData& data) {
// TODO(nektar): Implement.
return false;
}
bool AXVirtualView::ShouldIgnoreHoveredStateForTesting() {
// TODO(nektar): Implement.
return false;
}
bool AXVirtualView::IsOffscreen() const {
// TODO(nektar): Implement.
return false;
}
const ui::AXUniqueId& AXVirtualView::GetUniqueId() const {
return unique_id_;
}
bool AXVirtualView::IsParentVisible() const {
if (parent_view_) {
const auto* parent_node = static_cast<ui::AXPlatformNodeBase*>(
ui::AXPlatformNode::FromNativeViewAccessible(
parent_view_->GetNativeViewAccessible()));
if (!parent_node) {
NOTREACHED() << "AXVirtualView should be created on a platform with "
"native accessibility support.";
return false;
}
const ui::AXNodeData& parent_data = parent_node->GetData();
return !parent_data.HasState(ax::mojom::State::kInvisible);
}
if (virtual_parent_view_) {
return !virtual_parent_view_->GetData().HasState(
ax::mojom::State::kInvisible);
}
// Not attached to a parent yet.
return false;
}
} // namespace views
// 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 UI_VIEWS_ACCESSIBILITY_AX_VIRTUAL_VIEW_H_
#define UI_VIEWS_ACCESSIBILITY_AX_VIRTUAL_VIEW_H_
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate_base.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/views_export.h"
namespace ui {
struct AXActionData;
class AXUniqueId;
} // namespace ui
namespace views {
class View;
class ViewAccessibility;
// Implements a virtual view that is used only for accessibility.
//
// Some composite widgets such as tree and table views may utilize lightweight
// UI objects instead of actual views for displaying and managing their
// contents. We need a corresponding virtual accessibility view to expose
// information about these lightweight Ui objects to accessibility. An
// AXVirtualView is owned by its parent, which could either be a
// ViewAccessibility or an AXVirtualView.
class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
public:
AXVirtualView();
~AXVirtualView() override;
//
// Methods for managing parent - child relationships.
//
// Adds |view| as a child of this virtual view, optionally at |index|.
// We take ownership of our children.
void AddChildView(std::unique_ptr<AXVirtualView> view);
void AddChildViewAt(std::unique_ptr<AXVirtualView> view, int index);
// Moves |view| to the specified |index|. A negative value for |index| moves
// |view| to the end.
void ReorderChildView(AXVirtualView* view, int index);
// Removes |view| from this virtual view. The view's parent will change to
// nullptr. Hands ownership back to the caller.
std::unique_ptr<AXVirtualView> RemoveChildView(AXVirtualView* view);
// Removes all the children from this virtual view.
// The virtual views are deleted.
void RemoveAllChildViews();
bool has_children() const { return !children_.empty(); }
const AXVirtualView* child_at(int index) const;
AXVirtualView* child_at(int index);
// Returns the parent view if the parent is a real View and not an
// AXVirtualView. Returns nullptr otherwise.
const View* parent_view() const { return parent_view_; }
View* parent_view() { return parent_view_; }
// Returns the parent view if the parent is an AXVirtualView and not a real
// View. Returns nullptr otherwise.
const AXVirtualView* virtual_parent_view() const {
return virtual_parent_view_;
}
AXVirtualView* virtual_parent_view() { return virtual_parent_view_; }
// Returns true if |view| is contained within the hierarchy of this
// AXVirtualView, even as an indirect descendant. Will return true if |view|
// is also this AXVirtualView.
bool Contains(const AXVirtualView* view) const;
// Returns the index of |view|, or -1 if |view| is not a child of this virtual
// view.
int GetIndexOf(const AXVirtualView* view) const;
//
// Methods also found in ViewAccessibility.
//
gfx::NativeViewAccessible GetNativeObject() const;
void NotifyAccessibilityEvent(ax::mojom::Event event_type);
void OverrideRole(const ax::mojom::Role role);
void OverrideState(ax::mojom::State state);
void OverrideName(const std::string& name);
void OverrideName(const base::string16& name);
void OverrideDescription(const std::string& description);
void OverrideDescription(const base::string16& description);
void OverrideBoundsRect(const gfx::RectF& location);
// ui::AXPlatformNodeDelegate
const ui::AXNodeData& GetData() const override;
int GetChildCount() override;
gfx::NativeViewAccessible ChildAtIndex(int index) override;
gfx::NativeWindow GetTopLevelWidget() override;
gfx::NativeViewAccessible GetParent() override;
gfx::Rect GetClippedScreenBoundsRect() const override;
gfx::Rect GetUnclippedScreenBoundsRect() const override;
gfx::NativeViewAccessible HitTestSync(int x, int y) override;
gfx::NativeViewAccessible GetFocus() override;
ui::AXPlatformNode* GetFromNodeID(int32_t id) override;
bool AccessibilityPerformAction(const ui::AXActionData& data) override;
bool ShouldIgnoreHoveredStateForTesting() override;
bool IsOffscreen() const override;
const ui::AXUniqueId& GetUniqueId() const override;
private:
// Sets the parent view if the parent is a real View and not an AXVirtualView.
// It is invalid to set both |parent_view_| and |virtual_parent_view_|.
void set_parent_view(View* view) {
DCHECK(!virtual_parent_view_);
parent_view_ = view;
}
bool IsParentVisible() const;
// We own this, but it is reference-counted on some platforms so we can't use
// a unique_ptr. It is destroyed in the destructor.
ui::AXPlatformNode* ax_platform_node_;
// Weak. Owns us if not nullptr.
// Either |parent_view_| or |virtual_parent_view_| should be set but not both.
View* parent_view_;
// Weak. Owns us if not nullptr.
// Either |parent_view_| or |virtual_parent_view_| should be set but not both.
AXVirtualView* virtual_parent_view_;
// We own our children.
std::vector<std::unique_ptr<AXVirtualView>> children_;
ui::AXUniqueId unique_id_;
mutable ui::AXNodeData custom_data_;
friend class ViewAccessibility;
DISALLOW_COPY_AND_ASSIGN(AXVirtualView);
};
} // namespace views
#endif // UI_VIEWS_ACCESSIBILITY_AX_VIRTUAL_VIEW_H_
// 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 "ui/views/accessibility/ax_virtual_view.h"
#include "base/memory/ptr_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace test {
namespace {
class TestButton : public Button {
public:
TestButton() : Button(NULL) {}
~TestButton() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(TestButton);
};
} // namespace
class AXVirtualViewTest : public ViewsTestBase {
public:
AXVirtualViewTest() = default;
~AXVirtualViewTest() override = default;
void SetUp() override {
ViewsTestBase::SetUp();
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
widget_->Init(params);
button_ = new TestButton;
button_->SetSize(gfx::Size(20, 20));
widget_->GetContentsView()->AddChildView(button_);
virtual_label_ = new AXVirtualView;
virtual_label_->OverrideRole(ax::mojom::Role::kStaticText);
virtual_label_->OverrideName("Label");
button_->GetViewAccessibility().AddVirtualChildView(
base::WrapUnique(virtual_label_));
widget_->Show();
}
void TearDown() override {
if (!widget_->IsClosed())
widget_->Close();
ViewsTestBase::TearDown();
}
ViewAXPlatformNodeDelegate* GetButtonAccessibility() {
return static_cast<ViewAXPlatformNodeDelegate*>(
&button_->GetViewAccessibility());
}
protected:
Widget* widget_;
Button* button_;
// Weak, |button_| owns this.
AXVirtualView* virtual_label_;
private:
DISALLOW_COPY_AND_ASSIGN(AXVirtualViewTest);
};
TEST_F(AXVirtualViewTest, AccessibilityRoleAndName) {
EXPECT_EQ(ax::mojom::Role::kButton, GetButtonAccessibility()->GetData().role);
EXPECT_EQ(ax::mojom::Role::kStaticText, virtual_label_->GetData().role);
EXPECT_EQ("Label", virtual_label_->GetData().GetStringAttribute(
ax::mojom::StringAttribute::kName));
}
TEST_F(AXVirtualViewTest, VirtualLabelIsChildOfButton) {
EXPECT_EQ(1, GetButtonAccessibility()->GetChildCount());
EXPECT_EQ(0, virtual_label_->GetChildCount());
ASSERT_NE(nullptr, virtual_label_->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
ASSERT_NE(nullptr, GetButtonAccessibility()->ChildAtIndex(0));
EXPECT_EQ(virtual_label_->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(0));
}
TEST_F(AXVirtualViewTest, AddingAndRemovingVirtualChildren) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
EXPECT_EQ(1, virtual_label_->GetChildCount());
ASSERT_NE(nullptr, virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
EXPECT_EQ(2, virtual_label_->GetChildCount());
ASSERT_NE(nullptr, virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
EXPECT_EQ(2, virtual_label_->GetChildCount());
EXPECT_EQ(0, virtual_child_1->GetChildCount());
EXPECT_EQ(1, virtual_child_2->GetChildCount());
ASSERT_NE(nullptr, virtual_child_3->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_3->GetParent());
ASSERT_NE(nullptr, virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
virtual_child_2->RemoveChildView(virtual_child_3);
EXPECT_EQ(0, virtual_child_2->GetChildCount());
EXPECT_EQ(2, virtual_label_->GetChildCount());
virtual_label_->RemoveAllChildViews();
EXPECT_EQ(0, virtual_label_->GetChildCount());
}
TEST_F(AXVirtualViewTest, ReorderingVirtualChildren) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
ASSERT_EQ(1, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
ASSERT_EQ(2, virtual_label_->GetChildCount());
virtual_label_->ReorderChildView(virtual_child_1, -1);
ASSERT_EQ(2, virtual_label_->GetChildCount());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
virtual_label_->ReorderChildView(virtual_child_1, 0);
ASSERT_EQ(2, virtual_label_->GetChildCount());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
virtual_label_->RemoveAllChildViews();
ASSERT_EQ(0, virtual_label_->GetChildCount());
}
TEST_F(AXVirtualViewTest, ContainsVirtualChild) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
ASSERT_EQ(1, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
ASSERT_EQ(2, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
ASSERT_EQ(1, virtual_child_2->GetChildCount());
EXPECT_TRUE(virtual_label_->Contains(virtual_label_));
EXPECT_TRUE(virtual_label_->Contains(virtual_child_1));
EXPECT_TRUE(virtual_label_->Contains(virtual_child_2));
EXPECT_TRUE(virtual_label_->Contains(virtual_child_3));
EXPECT_TRUE(virtual_child_2->Contains(virtual_child_2));
EXPECT_TRUE(virtual_child_2->Contains(virtual_child_3));
EXPECT_FALSE(virtual_child_1->Contains(virtual_label_));
EXPECT_FALSE(virtual_child_2->Contains(virtual_label_));
EXPECT_FALSE(virtual_child_3->Contains(virtual_child_2));
virtual_label_->RemoveAllChildViews();
ASSERT_EQ(0, virtual_label_->GetChildCount());
}
// Verify that virtual views with invisible ancestors inherit the
// ax::mojom::State::kInvisible state.
TEST_F(AXVirtualViewTest, InvisibleVirtualViews) {
EXPECT_TRUE(widget_->IsVisible());
EXPECT_FALSE(GetButtonAccessibility()->GetData().HasState(
ax::mojom::State::kInvisible));
EXPECT_FALSE(
virtual_label_->GetData().HasState(ax::mojom::State::kInvisible));
button_->SetVisible(false);
EXPECT_TRUE(GetButtonAccessibility()->GetData().HasState(
ax::mojom::State::kInvisible));
EXPECT_TRUE(virtual_label_->GetData().HasState(ax::mojom::State::kInvisible));
button_->SetVisible(true);
}
} // namespace test
} // namespace views
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/accessibility/view_accessibility.h"
#include <algorithm>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/platform/ax_platform_node.h" #include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/base/ui_features.h" #include "ui/base/ui_features.h"
...@@ -36,6 +40,7 @@ bool IsValidRoleForViews(ax::mojom::Role role) { ...@@ -36,6 +40,7 @@ bool IsValidRoleForViews(ax::mojom::Role role) {
#if !BUILDFLAG_INTERNAL_HAS_NATIVE_ACCESSIBILITY() #if !BUILDFLAG_INTERNAL_HAS_NATIVE_ACCESSIBILITY()
// static // static
std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) { std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) {
// Cannot use std::make_unique because constructor is protected.
return base::WrapUnique(new ViewAccessibility(view)); return base::WrapUnique(new ViewAccessibility(view));
} }
#endif #endif
...@@ -45,6 +50,63 @@ ViewAccessibility::ViewAccessibility(View* view) ...@@ -45,6 +50,63 @@ ViewAccessibility::ViewAccessibility(View* view)
ViewAccessibility::~ViewAccessibility() = default; ViewAccessibility::~ViewAccessibility() = default;
void ViewAccessibility::AddVirtualChildView(
std::unique_ptr<AXVirtualView> virtual_view) {
DCHECK(virtual_view);
if (virtual_view->parent_view() == view())
return;
AddVirtualChildViewAt(std::move(virtual_view), virtual_child_count());
}
void ViewAccessibility::AddVirtualChildViewAt(
std::unique_ptr<AXVirtualView> virtual_view,
int index) {
DCHECK(virtual_view);
DCHECK(!virtual_view->parent_view()) << "This |view| already has a View "
"parent. Call RemoveVirtualChildView "
"first.";
DCHECK(!virtual_view->virtual_parent_view()) << "This |view| already has an "
"AXVirtualView parent. Call "
"RemoveChildView first.";
DCHECK_GE(index, 0);
DCHECK_LE(index, virtual_child_count());
virtual_view->set_parent_view(view());
virtual_children_.insert(virtual_children_.begin() + index,
std::move(virtual_view));
}
std::unique_ptr<AXVirtualView> ViewAccessibility::RemoveVirtualChildView(
AXVirtualView* virtual_view) {
DCHECK(virtual_view);
int cur_index = GetIndexOf(virtual_view);
if (cur_index < 0)
return {};
std::unique_ptr<AXVirtualView> child =
std::move(virtual_children_[cur_index]);
virtual_children_.erase(virtual_children_.begin() + cur_index);
child->set_parent_view(nullptr);
return child;
}
void ViewAccessibility::RemoveAllVirtualChildViews() {
while (!virtual_children_.empty())
RemoveVirtualChildView(virtual_children_.back().get());
}
int ViewAccessibility::GetIndexOf(const AXVirtualView* virtual_view) const {
DCHECK(virtual_view);
const auto iter =
std::find_if(virtual_children_.begin(), virtual_children_.end(),
[virtual_view](const auto& child) {
return child.get() == virtual_view;
});
return iter != virtual_children_.end()
? static_cast<int>(iter - virtual_children_.begin())
: -1;
}
const ui::AXUniqueId& ViewAccessibility::GetUniqueId() const { const ui::AXUniqueId& ViewAccessibility::GetUniqueId() const {
return unique_id_; return unique_id_;
} }
...@@ -109,7 +171,7 @@ bool ViewAccessibility::IsLeaf() const { ...@@ -109,7 +171,7 @@ bool ViewAccessibility::IsLeaf() const {
return is_leaf_; return is_leaf_;
} }
void ViewAccessibility::OverrideRole(ax::mojom::Role role) { void ViewAccessibility::OverrideRole(const ax::mojom::Role role) {
DCHECK(IsValidRoleForViews(role)); DCHECK(IsValidRoleForViews(role));
custom_data_.role = role; custom_data_.role = role;
...@@ -120,14 +182,15 @@ void ViewAccessibility::OverrideName(const std::string& name) { ...@@ -120,14 +182,15 @@ void ViewAccessibility::OverrideName(const std::string& name) {
} }
void ViewAccessibility::OverrideName(const base::string16& name) { void ViewAccessibility::OverrideName(const base::string16& name) {
custom_data_.SetName(base::UTF16ToUTF8(name)); custom_data_.SetName(name);
} }
void ViewAccessibility::OverrideDescription(const std::string& description) { void ViewAccessibility::OverrideDescription(const std::string& description) {
DCHECK(!custom_data_.HasStringAttribute( custom_data_.SetDescription(description);
ax::mojom::StringAttribute::kDescription)); }
custom_data_.AddStringAttribute(ax::mojom::StringAttribute::kDescription,
description); void ViewAccessibility::OverrideDescription(const base::string16& description) {
custom_data_.SetDescription(description);
} }
void ViewAccessibility::OverrideIsLeaf() { void ViewAccessibility::OverrideIsLeaf() {
......
...@@ -6,13 +6,16 @@ ...@@ -6,13 +6,16 @@
#define UI_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H_ #define UI_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_H_
#include <memory> #include <memory>
#include <string>
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/string16.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_unique_id.h" #include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/gfx/native_widget_types.h" #include "ui/gfx/native_widget_types.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/views_export.h" #include "ui/views/views_export.h"
namespace views { namespace views {
...@@ -45,10 +48,11 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -45,10 +48,11 @@ class VIEWS_EXPORT ViewAccessibility {
// Note that string attributes are only used if non-empty, so you can't // Note that string attributes are only used if non-empty, so you can't
// override a string with the empty string. // override a string with the empty string.
// //
void OverrideRole(ax::mojom::Role role); void OverrideRole(const ax::mojom::Role role);
void OverrideName(const std::string& name); void OverrideName(const std::string& name);
void OverrideName(const base::string16& name); void OverrideName(const base::string16& name);
void OverrideDescription(const std::string& description); void OverrideDescription(const std::string& description);
void OverrideDescription(const base::string16& description);
void OverrideIsLeaf(); // Force this node to be treated as a leaf node. void OverrideIsLeaf(); // Force this node to be treated as a leaf node.
virtual gfx::NativeViewAccessible GetNativeObject(); virtual gfx::NativeViewAccessible GetNativeObject();
...@@ -64,6 +68,39 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -64,6 +68,39 @@ class VIEWS_EXPORT ViewAccessibility {
bool is_ignored() const { return is_ignored_; } bool is_ignored() const { return is_ignored_; }
void set_is_ignored(bool ignored) { is_ignored_ = ignored; } void set_is_ignored(bool ignored) { is_ignored_ = ignored; }
//
// Methods for managing virtual views.
//
// Adds |virtual_view| as a child of this View, optionally at |index|.
// We take ownership of our virtual children.
void AddVirtualChildView(std::unique_ptr<AXVirtualView> virtual_view);
void AddVirtualChildViewAt(std::unique_ptr<AXVirtualView> virtual_view,
int index);
// Removes |virtual_view| from this View. The virtual view's parent will
// change to nullptr. Hands ownership back to the caller.
std::unique_ptr<AXVirtualView> RemoveVirtualChildView(
AXVirtualView* virtual_view);
// Removes all the virtual children from this View.
// The virtual views are deleted.
void RemoveAllVirtualChildViews();
int virtual_child_count() const {
return static_cast<int>(virtual_children_.size());
}
const AXVirtualView* virtual_child_at(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, virtual_child_count());
return virtual_children_[index].get();
}
// Returns the index of |virtual_view|, or -1 if |virtual_view| is not a child
// of this View.
int GetIndexOf(const AXVirtualView* virtual_view) const;
protected: protected:
explicit ViewAccessibility(View* view); explicit ViewAccessibility(View* view);
...@@ -73,6 +110,10 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -73,6 +110,10 @@ class VIEWS_EXPORT ViewAccessibility {
// Weak. Owns this. // Weak. Owns this.
View* const owner_view_; View* const owner_view_;
// If there are any virtual children, they override any real children.
// We own our virtual children.
std::vector<std::unique_ptr<AXVirtualView>> virtual_children_;
const ui::AXUniqueId unique_id_; const ui::AXUniqueId unique_id_;
// Contains data set explicitly via SetRole, SetName, etc. that overrides // Contains data set explicitly via SetRole, SetName, etc. that overrides
......
...@@ -9,10 +9,12 @@ ...@@ -9,10 +9,12 @@
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_role_properties.h" #include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/platform/ax_platform_node.h" #include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/events/event_utils.h" #include "ui/events/event_utils.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/accessibility/view_accessibility_utils.h" #include "ui/views/accessibility/view_accessibility_utils.h"
#include "ui/views/controls/native/native_view_host.h" #include "ui/views/controls/native/native_view_host.h"
#include "ui/views/view.h" #include "ui/views/view.h"
...@@ -227,6 +229,10 @@ const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const { ...@@ -227,6 +229,10 @@ const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const {
int ViewAXPlatformNodeDelegate::GetChildCount() { int ViewAXPlatformNodeDelegate::GetChildCount() {
if (IsLeaf()) if (IsLeaf())
return 0; return 0;
if (virtual_child_count())
return virtual_child_count();
int child_count = view()->child_count(); int child_count = view()->child_count();
std::vector<Widget*> child_widgets; std::vector<Widget*> child_widgets;
...@@ -242,9 +248,15 @@ int ViewAXPlatformNodeDelegate::GetChildCount() { ...@@ -242,9 +248,15 @@ int ViewAXPlatformNodeDelegate::GetChildCount() {
} }
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(int index) { gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(int index) {
DCHECK_GE(index, 0) << "Child indices should be greater or equal to 0.";
DCHECK_LT(index, GetChildCount())
<< "Child indices should be less than the child count.";
if (IsLeaf()) if (IsLeaf())
return nullptr; return nullptr;
if (virtual_child_count())
return virtual_child_at(index)->GetNativeObject();
// If this is a root view, our widget might have child widgets. Include // If this is a root view, our widget might have child widgets. Include
std::vector<Widget*> child_widgets; std::vector<Widget*> child_widgets;
bool is_tab_modal_showing; bool is_tab_modal_showing;
......
...@@ -5,21 +5,26 @@ ...@@ -5,21 +5,26 @@
#ifndef UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_ #ifndef UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_
#define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_ #define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_
#include <memory> #include <stdint.h>
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/string16.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate_base.h" #include "ui/accessibility/platform/ax_platform_node_delegate_base.h"
#include "ui/accessibility/platform/ax_unique_id.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h" #include "ui/gfx/native_widget_types.h"
#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/views_export.h"
#include "ui/views/widget/widget_observer.h" #include "ui/views/widget/widget_observer.h"
namespace ui {
struct AXActionData;
class AXUniqueId;
} // namespace ui
namespace views { namespace views {
class View; class View;
...@@ -29,9 +34,8 @@ class Widget; ...@@ -29,9 +34,8 @@ class Widget;
// |ViewAXPlatformNodeDelegate| to interface with the native accessibility // |ViewAXPlatformNodeDelegate| to interface with the native accessibility
// toolkit. This class owns the |AXPlatformNode|, which implements those native // toolkit. This class owns the |AXPlatformNode|, which implements those native
// APIs. // APIs.
class VIEWS_EXPORT ViewAXPlatformNodeDelegate class ViewAXPlatformNodeDelegate : public ViewAccessibility,
: public ViewAccessibility, public ui::AXPlatformNodeDelegateBase {
public ui::AXPlatformNodeDelegateBase {
public: public:
~ViewAXPlatformNodeDelegate() override; ~ViewAXPlatformNodeDelegate() override;
...@@ -56,8 +60,8 @@ class VIEWS_EXPORT ViewAXPlatformNodeDelegate ...@@ -56,8 +60,8 @@ class VIEWS_EXPORT ViewAXPlatformNodeDelegate
bool AccessibilityPerformAction(const ui::AXActionData& data) override; bool AccessibilityPerformAction(const ui::AXActionData& data) override;
bool ShouldIgnoreHoveredStateForTesting() override; bool ShouldIgnoreHoveredStateForTesting() override;
bool IsOffscreen() const override; bool IsOffscreen() const override;
const ui::AXUniqueId& GetUniqueId() // Also in |ViewAccessibility|.
const override; // Also in ViewAccessibility const ui::AXUniqueId& GetUniqueId() const override;
protected: protected:
explicit ViewAXPlatformNodeDelegate(View* view); explicit ViewAXPlatformNodeDelegate(View* view);
...@@ -74,7 +78,7 @@ class VIEWS_EXPORT ViewAXPlatformNodeDelegate ...@@ -74,7 +78,7 @@ class VIEWS_EXPORT ViewAXPlatformNodeDelegate
void OnMenuEnd(); void OnMenuEnd();
// We own this, but it is reference-counted on some platforms so we can't use // We own this, but it is reference-counted on some platforms so we can't use
// a scoped_ptr. It is dereferenced in the destructor. // a unique_ptr. It is destroyed in the destructor.
ui::AXPlatformNode* ax_node_; ui::AXPlatformNode* ax_node_;
mutable ui::AXNodeData data_; mutable ui::AXNodeData data_;
......
...@@ -5,8 +5,12 @@ ...@@ -5,8 +5,12 @@
#include "ui/views/accessibility/view_ax_platform_node_delegate.h" #include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/ax_widget_obj_wrapper.h" #include "ui/views/accessibility/ax_widget_obj_wrapper.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