Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Side by Side Diff: ui/views/accessibility/native_view_accessibility.cc

Issue 2119413004: a11y: Exclude children of nested keyboard accessible controls from a11y tree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Refactor to use ui::AX_ROLE_IGNORED for excluding a11y elements from the tree instead of focusabili… Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/views/accessibility/native_view_accessibility.h" 5 #include "ui/views/accessibility/native_view_accessibility.h"
6 6
7 #include "base/strings/utf_string_conversions.h" 7 #include "base/strings/utf_string_conversions.h"
8 #include "ui/events/event_utils.h" 8 #include "ui/events/event_utils.h"
9 #include "ui/gfx/native_widget_types.h" 9 #include "ui/gfx/native_widget_types.h"
10 #include "ui/views/controls/native/native_view_host.h" 10 #include "ui/views/controls/native/native_view_host.h"
11 #include "ui/views/view.h" 11 #include "ui/views/view.h"
12 #include "ui/views/widget/widget.h" 12 #include "ui/views/widget/widget.h"
13 13
14 namespace views { 14 namespace views {
15 15
16 #if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL) 16 namespace {
17
18 typedef std::map<View*, NativeViewAccessibility*> NativeViewAccessibilityMap;
19
20 bool IsAccessibilityFocusableWhenEnabled(View* view) {
21 return view->focus_behavior() != View::FocusBehavior::NEVER &&
22 view->IsDrawn();
23 }
24
25 // Used to determine if a View should be ignored by accessibility clients by
26 // being a non-keyboard-focusable child of a keyboard-focusable ancestor.
27 bool IsViewUnfocusableChildOfFocusableAncestor(View* view) {
28 if (IsAccessibilityFocusableWhenEnabled(view))
29 return false;
30
31 while (view->parent()) {
32 view = view->parent();
33 if (IsAccessibilityFocusableWhenEnabled(view))
34 return true;
35 }
36 return false;
37 }
38
39 // Convenience method for checking if a View should be ignored by a11y.
40 bool IsViewA11yIgnored(View* view) {
41 return NativeViewAccessibility::GetOrCreate(view)->GetData().role ==
42 ui::AX_ROLE_IGNORED;
43 }
44
45 } // namespace
46
17 // static 47 // static
18 NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { 48 NativeViewAccessibility* NativeViewAccessibility::GetOrCreate(View* view) {
19 return new NativeViewAccessibility(view); 49 NativeViewAccessibilityMap* views_to_nva = GetNativeViewAccessibilityMap();
50 NativeViewAccessibilityMap::iterator it = views_to_nva->find(view);
51 if (it != views_to_nva->end())
52 return (*it).second;
53 NativeViewAccessibility* nva = CreateNewNativeViewAccessibilityImpl(view);
54 views_to_nva->insert(NativeViewAccessibilityMap::value_type(view, nva));
55 return nva;
20 } 56 }
21 #endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
22 57
23 NativeViewAccessibility::NativeViewAccessibility(View* view) 58 NativeViewAccessibility::NativeViewAccessibility(View* view)
24 : view_(view), 59 : view_(view),
25 parent_widget_(nullptr), 60 parent_widget_(nullptr),
26 ax_node_(nullptr) { 61 ax_node_(nullptr) {
27 ax_node_ = ui::AXPlatformNode::Create(this); 62 ax_node_ = ui::AXPlatformNode::Create(this);
28 } 63 }
29 64
30 NativeViewAccessibility::~NativeViewAccessibility() { 65 NativeViewAccessibility::~NativeViewAccessibility() {
31 if (ax_node_) 66 if (ax_node_)
32 ax_node_->Destroy(); 67 ax_node_->Destroy();
33 if (parent_widget_) 68 if (parent_widget_)
34 parent_widget_->RemoveObserver(this); 69 parent_widget_->RemoveObserver(this);
70
71 // Remove this instance from the map when it's destroyed.
tapted 2016/12/22 02:56:36 In theory this should occur in NativeViewAccessibi
Patti Lor 2017/01/11 02:01:48 Done.
72 NativeViewAccessibilityMap* views_to_nva = GetNativeViewAccessibilityMap();
tapted 2016/12/22 02:56:36 We can just call GetNativeViewAccessibilityMap()
Patti Lor 2017/01/11 02:01:47 Oops, thanks for the tip!
73 NativeViewAccessibilityMap::iterator it = views_to_nva->find(view_);
74 if (it != views_to_nva->end()) {
75 views_to_nva->erase(it);
76 } else {
77 // This should only ever happen in tests where the NativeViewAccessibility
dmazzoni 2016/12/28 18:02:35 Are there any such tests? It might be nice to fix
Patti Lor 2017/01/11 02:01:47 Yes, NativeViewAccessibilityTest.CrashOnWidgetDest
78 // instance was not created via GetOrCreate().
79 }
35 } 80 }
36 81
37 gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() { 82 gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() {
38 return ax_node_ ? ax_node_->GetNativeViewAccessible() : nullptr; 83 return ax_node_ ? ax_node_->GetNativeViewAccessible() : nullptr;
39 } 84 }
40 85
41 void NativeViewAccessibility::Destroy() { 86 void NativeViewAccessibility::Destroy() {
42 delete this; 87 delete this;
43 } 88 }
44 89
(...skipping 28 matching lines...) Expand all
73 } 118 }
74 119
75 view_->GetAccessibleNodeData(&data_); 120 view_->GetAccessibleNodeData(&data_);
76 data_.location = gfx::RectF(view_->GetBoundsInScreen()); 121 data_.location = gfx::RectF(view_->GetBoundsInScreen());
77 base::string16 description; 122 base::string16 description;
78 view_->GetTooltipText(gfx::Point(), &description); 123 view_->GetTooltipText(gfx::Point(), &description);
79 data_.AddStringAttribute(ui::AX_ATTR_DESCRIPTION, 124 data_.AddStringAttribute(ui::AX_ATTR_DESCRIPTION,
80 base::UTF16ToUTF8(description)); 125 base::UTF16ToUTF8(description));
81 126
82 if (view_->IsAccessibilityFocusable()) 127 if (view_->IsAccessibilityFocusable())
83 data_.state |= (1 << ui::AX_STATE_FOCUSABLE); 128 data_.AddStateFlag(ui::AX_STATE_FOCUSABLE);
84 129
85 if (!view_->enabled()) 130 if (!view_->enabled())
86 data_.state |= (1 << ui::AX_STATE_DISABLED); 131 data_.AddStateFlag(ui::AX_STATE_DISABLED);
87 132
88 if (!view_->visible()) 133 if (!view_->visible())
89 data_.state |= (1 << ui::AX_STATE_INVISIBLE); 134 data_.AddStateFlag(ui::AX_STATE_INVISIBLE);
135
136 // Make sure this element is excluded from the a11y tree if there's a
137 // focusable parent. All keyboard focusable elements should be leaf nodes.
138 // Exceptions to this rule will themselves be accessibility focusable.
139 if (IsViewUnfocusableChildOfFocusableAncestor(view_))
140 data_.role = ui::AX_ROLE_IGNORED;
90 141
91 return data_; 142 return data_;
92 } 143 }
93 144
94 int NativeViewAccessibility::GetChildCount() { 145 int NativeViewAccessibility::GetChildCount() {
95 int child_count = view_->child_count(); 146 int child_count = 0;
147 for (int i = 0; i < view_->child_count(); ++i) {
148 View* child = view_->child_at(i);
149 if (IsViewA11yIgnored(child)) {
150 // If not visible to accessibility clients, use its child count instead.
151 child_count += GetOrCreate(child)->GetChildCount();
152 } else {
153 ++child_count;
154 }
155 }
96 156
97 std::vector<Widget*> child_widgets; 157 std::vector<Widget*> child_widgets;
98 PopulateChildWidgetVector(&child_widgets); 158 PopulateChildWidgetVector(&child_widgets);
99 child_count += child_widgets.size(); 159 child_count += child_widgets.size();
100 160
101 return child_count; 161 return child_count;
102 } 162 }
103 163
104 gfx::NativeViewAccessible NativeViewAccessibility::ChildAtIndex(int index) { 164 gfx::NativeViewAccessible NativeViewAccessibility::ChildAtIndex(int index) {
105 // If this is a root view, our widget might have child widgets. Include 165 if (GetChildCount() == 0)
166 return nullptr;
167
168 // Include the child widgets that may be present if this is a RootView.
106 std::vector<Widget*> child_widgets; 169 std::vector<Widget*> child_widgets;
107 PopulateChildWidgetVector(&child_widgets); 170 PopulateChildWidgetVector(&child_widgets);
108 int child_widget_count = static_cast<int>(child_widgets.size()); 171 int child_widget_count = static_cast<int>(child_widgets.size());
109 172
110 if (index < view_->child_count()) { 173 int ax_child_count = GetChildCount();
111 return view_->child_at(index)->GetNativeViewAccessible(); 174 if (index < ax_child_count - child_widget_count) {
tapted 2016/12/22 02:56:36 This logic is pretty complicated.. I think we effe
Patti Lor 2017/01/11 02:01:47 Done. I'm not sure if HitTestSync can be updated t
112 } else if (index < view_->child_count() + child_widget_count) { 175 int curr_child_count = 0;
176 for (int i = 0; i < view_->child_count(); ++i) {
177 View* child = view_->child_at(i);
178 bool ignored = IsViewA11yIgnored(child);
179 int pending_children = ignored ? GetOrCreate(child)->GetChildCount() : 1;
180 if (index < curr_child_count + pending_children) {
181 if (ignored) {
182 return GetOrCreate(child)->ChildAtIndex(index - curr_child_count);
183 }
184 return child->GetNativeViewAccessible();
185 }
186 curr_child_count += pending_children;
187 }
188 } else if (index < ax_child_count) {
113 Widget* child_widget = child_widgets[index - view_->child_count()]; 189 Widget* child_widget = child_widgets[index - view_->child_count()];
dmazzoni 2016/12/28 18:02:35 This isn't correct anymore - taking [index - view_
Patti Lor 2017/01/11 02:01:47 Thanks - updated to index - ax_child_count.
114 return child_widget->GetRootView()->GetNativeViewAccessible(); 190 return child_widget->GetRootView()->GetNativeViewAccessible();
115 } 191 }
116 192
117 return nullptr; 193 return nullptr;
118 } 194 }
119 195
120 gfx::NativeWindow NativeViewAccessibility::GetTopLevelWidget() { 196 gfx::NativeWindow NativeViewAccessibility::GetTopLevelWidget() {
121 if (view_->GetWidget()) 197 if (view_->GetWidget())
122 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); 198 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow();
123 return nullptr; 199 return nullptr;
124 } 200 }
125 201
126 gfx::NativeViewAccessible NativeViewAccessibility::GetParent() { 202 gfx::NativeViewAccessible NativeViewAccessibility::GetParent() {
127 if (view_->parent()) 203 View* parent_view = view_->parent();
128 return view_->parent()->GetNativeViewAccessible(); 204 if (parent_view) {
205 if (IsViewA11yIgnored(parent_view))
206 return GetOrCreate(parent_view)->GetParent();
207 else
tapted 2016/12/22 02:56:36 nit: "no else after return" - https://chromium.goo
Patti Lor 2017/01/11 02:01:48 Done, thanks for always digging up the style guide
208 return parent_view->GetNativeViewAccessible();
209 }
129 210
130 // TODO: move this to NativeViewAccessibilityMac. 211 // TODO: move this to NativeViewAccessibilityMac.
131 #if defined(OS_MACOSX) 212 #if defined(OS_MACOSX)
132 if (view_->GetWidget()) 213 if (view_->GetWidget())
133 return view_->GetWidget()->GetNativeView(); 214 return view_->GetWidget()->GetNativeView();
134 #endif 215 #endif
135 216
136 if (parent_widget_) 217 if (parent_widget_)
137 return parent_widget_->GetRootView()->GetNativeViewAccessible(); 218 return parent_widget_->GetRootView()->GetNativeViewAccessible();
138 219
139 return nullptr; 220 return nullptr;
140 } 221 }
141 222
142 gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() { 223 gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() {
143 return gfx::Vector2d(0, 0); // location is already in screen coordinates. 224 return gfx::Vector2d(0, 0); // Location is already in screen coordinates.
144 } 225 }
145 226
146 gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) { 227 gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) {
147 if (!view_ || !view_->GetWidget()) 228 if (!view_ || !view_->GetWidget())
148 return nullptr; 229 return nullptr;
149 230
150 // Search child widgets first, since they're on top in the z-order. 231 // Search child widgets first, since they're on top in the z-order.
151 std::vector<Widget*> child_widgets; 232 std::vector<Widget*> child_widgets;
152 PopulateChildWidgetVector(&child_widgets); 233 PopulateChildWidgetVector(&child_widgets);
153 for (Widget* child_widget : child_widgets) { 234 for (Widget* child_widget : child_widgets) {
154 View* child_root_view = child_widget->GetRootView(); 235 View* child_root_view = child_widget->GetRootView();
155 gfx::Point point(x, y); 236 gfx::Point point(x, y);
156 View::ConvertPointFromScreen(child_root_view, &point); 237 View::ConvertPointFromScreen(child_root_view, &point);
157 if (child_root_view->HitTestPoint(point)) 238 if (child_root_view->HitTestPoint(point))
158 return child_root_view->GetNativeViewAccessible(); 239 return child_root_view->GetNativeViewAccessible();
159 } 240 }
160 241
242 if (GetChildCount() == 0) {
243 if (IsViewA11yIgnored(view_))
244 return GetParent();
dmazzoni 2016/12/28 18:02:35 I think you should return nullptr here. HitTest i
Patti Lor 2017/01/11 02:01:47 Done.
245 return GetNativeObject();
246 }
247
161 gfx::Point point(x, y); 248 gfx::Point point(x, y);
162 View::ConvertPointFromScreen(view_, &point); 249 View::ConvertPointFromScreen(view_, &point);
163 if (!view_->HitTestPoint(point)) 250 if (!view_->HitTestPoint(point))
164 return nullptr; 251 return nullptr;
165 252
166 // Check if the point is within any of the immediate children of this 253 // Check if the point is within any of the immediate children of this view. We
167 // view. We don't have to search further because AXPlatformNode will 254 // don't have to search further because AXPlatformNodeWin will do a recursive
168 // do a recursive hit test if we return anything other than |this| or NULL. 255 // hit test if we return anything other than GetNativeObject() or nullptr.
169 for (int i = view_->child_count() - 1; i >= 0; --i) { 256 for (int i = view_->child_count() - 1; i >= 0; --i) {
tapted 2016/12/22 02:56:36 Does this need to consider whether the child is ig
Patti Lor 2017/01/11 02:01:48 No - skipping ignored children at this point of th
170 View* child_view = view_->child_at(i); 257 View* child_view = view_->child_at(i);
171 if (!child_view->visible()) 258 if (!child_view->visible())
172 continue; 259 continue;
173 260
174 gfx::Point point_in_child_coords(point); 261 gfx::Point point_in_child_coords(point);
175 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); 262 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords);
176 if (child_view->HitTestPoint(point_in_child_coords)) 263 if (child_view->HitTestPoint(point_in_child_coords))
177 return child_view->GetNativeViewAccessible(); 264 return child_view->GetNativeViewAccessible();
178 } 265 }
179 266
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
240 } 327 }
241 } 328 }
242 329
243 void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) { 330 void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) {
244 if (parent_widget_) 331 if (parent_widget_)
245 parent_widget_->RemoveObserver(this); 332 parent_widget_->RemoveObserver(this);
246 parent_widget_ = parent_widget; 333 parent_widget_ = parent_widget;
247 parent_widget_->AddObserver(this); 334 parent_widget_->AddObserver(this);
248 } 335 }
249 336
337 // static
338 NativeViewAccessibilityMap*
tapted 2016/12/22 02:56:36 nit: Return a reference (sometimes returning a poi
Patti Lor 2017/01/11 02:01:47 Done.
339 NativeViewAccessibility::GetNativeViewAccessibilityMap() {
tapted 2016/12/22 02:56:36 can this go in an anonymous namespace?
Patti Lor 2017/01/11 02:01:47 Done - I assumed you also meant to make it a funct
tapted 2017/01/11 18:27:39 yep
340 CR_DEFINE_STATIC_LOCAL(NativeViewAccessibilityMap, views_to_nva, ());
341 return &views_to_nva;
342 }
343
344 #if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
345 // static
346 NativeViewAccessibility*
347 NativeViewAccessibility::CreateNewNativeViewAccessibilityImpl(View* view) {
348 return new NativeViewAccessibility(view);
349 }
350 #endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
351
250 void NativeViewAccessibility::PopulateChildWidgetVector( 352 void NativeViewAccessibility::PopulateChildWidgetVector(
251 std::vector<Widget*>* result_child_widgets) { 353 std::vector<Widget*>* result_child_widgets) {
252 // Only attach child widgets to the root view. 354 // Only attach child widgets to the root view.
253 Widget* widget = view_->GetWidget(); 355 Widget* widget = view_->GetWidget();
254 if (!widget || widget->GetRootView() != view_) 356 if (!widget || widget->GetRootView() != view_)
255 return; 357 return;
256 358
257 std::set<Widget*> child_widgets; 359 std::set<Widget*> child_widgets;
258 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); 360 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets);
259 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { 361 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) {
(...skipping 16 matching lines...) Expand all
276 child_widget_platform_node->GetDelegate()); 378 child_widget_platform_node->GetDelegate());
277 if (child_widget_view_accessibility->parent_widget() != widget) 379 if (child_widget_view_accessibility->parent_widget() != widget)
278 child_widget_view_accessibility->SetParentWidget(widget); 380 child_widget_view_accessibility->SetParentWidget(widget);
279 } 381 }
280 382
281 result_child_widgets->push_back(child_widget); 383 result_child_widgets->push_back(child_widget);
282 } 384 }
283 } 385 }
284 386
285 } // namespace views 387 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698