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

Side by Side Diff: ui/views/accessibility/native_view_accessibility_base.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: Review comments. Created 3 years, 8 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_base.h" 5 #include "ui/views/accessibility/native_view_accessibility_base.h"
6 6
7 #include "base/memory/ptr_util.h" 7 #include "base/memory/ptr_util.h"
8 #include "base/strings/utf_string_conversions.h" 8 #include "base/strings/utf_string_conversions.h"
9 #include "ui/events/event_utils.h" 9 #include "ui/events/event_utils.h"
10 #include "ui/gfx/native_widget_types.h" 10 #include "ui/gfx/native_widget_types.h"
11 #include "ui/views/controls/native/native_view_host.h" 11 #include "ui/views/controls/native/native_view_host.h"
12 #include "ui/views/view.h" 12 #include "ui/views/view.h"
13 #include "ui/views/widget/widget.h" 13 #include "ui/views/widget/widget.h"
14 14
15 namespace views { 15 namespace views {
16 16
17 namespace {
18
19 bool IsAccessibilityFocusableWhenEnabled(View* view) {
20 return view->focus_behavior() != View::FocusBehavior::NEVER &&
21 view->IsDrawn();
22 }
23
24 // Used to determine if a View should be ignored by accessibility clients by
25 // being a non-keyboard-focusable child of a keyboard-focusable ancestor. E.g.,
26 // LabelButtons contain Labels, but a11y should just show that there's a button.
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 // Recursively gets a list of unignored gfx::NativeViewAccessible children of a
40 // given View. If a child is ignored, its children will be used instead.
41 std::vector<gfx::NativeViewAccessible> GetUnignoredA11yChildren(View* view) {
42 std::vector<gfx::NativeViewAccessible> children;
43 for (int i = 0; i < view->child_count(); ++i) {
44 View* child = view->child_at(i);
45 if (NativeViewAccessibilityBase::IsIgnoredView(child)) {
46 std::vector<gfx::NativeViewAccessible> grandchildren =
47 GetUnignoredA11yChildren(child);
48 children.insert(children.end(), grandchildren.begin(),
49 grandchildren.end());
50 } else {
51 children.push_back(child->GetNativeViewAccessible());
52 }
53 }
54 return children;
55 }
56
57 } // namespace
58
59 // static
60 bool NativeViewAccessibilityBase::IsIgnoredView(View* view) {
tapted 2017/04/05 06:03:33 Would would happen if we did something like (ax_p
Patti Lor 2017/05/24 07:29:11 So the invisible stuff works, but I think since th
61 NativeViewAccessibilityBase* nva = GetForView(view);
62 if (nva)
63 return nva->GetData().role == ui::AX_ROLE_IGNORED;
64 return false;
tapted 2017/04/04 08:13:13 Comment here like // If the native accessibility
Patti Lor 2017/04/11 05:40:45 Done.
65 }
66
17 NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view) 67 NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view)
18 : view_(view), 68 : view_(view),
19 parent_widget_(nullptr), 69 parent_widget_(nullptr),
20 ax_node_(ui::AXPlatformNode::Create(this)) { 70 ax_node_(ui::AXPlatformNode::Create(this)) {
21 DCHECK(ax_node_); 71 DCHECK(ax_node_);
22 } 72 }
23 73
24 NativeViewAccessibilityBase::~NativeViewAccessibilityBase() { 74 NativeViewAccessibilityBase::~NativeViewAccessibilityBase() {
25 ax_node_->Destroy(); 75 ax_node_->Destroy();
26 if (parent_widget_) 76 if (parent_widget_)
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
70 120
71 if (view_->IsAccessibilityFocusable()) 121 if (view_->IsAccessibilityFocusable())
72 data_.state |= (1 << ui::AX_STATE_FOCUSABLE); 122 data_.state |= (1 << ui::AX_STATE_FOCUSABLE);
73 123
74 if (!view_->enabled()) 124 if (!view_->enabled())
75 data_.state |= (1 << ui::AX_STATE_DISABLED); 125 data_.state |= (1 << ui::AX_STATE_DISABLED);
76 126
77 if (!view_->IsDrawn()) 127 if (!view_->IsDrawn())
78 data_.state |= (1 << ui::AX_STATE_INVISIBLE); 128 data_.state |= (1 << ui::AX_STATE_INVISIBLE);
79 129
130 // Make sure this element is excluded from the a11y tree if there's a
131 // focusable parent. All keyboard focusable elements should be leaf nodes.
132 // Exceptions to this rule will themselves be accessibility focusable.
133 if (IsViewUnfocusableChildOfFocusableAncestor(view_))
134 data_.role = ui::AX_ROLE_IGNORED;
80 return data_; 135 return data_;
81 } 136 }
82 137
83 int NativeViewAccessibilityBase::GetChildCount() { 138 int NativeViewAccessibilityBase::GetChildCount() {
84 int child_count = view_->child_count(); 139 int child_count = GetUnignoredA11yChildren(view_).size();
85 140
86 std::vector<Widget*> child_widgets; 141 std::vector<Widget*> child_widgets;
87 PopulateChildWidgetVector(&child_widgets); 142 PopulateChildWidgetVector(&child_widgets);
88 child_count += child_widgets.size(); 143 child_count += child_widgets.size();
89 144
90 return child_count; 145 return child_count;
91 } 146 }
92 147
93 gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) { 148 gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) {
94 // If this is a root view, our widget might have child widgets. Include 149 std::vector<gfx::NativeViewAccessible> a11y_children =
150 GetUnignoredA11yChildren(view_);
151 // Include the child widgets that may be present if this is a RootView.
95 std::vector<Widget*> child_widgets; 152 std::vector<Widget*> child_widgets;
96 PopulateChildWidgetVector(&child_widgets); 153 PopulateChildWidgetVector(&child_widgets);
97 int child_widget_count = static_cast<int>(child_widgets.size());
98 154
99 if (index < view_->child_count()) { 155 if (index >= static_cast<int>(a11y_children.size() + child_widgets.size()))
100 return view_->child_at(index)->GetNativeViewAccessible(); 156 return nullptr;
101 } else if (index < view_->child_count() + child_widget_count) { 157 if (index < static_cast<int>(a11y_children.size()))
102 Widget* child_widget = child_widgets[index - view_->child_count()]; 158 return a11y_children[index];
103 return child_widget->GetRootView()->GetNativeViewAccessible();
104 }
105 159
106 return nullptr; 160 Widget* child_widget = child_widgets[index - a11y_children.size()];
161 return child_widget->GetRootView()->GetNativeViewAccessible();
107 } 162 }
108 163
109 gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() { 164 gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() {
110 if (view_->GetWidget()) 165 if (view_->GetWidget())
111 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); 166 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow();
112 return nullptr; 167 return nullptr;
113 } 168 }
114 169
115 gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() { 170 gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() {
116 if (view_->parent()) 171 View* parent_view = view_->parent();
117 return view_->parent()->GetNativeViewAccessible(); 172 while (parent_view && IsIgnoredView(parent_view))
118 173 parent_view = parent_view->parent();
119 if (parent_widget_) 174 return parent_view ? parent_view->GetNativeViewAccessible()
120 return parent_widget_->GetRootView()->GetNativeViewAccessible(); 175 : GetNativeViewAccessibleForWidget();
121
122 return nullptr;
123 } 176 }
124 177
125 gfx::Vector2d NativeViewAccessibilityBase::GetGlobalCoordinateOffset() { 178 gfx::Vector2d NativeViewAccessibilityBase::GetGlobalCoordinateOffset() {
126 return gfx::Vector2d(0, 0); // location is already in screen coordinates. 179 return gfx::Vector2d(0, 0); // Location is already in screen coordinates.
127 } 180 }
128 181
129 gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x, 182 gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x,
130 int y) { 183 int y) {
131 if (!view_ || !view_->GetWidget()) 184 if (!view_ || !view_->GetWidget())
132 return nullptr; 185 return nullptr;
133 186
134 // Search child widgets first, since they're on top in the z-order. 187 // Search child widgets first, since they're on top in the z-order.
135 std::vector<Widget*> child_widgets; 188 std::vector<Widget*> child_widgets;
136 PopulateChildWidgetVector(&child_widgets); 189 PopulateChildWidgetVector(&child_widgets);
137 for (Widget* child_widget : child_widgets) { 190 for (Widget* child_widget : child_widgets) {
138 View* child_root_view = child_widget->GetRootView(); 191 View* child_root_view = child_widget->GetRootView();
139 gfx::Point point(x, y); 192 gfx::Point point(x, y);
140 View::ConvertPointFromScreen(child_root_view, &point); 193 View::ConvertPointFromScreen(child_root_view, &point);
141 if (child_root_view->HitTestPoint(point)) 194 if (child_root_view->HitTestPoint(point))
142 return child_root_view->GetNativeViewAccessible(); 195 return child_root_view->GetNativeViewAccessible();
143 } 196 }
144 197
198 // Check if the given point is inside |view_|.
145 gfx::Point point(x, y); 199 gfx::Point point(x, y);
146 View::ConvertPointFromScreen(view_, &point); 200 View::ConvertPointFromScreen(view_, &point);
147 if (!view_->HitTestPoint(point)) 201 if (!view_->HitTestPoint(point))
148 return nullptr; 202 return nullptr;
149 203
150 // Check if the point is within any of the immediate children of this 204 // Early return if this node is a leaf, depending on whether it's ignored.
151 // view. We don't have to search further because AXPlatformNode will 205 if (GetChildCount() == 0)
152 // do a recursive hit test if we return anything other than |this| or NULL. 206 return IsIgnoredView(view_) ? nullptr : GetNativeObject();
207
208 // Check if the point is within any of the immediate children of this view. We
209 // don't have to search further because AXPlatformNodeWin will do a recursive
210 // hit test if we return anything other than GetNativeObject() or nullptr.
211 // The exception to this is if the recursion needs to travel up the tree,
212 // which may return a non-direct ancestor of the given node.
153 for (int i = view_->child_count() - 1; i >= 0; --i) { 213 for (int i = view_->child_count() - 1; i >= 0; --i) {
154 View* child_view = view_->child_at(i); 214 View* child_view = view_->child_at(i);
155 if (!child_view->visible()) 215 NativeViewAccessibilityBase* child_nva = GetForView(child_view);
156 continue; 216 if (child_nva) {
tapted 2017/04/05 06:03:33 I think this means that any webview child can't be
157 217 gfx::Point point_in_child_coords(point);
158 gfx::Point point_in_child_coords(point); 218 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords);
159 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); 219 if (!child_nva->GetData().HasStateFlag(ui::AX_STATE_INVISIBLE) &&
160 if (child_view->HitTestPoint(point_in_child_coords)) 220 child_view->HitTestPoint(point_in_child_coords)) {
161 return child_view->GetNativeViewAccessible(); 221 if (child_nva->GetChildCount() == 0 && IsIgnoredView(child_view)) {
222 // Multiple levels might be jumped here.
223 return child_nva->GetParent();
224 }
225 return child_nva->GetNativeObject();
226 }
227 }
162 } 228 }
163 229
164 // If it's not inside any of our children, it's inside this view. 230 // If it's not inside any of our children, it's inside this view.
165 return GetNativeObject(); 231 return GetNativeObject();
166 } 232 }
167 233
168 gfx::NativeViewAccessible NativeViewAccessibilityBase::GetFocus() { 234 gfx::NativeViewAccessible NativeViewAccessibilityBase::GetFocus() {
169 FocusManager* focus_manager = view_->GetFocusManager(); 235 FocusManager* focus_manager = view_->GetFocusManager();
170 View* focused_view = 236 View* focused_view =
171 focus_manager ? focus_manager->GetFocusedView() : nullptr; 237 focus_manager ? focus_manager->GetFocusedView() : nullptr;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
218 } 284 }
219 } 285 }
220 286
221 void NativeViewAccessibilityBase::SetParentWidget(Widget* parent_widget) { 287 void NativeViewAccessibilityBase::SetParentWidget(Widget* parent_widget) {
222 if (parent_widget_) 288 if (parent_widget_)
223 parent_widget_->RemoveObserver(this); 289 parent_widget_->RemoveObserver(this);
224 parent_widget_ = parent_widget; 290 parent_widget_ = parent_widget;
225 parent_widget_->AddObserver(this); 291 parent_widget_->AddObserver(this);
226 } 292 }
227 293
294 // static
295 NativeViewAccessibilityBase* NativeViewAccessibilityBase::GetForView(
296 View* view) {
297 // Retrieving the gfx::NativeViewAccessible also ensures that a
298 // NativeViewAccessibility exists for the given View.
299 gfx::NativeViewAccessible native_object = view->GetNativeViewAccessible();
300 ui::AXPlatformNode* node =
301 ui::AXPlatformNode::FromNativeViewAccessible(native_object);
302 // WebViews with web contents have their own NativeViewAccessible
303 // implementation, so |node| will be null if |view| is a WebView.
304 if (node)
305 return static_cast<NativeViewAccessibilityBase*>(node->GetDelegate());
306 return nullptr;
307 }
308
309 gfx::NativeViewAccessible
310 NativeViewAccessibilityBase::GetNativeViewAccessibleForWidget() {
tapted 2017/04/05 06:03:33 I think this needs an override in NativeViewAccess
Patti Lor 2017/04/11 05:40:45 I'm confused, can you explain why? A call to NVAWi
311 if (parent_widget_)
312 return parent_widget_->GetRootView()->GetNativeViewAccessible();
313 return nullptr;
314 }
315
228 gfx::RectF NativeViewAccessibilityBase::GetBoundsInScreen() const { 316 gfx::RectF NativeViewAccessibilityBase::GetBoundsInScreen() const {
229 return gfx::RectF(view_->GetBoundsInScreen()); 317 return gfx::RectF(view_->GetBoundsInScreen());
230 } 318 }
231 319
232 void NativeViewAccessibilityBase::PopulateChildWidgetVector( 320 void NativeViewAccessibilityBase::PopulateChildWidgetVector(
233 std::vector<Widget*>* result_child_widgets) { 321 std::vector<Widget*>* result_child_widgets) {
234 // Only attach child widgets to the root view. 322 // Only attach child widgets to the root view.
235 Widget* widget = view_->GetWidget(); 323 Widget* widget = view_->GetWidget();
236 // Note that during window close, a Widget may exist in a state where it has 324 // Note that during window close, a Widget may exist in a state where it has
237 // no NativeView, but hasn't yet torn down its view hierarchy. 325 // no NativeView, but hasn't yet torn down its view hierarchy.
238 if (!widget || !widget->GetNativeView() || widget->GetRootView() != view_) 326 if (!widget || !widget->GetNativeView() || widget->GetRootView() != view_)
239 return; 327 return;
240 328
241 std::set<Widget*> child_widgets; 329 std::set<Widget*> child_widgets;
242 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); 330 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets);
243 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { 331 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) {
244 Widget* child_widget = *iter; 332 Widget* child_widget = *iter;
245 DCHECK_NE(widget, child_widget); 333 DCHECK_NE(widget, child_widget);
246 334
247 if (!child_widget->IsVisible()) 335 if (!child_widget->IsVisible())
248 continue; 336 continue;
249 337
250 if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey)) 338 if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey))
251 continue; 339 continue;
252 340
253 gfx::NativeViewAccessible child_widget_accessible = 341 NativeViewAccessibilityBase* child_widget_view_accessibility =
254 child_widget->GetRootView()->GetNativeViewAccessible(); 342 GetForView(child_widget->GetRootView());
255 ui::AXPlatformNode* child_widget_platform_node = 343 if (child_widget_view_accessibility &&
256 ui::AXPlatformNode::FromNativeViewAccessible(child_widget_accessible); 344 child_widget_view_accessibility->parent_widget() != widget)
257 if (child_widget_platform_node) { 345 child_widget_view_accessibility->SetParentWidget(widget);
258 NativeViewAccessibilityBase* child_widget_view_accessibility =
259 static_cast<NativeViewAccessibilityBase*>(
260 child_widget_platform_node->GetDelegate());
261 if (child_widget_view_accessibility->parent_widget() != widget)
262 child_widget_view_accessibility->SetParentWidget(widget);
263 }
264 346
265 result_child_widgets->push_back(child_widget); 347 result_child_widgets->push_back(child_widget);
266 } 348 }
267 } 349 }
268 350
269 } // namespace views 351 } // namespace views
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698