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

Unified 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: Rebase. 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 side-by-side diff with in-line comments
Download patch
Index: ui/views/accessibility/native_view_accessibility_base.cc
diff --git a/ui/views/accessibility/native_view_accessibility_base.cc b/ui/views/accessibility/native_view_accessibility_base.cc
index 2ed7e26fafaed75d104c732a70d5a6ee1dc336f5..94fd40c5741ccec8c8a5a4f534e4eca3e3a29fc7 100644
--- a/ui/views/accessibility/native_view_accessibility_base.cc
+++ b/ui/views/accessibility/native_view_accessibility_base.cc
@@ -14,6 +14,58 @@
namespace views {
+namespace {
+
+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. E.g.,
+// LabelButtons contain Labels, but a11y should just show that there's a button.
+bool IsViewUnfocusableChildOfFocusableAncestor(View* view) {
+ if (IsAccessibilityFocusableWhenEnabled(view))
+ return false;
+
+ while (view->parent()) {
+ view = view->parent();
+ if (IsAccessibilityFocusableWhenEnabled(view))
+ return true;
+ }
+ return false;
+}
+
+// Recursively gets a list of unignored gfx::NativeViewAccessible children of a
+// given View. If a child is ignored, its children will be used instead.
+std::vector<gfx::NativeViewAccessible> GetUnignoredA11yChildren(View* view) {
+ std::vector<gfx::NativeViewAccessible> children;
+ for (int i = 0; i < view->child_count(); ++i) {
+ View* child = view->child_at(i);
+ if (NativeViewAccessibilityBase::IsIgnoredView(child)) {
+ std::vector<gfx::NativeViewAccessible> grandchildren =
+ GetUnignoredA11yChildren(child);
+ children.insert(children.end(), grandchildren.begin(),
+ grandchildren.end());
+ } else {
+ children.push_back(child->GetNativeViewAccessible());
+ }
+ }
+ return children;
+}
+
+} // namespace
+
+// static
+bool NativeViewAccessibilityBase::IsIgnoredView(View* view) {
+ NativeViewAccessibilityBase* nva = GetForView(view);
+ if (nva)
+ return nva->GetData().role == ui::AX_ROLE_IGNORED;
+ // If the native accessibility object isn't backed by NativeViewAccessibility
+ // (e.g. when the View wraps a native object), assume it shouldn't be ignored.
+ return false;
+}
+
NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view)
: view_(view),
parent_widget_(nullptr),
@@ -66,11 +118,16 @@ const ui::AXNodeData& NativeViewAccessibilityBase::GetData() const {
if (!view_->IsDrawn())
data_.state |= (1 << 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 NativeViewAccessibilityBase::GetChildCount() {
- int child_count = view_->child_count();
+ int child_count = GetUnignoredA11yChildren(view_).size();
std::vector<Widget*> child_widgets;
PopulateChildWidgetVector(&child_widgets);
@@ -80,19 +137,19 @@ int NativeViewAccessibilityBase::GetChildCount() {
}
gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) {
- // If this is a root view, our widget might have child widgets. Include
+ std::vector<gfx::NativeViewAccessible> a11y_children =
+ GetUnignoredA11yChildren(view_);
+ // 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) {
- Widget* child_widget = child_widgets[index - view_->child_count()];
- return child_widget->GetRootView()->GetNativeViewAccessible();
- }
+ if (index >= static_cast<int>(a11y_children.size() + child_widgets.size()))
+ return nullptr;
+ if (index < static_cast<int>(a11y_children.size()))
+ return a11y_children[index];
- return nullptr;
+ Widget* child_widget = child_widgets[index - a11y_children.size()];
+ return child_widget->GetRootView()->GetNativeViewAccessible();
}
gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() {
@@ -102,13 +159,11 @@ gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() {
}
gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() {
- if (view_->parent())
- return view_->parent()->GetNativeViewAccessible();
-
- if (parent_widget_)
- return parent_widget_->GetRootView()->GetNativeViewAccessible();
-
- return nullptr;
+ View* parent_view = view_->parent();
+ while (parent_view && IsIgnoredView(parent_view))
+ parent_view = parent_view->parent();
+ return parent_view ? parent_view->GetNativeViewAccessible()
+ : GetNativeViewAccessibleForWidget();
}
gfx::Rect NativeViewAccessibilityBase::GetScreenBoundsRect() const {
@@ -131,23 +186,36 @@ gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x,
return child_root_view->GetNativeViewAccessible();
}
+ // Check if the given point is inside |view_|.
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.
+ // Early return if this node is a leaf, depending on whether it's ignored.
+ if (GetChildCount() == 0)
+ return IsIgnoredView(view_) ? nullptr : GetNativeObject();
+
+ // 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.
+ // The exception to this is if the recursion needs to travel up the tree,
+ // which may return a non-direct ancestor of the given node.
for (int i = view_->child_count() - 1; i >= 0; --i) {
View* child_view = view_->child_at(i);
- if (!child_view->visible())
- continue;
-
- gfx::Point point_in_child_coords(point);
- view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords);
- if (child_view->HitTestPoint(point_in_child_coords))
- return child_view->GetNativeViewAccessible();
+ NativeViewAccessibilityBase* child_nva = GetForView(child_view);
+ if (child_nva) {
+ gfx::Point point_in_child_coords(point);
+ view_->ConvertPointToTarget(view_, child_view, &point_in_child_coords);
+ if (!child_nva->GetData().HasStateFlag(ui::AX_STATE_INVISIBLE) &&
+ child_view->HitTestPoint(point_in_child_coords)) {
+ if (child_nva->GetChildCount() == 0 && IsIgnoredView(child_view)) {
+ // Multiple levels might be jumped here.
+ return child_nva->GetParent();
+ }
+ return child_nva->GetNativeObject();
+ }
+ }
}
// If it's not inside any of our children, it's inside this view.
@@ -185,6 +253,28 @@ void NativeViewAccessibilityBase::SetParentWidget(Widget* parent_widget) {
parent_widget_->AddObserver(this);
}
+// static
+NativeViewAccessibilityBase* NativeViewAccessibilityBase::GetForView(
+ View* view) {
+ // Retrieving the gfx::NativeViewAccessible also ensures that a
+ // NativeViewAccessibility exists for the given View.
+ gfx::NativeViewAccessible native_object = view->GetNativeViewAccessible();
+ ui::AXPlatformNode* node =
+ ui::AXPlatformNode::FromNativeViewAccessible(native_object);
+ // WebViews with web contents have their own NativeViewAccessible
+ // implementation, so |node| will be null if |view| is a WebView.
+ if (node)
+ return static_cast<NativeViewAccessibilityBase*>(node->GetDelegate());
+ return nullptr;
+}
+
+gfx::NativeViewAccessible
+NativeViewAccessibilityBase::GetNativeViewAccessibleForWidget() {
+ if (parent_widget_)
+ return parent_widget_->GetRootView()->GetNativeViewAccessible();
+ return nullptr;
+}
+
gfx::RectF NativeViewAccessibilityBase::GetBoundsInScreen() const {
return gfx::RectF(view_->GetBoundsInScreen());
}
@@ -210,17 +300,11 @@ void NativeViewAccessibilityBase::PopulateChildWidgetVector(
if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey))
continue;
- gfx::NativeViewAccessible child_widget_accessible =
- child_widget->GetRootView()->GetNativeViewAccessible();
- ui::AXPlatformNode* child_widget_platform_node =
- ui::AXPlatformNode::FromNativeViewAccessible(child_widget_accessible);
- if (child_widget_platform_node) {
- NativeViewAccessibilityBase* child_widget_view_accessibility =
- static_cast<NativeViewAccessibilityBase*>(
- child_widget_platform_node->GetDelegate());
- if (child_widget_view_accessibility->parent_widget() != widget)
- child_widget_view_accessibility->SetParentWidget(widget);
- }
+ NativeViewAccessibilityBase* child_widget_view_accessibility =
+ GetForView(child_widget->GetRootView());
+ if (child_widget_view_accessibility &&
+ child_widget_view_accessibility->parent_widget() != widget)
+ child_widget_view_accessibility->SetParentWidget(widget);
result_child_widgets->push_back(child_widget);
}
« no previous file with comments | « ui/views/accessibility/native_view_accessibility_base.h ('k') | ui/views/accessibility/native_view_accessibility_mac.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698