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 {
for (const std::unique_ptr<AXVirtualView>& child : children_) {
if (child->IsIgnored()) {
count += child->GetChildCount();
continue;
} else {
++count;
}
count++;
}
return count;
}
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())
<< "Child indices should be less than the child count.";
int i = 0;
<< "|index| should be less than the child count.";
for (const std::unique_ptr<AXVirtualView>& child : children_) {
if (child->IsIgnored()) {
if (index - i < child->GetChildCount()) {
gfx::NativeViewAccessible result = child->ChildAtIndex(index - i);
if (result)
return result;
}
i += child->GetChildCount();
continue;
int child_count = child->GetChildCount();
if (index < child_count)
return child->ChildAtIndex(index);
index -= child_count;
} else {
if (index == 0)
return child->GetNativeObject();
--index;
}
if (i == index)
return child->GetNativeObject();
i++;
DCHECK_GE(index, 0) << "|index| should be less than the child count.";
}
NOTREACHED() << "|index| should be less than the child count.";
return nullptr;
}
......@@ -297,8 +299,11 @@ gfx::NativeViewAccessible AXVirtualView::GetNativeViewAccessible() {
}
gfx::NativeViewAccessible AXVirtualView::GetParent() {
if (parent_view_)
return parent_view_->GetNativeObject();
if (parent_view_) {
if (!parent_view_->IsIgnored())
return parent_view_->GetNativeObject();
return GetDelegate()->GetParent();
}
if (virtual_parent_view_) {
if (virtual_parent_view_->IsIgnored())
......@@ -417,8 +422,8 @@ const ui::AXUniqueId& AXVirtualView::GetUniqueId() const {
return unique_id_;
}
// Virtual views need to implement this function in order for A11Y events
// to be routed correctly.
// Virtual views need to implement this function in order for accessibility
// events to be routed correctly.
gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() {
#if defined(OS_WIN)
if (GetOwnerView())
......
......@@ -165,14 +165,19 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
// Gets the real View that owns our shallowest virtual ancestor,, if any.
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;
// Gets or creates a wrapper suitable for use with tree sources.
AXVirtualViewWrapper* GetOrCreateWrapper(views::AXAuraObjCache* cache);
// 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;
// Handle a request from assistive technology to perform an action on this
......
......@@ -365,122 +365,6 @@ TEST_F(AXVirtualViewTest, InvisibleVirtualViews) {
button_->SetVisible(true);
}
// Verify that ignored virtual views are removed from the accessible tree and
// that their contents are intact.
TEST_F(AXVirtualViewTest, IgnoredVirtualViews) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
// An ignored node should not be exposed.
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
virtual_child_1->GetCustomData().AddState(ax::mojom::State::kIgnored);
ASSERT_EQ(0, virtual_label_->GetChildCount());
ASSERT_EQ(0, virtual_child_1->GetChildCount());
// The contents of ignored nodes should be exposed.
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_child_1->AddChildView(base::WrapUnique(virtual_child_2));
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
AXVirtualView* virtual_child_4 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_4));
ASSERT_EQ(1, virtual_label_->GetChildCount());
ASSERT_EQ(1, virtual_child_1->GetChildCount());
ASSERT_EQ(2, virtual_child_2->GetChildCount());
ASSERT_EQ(0, virtual_child_3->GetChildCount());
ASSERT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_child_1->ChildAtIndex(0));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->ChildAtIndex(1));
// The contents of ignored nodes should be unignored accessibility subtrees.
virtual_child_2->GetCustomData().role = ax::mojom::Role::kIgnored;
ASSERT_EQ(2, virtual_label_->GetChildCount());
ASSERT_EQ(2, virtual_child_1->GetChildCount());
ASSERT_EQ(2, virtual_child_2->GetChildCount());
ASSERT_EQ(0, virtual_child_3->GetChildCount());
ASSERT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_1->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_1->ChildAtIndex(1));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->ChildAtIndex(1));
// Test for mixed ignored and unignored virtual children.
AXVirtualView* virtual_child_5 = new AXVirtualView;
virtual_child_1->AddChildView(base::WrapUnique(virtual_child_5));
ASSERT_EQ(3, virtual_label_->GetChildCount());
ASSERT_EQ(3, virtual_child_1->GetChildCount());
ASSERT_EQ(2, virtual_child_2->GetChildCount());
ASSERT_EQ(0, virtual_child_5->GetChildCount());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_5->GetParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_5->GetNativeObject(),
virtual_label_->ChildAtIndex(2));
// An ignored root node should not be exposed.
virtual_label_->GetCustomData().AddState(ax::mojom::State::kIgnored);
ASSERT_EQ(3, GetButtonAccessibility()->GetChildCount());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_1->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_2->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_3->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_4->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_5->GetParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(1));
EXPECT_EQ(virtual_child_5->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(2));
// Test for mixed ignored and unignored root nodes.
AXVirtualView* virtual_label_2 = new AXVirtualView;
virtual_label_2->GetCustomData().role = ax::mojom::Role::kStaticText;
virtual_label_2->GetCustomData().SetName("Label");
button_->GetViewAccessibility().AddVirtualChildView(
base::WrapUnique(virtual_label_2));
ASSERT_EQ(4, GetButtonAccessibility()->GetChildCount());
ASSERT_EQ(0, virtual_label_2->GetChildCount());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_2->GetParent());
EXPECT_EQ(virtual_label_2->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(3));
// A focusable node should not be ignored.
virtual_child_1->GetCustomData().AddState(ax::mojom::State::kFocusable);
ASSERT_EQ(2, GetButtonAccessibility()->GetChildCount());
ASSERT_EQ(1, virtual_label_->GetChildCount());
EXPECT_EQ(virtual_child_1->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(0));
EXPECT_EQ(virtual_label_2->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(1));
}
TEST_F(AXVirtualViewTest, OverrideFocus) {
ViewAccessibility& button_accessibility = button_->GetViewAccessibility();
ASSERT_NE(nullptr, button_accessibility.GetNativeObject());
......@@ -595,16 +479,14 @@ TEST_F(AXVirtualViewTest, OverrideFocus) {
ax::mojom::Event::kChildrenChanged)});
}
TEST_F(AXVirtualViewTest, Navigation) {
TEST_F(AXVirtualViewTest, TreeNavigation) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
EXPECT_EQ(1, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_2));
EXPECT_EQ(2, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_3));
......@@ -612,29 +494,225 @@ TEST_F(AXVirtualViewTest, Navigation) {
AXVirtualView* virtual_child_4 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_4));
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(0, virtual_label_->GetIndexInParent());
EXPECT_EQ(0, virtual_child_1->GetIndexInParent());
EXPECT_EQ(1, virtual_child_2->GetIndexInParent());
EXPECT_EQ(2, virtual_child_3->GetIndexInParent());
EXPECT_EQ(0, virtual_child_4->GetIndexInParent());
EXPECT_EQ(3, virtual_label_->GetChildCount());
EXPECT_EQ(0, virtual_child_1->GetChildCount());
EXPECT_EQ(1, virtual_child_2->GetChildCount());
EXPECT_EQ(0, virtual_child_3->GetChildCount());
EXPECT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->ChildAtIndex(2));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_label_->GetFirstChild());
EXPECT_EQ(virtual_child_3->GetNativeObject(), virtual_label_->GetLastChild());
EXPECT_EQ(nullptr, virtual_child_1->GetFirstChild());
EXPECT_EQ(nullptr, virtual_child_1->GetLastChild());
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->GetFirstChild());
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->GetLastChild());
EXPECT_EQ(nullptr, virtual_child_4->GetFirstChild());
EXPECT_EQ(nullptr, virtual_child_4->GetLastChild());
EXPECT_EQ(nullptr, virtual_label_->GetNextSibling());
EXPECT_EQ(nullptr, virtual_label_->GetPreviousSibling());
EXPECT_EQ(0, virtual_label_->GetIndexInParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_child_1->GetNextSibling());
EXPECT_EQ(nullptr, virtual_child_1->GetPreviousSibling());
EXPECT_EQ(0, virtual_child_1->GetIndexInParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->GetNextSibling());
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_child_2->GetPreviousSibling());
EXPECT_EQ(1, virtual_child_2->GetIndexInParent());
EXPECT_EQ(nullptr, virtual_child_3->GetNextSibling());
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_child_3->GetPreviousSibling());
EXPECT_EQ(2, virtual_child_3->GetIndexInParent());
EXPECT_EQ(nullptr, virtual_child_4->GetNextSibling());
EXPECT_EQ(nullptr, virtual_child_4->GetPreviousSibling());
EXPECT_EQ(0, virtual_child_4->GetIndexInParent());
}
TEST_F(AXVirtualViewTest, TreeNavigationWithIgnoredVirtualViews) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
virtual_child_1->GetCustomData().AddState(ax::mojom::State::kIgnored);
EXPECT_EQ(0, virtual_label_->GetChildCount());
EXPECT_EQ(0, virtual_child_1->GetChildCount());
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_child_1->AddChildView(base::WrapUnique(virtual_child_2));
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_3));
AXVirtualView* virtual_child_4 = new AXVirtualView;
virtual_child_2->AddChildView(base::WrapUnique(virtual_child_4));
// While ignored nodes should not be accessible via any of the tree navigation
// methods, their descendants should be.
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_child_2->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(0, virtual_label_->GetIndexInParent());
EXPECT_EQ(-1, virtual_child_1->GetIndexInParent());
EXPECT_EQ(0, virtual_child_2->GetIndexInParent());
EXPECT_EQ(0, virtual_child_3->GetIndexInParent());
EXPECT_EQ(1, virtual_child_4->GetIndexInParent());
EXPECT_EQ(1, virtual_label_->GetChildCount());
EXPECT_EQ(1, virtual_child_1->GetChildCount());
EXPECT_EQ(2, virtual_child_2->GetChildCount());
EXPECT_EQ(0, virtual_child_3->GetChildCount());
EXPECT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_child_1->ChildAtIndex(0));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->ChildAtIndex(1));
// Try ignoring a node by changing its role, instead of its state.
virtual_child_2->GetCustomData().role = ax::mojom::Role::kIgnored;
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(2, virtual_label_->GetChildCount());
EXPECT_EQ(2, virtual_child_1->GetChildCount());
EXPECT_EQ(2, virtual_child_2->GetChildCount());
EXPECT_EQ(0, virtual_child_3->GetChildCount());
EXPECT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(0, virtual_label_->GetIndexInParent());
EXPECT_EQ(-1, virtual_child_1->GetIndexInParent());
EXPECT_EQ(-1, virtual_child_2->GetIndexInParent());
EXPECT_EQ(0, virtual_child_3->GetIndexInParent());
EXPECT_EQ(1, virtual_child_4->GetIndexInParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_1->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_1->ChildAtIndex(1));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_child_2->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_child_2->ChildAtIndex(1));
// Test for mixed ignored and unignored virtual children.
AXVirtualView* virtual_child_5 = new AXVirtualView;
virtual_child_1->AddChildView(base::WrapUnique(virtual_child_5));
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_1->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_2->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_3->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_4->GetParent());
EXPECT_EQ(virtual_label_->GetNativeObject(), virtual_child_5->GetParent());
EXPECT_EQ(3, virtual_label_->GetChildCount());
EXPECT_EQ(3, virtual_child_1->GetChildCount());
EXPECT_EQ(2, virtual_child_2->GetChildCount());
EXPECT_EQ(0, virtual_child_3->GetChildCount());
EXPECT_EQ(0, virtual_child_4->GetChildCount());
EXPECT_EQ(0, virtual_child_5->GetChildCount());
EXPECT_EQ(0, virtual_label_->GetIndexInParent());
EXPECT_EQ(-1, virtual_child_1->GetIndexInParent());
EXPECT_EQ(-1, virtual_child_2->GetIndexInParent());
EXPECT_EQ(0, virtual_child_3->GetIndexInParent());
EXPECT_EQ(1, virtual_child_4->GetIndexInParent());
EXPECT_EQ(2, virtual_child_5->GetIndexInParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->ChildAtIndex(1));
EXPECT_EQ(virtual_child_5->GetNativeObject(),
virtual_label_->ChildAtIndex(2));
// An ignored root node should not be exposed.
virtual_label_->GetCustomData().AddState(ax::mojom::State::kIgnored);
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_1->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_2->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_3->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_4->GetParent());
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_child_5->GetParent());
EXPECT_EQ(3, GetButtonAccessibility()->GetChildCount());
EXPECT_EQ(0, virtual_child_3->GetIndexInParent());
EXPECT_EQ(1, virtual_child_4->GetIndexInParent());
EXPECT_EQ(2, virtual_child_5->GetIndexInParent());
EXPECT_EQ(virtual_child_3->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(0));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(1));
EXPECT_EQ(virtual_child_5->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(2));
// Test for mixed ignored and unignored root nodes.
AXVirtualView* virtual_label_2 = new AXVirtualView;
virtual_label_2->GetCustomData().role = ax::mojom::Role::kStaticText;
virtual_label_2->GetCustomData().SetName("Label");
button_->GetViewAccessibility().AddVirtualChildView(
base::WrapUnique(virtual_label_2));
EXPECT_EQ(button_->GetNativeViewAccessible(), virtual_label_2->GetParent());
EXPECT_EQ(4, GetButtonAccessibility()->GetChildCount());
EXPECT_EQ(0, virtual_label_2->GetChildCount());
EXPECT_EQ(virtual_label_2->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(3));
// A focusable node should not be ignored.
virtual_child_1->GetCustomData().AddState(ax::mojom::State::kFocusable);
EXPECT_EQ(2, GetButtonAccessibility()->GetChildCount());
EXPECT_EQ(1, virtual_label_->GetChildCount());
EXPECT_EQ(virtual_child_1->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(0));
EXPECT_EQ(virtual_label_2->GetNativeObject(),
GetButtonAccessibility()->ChildAtIndex(1));
}
TEST_F(AXVirtualViewTest, HitTesting) {
......@@ -690,7 +768,7 @@ TEST_F(AXVirtualViewTest, GetTargetForEvents) {
EXPECT_EQ(HWNDForView(button_),
virtual_label_->GetTargetForNativeAccessibilityEvent());
}
#endif
#endif // defined(OS_WIN)
} // namespace test
} // namespace views
......@@ -97,7 +97,11 @@ class VIEWS_EXPORT ViewAccessibility {
Widget* GetNextFocus();
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 void NotifyAccessibilityEvent(ax::mojom::Event event_type);
// Causes the screen reader to announce |text|. If the current user is not
......
......@@ -113,14 +113,21 @@ void PostFlushEventQueueTaskIfNecessary() {
} // namespace
struct ViewAXPlatformNodeDelegate::ChildWidgetsResult {
std::vector<Widget*> child_widgets;
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult() = default;
// Set to true if, instead of populating |child_widgets| normally, a single
// child widget was returned (e.g. a dialog that should be read instead of
// the rest of the page contents).
bool is_tab_modal_showing;
};
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult(
std::vector<Widget*> child_widgets,
bool is_tab_modal_showing)
: child_widgets(child_widgets),
is_tab_modal_showing(is_tab_modal_showing) {}
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult(
const ViewAXPlatformNodeDelegate::ChildWidgetsResult& other) = default;
ViewAXPlatformNodeDelegate::ChildWidgetsResult::~ChildWidgetsResult() = default;
ViewAXPlatformNodeDelegate::ChildWidgetsResult&
ViewAXPlatformNodeDelegate::ChildWidgetsResult::operator=(
const ViewAXPlatformNodeDelegate::ChildWidgetsResult& other) = default;
ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view)
: ViewAccessibility(view) {
......@@ -136,18 +143,16 @@ ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view)
}
ViewAXPlatformNodeDelegate::~ViewAXPlatformNodeDelegate() {
if (ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject())
EndPopupFocusOverride();
if (ui::AXPlatformNode::GetPopupFocusOverride() ==
ax_platform_node_->GetNativeViewAccessible()) {
ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
}
ax_platform_node_->Destroy();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() const {
DCHECK(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
ax_platform_node_ = nullptr;
}
void ViewAXPlatformNodeDelegate::SetPopupFocusOverride() {
ui::AXPlatformNode::SetPopupFocusOverride(GetNativeObject());
ui::AXPlatformNode::SetPopupFocusOverride(GetNativeViewAccessible());
}
void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() {
......@@ -156,11 +161,17 @@ void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() {
bool ViewAXPlatformNodeDelegate::IsFocusedForTesting() {
if (ui::AXPlatformNode::GetPopupFocusOverride())
return ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject();
return ui::AXPlatformNode::GetPopupFocusOverride() ==
GetNativeViewAccessible();
return ViewAccessibility::IsFocusedForTesting();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() const {
DCHECK(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
......@@ -182,7 +193,7 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
case ax::mojom::Event::kFocus: {
if (ui::AXPlatformNode::GetPopupFocusOverride()) {
DCHECK_EQ(ui::AXPlatformNode::GetPopupFocusOverride(),
GetNativeObject())
GetNativeViewAccessible())
<< "If the popup focus override is on, then the kFocus event must "
"match it. Most likely the popup has closed, but did not call "
"ViewAccessibility::EndPopupFocusOverride(), and focus has "
......@@ -276,72 +287,124 @@ int ViewAXPlatformNodeDelegate::GetChildCount() const {
if (ViewAccessibility::IsLeaf())
return 0;
// If present, virtual view children override any real children.
if (!virtual_children().empty()) {
int count = 0;
for (const std::unique_ptr<AXVirtualView>& child : virtual_children()) {
if (child->IsIgnored()) {
count += child->GetChildCount();
continue;
// Ignored virtual views are not exposed in any accessibility platform APIs.
// Remove all ignored virtual view children and recursively replace them
// with their unignored children count.
int virtual_child_count = 0;
for (const std::unique_ptr<AXVirtualView>& virtual_child :
virtual_children()) {
if (virtual_child->IsIgnored()) {
virtual_child_count += virtual_child->GetChildCount();
} else {
++virtual_child_count;
}
count++;
}
return count;
// A virtual views subtree hides any real view children.
return virtual_child_count;
}
const auto child_widgets_result = GetChildWidgets();
const ChildWidgetsResult child_widgets_result = GetChildWidgets();
if (child_widgets_result.is_tab_modal_showing) {
// In order to support the "read title (NVDAKey+T)" and "read window
// (NVDAKey+B)" commands in the NVDA screen reader, hide the rest of the UI
// from the accessibility tree when a modal dialog is showing.
DCHECK_EQ(child_widgets_result.child_widgets.size(), 1U);
return 1;
}
return static_cast<int>(view()->children().size() +
child_widgets_result.child_widgets.size());
// Ignored views are not exposed in any accessibility platform APIs. Remove
// all ignored view children and recursively replace them with their unignored
// children count. This matches how AXPlatformNodeDelegate::GetChildCount()
// behaves for Web content.
int view_child_count = 0;
for (View* child : view()->children()) {
const ViewAccessibility& view_accessibility = child->GetViewAccessibility();
if (view_accessibility.IsIgnored()) {
const auto* child_view_delegate =
static_cast<const ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(child_view_delegate);
view_child_count += child_view_delegate->GetChildCount();
} else {
++view_child_count;
}
}
return view_child_count + int{child_widgets_result.child_widgets.size()};
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::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())
<< "Child indices should be less than the child count.";
<< "|index| should be less than the unignored child count.";
if (IsLeaf())
return nullptr;
size_t child_index = size_t{index};
if (!virtual_children().empty()) {
int i = 0;
for (const std::unique_ptr<AXVirtualView>& child : virtual_children()) {
if (child->IsIgnored()) {
if (index - i < child->GetChildCount()) {
gfx::NativeViewAccessible result = child->ChildAtIndex(index - i);
if (result)
return result;
}
i += child->GetChildCount();
continue;
// A virtual views subtree hides all the real view children.
for (const std::unique_ptr<AXVirtualView>& virtual_child :
virtual_children()) {
if (virtual_child->IsIgnored()) {
int virtual_child_count = virtual_child->GetChildCount();
if (index < virtual_child_count)
return virtual_child->ChildAtIndex(index);
index -= virtual_child_count;
} else {
if (index == 0)
return virtual_child->GetNativeObject();
--index;
}
if (i == index)
return child->GetNativeObject();
i++;
DCHECK_GE(index, 0);
}
NOTREACHED() << "|index| should be less than the unignored child count.";
return nullptr;
}
// If this is a root view, our widget might have child widgets. Include
const auto child_widgets_result = GetChildWidgets();
const auto& child_widgets = child_widgets_result.child_widgets;
// Our widget might have child widgets. If this is a root view, include those
// widgets in the list of the root view's children because this is the most
// opportune location in the accessibility tree to expose them.
const ChildWidgetsResult child_widgets_result = GetChildWidgets();
const std::vector<Widget*>& child_widgets =
child_widgets_result.child_widgets;
// If a visible tab modal dialog is present, ignore |index| and return the
// dialog.
// If a visible tab modal dialog is present, return the dialog's root view.
//
// This is 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, other than the dialog, from the screen reader.
if (child_widgets_result.is_tab_modal_showing) {
DCHECK_EQ(index, 0);
DCHECK_EQ(child_widgets.size(), 1U);
return child_widgets[0]->GetRootView()->GetNativeViewAccessible();
}
if (child_index < view()->children().size())
return view()->children()[child_index]->GetNativeViewAccessible();
for (View* child : view()->children()) {
ViewAccessibility& view_accessibility = child->GetViewAccessibility();
if (view_accessibility.IsIgnored()) {
auto* child_view_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(child_view_delegate);
int child_count = child_view_delegate->GetChildCount();
if (index < child_count)
return child_view_delegate->ChildAtIndex(index);
index -= child_count;
} else {
if (index == 0)
return view_accessibility.view()->GetNativeViewAccessible();
--index;
}
DCHECK_GE(index, 0);
}
child_index -= view()->children().size();
if (child_index < child_widgets_result.child_widgets.size())
return child_widgets[child_index]->GetRootView()->GetNativeViewAccessible();
if (index < int{child_widgets_result.child_widgets.size()})
return child_widgets[index]->GetRootView()->GetNativeViewAccessible();
NOTREACHED() << "|index| should be less than the unignored child count.";
return nullptr;
}
......@@ -354,14 +417,32 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNSWindow() {
return nullptr;
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeViewAccessible()
const {
// TODO(nektar): Make "GetNativeViewAccessible" const throughout the codebase.
return const_cast<ViewAXPlatformNodeDelegate*>(this)
->GetNativeViewAccessible();
}
gfx::NativeViewAccessible
ViewAXPlatformNodeDelegate::GetNativeViewAccessible() {
return GetNativeObject();
// The WebView class returns the BrowserAccessibility instance exposed by its
// WebContents child, not its own AXPlatformNode. This is done by overriding
// "GetNativeViewAccessible", so we can't simply call "GetNativeObject" here.
return view()->GetNativeViewAccessible();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetParent() {
if (view()->parent())
return view()->parent()->GetNativeViewAccessible();
if (View* parent_view = view()->parent()) {
ViewAccessibility& view_accessibility = parent_view->GetViewAccessibility();
if (!view_accessibility.IsIgnored())
return parent_view->GetNativeViewAccessible();
auto* parent_view_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(parent_view_delegate);
return parent_view_delegate->GetParent();
}
if (Widget* widget = view()->GetWidget()) {
Widget* top_widget = widget->GetTopLevelWidget();
......@@ -419,7 +500,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
return nullptr;
if (IsLeaf())
return GetNativeObject();
return GetNativeViewAccessible();
gfx::NativeView native_view = view()->GetWidget()->GetNativeView();
float scale_factor = 1.0;
......@@ -457,7 +538,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
return result;
}
// If it's not inside any of our virtual children, it's inside this view.
return GetNativeObject();
return GetNativeViewAccessible();
}
// Check if the point is within any of the immediate children of this
......@@ -478,7 +559,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
const auto i = std::find_if(v->children().rbegin(), v->children().rend(),
is_point_in_child);
// If it's not inside any of our children, it's inside this view.
return (i == v->children().rend()) ? GetNativeObject()
return (i == v->children().rend()) ? GetNativeViewAccessible()
: (*i)->GetNativeViewAccessible();
}
......@@ -689,12 +770,15 @@ void ViewAXPlatformNodeDelegate::GetViewsInGroupForSet(
ViewAXPlatformNodeDelegate::ChildWidgetsResult
ViewAXPlatformNodeDelegate::GetChildWidgets() const {
// Only attach child widgets to the root view.
// This method is used to create a parent / child relationship between the
// root view and any child widgets. Child widgets should only be exposed as
// the direct children of the root view. A root view should appear as the only
// child of a widget.
Widget* widget = view()->GetWidget();
// Note that during window close, a Widget may exist in a state where it has
// no NativeView, but hasn't yet torn down its view hierarchy.
if (!widget || !widget->GetNativeView() || widget->GetRootView() != view())
return {{}, false};
return ChildWidgetsResult();
std::set<Widget*> owned_widgets;
Widget::GetAllOwnedWidgets(widget->GetNativeView(), &owned_widgets);
......@@ -709,15 +793,19 @@ ViewAXPlatformNodeDelegate::GetChildWidgets() const {
const FocusManager* focus_manager = view()->GetFocusManager();
const View* focused_view =
focus_manager ? focus_manager->GetFocusedView() : nullptr;
const auto is_focused_child = [focused_view](Widget* child) {
return ViewAccessibilityUtils::IsFocusedChildWidget(child, focused_view);
const auto is_focused_child = [focused_view](Widget* child_widget) {
return ViewAccessibilityUtils::IsFocusedChildWidget(child_widget,
focused_view);
};
const auto i = std::find_if(visible_widgets.cbegin(), visible_widgets.cend(),
is_focused_child);
// In order to support the "read title (NVDAKey+T)" and "read window
// (NVDAKey+B)" commands in the NVDA screen reader, hide the rest of the UI
// from the accessibility tree when a modal dialog is showing.
if (i != visible_widgets.cend())
return {{*i}, true};
return ChildWidgetsResult({*i}, true /* is_tab_modal_showing */);
return {visible_widgets, false};
return ChildWidgetsResult(visible_widgets, false /* is_tab_modal_showing */);
}
} // namespace views
......@@ -61,6 +61,9 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
gfx::NativeViewAccessible ChildAtIndex(int index) override;
bool HasModalDialog() const 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 GetParent() override;
bool IsChildOfLeaf() const override;
......@@ -104,15 +107,35 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
ui::AXPlatformNode* ax_platform_node() { return ax_platform_node_; }
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.
// Searches from the View's parent to include siblings within that group.
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;
// Gets the real TableView, otherwise nullptr.
// Gets the real (non-virtual) TableView, otherwise nullptr.
TableView* GetAncestorTableView() const;
// We own this, but it is reference-counted on some platforms so we can't use
......
......@@ -187,8 +187,9 @@ ViewAXPlatformNodeDelegateAuraLinux::ViewAXPlatformNodeDelegateAuraLinux(
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateAuraLinux::GetParent() {
if (gfx::NativeViewAccessible parent =
ViewAXPlatformNodeDelegate::GetParent())
ViewAXPlatformNodeDelegate::GetParent()) {
return parent;
}
Widget* parent_widget =
GetWidgetOfParentWindowIncludingTransient(view()->GetWidget());
......
......@@ -37,7 +37,7 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetNSWindow() {
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetParent() {
if (view()->parent())
return view()->parent()->GetNativeViewAccessible();
return ViewAXPlatformNodeDelegate::GetParent();
auto* widget = view()->GetWidget();
if (!widget)
......
......@@ -423,35 +423,139 @@ TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
EXPECT_EQ(view_accessibility(group_ids[4])->GetPosInSet(), 1);
}
TEST_F(ViewAXPlatformNodeDelegateTest, Navigation) {
View::Views view_ids = SetUpExtraViews();
TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigation) {
// 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);
EXPECT_EQ(view_accessibility(view_ids[0])->GetPreviousSibling(),
view_accessibility(button_)->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[0])->GetIndexInParent(), 3);
EXPECT_EQ(view_accessibility(view_ids[1])->GetNextSibling(),
view_accessibility(view_ids[2])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[1])->GetPreviousSibling(), nullptr);
EXPECT_EQ(view_accessibility(view_ids[1])->GetIndexInParent(), 0);
EXPECT_EQ(view_accessibility(view_ids[2])->GetNextSibling(),
view_accessibility(view_ids[3])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[2])->GetPreviousSibling(),
view_accessibility(view_ids[1])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[2])->GetIndexInParent(), 1);
EXPECT_EQ(view_accessibility(view_ids[3])->GetNextSibling(),
view_accessibility(view_ids[4])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[3])->GetPreviousSibling(),
view_accessibility(view_ids[2])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[3])->GetIndexInParent(), 2);
EXPECT_EQ(view_accessibility(view_ids[4])->GetNextSibling(), nullptr);
EXPECT_EQ(view_accessibility(view_ids[4])->GetPreviousSibling(),
view_accessibility(view_ids[3])->GetNativeObject());
EXPECT_EQ(view_accessibility(view_ids[4])->GetIndexInParent(), 3);
TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) {
// 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* contents_view =
view_accessibility(widget_->GetContentsView());
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]);
// Mark the parent view and the second child view as ignored.
parent_view->OverrideIsIgnored(true);
child_view_2->OverrideIsIgnored(true);
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) {
......
......@@ -46,7 +46,7 @@ ViewAXPlatformNodeDelegateWin::~ViewAXPlatformNodeDelegateWin() = default;
gfx::NativeViewAccessible ViewAXPlatformNodeDelegateWin::GetParent() {
// If the View has a parent View, return that View's IAccessible.
if (view()->parent())
return view()->parent()->GetNativeViewAccessible();
return ViewAXPlatformNodeDelegate::GetParent();
// Otherwise we must be the RootView, get the corresponding Widget
// and Window.
......@@ -105,4 +105,5 @@ gfx::Rect ViewAXPlatformNodeDelegateWin::GetBoundsRect(
return gfx::Rect();
}
}
} // 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