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