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); |
} |