Commit ee59bc42 authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

Add robust implementation of PosInSet and SetSize

Build on existing naive PosInSet and SetSize implementation to handle
cases such as inconsistent or invalid assignments of kPosInSet and
kSetSize attributes

Change-Id: I966f3ad00e778e0d68bcb48d160f1be5818b587b
Reviewed-on: https://chromium-review.googlesource.com/c/1340523
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609017}
parent 549bcac9
...@@ -473,7 +473,8 @@ void AXNode::IdVectorToNodeVector(std::vector<int32_t>& ids, ...@@ -473,7 +473,8 @@ void AXNode::IdVectorToNodeVector(std::vector<int32_t>& ids,
} }
} }
bool AXNode::IsPosInSetUsedInRole() const { // Determines the roles in which PosInSet and SetSize are used
bool AXNode::IsSetSizePosInSetUsedInRole() const {
switch (data().role) { switch (data().role) {
case ax::mojom::Role::kArticle: case ax::mojom::Role::kArticle:
case ax::mojom::Role::kListItem: case ax::mojom::Role::kListItem:
...@@ -491,43 +492,104 @@ bool AXNode::IsPosInSetUsedInRole() const { ...@@ -491,43 +492,104 @@ bool AXNode::IsPosInSetUsedInRole() const {
} }
} }
// Finds the position of this node within a list. // Finds the 0-based index of first element in the container (with same role as
// Only takes into account elements that have same role as node. // node) where kPosInSet is assigned. Returns -1 if there are no elements with
int32_t AXNode::PosInSet() const { // kPosInSet assigned
int32_t AXNode::FirstAssignedPosInSet() const {
AXNode* parent = GetUnignoredParent(); AXNode* parent = GetUnignoredParent();
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* candidate = parent->GetUnignoredChildAtIndex(i);
if (!(candidate->data().role == data().role))
continue;
if (candidate->HasIntAttribute(ax::mojom::IntAttribute::kPosInSet))
return i;
}
return -1;
}
// Calculates node's position relative to first PosInSet-assigned element.
// Returns node's 0-based index within container (relative to nodes with the
// same role) if no element assigned PosInSet
int32_t AXNode::RelativePosFromFirstAssigned(
int32_t first_assigned_index) const {
AXNode* parent = GetUnignoredParent();
// Find nodes index in container
int nodes_index = 0;
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* candidate = parent->GetUnignoredChildAtIndex(i);
if (candidate == this)
break;
if (candidate->data().role == data().role)
++nodes_index;
}
if (first_assigned_index == -1)
return nodes_index;
// Calculate relative position
return nodes_index - first_assigned_index;
}
// Finds the position of this node within its container, relative to other
// nodes with the same role.
// Returns 1-based position if present in container, and 0 if not.
int32_t AXNode::PosInSet() const {
// Error checks // Error checks
if (!IsSetSizePosInSetUsedInRole())
return 0;
AXNode* parent = GetUnignoredParent();
if (!parent) if (!parent)
return 0; return 0;
if (parent->data().role != ax::mojom::Role::kList) if (parent->data().role != ax::mojom::Role::kList)
return 0; return 0;
if (!IsPosInSetUsedInRole())
return 0;
int position = 0; // Check if kPosInSet assigned and return if provided
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) { if (HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
AXNode* candidate = parent->GetUnignoredChildAtIndex(i); return GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
if (candidate->data().role == data().role)
++position;
if (candidate == this)
return position;
} }
return 0;
// Caluclate PosInSet
// 1. Find index of first element (with same role) that has kPosInSet Assigned
// 2. Find relative position of this node compared to first assigned
// 3. The PosInSet of this node = PosInSet(first assigned) + relative position
int32_t first_assigned_index = FirstAssignedPosInSet();
int32_t relative_position =
RelativePosFromFirstAssigned(first_assigned_index);
// If no element assigned PosInSet, return this node's 1-based position in the
// container
if (first_assigned_index == -1)
return relative_position + 1;
return parent->GetUnignoredChildAtIndex(first_assigned_index)
->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet) +
relative_position;
} }
// Finds the total number of elements of the list this node is contained within. // Calculates the number of elements within node's container that have the
// Only counts the elements that have the same role as node. // same role as node.
int32_t AXNode::SetSize() const { int32_t AXNode::SetSize() const {
AXNode* parent = GetUnignoredParent();
// Error checks // Error checks
if (!IsSetSizePosInSetUsedInRole())
return 0;
AXNode* parent = GetUnignoredParent();
if (!parent) if (!parent)
return 0; return 0;
if (parent->data().role != ax::mojom::Role::kList) if (parent->data().role != ax::mojom::Role::kList)
return 0; return 0;
if (!IsPosInSetUsedInRole())
return 0;
// TODO (akihiroota): List objects should report SetSize
// Check if kSetSize assigned and return if provided
if (HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
return GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
// Check if kSetSize assigned in container and return if provided
if (parent->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
return parent->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
// Calculate SetSize
int count = 0; int count = 0;
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) { for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* child = parent->GetUnignoredChildAtIndex(i); AXNode* child = parent->GetUnignoredChildAtIndex(i);
......
...@@ -181,14 +181,13 @@ class AX_EXPORT AXNode final { ...@@ -181,14 +181,13 @@ class AX_EXPORT AXNode final {
return data().GetHtmlAttribute(attribute, value); return data().GetHtmlAttribute(attribute, value);
} }
// Returns the position of node within a list. Returns 1-based index if // Finds the position of this node within its container, relative to others
// contained within a list, and 0 if not. // with the same role.
// Returns 1-based index if present in container, and 0 if not.
int32_t PosInSet() const; int32_t PosInSet() const;
// Returns the total number of nodes in the same list as node. Returns 0 // Calculates the number of elements within node's container that have the
// if the node is not contained wihtin a list. // same role as node. Returns 0 if the node is not present wihtin a container.
int32_t SetSize() const; int32_t SetSize() const;
// Returns true if the aria-posinset attribute is used in node's role
bool IsPosInSetUsedInRole() const;
const std::string& GetInheritedStringAttribute( const std::string& GetInheritedStringAttribute(
ax::mojom::StringAttribute attribute) const; ax::mojom::StringAttribute attribute) const;
...@@ -256,6 +255,17 @@ class AX_EXPORT AXNode final { ...@@ -256,6 +255,17 @@ class AX_EXPORT AXNode final {
void IdVectorToNodeVector(std::vector<int32_t>& ids, void IdVectorToNodeVector(std::vector<int32_t>& ids,
std::vector<AXNode*>* nodes) const; std::vector<AXNode*>* nodes) const;
// Helpers for PosInset and SetSize
// Returns true if the PosInSet or SetSize attributes are used in node's role
bool IsSetSizePosInSetUsedInRole() const;
// Calculates 0-based index of first element with PosInSet assigned.
// Returns -1 if no elements assign PosInSet
int32_t FirstAssignedPosInSet() const;
// Calculates node's position relative to first PosInSet-assigned element.
// Returns node's 0-based index within container (relative to nodes with the
// same role) if no element assigned PosInSet
int32_t RelativePosFromFirstAssigned(int32_t earliest_index) const;
OwnerTree* tree_; // Owns this. OwnerTree* tree_; // Owns this.
int index_in_parent_; int index_in_parent_;
AXNode* parent_; AXNode* parent_;
......
...@@ -1472,8 +1472,41 @@ TEST(AXTreeTest, ChildTreeIds) { ...@@ -1472,8 +1472,41 @@ TEST(AXTreeTest, ChildTreeIds) {
EXPECT_EQ(0U, child_tree_93_nodes.size()); EXPECT_EQ(0U, child_tree_93_nodes.size());
} }
// Simple test for PosInSet and SetSize. // Tests PosInSet and SetSize int attributes work if assigned
TEST(AXTreeTest, GetPosInSetSetSize) { TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem;
tree_update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 2);
tree_update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 12);
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 5);
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 12);
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
tree_update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 9);
tree_update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 12);
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->PosInSet(), 2);
EXPECT_EQ(item1->SetSize(), 12);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->PosInSet(), 5);
EXPECT_EQ(item2->SetSize(), 12);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->PosInSet(), 9);
EXPECT_EQ(item3->SetSize(), 12);
}
// Tests that PosInSet and SetSize can be calculated if not assigned.
TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
AXTreeUpdate tree_update; AXTreeUpdate tree_update;
tree_update.root_id = 1; tree_update.root_id = 1;
tree_update.nodes.resize(4); tree_update.nodes.resize(4);
...@@ -1499,41 +1532,67 @@ TEST(AXTreeTest, GetPosInSetSetSize) { ...@@ -1499,41 +1532,67 @@ TEST(AXTreeTest, GetPosInSetSetSize) {
EXPECT_EQ(item3->SetSize(), 3); EXPECT_EQ(item3->SetSize(), 3);
} }
// A test for PosInSet and SetSize on a list containing various roles. // Tests PosInSet unassigned, while SetSize assigned in container
TEST(AXTreeTest, GetPosInSetSetSizeDiverseList) { TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
AXTreeUpdate tree_update; AXTreeUpdate tree_update;
tree_update.root_id = 1; tree_update.root_id = 1;
tree_update.nodes.resize(9); tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1; tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList; tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6, 7, 8, 9}; tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 7);
tree_update.nodes[1].id = 2; tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; tree_update.nodes[1].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].id = 3; tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem; tree_update.nodes[2].role = ax::mojom::Role::kListItem;
tree_update.nodes[3].id = 4; tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kMenuItem; tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
// Items should inherit SetSize from container if not specified
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->SetSize(), 7);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->SetSize(), 7);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->SetSize(), 7);
}
// Tests PosInSet and SetSize on a list containing various roles.
TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(9);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6, 7, 8, 9};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kMenuItem; // 1 of 2
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 2 of 3
tree_update.nodes[4].id = 5; tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; // 2 of 2
tree_update.nodes[5].id = 6; tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kArticle; tree_update.nodes[5].role = ax::mojom::Role::kArticle; // 1 of 2
tree_update.nodes[6].id = 7; tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kArticle; tree_update.nodes[6].role = ax::mojom::Role::kArticle; // 2 of 2
tree_update.nodes[7].id = 8; tree_update.nodes[7].id = 8;
tree_update.nodes[7].role = ax::mojom::Role::kListItem; tree_update.nodes[7].role = ax::mojom::Role::kListItem; // 3 of 3
tree_update.nodes[8].id = 9; tree_update.nodes[8].id = 9;
tree_update.nodes[8].role = ax::mojom::Role::kImage; tree_update.nodes[8].role = ax::mojom::Role::kImage; // 0 of 0
AXTree tree(tree_update); AXTree tree(tree_update);
AXNode* listitem1 = tree.GetFromId(2); AXNode* listitem1 = tree.GetFromId(2);
EXPECT_EQ(listitem1->PosInSet(), 1); EXPECT_EQ(listitem1->PosInSet(), 1);
EXPECT_EQ(listitem1->SetSize(), 3); EXPECT_EQ(listitem1->SetSize(), 3);
AXNode* listitem2 = tree.GetFromId(3); AXNode* listitem2 = tree.GetFromId(3);
EXPECT_EQ(listitem2->PosInSet(), 2); EXPECT_EQ(listitem2->PosInSet(), 1);
EXPECT_EQ(listitem2->SetSize(), 3); EXPECT_EQ(listitem2->SetSize(), 2);
AXNode* menuitem1 = tree.GetFromId(4); AXNode* menuitem1 = tree.GetFromId(4);
EXPECT_EQ(menuitem1->PosInSet(), 1); EXPECT_EQ(menuitem1->PosInSet(), 2);
EXPECT_EQ(menuitem1->SetSize(), 2); EXPECT_EQ(menuitem1->SetSize(), 3);
AXNode* menuitem2 = tree.GetFromId(5); AXNode* menuitem2 = tree.GetFromId(5);
EXPECT_EQ(menuitem2->PosInSet(), 2); EXPECT_EQ(menuitem2->PosInSet(), 2);
EXPECT_EQ(menuitem2->SetSize(), 2); EXPECT_EQ(menuitem2->SetSize(), 2);
...@@ -1547,8 +1606,89 @@ TEST(AXTreeTest, GetPosInSetSetSizeDiverseList) { ...@@ -1547,8 +1606,89 @@ TEST(AXTreeTest, GetPosInSetSetSizeDiverseList) {
EXPECT_EQ(listitem3->PosInSet(), 3); EXPECT_EQ(listitem3->PosInSet(), 3);
EXPECT_EQ(listitem3->SetSize(), 3); EXPECT_EQ(listitem3->SetSize(), 3);
AXNode* image1 = tree.GetFromId(9); AXNode* image1 = tree.GetFromId(9);
// Roles that do not use PosInSet or SetSize should return 0
EXPECT_EQ(image1->PosInSet(), 0); EXPECT_EQ(image1->PosInSet(), 0);
EXPECT_EQ(image1->SetSize(), 0); EXPECT_EQ(image1->SetSize(), 0);
} }
// Tests PosInSet and SetSize on a nested list
TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(7);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4, 7};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem;
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kList;
tree_update.nodes[3].child_ids = {5, 6};
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kListItem;
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kListItem;
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
AXNode* outer_item1 = tree.GetFromId(2);
EXPECT_EQ(outer_item1->PosInSet(), 1);
EXPECT_EQ(outer_item1->SetSize(), 3);
AXNode* outer_item2 = tree.GetFromId(3);
EXPECT_EQ(outer_item2->PosInSet(), 2);
EXPECT_EQ(outer_item2->SetSize(), 3);
// List object itself should not report posinset or setsize
// TODO (akihiroota): Lists should report setsize in the future
AXNode* inner_list = tree.GetFromId(4);
EXPECT_EQ(inner_list->PosInSet(), 0);
EXPECT_EQ(inner_list->SetSize(), 0);
AXNode* inner_item1 = tree.GetFromId(5);
EXPECT_EQ(inner_item1->PosInSet(), 1);
EXPECT_EQ(inner_item1->SetSize(), 2);
AXNode* inner_item2 = tree.GetFromId(6);
EXPECT_EQ(inner_item2->PosInSet(), 2);
EXPECT_EQ(inner_item2->SetSize(), 2);
AXNode* outer_item3 = tree.GetFromId(7);
EXPECT_EQ(outer_item3->PosInSet(), 3);
EXPECT_EQ(outer_item3->SetSize(), 3);
}
// Tests PosInSet can be calculated if one item specifies PosInSet, but others
// are missing.
TEST(AXTreeTest, TestPosInSetMissing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 20);
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 13);
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
// Item1 should have pos of 12, since item2 is assigned a pos of 13
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->PosInSet(), 12);
EXPECT_EQ(item1->SetSize(), 20);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->PosInSet(), 13);
EXPECT_EQ(item2->SetSize(), 20);
// Item2 should have pos of 14, since item2 is assigned a pos of 13
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->PosInSet(), 14);
EXPECT_EQ(item3->SetSize(), 20);
}
} // namespace ui } // namespace ui
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