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

Unified 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 4 years 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 side-by-side diff with in-line comments
Download patch
Index: ui/views/accessibility/native_view_accessibility.cc
diff --git a/ui/views/accessibility/native_view_accessibility.cc b/ui/views/accessibility/native_view_accessibility.cc
index 7c960348478b020d7b0bfeed5adf0f50f45e5d70..9ca21840db8c6abfc22b9a660acabff361951fed 100644
--- a/ui/views/accessibility/native_view_accessibility.cc
+++ b/ui/views/accessibility/native_view_accessibility.cc
@@ -13,12 +13,47 @@
namespace views {
-#if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
+namespace {
+
+typedef std::map<View*, NativeViewAccessibility*> NativeViewAccessibilityMap;
+
+bool IsAccessibilityFocusableWhenEnabled(View* view) {
+ return view->focus_behavior() != View::FocusBehavior::NEVER &&
+ view->IsDrawn();
+}
+
+// Used to determine if a View should be ignored by accessibility clients by
+// being a non-keyboard-focusable child of a keyboard-focusable ancestor.
+bool IsViewUnfocusableChildOfFocusableAncestor(View* view) {
+ if (IsAccessibilityFocusableWhenEnabled(view))
+ return false;
+
+ while (view->parent()) {
+ view = view->parent();
+ if (IsAccessibilityFocusableWhenEnabled(view))
+ return true;
+ }
+ return false;
+}
+
+// Convenience method for checking if a View should be ignored by a11y.
+bool IsViewA11yIgnored(View* view) {
+ return NativeViewAccessibility::GetOrCreate(view)->GetData().role ==
+ ui::AX_ROLE_IGNORED;
+}
+
+} // namespace
+
// static
-NativeViewAccessibility* NativeViewAccessibility::Create(View* view) {
- return new NativeViewAccessibility(view);
+NativeViewAccessibility* NativeViewAccessibility::GetOrCreate(View* view) {
+ NativeViewAccessibilityMap* views_to_nva = GetNativeViewAccessibilityMap();
+ NativeViewAccessibilityMap::iterator it = views_to_nva->find(view);
+ if (it != views_to_nva->end())
+ return (*it).second;
+ NativeViewAccessibility* nva = CreateNewNativeViewAccessibilityImpl(view);
+ views_to_nva->insert(NativeViewAccessibilityMap::value_type(view, nva));
+ return nva;
}
-#endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
NativeViewAccessibility::NativeViewAccessibility(View* view)
: view_(view),
@@ -32,6 +67,16 @@ NativeViewAccessibility::~NativeViewAccessibility() {
ax_node_->Destroy();
if (parent_widget_)
parent_widget_->RemoveObserver(this);
+
+ // 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.
+ 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!
+ NativeViewAccessibilityMap::iterator it = views_to_nva->find(view_);
+ if (it != views_to_nva->end()) {
+ views_to_nva->erase(it);
+ } else {
+ // 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
+ // instance was not created via GetOrCreate().
+ }
}
gfx::NativeViewAccessible NativeViewAccessibility::GetNativeObject() {
@@ -80,19 +125,34 @@ const ui::AXNodeData& NativeViewAccessibility::GetData() {
base::UTF16ToUTF8(description));
if (view_->IsAccessibilityFocusable())
- data_.state |= (1 << ui::AX_STATE_FOCUSABLE);
+ data_.AddStateFlag(ui::AX_STATE_FOCUSABLE);
if (!view_->enabled())
- data_.state |= (1 << ui::AX_STATE_DISABLED);
+ data_.AddStateFlag(ui::AX_STATE_DISABLED);
if (!view_->visible())
- data_.state |= (1 << ui::AX_STATE_INVISIBLE);
+ data_.AddStateFlag(ui::AX_STATE_INVISIBLE);
+
+ // Make sure this element is excluded from the a11y tree if there's a
+ // focusable parent. All keyboard focusable elements should be leaf nodes.
+ // Exceptions to this rule will themselves be accessibility focusable.
+ if (IsViewUnfocusableChildOfFocusableAncestor(view_))
+ data_.role = ui::AX_ROLE_IGNORED;
return data_;
}
int NativeViewAccessibility::GetChildCount() {
- int child_count = view_->child_count();
+ int child_count = 0;
+ for (int i = 0; i < view_->child_count(); ++i) {
+ View* child = view_->child_at(i);
+ if (IsViewA11yIgnored(child)) {
+ // If not visible to accessibility clients, use its child count instead.
+ child_count += GetOrCreate(child)->GetChildCount();
+ } else {
+ ++child_count;
+ }
+ }
std::vector<Widget*> child_widgets;
PopulateChildWidgetVector(&child_widgets);
@@ -102,14 +162,30 @@ int NativeViewAccessibility::GetChildCount() {
}
gfx::NativeViewAccessible NativeViewAccessibility::ChildAtIndex(int index) {
- // If this is a root view, our widget might have child widgets. Include
+ if (GetChildCount() == 0)
+ return nullptr;
+
+ // Include the child widgets that may be present if this is a RootView.
std::vector<Widget*> child_widgets;
PopulateChildWidgetVector(&child_widgets);
int child_widget_count = static_cast<int>(child_widgets.size());
- if (index < view_->child_count()) {
- return view_->child_at(index)->GetNativeViewAccessible();
- } else if (index < view_->child_count() + child_widget_count) {
+ int ax_child_count = GetChildCount();
+ 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
+ int curr_child_count = 0;
+ for (int i = 0; i < view_->child_count(); ++i) {
+ View* child = view_->child_at(i);
+ bool ignored = IsViewA11yIgnored(child);
+ int pending_children = ignored ? GetOrCreate(child)->GetChildCount() : 1;
+ if (index < curr_child_count + pending_children) {
+ if (ignored) {
+ return GetOrCreate(child)->ChildAtIndex(index - curr_child_count);
+ }
+ return child->GetNativeViewAccessible();
+ }
+ curr_child_count += pending_children;
+ }
+ } else if (index < ax_child_count) {
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.
return child_widget->GetRootView()->GetNativeViewAccessible();
}
@@ -124,8 +200,13 @@ gfx::NativeWindow NativeViewAccessibility::GetTopLevelWidget() {
}
gfx::NativeViewAccessible NativeViewAccessibility::GetParent() {
- if (view_->parent())
- return view_->parent()->GetNativeViewAccessible();
+ View* parent_view = view_->parent();
+ if (parent_view) {
+ if (IsViewA11yIgnored(parent_view))
+ return GetOrCreate(parent_view)->GetParent();
+ 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
+ return parent_view->GetNativeViewAccessible();
+ }
// TODO: move this to NativeViewAccessibilityMac.
#if defined(OS_MACOSX)
@@ -140,7 +221,7 @@ gfx::NativeViewAccessible NativeViewAccessibility::GetParent() {
}
gfx::Vector2d NativeViewAccessibility::GetGlobalCoordinateOffset() {
- return gfx::Vector2d(0, 0); // location is already in screen coordinates.
+ return gfx::Vector2d(0, 0); // Location is already in screen coordinates.
}
gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) {
@@ -158,14 +239,20 @@ gfx::NativeViewAccessible NativeViewAccessibility::HitTestSync(int x, int y) {
return child_root_view->GetNativeViewAccessible();
}
+ if (GetChildCount() == 0) {
+ if (IsViewA11yIgnored(view_))
+ 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.
+ return GetNativeObject();
+ }
+
gfx::Point point(x, y);
View::ConvertPointFromScreen(view_, &point);
if (!view_->HitTestPoint(point))
return nullptr;
- // Check if the point is within any of the immediate children of this
- // view. We don't have to search further because AXPlatformNode will
- // do a recursive hit test if we return anything other than |this| or NULL.
+ // Check if the point is within any of the immediate children of this view. We
+ // don't have to search further because AXPlatformNodeWin will do a recursive
+ // hit test if we return anything other than GetNativeObject() or nullptr.
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
View* child_view = view_->child_at(i);
if (!child_view->visible())
@@ -247,6 +334,21 @@ void NativeViewAccessibility::SetParentWidget(Widget* parent_widget) {
parent_widget_->AddObserver(this);
}
+// static
+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.
+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
+ CR_DEFINE_STATIC_LOCAL(NativeViewAccessibilityMap, views_to_nva, ());
+ return &views_to_nva;
+}
+
+#if !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
+// static
+NativeViewAccessibility*
+NativeViewAccessibility::CreateNewNativeViewAccessibilityImpl(View* view) {
+ return new NativeViewAccessibility(view);
+}
+#endif // !defined(PLATFORM_HAS_NATIVE_VIEW_ACCESSIBILITY_IMPL)
+
void NativeViewAccessibility::PopulateChildWidgetVector(
std::vector<Widget*>* result_child_widgets) {
// Only attach child widgets to the root view.

Powered by Google App Engine
This is Rietveld 408576698