Commit 413ca727 authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

Implement Cache for pos_in_set and set_size values

Improve upon previous implementation of GetPosInSet and GetSetSize
for AXNode, which computed pos_in_set and set_size values upon every
call of either function. Introduced cache for these values in AXTree,
which maps a node's id to a struct holding its pos_in_set and set_size
values. Cache is invalidated upon updates to the AXTree.

Change-Id: I70c0878b05b53b98648af1f2b76d1e1bf6e6d294
Reviewed-on: https://chromium-review.googlesource.com/c/1351782Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Cr-Commit-Position: refs/heads/master@{#613316}
parent ce92577b
......@@ -512,87 +512,78 @@ void AXNode::IdVectorToNodeVector(std::vector<int32_t>& ids,
}
}
// Returns true if the node's role uses PosInSet and SetSize
// Returns false otherwise.
bool AXNode::IsSetSizePosInSetUsedInRole() const {
switch (data().role) {
case ax::mojom::Role::kArticle:
case ax::mojom::Role::kListItem:
case ax::mojom::Role::kMenuItem:
case ax::mojom::Role::kMenuItemRadio:
case ax::mojom::Role::kTab:
case ax::mojom::Role::kMenuItemCheckBox:
case ax::mojom::Role::kTreeItem:
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kRadioButton:
return true;
// pos_in_set and set_size related functions.
// Uses AXTree's cache to calculate node's pos_in_set.
int32_t AXNode::GetPosInSet() {
// Only allow this to be called on nodes that can hold pos_in_set values,
// which are defined in the ARIA spec.
if (!IsItemLike(data().role))
return 0;
default:
return false;
}
// See AXTree::GetPosInSet
return tree_->GetPosInSet(*this);
}
// Returns true if a node's role matches with the role of its container.
// Uses AXTree's cache to calculate node's pos_in_set.
int32_t AXNode::GetSetSize() {
// Only allow this to be called on nodes that can hold set_size values, which
// are defined in the ARIA spec.
if (!(IsItemLike(data().role) || IsSetLike(data().role)))
return 0;
// See AXTree::GetSetSize
return tree_->GetSetSize(*this);
}
// Returns true if the role of ordered set matches the role of item.
// Returns false otherwise.
bool AXNode::ContainerRoleMatches(AXNode* container) const {
ax::mojom::Role container_role = container->data().role;
switch (data().role) {
case ax::mojom::Role::kArticle:
return container_role == ax::mojom::Role::kFeed;
bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
ax::mojom::Role item_role = data().role;
// Switch on role of ordered set
switch (ordered_set->data().role) {
case ax::mojom::Role::kFeed:
return item_role == ax::mojom::Role::kArticle;
case ax::mojom::Role::kListItem:
return container_role == ax::mojom::Role::kList ||
container_role == ax::mojom::Role::kGroup;
case ax::mojom::Role::kList:
return item_role == ax::mojom::Role::kListItem;
case ax::mojom::Role::kMenuItem:
return container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kGroup ||
container_role == ax::mojom::Role::kMenuBar;
case ax::mojom::Role::kGroup:
return item_role == ax::mojom::Role::kListItem ||
item_role == ax::mojom::Role::kMenuItem ||
item_role == ax::mojom::Role::kMenuItemRadio ||
item_role == ax::mojom::Role::kTreeItem;
case ax::mojom::Role::kMenuItemRadio:
return container_role == ax::mojom::Role::kGroup ||
container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kMenuBar;
case ax::mojom::Role::kMenu:
return item_role == ax::mojom::Role::kMenuItem ||
item_role == ax::mojom::Role::kMenuItemRadio ||
item_role == ax::mojom::Role::kMenuItemCheckBox;
case ax::mojom::Role::kTab:
return container_role == ax::mojom::Role::kTabList;
case ax::mojom::Role::kMenuBar:
return item_role == ax::mojom::Role::kMenuItem ||
item_role == ax::mojom::Role::kMenuItemRadio ||
item_role == ax::mojom::Role::kMenuItemCheckBox;
case ax::mojom::Role::kMenuItemCheckBox:
return container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kMenuBar;
case ax::mojom::Role::kTabList:
return item_role == ax::mojom::Role::kTab;
case ax::mojom::Role::kTreeItem:
return container_role == ax::mojom::Role::kTree ||
container_role == ax::mojom::Role::kGroup;
case ax::mojom::Role::kTree:
return item_role == ax::mojom::Role::kTreeItem;
case ax::mojom::Role::kListBoxOption:
return container_role == ax::mojom::Role::kListBox;
case ax::mojom::Role::kListBox:
return item_role == ax::mojom::Role::kListBoxOption;
case ax::mojom::Role::kRadioButton:
return container_role == ax::mojom::Role::kRadioGroup;
case ax::mojom::Role::kRadioGroup:
return item_role == ax::mojom::Role::kRadioButton;
default:
return false;
}
}
int32_t AXNode::GetPosInSet() {
int32_t pos = -1;
int32_t size = -1;
ComputeSetSizePosInSet(&pos, &size);
return pos;
}
int32_t AXNode::GetSetSize() {
int32_t pos = -1;
int32_t size = -1;
ComputeSetSizePosInSet(&pos, &size);
return size;
}
// Finds and returns a pointer to node's container.
// Is not required to have a role that matches node's role.
// Returns nullptr if node is not contained within container.
AXNode* AXNode::GetContainer() const {
// Finds ordered set that immediately contains node.
// Is not required for set's role to match node's role.
AXNode* AXNode::GetOrderedSet() const {
AXNode* result = parent();
// Continue walking up while parent is invalid, ignored, or is a generic
// container.
......@@ -604,126 +595,4 @@ AXNode* AXNode::GetContainer() const {
return result;
}
// Populates items vector with all nodes within container whose roles match.
void AXNode::PopulateContainerItems(AXNode* container,
AXNode* local_parent,
std::vector<AXNode*>& items) const {
// Stop searching current path if roles of local_parent and container match.
// Don't compare the container to itself.
if (!(container == local_parent))
if (local_parent->data().role == container->data().role)
return;
for (int i = 0; i < local_parent->child_count(); ++i) {
AXNode* child = local_parent->children_[i];
// Add child to items if role matches with root container's role.
if (child->ContainerRoleMatches(container))
items.push_back(child);
// Recurse if there is a generic container or is ignored.
if (child->data().role == ax::mojom::Role::kGenericContainer ||
child->data().role == ax::mojom::Role::kIgnored) {
PopulateContainerItems(container, child, items);
}
}
}
// Computes pos_in_set and set_size values for this node.
void AXNode::ComputeSetSizePosInSet(int32_t* out_pos_in_set,
int32_t* out_set_size) {
// Error checks
AXNode* container = GetContainer();
if (!(container && IsSetSizePosInSetUsedInRole() &&
ContainerRoleMatches(container))) {
*out_pos_in_set = 0;
*out_set_size = 0;
return;
}
// Find all items within parent container and add to vector.
std::vector<AXNode*> items;
PopulateContainerItems(container, container, items);
// Necessary for calculating set_size. Keeps track of largest assigned
// kSetSize for each role.
std::unordered_map<ax::mojom::Role, int> largest_assigned_set_size;
// Iterate over vector of items and calculate pos_in_set and set_size for
// each. Items is not guaranteed to be populated with items of the same role.
// Use dictionary that maps role to frequency to calculate pos_in_set.
std::unordered_map<ax::mojom::Role, int> role_counts;
AXNode* node;
ax::mojom::Role node_role;
// Compute pos_in_set values.
for (unsigned int i = 0; i < items.size(); ++i) {
node = items[i];
node_role = node->data().role;
int32_t pos_in_set_value = 0;
if (role_counts.find(node_role) == role_counts.end())
// This is the first node with its role.
pos_in_set_value = 1;
else {
// This is the next node with its role.
pos_in_set_value = role_counts[node_role] + 1;
}
// Check if node has kPosInSet assigned. This assignment takes precedence
// over previous assignment.
if (node->HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
pos_in_set_value =
node->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
// If invalid assignment (decrease or duplicate), adjust value.
if (pos_in_set_value <= role_counts[node_role]) {
pos_in_set_value = role_counts[node_role] + 1;
}
}
// Assign pos_in_set and update role counts.
if (node == this) {
*out_pos_in_set = pos_in_set_value;
}
role_counts[node_role] = pos_in_set_value;
// Check if kSetSize is assigned and update if it's the largest assigned
// kSetSize.
if (node->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
largest_assigned_set_size[node_role] =
std::max(largest_assigned_set_size[node_role],
node->GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
// Compute set_size values.
for (unsigned int j = 0; j < items.size(); ++j) {
node = items[j];
node_role = node->data().role;
// TODO (akihiroota): List objects should report SetSize
// The SetSize of a node is the maximum of the following candidate values:
// 1. The PosInSet of the last value in the container (with same role as
// node's)
// 2. The Largest assigned SetSize in the container
// 3. The SetSize assigned within the node's container
int32_t pos_candidate = role_counts[node_role];
int32_t largest_set_size_candidate = 0;
if (largest_assigned_set_size.find(node_role) !=
largest_assigned_set_size.end()) {
largest_set_size_candidate = largest_assigned_set_size[node_role];
}
int32_t container_candidate = 0;
if (container->HasIntAttribute(ax::mojom::IntAttribute::kSetSize)) {
container_candidate =
container->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
// Assign set_size
if (node == this) {
*out_set_size =
std::max(std::max(pos_candidate, largest_set_size_candidate),
container_candidate);
}
}
}
} // namespace ui
......@@ -32,6 +32,9 @@ class AX_EXPORT AXNode final {
virtual AXTableInfo* GetTableInfo(const AXNode* table_node) const = 0;
// See AXTree.
virtual AXNode* GetFromId(int32_t id) const = 0;
virtual int32_t GetPosInSet(const AXNode& item) = 0;
virtual int32_t GetSetSize(const AXNode& node) = 0;
};
// The constructor requires a parent, id, and index in parent, but
......@@ -186,6 +189,12 @@ class AX_EXPORT AXNode final {
// PosInSet and SetSize public methods
int32_t GetPosInSet();
int32_t GetSetSize();
// Finds and returns a pointer to ordered set containing node.
AXNode* GetOrderedSet() const;
// Helpers for GetPosInSet and GetSetSize.
// Returns true if the role of ordered set matches the role of item.
// Returns false otherwise.
bool SetRoleMatchesItemRole(const AXNode* ordered_set) const;
const std::string& GetInheritedStringAttribute(
ax::mojom::StringAttribute attribute) const;
......@@ -262,21 +271,6 @@ class AX_EXPORT AXNode final {
void IdVectorToNodeVector(std::vector<int32_t>& ids,
std::vector<AXNode*>* nodes) const;
// Helpers for GetPosInSet and GetSetSize.
// Returns true if the role of parent container matches the role of node.
// Returns false otherwise.
bool ContainerRoleMatches(AXNode* parent) const;
// Returns true if the node's role uses PosInSet and SetSize
// Returns false otherwise.
bool IsSetSizePosInSetUsedInRole() const;
// Finds and returns a pointer to node's container.
AXNode* GetContainer() const;
// Populates items vector with all nodes within container whose roles match.
void PopulateContainerItems(AXNode* container,
AXNode* local_parent,
std::vector<AXNode*>& items) const;
// Computes pos_in_set and set_size values for this node.
void ComputeSetSizePosInSet(int32_t* out_pos_in_set, int32_t* out_set_size);
OwnerTree* tree_; // Owns this.
int index_in_parent_;
......
......@@ -160,6 +160,23 @@ bool IsImage(const ax::mojom::Role role) {
}
}
bool IsItemLike(const ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kArticle:
case ax::mojom::Role::kListItem:
case ax::mojom::Role::kMenuItem:
case ax::mojom::Role::kMenuItemRadio:
case ax::mojom::Role::kTab:
case ax::mojom::Role::kMenuItemCheckBox:
case ax::mojom::Role::kTreeItem:
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kRadioButton:
return true;
default:
return false;
}
}
bool IsLink(const ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kDocBackLink:
......@@ -241,6 +258,23 @@ bool IsRowContainer(const ax::mojom::Role role) {
}
}
bool IsSetLike(const ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kFeed:
case ax::mojom::Role::kList:
case ax::mojom::Role::kGroup:
case ax::mojom::Role::kMenu:
case ax::mojom::Role::kMenuBar:
case ax::mojom::Role::kTabList:
case ax::mojom::Role::kTree:
case ax::mojom::Role::kListBox:
case ax::mojom::Role::kRadioGroup:
return true;
default:
return false;
}
}
bool IsTableHeader(ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kColumnHeader:
......
......@@ -41,6 +41,10 @@ AX_EXPORT bool IsHeadingOrTableHeader(const ax::mojom::Role role);
// Returns true if the provided role belongs to an image, graphic, canvas, etc.
AX_EXPORT bool IsImage(const ax::mojom::Role role);
// Returns true if the provided role is item-like, specifically if it can hold
// pos_in_set and set_size values.
AX_EXPORT bool IsItemLike(const ax::mojom::Role role);
// Returns true if the provided role belongs to a link.
AX_EXPORT bool IsLink(const ax::mojom::Role role);
......@@ -61,6 +65,10 @@ AX_EXPORT bool IsMenuRelated(const ax::mojom::Role role);
// table or grid row.
AX_EXPORT bool IsRowContainer(const ax::mojom::Role role);
// Returns true if the provided role is ordered-set like, specifically if it
// can hold set_size values.
AX_EXPORT bool IsSetLike(const ax::mojom::Role role);
// Returns true if the provided role belongs to a table header.
AX_EXPORT bool IsTableHeader(ax::mojom::Role role);
......
......@@ -13,6 +13,7 @@
#include "base/strings/stringprintf.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_table_info.h"
#include "ui/gfx/transform.h"
......@@ -468,6 +469,9 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
this, root_->id() != old_root_id, changes);
}
// Clear list_info_map_
ordered_set_info_map_.clear();
return true;
}
......@@ -878,4 +882,133 @@ int32_t AXTree::GetNextNegativeInternalNodeId() {
return return_value;
}
// Populates items vector with all items within ordered set whose roles match.
void AXTree::PopulateOrderedSetItems(const AXNode* ordered_set,
const AXNode* local_parent,
std::vector<const AXNode*>& items) const {
// Stop searching current path if roles of local_parent and ordered set match.
// Don't compare the container to itself.
if (!(ordered_set == local_parent)) {
if (local_parent->data().role == ordered_set->data().role)
return;
}
for (int i = 0; i < local_parent->child_count(); ++i) {
const AXNode* child = local_parent->GetUnignoredChildAtIndex(i);
// Add child to items if role matches with ordered set's role.
if (child->SetRoleMatchesItemRole(ordered_set))
items.push_back(child);
// Recurse if there is a generic container or is ignored.
if (child->data().role == ax::mojom::Role::kGenericContainer ||
child->data().role == ax::mojom::Role::kIgnored) {
PopulateOrderedSetItems(ordered_set, child, items);
}
}
}
// Given an ordered_set, compute pos_in_set and set_size for all of its items
// and store values in cache.
void AXTree::ComputeSetSizePosInSetAndCache(const AXNode* ordered_set) {
// Default ordered_set's pos_in_set and set_size to 0.
ordered_set_info_map_[ordered_set->id()] = OrderedSetInfo();
// Find all items within ordered_set and add to vector.
std::vector<const AXNode*> items;
PopulateOrderedSetItems(ordered_set, ordered_set, items);
// Keep track of the number of elements ordered_set has
int32_t num_elements = 0;
// Necessary for calculating set_size.
int32_t largest_assigned_set_size = 0;
// Compute pos_in_set_values.
for (size_t i = 0; i < items.size(); ++i) {
const AXNode* item = items[i];
ordered_set_info_map_[item->id()] = OrderedSetInfo();
int32_t pos_in_set_value = 0;
pos_in_set_value = num_elements + 1;
// Check if item has a valid kPosInSet assignment, which takes precedence
// over previous assignment. Invalid assignments are decreasing or
// duplicates, and should be ignored.
pos_in_set_value =
std::max(pos_in_set_value,
item->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
// Assign pos_in_set and update role counts.
ordered_set_info_map_[item->id()].pos_in_set = pos_in_set_value;
num_elements = pos_in_set_value;
// Check if kSetSize is assigned and update if it's the largest assigned
// kSetSize.
if (item->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
largest_assigned_set_size =
std::max(largest_assigned_set_size,
item->GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
// Compute set_size value.
// The SetSize of an ordered set (and all of its items) is the maximum of the
// following candidate values:
// 1. The PosInSet of the last item in the ordered set
// 2. The Largest assigned SetSize in the ordered set.
// 3. The SetSize assigned within the ordered set.
int32_t pos_candidate = num_elements;
int32_t largest_set_size_candidate = largest_assigned_set_size;
int32_t ordered_set_candidate = 0;
if (ordered_set->HasIntAttribute(ax::mojom::IntAttribute::kSetSize)) {
ordered_set_candidate =
ordered_set->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
int32_t set_size_value =
std::max(std::max(pos_candidate, largest_set_size_candidate),
ordered_set_candidate);
// Assign set_size to ordered set
ordered_set_info_map_[ordered_set->id()].set_size = set_size_value;
// Assign set_size to items
for (size_t j = 0; j < items.size(); ++j) {
const AXNode* item = items[j];
ordered_set_info_map_[item->id()].set_size = set_size_value;
}
}
// Returns the pos_in_set of item. Looks in ordered_set_info_map_ for cached
// value. Calculates pos_in_set and set_size for item (and all other items in
// the same ordered set) if no value is present in the cache.
// This function is guaranteed to be only called on nodes that can hold
// pos_in_set values, minimizing the size of the cache.
int32_t AXTree::GetPosInSet(const AXNode& item) {
// If item's id is not in the cache, compute it.
if (ordered_set_info_map_.find(item.id()) == ordered_set_info_map_.end())
ComputeSetSizePosInSetAndCache(item.GetOrderedSet());
return ordered_set_info_map_[item.id()].pos_in_set;
}
// Returns the set_size of node. node could be an ordered set or an item.
// Looks in ordered_set_info_map_ for cached value. Calculates pos_inset_set
// and set_size for all nodes in same ordered set if no value is present in the
// cache.
// This function is guaranteed to be only called on nodes that can hold
// set_size values, minimizing the size of the cache.
int32_t AXTree::GetSetSize(const AXNode& node) {
const AXNode* ordered_set;
// If node's id is not in the cache, compute it.
if (ordered_set_info_map_.find(node.id()) == ordered_set_info_map_.end()) {
// If node is item-like, find its outerlying ordered set
if (IsItemLike(node.data().role))
ordered_set = node.GetOrderedSet();
// If its set-like, then it is the ordered set
else
ordered_set = &node;
ComputeSetSizePosInSetAndCache(ordered_set);
}
return ordered_set_info_map_[node.id()].set_size;
}
} // namespace ui
......@@ -257,6 +257,19 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// conflict with positive-numbered node IDs from tree sources.
int32_t GetNextNegativeInternalNodeId();
// Returns the pos_in_set of item. Looks in ordered_set_info_map_ for cached
// value. Calculates pos_in_set and set_size for item (and all other items in
// the same ordered set) if no value is present in the cache.
// This function is guaranteed to be only called on nodes that can hold
// pos_in_set values, minimizing the size of the cache.
int32_t GetPosInSet(const AXNode& item) override;
// Returns the set_size of node. Looks in ordered_set_info_map_ for cached
// value. Calculates pos_inset_set and set_size for node (and all other nodes
// in the same ordered set) if no value is present in the cache.
// This function is guaranteed to be only called on nodes that can hold
// set_size values, minimizing the size of the cache.
int32_t GetSetSize(const AXNode& node) override;
private:
friend class AXTableInfoTest;
......@@ -340,6 +353,27 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// this code to be unit-tested on other platforms (for example, more
// code sanitizers run on Linux).
bool enable_extra_mac_nodes_ = false;
// Contains pos_in_set and set_size data for an AXNode.
struct OrderedSetInfo {
int32_t pos_in_set;
int32_t set_size;
OrderedSetInfo() : pos_in_set(0), set_size(0) {}
~OrderedSetInfo() {}
};
// Populates items vector with all items within ordered_set whose roles match.
void PopulateOrderedSetItems(const AXNode* ordered_set,
const AXNode* local_parent,
std::vector<const AXNode*>& items) const;
// Helper for GetPosInSet and GetSetSize. Computes the pos_in_set and set_size
// values of all items in ordered_set and caches those values.
void ComputeSetSizePosInSetAndCache(const AXNode* ordered_set);
// Map from node ID to OrderedSetInfo, if the given node
// is an element within an ordered set.
// Invalidated every time the tree is updated.
mutable std::unordered_map<int32_t, OrderedSetInfo> ordered_set_info_map_;
};
} // namespace ui
......
......@@ -1472,7 +1472,7 @@ TEST(AXTreeTest, ChildTreeIds) {
EXPECT_EQ(0U, child_tree_93_nodes.size());
}
// Tests PosInSet and SetSize int attributes work if assigned.
// Tests GetPosInSet and GetSetSize return the assigned int attribute values.
TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1505,7 +1505,7 @@ TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
EXPECT_EQ(item3->GetSetSize(), 12);
}
// Tests that PosInSet and SetSize can be calculated if not assigned.
// Tests that pos_in_set and set_size can be calculated if not assigned.
TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1532,7 +1532,8 @@ TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
EXPECT_EQ(item3->GetSetSize(), 3);
}
// Tests PosInSet unassigned, while SetSize assigned in container.
// Tests pos_in_set can be calculated if unassigned, and set_size can be
// assigned on the outerlying ordered set.
TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1549,7 +1550,7 @@ TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
// Items should inherit SetSize from container if not specified.
// Items should inherit set_size from ordered set if not specified.
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetSetSize(), 7);
AXNode* item2 = tree.GetFromId(3);
......@@ -1558,60 +1559,45 @@ TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
EXPECT_EQ(item3->GetSetSize(), 7);
}
// Tests PosInSet and SetSize on a list containing various roles.
// Roles for items and associated container should match up.
// Tests GetPosInSet and GetSetSize on a list containing various roles.
// Roles for items and associated ordered set should match up.
TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(9);
tree_update.nodes.resize(6);
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[0].role = ax::mojom::Role::kMenu;
tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
tree_update.nodes[1].role = ax::mojom::Role::kMenuItem; // 1 of 4
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kMenuItem; // 0 of 0
tree_update.nodes[2].role = ax::mojom::Role::kMenuItem; // 2 of 4
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 2 of 3
tree_update.nodes[3].role = ax::mojom::Role::kMenuItemRadio; // 3 of 4
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; // 0 of 0
tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; // 4 of 4
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kArticle; // 0 of 0
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kArticle; // 0 of 0
tree_update.nodes[7].id = 8;
tree_update.nodes[7].role = ax::mojom::Role::kListItem; // 3 of 3
tree_update.nodes[8].id = 9;
tree_update.nodes[8].role = ax::mojom::Role::kImage; // 0 of 0
tree_update.nodes[5].role = ax::mojom::Role::kTab; // 0 of 0
AXTree tree(tree_update);
AXNode* listitem1 = tree.GetFromId(2);
EXPECT_EQ(listitem1->GetPosInSet(), 1);
EXPECT_EQ(listitem1->GetSetSize(), 3);
AXNode* menuitem1 = tree.GetFromId(3);
EXPECT_EQ(menuitem1->GetPosInSet(), 0);
EXPECT_EQ(menuitem1->GetSetSize(), 0);
AXNode* listitem2 = tree.GetFromId(4);
EXPECT_EQ(listitem2->GetPosInSet(), 2);
EXPECT_EQ(listitem2->GetSetSize(), 3);
AXNode* menuitem2 = tree.GetFromId(5);
EXPECT_EQ(menuitem2->GetPosInSet(), 0);
EXPECT_EQ(menuitem2->GetSetSize(), 0);
AXNode* article1 = tree.GetFromId(6);
EXPECT_EQ(article1->GetPosInSet(), 0);
EXPECT_EQ(article1->GetSetSize(), 0);
AXNode* article2 = tree.GetFromId(7);
EXPECT_EQ(article2->GetPosInSet(), 0);
EXPECT_EQ(article2->GetSetSize(), 0);
AXNode* listitem3 = tree.GetFromId(8);
EXPECT_EQ(listitem3->GetPosInSet(), 3);
EXPECT_EQ(listitem3->GetSetSize(), 3);
AXNode* image = tree.GetFromId(9);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 4);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 4);
AXNode* radio = tree.GetFromId(4);
EXPECT_EQ(radio->GetPosInSet(), 3);
EXPECT_EQ(radio->GetSetSize(), 4);
AXNode* item3 = tree.GetFromId(5);
EXPECT_EQ(item3->GetPosInSet(), 4);
EXPECT_EQ(item3->GetSetSize(), 4);
AXNode* image = tree.GetFromId(6);
EXPECT_EQ(image->GetPosInSet(), 0);
EXPECT_EQ(image->GetSetSize(), 0);
}
// Tests PosInSet and SetSize on a nested list.
// Tests GetPosInSet and GetSetSize on a nested list.
TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1641,12 +1627,6 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
EXPECT_EQ(outer_item2->GetPosInSet(), 2);
EXPECT_EQ(outer_item2->GetSetSize(), 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->GetPosInSet(), 0);
EXPECT_EQ(inner_list->GetSetSize(), 0);
AXNode* inner_item1 = tree.GetFromId(5);
EXPECT_EQ(inner_item1->GetPosInSet(), 1);
EXPECT_EQ(inner_item1->GetSetSize(), 2);
......@@ -1659,8 +1639,8 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
EXPECT_EQ(outer_item3->GetSetSize(), 3);
}
// Tests PosInSet can be calculated if one item specifies PosInSet, but others
// are missing.
// Tests pos_in_set can be calculated if one item specifies pos_in_set, but
// other assignments are missing.
TEST(AXTreeTest, TestPosInSetMissing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1691,7 +1671,7 @@ TEST(AXTreeTest, TestPosInSetMissing) {
EXPECT_EQ(item3->GetSetSize(), 20);
}
// A more difficult test that invovles missing PosInSet and SetSize values.
// A more difficult test that invovles missing pos_in_set and set_size values.
TEST(AXTreeTest, TestSetSizePosInSetMissingDifficult) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1732,7 +1712,7 @@ TEST(AXTreeTest, TestSetSizePosInSetMissingDifficult) {
EXPECT_EQ(item5->GetSetSize(), 11);
}
// Tests that code overwrites decreasing SetSize assignments to largest of
// Tests that code overwrites decreasing set_size assignments to largest of
// assigned values.
TEST(AXTreeTest, TestSetSizeDecreasing) {
AXTreeUpdate tree_update;
......@@ -1762,7 +1742,7 @@ TEST(AXTreeTest, TestSetSizeDecreasing) {
EXPECT_EQ(item3->GetSetSize(), 5);
}
// Tests that code overwrites decreasing PosInSet values.
// Tests that code overwrites decreasing pos_in_set values.
TEST(AXTreeTest, TestPosInSetDecreasing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1791,7 +1771,7 @@ TEST(AXTreeTest, TestPosInSetDecreasing) {
EXPECT_EQ(item3->GetSetSize(), 8);
}
// Tests that code overwrites duplicate PosInSet values. Note this case is
// Tests that code overwrites duplicate pos_in_set values. Note this case is
// tricky; an update to the second element causes an update to the third
// element.
TEST(AXTreeTest, TestPosInSetDuplicates) {
......@@ -1823,7 +1803,7 @@ TEST(AXTreeTest, TestPosInSetDuplicates) {
EXPECT_EQ(item3->GetSetSize(), 8);
}
// Tests PosInSet and SetSize when some list items are nested in a generic
// Tests GetPosInSet and GetSetSize when some list items are nested in a generic
// container.
TEST(AXTreeTest, TestSetSizePosInSetNestedContainer) {
AXTreeUpdate tree_update;
......@@ -1868,4 +1848,183 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedContainer) {
EXPECT_EQ(item4->GetSetSize(), 4);
}
// Tests GetSetSize and GetPosInSet are correct, even when list items change.
// This test is directed at the caching functionality of pos_in_set and
// set_size. Tests that previously calculated values are not used after
// tree is updated.
TEST(AXTreeTest, TestSetSizePosInSetDeleteItem) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(4);
initial_state.nodes[0].id = 1;
initial_state.nodes[0].role = ax::mojom::Role::kList;
initial_state.nodes[0].child_ids = {2, 3, 4};
initial_state.nodes[1].id = 2;
initial_state.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
initial_state.nodes[2].id = 3;
initial_state.nodes[2].role = ax::mojom::Role::kListItem; // 2 of 3
initial_state.nodes[3].id = 4;
initial_state.nodes[3].role = ax::mojom::Role::kListItem; // 3 of 3
AXTree tree(initial_state);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 3);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 3);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 3);
EXPECT_EQ(item3->GetSetSize(), 3);
// TreeUpdates only need to describe what changed in tree.
AXTreeUpdate update = initial_state;
update.nodes.resize(1);
update.nodes[0].child_ids = {2, 4}; // Delete item 2 of 3 from list.
EXPECT_TRUE(tree.Unserialize(update));
AXNode* new_item1 = tree.GetFromId(2);
EXPECT_EQ(new_item1->GetPosInSet(), 1);
EXPECT_EQ(new_item1->GetSetSize(), 2);
AXNode* new_item2 = tree.GetFromId(4);
EXPECT_EQ(new_item2->GetPosInSet(), 2);
EXPECT_EQ(new_item2->GetSetSize(), 2);
}
// Tests GetSetSize and GetPosInSet are correct, even when list items change.
// This test adds an item to the front of a list, which invalidates previously
// calculated pos_in_set and set_size values. Tests that old values are not
// used after tree is updated.
TEST(AXTreeTest, TestSetSizePosInSetAddItem) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(4);
initial_state.nodes[0].id = 1;
initial_state.nodes[0].role = ax::mojom::Role::kList;
initial_state.nodes[0].child_ids = {2, 3, 4};
initial_state.nodes[1].id = 2;
initial_state.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
initial_state.nodes[2].id = 3;
initial_state.nodes[2].role = ax::mojom::Role::kListItem; // 2 of 3
initial_state.nodes[3].id = 4;
initial_state.nodes[3].role = ax::mojom::Role::kListItem; // 3 of 3
AXTree tree(initial_state);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 3);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 3);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 3);
EXPECT_EQ(item3->GetSetSize(), 3);
// Insert item at beginning of list
AXTreeUpdate update = initial_state;
update.nodes.resize(2);
update.nodes[0].id = 1;
update.nodes[0].child_ids = {5, 2, 3, 4};
update.nodes[1].id = 5;
update.nodes[1].role = ax::mojom::Role::kListItem;
EXPECT_TRUE(tree.Unserialize(update));
AXNode* new_item1 = tree.GetFromId(5);
EXPECT_EQ(new_item1->GetPosInSet(), 1);
EXPECT_EQ(new_item1->GetSetSize(), 4);
AXNode* new_item2 = tree.GetFromId(2);
EXPECT_EQ(new_item2->GetPosInSet(), 2);
EXPECT_EQ(new_item2->GetSetSize(), 4);
AXNode* new_item3 = tree.GetFromId(3);
EXPECT_EQ(new_item3->GetPosInSet(), 3);
EXPECT_EQ(new_item3->GetSetSize(), 4);
AXNode* new_item4 = tree.GetFromId(4);
EXPECT_EQ(new_item4->GetPosInSet(), 4);
EXPECT_EQ(new_item4->GetSetSize(), 4);
}
// Tests that the outerlying ordered set reports its set_size. Ordered sets
// should not report a pos_in_set value other than 0, since they are not
// considered to be items within a set (even when nested).
TEST(AXTreeTest, TestOrderedSetReportsSetSize) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(12);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList; // set_size = 3
tree_update.nodes[0].child_ids = {2, 3, 4, 7, 8, 9, 12};
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::kListItem; // 2 of 3
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kList; // set_size = 2
tree_update.nodes[3].child_ids = {5, 6};
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kListItem; // 1 of 2
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kListItem; // 2 of 2
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kListItem; // 3 of 3
tree_update.nodes[7].id = 8;
tree_update.nodes[7].role = ax::mojom::Role::kList; // set_size = 0
tree_update.nodes[8].id = 9;
tree_update.nodes[8].role =
ax::mojom::Role::kList; // set_size = 1 because only 1 item whose role
// matches
tree_update.nodes[8].child_ids = {10, 11};
tree_update.nodes[9].id = 10;
tree_update.nodes[9].role = ax::mojom::Role::kArticle;
tree_update.nodes[10].id = 11;
tree_update.nodes[10].role = ax::mojom::Role::kListItem;
tree_update.nodes[11].id = 12;
tree_update.nodes[11].role = ax::mojom::Role::kList;
tree_update.nodes[11].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 5);
AXTree tree(tree_update);
AXNode* outer_list = tree.GetFromId(1);
EXPECT_EQ(outer_list->GetPosInSet(), 0); // Ordered sets have pos of 0
EXPECT_EQ(outer_list->GetSetSize(), 3);
AXNode* outer_list_item1 = tree.GetFromId(2);
EXPECT_EQ(outer_list_item1->GetPosInSet(), 1);
EXPECT_EQ(outer_list_item1->GetSetSize(), 3);
AXNode* outer_list_item2 = tree.GetFromId(3);
EXPECT_EQ(outer_list_item2->GetPosInSet(), 2);
EXPECT_EQ(outer_list_item2->GetSetSize(), 3);
AXNode* outer_list_item3 = tree.GetFromId(7);
EXPECT_EQ(outer_list_item3->GetPosInSet(), 3);
EXPECT_EQ(outer_list_item3->GetSetSize(), 3);
AXNode* inner_list1 = tree.GetFromId(4);
EXPECT_EQ(inner_list1->GetPosInSet(),
0); // Ordered sets have pos of 0, even when nested
EXPECT_EQ(inner_list1->GetSetSize(), 2);
AXNode* inner_list1_item1 = tree.GetFromId(5);
EXPECT_EQ(inner_list1_item1->GetPosInSet(), 1);
EXPECT_EQ(inner_list1_item1->GetSetSize(), 2);
AXNode* inner_list1_item2 = tree.GetFromId(6);
EXPECT_EQ(inner_list1_item2->GetPosInSet(), 2);
EXPECT_EQ(inner_list1_item2->GetSetSize(), 2);
AXNode* inner_list2 = tree.GetFromId(8); // Empty list
EXPECT_EQ(inner_list2->GetPosInSet(), 0);
EXPECT_EQ(inner_list2->GetSetSize(), 0);
AXNode* inner_list3 = tree.GetFromId(9);
EXPECT_EQ(inner_list3->GetPosInSet(), 0);
EXPECT_EQ(inner_list3->GetSetSize(), 1); // Only 1 item whose role matches
AXNode* inner_list3_article1 = tree.GetFromId(10);
EXPECT_EQ(inner_list3_article1->GetPosInSet(), 0);
EXPECT_EQ(inner_list3_article1->GetSetSize(), 0);
AXNode* inner_list3_item1 = tree.GetFromId(11);
EXPECT_EQ(inner_list3_item1->GetPosInSet(), 1);
EXPECT_EQ(inner_list3_item1->GetSetSize(), 1);
AXNode* inner_list4 = tree.GetFromId(12);
EXPECT_EQ(inner_list4->GetPosInSet(), 0);
// Even though list is empty, kSetSize attribute was set, so it takes
// precedence
EXPECT_EQ(inner_list4->GetSetSize(), 5);
}
} // 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