diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-16 11:45:35 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-17 08:59:23 +0000 |
commit | 552906b0f222c5d5dd11b9fd73829d510980461a (patch) | |
tree | 3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/ui/views/accessibility | |
parent | 1b05827804eaf047779b597718c03e7d38344261 (diff) | |
download | qtwebengine-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')
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 |