#include "ui/accessibility/ax_tree.h"
#include <stddef.h>
#include <numeric>
#include <utility>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/strings/stringprintf.h"
#include "base/timer/elapsed_timer.h"
#include "components/crash/core/common/crash_key.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_event.h"
#include "ui/accessibility/ax_language_detection.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_selection.h"
#include "ui/accessibility/ax_table_info.h"
#include "ui/accessibility/ax_tree_observer.h"
#include "ui/gfx/geometry/transform.h"
namespace ui {
namespace {
std::string TreeToStringHelper(const AXNode* node, int indent, bool verbose) {
if (!node)
return "";
return std::accumulate(
node->children().cbegin(), node->children().cend(),
std::string(2 * indent, ' ') + node->data().ToString(verbose) + "\n",
[indent, verbose](const std::string& str, const auto* child) {
return str + TreeToStringHelper(child, indent + 1, verbose);
});
}
template <typename K, typename V>
bool KeyValuePairsKeysMatch(std::vector<std::pair<K, V>> pairs1,
std::vector<std::pair<K, V>> pairs2) {
if (pairs1.size() != pairs2.size())
return false;
for (size_t i = 0; i < pairs1.size(); ++i) {
if (pairs1[i].first != pairs2[i].first)
return false;
}
return true;
}
template <typename K, typename V>
std::map<K, V> MapFromKeyValuePairs(std::vector<std::pair<K, V>> pairs) {
std::map<K, V> result;
for (size_t i = 0; i < pairs.size(); ++i)
result[pairs[i].first] = pairs[i].second;
return result;
}
template <typename K, typename V, typename F>
void CallIfAttributeValuesChanged(const std::vector<std::pair<K, V>>& pairs1,
const std::vector<std::pair<K, V>>& pairs2,
const V& empty_value,
F callback) {
if (KeyValuePairsKeysMatch(pairs1, pairs2)) {
for (size_t i = 0; i < pairs1.size(); ++i) {
if (pairs1[i].second != pairs2[i].second)
callback(pairs1[i].first, pairs1[i].second, pairs2[i].second);
}
return;
}
auto map1 = MapFromKeyValuePairs(pairs1);
auto map2 = MapFromKeyValuePairs(pairs2);
for (size_t i = 0; i < pairs1.size(); ++i) {
const auto& new_iter = map2.find(pairs1[i].first);
if (pairs1[i].second != empty_value && new_iter == map2.end())
callback(pairs1[i].first, pairs1[i].second, empty_value);
}
for (size_t i = 0; i < pairs2.size(); ++i) {
const auto& iter = map1.find(pairs2[i].first);
if (pairs2[i].second == empty_value && iter == map1.end())
continue;
if (iter == map1.end())
callback(pairs2[i].first, empty_value, pairs2[i].second);
else if (iter->second != pairs2[i].second)
callback(pairs2[i].first, iter->second, pairs2[i].second);
}
}
bool IsCollapsed(const AXNode* node) {
return node && node->HasState(ax::mojom::State::kCollapsed);
}
}
bool AXTree::is_focused_node_always_unignored_ = false;
struct PendingStructureChanges {
explicit PendingStructureChanges(const AXNode* node)
: destroy_subtree_count(0),
destroy_node_count(0),
create_node_count(0),
node_exists(!!node),
parent_node_id((node && node->parent())
? absl::make_optional<AXNodeID>(node->parent()->id())
: absl::nullopt),
last_known_data(node ? &node->data() : nullptr) {}
bool DoesNodeExpectAnyStructureChanges() const {
return DoesNodeExpectSubtreeWillBeDestroyed() ||
DoesNodeExpectNodeWillBeDestroyed() ||
DoesNodeExpectNodeWillBeCreated();
}
bool DoesNodeExpectSubtreeOrNodeWillBeDestroyed() const {
return DoesNodeExpectSubtreeWillBeDestroyed() ||
DoesNodeExpectNodeWillBeDestroyed();
}
bool DoesNodeExpectSubtreeWillBeDestroyed() const {
return destroy_subtree_count;
}
bool DoesNodeExpectNodeWillBeDestroyed() const { return destroy_node_count; }
bool DoesNodeExpectNodeWillBeCreated() const { return create_node_count; }
bool DoesNodeRequireInit() const { return node_exists && !last_known_data; }
int32_t destroy_subtree_count;
int32_t destroy_node_count;
int32_t create_node_count;
bool node_exists;
absl::optional<AXNodeID> parent_node_id;
raw_ptr<const AXNodeData> last_known_data;
};
enum class AXTreePendingStructureStatus {
kNotStarted,
kComputing,
kComplete,
kFailed,
};
struct AXTreeUpdateState {
AXTreeUpdateState(const AXTree& tree, const AXTreeUpdate& pending_tree_update)
: pending_update_status(AXTreePendingStructureStatus::kNotStarted),
root_will_be_created(false),
pending_tree_update(pending_tree_update),
tree(tree) {}
bool IsRemovedNode(const AXNode* node) const {
return base::Contains(removed_node_ids, node->id());
}
bool IsCreatedNode(AXNodeID node_id) const {
return base::Contains(new_node_ids, node_id);
}
bool IsCreatedNode(const AXNode* node) const {
return IsCreatedNode(node->id());
}
bool IsReparentedNode(const AXNode* node) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
PendingStructureChanges* data = GetPendingStructureChanges(node->id());
if (!data)
return false;
return (data->DoesNodeExpectNodeWillBeDestroyed() || IsRemovedNode(node)) &&
data->node_exists;
}
bool DoesPendingNodeRequireInit(AXNodeID node_id) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
PendingStructureChanges* data = GetPendingStructureChanges(node_id);
return data && data->DoesNodeRequireInit();
}
absl::optional<AXNodeID> GetParentIdForPendingNode(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
DCHECK(!data->parent_node_id ||
ShouldPendingNodeExistInTree(*data->parent_node_id));
return data->parent_node_id;
}
bool ShouldPendingNodeExistInTree(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
return GetOrCreatePendingStructureChanges(node_id)->node_exists;
}
const AXNodeData& GetLastKnownPendingNodeData(AXNodeID node_id) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
static base::NoDestructor<ui::AXNodeData> empty_data;
PendingStructureChanges* data = GetPendingStructureChanges(node_id);
return (data && data->last_known_data) ? *data->last_known_data
: *empty_data;
}
void ClearLastKnownPendingNodeData(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
GetOrCreatePendingStructureChanges(node_id)->last_known_data = nullptr;
}
void SetLastKnownPendingNodeData(const AXNodeData* node_data) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
GetOrCreatePendingStructureChanges(node_data->id)->last_known_data =
node_data;
}
int32_t GetPendingDestroySubtreeCount(AXNodeID node_id) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
return data->destroy_subtree_count;
return 0;
}
bool IncrementPendingDestroySubtreeCount(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
if (!data->node_exists)
return false;
++data->destroy_subtree_count;
return true;
}
void DecrementPendingDestroySubtreeCount(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
DCHECK_GT(data->destroy_subtree_count, 0);
--data->destroy_subtree_count;
}
}
int32_t GetPendingDestroyNodeCount(AXNodeID node_id) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
return data->destroy_node_count;
return 0;
}
bool IncrementPendingDestroyNodeCount(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
if (!data->node_exists)
return false;
++data->destroy_node_count;
data->node_exists = false;
data->last_known_data = nullptr;
data->parent_node_id = absl::nullopt;
if (pending_root_id == node_id)
pending_root_id = absl::nullopt;
return true;
}
void DecrementPendingDestroyNodeCount(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
DCHECK_GT(data->destroy_node_count, 0);
--data->destroy_node_count;
}
}
int32_t GetPendingCreateNodeCount(AXNodeID node_id) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id))
return data->create_node_count;
return 0;
}
bool IncrementPendingCreateNodeCount(
AXNodeID node_id,
absl::optional<AXNodeID> parent_node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
PendingStructureChanges* data = GetOrCreatePendingStructureChanges(node_id);
if (data->node_exists)
return false;
++data->create_node_count;
data->node_exists = true;
data->parent_node_id = parent_node_id;
return true;
}
void DecrementPendingCreateNodeCount(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComplete, pending_update_status)
<< "This method should not be called before pending changes have "
"finished computing.";
if (PendingStructureChanges* data = GetPendingStructureChanges(node_id)) {
DCHECK_GT(data->create_node_count, 0);
--data->create_node_count;
}
}
bool HasIgnoredChanged(const AXNodeData& new_data) const {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
const AXNodeData& old_data = GetLastKnownPendingNodeData(new_data.id);
return AXTree::ComputeNodeIsIgnored(
old_tree_data ? &old_tree_data.value() : nullptr, old_data) !=
AXTree::ComputeNodeIsIgnored(
new_tree_data ? &new_tree_data.value() : nullptr, new_data);
}
bool InvalidatesUnignoredCachedValues(AXNodeID node_id) const {
return base::Contains(invalidate_unignored_cached_values_ids, node_id);
}
void InvalidateParentNodeUnignoredCacheValues(AXNodeID node_id) {
DCHECK_EQ(AXTreePendingStructureStatus::kComputing, pending_update_status)
<< "This method should only be called while computing pending changes, "
"before updates are made to the tree.";
absl::optional<AXNodeID> parent_node_id =
GetParentIdForPendingNode(node_id);
if (parent_node_id) {
invalidate_unignored_cached_values_ids.insert(*parent_node_id);
}
}
AXTreePendingStructureStatus pending_update_status;
absl::optional<AXNodeID> pending_root_id;
bool root_will_be_created;
std::set<AXNodeID> pending_node_ids;
std::vector<AXNodeData> updated_nodes;
std::set<AXNodeID> invalidate_unignored_cached_values_ids;
std::set<AXNodeID> node_data_changed_ids;
std::set<AXNodeID> ignored_state_changed_ids;
std::set<AXNodeID> new_node_ids;
std::set<AXNodeID> removed_node_ids;
std::map<AXNodeID, std::unique_ptr<PendingStructureChanges>>
node_id_to_pending_data;
std::map<AXNodeID, AXNodeData> old_node_id_to_data;
absl::optional<AXTreeData> old_tree_data;
absl::optional<AXTreeData> new_tree_data;
const raw_ref<const AXTreeUpdate> pending_tree_update;
private:
PendingStructureChanges* GetPendingStructureChanges(AXNodeID node_id) const {
auto iter = node_id_to_pending_data.find(node_id);
return (iter != node_id_to_pending_data.cend()) ? iter->second.get()
: nullptr;
}
PendingStructureChanges* GetOrCreatePendingStructureChanges(
AXNodeID node_id) {
auto iter = node_id_to_pending_data.find(node_id);
if (iter == node_id_to_pending_data.cend()) {
const AXNode* node = tree->GetFromId(node_id);
iter = node_id_to_pending_data
.emplace(std::make_pair(
node_id, std::make_unique<PendingStructureChanges>(node)))
.first;
}
return iter->second.get();
}
const raw_ref<const AXTree> tree;
};
AXTree::NodeSetSizePosInSetInfo::NodeSetSizePosInSetInfo() = default;
AXTree::NodeSetSizePosInSetInfo::~NodeSetSizePosInSetInfo() = default;
struct AXTree::OrderedSetContent {
explicit OrderedSetContent(const AXNode* ordered_set = nullptr)
: ordered_set_(ordered_set) {}
~OrderedSetContent() = default;
std::vector<const AXNode*> set_items_;
raw_ptr<const AXNode> ordered_set_;
};
struct AXTree::OrderedSetItemsMap {
OrderedSetItemsMap() = default;
~OrderedSetItemsMap() = default;
bool HierarchicalLevelExists(absl::optional<int> level) {
if (items_map_.find(level) == items_map_.end())
return false;
return true;
}
void Add(absl::optional<int> level,
const OrderedSetContent& ordered_set_content) {
if (!HierarchicalLevelExists(level))
items_map_[level] = std::vector<OrderedSetContent>();
items_map_[level].push_back(ordered_set_content);
}
void AddItemToBack(absl::optional<int> level, const AXNode* item) {
if (!HierarchicalLevelExists(level))
return;
std::vector<OrderedSetContent>& sets_list = items_map_[level];
if (!sets_list.empty()) {
OrderedSetContent& ordered_set_content = sets_list.back();
ordered_set_content.set_items_.push_back(item);
}
}
OrderedSetContent* GetFirstOrderedSetContent() {
if (items_map_.empty())
return nullptr;
std::vector<OrderedSetContent>& sets_list = items_map_.begin()->second;
if (sets_list.empty())
return nullptr;
return &(sets_list.front());
}
void Clear() { items_map_.clear(); }
std::map<absl::optional<int32_t>, std::vector<OrderedSetContent>> items_map_;
};
void AXTree::SetFocusedNodeShouldNeverBeIgnored() {
is_focused_node_always_unignored_ = true;
}
bool AXTree::ComputeNodeIsIgnored(const AXTreeData* optional_tree_data,
const AXNodeData& node_data) {
bool is_ignored = node_data.HasState(ax::mojom::State::kIgnored) ||
node_data.role == ax::mojom::Role::kNone;
if (is_focused_node_always_unignored_ && is_ignored && optional_tree_data &&
optional_tree_data->focus_id != kInvalidAXNodeID &&
node_data.id == optional_tree_data->focus_id) {
is_ignored = false;
}
return is_ignored;
}
bool AXTree::ComputeNodeIsIgnoredChanged(
const AXTreeData* optional_old_tree_data,
const AXNodeData& old_node_data,
const AXTreeData* optional_new_tree_data,
const AXNodeData& new_node_data) {
const bool old_node_is_ignored =
ComputeNodeIsIgnored(optional_old_tree_data, old_node_data);
const bool new_node_is_ignored =
ComputeNodeIsIgnored(optional_new_tree_data, new_node_data);
return old_node_is_ignored != new_node_is_ignored;
}
AXTree::AXTree() {
DCHECK(!language_detection_manager);
language_detection_manager =
std::make_unique<AXLanguageDetectionManager>(this);
}
AXTree::AXTree(const AXTreeUpdate& initial_state) {
CHECK(Unserialize(initial_state)) << error();
DCHECK(!language_detection_manager);
language_detection_manager =
std::make_unique<AXLanguageDetectionManager>(this);
}
AXTree::~AXTree() {
Destroy();
language_detection_manager.reset();
}
void AXTree::AddObserver(AXTreeObserver* observer) {
observers_.AddObserver(observer);
}
bool AXTree::HasObserver(AXTreeObserver* observer) {
return observers_.HasObserver(observer);
}
void AXTree::RemoveObserver(AXTreeObserver* observer) {
observers_.RemoveObserver(observer);
}
const AXTreeID& AXTree::GetAXTreeID() const {
return data().tree_id;
}
const AXTreeData& AXTree::data() const {
return data_;
}
AXNode* AXTree::GetFromId(AXNodeID id) const {
if (id == ui::kInvalidAXNodeID)
return nullptr;
auto iter = id_map_.find(id);
return iter != id_map_.end() ? iter->second.get() : nullptr;
}
void AXTree::Destroy() {
base::ElapsedThreadTimer timer;
table_info_map_.clear();
if (!root_)
return;
RecursivelyNotifyNodeDeletedForTreeTeardown(root_);
{
ScopedTreeUpdateInProgressStateSetter tree_update_in_progress(*this);
DestroyNodeAndSubtree(root_.ExtractAsDangling(), nullptr);
}
UMA_HISTOGRAM_TIMES("Accessibility.Performance.AXTree.Destroy",
timer.Elapsed());
}
void AXTree::UpdateDataForTesting(const AXTreeData& new_data) {
if (data_ == new_data)
return;
AXTreeUpdate update;
update.has_tree_data = true;
update.tree_data = new_data;
Unserialize(update);
}
gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
gfx::RectF bounds,
bool* offscreen,
bool clip_bounds,
bool skip_container_offset,
bool allow_recursion) const {
if (bounds.width() == 0 && bounds.height() == 0) {
bounds = node->data().relative_bounds.bounds;
if (bounds.IsEmpty() && !GetTreeUpdateInProgressState() &&
allow_recursion) {
for (auto* child : node->children()) {
bool ignore_offscreen;
gfx::RectF child_bounds =
RelativeToTreeBoundsInternal(child, gfx::RectF(), &ignore_offscreen,
clip_bounds, skip_container_offset,
false);
bounds.Union(child_bounds);
}
if (bounds.width() > 0 && bounds.height() > 0) {
return bounds;
}
}
} else if (!skip_container_offset) {
bounds.Offset(node->data().relative_bounds.bounds.x(),
node->data().relative_bounds.bounds.y());
}
const AXNode* original_node = node;
while (node != nullptr) {
if (node->data().relative_bounds.transform)
bounds = node->data().relative_bounds.transform->MapRect(bounds);
const AXNode* container =
GetFromId(node->data().relative_bounds.offset_container_id);
if (!container && container != root())
container = root();
if (!container || container == node || skip_container_offset)
break;
gfx::RectF container_bounds = container->data().relative_bounds.bounds;
bounds.Offset(container_bounds.x(), container_bounds.y());
int scroll_x = 0;
int scroll_y = 0;
if (container->GetIntAttribute(ax::mojom::IntAttribute::kScrollX,
&scroll_x) &&
container->GetIntAttribute(ax::mojom::IntAttribute::kScrollY,
&scroll_y)) {
bounds.Offset(-scroll_x, -scroll_y);
}
gfx::RectF intersection = bounds;
intersection.Intersect(container_bounds);
gfx::RectF clipped = bounds;
if (container->GetBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren)) {
if (!intersection.IsEmpty()) {
clipped = intersection;
} else {
if (clipped.x() >= container_bounds.width()) {
clipped.set_x(container_bounds.right() - 1);
clipped.set_width(1);
} else if (clipped.x() + clipped.width() <= 0) {
clipped.set_x(container_bounds.x());
clipped.set_width(1);
}
if (clipped.y() >= container_bounds.height()) {
clipped.set_y(container_bounds.bottom() - 1);
clipped.set_height(1);
} else if (clipped.y() + clipped.height() <= 0) {
clipped.set_y(container_bounds.y());
clipped.set_height(1);
}
}
}
if (clip_bounds)
bounds = clipped;
if (container->GetBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren) &&
intersection.IsEmpty() && !clipped.IsEmpty()) {
if (offscreen != nullptr)
*offscreen |= true;
}
node = container;
}
if (bounds.width() == 0 && bounds.height() == 0) {
const AXNode* ancestor = original_node->parent();
gfx::RectF ancestor_bounds;
while (ancestor) {
ancestor_bounds = ancestor->data().relative_bounds.bounds;
if (ancestor_bounds.width() > 0 || ancestor_bounds.height() > 0)
break;
ancestor = ancestor->parent();
}
if (ancestor && allow_recursion) {
bool ignore_offscreen;
ancestor_bounds = RelativeToTreeBoundsInternal(
ancestor, gfx::RectF(), &ignore_offscreen, clip_bounds,
skip_container_offset,
false);
gfx::RectF original_bounds = original_node->data().relative_bounds.bounds;
if (original_bounds.x() == 0 && original_bounds.y() == 0) {
bounds = ancestor_bounds;
} else {
bounds.set_width(std::max(0.0f, ancestor_bounds.right() - bounds.x()));
bounds.set_height(
std::max(0.0f, ancestor_bounds.bottom() - bounds.y()));
}
if (offscreen != nullptr)
*offscreen |= true;
}
}
return bounds;
}
gfx::RectF AXTree::RelativeToTreeBounds(const AXNode* node,
gfx::RectF bounds,
bool* offscreen,
bool clip_bounds,
bool skip_container_offset) const {
bool allow_recursion = true;
return RelativeToTreeBoundsInternal(node, bounds, offscreen, clip_bounds,
skip_container_offset, allow_recursion);
}
gfx::RectF AXTree::GetTreeBounds(const AXNode* node,
bool* offscreen,
bool clip_bounds) const {
return RelativeToTreeBounds(node, gfx::RectF(), offscreen, clip_bounds);
}
std::set<AXNodeID> AXTree::GetReverseRelations(ax::mojom::IntAttribute attr,
AXNodeID dst_id) const {
DCHECK(IsNodeIdIntAttribute(attr));
const auto& attr_relations = int_reverse_relations_.find(attr);
if (attr_relations != int_reverse_relations_.end()) {
const auto& result = attr_relations->second.find(dst_id);
if (result != attr_relations->second.end())
return result->second;
}
return std::set<AXNodeID>();
}
std::set<AXNodeID> AXTree::GetReverseRelations(ax::mojom::IntListAttribute attr,
AXNodeID dst_id) const {
DCHECK(IsNodeIdIntListAttribute(attr));
const auto& attr_relations = intlist_reverse_relations_.find(attr);
if (attr_relations != intlist_reverse_relations_.end()) {
const auto& result = attr_relations->second.find(dst_id);
if (result != attr_relations->second.end())
return result->second;
}
return std::set<AXNodeID>();
}
std::set<AXNodeID> AXTree::GetNodeIdsForChildTreeId(
AXTreeID child_tree_id) const {
const auto& result = child_tree_id_reverse_map_.find(child_tree_id);
if (result != child_tree_id_reverse_map_.end())
return result->second;
return std::set<AXNodeID>();
}
const std::set<AXTreeID> AXTree::GetAllChildTreeIds() const {
std::set<AXTreeID> result;
for (auto entry : child_tree_id_reverse_map_)
result.insert(entry.first);
return result;
}
bool AXTree::Unserialize(const AXTreeUpdate& update) {
#if DCHECK_IS_ON()
for (const auto& new_data : update.nodes)
DCHECK(new_data.id != kInvalidAXNodeID)
<< "AXTreeUpdate contains invalid node: " << update.ToString();
if (update.tree_data.tree_id != AXTreeIDUnknown() &&
data_.tree_id != AXTreeIDUnknown()) {
DCHECK_EQ(update.tree_data.tree_id, data_.tree_id)
<< "Tree id mismatch between tree update and this tree.";
}
#endif
event_data_ = std::make_unique<AXEvent>();
event_data_->event_from = update.event_from;
event_data_->event_from_action = update.event_from_action;
event_data_->event_intents = update.event_intents;
base::ScopedClosureRunner clear_event_data(base::BindOnce(
[](std::unique_ptr<AXEvent>* event_data) { event_data->reset(); },
&event_data_));
AXTreeUpdateState update_state(*this, update);
const AXNodeID old_root_id = root_ ? root_->id() : kInvalidAXNodeID;
DCHECK(old_root_id != kInvalidAXNodeID || update.root_id != kInvalidAXNodeID)
<< "Tree must have a valid root or update must have a valid root.";
if (!ComputePendingChanges(update, &update_state))
return false;
SCOPED_UMA_HISTOGRAM_TIMER("Accessibility.Performance.Tree.Unserialize");
if (features::IsUnserializeOptimizationsEnabled()) {
for (AXTreeObserver& observer : observers_) {
for (auto&& pair : update_state.node_id_to_pending_data) {
AXNode* node = GetFromId(pair.first);
if (!node || node->id() == kInvalidAXNodeID) {
continue;
}
if (pair.second->DoesNodeExpectSubtreeWillBeDestroyed()) {
if (update_state.IsReparentedNode(node)) {
observer.OnSubtreeWillBeReparented(this, node);
} else if (node->parent() ||
base::Contains(update_state.ignored_state_changed_ids,
node->parent()->id()) ||
!node->parent()->IsIgnored()) {
observer.OnSubtreeWillBeDeleted(this, node);
}
}
if (pair.second->DoesNodeExpectNodeWillBeDestroyed()) {
table_info_map_.erase(node->id());
if (update_state.IsReparentedNode(node)) {
observer.OnNodeWillBeReparented(this, node);
} else {
observer.OnNodeWillBeDeleted(this, node);
}
}
}
}
} else {
for (auto&& pair : update_state.node_id_to_pending_data) {
const AXNodeID node_id = pair.first;
const std::unique_ptr<PendingStructureChanges>& data = pair.second;
if (data->DoesNodeExpectSubtreeOrNodeWillBeDestroyed()) {
if (AXNode* node = GetFromId(node_id)) {
if (data->DoesNodeExpectSubtreeWillBeDestroyed()) {
NotifySubtreeWillBeReparentedOrDeleted(node, &update_state);
}
if (data->DoesNodeExpectNodeWillBeDestroyed()) {
NotifyNodeWillBeReparentedOrDeleted(node, &update_state);
}
}
}
}
}
std::set<AXNodeID> notified_node_attributes_will_change;
if (features::IsUnserializeOptimizationsEnabled()) {
std::vector<std::pair<AXNodeData, AXNodeData>> nodes_to_notify;
for (const auto& new_data : update_state.updated_nodes) {
const bool is_new_root =
update_state.root_will_be_created && new_data.id == update.root_id;
if (is_new_root || new_data.id == kInvalidAXNodeID) {
continue;
}
AXNode* node = GetFromId(new_data.id);
if (node &&
notified_node_attributes_will_change.insert(new_data.id).second) {
nodes_to_notify.emplace_back(node->data(), new_data);
}
}
for (AXTreeObserver& observer : observers_) {
for (const auto& pair : nodes_to_notify) {
observer.OnNodeDataWillChange(this, pair.first, pair.second);
}
}
} else {
for (const auto& new_data : update_state.updated_nodes) {
const bool is_new_root =
update_state.root_will_be_created && new_data.id == update.root_id;
if (is_new_root) {
continue;
}
AXNode* node = GetFromId(new_data.id);
if (node &&
notified_node_attributes_will_change.insert(new_data.id).second) {
NotifyNodeAttributesWillChange(
node, update_state,
update_state.old_tree_data ? &update_state.old_tree_data.value()
: nullptr,
node->data(),
update_state.new_tree_data ? &update_state.new_tree_data.value()
: nullptr,
new_data);
}
}
}
if (features::IsUnserializeOptimizationsEnabled()) {
std::vector<AXNode*> nodes_to_notify;
for (AXNodeID id : update_state.ignored_state_changed_ids) {
AXNode* node = GetFromId(id);
if (node) {
nodes_to_notify.push_back(node);
}
}
for (AXTreeObserver& observer : observers_) {
for (AXNode* node : nodes_to_notify) {
bool will_be_ignored = !node->IsIgnored();
bool is_root_of_ignored_change =
!node->parent() ||
node->IsIgnored() != node->parent()->IsIgnored() ||
!base::Contains(update_state.ignored_state_changed_ids,
node->parent()->id());
observer.OnIgnoredWillChange(this, node, will_be_ignored,
is_root_of_ignored_change);
}
}
} else {
for (AXNodeID id : update_state.ignored_state_changed_ids) {
AXNode* node = GetFromId(id);
if (node) {
bool will_be_ignored = !node->IsIgnored();
bool is_root_of_ignored_change =
!node->parent() ||
!base::Contains(update_state.ignored_state_changed_ids,
node->parent()->id()) ||
node->IsIgnored() != node->parent()->IsIgnored();
for (AXTreeObserver& observer : observers_) {
observer.OnIgnoredWillChange(this, node, will_be_ignored,
is_root_of_ignored_change);
}
}
}
}
std::vector<AXTreeObserver::Change> changes;
{
ScopedTreeUpdateInProgressStateSetter tree_update_in_progress(*this);
if (update_state.new_tree_data)
data_ = update.tree_data;
bool root_updated = false;
if (update.node_id_to_clear != kInvalidAXNodeID) {
int node_id_to_clear = update.node_id_to_clear;
if (!GetFromId(node_id_to_clear) && update.root_id == node_id_to_clear &&
update.root_id != old_root_id && root_) {
node_id_to_clear = old_root_id;
}
if (AXNode* cleared_node = GetFromId(node_id_to_clear)) {
DCHECK(root_);
if (cleared_node == root_) {
if (update.root_id != old_root_id) {
AXNode* old_root = root_;
root_ = nullptr;
DestroySubtree(old_root, &update_state);
} else {
root_updated = true;
}
}
if (root_) {
for (auto* child : cleared_node->children())
DestroySubtree(child, &update_state);
std::vector<AXNode*> children;
cleared_node->SwapChildren(&children);
update_state.pending_node_ids.insert(cleared_node->id());
}
}
}
DCHECK_EQ(update.root_id != kInvalidAXNodeID && !GetFromId(update.root_id),
update_state.root_will_be_created);
for (const AXNodeData& updated_node_data : update_state.updated_nodes) {
const bool is_new_root = update_state.root_will_be_created &&
updated_node_data.id == update.root_id;
if (!UpdateNode(updated_node_data, is_new_root, &update_state))
return false;
}
if (!root_) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNoRoot);
RecordError(update_state, "Tree has no root.");
return false;
}
if (!ValidatePendingChangesComplete(update_state))
return false;
changes.reserve(update_state.updated_nodes.size());
std::set<AXNodeID> table_ids_checked;
for (const AXNodeData& node_data : update_state.updated_nodes) {
AXNode* node = GetFromId(node_data.id);
while (node) {
if (table_ids_checked.find(node->id()) != table_ids_checked.end())
break;
const auto& table_info_entry = table_info_map_.find(node->id());
if (table_info_entry != table_info_map_.end()) {
table_info_entry->second->Invalidate();
#if defined(AX_EXTRA_MAC_NODES)
changes.emplace_back(node, AXTreeObserver::NODE_CHANGED);
#endif
}
table_ids_checked.insert(node->id());
node = node->parent();
}
}
node_set_size_pos_in_set_info_map_.clear();
std::set<AXNodeID> visited_observer_changes;
for (const AXNodeData& updated_node_data : update_state.updated_nodes) {
AXNode* node = GetFromId(updated_node_data.id);
if (!node ||
!visited_observer_changes.emplace(updated_node_data.id).second)
continue;
bool is_new_node = update_state.IsCreatedNode(node);
bool is_reparented_node = update_state.IsReparentedNode(node);
AXTreeObserver::ChangeType change = AXTreeObserver::NODE_CHANGED;
if (is_new_node) {
if (is_reparented_node) {
bool is_subtree = !node->parent() ||
!update_state.IsCreatedNode(node->parent()) ||
(node->parent() == root_ && root_updated);
change = is_subtree ? AXTreeObserver::SUBTREE_REPARENTED
: AXTreeObserver::NODE_REPARENTED;
} else {
bool is_subtree = !node->parent() ||
!update_state.IsCreatedNode(node->parent()) ||
update_state.IsRemovedNode(node->parent()) ||
(node->parent() == root_ && root_updated);
change = is_subtree ? AXTreeObserver::SUBTREE_CREATED
: AXTreeObserver::NODE_CREATED;
}
}
changes.emplace_back(node, change);
}
std::set<AXNodeID> cleared_computed_node_data_ids;
for (AXNodeID node_id : update_state.node_data_changed_ids) {
AXNode* node = GetFromId(node_id);
while (node) {
if (cleared_computed_node_data_ids.insert(node->id()).second)
node->ClearComputedNodeData();
node = node->parent();
}
}
std::set<AXNodeID> updated_unignored_cached_values_ids;
for (AXNodeID node_id :
update_state.invalidate_unignored_cached_values_ids) {
AXNode* unignored_ancestor = GetUnignoredAncestorFromId(node_id);
if (unignored_ancestor &&
updated_unignored_cached_values_ids.insert(unignored_ancestor->id())
.second) {
unignored_ancestor->UpdateUnignoredCachedValues();
if (unignored_ancestor->id() != node_id &&
visited_observer_changes.emplace(unignored_ancestor->id()).second) {
changes.emplace_back(unignored_ancestor,
AXTreeObserver::NODE_CHANGED);
}
}
}
}
if (update_state.old_tree_data) {
DCHECK(update.has_tree_data)
<< "If `UpdateState::old_tree_data` exists, then there must be a "
"request to update the tree data.";
for (AXTreeObserver& observer : observers_)
observer.OnTreeDataChanged(this, *update_state.old_tree_data, data_);
}
if (features::IsUnserializeOptimizationsEnabled()) {
std::vector<AXNode*> reparented_nodes_to_notify;
std::vector<AXNode*> created_nodes_to_notify;
for (AXNodeID node_id : update_state.new_node_ids) {
AXNode* node = GetFromId(node_id);
if (!node || node->id() == kInvalidAXNodeID) {
continue;
}
if (update_state.IsReparentedNode(node)) {
reparented_nodes_to_notify.emplace_back(node);
} else {
created_nodes_to_notify.emplace_back(node);
}
}
std::vector<AXNodeID> deleted_nodes_to_notify;
for (AXNodeID node_id : update_state.removed_node_ids) {
if (node_id == kInvalidAXNodeID) {
continue;
}
if (!update_state.IsCreatedNode(node_id)) {
deleted_nodes_to_notify.emplace_back(node_id);
}
}
for (AXTreeObserver& observer : observers_) {
for (AXNode* node : reparented_nodes_to_notify) {
observer.OnNodeReparented(this, node);
}
for (AXNode* node : created_nodes_to_notify) {
observer.OnNodeCreated(this, node);
}
}
for (AXTreeObserver& observer : observers_) {
for (AXNodeID node_id : deleted_nodes_to_notify) {
observer.OnNodeDeleted(this, node_id);
}
}
} else {
for (AXNodeID node_id : update_state.new_node_ids) {
AXNode* node = GetFromId(node_id);
if (node) {
NotifyNodeHasBeenReparentedOrCreated(node, &update_state);
}
}
for (AXNodeID node_id : update_state.removed_node_ids) {
if (!update_state.IsCreatedNode(node_id)) {
NotifyNodeHasBeenDeleted(node_id);
}
}
}
if (features::IsUnserializeOptimizationsEnabled()) {
for (AXTreeObserver& observer : observers_) {
DCHECK(!GetTreeUpdateInProgressState());
for (AXNodeID changed_id : update_state.node_data_changed_ids) {
AXNode* node = GetFromId(changed_id);
DCHECK(node);
DCHECK(node->id() != kInvalidAXNodeID);
const bool is_new_root =
update_state.root_will_be_created && changed_id == update.root_id;
if (!is_new_root) {
auto it = update_state.old_node_id_to_data.find(changed_id);
if (it != update_state.old_node_id_to_data.end()) {
NotifyNodeAttributesHaveBeenChangedOptimized(
node, update_state, observer,
update_state.old_tree_data ? &update_state.old_tree_data.value()
: nullptr,
it->second,
update_state.new_tree_data ? &update_state.new_tree_data.value()
: nullptr,
node->data());
}
}
observer.OnNodeChanged(this, node);
}
}
for (AXTreeObserver& observer : observers_) {
observer.OnAtomicUpdateFinished(this, root_->id() != old_root_id,
changes);
}
return true;
}
for (AXNodeID changed_id : update_state.node_data_changed_ids) {
AXNode* node = GetFromId(changed_id);
DCHECK(node);
const bool is_new_root =
update_state.root_will_be_created && changed_id == update.root_id;
if (!is_new_root) {
auto it = update_state.old_node_id_to_data.find(changed_id);
if (it != update_state.old_node_id_to_data.end()) {
NotifyNodeAttributesHaveBeenChanged(
node, update_state,
update_state.old_tree_data ? &update_state.old_tree_data.value()
: nullptr,
it->second,
update_state.new_tree_data ? &update_state.new_tree_data.value()
: nullptr,
node->data());
}
}
for (AXTreeObserver& observer : observers_)
observer.OnNodeChanged(this, node);
}
for (AXTreeObserver& observer : observers_)
observer.OnAtomicUpdateFinished(this, root_->id() != old_root_id, changes);
return true;
}
AXTableInfo* AXTree::GetTableInfo(const AXNode* const_table_node) const {
DCHECK(!GetTreeUpdateInProgressState());
AXNode* table_node = const_cast<AXNode*>(const_table_node);
AXTree* tree = const_cast<AXTree*>(this);
DCHECK(table_node);
const auto& cached = table_info_map_.find(table_node->id());
if (cached != table_info_map_.end()) {
AXTableInfo* table_info = cached->second.get();
if (!table_info->valid()) {
if (!table_info->Update()) {
table_info_map_.erase(table_node->id());
return nullptr;
}
}
return table_info;
}
AXTableInfo* table_info = AXTableInfo::Create(tree, table_node);
if (!table_info)
return nullptr;
table_info_map_[table_node->id()] = base::WrapUnique<AXTableInfo>(table_info);
return table_info;
}
std::string AXTree::ToString(bool verbose) const {
return "AXTree" + data_.ToString() + "\n" +
TreeToStringHelper(root_, 0, verbose);
}
AXNode* AXTree::CreateNode(AXNode* parent,
AXNodeID id,
size_t index_in_parent,
AXTreeUpdateState* update_state) {
DCHECK(GetTreeUpdateInProgressState());
SANITIZER_CHECK(id != kInvalidAXNodeID);
DCHECK(!GetFromId(id));
DCHECK_GT(update_state->GetPendingCreateNodeCount(id), 0);
DCHECK(update_state->InvalidatesUnignoredCachedValues(id));
DCHECK(!parent ||
update_state->InvalidatesUnignoredCachedValues(parent->id()));
update_state->DecrementPendingCreateNodeCount(id);
update_state->new_node_ids.insert(id);
auto node = std::make_unique<AXNode>(this, parent, id, index_in_parent,
parent ? 0 : index_in_parent);
auto emplaced = id_map_.emplace(id, std::move(node));
DCHECK(emplaced.second);
return emplaced.first->second.get();
}
bool AXTree::ComputePendingChanges(const AXTreeUpdate& update,
AXTreeUpdateState* update_state) {
DCHECK_EQ(AXTreePendingStructureStatus::kNotStarted,
update_state->pending_update_status)
<< "Pending changes have already started being computed.";
update_state->pending_update_status =
AXTreePendingStructureStatus::kComputing;
base::AutoReset<absl::optional<AXNodeID>> pending_root_id_resetter(
&update_state->pending_root_id,
root_ ? absl::make_optional<AXNodeID>(root_->id()) : absl::nullopt);
if (update.has_tree_data && data_ != update.tree_data) {
update_state->old_tree_data = data_;
update_state->new_tree_data = update.tree_data;
}
update_state->updated_nodes = update.nodes;
if (update.node_id_to_clear != kInvalidAXNodeID) {
if (AXNode* cleared_node = GetFromId(update.node_id_to_clear)) {
DCHECK(root_);
if (cleared_node == root_ &&
update.root_id != update_state->pending_root_id) {
MarkSubtreeForDestruction(*update_state->pending_root_id, update_state);
}
if (update_state->ShouldPendingNodeExistInTree(root_->id())) {
update_state->invalidate_unignored_cached_values_ids.insert(
cleared_node->id());
update_state->ClearLastKnownPendingNodeData(cleared_node->id());
for (AXNode* child : cleared_node->children()) {
MarkSubtreeForDestruction(child->id(), update_state);
}
}
}
}
if (is_focused_node_always_unignored_ && update_state->old_tree_data &&
update_state->new_tree_data) {
if (update_state->old_tree_data->focus_id != kInvalidAXNodeID) {
const AXNode* old_focus =
GetFromId(update_state->old_tree_data->focus_id);
if (old_focus &&
update_state->ShouldPendingNodeExistInTree(old_focus->id()) &&
!base::Contains(update_state->updated_nodes, old_focus->id(),
&AXNodeData::id)) {
update_state->updated_nodes.push_back(old_focus->data());
}
}
if (update_state->new_tree_data->focus_id != kInvalidAXNodeID) {
const AXNode* new_focus =
GetFromId(update_state->new_tree_data->focus_id);
if (new_focus &&
update_state->ShouldPendingNodeExistInTree(new_focus->id()) &&
!base::Contains(update_state->updated_nodes, new_focus->id(),
&AXNodeData::id)) {
update_state->updated_nodes.push_back(new_focus->data());
}
}
}
if (update.root_id != kInvalidAXNodeID) {
update_state->root_will_be_created =
!GetFromId(update.root_id) ||
!update_state->ShouldPendingNodeExistInTree(update.root_id);
}
for (const AXNodeData& new_data : update_state->updated_nodes) {
if (new_data.id == kInvalidAXNodeID)
continue;
bool is_new_root =
update_state->root_will_be_created && new_data.id == update.root_id;
if (!ComputePendingChangesToNode(new_data, is_new_root, update_state)) {
update_state->pending_update_status =
AXTreePendingStructureStatus::kFailed;
return false;
}
}
update_state->pending_update_status = AXTreePendingStructureStatus::kComplete;
return true;
}
bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
bool is_new_root,
AXTreeUpdateState* update_state) {
for (size_t j = 0; j < new_data.child_ids.size(); j++) {
const AXNode* node = GetFromId(new_data.child_ids[j]);
if (node && node->GetIndexInParent() != j)
update_state->InvalidateParentNodeUnignoredCacheValues(node->id());
}
if (!update_state->ShouldPendingNodeExistInTree(new_data.id)) {
if (!is_new_root) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNotInTree);
RecordError(*update_state,
base::StringPrintf(
"%d will not be in the tree and is not the new root",
new_data.id));
return false;
}
if (!update_state->IncrementPendingCreateNodeCount(new_data.id,
absl::nullopt)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPending);
RecordError(
*update_state,
base::StringPrintf(
"Node %d is already pending for creation, cannot be the new root",
new_data.id));
return false;
}
if (update_state->pending_root_id) {
MarkSubtreeForDestruction(*update_state->pending_root_id, update_state);
}
update_state->pending_root_id = new_data.id;
}
std::set<AXNodeID> new_child_id_set;
for (AXNodeID new_child_id : new_data.child_ids) {
if (base::Contains(new_child_id_set, new_child_id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kDuplicateChild);
RecordError(*update_state,
base::StringPrintf("Node %d has duplicate child id %d",
new_data.id, new_child_id));
return false;
}
new_child_id_set.insert(new_child_id);
}
if (update_state->DoesPendingNodeRequireInit(new_data.id)) {
update_state->invalidate_unignored_cached_values_ids.insert(new_data.id);
update_state->InvalidateParentNodeUnignoredCacheValues(new_data.id);
for (AXNodeID child_id : new_child_id_set) {
update_state->invalidate_unignored_cached_values_ids.insert(child_id);
if (!update_state->IncrementPendingCreateNodeCount(child_id,
new_data.id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPendingForChild);
RecordError(*update_state,
base::StringPrintf("Node %d is already pending for "
"creation, cannot be a new child",
child_id));
return false;
}
}
update_state->SetLastKnownPendingNodeData(&new_data);
return true;
}
const AXNodeData& old_data =
update_state->GetLastKnownPendingNodeData(new_data.id);
AXTreeData* old_tree_data = update_state->old_tree_data
? &update_state->old_tree_data.value()
: nullptr;
AXTreeData* new_tree_data = update_state->new_tree_data
? &update_state->new_tree_data.value()
: nullptr;
if (ComputeNodeIsIgnoredChanged(old_tree_data, old_data, new_tree_data,
new_data)) {
update_state->ignored_state_changed_ids.insert(new_data.id);
}
std::set<AXNodeID> old_child_id_set(old_data.child_ids.cbegin(),
old_data.child_ids.cend());
std::vector<AXNodeID> create_or_destroy_ids;
std::set_symmetric_difference(
old_child_id_set.cbegin(), old_child_id_set.cend(),
new_child_id_set.cbegin(), new_child_id_set.cend(),
std::back_inserter(create_or_destroy_ids));
if (!create_or_destroy_ids.empty() ||
update_state->HasIgnoredChanged(new_data)) {
update_state->invalidate_unignored_cached_values_ids.insert(new_data.id);
update_state->InvalidateParentNodeUnignoredCacheValues(new_data.id);
}
for (AXNodeID child_id : create_or_destroy_ids) {
if (base::Contains(new_child_id_set, child_id)) {
if (update_state->ShouldPendingNodeExistInTree(child_id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kReparent);
RecordError(*update_state,
base::StringPrintf("Node %d is not marked for destruction, "
"would be reparented to %d",
child_id, new_data.id));
return false;
}
update_state->invalidate_unignored_cached_values_ids.insert(child_id);
if (!update_state->IncrementPendingCreateNodeCount(child_id,
new_data.id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPendingForChild);
RecordError(*update_state,
base::StringPrintf("Node %d is already pending for "
"creation, cannot be a new child",
child_id));
return false;
}
} else {
MarkSubtreeForDestruction(child_id, update_state);
}
}
update_state->SetLastKnownPendingNodeData(&new_data);
return true;
}
bool AXTree::UpdateNode(const AXNodeData& src,
bool is_new_root,
AXTreeUpdateState* update_state) {
DCHECK(GetTreeUpdateInProgressState());
AXNode* node = GetFromId(src.id);
if (node) {
update_state->pending_node_ids.erase(node->id());
UpdateReverseRelations(node, src);
if (!update_state->IsCreatedNode(node) ||
update_state->IsReparentedNode(node)) {
update_state->old_node_id_to_data.insert(
std::make_pair(node->id(), node->TakeData()));
}
node->SetData(src);
} else {
if (!is_new_root) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNotInTree);
RecordError(*update_state,
base::StringPrintf(
"%d is not in the tree and not the new root", src.id));
return false;
}
node = CreateNode(nullptr, src.id, 0, update_state);
UpdateReverseRelations(node, src);
node->SetData(src);
}
if (src.GetBoolAttribute(ax::mojom::BoolAttribute::kIsPageBreakingObject))
has_pagination_support_ = true;
update_state->node_data_changed_ids.insert(node->id());
DeleteOldChildren(node, src.child_ids, update_state);
std::vector<AXNode*> new_children;
bool success = CreateNewChildVector(
node, src.child_ids, &new_children, update_state);
node->SwapChildren(&new_children);
if (is_new_root) {
AXNode* old_root = root_;
root_ = node;
if (old_root && old_root != node) {
DestroySubtree(old_root, update_state);
}
}
return success;
}
void AXTree::NotifySubtreeWillBeReparentedOrDeleted(
AXNode* node,
const AXTreeUpdateState* update_state) {
DCHECK(!GetTreeUpdateInProgressState());
if (node->id() == kInvalidAXNodeID)
return;
bool notify_reparented = update_state->IsReparentedNode(node);
bool notify_removed = !notify_reparented;
if (notify_removed && node->parent() &&
base::Contains(update_state->ignored_state_changed_ids,
node->parent()->id()) &&
!node->parent()->IsIgnored()) {
notify_removed = false;
}
for (AXTreeObserver& observer : observers_) {
if (notify_reparented)
observer.OnSubtreeWillBeReparented(this, node);
if (notify_removed)
observer.OnSubtreeWillBeDeleted(this, node);
}
}
void AXTree::NotifyNodeWillBeReparentedOrDeleted(
AXNode* node,
const AXTreeUpdateState* update_state) {
DCHECK(!GetTreeUpdateInProgressState());
AXNodeID id = node->id();
if (id == kInvalidAXNodeID)
return;
table_info_map_.erase(id);
bool notify_reparented = update_state->IsReparentedNode(node);
for (AXTreeObserver& observer : observers_) {
if (notify_reparented)
observer.OnNodeWillBeReparented(this, node);
else
observer.OnNodeWillBeDeleted(this, node);
}
DCHECK(table_info_map_.find(id) == table_info_map_.end())
<< "Table info should never be recreated during node deletion";
}
void AXTree::RecursivelyNotifyNodeDeletedForTreeTeardown(AXNode* node) {
DCHECK(!GetTreeUpdateInProgressState());
if (node->id() == kInvalidAXNodeID)
return;
for (AXTreeObserver& observer : observers_)
observer.OnNodeDeleted(this, node->id());
for (auto* child : node->children())
RecursivelyNotifyNodeDeletedForTreeTeardown(child);
}
void AXTree::NotifyNodeHasBeenDeleted(AXNodeID node_id) {
DCHECK(!GetTreeUpdateInProgressState());
if (node_id == kInvalidAXNodeID)
return;
for (AXTreeObserver& observer : observers_)
observer.OnNodeDeleted(this, node_id);
}
void AXTree::NotifyNodeHasBeenReparentedOrCreated(
AXNode* node,
const AXTreeUpdateState* update_state) {
DCHECK(!GetTreeUpdateInProgressState());
if (node->id() == kInvalidAXNodeID)
return;
bool is_reparented = update_state->IsReparentedNode(node);
for (AXTreeObserver& observer : observers_) {
if (is_reparented) {
observer.OnNodeReparented(this, node);
} else {
observer.OnNodeCreated(this, node);
}
}
}
void AXTree::NotifyChildTreeConnectionChanged(AXNode* node,
AXTree* child_tree) {
DCHECK(node->tree() == this);
for (AXTreeObserver& observer : observers_) {
observer.OnChildTreeConnectionChanged(node);
}
}
void AXTree::NotifyNodeAttributesWillChange(
AXNode* node,
AXTreeUpdateState& update_state,
const AXTreeData* optional_old_tree_data,
const AXNodeData& old_data,
const AXTreeData* optional_new_tree_data,
const AXNodeData& new_data) {
DCHECK(!GetTreeUpdateInProgressState());
if (new_data.id == kInvalidAXNodeID)
return;
for (AXTreeObserver& observer : observers_)
observer.OnNodeDataWillChange(this, old_data, new_data);
}
void AXTree::NotifyNodeAttributesHaveBeenChanged(
AXNode* node,
AXTreeUpdateState& update_state,
const AXTreeData* optional_old_tree_data,
const AXNodeData& old_data,
const AXTreeData* optional_new_tree_data,
const AXNodeData& new_data) {
DCHECK(!GetTreeUpdateInProgressState());
DCHECK(node);
DCHECK(node->id() != kInvalidAXNodeID);
if (node->GetRole() == ax::mojom::Role::kRootWebArea &&
old_data.child_ids.empty() && !node->GetParentCrossingTreeBoundary()) {
return;
}
for (AXTreeObserver& observer : observers_)
observer.OnNodeDataChanged(this, old_data, new_data);
if (old_data.role != new_data.role) {
for (AXTreeObserver& observer : observers_)
observer.OnRoleChanged(this, node, old_data.role, new_data.role);
}
if (base::Contains(update_state.ignored_state_changed_ids, new_data.id)) {
for (AXTreeObserver& observer : observers_)
observer.OnIgnoredChanged(this, node, node->IsIgnored());
}
if (old_data.state != new_data.state) {
for (int32_t i = static_cast<int32_t>(ax::mojom::State::kNone) + 1;
i <= static_cast<int32_t>(ax::mojom::State::kMaxValue); ++i) {
ax::mojom::State state = static_cast<ax::mojom::State>(i);
if (state == ax::mojom::State::kIgnored)
continue;
if (old_data.HasState(state) != new_data.HasState(state)) {
for (AXTreeObserver& observer : observers_)
observer.OnStateChanged(this, node, state, new_data.HasState(state));
}
}
}
auto string_callback = [this, node](ax::mojom::StringAttribute attr,
const std::string& old_string,
const std::string& new_string) {
DCHECK_NE(old_string, new_string);
for (AXTreeObserver& observer : observers_) {
observer.OnStringAttributeChanged(this, node, attr, old_string,
new_string);
}
};
CallIfAttributeValuesChanged(old_data.string_attributes,
new_data.string_attributes, std::string(),
string_callback);
auto bool_callback = [this, node](ax::mojom::BoolAttribute attr,
const bool& old_bool,
const bool& new_bool) {
DCHECK_NE(old_bool, new_bool);
for (AXTreeObserver& observer : observers_)
observer.OnBoolAttributeChanged(this, node, attr, new_bool);
};
CallIfAttributeValuesChanged(old_data.bool_attributes,
new_data.bool_attributes, false, bool_callback);
auto float_callback = [this, node](ax::mojom::FloatAttribute attr,
const float& old_float,
const float& new_float) {
DCHECK_NE(old_float, new_float);
for (AXTreeObserver& observer : observers_)
observer.OnFloatAttributeChanged(this, node, attr, old_float, new_float);
};
CallIfAttributeValuesChanged(old_data.float_attributes,
new_data.float_attributes, 0.0f, float_callback);
auto int_callback = [this, node](ax::mojom::IntAttribute attr,
const int& old_int, const int& new_int) {
DCHECK_NE(old_int, new_int);
for (AXTreeObserver& observer : observers_)
observer.OnIntAttributeChanged(this, node, attr, old_int, new_int);
};
CallIfAttributeValuesChanged(old_data.int_attributes, new_data.int_attributes,
0, int_callback);
auto intlist_callback = [this, node](
ax::mojom::IntListAttribute attr,
const std::vector<int32_t>& old_intlist,
const std::vector<int32_t>& new_intlist) {
for (AXTreeObserver& observer : observers_)
observer.OnIntListAttributeChanged(this, node, attr, old_intlist,
new_intlist);
};
CallIfAttributeValuesChanged(old_data.intlist_attributes,
new_data.intlist_attributes,
std::vector<int32_t>(), intlist_callback);
auto stringlist_callback =
[this, node](ax::mojom::StringListAttribute attr,
const std::vector<std::string>& old_stringlist,
const std::vector<std::string>& new_stringlist) {
for (AXTreeObserver& observer : observers_)
observer.OnStringListAttributeChanged(this, node, attr,
old_stringlist, new_stringlist);
};
CallIfAttributeValuesChanged(old_data.stringlist_attributes,
new_data.stringlist_attributes,
std::vector<std::string>(), stringlist_callback);
}
void AXTree::NotifyNodeAttributesHaveBeenChangedOptimized(
AXNode* node,
AXTreeUpdateState& update_state,
AXTreeObserver& observer,
const AXTreeData* optional_old_tree_data,
const AXNodeData& old_data,
const AXTreeData* optional_new_tree_data,
const AXNodeData& new_data) {
CHECK(features::IsUnserializeOptimizationsEnabled());
if (node->GetRole() == ax::mojom::Role::kRootWebArea &&
old_data.child_ids.empty() && !node->GetParentCrossingTreeBoundary()) {
return;
}
observer.OnNodeDataChanged(this, old_data, new_data);
if (old_data.role != new_data.role) {
observer.OnRoleChanged(this, node, old_data.role, new_data.role);
}
if (base::Contains(update_state.ignored_state_changed_ids, new_data.id)) {
observer.OnIgnoredChanged(this, node, node->IsIgnored());
}
if (old_data.state != new_data.state) {
for (int32_t i = static_cast<int32_t>(ax::mojom::State::kNone) + 1;
i <= static_cast<int32_t>(ax::mojom::State::kMaxValue); ++i) {
ax::mojom::State state = static_cast<ax::mojom::State>(i);
if (state == ax::mojom::State::kIgnored) {
continue;
}
if (old_data.HasState(state) != new_data.HasState(state)) {
observer.OnStateChanged(this, node, state, new_data.HasState(state));
}
}
}
auto string_callback = [this, node, &observer](
ax::mojom::StringAttribute attr,
const std::string& old_string,
const std::string& new_string) {
DCHECK_NE(old_string, new_string);
observer.OnStringAttributeChanged(this, node, attr, old_string, new_string);
};
CallIfAttributeValuesChanged(old_data.string_attributes,
new_data.string_attributes, std::string(),
string_callback);
auto bool_callback = [this, node, &observer](ax::mojom::BoolAttribute attr,
const bool& old_bool,
const bool& new_bool) {
DCHECK_NE(old_bool, new_bool);
observer.OnBoolAttributeChanged(this, node, attr, new_bool);
};
CallIfAttributeValuesChanged(old_data.bool_attributes,
new_data.bool_attributes, false, bool_callback);
auto float_callback = [this, node, &observer](ax::mojom::FloatAttribute attr,
const float& old_float,
const float& new_float) {
DCHECK_NE(old_float, new_float);
observer.OnFloatAttributeChanged(this, node, attr, old_float, new_float);
};
CallIfAttributeValuesChanged(old_data.float_attributes,
new_data.float_attributes, 0.0f, float_callback);
auto int_callback = [this, node, &observer](ax::mojom::IntAttribute attr,
const int& old_int,
const int& new_int) {
DCHECK_NE(old_int, new_int);
observer.OnIntAttributeChanged(this, node, attr, old_int, new_int);
};
CallIfAttributeValuesChanged(old_data.int_attributes, new_data.int_attributes,
0, int_callback);
auto intlist_callback = [this, node, &observer](
ax::mojom::IntListAttribute attr,
const std::vector<int32_t>& old_intlist,
const std::vector<int32_t>& new_intlist) {
observer.OnIntListAttributeChanged(this, node, attr, old_intlist,
new_intlist);
};
CallIfAttributeValuesChanged(old_data.intlist_attributes,
new_data.intlist_attributes,
std::vector<int32_t>(), intlist_callback);
auto stringlist_callback =
[this, node, &observer](ax::mojom::StringListAttribute attr,
const std::vector<std::string>& old_stringlist,
const std::vector<std::string>& new_stringlist) {
observer.OnStringListAttributeChanged(this, node, attr, old_stringlist,
new_stringlist);
};
CallIfAttributeValuesChanged(old_data.stringlist_attributes,
new_data.stringlist_attributes,
std::vector<std::string>(), stringlist_callback);
}
void AXTree::UpdateReverseRelations(AXNode* node, const AXNodeData& new_data) {
DCHECK(GetTreeUpdateInProgressState());
const AXNodeData& old_data = node->data();
int id = new_data.id;
auto int_callback = [this, id](ax::mojom::IntAttribute attr,
const int& old_id, const int& new_id) {
if (!IsNodeIdIntAttribute(attr))
return;
auto& map = int_reverse_relations_[attr];
if (map.find(old_id) != map.end()) {
map[old_id].erase(id);
if (map[old_id].empty())
map.erase(old_id);
}
if (new_id)
map[new_id].insert(id);
};
CallIfAttributeValuesChanged(old_data.int_attributes, new_data.int_attributes,
0, int_callback);
auto intlist_callback = [this, id](ax::mojom::IntListAttribute attr,
const std::vector<int32_t>& old_idlist,
const std::vector<int32_t>& new_idlist) {
if (!IsNodeIdIntListAttribute(attr))
return;
auto& map = intlist_reverse_relations_[attr];
for (AXNodeID old_id : old_idlist) {
if (map.find(old_id) != map.end()) {
map[old_id].erase(id);
if (map[old_id].empty())
map.erase(old_id);
}
}
for (AXNodeID new_id : new_idlist)
intlist_reverse_relations_[attr][new_id].insert(id);
};
CallIfAttributeValuesChanged(old_data.intlist_attributes,
new_data.intlist_attributes,
std::vector<AXNodeID>(), intlist_callback);
auto string_callback = [this, id](ax::mojom::StringAttribute attr,
const std::string& old_string,
const std::string& new_string) {
if (attr == ax::mojom::StringAttribute::kChildTreeId) {
AXTreeID old_ax_tree_id = AXTreeID::FromString(old_string);
const auto& iter = child_tree_id_reverse_map_.find(old_ax_tree_id);
if (iter != child_tree_id_reverse_map_.end()) {
std::set<AXNodeID>& node_ids_for_tree_id = iter->second;
node_ids_for_tree_id.erase(id);
if (node_ids_for_tree_id.empty())
child_tree_id_reverse_map_.erase(iter);
}
if (!new_string.empty()) {
AXTreeID new_ax_tree_id = AXTreeID::FromString(new_string);
child_tree_id_reverse_map_[new_ax_tree_id].insert(id);
}
}
};
CallIfAttributeValuesChanged(old_data.string_attributes,
new_data.string_attributes, std::string(),
string_callback);
}
bool AXTree::ValidatePendingChangesComplete(
const AXTreeUpdateState& update_state) {
if (!update_state.pending_node_ids.empty()) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kPendingNodes);
std::string error = "Nodes left pending by the update:";
for (const AXNodeID pending_id : update_state.pending_node_ids)
error += base::StringPrintf(" %d", pending_id);
RecordError(update_state, error);
return false;
}
if (!update_state.node_id_to_pending_data.empty()) {
std::string destroy_subtree_ids;
std::string destroy_node_ids;
std::string create_node_ids;
bool has_pending_changes = false;
for (auto&& pair : update_state.node_id_to_pending_data) {
const AXNodeID pending_id = pair.first;
const std::unique_ptr<PendingStructureChanges>& data = pair.second;
if (data->DoesNodeExpectAnyStructureChanges()) {
if (data->DoesNodeExpectSubtreeWillBeDestroyed())
destroy_subtree_ids += base::StringPrintf(" %d", pending_id);
if (data->DoesNodeExpectNodeWillBeDestroyed())
destroy_node_ids += base::StringPrintf(" %d", pending_id);
if (data->DoesNodeExpectNodeWillBeCreated())
create_node_ids += base::StringPrintf(" %d", pending_id);
has_pending_changes = true;
}
}
if (has_pending_changes) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kPendingChanges);
RecordError(
update_state,
base::StringPrintf(
"Changes left pending by the update; "
"destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
create_node_ids.c_str()));
}
return !has_pending_changes;
}
return true;
}
void AXTree::MarkSubtreeForDestruction(AXNodeID node_id,
AXTreeUpdateState* update_state) {
update_state->IncrementPendingDestroySubtreeCount(node_id);
MarkNodesForDestructionRecursive(node_id, update_state);
}
void AXTree::MarkNodesForDestructionRecursive(AXNodeID node_id,
AXTreeUpdateState* update_state) {
if (!update_state->ShouldPendingNodeExistInTree(node_id))
return;
const AXNodeData& last_known_data =
update_state->GetLastKnownPendingNodeData(node_id);
update_state->IncrementPendingDestroyNodeCount(node_id);
for (AXNodeID child_id : last_known_data.child_ids) {
MarkNodesForDestructionRecursive(child_id, update_state);
}
}
void AXTree::DestroySubtree(AXNode* node,
AXTreeUpdateState* update_state) {
DCHECK(GetTreeUpdateInProgressState());
DCHECK(update_state);
DCHECK_GT(update_state->GetPendingDestroySubtreeCount(node->id()), 0);
DCHECK(!node->parent() ||
update_state->InvalidatesUnignoredCachedValues(node->parent()->id()));
update_state->DecrementPendingDestroySubtreeCount(node->id());
DestroyNodeAndSubtree(node, update_state);
}
void AXTree::DestroyNodeAndSubtree(AXNode* node,
AXTreeUpdateState* update_state) {
AXNodeID id = node->id();
DCHECK(GetTreeUpdateInProgressState());
DCHECK(!update_state || update_state->GetPendingDestroyNodeCount(id) > 0);
AXNodeData empty_data;
empty_data.id = id;
UpdateReverseRelations(node, empty_data);
auto iter = id_map_.find(id);
DCHECK(iter != id_map_.end());
std::unique_ptr<AXNode> node_to_delete = std::move(iter->second);
id_map_.erase(iter);
node = nullptr;
for (auto* child : node_to_delete->children())
DestroyNodeAndSubtree(child, update_state);
if (update_state) {
update_state->pending_node_ids.erase(id);
update_state->DecrementPendingDestroyNodeCount(id);
update_state->removed_node_ids.insert(id);
update_state->new_node_ids.erase(id);
update_state->node_data_changed_ids.erase(id);
if (update_state->IsReparentedNode(node_to_delete.get())) {
update_state->old_node_id_to_data.insert(
std::make_pair(id, node_to_delete->TakeData()));
}
}
}
void AXTree::DeleteOldChildren(AXNode* node,
const std::vector<AXNodeID>& new_child_ids,
AXTreeUpdateState* update_state) {
DCHECK(GetTreeUpdateInProgressState());
std::set<AXNodeID> new_child_id_set(new_child_ids.begin(),
new_child_ids.end());
for (AXNode* child : node->children()) {
if (!base::Contains(new_child_id_set, child->id()))
DestroySubtree(child, update_state);
}
}
bool AXTree::CreateNewChildVector(AXNode* node,
const std::vector<AXNodeID>& new_child_ids,
std::vector<AXNode*>* new_children,
AXTreeUpdateState* update_state) {
DCHECK(GetTreeUpdateInProgressState());
bool success = true;
for (size_t i = 0; i < new_child_ids.size(); ++i) {
AXNodeID child_id = new_child_ids[i];
AXNode* child = GetFromId(child_id);
if (child) {
if (child->parent() != node) {
if (child->parent()) {
RecordError(*update_state,
base::StringPrintf("Node %d reparented from %d to %d",
child->id(), child->parent()->id(),
node->id()));
} else {
std::ostringstream error;
error << "Node did not have a previous parent, but "
"reparenting error triggered:"
<< "\n* root_will_be_created = "
<< update_state->root_will_be_created
<< "\n* pending_root_id = "
<< (update_state->pending_root_id
? *update_state->pending_root_id
: kInvalidAXNodeID)
<< "\n* new parent = " << *node << "\n* Old parent = "
<< (child->parent()
? child->parent()->data().ToString( false)
: "-")
<< "\n* child = " << *child;
RecordError(*update_state, error.str(), true);
}
success = false;
continue;
}
child->SetIndexInParent(i);
} else {
child = CreateNode(node, child_id, i, update_state);
update_state->pending_node_ids.insert(child->id());
}
new_children->push_back(child);
}
return success;
}
AXNode* AXTree::GetUnignoredAncestorFromId(AXNodeID node_id) const {
AXNode* node = GetFromId(node_id);
while (node && node->IsIgnored())
node = node->parent();
return node;
}
AXNodeID AXTree::GetNextNegativeInternalNodeId() {
AXNodeID return_value = next_negative_internal_node_id_;
next_negative_internal_node_id_--;
if (next_negative_internal_node_id_ > 0)
next_negative_internal_node_id_ = -1;
return return_value;
}
void AXTree::PopulateOrderedSetItemsMap(
const AXNode& original_node,
const AXNode* ordered_set,
OrderedSetItemsMap* items_map_to_be_populated) const {
if (original_node.IsIgnored())
return;
absl::optional<int> ordered_set_min_level =
ordered_set->GetHierarchicalLevel();
for (AXNode::UnignoredChildIterator child =
ordered_set->UnignoredChildrenBegin();
child != ordered_set->UnignoredChildrenEnd(); ++child) {
absl::optional<int> child_level = child->GetHierarchicalLevel();
if (child_level) {
ordered_set_min_level = ordered_set_min_level
? std::min(child_level, ordered_set_min_level)
: child_level;
}
}
RecursivelyPopulateOrderedSetItemsMap(original_node, ordered_set, ordered_set,
ordered_set_min_level, absl::nullopt,
items_map_to_be_populated);
if (&original_node == ordered_set &&
!items_map_to_be_populated->HierarchicalLevelExists(
ordered_set_min_level)) {
items_map_to_be_populated->Add(ordered_set_min_level,
OrderedSetContent(&original_node));
}
}
void AXTree::RecursivelyPopulateOrderedSetItemsMap(
const AXNode& original_node,
const AXNode* ordered_set,
const AXNode* local_parent,
absl::optional<int> ordered_set_min_level,
absl::optional<int> prev_level,
OrderedSetItemsMap* items_map_to_be_populated) const {
if (ordered_set->GetRole() == local_parent->GetRole() &&
ordered_set != local_parent)
return;
for (AXNode::UnignoredChildIterator itr =
local_parent->UnignoredChildrenBegin();
itr != local_parent->UnignoredChildrenEnd(); ++itr) {
const AXNode* child = itr.get();
if (child->data().IsInvisible() && !IsCollapsed(local_parent) &&
!IsCollapsed(local_parent->parent())) {
continue;
}
absl::optional<int> curr_level = child->GetHierarchicalLevel();
if (child->GetRole() == ax::mojom::Role::kComment ||
(original_node.GetRole() == ax::mojom::Role::kRadioButton &&
child->GetRole() == ax::mojom::Role::kRadioButton) ||
(original_node.GetRole() != ax::mojom::Role::kRadioButton &&
child->SetRoleMatchesItemRole(ordered_set))) {
if (!curr_level && child->GetUnignoredParent() == ordered_set)
curr_level = ordered_set_min_level;
if (!items_map_to_be_populated->HierarchicalLevelExists(curr_level)) {
bool use_ordered_set = child->SetRoleMatchesItemRole(ordered_set) &&
ordered_set_min_level == curr_level;
const AXNode* child_ordered_set =
use_ordered_set ? ordered_set : nullptr;
items_map_to_be_populated->Add(curr_level,
OrderedSetContent(child_ordered_set));
}
items_map_to_be_populated->AddItemToBack(curr_level, child);
}
if (child->IsIgnoredContainerForOrderedSet()) {
RecursivelyPopulateOrderedSetItemsMap(original_node, ordered_set, child,
ordered_set_min_level, curr_level,
items_map_to_be_populated);
}
if (child->SetRoleMatchesItemRole(ordered_set) && curr_level < prev_level)
items_map_to_be_populated->Add(prev_level, OrderedSetContent());
prev_level = curr_level;
}
}
void AXTree::ComputeSetSizePosInSetAndCache(const AXNode& node,
const AXNode* ordered_set) {
DCHECK(ordered_set);
if (node.GetRole() != ax::mojom::Role::kComment &&
node.GetRole() != ax::mojom::Role::kRadioButton &&
!node.SetRoleMatchesItemRole(ordered_set) && !node.IsOrderedSet())
return;
OrderedSetItemsMap items_map_to_be_populated;
PopulateOrderedSetItemsMap(node, ordered_set, &items_map_to_be_populated);
if (node.GetRole() == ax::mojom::Role::kComboBoxSelect &&
node.GetUnignoredChildCount() > 0) {
OrderedSetContent* set_content =
items_map_to_be_populated.GetFirstOrderedSetContent();
if (set_content && set_content->set_items_.size() == 1) {
const AXNode* menu_list_popup = set_content->set_items_.front();
if (menu_list_popup->GetRole() == ax::mojom::Role::kMenuListPopup) {
items_map_to_be_populated.Clear();
PopulateOrderedSetItemsMap(node, menu_list_popup,
&items_map_to_be_populated);
set_content = items_map_to_be_populated.GetFirstOrderedSetContent();
if (set_content)
set_content->ordered_set_ = &node;
}
}
}
for (auto element : items_map_to_be_populated.items_map_) {
for (const OrderedSetContent& ordered_set_content : element.second) {
ComputeSetSizePosInSetAndCacheHelper(ordered_set_content);
}
}
}
void AXTree::ComputeSetSizePosInSetAndCacheHelper(
const OrderedSetContent& ordered_set_content) {
int32_t num_elements = 0;
int32_t max_item_set_size_from_attribute = 0;
for (const AXNode* item : ordered_set_content.set_items_) {
int32_t pos_in_set_value =
std::max(num_elements + 1,
item->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
if (item->GetHierarchicalLevel() &&
item->HasIntAttribute(ax::mojom::IntAttribute::kPosInSet))
pos_in_set_value =
item->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
num_elements = pos_in_set_value;
node_set_size_pos_in_set_info_map_[item->id()] = NodeSetSizePosInSetInfo();
node_set_size_pos_in_set_info_map_[item->id()].pos_in_set =
pos_in_set_value;
max_item_set_size_from_attribute =
std::max(max_item_set_size_from_attribute,
item->GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
int32_t set_size_value =
std::max(num_elements, max_item_set_size_from_attribute);
if (const AXNode* ordered_set = ordered_set_content.ordered_set_) {
set_size_value = std::max(
set_size_value,
ordered_set->GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
absl::optional<int> ordered_set_level = ordered_set->GetHierarchicalLevel();
if (node_set_size_pos_in_set_info_map_.find(ordered_set->id()) ==
node_set_size_pos_in_set_info_map_.end()) {
node_set_size_pos_in_set_info_map_[ordered_set->id()] =
NodeSetSizePosInSetInfo();
node_set_size_pos_in_set_info_map_[ordered_set->id()]
.lowest_hierarchical_level = ordered_set_level;
} else if (node_set_size_pos_in_set_info_map_[ordered_set->id()]
.lowest_hierarchical_level > ordered_set_level) {
node_set_size_pos_in_set_info_map_[ordered_set->id()]
.lowest_hierarchical_level = ordered_set_level;
}
node_set_size_pos_in_set_info_map_[ordered_set->id()].set_size =
set_size_value;
}
for (const AXNode* item : ordered_set_content.set_items_) {
if (item->GetHierarchicalLevel() &&
item->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
node_set_size_pos_in_set_info_map_[item->id()].set_size =
item->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
else
node_set_size_pos_in_set_info_map_[item->id()].set_size = set_size_value;
}
}
absl::optional<int> AXTree::GetPosInSet(const AXNode& node) {
if (node.IsIgnored()) {
return absl::nullopt;
}
if ((node.GetRole() == ax::mojom::Role::kComboBoxSelect ||
node.GetRole() == ax::mojom::Role::kPopUpButton) &&
node.GetUnignoredChildCount() == 0 &&
node.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
return node.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
}
if (node_set_size_pos_in_set_info_map_.find(node.id()) !=
node_set_size_pos_in_set_info_map_.end()) {
return node_set_size_pos_in_set_info_map_[node.id()].pos_in_set;
}
if (GetTreeUpdateInProgressState())
return absl::nullopt;
if (!node.IsOrderedSetItem()) {
return absl::nullopt;
}
const AXNode* ordered_set = node.GetOrderedSet();
if (!ordered_set)
return absl::nullopt;
ComputeSetSizePosInSetAndCache(node, ordered_set);
absl::optional<int> pos_in_set =
node_set_size_pos_in_set_info_map_[node.id()].pos_in_set;
if (pos_in_set.has_value() && pos_in_set.value() < 1)
return absl::nullopt;
return pos_in_set;
}
absl::optional<int> AXTree::GetSetSize(const AXNode& node) {
if (node.IsIgnored()) {
return absl::nullopt;
};
if ((node.GetRole() == ax::mojom::Role::kComboBoxSelect ||
node.GetRole() == ax::mojom::Role::kPopUpButton) &&
node.GetUnignoredChildCount() == 0 &&
node.HasIntAttribute(ax::mojom::IntAttribute::kSetSize)) {
return node.GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
if (node_set_size_pos_in_set_info_map_.find(node.id()) !=
node_set_size_pos_in_set_info_map_.end()) {
return node_set_size_pos_in_set_info_map_[node.id()].set_size;
}
if (GetTreeUpdateInProgressState())
return absl::nullopt;
if ((!node.IsOrderedSetItem() && !node.IsOrderedSet()) ||
node.IsEmbeddedGroup()) {
return absl::nullopt;
}
const AXNode* ordered_set = &node;
if (node.IsOrderedSetItem())
ordered_set = node.GetOrderedSet();
if (!ordered_set)
return absl::nullopt;
if (node.GetRole() == ax::mojom::Role::kPopUpButton ||
node.GetRole() == ax::mojom::Role::kComboBoxSelect) {
const auto& controls_ids =
node.GetIntListAttribute(ax::mojom::IntListAttribute::kControlsIds);
if (controls_ids.size() == 1 && GetFromId(controls_ids[0]) &&
controls_ids[0] != node.id()) {
const AXNode& controlled_item = *GetFromId(controls_ids[0]);
absl::optional<int> controlled_item_set_size =
GetSetSize(controlled_item);
node_set_size_pos_in_set_info_map_[node.id()].set_size =
controlled_item_set_size;
return controlled_item_set_size;
}
}
ComputeSetSizePosInSetAndCache(node, ordered_set);
absl::optional<int> set_size =
node_set_size_pos_in_set_info_map_[node.id()].set_size;
if (set_size.has_value() && set_size.value() < 0)
return absl::nullopt;
return set_size;
}
AXSelection AXTree::GetSelection() const {
return AXSelection(*this);
}
AXSelection AXTree::GetUnignoredSelection() const {
return GetSelection().ToUnignoredSelection();
}
bool AXTree::GetTreeUpdateInProgressState() const {
return tree_update_in_progress_;
}
void AXTree::SetTreeUpdateInProgressState(bool set_tree_update_value) {
tree_update_in_progress_ = set_tree_update_value;
}
bool AXTree::HasPaginationSupport() const {
return has_pagination_support_;
}
void AXTree::NotifyTreeManagerWillBeRemoved(AXTreeID previous_tree_id) {
if (previous_tree_id == AXTreeIDUnknown())
return;
for (AXTreeObserver& observer : observers_)
observer.OnTreeManagerWillBeRemoved(previous_tree_id);
}
void AXTree::RecordError(const AXTreeUpdateState& update_state,
std::string new_error,
bool is_fatal) {
if (!error_.empty())
error_ = error_ + "\n";
error_ = error_ + new_error;
#if defined(AX_FAIL_FAST_BUILD)
is_fatal = true;
#endif
std::ostringstream verbose_error;
verbose_error << new_error << "\n** Pending tree update **\n"
<< update_state.pending_tree_update->ToString(
false)
<< "\n** AXTreeData ** \n"
<< data_.ToString() + "\n** AXTree **"
<< TreeToStringHelper(root_, 0, false).substr(0, 1000);
if (is_fatal && !disallow_fail_fast_) {
LOG(FATAL) << verbose_error.str();
}
static auto* const ax_tree_error_key = base::debug::AllocateCrashKeyString(
"ax_tree_error", base::debug::CrashKeySize::Size256);
static auto* const ax_tree_update_key = base::debug::AllocateCrashKeyString(
"ax_tree_update", base::debug::CrashKeySize::Size256);
static auto* const ax_tree_key = base::debug::AllocateCrashKeyString(
"ax_tree", base::debug::CrashKeySize::Size256);
static auto* const ax_tree_data_key = base::debug::AllocateCrashKeyString(
"ax_tree_data", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(ax_tree_error_key, new_error);
base::debug::SetCrashKeyString(ax_tree_update_key,
update_state.pending_tree_update->ToString());
base::debug::SetCrashKeyString(ax_tree_key,
TreeToStringHelper(root_, 0, false));
base::debug::SetCrashKeyString(ax_tree_data_key, data_.ToString());
LOG(ERROR) << verbose_error.str();
}
}