1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/views/accessibility/ax_virtual_view.h"
6 
7 #include <stdint.h>
8 
9 #include <algorithm>
10 #include <map>
11 #include <utility>
12 
13 #include "base/callback.h"
14 #include "base/containers/adapters.h"
15 #include "base/no_destructor.h"
16 #include "build/build_config.h"
17 #include "ui/accessibility/ax_action_data.h"
18 #include "ui/accessibility/ax_tree_data.h"
19 #include "ui/accessibility/platform/ax_platform_node.h"
20 #include "ui/base/layout.h"
21 #include "ui/base/ui_base_types.h"
22 #include "ui/gfx/geometry/rect_conversions.h"
23 #include "ui/views/accessibility/view_accessibility.h"
24 #include "ui/views/accessibility/view_ax_platform_node_delegate.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/widget.h"
27 
28 #if OS_WIN
29 #include "ui/views/win/hwnd_util.h"
30 #endif
31 
32 namespace views {
33 
34 // Tracks all virtual ax views.
GetIdMap()35 std::map<int32_t, AXVirtualView*>& GetIdMap() {
36   static base::NoDestructor<std::map<int32_t, AXVirtualView*>> id_to_obj_map;
37   return *id_to_obj_map;
38 }
39 
40 // static
41 const char AXVirtualView::kViewClassName[] = "AXVirtualView";
42 
43 // static
GetFromId(int32_t id)44 AXVirtualView* AXVirtualView::GetFromId(int32_t id) {
45   auto& id_map = GetIdMap();
46   const auto& it = id_map.find(id);
47   return it != id_map.end() ? it->second : nullptr;
48 }
49 
AXVirtualView()50 AXVirtualView::AXVirtualView() {
51   GetIdMap()[unique_id_.Get()] = this;
52   ax_platform_node_ = ui::AXPlatformNode::Create(this);
53   DCHECK(ax_platform_node_);
54   custom_data_.AddStringAttribute(ax::mojom::StringAttribute::kClassName,
55                                   GetViewClassName());
56 }
57 
~AXVirtualView()58 AXVirtualView::~AXVirtualView() {
59   GetIdMap().erase(unique_id_.Get());
60   DCHECK(!parent_view_ || !virtual_parent_view_)
61       << "Either |parent_view_| or |virtual_parent_view_| could be set but "
62          "not both.";
63 
64   if (ax_platform_node_) {
65     ax_platform_node_->Destroy();
66     ax_platform_node_ = nullptr;
67   }
68 }
69 
AddChildView(std::unique_ptr<AXVirtualView> view)70 void AXVirtualView::AddChildView(std::unique_ptr<AXVirtualView> view) {
71   DCHECK(view);
72   if (view->virtual_parent_view_ == this)
73     return;  // Already a child of this virtual view.
74   AddChildViewAt(std::move(view), int{children_.size()});
75 }
76 
AddChildViewAt(std::unique_ptr<AXVirtualView> view,int index)77 void AXVirtualView::AddChildViewAt(std::unique_ptr<AXVirtualView> view,
78                                    int index) {
79   DCHECK(view);
80   CHECK_NE(view.get(), this)
81       << "You cannot add an AXVirtualView as its own child.";
82   DCHECK(!view->parent_view_) << "This |view| already has a View "
83                                  "parent. Call RemoveVirtualChildView first.";
84   DCHECK(!view->virtual_parent_view_) << "This |view| already has an "
85                                          "AXVirtualView parent. Call "
86                                          "RemoveChildView first.";
87   DCHECK_GE(index, 0);
88   DCHECK_LE(index, int{children_.size()});
89 
90   view->virtual_parent_view_ = this;
91   children_.insert(children_.begin() + index, std::move(view));
92   if (GetOwnerView()) {
93     GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
94                                              true);
95   }
96 }
97 
ReorderChildView(AXVirtualView * view,int index)98 void AXVirtualView::ReorderChildView(AXVirtualView* view, int index) {
99   DCHECK(view);
100   if (index >= int{children_.size()})
101     return;
102   if (index < 0)
103     index = int{children_.size()} - 1;
104 
105   DCHECK_EQ(view->virtual_parent_view_, this);
106   if (children_[index].get() == view)
107     return;
108 
109   int cur_index = GetIndexOf(view);
110   if (cur_index < 0)
111     return;
112 
113   std::unique_ptr<AXVirtualView> child = std::move(children_[cur_index]);
114   children_.erase(children_.begin() + cur_index);
115   children_.insert(children_.begin() + index, std::move(child));
116 
117   GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
118                                            true);
119 }
120 
RemoveFromParentView()121 std::unique_ptr<AXVirtualView> AXVirtualView::RemoveFromParentView() {
122   if (parent_view_)
123     return parent_view_->RemoveVirtualChildView(this);
124 
125   if (virtual_parent_view_)
126     return virtual_parent_view_->RemoveChildView(this);
127 
128   // This virtual view hasn't been added to a parent view yet.
129   NOTREACHED() << "Cannot remove from parent view if there is no parent.";
130   return {};
131 }
132 
RemoveChildView(AXVirtualView * view)133 std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView(
134     AXVirtualView* view) {
135   DCHECK(view);
136   int cur_index = GetIndexOf(view);
137   if (cur_index < 0)
138     return {};
139 
140   bool focus_changed = false;
141   if (GetOwnerView()) {
142     ViewAccessibility& view_accessibility =
143         GetOwnerView()->GetViewAccessibility();
144     if (view_accessibility.FocusedVirtualChild() &&
145         Contains(view_accessibility.FocusedVirtualChild())) {
146       focus_changed = true;
147     }
148   }
149 
150   std::unique_ptr<AXVirtualView> child = std::move(children_[cur_index]);
151   children_.erase(children_.begin() + cur_index);
152   child->virtual_parent_view_ = nullptr;
153   child->populate_data_callback_.Reset();
154 
155   if (GetOwnerView()) {
156     if (focus_changed)
157       GetOwnerView()->GetViewAccessibility().OverrideFocus(nullptr);
158     GetOwnerView()->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
159                                              true);
160   }
161 
162   return child;
163 }
164 
RemoveAllChildViews()165 void AXVirtualView::RemoveAllChildViews() {
166   while (!children_.empty())
167     RemoveChildView(children_.back().get());
168 }
169 
Contains(const AXVirtualView * view) const170 bool AXVirtualView::Contains(const AXVirtualView* view) const {
171   DCHECK(view);
172   for (const AXVirtualView* v = view; v; v = v->virtual_parent_view_) {
173     if (v == this)
174       return true;
175   }
176   return false;
177 }
178 
GetIndexOf(const AXVirtualView * view) const179 int AXVirtualView::GetIndexOf(const AXVirtualView* view) const {
180   DCHECK(view);
181   const auto iter =
182       std::find_if(children_.begin(), children_.end(),
183                    [view](const auto& child) { return child.get() == view; });
184   return iter != children_.end() ? static_cast<int>(iter - children_.begin())
185                                  : -1;
186 }
187 
GetViewClassName() const188 const char* AXVirtualView::GetViewClassName() const {
189   return kViewClassName;
190 }
191 
GetNativeObject() const192 gfx::NativeViewAccessible AXVirtualView::GetNativeObject() const {
193   DCHECK(ax_platform_node_);
194   return ax_platform_node_->GetNativeViewAccessible();
195 }
196 
NotifyAccessibilityEvent(ax::mojom::Event event_type)197 void AXVirtualView::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
198   DCHECK(ax_platform_node_);
199   if (GetOwnerView()) {
200     const ViewAccessibility::AccessibilityEventsCallback& events_callback =
201         GetOwnerView()->GetViewAccessibility().accessibility_events_callback();
202     if (events_callback)
203       events_callback.Run(this, event_type);
204   }
205   ax_platform_node_->NotifyAccessibilityEvent(event_type);
206 }
207 
GetCustomData()208 ui::AXNodeData& AXVirtualView::GetCustomData() {
209   return custom_data_;
210 }
211 
SetPopulateDataCallback(base::RepeatingCallback<void (ui::AXNodeData *)> callback)212 void AXVirtualView::SetPopulateDataCallback(
213     base::RepeatingCallback<void(ui::AXNodeData*)> callback) {
214   populate_data_callback_ = std::move(callback);
215 }
216 
UnsetPopulateDataCallback()217 void AXVirtualView::UnsetPopulateDataCallback() {
218   populate_data_callback_.Reset();
219 }
220 
221 // ui::AXPlatformNodeDelegate
222 
GetData() const223 const ui::AXNodeData& AXVirtualView::GetData() const {
224   // Make a copy of our |custom_data_| so that any modifications will not be
225   // made to the data that users of this class will be manipulating.
226   static ui::AXNodeData node_data;
227   node_data = custom_data_;
228 
229   node_data.id = GetUniqueId().Get();
230 
231   if (!GetOwnerView() || !GetOwnerView()->GetEnabled())
232     node_data.SetRestriction(ax::mojom::Restriction::kDisabled);
233 
234   if (!GetOwnerView() || !GetOwnerView()->IsDrawn())
235     node_data.AddState(ax::mojom::State::kInvisible);
236 
237   if (GetOwnerView() && GetOwnerView()->context_menu_controller())
238     node_data.AddAction(ax::mojom::Action::kShowContextMenu);
239 
240   if (populate_data_callback_ && GetOwnerView())
241     populate_data_callback_.Run(&node_data);
242 
243   // According to the ARIA spec, the node should not be ignored if it is
244   // focusable. This is to ensure that the focusable node is both understandable
245   // and operable.
246   if (node_data.HasState(ax::mojom::State::kIgnored) &&
247       node_data.HasState(ax::mojom::State::kFocusable)) {
248     node_data.RemoveState(ax::mojom::State::kIgnored);
249   }
250 
251   return node_data;
252 }
253 
GetChildCount() const254 int AXVirtualView::GetChildCount() const {
255   int count = 0;
256   for (const std::unique_ptr<AXVirtualView>& child : children_) {
257     if (child->IsIgnored()) {
258       count += child->GetChildCount();
259       continue;
260     }
261     count++;
262   }
263   return count;
264 }
265 
ChildAtIndex(int index)266 gfx::NativeViewAccessible AXVirtualView::ChildAtIndex(int index) {
267   DCHECK_GE(index, 0) << "Child indices should be greater or equal to 0.";
268   DCHECK_LT(index, GetChildCount())
269       << "Child indices should be less than the child count.";
270   int i = 0;
271   for (const std::unique_ptr<AXVirtualView>& child : children_) {
272     if (child->IsIgnored()) {
273       if (index - i < child->GetChildCount()) {
274         gfx::NativeViewAccessible result = child->ChildAtIndex(index - i);
275         if (result)
276           return result;
277       }
278       i += child->GetChildCount();
279       continue;
280     }
281     if (i == index)
282       return child->GetNativeObject();
283     i++;
284   }
285   return nullptr;
286 }
287 
288 #if !defined(OS_APPLE)
GetNSWindow()289 gfx::NativeViewAccessible AXVirtualView::GetNSWindow() {
290   NOTREACHED();
291   return nullptr;
292 }
293 #endif
294 
GetNativeViewAccessible()295 gfx::NativeViewAccessible AXVirtualView::GetNativeViewAccessible() {
296   return GetNativeObject();
297 }
298 
GetParent()299 gfx::NativeViewAccessible AXVirtualView::GetParent() {
300   if (parent_view_)
301     return parent_view_->GetNativeObject();
302 
303   if (virtual_parent_view_) {
304     if (virtual_parent_view_->IsIgnored())
305       return virtual_parent_view_->GetParent();
306     return virtual_parent_view_->GetNativeObject();
307   }
308 
309   // This virtual view hasn't been added to a parent view yet.
310   return nullptr;
311 }
312 
GetBoundsRect(const ui::AXCoordinateSystem coordinate_system,const ui::AXClippingBehavior clipping_behavior,ui::AXOffscreenResult * offscreen_result) const313 gfx::Rect AXVirtualView::GetBoundsRect(
314     const ui::AXCoordinateSystem coordinate_system,
315     const ui::AXClippingBehavior clipping_behavior,
316     ui::AXOffscreenResult* offscreen_result) const {
317   // We could optionally add clipping here if ever needed.
318   // TODO(nektar): Implement bounds that are relative to the parent.
319   gfx::Rect bounds = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
320   View* owner_view = GetOwnerView();
321   if (owner_view && owner_view->GetWidget())
322     View::ConvertRectToScreen(owner_view, &bounds);
323   switch (coordinate_system) {
324     case ui::AXCoordinateSystem::kScreenDIPs:
325       return bounds;
326     case ui::AXCoordinateSystem::kScreenPhysicalPixels: {
327       float scale_factor = 1.0;
328       if (owner_view && owner_view->GetWidget()) {
329         gfx::NativeView native_view = owner_view->GetWidget()->GetNativeView();
330         if (native_view)
331           scale_factor = ui::GetScaleFactorForNativeView(native_view);
332       }
333       return gfx::ScaleToEnclosingRect(bounds, scale_factor);
334     }
335     case ui::AXCoordinateSystem::kRootFrame:
336     case ui::AXCoordinateSystem::kFrame:
337       NOTIMPLEMENTED();
338       return gfx::Rect();
339   }
340 }
341 
HitTestSync(int screen_physical_pixel_x,int screen_physical_pixel_y) const342 gfx::NativeViewAccessible AXVirtualView::HitTestSync(
343     int screen_physical_pixel_x,
344     int screen_physical_pixel_y) const {
345   const ui::AXNodeData& node_data = GetData();
346   if (node_data.HasState(ax::mojom::State::kInvisible))
347     return nullptr;
348 
349   // Check if the point is within any of the virtual children of this view.
350   // AXVirtualView's HitTestSync is a recursive function that will return the
351   // deepest child, since it does not support relative bounds.
352   // Search the greater indices first, since they're on top in the z-order.
353   for (const std::unique_ptr<AXVirtualView>& child :
354        base::Reversed(children_)) {
355     gfx::NativeViewAccessible result =
356         child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y);
357     if (result)
358       return result;
359   }
360 
361   // If it's not inside any of our virtual children, and it's inside the bounds
362   // of this virtual view, then it's inside this virtual view.
363   gfx::Rect bounds_in_screen_physical_pixels =
364       GetBoundsRect(ui::AXCoordinateSystem::kScreenPhysicalPixels,
365                     ui::AXClippingBehavior::kUnclipped);
366   if (bounds_in_screen_physical_pixels.Contains(
367           static_cast<float>(screen_physical_pixel_x),
368           static_cast<float>(screen_physical_pixel_y)) &&
369       !node_data.IsIgnored()) {
370     return GetNativeObject();
371   }
372 
373   return nullptr;
374 }
375 
GetFocus()376 gfx::NativeViewAccessible AXVirtualView::GetFocus() {
377   View* owner_view = GetOwnerView();
378   if (owner_view) {
379     if (!(owner_view->HasFocus())) {
380       return nullptr;
381     }
382     return owner_view->GetViewAccessibility().GetFocusedDescendant();
383   }
384 
385   // This virtual view hasn't been added to a parent view yet.
386   return nullptr;
387 }
388 
GetFromNodeID(int32_t id)389 ui::AXPlatformNode* AXVirtualView::GetFromNodeID(int32_t id) {
390   AXVirtualView* virtual_view = GetFromId(id);
391   if (virtual_view) {
392     return virtual_view->ax_platform_node();
393   }
394   return nullptr;
395 }
396 
AccessibilityPerformAction(const ui::AXActionData & data)397 bool AXVirtualView::AccessibilityPerformAction(const ui::AXActionData& data) {
398   bool result = false;
399   if (custom_data_.HasAction(data.action))
400     result = HandleAccessibleAction(data);
401   if (!result && GetOwnerView())
402     return HandleAccessibleActionInOwnerView(data);
403   return result;
404 }
405 
ShouldIgnoreHoveredStateForTesting()406 bool AXVirtualView::ShouldIgnoreHoveredStateForTesting() {
407   // TODO(nektar): Implement.
408   return false;
409 }
410 
IsOffscreen() const411 bool AXVirtualView::IsOffscreen() const {
412   // TODO(nektar): Implement.
413   return false;
414 }
415 
GetUniqueId() const416 const ui::AXUniqueId& AXVirtualView::GetUniqueId() const {
417   return unique_id_;
418 }
419 
420 // Virtual views need to implement this function in order for A11Y events
421 // to be routed correctly.
GetTargetForNativeAccessibilityEvent()422 gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() {
423 #if defined(OS_WIN)
424   if (GetOwnerView())
425     return HWNDForView(GetOwnerView());
426 #endif
427   return gfx::kNullAcceleratedWidget;
428 }
429 
GetTableHasColumnOrRowHeaderNode() const430 base::Optional<bool> AXVirtualView::GetTableHasColumnOrRowHeaderNode() const {
431   return GetDelegate()->GetTableHasColumnOrRowHeaderNode();
432 }
433 
GetColHeaderNodeIds() const434 std::vector<int32_t> AXVirtualView::GetColHeaderNodeIds() const {
435   return GetDelegate()->GetColHeaderNodeIds();
436 }
437 
GetColHeaderNodeIds(int col_index) const438 std::vector<int32_t> AXVirtualView::GetColHeaderNodeIds(int col_index) const {
439   return GetDelegate()->GetColHeaderNodeIds(col_index);
440 }
441 
IsIgnored() const442 bool AXVirtualView::IsIgnored() const {
443   return GetData().IsIgnored();
444 }
445 
HandleAccessibleAction(const ui::AXActionData & action_data)446 bool AXVirtualView::HandleAccessibleAction(
447     const ui::AXActionData& action_data) {
448   if (!GetOwnerView())
449     return false;
450 
451   switch (action_data.action) {
452     case ax::mojom::Action::kShowContextMenu: {
453       const gfx::Rect screen_bounds = GetBoundsRect(
454           ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kClipped,
455           nullptr /* offscreen_result */);
456       if (!screen_bounds.IsEmpty()) {
457         GetOwnerView()->ShowContextMenu(screen_bounds.CenterPoint(),
458                                         ui::MENU_SOURCE_KEYBOARD);
459         return true;
460       }
461       break;
462     }
463 
464     default:
465       break;
466   }
467 
468   return HandleAccessibleActionInOwnerView(action_data);
469 }
470 
HandleAccessibleActionInOwnerView(const ui::AXActionData & action_data)471 bool AXVirtualView::HandleAccessibleActionInOwnerView(
472     const ui::AXActionData& action_data) {
473   DCHECK(GetOwnerView());
474   // Save the node id so that the owner view can determine which virtual view
475   // is being targeted for action.
476   ui::AXActionData forwarded_action_data = action_data;
477   forwarded_action_data.target_node_id = GetData().id;
478   return GetOwnerView()->HandleAccessibleAction(forwarded_action_data);
479 }
480 
GetOwnerView() const481 View* AXVirtualView::GetOwnerView() const {
482   if (parent_view_)
483     return parent_view_->view();
484 
485   if (virtual_parent_view_)
486     return virtual_parent_view_->GetOwnerView();
487 
488   // This virtual view hasn't been added to a parent view yet.
489   return nullptr;
490 }
491 
GetDelegate() const492 ViewAXPlatformNodeDelegate* AXVirtualView::GetDelegate() const {
493   DCHECK(GetOwnerView());
494   return static_cast<ViewAXPlatformNodeDelegate*>(
495       &GetOwnerView()->GetViewAccessibility());
496 }
497 
GetOrCreateWrapper(views::AXAuraObjCache * cache)498 AXVirtualViewWrapper* AXVirtualView::GetOrCreateWrapper(
499     views::AXAuraObjCache* cache) {
500 #if defined(USE_AURA)
501   if (!wrapper_)
502     wrapper_ = std::make_unique<AXVirtualViewWrapper>(this, cache);
503 #endif
504   return wrapper_.get();
505 }
506 
507 }  // namespace views
508