blob: 506a7498f04a4e2477cf163787d911118dd2ec6c [file] [log] [blame]
// 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