Commit 9547d4ed authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Remove ignored views from the accessibility tree visible to platform APIs

This patch is the first, (not user visible), fix for a bug with the reading of lines in NVDA.
When the screen reader focus is in the accessibility tree for Views, such as in a context menu, a toolbar
or the main application's menu, the "Read Line" command (NVDAKey+Up) doesn't always read the currently focused item.

We were exposing ignored views in the accessibility tree that was exposed to platform APIs.
As a result, platform specific code under ui/accessibility was building IA2 and ATK hypertext for ignored nodes,
while text navigation code in the same component was trying to locate non-existent line boundaries in that hypertext.

A followup patch will ensure that the names of Views that come from their contents
are clearly marked as such.

R=dmazzoni@chromium.org, aleventhal@chromium.org

AX-Relnotes: n/a.

Change-Id: I5fdae5714b10b10db3dc5b5dcf22d118f322e9e7
Bug: 1098528
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435645
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#828873}
parent 35df29db
...@@ -256,32 +256,34 @@ int AXVirtualView::GetChildCount() const { ...@@ -256,32 +256,34 @@ int AXVirtualView::GetChildCount() const {
for (const std::unique_ptr<AXVirtualView>& child : children_) { for (const std::unique_ptr<AXVirtualView>& child : children_) {
if (child->IsIgnored()) { if (child->IsIgnored()) {
count += child->GetChildCount(); count += child->GetChildCount();
continue; } else {
++count;
} }
count++;
} }
return count; return count;
} }
gfx::NativeViewAccessible AXVirtualView::ChildAtIndex(int index) { gfx::NativeViewAccessible AXVirtualView::ChildAtIndex(int index) {
DCHECK_GE(index, 0) << "Child indices should be greater or equal to 0."; DCHECK_GE(index, 0) << "|index| should be greater or equal to 0.";
DCHECK_LT(index, GetChildCount()) DCHECK_LT(index, GetChildCount())
<< "Child indices should be less than the child count."; << "|index| should be less than the child count.";
int i = 0;
for (const std::unique_ptr<AXVirtualView>& child : children_) { for (const std::unique_ptr<AXVirtualView>& child : children_) {
if (child->IsIgnored()) { if (child->IsIgnored()) {
if (index - i < child->GetChildCount()) { int child_count = child->GetChildCount();
gfx::NativeViewAccessible result = child->ChildAtIndex(index - i); if (index < child_count)
if (result) return child->ChildAtIndex(index);
return result; index -= child_count;
} } else {
i += child->GetChildCount(); if (index == 0)
continue;
}
if (i == index)
return child->GetNativeObject(); return child->GetNativeObject();
i++; --index;
} }
DCHECK_GE(index, 0) << "|index| should be less than the child count.";
}
NOTREACHED() << "|index| should be less than the child count.";
return nullptr; return nullptr;
} }
...@@ -297,8 +299,11 @@ gfx::NativeViewAccessible AXVirtualView::GetNativeViewAccessible() { ...@@ -297,8 +299,11 @@ gfx::NativeViewAccessible AXVirtualView::GetNativeViewAccessible() {
} }
gfx::NativeViewAccessible AXVirtualView::GetParent() { gfx::NativeViewAccessible AXVirtualView::GetParent() {
if (parent_view_) if (parent_view_) {
if (!parent_view_->IsIgnored())
return parent_view_->GetNativeObject(); return parent_view_->GetNativeObject();
return GetDelegate()->GetParent();
}
if (virtual_parent_view_) { if (virtual_parent_view_) {
if (virtual_parent_view_->IsIgnored()) if (virtual_parent_view_->IsIgnored())
...@@ -417,8 +422,8 @@ const ui::AXUniqueId& AXVirtualView::GetUniqueId() const { ...@@ -417,8 +422,8 @@ const ui::AXUniqueId& AXVirtualView::GetUniqueId() const {
return unique_id_; return unique_id_;
} }
// Virtual views need to implement this function in order for A11Y events // Virtual views need to implement this function in order for accessibility
// to be routed correctly. // events to be routed correctly.
gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() { gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() {
#if defined(OS_WIN) #if defined(OS_WIN)
if (GetOwnerView()) if (GetOwnerView())
......
...@@ -165,14 +165,19 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase { ...@@ -165,14 +165,19 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// Gets the real View that owns our shallowest virtual ancestor,, if any. // Gets the real View that owns our shallowest virtual ancestor,, if any.
View* GetOwnerView() const; View* GetOwnerView() const;
// Gets the view platform delegate if exists, otherwise nullptr. // Gets the delegate for our owning View; if we are on a platform that exposes
// Views directly to platform APIs instead of serializing them into an AXTree.
// Otherwise, returns nullptr.
ViewAXPlatformNodeDelegate* GetDelegate() const; ViewAXPlatformNodeDelegate* GetDelegate() const;
// Gets or creates a wrapper suitable for use with tree sources. // Gets or creates a wrapper suitable for use with tree sources.
AXVirtualViewWrapper* GetOrCreateWrapper(views::AXAuraObjCache* cache); AXVirtualViewWrapper* GetOrCreateWrapper(views::AXAuraObjCache* cache);
// Returns true if this node is ignored and should be hidden from the // Returns true if this node is ignored and should be hidden from the
// accessibility tree. This does not impact the node's descendants. // accessibility tree. Methods that are used to navigate the accessibility
// tree, such as "ChildAtIndex", "GetParent", and "GetChildCount", among
// others, also skip ignored nodes. This does not impact the node's
// descendants.
bool IsIgnored() const; bool IsIgnored() const;
// Handle a request from assistive technology to perform an action on this // Handle a request from assistive technology to perform an action on this
......
...@@ -97,7 +97,11 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -97,7 +97,11 @@ class VIEWS_EXPORT ViewAccessibility {
Widget* GetNextFocus(); Widget* GetNextFocus();
Widget* GetPreviousFocus(); Widget* GetPreviousFocus();
// Returns the accessibility object that represents the View whose
// accessibility is managed by this instance. This may be an AXPlatformNode or
// it may be a native accessible object implemented by another class.
virtual gfx::NativeViewAccessible GetNativeObject() const; virtual gfx::NativeViewAccessible GetNativeObject() const;
virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type); virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type);
// Causes the screen reader to announce |text|. If the current user is not // Causes the screen reader to announce |text|. If the current user is not
......
...@@ -61,6 +61,9 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility, ...@@ -61,6 +61,9 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
gfx::NativeViewAccessible ChildAtIndex(int index) override; gfx::NativeViewAccessible ChildAtIndex(int index) override;
bool HasModalDialog() const override; bool HasModalDialog() const override;
gfx::NativeViewAccessible GetNSWindow() override; gfx::NativeViewAccessible GetNSWindow() override;
// TODO(nektar): Make "GetNativeViewAccessible" a const method throughout the
// codebase.
gfx::NativeViewAccessible GetNativeViewAccessible() const;
gfx::NativeViewAccessible GetNativeViewAccessible() override; gfx::NativeViewAccessible GetNativeViewAccessible() override;
gfx::NativeViewAccessible GetParent() override; gfx::NativeViewAccessible GetParent() override;
bool IsChildOfLeaf() const override; bool IsChildOfLeaf() const override;
...@@ -104,15 +107,35 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility, ...@@ -104,15 +107,35 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
ui::AXPlatformNode* ax_platform_node() { return ax_platform_node_; } ui::AXPlatformNode* ax_platform_node() { return ax_platform_node_; }
private: private:
struct ChildWidgetsResult final {
ChildWidgetsResult();
ChildWidgetsResult(std::vector<Widget*> child_widgets,
bool is_tab_modal_showing);
ChildWidgetsResult(const ChildWidgetsResult& other);
virtual ~ChildWidgetsResult();
ChildWidgetsResult& operator=(const ChildWidgetsResult& other);
std::vector<Widget*> child_widgets;
// When the focus is within a child widget, |child_widgets| contains only
// that widget. Otherwise, |child_widgets| contains all child widgets.
//
// The former arises when a modal dialog is showing. In order to support the
// "read title (NVDAKey+T)" and "read window (NVDAKey+B)" commands in the
// NVDA screen reader, we need to hide the rest of the UI from the
// accessibility tree for these commands to work properly.
bool is_tab_modal_showing = false;
};
// Uses Views::GetViewsInGroup to find nearby Views in the same group. // Uses Views::GetViewsInGroup to find nearby Views in the same group.
// Searches from the View's parent to include siblings within that group. // Searches from the View's parent to include siblings within that group.
void GetViewsInGroupForSet(std::vector<View*>* views_in_group) const; void GetViewsInGroupForSet(std::vector<View*>* views_in_group) const;
struct ChildWidgetsResult; // If this delegate is attached to the root view, returns all the child
// widgets of this view's owning widget.
ChildWidgetsResult GetChildWidgets() const; ChildWidgetsResult GetChildWidgets() const;
// Gets the real TableView, otherwise nullptr. // Gets the real (non-virtual) TableView, otherwise nullptr.
TableView* GetAncestorTableView() const; TableView* GetAncestorTableView() const;
// We own this, but it is reference-counted on some platforms so we can't use // We own this, but it is reference-counted on some platforms so we can't use
......
...@@ -187,8 +187,9 @@ ViewAXPlatformNodeDelegateAuraLinux::ViewAXPlatformNodeDelegateAuraLinux( ...@@ -187,8 +187,9 @@ ViewAXPlatformNodeDelegateAuraLinux::ViewAXPlatformNodeDelegateAuraLinux(
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateAuraLinux::GetParent() { gfx::NativeViewAccessible ViewAXPlatformNodeDelegateAuraLinux::GetParent() {
if (gfx::NativeViewAccessible parent = if (gfx::NativeViewAccessible parent =
ViewAXPlatformNodeDelegate::GetParent()) ViewAXPlatformNodeDelegate::GetParent()) {
return parent; return parent;
}
Widget* parent_widget = Widget* parent_widget =
GetWidgetOfParentWindowIncludingTransient(view()->GetWidget()); GetWidgetOfParentWindowIncludingTransient(view()->GetWidget());
......
...@@ -37,7 +37,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetNSWindow() { ...@@ -37,7 +37,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetNSWindow() {
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetParent() { gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetParent() {
if (view()->parent()) if (view()->parent())
return view()->parent()->GetNativeViewAccessible(); return ViewAXPlatformNodeDelegate::GetParent();
auto* widget = view()->GetWidget(); auto* widget = view()->GetWidget();
if (!widget) if (!widget)
......
...@@ -423,35 +423,139 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) { ...@@ -423,35 +423,139 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
EXPECT_EQ(view_accessibility(group_ids[4])->GetPosInSet(), 1); EXPECT_EQ(view_accessibility(group_ids[4])->GetPosInSet(), 1);
} }
TEST_F(ViewAXPlatformNodeDelegateTest, Navigation) { TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigation) {
View::Views view_ids = SetUpExtraViews(); // Adds one extra parent view with four child views to our widget. The parent
// view is added as the next sibling of the already present button view.
//
// Widget
// ++Button
// ++++Label
// 0 = ++ParentView
// 1 = ++++ChildView1
// 2 = ++++ChildView2
// 3 = ChildView3
// 4 = ChildView4
View::Views extra_views = SetUpExtraViews();
ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]);
ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]);
ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]);
ViewAXPlatformNodeDelegate* child_view_3 = view_accessibility(extra_views[3]);
ViewAXPlatformNodeDelegate* child_view_4 = view_accessibility(extra_views[4]);
EXPECT_EQ(view_accessibility(widget_->GetContentsView())->GetNativeObject(),
parent_view->GetParent());
EXPECT_EQ(4, parent_view->GetChildCount());
EXPECT_EQ(2, button_accessibility()->GetIndexInParent());
EXPECT_EQ(3, parent_view->GetIndexInParent());
EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0));
EXPECT_EQ(child_view_2->GetNativeObject(), parent_view->ChildAtIndex(1));
EXPECT_EQ(child_view_3->GetNativeObject(), parent_view->ChildAtIndex(2));
EXPECT_EQ(child_view_4->GetNativeObject(), parent_view->ChildAtIndex(3));
EXPECT_EQ(nullptr, parent_view->GetNextSibling());
EXPECT_EQ(button_accessibility()->GetNativeObject(),
parent_view->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_1->GetParent());
EXPECT_EQ(0, child_view_1->GetChildCount());
EXPECT_EQ(0, child_view_1->GetIndexInParent());
EXPECT_EQ(child_view_2->GetNativeObject(), child_view_1->GetNextSibling());
EXPECT_EQ(nullptr, child_view_1->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_2->GetParent());
EXPECT_EQ(0, child_view_2->GetChildCount());
EXPECT_EQ(1, child_view_2->GetIndexInParent());
EXPECT_EQ(child_view_3->GetNativeObject(), child_view_2->GetNextSibling());
EXPECT_EQ(child_view_1->GetNativeObject(),
child_view_2->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_3->GetParent());
EXPECT_EQ(0, child_view_3->GetChildCount());
EXPECT_EQ(2, child_view_3->GetIndexInParent());
EXPECT_EQ(child_view_4->GetNativeObject(), child_view_3->GetNextSibling());
EXPECT_EQ(child_view_2->GetNativeObject(),
child_view_3->GetPreviousSibling());
EXPECT_EQ(parent_view->GetNativeObject(), child_view_4->GetParent());
EXPECT_EQ(0, child_view_4->GetChildCount());
EXPECT_EQ(3, child_view_4->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_4->GetNextSibling());
EXPECT_EQ(child_view_3->GetNativeObject(),
child_view_4->GetPreviousSibling());
}
EXPECT_EQ(view_accessibility(view_ids[0])->GetNextSibling(), nullptr); TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) {
EXPECT_EQ(view_accessibility(view_ids[0])->GetPreviousSibling(), // Adds one extra parent view with four child views to our widget. The parent
view_accessibility(button_)->GetNativeObject()); // view is added as the next sibling of the already present button view.
EXPECT_EQ(view_accessibility(view_ids[0])->GetIndexInParent(), 3); //
// Widget
EXPECT_EQ(view_accessibility(view_ids[1])->GetNextSibling(), // ++Button
view_accessibility(view_ids[2])->GetNativeObject()); // ++++Label
EXPECT_EQ(view_accessibility(view_ids[1])->GetPreviousSibling(), nullptr); // 0 = ++ParentView
EXPECT_EQ(view_accessibility(view_ids[1])->GetIndexInParent(), 0); // 1 = ++++ChildView1
// 2 = ++++ChildView2
EXPECT_EQ(view_accessibility(view_ids[2])->GetNextSibling(), // 3 = ChildView3
view_accessibility(view_ids[3])->GetNativeObject()); // 4 = ChildView4
EXPECT_EQ(view_accessibility(view_ids[2])->GetPreviousSibling(), View::Views extra_views = SetUpExtraViews();
view_accessibility(view_ids[1])->GetNativeObject()); ViewAXPlatformNodeDelegate* contents_view =
EXPECT_EQ(view_accessibility(view_ids[2])->GetIndexInParent(), 1); view_accessibility(widget_->GetContentsView());
ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]);
EXPECT_EQ(view_accessibility(view_ids[3])->GetNextSibling(), ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]);
view_accessibility(view_ids[4])->GetNativeObject()); ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]);
EXPECT_EQ(view_accessibility(view_ids[3])->GetPreviousSibling(), ViewAXPlatformNodeDelegate* child_view_3 = view_accessibility(extra_views[3]);
view_accessibility(view_ids[2])->GetNativeObject()); ViewAXPlatformNodeDelegate* child_view_4 = view_accessibility(extra_views[4]);
EXPECT_EQ(view_accessibility(view_ids[3])->GetIndexInParent(), 2);
// Mark the parent view and the second child view as ignored.
EXPECT_EQ(view_accessibility(view_ids[4])->GetNextSibling(), nullptr); parent_view->OverrideIsIgnored(true);
EXPECT_EQ(view_accessibility(view_ids[4])->GetPreviousSibling(), child_view_2->OverrideIsIgnored(true);
view_accessibility(view_ids[3])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[4])->GetIndexInParent(), 3); EXPECT_EQ(contents_view->GetNativeObject(), parent_view->GetParent());
EXPECT_EQ(3, parent_view->GetChildCount());
EXPECT_EQ(2, button_accessibility()->GetIndexInParent());
EXPECT_EQ(-1, parent_view->GetIndexInParent());
EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0));
EXPECT_EQ(child_view_3->GetNativeObject(), parent_view->ChildAtIndex(1));
EXPECT_EQ(child_view_4->GetNativeObject(), parent_view->ChildAtIndex(2));
EXPECT_EQ(button_accessibility()->GetNativeObject(),
contents_view->ChildAtIndex(2));
EXPECT_EQ(child_view_1->GetNativeObject(), contents_view->ChildAtIndex(3));
EXPECT_EQ(child_view_3->GetNativeObject(), contents_view->ChildAtIndex(4));
EXPECT_EQ(child_view_4->GetNativeObject(), contents_view->ChildAtIndex(5));
EXPECT_EQ(nullptr, parent_view->GetNextSibling());
EXPECT_EQ(nullptr, parent_view->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_1->GetParent());
EXPECT_EQ(0, child_view_1->GetChildCount());
EXPECT_EQ(3, child_view_1->GetIndexInParent());
EXPECT_EQ(child_view_3->GetNativeObject(), child_view_1->GetNextSibling());
EXPECT_EQ(button_accessibility()->GetNativeObject(),
child_view_1->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_2->GetParent());
EXPECT_EQ(0, child_view_2->GetChildCount());
EXPECT_EQ(-1, child_view_2->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_2->GetNextSibling());
EXPECT_EQ(nullptr, child_view_2->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_3->GetParent());
EXPECT_EQ(0, child_view_3->GetChildCount());
EXPECT_EQ(4, child_view_3->GetIndexInParent());
EXPECT_EQ(child_view_4->GetNativeObject(), child_view_3->GetNextSibling());
EXPECT_EQ(child_view_1->GetNativeObject(),
child_view_3->GetPreviousSibling());
EXPECT_EQ(contents_view->GetNativeObject(), child_view_4->GetParent());
EXPECT_EQ(0, child_view_4->GetChildCount());
EXPECT_EQ(5, child_view_4->GetIndexInParent());
EXPECT_EQ(nullptr, child_view_4->GetNextSibling());
EXPECT_EQ(child_view_3->GetNativeObject(),
child_view_4->GetPreviousSibling());
} }
TEST_F(ViewAXPlatformNodeDelegateTest, OverrideHasPopup) { TEST_F(ViewAXPlatformNodeDelegateTest, OverrideHasPopup) {
......
...@@ -46,7 +46,7 @@ ViewAXPlatformNodeDelegateWin::~ViewAXPlatformNodeDelegateWin() = default; ...@@ -46,7 +46,7 @@ ViewAXPlatformNodeDelegateWin::~ViewAXPlatformNodeDelegateWin() = default;
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateWin::GetParent() { gfx::NativeViewAccessible ViewAXPlatformNodeDelegateWin::GetParent() {
// If the View has a parent View, return that View's IAccessible. // If the View has a parent View, return that View's IAccessible.
if (view()->parent()) if (view()->parent())
return view()->parent()->GetNativeViewAccessible(); return ViewAXPlatformNodeDelegate::GetParent();
// Otherwise we must be the RootView, get the corresponding Widget // Otherwise we must be the RootView, get the corresponding Widget
// and Window. // and Window.
...@@ -105,4 +105,5 @@ gfx::Rect ViewAXPlatformNodeDelegateWin::GetBoundsRect( ...@@ -105,4 +105,5 @@ gfx::Rect ViewAXPlatformNodeDelegateWin::GetBoundsRect(
return gfx::Rect(); return gfx::Rect();
} }
} }
} // namespace views } // namespace views
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment