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