| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/accessibility/ax_tree_manager_base.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "ui/accessibility/ax_node.h" |
| |
| namespace ui { |
| |
| // static |
| AXTreeManagerBase* AXTreeManagerBase::GetManager(const AXTreeID& tree_id) { |
| if (tree_id.type() == ax::mojom::AXTreeIDType::kUnknown) |
| return nullptr; |
| auto iter = GetTreeManagerMapInstance().find(tree_id); |
| if (iter == GetTreeManagerMapInstance().end()) |
| return nullptr; |
| return iter->second; |
| } |
| |
| // static |
| std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>& |
| AXTreeManagerBase::GetTreeManagerMapInstance() { |
| static base::NoDestructor< |
| std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>> |
| map_instance; |
| return *map_instance; |
| } |
| |
| AXTreeManagerBase::AXTreeManagerBase() = default; |
| |
| AXTreeManagerBase::AXTreeManagerBase(std::unique_ptr<AXTree> tree) { |
| if (!tree) |
| return; |
| |
| const AXTreeID& tree_id = tree->GetAXTreeID(); |
| if (tree_id.type() == ax::mojom::AXTreeIDType::kUnknown) { |
| NOTREACHED() << "Invalid tree ID.\n" << tree->ToString(); |
| } |
| |
| tree_ = std::move(tree); |
| GetTreeManagerMapInstance()[tree_id] = this; |
| } |
| |
| AXTreeManagerBase::AXTreeManagerBase(const AXTreeUpdate& initial_state) |
| : AXTreeManagerBase(std::make_unique<AXTree>(initial_state)) {} |
| |
| AXTreeManagerBase::~AXTreeManagerBase() { |
| if (!tree_) |
| return; |
| |
| DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown); |
| tree_->NotifyTreeManagerWillBeRemoved(GetTreeID()); |
| GetTreeManagerMapInstance().erase(GetTreeID()); |
| } |
| |
| AXTreeManagerBase::AXTreeManagerBase(AXTreeManagerBase&& manager) { |
| if (!manager.tree_) { |
| ReleaseTree(); |
| return; |
| } |
| |
| manager.tree_->NotifyTreeManagerWillBeRemoved(manager.GetTreeID()); |
| GetTreeManagerMapInstance().erase(manager.GetTreeID()); |
| SetTree(std::move(manager.tree_)); |
| } |
| |
| AXTreeManagerBase& AXTreeManagerBase::operator=(AXTreeManagerBase&& manager) { |
| if (this == &manager) |
| return *this; |
| |
| if (manager.tree_) { |
| manager.tree_->NotifyTreeManagerWillBeRemoved(manager.GetTreeID()); |
| GetTreeManagerMapInstance().erase(manager.GetTreeID()); |
| SetTree(std::move(manager.tree_)); |
| } else { |
| ReleaseTree(); |
| } |
| |
| return *this; |
| } |
| |
| AXTree* AXTreeManagerBase::GetTree() const { |
| return tree_.get(); |
| } |
| |
| std::unique_ptr<AXTree> AXTreeManagerBase::SetTree( |
| std::unique_ptr<AXTree> tree) { |
| if (!tree) { |
| NOTREACHED() |
| << "Attempting to set a new tree, but no tree has been provided."; |
| } |
| |
| if (tree->GetAXTreeID().type() == ax::mojom::AXTreeIDType::kUnknown) { |
| NOTREACHED() << "Invalid tree ID.\n" << tree->ToString(); |
| } |
| |
| if (tree_) { |
| tree_->NotifyTreeManagerWillBeRemoved(GetTreeID()); |
| GetTreeManagerMapInstance().erase(GetTreeID()); |
| } |
| |
| std::swap(tree_, tree); |
| GetTreeManagerMapInstance()[GetTreeID()] = this; |
| return tree; |
| } |
| |
| std::unique_ptr<AXTree> AXTreeManagerBase::SetTree( |
| const AXTreeUpdate& initial_state) { |
| return SetTree(std::make_unique<AXTree>(initial_state)); |
| } |
| |
| std::unique_ptr<AXTree> AXTreeManagerBase::ReleaseTree() { |
| if (!tree_) |
| return {}; |
| |
| tree_->NotifyTreeManagerWillBeRemoved(GetTreeID()); |
| GetTreeManagerMapInstance().erase(GetTreeID()); |
| return std::move(tree_); |
| } |
| |
| AXTreeUpdate AXTreeManagerBase::SnapshotTree() const { |
| NOTIMPLEMENTED(); |
| return {}; |
| } |
| |
| bool AXTreeManagerBase::ApplyTreeUpdate(const AXTreeUpdate& update) { |
| if (tree_) |
| return tree_->Unserialize(update); |
| return false; |
| } |
| |
| const AXTreeID& AXTreeManagerBase::GetTreeID() const { |
| if (tree_) |
| return tree_->GetAXTreeID(); |
| return AXTreeIDUnknown(); |
| } |
| |
| const AXTreeID& AXTreeManagerBase::GetParentTreeID() const { |
| if (tree_) |
| return GetTreeData().parent_tree_id; |
| return AXTreeIDUnknown(); |
| } |
| |
| const AXTreeData& AXTreeManagerBase::GetTreeData() const { |
| static const base::NoDestructor<AXTreeData> empty_tree_data; |
| if (tree_) |
| return tree_->data(); |
| return *empty_tree_data; |
| } |
| |
| // static |
| AXNode* AXTreeManagerBase::GetNodeFromTree(const AXTreeID& tree_id, |
| const AXNodeID& node_id) { |
| const AXTreeManagerBase* manager = GetManager(tree_id); |
| if (manager) |
| return manager->GetNode(node_id); |
| return nullptr; |
| } |
| |
| AXNode* AXTreeManagerBase::GetNode(const AXNodeID& node_id) const { |
| if (tree_) |
| return tree_->GetFromId(node_id); |
| return nullptr; |
| } |
| |
| AXNode* AXTreeManagerBase::GetRoot() const { |
| if (!tree_) |
| return nullptr; |
| // tree_->root() can be nullptr during `AXTreeObserver` callbacks. |
| return tree_->root(); |
| } |
| |
| AXNode* AXTreeManagerBase::GetRootOfChildTree( |
| const AXNodeID& host_node_id) const { |
| const AXNode* host_node = GetNode(host_node_id); |
| if (host_node) |
| return GetRootOfChildTree(*host_node); |
| return nullptr; |
| } |
| |
| AXNode* AXTreeManagerBase::GetRootOfChildTree(const AXNode& host_node) const { |
| const AXTreeID& child_tree_id = AXTreeID::FromString( |
| host_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId)); |
| const AXTreeManagerBase* child_manager = GetManager(child_tree_id); |
| if (!child_manager || !child_manager->GetTree()) |
| return nullptr; |
| // `AXTree::root()` can be nullptr during `AXTreeObserver` callbacks. |
| return child_manager->GetTree()->root(); |
| } |
| |
| AXNode* AXTreeManagerBase::GetHostNode() const { |
| const AXTreeID& parent_tree_id = GetParentTreeID(); |
| const AXTreeManagerBase* parent_manager = GetManager(parent_tree_id); |
| if (!parent_manager || !parent_manager->GetTree()) |
| return nullptr; // The parent tree is not present or is empty. |
| |
| const std::set<AXNodeID>& host_node_ids = |
| parent_manager->GetTree()->GetNodeIdsForChildTreeId(GetTreeID()); |
| if (host_node_ids.empty()) { |
| // The parent tree is present but is still not connected. A connection will |
| // be established when it updates one of its nodes, turning it into a host |
| // node pointing to this child tree. |
| return nullptr; |
| } |
| |
| DCHECK_EQ(host_node_ids.size(), 1u) |
| << "Multiple nodes claim the same child tree ID."; |
| const AXNodeID& host_node_id = *host_node_ids.begin(); |
| AXNode* parent_node = parent_manager->GetNode(host_node_id); |
| DCHECK(parent_node); |
| DCHECK_EQ(GetTreeID(), AXTreeID::FromString(parent_node->GetStringAttribute( |
| ax::mojom::StringAttribute::kChildTreeId))) |
| << "A node that hosts a child tree should expose its tree ID in its " |
| "`kChildTreeId` attribute."; |
| return parent_node; |
| } |
| |
| bool AXTreeManagerBase::AttachChildTree(const AXNodeID& host_node_id, |
| AXTreeManagerBase& child_manager) { |
| AXNode* host_node = GetNode(host_node_id); |
| if (host_node) |
| return AttachChildTree(*host_node, child_manager); |
| return false; |
| } |
| |
| bool AXTreeManagerBase::AttachChildTree(AXNode& host_node, |
| AXTreeManagerBase& child_manager) { |
| if (!tree_ || !child_manager.GetTree()) |
| return false; |
| if (child_manager.GetParentTreeID().type() != |
| ax::mojom::AXTreeIDType::kUnknown) { |
| return false; // Child manager already attached to another host node. |
| } |
| |
| DCHECK_EQ(GetNode(host_node.id()), &host_node) |
| << "`host_node` should belong to this manager."; |
| DCHECK(host_node.tree()) |
| << "A node should always be attached to its owning tree."; |
| DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown); |
| DCHECK_NE(child_manager.GetTreeID().type(), |
| ax::mojom::AXTreeIDType::kUnknown); |
| if (!host_node.IsLeaf()) { |
| // For now, child trees can only be attached on leaf nodes, otherwise |
| // behavior would be ambiguous. |
| return false; |
| } |
| |
| { |
| AXTreeUpdate update; |
| update.nodes.emplace_back(AXNodeData(host_node.data())); |
| DCHECK( |
| !host_node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) |
| << "`AXNode::IsLeaf()` should mark all nodes with child tree IDs as " |
| "leaves.\n" |
| << host_node; |
| update.nodes[0].AddChildTreeId(child_manager.GetTreeID()); |
| CHECK(ApplyTreeUpdate(update)) << GetTree()->error(); |
| } |
| |
| { |
| AXTreeData tree_data = child_manager.GetTreeData(); |
| tree_data.parent_tree_id = GetTreeID(); |
| AXTreeUpdate update; |
| update.has_tree_data = true; |
| update.tree_data = tree_data; |
| CHECK(child_manager.ApplyTreeUpdate(update)) |
| << child_manager.GetTree()->error(); |
| } |
| |
| return true; |
| } |
| |
| std::optional<AXTreeManagerBase> AXTreeManagerBase::AttachChildTree( |
| const AXNodeID& host_node_id, |
| const AXTreeUpdate& initial_state) { |
| AXNode* host_node = GetNode(host_node_id); |
| if (host_node) |
| return AttachChildTree(*host_node, initial_state); |
| return std::nullopt; |
| } |
| |
| std::optional<AXTreeManagerBase> AXTreeManagerBase::AttachChildTree( |
| AXNode& host_node, |
| const AXTreeUpdate& initial_state) { |
| AXTreeManagerBase child_manager(initial_state); |
| if (AttachChildTree(host_node, child_manager)) |
| return child_manager; |
| return std::nullopt; |
| } |
| |
| AXTreeManagerBase* AXTreeManagerBase::DetachChildTree( |
| const AXNodeID& host_node_id) { |
| AXNode* host_node = GetNode(host_node_id); |
| if (host_node) |
| return DetachChildTree(*host_node); |
| return nullptr; |
| } |
| |
| AXTreeManagerBase* AXTreeManagerBase::DetachChildTree(AXNode& host_node) { |
| if (!tree_) |
| return nullptr; |
| |
| DCHECK_EQ(GetNode(host_node.id()), &host_node) |
| << "`host_node` should belong to this manager."; |
| DCHECK(host_node.tree()) |
| << "A node should always be attached to its owning tree."; |
| DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown); |
| if (!host_node.IsLeaf()) { |
| // For now, child trees are only allowed to be attached on leaf nodes, |
| // otherwise behavior would be ambiguous. |
| return nullptr; |
| } |
| |
| const AXTreeID& child_tree_id = AXTreeID::FromString( |
| host_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId)); |
| AXTreeManagerBase* child_manager = GetManager(child_tree_id); |
| if (!child_manager || !child_manager->GetTree()) |
| return nullptr; |
| |
| DCHECK_NE(child_manager->GetTreeID().type(), |
| ax::mojom::AXTreeIDType::kUnknown); |
| |
| { |
| AXTreeUpdate update; |
| update.nodes.emplace_back(AXNodeData(host_node.data())); |
| DCHECK_NE(child_manager->GetTreeData().parent_tree_id.type(), |
| ax::mojom::AXTreeIDType::kUnknown) |
| << "Child tree should be attached to its host node.\n" |
| << host_node; |
| update.nodes[0].RemoveStringAttribute( |
| ax::mojom::StringAttribute::kChildTreeId); |
| CHECK(ApplyTreeUpdate(update)) << GetTree()->error(); |
| } |
| |
| { |
| AXTreeData tree_data = child_manager->GetTreeData(); |
| tree_data.parent_tree_id = AXTreeIDUnknown(); |
| AXTreeUpdate update; |
| update.has_tree_data = true; |
| update.tree_data = tree_data; |
| CHECK(child_manager->ApplyTreeUpdate(update)) |
| << child_manager->GetTree()->error(); |
| } |
| |
| return child_manager; |
| } |
| |
| } // namespace ui |