 Chromium Code Reviews
 Chromium Code Reviews Issue 2119413004:
  a11y: Exclude children of nested keyboard accessible controls from a11y tree.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 2119413004:
  a11y: Exclude children of nested keyboard accessible controls from a11y tree.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| 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/accessibility/platform/ax_platform_node.h" | |
| 8 #include "ui/events/event_utils.h" | 9 #include "ui/events/event_utils.h" | 
| 9 #include "ui/gfx/native_widget_types.h" | 10 #include "ui/gfx/native_widget_types.h" | 
| 10 #include "ui/views/controls/native/native_view_host.h" | 11 #include "ui/views/controls/native/native_view_host.h" | 
| 11 #include "ui/views/view.h" | 12 #include "ui/views/view.h" | 
| 12 #include "ui/views/widget/widget.h" | 13 #include "ui/views/widget/widget.h" | 
| 13 | 14 | 
| 14 namespace views { | 15 namespace views { | 
| 15 | 16 | 
| 16 #if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL) | 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. | |
| 26 bool IsViewUnfocusableChildOfFocusableAncestor(View* view) { | |
| 27 if (IsAccessibilityFocusableWhenEnabled(view)) | |
| 28 return false; | |
| 29 | |
| 30 while (view->parent()) { | |
| 31 view = view->parent(); | |
| 32 if (IsAccessibilityFocusableWhenEnabled(view)) | |
| 33 return true; | |
| 34 } | |
| 35 return false; | |
| 36 } | |
| 37 | |
| 38 // Convenience method for checking if a View should be ignored by a11y. | |
| 39 bool IsViewA11yIgnored(View* view) { | |
| 40 return NativeViewAccessibility::GetForView(view)->GetData().role == | |
| 41 ui::AX_ROLE_IGNORED; | |
| 42 } | |
| 43 | |
| 44 // Recursively gets a list of unignored gfx::NativeViewAccessible children of a | |
| 45 // given View. If a child is ignored, its children will be used instead. | |
| 46 std::vector<gfx::NativeViewAccessible> GetUnignoredA11yChildren(View* view) { | |
| 47 std::vector<gfx::NativeViewAccessible> children; | |
| 48 for (int i = 0; i < view->child_count(); ++i) { | |
| 49 View* child = view->child_at(i); | |
| 50 if (IsViewA11yIgnored(child)) { | |
| 51 std::vector<gfx::NativeViewAccessible> grandchildren = | |
| 52 GetUnignoredA11yChildren(child); | |
| 53 children.insert(children.end(), grandchildren.begin(), | |
| 54 grandchildren.end()); | |
| 55 } else { | |
| 56 children.push_back(child->GetNativeViewAccessible()); | |
| 57 } | |
| 58 } | |
| 59 return children; | |
| 60 } | |
| 61 | |
| 62 } // namespace | |
| 63 | |
| 17 // static | 64 // static | 
| 18 NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { | 65 std::unique_ptr<NativeViewAccessibility> NativeViewAccessibility::CreateForView( | 
| 19 return new NativeViewAccessibility(view); | 66 View* view) { | 
| 67 return base::WrapUnique(Create(view)); | |
| 20 } | 68 } | 
| 21 #endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL) | 69 | 
| 70 // static | |
| 71 NativeViewAccessibility* NativeViewAccessibility::GetForView(View* view) { | |
| 72 // Retrieving the gfx::NativeViewAccessible also ensures that a | |
| 73 // NativeViewAccessibility exists for the given View. | |
| 74 gfx::NativeViewAccessible native_object = view->GetNativeViewAccessible(); | |
| 75 ui::AXPlatformNode* node = | |
| 76 ui::AXPlatformNode::FromNativeViewAccessible(native_object); | |
| 77 DCHECK(node); | |
| 78 return static_cast<NativeViewAccessibility*>(node->GetDelegate()); | |
| 79 } | |
| 22 | 80 | 
| 23 NativeViewAccessibility::NativeViewAccessibility(View* view) | 81 NativeViewAccessibility::NativeViewAccessibility(View* view) | 
| 24 : view_(view), | 82 : view_(view), | 
| 25 parent_widget_(nullptr), | 83 parent_widget_(nullptr), | 
| 26 ax_node_(nullptr) { | 84 ax_node_(nullptr) { | 
| 27 ax_node_ = ui::AXPlatformNode::Create(this); | 85 ax_node_ = ui::AXPlatformNode::Create(this); | 
| 28 } | 86 } | 
| 29 | 87 | 
| 30 NativeViewAccessibility::~NativeViewAccessibility() { | 88 NativeViewAccessibility::~NativeViewAccessibility() { | 
| 31 if (ax_node_) | 89 if (ax_node_) | 
| 32 ax_node_->Destroy(); | 90 ax_node_->Destroy(); | 
| 33 if (parent_widget_) | 91 if (parent_widget_) | 
| 34 parent_widget_->RemoveObserver(this); | 92 parent_widget_->RemoveObserver(this); | 
| 35 } | 93 } | 
| 36 | 94 | 
| 37 gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() { | 95 gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() { | 
| 38 return ax_node_ ? ax_node_->GetNativeViewAccessible() : nullptr; | 96 return ax_node_ ? ax_node_->GetNativeViewAccessible() : nullptr; | 
| 39 } | 97 } | 
| 40 | 98 | 
| 41 void NativeViewAccessibility::Destroy() { | |
| 42 delete this; | |
| 43 } | |
| 44 | |
| 45 void NativeViewAccessibility::NotifyAccessibilityEvent(ui::AXEvent event_type) { | 99 void NativeViewAccessibility::NotifyAccessibilityEvent(ui::AXEvent event_type) { | 
| 46 if (ax_node_) | 100 if (ax_node_) | 
| 47 ax_node_->NotifyAccessibilityEvent(event_type); | 101 ax_node_->NotifyAccessibilityEvent(event_type); | 
| 48 } | 102 } | 
| 49 | 103 | 
| 50 bool NativeViewAccessibility::SetFocused(bool focused) { | 104 bool NativeViewAccessibility::SetFocused(bool focused) { | 
| 51 if (!ui::AXNodeData::IsFlagSet(GetData().state, ui::AX_STATE_FOCUSABLE)) | 105 if (!ui::AXNodeData::IsFlagSet(GetData().state, ui::AX_STATE_FOCUSABLE)) | 
| 52 return false; | 106 return false; | 
| 53 | 107 | 
| 54 if (focused) | 108 if (focused) | 
| (...skipping 18 matching lines...) Expand all Loading... | |
| 73 } | 127 } | 
| 74 | 128 | 
| 75 view_->GetAccessibleNodeData(&data_); | 129 view_->GetAccessibleNodeData(&data_); | 
| 76 data_.location = gfx::RectF(view_->GetBoundsInScreen()); | 130 data_.location = gfx::RectF(view_->GetBoundsInScreen()); | 
| 77 base::string16 description; | 131 base::string16 description; | 
| 78 view_->GetTooltipText(gfx::Point(), &description); | 132 view_->GetTooltipText(gfx::Point(), &description); | 
| 79 data_.AddStringAttribute(ui::AX_ATTR_DESCRIPTION, | 133 data_.AddStringAttribute(ui::AX_ATTR_DESCRIPTION, | 
| 80 base::UTF16ToUTF8(description)); | 134 base::UTF16ToUTF8(description)); | 
| 81 | 135 | 
| 82 if (view_->IsAccessibilityFocusable()) | 136 if (view_->IsAccessibilityFocusable()) | 
| 83 data_.state |= (1 << ui::AX_STATE_FOCUSABLE); | 137 data_.AddStateFlag(ui::AX_STATE_FOCUSABLE); | 
| 84 | 138 | 
| 85 if (!view_->enabled()) | 139 if (!view_->enabled()) | 
| 86 data_.state |= (1 << ui::AX_STATE_DISABLED); | 140 data_.AddStateFlag(ui::AX_STATE_DISABLED); | 
| 87 | 141 | 
| 88 if (!view_->IsDrawn()) | 142 if (!view_->IsDrawn()) | 
| 89 data_.state |= (1 << ui::AX_STATE_INVISIBLE); | 143 data_.AddStateFlag(ui::AX_STATE_INVISIBLE); | 
| 144 | |
| 145 // Make sure this element is excluded from the a11y tree if there's a | |
| 146 // focusable parent. All keyboard focusable elements should be leaf nodes. | |
| 147 // Exceptions to this rule will themselves be accessibility focusable. | |
| 148 if (IsViewUnfocusableChildOfFocusableAncestor(view_)) | |
| 149 data_.role = ui::AX_ROLE_IGNORED; | |
| 90 | 150 | 
| 91 return data_; | 151 return data_; | 
| 92 } | 152 } | 
| 93 | 153 | 
| 94 int NativeViewAccessibility::GetChildCount() { | 154 int NativeViewAccessibility::GetChildCount() { | 
| 95 int child_count = view_->child_count(); | 155 int child_count = GetUnignoredA11yChildren(view_).size(); | 
| 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 std::vector<gfx::NativeViewAccessible> a11y_children = | 
| 166 GetUnignoredA11yChildren(view_); | |
| 167 int total_child_count = a11y_children.size(); | |
| 168 | |
| 169 if (index >= total_child_count) | |
| 170 return nullptr; | |
| 171 | |
| 172 // Include the child widgets that may be present if this is a RootView. | |
| 106 std::vector<Widget*> child_widgets; | 173 std::vector<Widget*> child_widgets; | 
| 107 PopulateChildWidgetVector(&child_widgets); | 174 PopulateChildWidgetVector(&child_widgets); | 
| 108 int child_widget_count = static_cast<int>(child_widgets.size()); | |
| 109 | 175 | 
| 110 if (index < view_->child_count()) { | 176 int child_view_count = total_child_count - child_widgets.size(); | 
| 111 return view_->child_at(index)->GetNativeViewAccessible(); | 177 if (index < child_view_count) { | 
| 112 } else if (index < view_->child_count() + child_widget_count) { | 178 return a11y_children[index]; | 
| 113 Widget* child_widget = child_widgets[index - view_->child_count()]; | |
| 114 return child_widget->GetRootView()->GetNativeViewAccessible(); | |
| 115 } | 179 } | 
| 116 | 180 | 
| 117 return nullptr; | 181 Widget* child_widget = child_widgets[index - child_view_count]; | 
| 182 return child_widget->GetRootView()->GetNativeViewAccessible(); | |
| 118 } | 183 } | 
| 119 | 184 | 
| 120 gfx::NativeWindow NativeViewAccessibility::GetTopLevelWidget() { | 185 gfx::NativeWindow NativeViewAccessibility::GetTopLevelWidget() { | 
| 121 if (view_->GetWidget()) | 186 if (view_->GetWidget()) | 
| 122 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); | 187 return view_->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); | 
| 123 return nullptr; | 188 return nullptr; | 
| 124 } | 189 } | 
| 125 | 190 | 
| 126 gfx::NativeViewAccessible NativeViewAccessibility::GetParent() { | 191 gfx::NativeViewAccessible NativeViewAccessibility::GetParent() { | 
| 
tapted
2017/02/15 02:10:45
yeah NativeViewAccessible is `typedef void*` on Ch
 
Patti Lor
2017/02/21 03:29:17
Yep - so I ended up just returning a new instance
 | |
| 127 if (view_->parent()) | 192 View* parent_view = view_->parent(); | 
| 128 return view_->parent()->GetNativeViewAccessible(); | 193 if (parent_view) { | 
| 194 if (IsViewA11yIgnored(parent_view)) | |
| 195 return GetForView(parent_view)->GetParent(); | |
| 196 return parent_view->GetNativeViewAccessible(); | |
| 197 } | |
| 129 | 198 | 
| 130 // TODO: move this to NativeViewAccessibilityMac. | 199 // TODO: move this to NativeViewAccessibilityMac. | 
| 131 #if defined(OS_MACOSX) | 200 #if defined(OS_MACOSX) | 
| 132 if (view_->GetWidget()) | 201 if (view_->GetWidget()) | 
| 133 return view_->GetWidget()->GetNativeView(); | 202 return (gfx::NativeViewAccessible)view_->GetWidget()->GetNativeView(); | 
| 
tapted
2017/02/15 02:10:45
can the c-style cast be removed?
 
Patti Lor
2017/02/21 03:29:17
Yep! Originally, I tried a static_cast instead, bu
 | |
| 134 #endif | 203 #endif | 
| 135 | 204 | 
| 136 if (parent_widget_) | 205 if (parent_widget_) | 
| 137 return parent_widget_->GetRootView()->GetNativeViewAccessible(); | 206 return parent_widget_->GetRootView()->GetNativeViewAccessible(); | 
| 138 | 207 | 
| 139 return nullptr; | 208 return nullptr; | 
| 140 } | 209 } | 
| 141 | 210 | 
| 142 gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() { | 211 gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() { | 
| 143 return gfx::Vector2d(0, 0); // location is already in screen coordinates. | 212 return gfx::Vector2d(0, 0); // Location is already in screen coordinates. | 
| 144 } | 213 } | 
| 145 | 214 | 
| 146 gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) { | 215 gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) { | 
| 147 if (!view_ || !view_->GetWidget()) | 216 if (!view_ || !view_->GetWidget()) | 
| 148 return nullptr; | 217 return nullptr; | 
| 149 | 218 | 
| 150 // Search child widgets first, since they're on top in the z-order. | 219 // Search child widgets first, since they're on top in the z-order. | 
| 151 std::vector<Widget*> child_widgets; | 220 std::vector<Widget*> child_widgets; | 
| 152 PopulateChildWidgetVector(&child_widgets); | 221 PopulateChildWidgetVector(&child_widgets); | 
| 153 for (Widget* child_widget : child_widgets) { | 222 for (Widget* child_widget : child_widgets) { | 
| 154 View* child_root_view = child_widget->GetRootView(); | 223 View* child_root_view = child_widget->GetRootView(); | 
| 155 gfx::Point point(x, y); | 224 gfx::Point point(x, y); | 
| 156 View::ConvertPointFromScreen(child_root_view, &point); | 225 View::ConvertPointFromScreen(child_root_view, &point); | 
| 157 if (child_root_view->HitTestPoint(point)) | 226 if (child_root_view->HitTestPoint(point)) | 
| 158 return child_root_view->GetNativeViewAccessible(); | 227 return child_root_view->GetNativeViewAccessible(); | 
| 159 } | 228 } | 
| 160 | 229 | 
| 230 if (GetChildCount() == 0) { | |
| 231 if (IsViewA11yIgnored(view_)) | |
| 232 return nullptr; | |
| 233 return GetNativeObject(); | |
| 234 } | |
| 235 | |
| 161 gfx::Point point(x, y); | 236 gfx::Point point(x, y); | 
| 162 View::ConvertPointFromScreen(view_, &point); | 237 View::ConvertPointFromScreen(view_, &point); | 
| 163 if (!view_->HitTestPoint(point)) | 238 if (!view_->HitTestPoint(point)) | 
| 164 return nullptr; | 239 return nullptr; | 
| 165 | 240 | 
| 166 // Check if the point is within any of the immediate children of this | 241 // 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 | 242 // 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. | 243 // hit test if we return anything other than GetNativeObject() or nullptr. | 
| 169 for (int i = view_->child_count() - 1; i >= 0; --i) { | 244 for (int i = view_->child_count() - 1; i >= 0; --i) { | 
| 170 View* child_view = view_->child_at(i); | 245 View* child_view = view_->child_at(i); | 
| 171 if (!child_view->visible()) | 246 if (!child_view->visible()) | 
| 172 continue; | 247 continue; | 
| 173 | 248 | 
| 174 gfx::Point point_in_child_coords(point); | 249 gfx::Point point_in_child_coords(point); | 
| 175 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); | 250 view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords); | 
| 176 if (child_view->HitTestPoint(point_in_child_coords)) | 251 if (child_view->HitTestPoint(point_in_child_coords)) | 
| 177 return child_view->GetNativeViewAccessible(); | 252 return child_view->GetNativeViewAccessible(); | 
| 178 } | 253 } | 
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 240 } | 315 } | 
| 241 } | 316 } | 
| 242 | 317 | 
| 243 void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) { | 318 void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) { | 
| 244 if (parent_widget_) | 319 if (parent_widget_) | 
| 245 parent_widget_->RemoveObserver(this); | 320 parent_widget_->RemoveObserver(this); | 
| 246 parent_widget_ = parent_widget; | 321 parent_widget_ = parent_widget; | 
| 247 parent_widget_->AddObserver(this); | 322 parent_widget_->AddObserver(this); | 
| 248 } | 323 } | 
| 249 | 324 | 
| 325 #if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL) | |
| 326 // static | |
| 327 NativeViewAccessibility* NativeViewAccessibility::Create(View* view) { | |
| 328 return new NativeViewAccessibility(view); | |
| 329 } | |
| 330 #endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL) | |
| 331 | |
| 250 void NativeViewAccessibility::PopulateChildWidgetVector( | 332 void NativeViewAccessibility::PopulateChildWidgetVector( | 
| 251 std::vector<Widget*>* result_child_widgets) { | 333 std::vector<Widget*>* result_child_widgets) { | 
| 252 // Only attach child widgets to the root view. | 334 // Only attach child widgets to the root view. | 
| 253 Widget* widget = view_->GetWidget(); | 335 Widget* widget = view_->GetWidget(); | 
| 254 if (!widget || widget->GetRootView() != view_) | 336 if (!widget || widget->GetRootView() != view_) | 
| 255 return; | 337 return; | 
| 256 | 338 | 
| 257 std::set<Widget*> child_widgets; | 339 std::set<Widget*> child_widgets; | 
| 258 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); | 340 Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); | 
| 259 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { | 341 for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { | 
| (...skipping 16 matching lines...) Expand all Loading... | |
| 276 child_widget_platform_node->GetDelegate()); | 358 child_widget_platform_node->GetDelegate()); | 
| 277 if (child_widget_view_accessibility->parent_widget() != widget) | 359 if (child_widget_view_accessibility->parent_widget() != widget) | 
| 278 child_widget_view_accessibility->SetParentWidget(widget); | 360 child_widget_view_accessibility->SetParentWidget(widget); | 
| 279 } | 361 } | 
| 280 | 362 | 
| 281 result_child_widgets->push_back(child_widget); | 363 result_child_widgets->push_back(child_widget); | 
| 282 } | 364 } | 
| 283 } | 365 } | 
| 284 | 366 | 
| 285 } // namespace views | 367 } // namespace views | 
| OLD | NEW |