summaryrefslogtreecommitdiff
path: root/chromium/ui/views/accessibility
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-07-16 11:45:35 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-07-17 08:59:23 +0000
commit552906b0f222c5d5dd11b9fd73829d510980461a (patch)
tree3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/ui/views/accessibility
parent1b05827804eaf047779b597718c03e7d38344261 (diff)
downloadqtwebengine-chromium-552906b0f222c5d5dd11b9fd73829d510980461a.tar.gz
BASELINE: Update Chromium to 83.0.4103.122
Change-Id: Ie3a82f5bb0076eec2a7c6a6162326b4301ee291e Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/ui/views/accessibility')
-rw-r--r--chromium/ui/views/accessibility/accessibility_alert_window.cc1
-rw-r--r--chromium/ui/views/accessibility/ax_aura_obj_cache.cc30
-rw-r--r--chromium/ui/views/accessibility/ax_aura_obj_cache.h9
-rw-r--r--chromium/ui/views/accessibility/ax_aura_obj_cache_unittest.cc3
-rw-r--r--chromium/ui/views/accessibility/ax_aura_obj_wrapper.h3
-rw-r--r--chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc3
-rw-r--r--chromium/ui/views/accessibility/ax_tree_source_views.cc1
-rw-r--r--chromium/ui/views/accessibility/ax_tree_source_views.h5
-rw-r--r--chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc2
-rw-r--r--chromium/ui/views/accessibility/ax_view_obj_wrapper.cc4
-rw-r--r--chromium/ui/views/accessibility/ax_view_obj_wrapper.h2
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view.cc160
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view.h29
-rw-r--r--chromium/ui/views/accessibility/ax_virtual_view_unittest.cc242
-rw-r--r--chromium/ui/views/accessibility/ax_widget_obj_wrapper.h2
-rw-r--r--chromium/ui/views/accessibility/ax_window_obj_wrapper.cc33
-rw-r--r--chromium/ui/views/accessibility/ax_window_obj_wrapper.h2
-rw-r--r--chromium/ui/views/accessibility/view_accessibility.cc33
-rw-r--r--chromium/ui/views/accessibility/view_accessibility.h23
-rw-r--r--chromium/ui/views/accessibility/view_accessibility_utils.cc2
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc91
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate.h15
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc12
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h1
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc5
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc90
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc6
-rw-r--r--chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc31
-rw-r--r--chromium/ui/views/accessibility/views_ax_tree_manager.cc165
-rw-r--r--chromium/ui/views/accessibility/views_ax_tree_manager.h152
-rw-r--r--chromium/ui/views/accessibility/views_ax_tree_manager_unittest.cc189
31 files changed, 1184 insertions, 162 deletions
diff --git a/chromium/ui/views/accessibility/accessibility_alert_window.cc b/chromium/ui/views/accessibility/accessibility_alert_window.cc
index bd10be7fc02..358b295e012 100644
--- a/chromium/ui/views/accessibility/accessibility_alert_window.cc
+++ b/chromium/ui/views/accessibility/accessibility_alert_window.cc
@@ -41,4 +41,5 @@ void AccessibilityAlertWindow::OnWillDestroyEnv() {
observer_.RemoveAll();
alert_window_.reset();
}
+
} // namespace views
diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc
index 0e90ea2aa28..3197d2eec5c 100644
--- a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc
+++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc
@@ -4,6 +4,8 @@
#include "ui/views/accessibility/ax_aura_obj_cache.h"
+#include <utility>
+
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
@@ -35,15 +37,19 @@ AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(View* view) {
// Avoid problems with transient focus events. https://crbug.com/729449
if (!view->GetWidget())
return nullptr;
- return CreateInternal<AXViewObjWrapper>(view, view_to_id_map_);
+ return CreateInternal<AXViewObjWrapper>(view, &view_to_id_map_);
}
AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(Widget* widget) {
- return CreateInternal<AXWidgetObjWrapper>(widget, widget_to_id_map_);
+ return CreateInternal<AXWidgetObjWrapper>(widget, &widget_to_id_map_);
}
AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(aura::Window* window) {
- return CreateInternal<AXWindowObjWrapper>(window, window_to_id_map_);
+ return CreateInternal<AXWindowObjWrapper>(window, &window_to_id_map_);
+}
+
+void AXAuraObjCache::CreateOrReplace(std::unique_ptr<AXAuraObjWrapper> obj) {
+ cache_[obj->GetUniqueId()] = std::move(obj);
}
int32_t AXAuraObjCache::GetID(View* view) const {
@@ -59,7 +65,7 @@ int32_t AXAuraObjCache::GetID(aura::Window* window) const {
}
void AXAuraObjCache::Remove(View* view) {
- RemoveInternal(view, view_to_id_map_);
+ RemoveInternal(view, &view_to_id_map_);
}
void AXAuraObjCache::RemoveViewSubtree(View* view) {
@@ -69,7 +75,7 @@ void AXAuraObjCache::RemoveViewSubtree(View* view) {
}
void AXAuraObjCache::Remove(Widget* widget) {
- RemoveInternal(widget, widget_to_id_map_);
+ RemoveInternal(widget, &widget_to_id_map_);
// When an entire widget is deleted, it doesn't always send a notification
// on each of its views, so we need to explore them recursively.
@@ -81,7 +87,7 @@ void AXAuraObjCache::Remove(Widget* widget) {
void AXAuraObjCache::Remove(aura::Window* window, aura::Window* parent) {
int id = GetIDInternal(parent, window_to_id_map_);
AXAuraObjWrapper* parent_window_obj = Get(id);
- RemoveInternal(window, window_to_id_map_);
+ RemoveInternal(window, &window_to_id_map_);
if (parent && delegate_)
delegate_->OnChildWindowRemoved(parent_window_obj);
}
@@ -202,18 +208,18 @@ void AXAuraObjCache::OnRootWindowObjDestroyed(aura::Window* window) {
template <typename AuraViewWrapper, typename AuraView>
AXAuraObjWrapper* AXAuraObjCache::CreateInternal(
AuraView* aura_view,
- std::map<AuraView*, int32_t>& aura_view_to_id_map) {
+ std::map<AuraView*, int32_t>* aura_view_to_id_map) {
if (!aura_view)
return nullptr;
- auto it = aura_view_to_id_map.find(aura_view);
+ auto it = aura_view_to_id_map->find(aura_view);
- if (it != aura_view_to_id_map.end())
+ if (it != aura_view_to_id_map->end())
return Get(it->second);
auto wrapper = std::make_unique<AuraViewWrapper>(this, aura_view);
int32_t id = wrapper->GetUniqueId();
- aura_view_to_id_map[aura_view] = id;
+ (*aura_view_to_id_map)[aura_view] = id;
cache_[id] = std::move(wrapper);
return cache_[id].get();
}
@@ -233,11 +239,11 @@ int32_t AXAuraObjCache::GetIDInternal(
template <typename AuraView>
void AXAuraObjCache::RemoveInternal(
AuraView* aura_view,
- std::map<AuraView*, int32_t>& aura_view_to_id_map) {
+ std::map<AuraView*, int32_t>* aura_view_to_id_map) {
int32_t id = GetID(aura_view);
if (id == ui::AXNode::kInvalidAXID)
return;
- aura_view_to_id_map.erase(aura_view);
+ aura_view_to_id_map->erase(aura_view);
cache_.erase(id);
}
diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.h b/chromium/ui/views/accessibility/ax_aura_obj_cache.h
index 33fec28da18..05d4a795bc2 100644
--- a/chromium/ui/views/accessibility/ax_aura_obj_cache.h
+++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.h
@@ -55,6 +55,11 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver {
AXAuraObjWrapper* GetOrCreate(Widget* widget);
AXAuraObjWrapper* GetOrCreate(aura::Window* window);
+ // Creates an entry in this cache given a wrapper object. Use this method when
+ // creating a wrapper not backed by any of the supported views above. This
+ // cache will take ownership. Will replace an existing entry with the same id.
+ void CreateOrReplace(std::unique_ptr<AXAuraObjWrapper> obj);
+
// Gets an id given an Aura view.
int32_t GetID(View* view) const;
int32_t GetID(Widget* widget) const;
@@ -114,7 +119,7 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver {
template <typename AuraViewWrapper, typename AuraView>
AXAuraObjWrapper* CreateInternal(
AuraView* aura_view,
- std::map<AuraView*, int32_t>& aura_view_to_id_map);
+ std::map<AuraView*, int32_t>* aura_view_to_id_map);
template <typename AuraView>
int32_t GetIDInternal(
@@ -123,7 +128,7 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver {
template <typename AuraView>
void RemoveInternal(AuraView* aura_view,
- std::map<AuraView*, int32_t>& aura_view_to_id_map);
+ std::map<AuraView*, int32_t>* aura_view_to_id_map);
std::map<views::View*, int32_t> view_to_id_map_;
std::map<views::Widget*, int32_t> widget_to_id_map_;
diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache_unittest.cc b/chromium/ui/views/accessibility/ax_aura_obj_cache_unittest.cc
index 073a05654bd..4008179a8bc 100644
--- a/chromium/ui/views/accessibility/ax_aura_obj_cache_unittest.cc
+++ b/chromium/ui/views/accessibility/ax_aura_obj_cache_unittest.cc
@@ -4,6 +4,9 @@
#include "ui/views/accessibility/ax_aura_obj_cache.h"
+#include <string>
+#include <utility>
+
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
diff --git a/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h b/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h
index d950d913f1c..39ef97a6c04 100644
--- a/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h
@@ -34,8 +34,7 @@ class VIEWS_EXPORT AXAuraObjWrapper {
// Traversal and serialization.
virtual AXAuraObjWrapper* GetParent() = 0;
- virtual void GetChildren(
- std::vector<AXAuraObjWrapper*>* out_children) = 0;
+ virtual void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) = 0;
virtual void Serialize(ui::AXNodeData* out_node_data) = 0;
virtual int32_t GetUniqueId() const = 0;
diff --git a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
index 284179e4ed5..f4315d4fb58 100644
--- a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
+++ b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <windows.h>
+#include <windows.h> // Must come before other Windows system headers.
+
#include <oleacc.h>
#include <wrl/client.h>
diff --git a/chromium/ui/views/accessibility/ax_tree_source_views.cc b/chromium/ui/views/accessibility/ax_tree_source_views.cc
index 9e52448ad75..fbbca160b51 100644
--- a/chromium/ui/views/accessibility/ax_tree_source_views.cc
+++ b/chromium/ui/views/accessibility/ax_tree_source_views.cc
@@ -4,6 +4,7 @@
#include "ui/views/accessibility/ax_tree_source_views.h"
+#include <string>
#include <vector>
#include "ui/accessibility/ax_action_data.h"
diff --git a/chromium/ui/views/accessibility/ax_tree_source_views.h b/chromium/ui/views/accessibility/ax_tree_source_views.h
index cdbbea25281..2640ea24442 100644
--- a/chromium/ui/views/accessibility/ax_tree_source_views.h
+++ b/chromium/ui/views/accessibility/ax_tree_source_views.h
@@ -5,6 +5,9 @@
#ifndef UI_VIEWS_ACCESSIBILITY_AX_TREE_SOURCE_VIEWS_H_
#define UI_VIEWS_ACCESSIBILITY_AX_TREE_SOURCE_VIEWS_H_
+#include <string>
+#include <vector>
+
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_source.h"
#include "ui/views/views_export.h"
@@ -13,7 +16,7 @@ namespace ui {
struct AXActionData;
struct AXNodeData;
struct AXTreeData;
-}
+} // namespace ui
namespace views {
diff --git a/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
index cfca0e07862..bb90519552e 100644
--- a/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
+++ b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc
@@ -4,6 +4,8 @@
#include "ui/views/accessibility/ax_tree_source_views.h"
+#include <memory>
+#include <utility>
#include <vector>
#include "base/strings/utf_string_conversions.h"
diff --git a/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc
index 951ad0f567b..64b0303bef6 100644
--- a/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc
+++ b/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc
@@ -4,6 +4,8 @@
#include "ui/views/accessibility/ax_view_obj_wrapper.h"
+#include <vector>
+
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_unique_id.h"
@@ -24,7 +26,7 @@ AXViewObjWrapper::AXViewObjWrapper(AXAuraObjCache* aura_obj_cache, View* view)
AXViewObjWrapper::~AXViewObjWrapper() = default;
bool AXViewObjWrapper::IsIgnored() {
- return view_ ? view_->GetViewAccessibility().IsIgnored() : true;
+ return !view_ || view_->GetViewAccessibility().IsIgnored();
}
AXAuraObjWrapper* AXViewObjWrapper::GetParent() {
diff --git a/chromium/ui/views/accessibility/ax_view_obj_wrapper.h b/chromium/ui/views/accessibility/ax_view_obj_wrapper.h
index 6eb3f0e8066..02717a0ebc1 100644
--- a/chromium/ui/views/accessibility/ax_view_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_view_obj_wrapper.h
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <vector>
+
#include "base/scoped_observer.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/view.h"
diff --git a/chromium/ui/views/accessibility/ax_virtual_view.cc b/chromium/ui/views/accessibility/ax_virtual_view.cc
index f5ae13263af..cf273bc4d05 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view.cc
+++ b/chromium/ui/views/accessibility/ax_virtual_view.cc
@@ -16,6 +16,7 @@
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/base/ui_base_types.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/view.h"
@@ -66,13 +67,8 @@ AXVirtualView::~AXVirtualView() {
void AXVirtualView::AddChildView(std::unique_ptr<AXVirtualView> view) {
DCHECK(view);
if (view->virtual_parent_view_ == this)
- return;
- AddChildViewAt(std::move(view), GetChildCount());
-
- if (GetOwnerView()) {
- GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- false);
- }
+ return; // Already a child of this virtual view.
+ AddChildViewAt(std::move(view), int{children_.size()});
}
void AXVirtualView::AddChildViewAt(std::unique_ptr<AXVirtualView> view,
@@ -86,22 +82,22 @@ void AXVirtualView::AddChildViewAt(std::unique_ptr<AXVirtualView> view,
"AXVirtualView parent. Call "
"RemoveChildView first.";
DCHECK_GE(index, 0);
- DCHECK_LE(index, GetChildCount());
+ DCHECK_LE(index, int{children_.size()});
view->virtual_parent_view_ = this;
children_.insert(children_.begin() + index, std::move(view));
if (GetOwnerView()) {
GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- false);
+ true);
}
}
void AXVirtualView::ReorderChildView(AXVirtualView* view, int index) {
DCHECK(view);
- if (index >= GetChildCount())
+ if (index >= int{children_.size()})
return;
if (index < 0)
- index = GetChildCount() - 1;
+ index = int{children_.size()} - 1;
DCHECK_EQ(view->virtual_parent_view_, this);
if (children_[index].get() == view)
@@ -116,7 +112,19 @@ void AXVirtualView::ReorderChildView(AXVirtualView* view, int index) {
children_.insert(children_.begin() + index, std::move(child));
GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- false);
+ true);
+}
+
+std::unique_ptr<AXVirtualView> AXVirtualView::RemoveFromParentView() {
+ if (parent_view_)
+ return parent_view_->RemoveVirtualChildView(this);
+
+ if (virtual_parent_view_)
+ return virtual_parent_view_->RemoveChildView(this);
+
+ // This virtual view hasn't been added to a parent view yet.
+ NOTREACHED() << "Cannot remove from parent view if there is no parent.";
+ return {};
}
std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
@@ -126,12 +134,13 @@ std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
if (cur_index < 0)
return {};
+ bool focus_changed = false;
if (GetOwnerView()) {
ViewAccessibility& view_accessibility =
GetOwnerView()->GetViewAccessibility();
if (view_accessibility.FocusedVirtualChild() &&
Contains(view_accessibility.FocusedVirtualChild())) {
- view_accessibility.OverrideFocus(nullptr);
+ focus_changed = true;
}
}
@@ -141,8 +150,10 @@ std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
child->populate_data_callback_.Reset();
if (GetOwnerView()) {
+ if (focus_changed)
+ GetOwnerView()->GetViewAccessibility().OverrideFocus(nullptr);
GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- false);
+ true);
}
return child;
@@ -151,11 +162,6 @@ std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
void AXVirtualView::RemoveAllChildViews() {
while (!children_.empty())
RemoveChildView(children_.back().get());
-
- if (GetOwnerView()) {
- GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
- false);
- }
}
bool AXVirtualView::Contains(const AXVirtualView* view) const {
@@ -187,6 +193,12 @@ gfx::NativeViewAccessible AXVirtualView::GetNativeObject() const {
void AXVirtualView::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
+ if (GetOwnerView()) {
+ const ViewAccessibility::AccessibilityEventsCallback& events_callback =
+ GetOwnerView()->GetViewAccessibility().accessibility_events_callback();
+ if (events_callback)
+ events_callback.Run(this, event_type);
+ }
ax_platform_node_->NotifyAccessibilityEvent(event_type);
}
@@ -195,7 +207,7 @@ ui::AXNodeData& AXVirtualView::GetCustomData() {
}
void AXVirtualView::SetPopulateDataCallback(
- base::RepeatingCallback<void(const View&, ui::AXNodeData*)> callback) {
+ base::RepeatingCallback<void(ui::AXNodeData*)> callback) {
populate_data_callback_ = std::move(callback);
}
@@ -223,20 +235,41 @@ const ui::AXNodeData& AXVirtualView::GetData() const {
node_data.AddAction(ax::mojom::Action::kShowContextMenu);
if (populate_data_callback_ && GetOwnerView())
- populate_data_callback_.Run(*GetOwnerView(), &node_data);
+ populate_data_callback_.Run(&node_data);
return node_data;
}
-int AXVirtualView::GetChildCount() {
- return static_cast<int>(children_.size());
+int AXVirtualView::GetChildCount() const {
+ int count = 0;
+ for (const std::unique_ptr<AXVirtualView>& child : children_) {
+ if (child->IsIgnored()) {
+ count += child->GetChildCount();
+ continue;
+ }
+ count++;
+ }
+ return count;
}
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();
+ int i = 0;
+ for (const std::unique_ptr<AXVirtualView>& child : children_) {
+ if (child->IsIgnored()) {
+ if (index - i < child->GetChildCount()) {
+ gfx::NativeViewAccessible result = child->ChildAtIndex(index - i);
+ if (result)
+ return result;
+ }
+ i += child->GetChildCount();
+ continue;
+ }
+ if (i == index)
+ return child->GetNativeObject();
+ i++;
+ }
return nullptr;
}
@@ -255,8 +288,11 @@ gfx::NativeViewAccessible AXVirtualView::GetParent() {
if (parent_view_)
return parent_view_->GetNativeObject();
- if (virtual_parent_view_)
+ if (virtual_parent_view_) {
+ if (virtual_parent_view_->IsIgnored())
+ return virtual_parent_view_->GetParent();
return virtual_parent_view_->GetNativeObject();
+ }
// This virtual view hasn't been added to a parent view yet.
return nullptr;
@@ -267,10 +303,11 @@ gfx::Rect AXVirtualView::GetBoundsRect(
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
- case ui::AXCoordinateSystem::kScreen:
+ case ui::AXCoordinateSystem::kScreenDIPs:
// We could optionally add clipping here if ever needed.
// TODO(nektar): Implement bounds that are relative to the parent.
return gfx::ToEnclosingRect(custom_data_.relative_bounds.bounds);
+ case ui::AXCoordinateSystem::kScreenPhysicalPixels:
case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
@@ -278,17 +315,36 @@ gfx::Rect AXVirtualView::GetBoundsRect(
}
}
-gfx::NativeViewAccessible AXVirtualView::HitTestSync(int x, int y) {
- // TODO(nektar): Implement.
- return GetNativeObject();
+gfx::NativeViewAccessible AXVirtualView::HitTestSync(
+ int screen_physical_pixel_x,
+ int screen_physical_pixel_y) const {
+ if (custom_data_.relative_bounds.bounds.Contains(
+ static_cast<float>(screen_physical_pixel_x),
+ static_cast<float>(screen_physical_pixel_y))) {
+ if (!IsIgnored())
+ return GetNativeObject();
+ }
+
+ // Check if the point is within any of the virtual children of this view.
+ // AXVirtualView's HitTestSync is a recursive function that will return the
+ // deepest child, since it does not support relative bounds.
+ for (const std::unique_ptr<AXVirtualView>& child : children_) {
+ gfx::NativeViewAccessible result =
+ child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y);
+ if (result)
+ return result;
+ }
+ return nullptr;
}
gfx::NativeViewAccessible AXVirtualView::GetFocus() {
- if (parent_view_)
- return parent_view_->GetFocusedDescendant();
-
- if (virtual_parent_view_)
- return virtual_parent_view_->GetFocus();
+ auto* owner_view = GetOwnerView();
+ if (owner_view) {
+ if (!(owner_view->HasFocus())) {
+ return nullptr;
+ }
+ return owner_view->GetViewAccessibility().GetFocusedDescendant();
+ }
// This virtual view hasn't been added to a parent view yet.
return nullptr;
@@ -332,9 +388,41 @@ gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() {
return gfx::kNullAcceleratedWidget;
}
+bool AXVirtualView::IsIgnored() const {
+ const ui::AXNodeData& node_data = GetData();
+
+ // According to the ARIA spec, the node should not be ignored if it is
+ // focusable. This is to ensure that the focusable node is both understandable
+ // and operable.
+ if (node_data.HasState(ax::mojom::State::kFocusable))
+ return false;
+
+ return node_data.IsIgnored();
+}
+
bool AXVirtualView::HandleAccessibleAction(
const ui::AXActionData& action_data) {
- return false;
+ if (!GetOwnerView())
+ return false;
+
+ switch (action_data.action) {
+ case ax::mojom::Action::kShowContextMenu: {
+ const gfx::Rect screen_bounds = GetBoundsRect(
+ ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kClipped,
+ nullptr /* offscreen_result */);
+ if (!screen_bounds.IsEmpty()) {
+ GetOwnerView()->ShowContextMenu(screen_bounds.CenterPoint(),
+ ui::MENU_SOURCE_KEYBOARD);
+ return true;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return GetOwnerView()->HandleAccessibleAction(action_data);
}
View* AXVirtualView::GetOwnerView() const {
diff --git a/chromium/ui/views/accessibility/ax_virtual_view.h b/chromium/ui/views/accessibility/ax_virtual_view.h
index f4b0b073684..c74273c6ac8 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view.h
+++ b/chromium/ui/views/accessibility/ax_virtual_view.h
@@ -8,7 +8,6 @@
#include <stdint.h>
#include <memory>
-#include <string>
#include <vector>
#include "base/callback_forward.h"
@@ -74,6 +73,10 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// |view| to the end.
void ReorderChildView(AXVirtualView* view, int index);
+ // Removes this virtual view from its parent, which could either be a virtual
+ // or a real view. Hands ownership of this view back to the caller.
+ std::unique_ptr<AXVirtualView> RemoveFromParentView();
+
// 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);
@@ -120,13 +123,18 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// via a callback. This should be used for attributes that change often and
// would be queried every time a client accesses this view's AXNodeData.
void SetPopulateDataCallback(
- base::RepeatingCallback<void(const View&, ui::AXNodeData*)> callback);
+ base::RepeatingCallback<void(ui::AXNodeData*)> callback);
void UnsetPopulateDataCallback();
- // ui::AXPlatformNodeDelegate. Note that some of these functions have
- // Mac-specific implementations in ax_virtual_view_mac.mm.
+ // ui::AXPlatformNodeDelegate. Note that
+ // - Some of these functions have Mac-specific implementations in
+ // ax_virtual_view_mac.mm.
+ // - GetChildCount(), ChildAtIndex(), and GetParent() are used by assistive
+ // technologies to access the unignored accessibility tree, which doesn't
+ // necessarily reflect the internal descendant tree. (An ignored node means
+ // that the node should not be exposed to the platform.)
const ui::AXNodeData& GetData() const override;
- int GetChildCount() override;
+ int GetChildCount() const override;
gfx::NativeViewAccessible ChildAtIndex(int index) override;
gfx::NativeViewAccessible GetNSWindow() override;
gfx::NativeViewAccessible GetNativeViewAccessible() override;
@@ -135,7 +143,9 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const override;
- gfx::NativeViewAccessible HitTestSync(int x, int y) override;
+ gfx::NativeViewAccessible HitTestSync(
+ int screen_physical_pixel_x,
+ int screen_physical_pixel_y) const override;
gfx::NativeViewAccessible GetFocus() override;
ui::AXPlatformNode* GetFromNodeID(int32_t id) override;
bool AccessibilityPerformAction(const ui::AXActionData& data) override;
@@ -150,6 +160,10 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// Gets or creates a wrapper suitable for use with tree sources.
AXVirtualViewWrapper* GetOrCreateWrapper(views::AXAuraObjCache* cache);
+ // Returns true if this node is ignored and should be hidden from the
+ // accessibility tree. This does not impact the node's descendants.
+ bool IsIgnored() const;
+
// Handle a request from assistive technology to perform an action on this
// virtual view. Returns true on success, but note that the success/failure is
// not propagated to the client that requested the action, since the
@@ -186,8 +200,7 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
ui::AXUniqueId unique_id_;
ui::AXNodeData custom_data_;
- base::RepeatingCallback<void(const View&, ui::AXNodeData*)>
- populate_data_callback_;
+ base::RepeatingCallback<void(ui::AXNodeData*)> populate_data_callback_;
std::unique_ptr<AXVirtualViewWrapper> wrapper_;
diff --git a/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc b/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
index fd548e57ae5..3f1cb20f077 100644
--- a/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
+++ b/chromium/ui/views/accessibility/ax_virtual_view_unittest.cc
@@ -4,12 +4,20 @@
#include "ui/views/accessibility/ax_virtual_view.h"
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
+#include "ui/views/accessibility/view_accessibility.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"
@@ -44,6 +52,7 @@ class AXVirtualViewTest : public ViewsTestBase {
void SetUp() override {
ViewsTestBase::SetUp();
+ ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
@@ -58,6 +67,20 @@ class AXVirtualViewTest : public ViewsTestBase {
button_->GetViewAccessibility().AddVirtualChildView(
base::WrapUnique(virtual_label_));
widget_->Show();
+
+ ViewAccessibility::AccessibilityEventsCallback
+ accessibility_events_callback = base::BindRepeating(
+ [](std::vector<std::pair<const ui::AXPlatformNodeDelegate*,
+ const ax::mojom::Event>>*
+ accessibility_events,
+ const ui::AXPlatformNodeDelegate* delegate,
+ const ax::mojom::Event event_type) {
+ DCHECK(accessibility_events);
+ accessibility_events->push_back({delegate, event_type});
+ },
+ &accessibility_events_);
+ button_->GetViewAccessibility().set_accessibility_events_callback(
+ std::move(accessibility_events_callback));
}
void TearDown() override {
@@ -66,16 +89,40 @@ class AXVirtualViewTest : public ViewsTestBase {
ViewsTestBase::TearDown();
}
- ViewAXPlatformNodeDelegate* GetButtonAccessibility() {
+ protected:
+ ViewAXPlatformNodeDelegate* GetButtonAccessibility() const {
return static_cast<ViewAXPlatformNodeDelegate*>(
&button_->GetViewAccessibility());
}
- protected:
+ void ExpectReceivedAccessibilityEvents(
+ const std::vector<std::pair<const ui::AXPlatformNodeDelegate*,
+ const ax::mojom::Event>>& expected_events) {
+ EXPECT_EQ(accessibility_events_.size(), expected_events.size());
+
+ size_t i = 0;
+ for (const auto& actual_event : accessibility_events_) {
+ if (i >= expected_events.size())
+ break;
+
+ const auto& expected_event = expected_events[i];
+ EXPECT_EQ(actual_event.first, expected_event.first);
+ EXPECT_EQ(actual_event.second, expected_event.second);
+ ++i;
+ }
+
+ accessibility_events_.clear();
+ }
+
Widget* widget_;
Button* button_;
// Weak, |button_| owns this.
AXVirtualView* virtual_label_;
+
+ private:
+ std::vector<
+ std::pair<const ui::AXPlatformNodeDelegate*, const ax::mojom::Event>>
+ accessibility_events_;
};
TEST_F(AXVirtualViewTest, AccessibilityRoleAndName) {
@@ -138,8 +185,26 @@ TEST_F(AXVirtualViewTest, VirtualLabelIsChildOfButton) {
GetButtonAccessibility()->ChildAtIndex(0));
}
+TEST_F(AXVirtualViewTest, RemoveFromParentView) {
+ ASSERT_EQ(1, GetButtonAccessibility()->GetChildCount());
+ std::unique_ptr<AXVirtualView> removed_label =
+ virtual_label_->RemoveFromParentView();
+ EXPECT_EQ(nullptr, removed_label->GetParent());
+ EXPECT_TRUE(GetButtonAccessibility()->virtual_children().empty());
+
+ AXVirtualView* virtual_child_1 = new AXVirtualView;
+ removed_label->AddChildView(base::WrapUnique(virtual_child_1));
+ ASSERT_EQ(1, removed_label->GetChildCount());
+ ASSERT_NE(nullptr, virtual_child_1->GetParent());
+ std::unique_ptr<AXVirtualView> removed_child_1 =
+ virtual_child_1->RemoveFromParentView();
+ EXPECT_EQ(nullptr, removed_child_1->GetParent());
+ EXPECT_EQ(0, removed_label->GetChildCount());
+}
+
TEST_F(AXVirtualViewTest, AddingAndRemovingVirtualChildren) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
+ ExpectReceivedAccessibilityEvents({});
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
@@ -149,6 +214,8 @@ TEST_F(AXVirtualViewTest, AddingAndRemovingVirtualChildren) {
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
@@ -158,6 +225,8 @@ TEST_F(AXVirtualViewTest, AddingAndRemovingVirtualChildren) {
ASSERT_NE(nullptr, virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
@@ -169,13 +238,24 @@ TEST_F(AXVirtualViewTest, AddingAndRemovingVirtualChildren) {
ASSERT_NE(nullptr, virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
virtual_child_2->RemoveChildView(virtual_child_3);
EXPECT_EQ(0, virtual_child_2->GetChildCount());
EXPECT_EQ(2, virtual_label_->GetChildCount());
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
virtual_label_->RemoveAllChildViews();
EXPECT_EQ(0, virtual_label_->GetChildCount());
+ // There should be two "kChildrenChanged" events because Two virtual child
+ // views are removed in total.
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged),
+ std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged)});
}
TEST_F(AXVirtualViewTest, ReorderingVirtualChildren) {
@@ -285,42 +365,188 @@ TEST_F(AXVirtualViewTest, InvisibleVirtualViews) {
button_->SetVisible(true);
}
+// Verify that ignored virtual views are removed from the accessible tree and
+// that their contents are intact.
+TEST_F(AXVirtualViewTest, IgnoredVirtualViews) {
+ ASSERT_EQ(0, virtual_label_->GetChildCount());
+
+ // An ignored node should not be exposed.
+ AXVirtualView* virtual_child_1 = new AXVirtualView;
+ virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
+ virtual_child_1->GetCustomData().AddState(ax::mojom::State::kIgnored);
+ ASSERT_EQ(0, virtual_label_->GetChildCount());
+ ASSERT_EQ(0, virtual_child_1->GetChildCount());
+
+ // The contents of ignored nodes should be exposed.
+ AXVirtualView* virtual_child_2 = new AXVirtualView;
+ virtual_child_1->AddChildView(base::WrapUnique(virtual_child_2));
+ AXVirtualView* virtual_child_3 = new AXVirtualView;
+ virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
+ AXVirtualView* virtual_child_4 = new AXVirtualView;
+ virtual_child_2->AddChildView(base::WrapUnique(virtual_child_4));
+ ASSERT_EQ(1, virtual_label_->GetChildCount());
+ ASSERT_EQ(1, virtual_child_1->GetChildCount());
+ ASSERT_EQ(2, virtual_child_2->GetChildCount());
+ ASSERT_EQ(0, virtual_child_3->GetChildCount());
+ ASSERT_EQ(0, virtual_child_4->GetChildCount());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
+ EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_3->GetParent());
+ EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_4->GetParent());
+ EXPECT_EQ(virtual_child_2->GetNativeObject(),
+ virtual_label_->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_2->GetNativeObject(),
+ virtual_child_1->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ virtual_child_2->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ virtual_child_2->ChildAtIndex(1));
+
+ // The contents of ignored nodes should be unignored accessibility subtrees.
+ virtual_child_2->GetCustomData().role = ax::mojom::Role::kIgnored;
+ ASSERT_EQ(2, virtual_label_->GetChildCount());
+ ASSERT_EQ(2, virtual_child_1->GetChildCount());
+ ASSERT_EQ(2, virtual_child_2->GetChildCount());
+ ASSERT_EQ(0, virtual_child_3->GetChildCount());
+ ASSERT_EQ(0, virtual_child_4->GetChildCount());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_3->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_4->GetParent());
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ virtual_label_->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ virtual_label_->ChildAtIndex(1));
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ virtual_child_1->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ virtual_child_1->ChildAtIndex(1));
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ virtual_child_2->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ virtual_child_2->ChildAtIndex(1));
+
+ // Test for mixed ignored and unignored virtual children.
+ AXVirtualView* virtual_child_5 = new AXVirtualView;
+ virtual_child_1->AddChildView(base::WrapUnique(virtual_child_5));
+ ASSERT_EQ(3, virtual_label_->GetChildCount());
+ ASSERT_EQ(3, virtual_child_1->GetChildCount());
+ ASSERT_EQ(2, virtual_child_2->GetChildCount());
+ ASSERT_EQ(0, virtual_child_5->GetChildCount());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
+ EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_5->GetParent());
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ virtual_label_->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ virtual_label_->ChildAtIndex(1));
+ EXPECT_EQ(virtual_child_5->GetNativeObject(),
+ virtual_label_->ChildAtIndex(2));
+
+ // An ignored root node should not be exposed.
+ virtual_label_->GetCustomData().AddState(ax::mojom::State::kIgnored);
+ ASSERT_EQ(3, GetButtonAccessibility()->GetChildCount());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_1->GetParent());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_2->GetParent());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_3->GetParent());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_4->GetParent());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_5->GetParent());
+ EXPECT_EQ(virtual_child_3->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(0));
+ EXPECT_EQ(virtual_child_4->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(1));
+ EXPECT_EQ(virtual_child_5->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(2));
+
+ // Test for mixed ignored and unignored root nodes.
+ AXVirtualView* virtual_label_2 = new AXVirtualView;
+ virtual_label_2->GetCustomData().role = ax::mojom::Role::kStaticText;
+ virtual_label_2->GetCustomData().SetName("Label");
+ button_->GetViewAccessibility().AddVirtualChildView(
+ base::WrapUnique(virtual_label_2));
+ ASSERT_EQ(4, GetButtonAccessibility()->GetChildCount());
+ ASSERT_EQ(0, virtual_label_2->GetChildCount());
+ EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_2->GetParent());
+ EXPECT_EQ(virtual_label_2->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(3));
+
+ // A focusable node should not be ignored.
+ virtual_child_1->GetCustomData().AddState(ax::mojom::State::kFocusable);
+ ASSERT_EQ(2, GetButtonAccessibility()->GetChildCount());
+ ASSERT_EQ(1, virtual_label_->GetChildCount());
+ EXPECT_EQ(virtual_child_1->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(0));
+ EXPECT_EQ(virtual_label_2->GetNativeObject(),
+ GetButtonAccessibility()->ChildAtIndex(1));
+}
+
TEST_F(AXVirtualViewTest, OverrideFocus) {
ViewAccessibility& button_accessibility = button_->GetViewAccessibility();
ASSERT_NE(nullptr, button_accessibility.GetNativeObject());
ASSERT_NE(nullptr, virtual_label_->GetNativeObject());
+ ExpectReceivedAccessibilityEvents({});
EXPECT_EQ(button_accessibility.GetNativeObject(),
button_accessibility.GetFocusedDescendant());
button_accessibility.OverrideFocus(virtual_label_);
EXPECT_EQ(virtual_label_->GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(virtual_label_, ax::mojom::Event::kFocus)});
+
button_accessibility.OverrideFocus(nullptr);
EXPECT_EQ(button_accessibility.GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus)});
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());
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
ASSERT_EQ(2, virtual_label_->GetChildCount());
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
button_accessibility.OverrideFocus(virtual_child_1);
EXPECT_EQ(virtual_child_1->GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(virtual_child_1, ax::mojom::Event::kFocus)});
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
ASSERT_EQ(1, virtual_child_2->GetChildCount());
+ ExpectReceivedAccessibilityEvents({std::make_pair(
+ GetButtonAccessibility(), ax::mojom::Event::kChildrenChanged)});
EXPECT_EQ(virtual_child_1->GetNativeObject(),
button_accessibility.GetFocusedDescendant());
button_accessibility.OverrideFocus(virtual_child_3);
EXPECT_EQ(virtual_child_3->GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(virtual_child_3, ax::mojom::Event::kFocus)});
+
+ // Test that calling GetFocus() while the owner view is not focused will
+ // return nullptr.
+ EXPECT_EQ(nullptr, virtual_label_->GetFocus());
+ EXPECT_EQ(nullptr, virtual_child_1->GetFocus());
+ EXPECT_EQ(nullptr, virtual_child_2->GetFocus());
+ EXPECT_EQ(nullptr, virtual_child_3->GetFocus());
+
+ button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
+ button_->RequestFocus();
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus),
+ std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged)});
// Test that calling GetFocus() from any object in the tree will return the
// same result.
@@ -331,6 +557,11 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
virtual_label_->RemoveChildView(virtual_child_2);
ASSERT_EQ(1, virtual_label_->GetChildCount());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus),
+ std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged)});
+
EXPECT_EQ(button_accessibility.GetNativeObject(),
button_accessibility.GetFocusedDescendant());
EXPECT_EQ(button_accessibility.GetNativeObject(), virtual_label_->GetFocus());
@@ -340,10 +571,17 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
button_accessibility.OverrideFocus(virtual_child_1);
EXPECT_EQ(virtual_child_1->GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(virtual_child_1, ax::mojom::Event::kFocus)});
+
virtual_label_->RemoveAllChildViews();
ASSERT_EQ(0, virtual_label_->GetChildCount());
EXPECT_EQ(button_accessibility.GetNativeObject(),
button_accessibility.GetFocusedDescendant());
+ ExpectReceivedAccessibilityEvents(
+ {std::make_pair(GetButtonAccessibility(), ax::mojom::Event::kFocus),
+ std::make_pair(GetButtonAccessibility(),
+ ax::mojom::Event::kChildrenChanged)});
}
TEST_F(AXVirtualViewTest, Navigation) {
diff --git a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
index 59e5c5c1b67..e5671607a61 100644
--- a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <vector>
+
#include "base/scoped_observer.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
index 1941b4050fd..8a6a8c6a83d 100644
--- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
+++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc
@@ -6,6 +6,9 @@
#include <stddef.h>
+#include <string>
+#include <vector>
+
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
@@ -36,23 +39,28 @@ Widget* GetWidgetForWindow(aura::Window* window) {
return widget;
}
-// Fires location change events on a window, taking into account its
-// associated widget, that widget's root view, and descendant windows.
-void FireLocationChangesRecursively(aura::Window* window,
- AXAuraObjCache* cache) {
- cache->FireEvent(cache->GetOrCreate(window),
- ax::mojom::Event::kLocationChanged);
-
+// Fires |event| on the |window|, and the Widget and RootView associated with
+// |window|.
+void FireEventOnWindowChildWidgetAndRootView(aura::Window* window,
+ ax::mojom::Event event,
+ AXAuraObjCache* cache) {
+ cache->FireEvent(cache->GetOrCreate(window), event);
Widget* widget = GetWidgetForWindow(window);
if (widget) {
- cache->FireEvent(cache->GetOrCreate(widget),
- ax::mojom::Event::kLocationChanged);
+ cache->FireEvent(cache->GetOrCreate(widget), event);
views::View* root_view = widget->GetRootView();
if (root_view)
- root_view->NotifyAccessibilityEvent(ax::mojom::Event::kLocationChanged,
- true);
+ root_view->NotifyAccessibilityEvent(event, true);
}
+}
+
+// Fires location change events on a window, taking into account its
+// associated widget, that widget's root view, and descendant windows.
+void FireLocationChangesRecursively(aura::Window* window,
+ AXAuraObjCache* cache) {
+ FireEventOnWindowChildWidgetAndRootView(
+ window, ax::mojom::Event::kLocationChanged, cache);
for (auto* child : window->children())
FireLocationChangesRecursively(child, cache);
@@ -186,7 +194,8 @@ void AXWindowObjWrapper::OnWindowTransformed(aura::Window* window,
}
void AXWindowObjWrapper::OnWindowTitleChanged(aura::Window* window) {
- FireEvent(ax::mojom::Event::kTextChanged);
+ FireEventOnWindowChildWidgetAndRootView(
+ window_, ax::mojom::Event::kTreeChanged, aura_obj_cache_);
}
void AXWindowObjWrapper::FireEvent(ax::mojom::Event event_type) {
diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
index b5e44c4751c..1877e2ebc86 100644
--- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
+++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <vector>
+
#include "base/scoped_observer.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/accessibility/platform/ax_unique_id.h"
diff --git a/chromium/ui/views/accessibility/view_accessibility.cc b/chromium/ui/views/accessibility/view_accessibility.cc
index 05a1b31b10d..4ff677b664e 100644
--- a/chromium/ui/views/accessibility/view_accessibility.cc
+++ b/chromium/ui/views/accessibility/view_accessibility.cc
@@ -7,10 +7,12 @@
#include <algorithm>
#include <utility>
+#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/buildflags.h"
#include "ui/views/view.h"
#include "ui/views/widget/root_view.h"
@@ -83,7 +85,7 @@ std::unique_ptr<AXVirtualView> ViewAccessibility::RemoveVirtualChildView(
child->set_parent_view(nullptr);
child->UnsetPopulateDataCallback();
if (focused_virtual_child_ && child->Contains(focused_virtual_child_))
- focused_virtual_child_ = nullptr;
+ OverrideFocus(nullptr);
return child;
}
@@ -221,6 +223,12 @@ void ViewAccessibility::OverrideFocus(AXVirtualView* virtual_view) {
DCHECK(!virtual_view || Contains(virtual_view))
<< "|virtual_view| must be nullptr or a descendant of this view.";
focused_virtual_child_ = virtual_view;
+
+ if (focused_virtual_child_) {
+ focused_virtual_child_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus);
+ } else {
+ view_->NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
+ }
}
void ViewAccessibility::OverrideRole(const ax::mojom::Role role) {
@@ -288,10 +296,17 @@ Widget* ViewAccessibility::GetPreviousFocus() {
return previous_focus_;
}
-gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() {
+gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() const {
return nullptr;
}
+void ViewAccessibility::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
+ // On certain platforms, e.g. Chrome OS, we don't create any
+ // AXPlatformDelegates, so the base method in this file would be called.
+ if (accessibility_events_callback_)
+ accessibility_events_callback_.Run(nullptr, event_type);
+}
+
void ViewAccessibility::AnnounceText(const base::string16& text) {
Widget* const widget = view_->GetWidget();
if (!widget)
@@ -309,4 +324,18 @@ gfx::NativeViewAccessible ViewAccessibility::GetFocusedDescendant() {
return view_->GetNativeViewAccessible();
}
+void ViewAccessibility::FireFocusAfterMenuClose() {
+ NotifyAccessibilityEvent(ax::mojom::Event::kFocusAfterMenuClose);
+}
+
+const ViewAccessibility::AccessibilityEventsCallback&
+ViewAccessibility::accessibility_events_callback() const {
+ return accessibility_events_callback_;
+}
+
+void ViewAccessibility::set_accessibility_events_callback(
+ ViewAccessibility::AccessibilityEventsCallback callback) {
+ accessibility_events_callback_ = std::move(callback);
+}
+
} // namespace views
diff --git a/chromium/ui/views/accessibility/view_accessibility.h b/chromium/ui/views/accessibility/view_accessibility.h
index 4f90ef859b0..50fdcfef62b 100644
--- a/chromium/ui/views/accessibility/view_accessibility.h
+++ b/chromium/ui/views/accessibility/view_accessibility.h
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
+#include "base/callback_forward.h"
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
@@ -18,6 +19,12 @@
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/views_export.h"
+namespace ui {
+
+class AXPlatformNodeDelegate;
+
+} // namespace ui
+
namespace views {
class View;
@@ -34,6 +41,9 @@ class Widget;
// that implements the native accessibility APIs on a specific platform.
class VIEWS_EXPORT ViewAccessibility {
public:
+ using AccessibilityEventsCallback =
+ base::RepeatingCallback<void(const ui::AXPlatformNodeDelegate*,
+ const ax::mojom::Event)>;
using AXVirtualViews = AXVirtualView::AXVirtualViews;
static std::unique_ptr<ViewAccessibility> Create(View* view);
@@ -87,8 +97,8 @@ class VIEWS_EXPORT ViewAccessibility {
Widget* GetNextFocus();
Widget* GetPreviousFocus();
- virtual gfx::NativeViewAccessible GetNativeObject();
- virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type) {}
+ virtual gfx::NativeViewAccessible GetNativeObject() const;
+ virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type);
// Causes the screen reader to announce |text|. If the current user is not
// using a screen reader, has no effect.
@@ -134,9 +144,18 @@ class VIEWS_EXPORT ViewAccessibility {
// native accessibility object associated with this view.
gfx::NativeViewAccessible GetFocusedDescendant();
+ virtual void FireFocusAfterMenuClose();
+
+ // Used for testing. Allows a test to watch accessibility events.
+ const AccessibilityEventsCallback& accessibility_events_callback() const;
+ void set_accessibility_events_callback(AccessibilityEventsCallback callback);
+
protected:
explicit ViewAccessibility(View* view);
+ // Used for testing. Called every time an accessibility event is fired.
+ AccessibilityEventsCallback accessibility_events_callback_;
+
private:
// Weak. Owns this.
View* const view_;
diff --git a/chromium/ui/views/accessibility/view_accessibility_utils.cc b/chromium/ui/views/accessibility/view_accessibility_utils.cc
index 31df7b53bdb..a3e5dc60285 100644
--- a/chromium/ui/views/accessibility/view_accessibility_utils.cc
+++ b/chromium/ui/views/accessibility/view_accessibility_utils.cc
@@ -4,6 +4,8 @@
#include "ui/views/accessibility/view_accessibility_utils.h"
+#include <set>
+
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
index 550c3ec8a29..748c8569207 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc
@@ -6,6 +6,9 @@
#include <map>
#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
#include "base/bind.h"
#include "base/lazy_instance.h"
@@ -140,7 +143,7 @@ ViewAXPlatformNodeDelegate::~ViewAXPlatformNodeDelegate() {
ax_platform_node_->Destroy();
}
-gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() {
+gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() const {
DCHECK(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
@@ -148,6 +151,8 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() {
void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
+ if (accessibility_events_callback_)
+ accessibility_events_callback_.Run(this, event_type);
if (g_is_queueing_events) {
g_event_queue.Get().emplace_back(event_type, GetUniqueId());
return;
@@ -217,6 +222,29 @@ void ViewAXPlatformNodeDelegate::OnMenuEnd() {
ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
}
+void ViewAXPlatformNodeDelegate::FireFocusAfterMenuClose() {
+ ui::AXPlatformNodeBase* focused_node =
+ static_cast<ui::AXPlatformNodeBase*>(ax_platform_node_);
+ // Continue to drill down focused nodes to get to the "deepest" node that is
+ // focused, this is not necessarily a view. (It could be web content.)
+ while (focused_node) {
+ ui::AXPlatformNodeBase* deeper_focus = static_cast<ui::AXPlatformNodeBase*>(
+ ui::AXPlatformNode::FromNativeViewAccessible(focused_node->GetFocus()));
+ if (!deeper_focus || deeper_focus == focused_node)
+ break;
+ focused_node = deeper_focus;
+ }
+ if (focused_node) {
+ // callback used for testing
+ if (accessibility_events_callback_)
+ accessibility_events_callback_.Run(
+ this, ax::mojom::Event::kFocusAfterMenuClose);
+
+ focused_node->NotifyAccessibilityEvent(
+ ax::mojom::Event::kFocusAfterMenuClose);
+ }
+}
+
// ui::AXPlatformNodeDelegate
const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const {
@@ -247,12 +275,21 @@ const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const {
return data_;
}
-int ViewAXPlatformNodeDelegate::GetChildCount() {
+int ViewAXPlatformNodeDelegate::GetChildCount() const {
if (IsLeaf())
return 0;
- if (!virtual_children().empty())
- return int{virtual_children().size()};
+ if (!virtual_children().empty()) {
+ int count = 0;
+ for (const std::unique_ptr<AXVirtualView>& child : virtual_children()) {
+ if (child->IsIgnored()) {
+ count += child->GetChildCount();
+ continue;
+ }
+ count++;
+ }
+ return count;
+ }
const auto child_widgets_result = GetChildWidgets();
if (child_widgets_result.is_tab_modal_showing) {
@@ -271,8 +308,24 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(int index) {
return nullptr;
size_t child_index = size_t{index};
- if (!virtual_children().empty())
- return virtual_children()[child_index]->GetNativeObject();
+ if (!virtual_children().empty()) {
+ int i = 0;
+ for (const std::unique_ptr<AXVirtualView>& child : virtual_children()) {
+ if (child->IsIgnored()) {
+ if (index - i < child->GetChildCount()) {
+ gfx::NativeViewAccessible result = child->ChildAtIndex(index - i);
+ if (result)
+ return result;
+ }
+ i += child->GetChildCount();
+ continue;
+ }
+ if (i == index)
+ return child->GetNativeObject();
+ i++;
+ }
+ return nullptr;
+ }
// If this is a root view, our widget might have child widgets. Include
const auto child_widgets_result = GetChildWidgets();
@@ -323,9 +376,10 @@ gfx::Rect ViewAXPlatformNodeDelegate::GetBoundsRect(
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
- case ui::AXCoordinateSystem::kScreen:
+ case ui::AXCoordinateSystem::kScreenDIPs:
// We could optionally add clipping here if ever needed.
return view()->GetBoundsInScreen();
+ case ui::AXCoordinateSystem::kScreenPhysicalPixels:
case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
@@ -333,8 +387,9 @@ gfx::Rect ViewAXPlatformNodeDelegate::GetBoundsRect(
}
}
-gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(int x,
- int y) {
+gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
+ int screen_physical_pixel_x,
+ int screen_physical_pixel_y) const {
if (!view() || !view()->GetWidget())
return nullptr;
@@ -347,19 +402,19 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(int x,
scale_factor = ui::GetScaleFactorForNativeView(native_view);
scale_factor = scale_factor <= 0 ? 1.0 : scale_factor;
}
- x /= scale_factor;
- y /= scale_factor;
+ int screen_dips_x = screen_physical_pixel_x / scale_factor;
+ int screen_dips_y = screen_physical_pixel_y / scale_factor;
// Search child widgets first, since they're on top in the z-order.
for (Widget* child_widget : GetChildWidgets().child_widgets) {
View* child_root_view = child_widget->GetRootView();
- gfx::Point point(x, y);
+ gfx::Point point(screen_dips_x, screen_dips_y);
View::ConvertPointFromScreen(child_root_view, &point);
if (child_root_view->HitTestPoint(point))
return child_root_view->GetNativeViewAccessible();
}
- gfx::Point point(x, y);
+ gfx::Point point(screen_dips_x, screen_dips_y);
View::ConvertPointFromScreen(view(), &point);
if (!view()->HitTestPoint(point))
return nullptr;
@@ -420,7 +475,7 @@ bool ViewAXPlatformNodeDelegate::ShouldIgnoreHoveredStateForTesting() {
}
bool ViewAXPlatformNodeDelegate::IsOffscreen() const {
- // TODO: need to implement.
+ // TODO(katydek): need to implement.
return false;
}
@@ -517,13 +572,13 @@ void ViewAXPlatformNodeDelegate::GetViewsInGroupForSet(
ViewAccessibility& view_accessibility =
view->GetViewAccessibility();
bool is_ignored = view_accessibility.IsIgnored();
- // TODO Remove the ViewAXPlatformNodeDelegate::GetData() part of
- // this lambda, once the temporary code in GetData() setting the
- // role to kIgnored is moved to ViewAccessibility.
+ // TODO(dmazzoni): Remove the remainder of this lambda once the
+ // temporary code in GetData() setting the role to kIgnored is moved
+ // to ViewAccessibility.
ViewAXPlatformNodeDelegate* ax_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
if (ax_delegate)
- is_ignored = is_ignored || ax_delegate->GetData().IsIgnored();
+ is_ignored = is_ignored || ax_delegate->IsIgnored();
return is_ignored;
}),
views_in_group->end());
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
index 4566df7c448..433ef8986f7 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h
@@ -7,6 +7,8 @@
#include <stdint.h>
+#include <vector>
+
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
@@ -41,15 +43,20 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
~ViewAXPlatformNodeDelegate() override;
// ViewAccessibility:
- gfx::NativeViewAccessible GetNativeObject() override;
+ gfx::NativeViewAccessible GetNativeObject() const override;
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override;
#if defined(OS_MACOSX)
void AnnounceText(const base::string16& text) override;
#endif
+ void FireFocusAfterMenuClose() override;
// ui::AXPlatformNodeDelegate
+ // Note that, for parents of virtual views, GetChildCount() and ChildAtIndex()
+ // present to assistive technologies the unignored accessibility subtree,
+ // which doesn't necessarily reflect the internal descendant tree. (An ignored
+ // node means that the node should not be exposed to the platform.)
const ui::AXNodeData& GetData() const override;
- int GetChildCount() override;
+ int GetChildCount() const override;
gfx::NativeViewAccessible ChildAtIndex(int index) override;
gfx::NativeViewAccessible GetNSWindow() override;
gfx::NativeViewAccessible GetNativeViewAccessible() override;
@@ -58,7 +65,9 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const override;
- gfx::NativeViewAccessible HitTestSync(int x, int y) override;
+ gfx::NativeViewAccessible HitTestSync(
+ int screen_physical_pixel_x,
+ int screen_physical_pixel_y) const override;
gfx::NativeViewAccessible GetFocus() override;
ui::AXPlatformNode* GetFromNodeID(int32_t id) override;
ui::AXPlatformNode* GetFromTreeIDAndNodeID(const ui::AXTreeID& ax_tree_id,
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc
index b16fd4b10d4..5e5df13782e 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc
@@ -9,6 +9,7 @@
#include <vector>
#include "base/memory/singleton.h"
+#include "base/scoped_observer.h"
#include "base/stl_util.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
@@ -86,7 +87,7 @@ class AuraLinuxApplication : public ui::AXPlatformNodeDelegateBase,
return;
widgets_.push_back(widget);
- widget->AddObserver(this);
+ observer_.Add(widget);
aura::Window* window = widget->GetNativeWindow();
if (!window)
@@ -103,6 +104,7 @@ class AuraLinuxApplication : public ui::AXPlatformNodeDelegateBase,
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override {
+ observer_.Remove(widget);
auto iter = std::find(widgets_.begin(), widgets_.end(), widget);
if (iter != widgets_.end())
widgets_.erase(iter);
@@ -126,7 +128,9 @@ class AuraLinuxApplication : public ui::AXPlatformNodeDelegateBase,
const ui::AXNodeData& GetData() const override { return data_; }
- int GetChildCount() override { return static_cast<int>(widgets_.size()); }
+ int GetChildCount() const override {
+ return static_cast<int>(widgets_.size());
+ }
gfx::NativeViewAccessible ChildAtIndex(int index) override {
if (index < 0 || index >= GetChildCount())
@@ -159,6 +163,7 @@ class AuraLinuxApplication : public ui::AXPlatformNodeDelegateBase,
ui::AXNodeData data_;
ui::AXUniqueId unique_id_;
std::vector<Widget*> widgets_;
+ ScopedObserver<views::Widget, views::WidgetObserver> observer_{this};
};
} // namespace
@@ -175,9 +180,6 @@ ViewAXPlatformNodeDelegateAuraLinux::ViewAXPlatformNodeDelegateAuraLinux(
view->AddObserver(this);
}
-ViewAXPlatformNodeDelegateAuraLinux::~ViewAXPlatformNodeDelegateAuraLinux() =
- default;
-
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateAuraLinux::GetParent() {
if (gfx::NativeViewAccessible parent =
ViewAXPlatformNodeDelegate::GetParent())
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h
index 72052ec2047..cc5cbf179ed 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h
@@ -20,7 +20,6 @@ class ViewAXPlatformNodeDelegateAuraLinux : public ViewAXPlatformNodeDelegate,
const ViewAXPlatformNodeDelegateAuraLinux&) = delete;
ViewAXPlatformNodeDelegateAuraLinux& operator=(
const ViewAXPlatformNodeDelegateAuraLinux&) = delete;
- ~ViewAXPlatformNodeDelegateAuraLinux() override;
// |ViewAXPlatformNodeDelegate| overrides:
gfx::NativeViewAccessible GetParent() override;
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
index 4fa37a9423f..f9c0de73d1b 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
@@ -6,6 +6,7 @@
#include <atk/atk.h>
+#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h"
@@ -16,6 +17,10 @@ class ViewAXPlatformNodeDelegateAuraLinuxTest : public ViewsTestBase {
public:
ViewAXPlatformNodeDelegateAuraLinuxTest() = default;
~ViewAXPlatformNodeDelegateAuraLinuxTest() override = default;
+ void SetUp() override {
+ ViewsTestBase::SetUp();
+ ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
+ }
};
TEST_F(ViewAXPlatformNodeDelegateAuraLinuxTest, TextfieldAccessibility) {
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
index 92884462848..cf4bc178a1d 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc
@@ -4,10 +4,16 @@
#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
+#include <memory>
+#include <utility>
+#include <vector>
+
#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/platform/ax_platform_node.h"
+#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
@@ -47,6 +53,7 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
void SetUp() override {
ViewsTestBase::SetUp();
+ ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
widget_ = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
@@ -95,30 +102,21 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
// Sets up a more complicated structure of Views - one parent View with four
// child Views.
- std::vector<View*> SetUpExtraViews() {
- View* parent_view = new View();
- widget_->GetContentsView()->AddChildView(parent_view);
- std::vector<View*> views{parent_view};
-
- const int num_children = 4;
- for (int i = 0; i < num_children; i++) {
- View* child_view = new View();
- parent_view->AddChildView(child_view);
- views.push_back(child_view);
- }
+ View::Views SetUpExtraViews() {
+ View* parent_view =
+ widget_->GetContentsView()->AddChildView(std::make_unique<View>());
+ View::Views views{parent_view};
+ for (int i = 0; i < 4; i++)
+ views.push_back(parent_view->AddChildView(std::make_unique<View>()));
return views;
}
- // Adds group id information to the first 5 values in |views|. If |views| is
- // empty, populates it with one parent View and four child Views. It is
- // assumed |views| is either empty or has at least 5 items.
- void SetUpExtraViewsWithGroups(std::vector<View*>& views) {
+ // Adds group id information to the first 5 values in |views|.
+ void SetUpExtraViewsGroups(const View::Views& views) {
// v[0] g1
// | | | |
// v[1] g1 v[2] g1 v[3] g2 v[4]
- if (views.empty())
- views = SetUpExtraViews();
- EXPECT_GE(views.size(), (size_t)5);
+ ASSERT_GE(views.size(), 5u);
views[0]->SetGroup(1);
views[1]->SetGroup(1);
@@ -127,16 +125,12 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
// Skip views[4] - no group id.
}
- // Adds posInSet and setSize overrides to the first 5 values in |views|. If
- // |views| is empty, populates it with one parent View and four child Views.
- // It is assumed |views| is either empty or has at least 5 items.
- void SetUpExtraViewsWithSetOverrides(std::vector<View*>& views) {
+ // Adds posInSet and setSize overrides to the first 5 values in |views|.
+ void SetUpExtraViewsSetOverrides(const View::Views& views) {
// v[0] p4 s4
// | | | |
// v[1] p3 s4 v[2] p2 s4 v[3] p- s- v[4] p1 s4
- if (views.empty())
- views = SetUpExtraViews();
- EXPECT_GE(views.size(), (size_t)5);
+ ASSERT_GE(views.size(), 5u);
views[0]->GetViewAccessibility().OverridePosInSet(4, 4);
views[1]->GetViewAccessibility().OverridePosInSet(3, 4);
@@ -243,8 +237,8 @@ TEST_F(ViewAXPlatformNodeDelegateTest, GetAuthorUniqueIdNonDefault) {
}
TEST_F(ViewAXPlatformNodeDelegateTest, IsOrderedSet) {
- std::vector<View*> group_ids;
- SetUpExtraViewsWithGroups(group_ids);
+ View::Views group_ids = SetUpExtraViews();
+ SetUpExtraViewsGroups(group_ids);
// Only last element has no group id.
EXPECT_TRUE(view_accessibility(group_ids[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[1])->IsOrderedSet());
@@ -258,8 +252,8 @@ TEST_F(ViewAXPlatformNodeDelegateTest, IsOrderedSet) {
EXPECT_TRUE(view_accessibility(group_ids[3])->IsOrderedSetItem());
EXPECT_FALSE(view_accessibility(group_ids[4])->IsOrderedSetItem());
- std::vector<View*> overrides;
- SetUpExtraViewsWithSetOverrides(overrides);
+ View::Views overrides = SetUpExtraViews();
+ SetUpExtraViewsSetOverrides(overrides);
// Only overrides[3] has no override values for setSize/ posInSet.
EXPECT_TRUE(view_accessibility(overrides[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[1])->IsOrderedSet());
@@ -276,8 +270,8 @@ TEST_F(ViewAXPlatformNodeDelegateTest, IsOrderedSet) {
TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
// Test Views with group ids.
- std::vector<View*> group_ids;
- SetUpExtraViewsWithGroups(group_ids);
+ View::Views group_ids = SetUpExtraViews();
+ SetUpExtraViewsGroups(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 1);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 3);
@@ -304,8 +298,8 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
group_ids[2]->GetViewAccessibility().OverrideIsIgnored(false);
// Test Views with setSize/ posInSet override values set.
- std::vector<View*> overrides;
- SetUpExtraViewsWithSetOverrides(overrides);
+ View::Views overrides = SetUpExtraViews();
+ SetUpExtraViewsSetOverrides(overrides);
EXPECT_EQ(view_accessibility(overrides[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(overrides[1])->GetSetSize(), 4);
@@ -322,7 +316,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
// Test Views with both group ids and setSize/ posInSet override values set.
// Make sure the override values take precedence when both are set.
// Add setSize/ posInSet overrides to the Views with group ids.
- SetUpExtraViewsWithSetOverrides(group_ids);
+ SetUpExtraViewsSetOverrides(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 4);
@@ -338,7 +332,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
}
TEST_F(ViewAXPlatformNodeDelegateTest, Navigation) {
- std::vector<View*> view_ids = SetUpExtraViews();
+ View::Views view_ids = SetUpExtraViews();
EXPECT_EQ(view_accessibility(view_ids[0])->GetNextSibling(), nullptr);
EXPECT_EQ(view_accessibility(view_ids[0])->GetPreviousSibling(),
@@ -369,7 +363,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, Navigation) {
}
TEST_F(ViewAXPlatformNodeDelegateTest, OverrideHasPopup) {
- std::vector<View*> view_ids = SetUpExtraViews();
+ View::Views view_ids = SetUpExtraViews();
view_ids[1]->GetViewAccessibility().OverrideHasPopup(
ax::mojom::HasPopup::kTrue);
@@ -389,6 +383,26 @@ TEST_F(ViewAXPlatformNodeDelegateTest, OverrideHasPopup) {
EXPECT_EQ(node_data_2.GetHasPopup(), ax::mojom::HasPopup::kMenu);
}
+TEST_F(ViewAXPlatformNodeDelegateTest, FocusOnMenuClose) {
+ // Set Focus on the button
+ button_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
+ EXPECT_EQ(nullptr, button_->GetFocusManager()->GetFocusedView());
+ EXPECT_EQ(nullptr, button_accessibility()->GetFocus());
+
+ EXPECT_TRUE(SetFocused(button_accessibility(), true));
+ EXPECT_EQ(button_->GetNativeViewAccessible(),
+ button_accessibility()->GetFocus());
+
+ // Fire FocusAfterMenuClose event on the button.
+ base::RunLoop run_loop;
+ ui::AXPlatformNodeBase::SetOnNotifyEventCallbackForTesting(
+ ax::mojom::Event::kFocusAfterMenuClose, run_loop.QuitClosure());
+ button_accessibility()->FireFocusAfterMenuClose();
+ run_loop.Run();
+ EXPECT_EQ(button_->GetNativeViewAccessible(),
+ button_accessibility()->GetFocus());
+}
+
#if defined(USE_AURA)
class DerivedTestView : public View {
public:
@@ -425,8 +439,8 @@ using ViewAccessibilityTest = ViewsTestBase;
// Check if the destruction of the widget ends successfully if |view|'s
// visibility changed during destruction.
TEST_F(ViewAccessibilityTest, LayoutCalledInvalidateRootView) {
- // TODO: Construct a real AutomationManagerAura rather than using this
- // observer to simulate it.
+ // TODO(jamescook): Construct a real AutomationManagerAura rather than using
+ // this observer to simulate it.
AXAuraObjCache cache;
TestAXEventObserver observer(&cache);
std::unique_ptr<Widget> widget(new Widget);
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc
index 77b16fcd2c7..041d532c58b 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc
@@ -93,10 +93,12 @@ gfx::Rect ViewAXPlatformNodeDelegateWin::GetBoundsRect(
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
- case ui::AXCoordinateSystem::kScreen:
- // We could optionally add clipping here if ever needed.
+ case ui::AXCoordinateSystem::kScreenPhysicalPixels:
return display::win::ScreenWin::DIPToScreenRect(
HWNDForView(view()), view()->GetBoundsInScreen());
+ case ui::AXCoordinateSystem::kScreenDIPs:
+ // We could optionally add clipping here if ever needed.
+ return view()->GetBoundsInScreen();
case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
index 162bf5722de..8061724c841 100644
--- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
+++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc
@@ -7,6 +7,8 @@
#include <oleacc.h>
#include <wrl/client.h>
+#include <utility>
+
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"
#include "third_party/iaccessible2/ia2_api_all.h"
@@ -15,9 +17,9 @@
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h"
-using Microsoft::WRL::ComPtr;
using base::win::ScopedBstr;
using base::win::ScopedVariant;
+using Microsoft::WRL::ComPtr;
namespace views {
namespace test {
@@ -88,15 +90,16 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAccessibility) {
ScopedVariant childid_self(CHILDID_SELF);
ASSERT_EQ(S_OK,
textfield_accessible->get_accName(childid_self, name.Receive()));
- EXPECT_STREQ(L"Name", static_cast<BSTR>(name));
+ EXPECT_STREQ(L"Name", name.Get());
ScopedBstr value;
ASSERT_EQ(S_OK,
textfield_accessible->get_accValue(childid_self, value.Receive()));
- EXPECT_STREQ(L"Value", static_cast<BSTR>(value));
+ EXPECT_STREQ(L"Value", value.Get());
ScopedBstr new_value(L"New value");
- ASSERT_EQ(S_OK, textfield_accessible->put_accValue(childid_self, new_value));
+ ASSERT_EQ(S_OK,
+ textfield_accessible->put_accValue(childid_self, new_value.Get()));
EXPECT_STREQ(L"New value", textfield->GetText().c_str());
}
@@ -131,15 +134,15 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAssociatedLabel) {
ScopedVariant childid_self(CHILDID_SELF);
ASSERT_EQ(S_OK,
textfield_accessible->get_accName(childid_self, name.Receive()));
- ASSERT_STREQ(L"Label", name);
+ ASSERT_STREQ(L"Label", name.Get());
ComPtr<IAccessible2_2> textfield_ia2;
EXPECT_EQ(S_OK, textfield_accessible.As(&textfield_ia2));
ScopedBstr type(IA2_RELATION_LABELLED_BY);
IUnknown** targets;
LONG n_targets;
- EXPECT_EQ(S_OK, textfield_ia2->get_relationTargetsOfType(type, 0, &targets,
- &n_targets));
+ EXPECT_EQ(S_OK, textfield_ia2->get_relationTargetsOfType(
+ type.Get(), 0, &targets, &n_targets));
ASSERT_EQ(1, n_targets);
ComPtr<IUnknown> label_unknown(targets[0]);
ComPtr<IAccessible> label_accessible;
@@ -277,9 +280,9 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
// Initially, there are no alerts
ScopedBstr alerts_bstr(L"alerts");
IUnknown** targets;
- long n_targets;
+ LONG n_targets;
ASSERT_EQ(S_FALSE, root_view_accessible->get_relationTargetsOfType(
- alerts_bstr, 0, &targets, &n_targets));
+ alerts_bstr.Get(), 0, &targets, &n_targets));
ASSERT_EQ(0, n_targets);
// Fire alert events on the infobars.
@@ -288,7 +291,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
// Now calling get_relationTargetsOfType should retrieve the alerts.
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
- alerts_bstr, 0, &targets, &n_targets));
+ alerts_bstr.Get(), 0, &targets, &n_targets));
ASSERT_EQ(2, n_targets);
ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0]));
ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[1]));
@@ -296,7 +299,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
// If we set max_targets to 1, we should only get the first one.
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
- alerts_bstr, 1, &targets, &n_targets));
+ alerts_bstr.Get(), 1, &targets, &n_targets));
ASSERT_EQ(1, n_targets);
ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0]));
CoTaskMemFree(targets);
@@ -304,7 +307,7 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) {
// If we delete the first view, we should only get the second one now.
delete infobar;
ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType(
- alerts_bstr, 0, &targets, &n_targets));
+ alerts_bstr.Get(), 0, &targets, &n_targets));
ASSERT_EQ(1, n_targets);
ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[0]));
CoTaskMemFree(targets);
@@ -379,13 +382,13 @@ TEST_F(ViewAXPlatformNodeDelegateWinTest, Overrides) {
// Name.
ScopedBstr name;
ASSERT_EQ(S_OK, content_accessible->get_accName(child_index, name.Receive()));
- ASSERT_STREQ(L"Name", name);
+ ASSERT_STREQ(L"Name", name.Get());
// Description.
ScopedBstr description;
ASSERT_EQ(S_OK, content_accessible->get_accDescription(
child_index, description.Receive()));
- ASSERT_STREQ(L"Description", description);
+ ASSERT_STREQ(L"Description", description.Get());
// Get the child accessible.
ComPtr<IDispatch> alert_dispatch;
diff --git a/chromium/ui/views/accessibility/views_ax_tree_manager.cc b/chromium/ui/views/accessibility/views_ax_tree_manager.cc
new file mode 100644
index 00000000000..15954cf453f
--- /dev/null
+++ b/chromium/ui/views/accessibility/views_ax_tree_manager.cc
@@ -0,0 +1,165 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/accessibility/views_ax_tree_manager.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "ui/accessibility/ax_action_data.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_tree_manager_map.h"
+#include "ui/accessibility/ax_tree_source_checker.h"
+#include "ui/accessibility/ax_tree_update.h"
+#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+ViewsAXTreeManager::ViewsAXTreeManager(Widget* widget)
+ : widget_(widget),
+ tree_source_(cache_.GetOrCreate(widget),
+ ui::AXTreeID::CreateNewAXTreeID(),
+ &cache_),
+ tree_serializer_(&tree_source_),
+ event_generator_(&ax_tree_) {
+ DCHECK(widget);
+ ui::AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+ views_event_observer_.Add(AXEventManager::Get());
+ View* root_view = widget->GetRootView();
+ if (root_view)
+ root_view->NotifyAccessibilityEvent(ax::mojom::Event::kLoadComplete, true);
+}
+
+ViewsAXTreeManager::~ViewsAXTreeManager() {
+ event_generator_.ReleaseTree();
+ views_event_observer_.RemoveAll();
+ ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
+}
+
+void ViewsAXTreeManager::SetGeneratedEventCallbackForTesting(
+ const GeneratedEventCallbackForTesting& callback) {
+ generated_event_callback_for_testing_ = callback;
+}
+
+void ViewsAXTreeManager::UnsetGeneratedEventCallbackForTesting() {
+ generated_event_callback_for_testing_.Reset();
+}
+
+ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
+ const ui::AXTreeID tree_id,
+ const ui::AXNode::AXID node_id) const {
+ const ui::AXTreeManager* manager =
+ ui::AXTreeManagerMap::GetInstance().GetManager(tree_id);
+ return manager ? manager->GetNodeFromTree(node_id) : nullptr;
+}
+
+ui::AXNode* ViewsAXTreeManager::GetNodeFromTree(
+ const ui::AXNode::AXID node_id) const {
+ return ax_tree_.GetFromId(node_id);
+}
+
+ui::AXTreeID ViewsAXTreeManager::GetTreeID() const {
+ return ax_tree_.GetAXTreeID();
+}
+
+ui::AXTreeID ViewsAXTreeManager::GetParentTreeID() const {
+ // TODO(nektar): Implement stiching of AXTrees, e.g. a dialog to the main
+ // window.
+ return ui::AXTreeIDUnknown();
+}
+
+ui::AXNode* ViewsAXTreeManager::GetRootAsAXNode() const {
+ return ax_tree_.root();
+}
+
+ui::AXNode* ViewsAXTreeManager::GetParentNodeFromParentTreeAsAXNode() const {
+ // TODO(nektar): Implement stiching of AXTrees, e.g. a dialog to the main
+ // window.
+ return nullptr;
+}
+
+void ViewsAXTreeManager::OnViewEvent(View* view, ax::mojom::Event event) {
+ DCHECK(view);
+ AXAuraObjWrapper* wrapper = cache_.GetOrCreate(view);
+ if (!wrapper)
+ return;
+ modified_nodes_.insert(wrapper->GetUniqueId());
+
+ if (waiting_to_serialize_)
+ return;
+ waiting_to_serialize_ = true;
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ViewsAXTreeManager::SerializeTreeUpdates,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ViewsAXTreeManager::PerformAction(const ui::AXActionData& data) {
+ tree_source_.HandleAccessibleAction(data);
+}
+
+void ViewsAXTreeManager::SerializeTreeUpdates() {
+ // Better to set this flag to false early in case this method, or any method
+ // it calls, causes an event to get fired.
+ waiting_to_serialize_ = false;
+
+ // Make sure the focused node is serialized.
+ AXAuraObjWrapper* focused_wrapper = cache_.GetFocus();
+ if (focused_wrapper)
+ modified_nodes_.insert(focused_wrapper->GetUniqueId());
+
+ std::vector<ui::AXTreeUpdate> updates;
+ for (const ui::AXNode::AXID node_id : modified_nodes_) {
+ AXAuraObjWrapper* wrapper = cache_.Get(node_id);
+ if (!wrapper)
+ continue;
+
+ ui::AXTreeUpdate update;
+ if (!tree_serializer_.SerializeChanges(wrapper, &update)) {
+ std::string error;
+ ui::AXTreeSourceChecker<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>
+ checker(&tree_source_);
+ checker.CheckAndGetErrorString(&error);
+ NOTREACHED() << error << '\n' << update.ToString();
+ return;
+ }
+
+ updates.push_back(update);
+ }
+
+ UnserializeTreeUpdates(updates);
+}
+
+void ViewsAXTreeManager::UnserializeTreeUpdates(
+ const std::vector<ui::AXTreeUpdate>& updates) {
+ for (const ui::AXTreeUpdate& update : updates) {
+ if (!ax_tree_.Unserialize(update)) {
+ NOTREACHED() << ax_tree_.error();
+ return;
+ }
+ }
+
+ // Unserializing the updates into our AXTree should have prompted our
+ // AXEventGenerator to generate events based on the updates.
+ for (const ui::AXEventGenerator::TargetedEvent& targeted_event :
+ event_generator_) {
+ FireGeneratedEvent(targeted_event.event_params.event, *targeted_event.node);
+ }
+ event_generator_.ClearEvents();
+}
+
+void ViewsAXTreeManager::FireGeneratedEvent(
+ const ui::AXEventGenerator::Event& event,
+ const ui::AXNode& node) const {
+ if (!generated_event_callback_for_testing_.is_null())
+ generated_event_callback_for_testing_.Run(widget_, event, node.id());
+ // TODO(nektar): Implement this other than "for testing".
+}
+
+} // namespace views
diff --git a/chromium/ui/views/accessibility/views_ax_tree_manager.h b/chromium/ui/views/accessibility/views_ax_tree_manager.h
new file mode 100644
index 00000000000..5212d3d2a44
--- /dev/null
+++ b/chromium/ui/views/accessibility/views_ax_tree_manager.h
@@ -0,0 +1,152 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_ACCESSIBILITY_VIEWS_AX_TREE_MANAGER_H_
+#define UI_VIEWS_ACCESSIBILITY_VIEWS_AX_TREE_MANAGER_H_
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "ui/accessibility/ax_action_handler.h"
+#include "ui/accessibility/ax_enums.mojom-forward.h"
+#include "ui/accessibility/ax_event_generator.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_manager.h"
+#include "ui/accessibility/ax_tree_serializer.h"
+#include "ui/views/accessibility/ax_aura_obj_cache.h"
+#include "ui/views/accessibility/ax_event_manager.h"
+#include "ui/views/accessibility/ax_event_observer.h"
+#include "ui/views/accessibility/ax_tree_source_views.h"
+#include "ui/views/views_export.h"
+
+namespace ui {
+
+struct AXActionData;
+
+} // namespace ui
+
+namespace views {
+
+class AXAuraObjWrapper;
+class View;
+class Widget;
+
+// Manages an accessibility tree that mirrors the Views tree for a particular
+// widget.
+//
+// TODO(nektar): Enable navigating to parent and child accessibility trees for
+// parent and child widgets, thereby allowing stiching the various accessibility
+// trees into a unified platform tree.
+//
+// The Views tree is serialized into an AXTreeSource and immediately
+// deserialized into an AXTree, both in the same process.
+class VIEWS_EXPORT ViewsAXTreeManager : public ui::AXTreeManager,
+ public ui::AXActionHandler,
+ public AXEventObserver {
+ public:
+ using GeneratedEventCallbackForTesting = base::RepeatingCallback<
+ void(Widget*, ui::AXEventGenerator::Event, ui::AXNode::AXID)>;
+
+ // Creates an instance of this class that manages an AXTree mirroring the
+ // Views tree rooted at a given Widget.
+ //
+ // The provided |widget| doesn't own this class.
+ explicit ViewsAXTreeManager(Widget* widget);
+
+ ~ViewsAXTreeManager() override;
+ ViewsAXTreeManager(const ViewsAXTreeManager& manager) = delete;
+ ViewsAXTreeManager& operator=(const ViewsAXTreeManager& manager) = delete;
+
+ // Returns a reference to the managed AXTree.
+ const ui::AXTree& ax_tree() const { return ax_tree_; }
+
+ // For testing only, register a function to be called when a generated event
+ // is fired from this ViewsAXTreeManager.
+ void SetGeneratedEventCallbackForTesting(
+ const GeneratedEventCallbackForTesting& callback);
+
+ // For testing only, unregister the function that was previously registered to
+ // be called when a generated event is fired from this ViewsAXTreeManager.
+ void UnsetGeneratedEventCallbackForTesting();
+
+ // AXTreeManager implementation.
+ ui::AXNode* GetNodeFromTree(const ui::AXTreeID tree_id,
+ const ui::AXNode::AXID node_id) const override;
+ ui::AXNode* GetNodeFromTree(const ui::AXNode::AXID node_id) const override;
+ ui::AXTreeID GetTreeID() const override;
+ ui::AXTreeID GetParentTreeID() const override;
+ ui::AXNode* GetRootAsAXNode() const override;
+ ui::AXNode* GetParentNodeFromParentTreeAsAXNode() const override;
+
+ // AXActionHandler implementation.
+ void PerformAction(const ui::AXActionData& data) override;
+
+ // AXEventObserver implementation.
+ void OnViewEvent(views::View* view, ax::mojom::Event event) override;
+
+ private:
+ using ViewsAXTreeSerializer =
+ ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>;
+
+ void SerializeTreeUpdates();
+ void UnserializeTreeUpdates(const std::vector<ui::AXTreeUpdate>& updates);
+
+ // Determines the platform node which corresponds to the given |node| and
+ // fires the given |event| on it.
+ //
+ // TODO(nektar): Implement this other than for testing.
+ void FireGeneratedEvent(const ui::AXEventGenerator::Event& event,
+ const ui::AXNode& node) const;
+
+ // The Widget for which this class manages an AXTree.
+ //
+ // Weak, a Widget doesn't own this class.
+ Widget* const widget_;
+
+ // Set to true if we are still waiting for a task to serialize all previously
+ // modified nodes.
+ bool waiting_to_serialize_ = false;
+
+ // The set of nodes in the source tree that might have been modified after an
+ // event has been fired on them.
+ std::set<ui::AXNode::AXID> modified_nodes_;
+
+ // The cache that maps objects in the Views tree to AXAuraObjWrapper objects
+ // that are used to serialize the Views tree.
+ AXAuraObjCache cache_;
+
+ // The tree source that enables us to serialize the Views tree.
+ AXTreeSourceViews tree_source_;
+
+ // The serializer that serializes the Views tree into one or more
+ // AXTreeUpdate.
+ ViewsAXTreeSerializer tree_serializer_;
+
+ // The AXTree that mirrors the Views tree and which is created by
+ // deserializing the updates from |tree_source_|.
+ ui::AXTree ax_tree_;
+
+ // For automatically generating events based on changes to |tree_|.
+ ui::AXEventGenerator event_generator_;
+
+ // For testing only: A function to call when a generated event is fired.
+ GeneratedEventCallbackForTesting generated_event_callback_for_testing_;
+
+ // To prevent any use-after-free, members below this line should be declared
+ // last.
+ ScopedObserver<AXEventManager, AXEventObserver> views_event_observer_{this};
+ base::WeakPtrFactory<ViewsAXTreeManager> weak_factory_{this};
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_ACCESSIBILITY_VIEWS_AX_TREE_MANAGER_H_
diff --git a/chromium/ui/views/accessibility/views_ax_tree_manager_unittest.cc b/chromium/ui/views/accessibility/views_ax_tree_manager_unittest.cc
new file mode 100644
index 00000000000..e5a396d6f8f
--- /dev/null
+++ b/chromium/ui/views/accessibility/views_ax_tree_manager_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/accessibility/views_ax_tree_manager.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_event_generator.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+namespace test {
+
+namespace {
+
+class TestButton : public Button {
+ public:
+ TestButton() : Button(nullptr) {}
+ TestButton(const TestButton&) = delete;
+ TestButton& operator=(const TestButton&) = delete;
+ ~TestButton() override = default;
+};
+
+class ViewsAXTreeManagerTest : public ViewsTestBase {
+ public:
+ ViewsAXTreeManagerTest() = default;
+ ~ViewsAXTreeManagerTest() override = default;
+ ViewsAXTreeManagerTest(const ViewsAXTreeManagerTest&) = delete;
+ ViewsAXTreeManagerTest& operator=(const ViewsAXTreeManagerTest&) = delete;
+
+ protected:
+ void SetUp() override;
+ void TearDown() override;
+ ui::AXNode* FindNode(const ax::mojom::Role role,
+ const std::string& name_or_value) const;
+ void WaitFor(const ui::AXEventGenerator::Event event);
+
+ Widget* widget() const { return widget_; }
+ Button* button() const { return button_; }
+ Label* label() const { return label_; }
+ const ViewsAXTreeManager& manager() const { return *manager_; }
+
+ private:
+ ui::AXNode* FindNodeInSubtree(ui::AXNode* root,
+ const ax::mojom::Role role,
+ const std::string& name_or_value) const;
+ void OnGeneratedEvent(Widget* widget,
+ ui::AXEventGenerator::Event event,
+ ui::AXNode::AXID node_id);
+
+ Widget* widget_ = nullptr;
+ Button* button_ = nullptr;
+ Label* label_ = nullptr;
+ std::unique_ptr<ViewsAXTreeManager> manager_;
+ ui::AXEventGenerator::Event event_to_wait_for_;
+ std::unique_ptr<base::RunLoop> loop_runner_;
+};
+
+void ViewsAXTreeManagerTest::SetUp() {
+ ViewsTestBase::SetUp();
+
+ widget_ = new Widget;
+ Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ widget_->Init(std::move(params));
+
+ button_ = new TestButton();
+ button_->SetSize(gfx::Size(20, 20));
+
+ label_ = new Label();
+ button_->AddChildView(label_);
+
+ widget_->GetContentsView()->AddChildView(button_);
+ widget_->Show();
+
+ manager_ = std::make_unique<ViewsAXTreeManager>(widget_);
+ ASSERT_NE(nullptr, manager_.get());
+ manager_->SetGeneratedEventCallbackForTesting(base::BindRepeating(
+ &ViewsAXTreeManagerTest::OnGeneratedEvent, base::Unretained(this)));
+ WaitFor(ui::AXEventGenerator::Event::LOAD_COMPLETE);
+}
+
+void ViewsAXTreeManagerTest::TearDown() {
+ manager_->UnsetGeneratedEventCallbackForTesting();
+ manager_.reset();
+ if (!widget_->IsClosed())
+ widget_->Close();
+ ViewsTestBase::TearDown();
+}
+
+ui::AXNode* ViewsAXTreeManagerTest::FindNode(
+ const ax::mojom::Role role,
+ const std::string& name_or_value) const {
+ ui::AXNode* root = manager_->GetRootAsAXNode();
+ EXPECT_NE(nullptr, root);
+ return FindNodeInSubtree(root, role, name_or_value);
+}
+
+void ViewsAXTreeManagerTest::WaitFor(const ui::AXEventGenerator::Event event) {
+ ASSERT_FALSE(loop_runner_ && loop_runner_->IsRunningOnCurrentThread())
+ << "Waiting for multiple events is currently not supported.";
+ loop_runner_ = std::make_unique<base::RunLoop>();
+ event_to_wait_for_ = event;
+ loop_runner_->Run();
+}
+
+ui::AXNode* ViewsAXTreeManagerTest::FindNodeInSubtree(
+ ui::AXNode* root,
+ const ax::mojom::Role role,
+ const std::string& name_or_value) const {
+ EXPECT_NE(nullptr, root);
+ const std::string& name =
+ root->GetStringAttribute(ax::mojom::StringAttribute::kName);
+ const std::string& value =
+ root->GetStringAttribute(ax::mojom::StringAttribute::kValue);
+ if (root->data().role == role &&
+ (name == name_or_value || value == name_or_value)) {
+ return root;
+ }
+
+ for (ui::AXNode* child : root->children()) {
+ ui::AXNode* result = FindNodeInSubtree(child, role, name_or_value);
+ if (result)
+ return result;
+ }
+ return nullptr;
+}
+
+void ViewsAXTreeManagerTest::OnGeneratedEvent(Widget* widget,
+ ui::AXEventGenerator::Event event,
+ ui::AXNode::AXID node_id) {
+ ASSERT_NE(nullptr, manager_.get())
+ << "Should not be called after TearDown().";
+ if (loop_runner_ && event == event_to_wait_for_)
+ loop_runner_->Quit();
+}
+
+} // namespace
+
+TEST_F(ViewsAXTreeManagerTest, MirrorInitialTree) {
+ ui::AXNodeData button_data;
+ button()->GetViewAccessibility().GetAccessibleNodeData(&button_data);
+ ui::AXNode* ax_button = FindNode(ax::mojom::Role::kButton, "");
+ ASSERT_NE(nullptr, ax_button);
+ EXPECT_EQ(button_data.role, ax_button->data().role);
+ EXPECT_EQ(
+ button_data.GetStringAttribute(ax::mojom::StringAttribute::kDescription),
+ ax_button->data().GetStringAttribute(
+ ax::mojom::StringAttribute::kDescription));
+ EXPECT_EQ(
+ button_data.GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb),
+ ax_button->data().GetIntAttribute(
+ ax::mojom::IntAttribute::kDefaultActionVerb));
+ EXPECT_TRUE(ax_button->data().HasState(ax::mojom::State::kFocusable));
+}
+
+TEST_F(ViewsAXTreeManagerTest, PerformAction) {
+ ui::AXNode* ax_button = FindNode(ax::mojom::Role::kButton, "");
+ ASSERT_NE(nullptr, ax_button);
+ ASSERT_FALSE(ax_button->data().HasIntAttribute(
+ ax::mojom::IntAttribute::kCheckedState));
+ button()->SetState(TestButton::STATE_PRESSED);
+ button()->NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged,
+ true);
+ WaitFor(ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED);
+}
+
+} // namespace test
+} // namespace views