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/1351782
Reviewed-by: Dominic Mazzoni <[email protected]>
Commit-Queue: Akihiro Ota <[email protected]>
Cr-Commit-Position: refs/heads/master@{#613316}
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index bc78724..2c95578 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -512,87 +512,78 @@
}
}
-// 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;
-
- default:
- return false;
- }
-}
-
-// Returns true if a node's role matches with the role of its container.
-// 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;
-
- case ax::mojom::Role::kListItem:
- return container_role == ax::mojom::Role::kList ||
- container_role == ax::mojom::Role::kGroup;
-
- 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::kMenuItemRadio:
- return container_role == ax::mojom::Role::kGroup ||
- container_role == ax::mojom::Role::kMenu ||
- container_role == ax::mojom::Role::kMenuBar;
-
- case ax::mojom::Role::kTab:
- return container_role == ax::mojom::Role::kTabList;
-
- case ax::mojom::Role::kMenuItemCheckBox:
- return container_role == ax::mojom::Role::kMenu ||
- container_role == ax::mojom::Role::kMenuBar;
-
- case ax::mojom::Role::kTreeItem:
- return container_role == ax::mojom::Role::kTree ||
- container_role == ax::mojom::Role::kGroup;
-
- case ax::mojom::Role::kListBoxOption:
- return container_role == ax::mojom::Role::kListBox;
-
- case ax::mojom::Role::kRadioButton:
- return container_role == ax::mojom::Role::kRadioGroup;
-
- default:
- return false;
- }
-}
-
+// pos_in_set and set_size related functions.
+// Uses AXTree's cache to calculate node's pos_in_set.
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;
+ // 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;
+
+ // See AXTree::GetPosInSet
+ return tree_->GetPosInSet(*this);
}
-// 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 {
+// 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::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::kList:
+ return item_role == ax::mojom::Role::kListItem;
+
+ 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::kMenu:
+ return item_role == ax::mojom::Role::kMenuItem ||
+ item_role == ax::mojom::Role::kMenuItemRadio ||
+ item_role == ax::mojom::Role::kMenuItemCheckBox;
+
+ 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::kTabList:
+ return item_role == ax::mojom::Role::kTab;
+
+ case ax::mojom::Role::kTree:
+ return item_role == ax::mojom::Role::kTreeItem;
+
+ case ax::mojom::Role::kListBox:
+ return item_role == ax::mojom::Role::kListBoxOption;
+
+ case ax::mojom::Role::kRadioGroup:
+ return item_role == ax::mojom::Role::kRadioButton;
+
+ default:
+ return false;
+ }
+}
+
+// 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 @@
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
diff --git a/ui/accessibility/ax_node.h b/ui/accessibility/ax_node.h
index 3349c38..7b2893c5 100644
--- a/ui/accessibility/ax_node.h
+++ b/ui/accessibility/ax_node.h
@@ -32,6 +32,9 @@
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 @@
// 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 @@
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_;
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index 0c851bb..a73a9e6 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -160,6 +160,23 @@
}
}
+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 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:
diff --git a/ui/accessibility/ax_role_properties.h b/ui/accessibility/ax_role_properties.h
index c6a8026..d9a1e73 100644
--- a/ui/accessibility/ax_role_properties.h
+++ b/ui/accessibility/ax_role_properties.h
@@ -41,6 +41,10 @@
// 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 @@
// 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);
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index 1b5932fb..b9fb4ae 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -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 @@
this, root_->id() != old_root_id, changes);
}
+ // Clear list_info_map_
+ ordered_set_info_map_.clear();
+
return true;
}
@@ -878,4 +882,133 @@
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
diff --git a/ui/accessibility/ax_tree.h b/ui/accessibility/ax_tree.h
index fb8f2b74..1c36cd2 100644
--- a/ui/accessibility/ax_tree.h
+++ b/ui/accessibility/ax_tree.h
@@ -257,6 +257,19 @@
// 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 @@
// 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
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 109eda9..b5a6452 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -1472,7 +1472,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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