Commit 241bd994 authored by Anna Mace's avatar Anna Mace Committed by Commit Bot

Implement GetSetSize/ GetPosInSet for Views Accessibility in ViewAXPlatformNodeDelegate

Implement GetSetSize, GetPosInSet, IsOrderedSet and IsOrderedSetItem
overrides for ViewAXPlatformNodeDelegate. These functions currently use
the empty base implementation (i.e. return false, 0) so screen readers
will not get information about sets for View elements, e.g. groups of
radio buttons. With this change, screenreaders using UIA will read 'n of m' for
items within a set.

The Views-specific implementation for these functions considers two
things when calculating set size and pos in set:
-If AXNodeData has the attribute kPosInSet or kSetSize, it will return
that value first.
-If not, it will see if this View is contained in a group (View::GetGroup()).
If so, it will count how many nearby Views are in that same group. If
the View has a parent, it will start from the parent and include all
ancestors of the parent. That way, sibling elements within the same group
are included in the count. When calculating position in set, it will
return the index of that View in the resulting vector of Views.

Views::SetGroup is only used in a few places in the codebase. I have
based the implementation off those uses so they are all supported. I
general it seems Views that share group ids are siblings, and sometimes
also their parent.
1. Radio buttons are constructed with a group_id passed in. Typically, it
seems RadioButtons are siblings.
2. Groups of buttons in dialogs (e.g. "Bookmark added" dialog "More",
"Done" and "Remove" buttons) may be in groups.
See DialogClientView::UpdateDialogButton
  button->SetGroup(kButtonGroup)
3. ParentAccessView::AccessCodeInput contains multiple AccessibleInputFields.
These are all in the same group, including the parent AccessCodeInput.
  SetGroup(kParentAccessInputGroup);
  ..
  field->SetGroup(kParentAccessInputGroup);
4. DesktopMediaListView contains many child DesktopMediaSourceViews,
which are all in the same group
  source_view->SetGroup(kDesktopMediaSourceViewGroupId);

Based on that, I think it is reasonable to only count the parent and
sibling Views (and their ancestors) in calculating the number of Views
in a group.

Change-Id: I0fb72ced30b1c8220cd076d92ecb279802d4b36e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1669329
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#672076}
parent 12ab8499
...@@ -408,6 +408,92 @@ const ui::AXUniqueId& ViewAXPlatformNodeDelegate::GetUniqueId() const { ...@@ -408,6 +408,92 @@ const ui::AXUniqueId& ViewAXPlatformNodeDelegate::GetUniqueId() const {
return ViewAccessibility::GetUniqueId(); return ViewAccessibility::GetUniqueId();
} }
bool ViewAXPlatformNodeDelegate::IsOrderedSetItem() const {
const ui::AXNodeData& data = GetData();
return (view()->GetGroup() >= 0) ||
(data.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet) &&
data.HasIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
bool ViewAXPlatformNodeDelegate::IsOrderedSet() const {
return (view()->GetGroup() >= 0) ||
GetData().HasIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
base::Optional<int> ViewAXPlatformNodeDelegate::GetPosInSet() const {
// Consider overridable attributes first.
const ui::AXNodeData& data = GetData();
if (data.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet))
return data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
std::vector<View*> views_in_group;
GetViewsInGroupForSet(&views_in_group);
if (views_in_group.empty())
return base::nullopt;
// Check this is in views_in_group; it may be removed if it is ignored.
auto found_view =
std::find(views_in_group.begin(), views_in_group.end(), view());
if (found_view == views_in_group.end())
return base::nullopt;
int posInSet = std::distance(views_in_group.begin(), found_view);
// posInSet is zero-based; users expect one-based, so increment.
return ++posInSet;
}
base::Optional<int> ViewAXPlatformNodeDelegate::GetSetSize() const {
// Consider overridable attributes first.
const ui::AXNodeData& data = GetData();
if (data.HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
return data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
std::vector<View*> views_in_group;
GetViewsInGroupForSet(&views_in_group);
if (views_in_group.empty())
return base::nullopt;
// Check this is in views_in_group; it may be removed if it is ignored.
auto found_view =
std::find(views_in_group.begin(), views_in_group.end(), view());
if (found_view == views_in_group.end())
return base::nullopt;
return views_in_group.size();
}
void ViewAXPlatformNodeDelegate::GetViewsInGroupForSet(
std::vector<View*>* views_in_group) const {
const int group_id = view()->GetGroup();
if (group_id < 0)
return;
View* view_to_check = view();
// If this view has a parent, check from the parent, to make sure we catch any
// siblings.
if (view()->parent())
view_to_check = view()->parent();
view_to_check->GetViewsInGroup(group_id, views_in_group);
// Remove any views that are ignored in the accessibility tree.
views_in_group->erase(
std::remove_if(
views_in_group->begin(), views_in_group->end(),
[](View* view) {
ViewAccessibility& view_accessibility =
view->GetViewAccessibility();
bool is_ignored = view_accessibility.IsIgnored();
// TODO Remove the ViewAXPlatformNodeDelegate::GetData() part of
// this lambda, once the temporary code in GetData() setting the
// role to kIgnored is moved to ViewAccessibility.
ViewAXPlatformNodeDelegate* ax_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
if (ax_delegate)
is_ignored = is_ignored || (ax_delegate->GetData().role ==
ax::mojom::Role::kIgnored);
return is_ignored;
}),
views_in_group->end());
}
ViewAXPlatformNodeDelegate::ChildWidgetsResult ViewAXPlatformNodeDelegate::ChildWidgetsResult
ViewAXPlatformNodeDelegate::GetChildWidgets() const { ViewAXPlatformNodeDelegate::GetChildWidgets() const {
// Only attach child widgets to the root view. // Only attach child widgets to the root view.
......
...@@ -66,10 +66,20 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility, ...@@ -66,10 +66,20 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
// Also in |ViewAccessibility|. // Also in |ViewAccessibility|.
const ui::AXUniqueId& GetUniqueId() const override; const ui::AXUniqueId& GetUniqueId() const override;
// Ordered-set-like and item-like nodes.
bool IsOrderedSetItem() const override;
bool IsOrderedSet() const override;
base::Optional<int> GetPosInSet() const override;
base::Optional<int> GetSetSize() const override;
protected: protected:
explicit ViewAXPlatformNodeDelegate(View* view); explicit ViewAXPlatformNodeDelegate(View* view);
private: private:
// 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; struct ChildWidgetsResult;
ChildWidgetsResult GetChildWidgets() const; ChildWidgetsResult GetChildWidgets() const;
......
...@@ -78,6 +78,11 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { ...@@ -78,6 +78,11 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
&label_->GetViewAccessibility()); &label_->GetViewAccessibility());
} }
ViewAXPlatformNodeDelegate* view_accessibility(View* view) {
return static_cast<ViewAXPlatformNodeDelegate*>(
&view->GetViewAccessibility());
}
bool SetFocused(ViewAXPlatformNodeDelegate* ax_delegate, bool focused) { bool SetFocused(ViewAXPlatformNodeDelegate* ax_delegate, bool focused) {
ui::AXActionData data; ui::AXActionData data;
data.action = data.action =
...@@ -85,6 +90,58 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { ...@@ -85,6 +90,58 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase {
return ax_delegate->AccessibilityPerformAction(data); return ax_delegate->AccessibilityPerformAction(data);
} }
// Sets up a more complicated structure of Views - one parent View with four
// child Views.
std::vector<View*> SetUpExtraViews() {
View* parent_view = new View();
widget_->GetContentsView()->AddChildView(parent_view);
std::vector<View*> views{parent_view};
const int num_children = 4;
for (int i = 0; i < num_children; i++) {
View* child_view = new View();
parent_view->AddChildView(child_view);
views.push_back(child_view);
}
return views;
}
// Adds group id information to the first 5 values in |views|. If |views| is
// empty, populates it with one parent View and four child Views. It is
// assumed |views| is either empty or has at least 5 items.
void SetUpExtraViewsWithGroups(std::vector<View*>& views) {
// v[0] g1
// | | | |
// v[1] g1 v[2] g1 v[3] g2 v[4]
if (views.empty())
views = SetUpExtraViews();
EXPECT_GE(views.size(), (size_t)5);
views[0]->SetGroup(1);
views[1]->SetGroup(1);
views[2]->SetGroup(1);
views[3]->SetGroup(2);
// Skip views[4] - no group id.
}
// Adds posInSet and setSize overrides to the first 5 values in |views|. If
// |views| is empty, populates it with one parent View and four child Views.
// It is assumed |views| is either empty or has at least 5 items.
void SetUpExtraViewsWithSetOverrides(std::vector<View*>& views) {
// v[0] p4 s4
// | | | |
// v[1] p3 s4 v[2] p2 s4 v[3] p- s- v[4] p1 s4
if (views.empty())
views = SetUpExtraViews();
EXPECT_GE(views.size(), (size_t)5);
views[0]->GetViewAccessibility().OverridePosInSet(4, 4);
views[1]->GetViewAccessibility().OverridePosInSet(3, 4);
views[2]->GetViewAccessibility().OverridePosInSet(2, 4);
// Skip views[3] - no override.
views[4]->GetViewAccessibility().OverridePosInSet(1, 4);
}
protected: protected:
const int DEFAULT_VIEW_ID = 0; const int DEFAULT_VIEW_ID = 0;
const int NON_DEFAULT_VIEW_ID = 1; const int NON_DEFAULT_VIEW_ID = 1;
...@@ -185,6 +242,101 @@ TEST_F(ViewAXPlatformNodeDelegateTest, GetAuthorUniqueIdNonDefault) { ...@@ -185,6 +242,101 @@ TEST_F(ViewAXPlatformNodeDelegateTest, GetAuthorUniqueIdNonDefault) {
button_accessibility()->GetAuthorUniqueId()); button_accessibility()->GetAuthorUniqueId());
} }
TEST_F(ViewAXPlatformNodeDelegateTest, IsOrderedSet) {
std::vector<View*> group_ids;
SetUpExtraViewsWithGroups(group_ids);
// Only last element has no group id.
EXPECT_TRUE(view_accessibility(group_ids[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[1])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[2])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[3])->IsOrderedSet());
EXPECT_FALSE(view_accessibility(group_ids[4])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(group_ids[0])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[1])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[2])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(group_ids[3])->IsOrderedSetItem());
EXPECT_FALSE(view_accessibility(group_ids[4])->IsOrderedSetItem());
std::vector<View*> overrides;
SetUpExtraViewsWithSetOverrides(overrides);
// Only overrides[3] has no override values for setSize/ posInSet.
EXPECT_TRUE(view_accessibility(overrides[0])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[1])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[2])->IsOrderedSet());
EXPECT_FALSE(view_accessibility(overrides[3])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[4])->IsOrderedSet());
EXPECT_TRUE(view_accessibility(overrides[0])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[1])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[2])->IsOrderedSetItem());
EXPECT_FALSE(view_accessibility(overrides[3])->IsOrderedSetItem());
EXPECT_TRUE(view_accessibility(overrides[4])->IsOrderedSetItem());
}
TEST_F(ViewAXPlatformNodeDelegateTest, SetSizeAndPosition) {
// Test Views with group ids.
std::vector<View*> group_ids;
SetUpExtraViewsWithGroups(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 1);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[1])->GetPosInSet(), 2);
EXPECT_EQ(view_accessibility(group_ids[2])->GetSetSize(), 3);
EXPECT_EQ(view_accessibility(group_ids[2])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(group_ids[3])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[3])->GetPosInSet(), 1);
EXPECT_FALSE(view_accessibility(group_ids[4])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[4])->GetPosInSet().has_value());
// Check if a View is ignored, it is not counted in SetSize or PosInSet
group_ids[1]->GetViewAccessibility().OverrideIsIgnored(true);
group_ids[2]->GetViewAccessibility().OverrideIsIgnored(true);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 1);
EXPECT_FALSE(view_accessibility(group_ids[1])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[1])->GetPosInSet().has_value());
EXPECT_FALSE(view_accessibility(group_ids[2])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(group_ids[2])->GetPosInSet().has_value());
group_ids[1]->GetViewAccessibility().OverrideIsIgnored(false);
group_ids[2]->GetViewAccessibility().OverrideIsIgnored(false);
// Test Views with setSize/ posInSet override values set.
std::vector<View*> overrides;
SetUpExtraViewsWithSetOverrides(overrides);
EXPECT_EQ(view_accessibility(overrides[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(overrides[1])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[1])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(overrides[2])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[2])->GetPosInSet(), 2);
EXPECT_FALSE(view_accessibility(overrides[3])->GetSetSize().has_value());
EXPECT_FALSE(view_accessibility(overrides[3])->GetPosInSet().has_value());
EXPECT_EQ(view_accessibility(overrides[4])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(overrides[4])->GetPosInSet(), 1);
// Test Views with both group ids and setSize/ posInSet override values set.
// Make sure the override values take precedence when both are set.
// Add setSize/ posInSet overrides to the Views with group ids.
SetUpExtraViewsWithSetOverrides(group_ids);
EXPECT_EQ(view_accessibility(group_ids[0])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[0])->GetPosInSet(), 4);
EXPECT_EQ(view_accessibility(group_ids[1])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[1])->GetPosInSet(), 3);
EXPECT_EQ(view_accessibility(group_ids[2])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[2])->GetPosInSet(), 2);
EXPECT_EQ(view_accessibility(group_ids[3])->GetSetSize(), 1);
EXPECT_EQ(view_accessibility(group_ids[3])->GetPosInSet(), 1);
EXPECT_EQ(view_accessibility(group_ids[4])->GetSetSize(), 4);
EXPECT_EQ(view_accessibility(group_ids[4])->GetPosInSet(), 1);
}
#if defined(USE_AURA) #if defined(USE_AURA)
class DerivedTestView : public View { class DerivedTestView : public View {
public: public:
......
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