Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 |
| OLD | NEW |