blob: 8b56fa2691546ebdbad4dd07215247e595f2b8af [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2022 The Chromium Authors
Nektarios Paisios50e579f12022-05-19 19:22:392// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/accessibility/ax_tree_manager_base.h"
6
7#include <set>
8#include <utility>
9
10#include "base/check_op.h"
11#include "base/no_destructor.h"
12#include "base/notreached.h"
13#include "ui/accessibility/ax_node.h"
14
15namespace ui {
16
17// static
18AXTreeManagerBase* AXTreeManagerBase::GetManager(const AXTreeID& tree_id) {
19 if (tree_id.type() == ax::mojom::AXTreeIDType::kUnknown)
20 return nullptr;
21 auto iter = GetTreeManagerMapInstance().find(tree_id);
22 if (iter == GetTreeManagerMapInstance().end())
23 return nullptr;
24 return iter->second;
25}
26
27// static
Bruce Dawson9d565ec2022-10-31 17:42:3428std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>&
Nektarios Paisios50e579f12022-05-19 19:22:3929AXTreeManagerBase::GetTreeManagerMapInstance() {
Bruce Dawson9d565ec2022-10-31 17:42:3430 static base::NoDestructor<
31 std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>>
Nektarios Paisios50e579f12022-05-19 19:22:3932 map_instance;
33 return *map_instance;
34}
35
36AXTreeManagerBase::AXTreeManagerBase() = default;
37
38AXTreeManagerBase::AXTreeManagerBase(std::unique_ptr<AXTree> tree) {
39 if (!tree)
40 return;
41
42 const AXTreeID& tree_id = tree->GetAXTreeID();
43 if (tree_id.type() == ax::mojom::AXTreeIDType::kUnknown) {
44 NOTREACHED() << "Invalid tree ID.\n" << tree->ToString();
45 return;
46 }
47
48 tree_ = std::move(tree);
49 GetTreeManagerMapInstance()[tree_id] = this;
50}
51
52AXTreeManagerBase::AXTreeManagerBase(const AXTreeUpdate& initial_state)
53 : AXTreeManagerBase(std::make_unique<AXTree>(initial_state)) {}
54
55AXTreeManagerBase::~AXTreeManagerBase() {
56 if (!tree_)
57 return;
58
59 DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown);
60 tree_->NotifyTreeManagerWillBeRemoved(GetTreeID());
61 GetTreeManagerMapInstance().erase(GetTreeID());
62}
63
64AXTreeManagerBase::AXTreeManagerBase(AXTreeManagerBase&& manager) {
65 if (!manager.tree_) {
66 ReleaseTree();
67 return;
68 }
69
70 manager.tree_->NotifyTreeManagerWillBeRemoved(manager.GetTreeID());
71 GetTreeManagerMapInstance().erase(manager.GetTreeID());
72 SetTree(std::move(manager.tree_));
73}
74
75AXTreeManagerBase& AXTreeManagerBase::operator=(AXTreeManagerBase&& manager) {
76 if (this == &manager)
77 return *this;
78
79 if (manager.tree_) {
80 manager.tree_->NotifyTreeManagerWillBeRemoved(manager.GetTreeID());
81 GetTreeManagerMapInstance().erase(manager.GetTreeID());
82 SetTree(std::move(manager.tree_));
83 } else {
84 ReleaseTree();
85 }
86
87 return *this;
88}
89
90AXTree* AXTreeManagerBase::GetTree() const {
91 return tree_.get();
92}
93
94std::unique_ptr<AXTree> AXTreeManagerBase::SetTree(
95 std::unique_ptr<AXTree> tree) {
96 if (!tree) {
97 NOTREACHED()
98 << "Attempting to set a new tree, but no tree has been provided.";
99 return {};
100 }
101
102 if (tree->GetAXTreeID().type() == ax::mojom::AXTreeIDType::kUnknown) {
103 NOTREACHED() << "Invalid tree ID.\n" << tree->ToString();
104 return {};
105 }
106
107 if (tree_) {
108 tree_->NotifyTreeManagerWillBeRemoved(GetTreeID());
109 GetTreeManagerMapInstance().erase(GetTreeID());
110 }
111
112 std::swap(tree_, tree);
113 GetTreeManagerMapInstance()[GetTreeID()] = this;
114 return tree;
115}
116
117std::unique_ptr<AXTree> AXTreeManagerBase::SetTree(
118 const AXTreeUpdate& initial_state) {
119 return SetTree(std::make_unique<AXTree>(initial_state));
120}
121
122std::unique_ptr<AXTree> AXTreeManagerBase::ReleaseTree() {
123 if (!tree_)
124 return {};
125
126 tree_->NotifyTreeManagerWillBeRemoved(GetTreeID());
127 GetTreeManagerMapInstance().erase(GetTreeID());
128 return std::move(tree_);
129}
130
131AXTreeUpdate AXTreeManagerBase::SnapshotTree() const {
132 NOTIMPLEMENTED();
133 return {};
134}
135
136bool AXTreeManagerBase::ApplyTreeUpdate(const AXTreeUpdate& update) {
137 if (tree_)
138 return tree_->Unserialize(update);
139 return false;
140}
141
142const AXTreeID& AXTreeManagerBase::GetTreeID() const {
143 if (tree_)
144 return tree_->GetAXTreeID();
145 return AXTreeIDUnknown();
146}
147
148const AXTreeID& AXTreeManagerBase::GetParentTreeID() const {
149 if (tree_)
150 return GetTreeData().parent_tree_id;
151 return AXTreeIDUnknown();
152}
153
154const AXTreeData& AXTreeManagerBase::GetTreeData() const {
155 static const base::NoDestructor<AXTreeData> empty_tree_data;
156 if (tree_)
157 return tree_->data();
158 return *empty_tree_data;
159}
160
161// static
162AXNode* AXTreeManagerBase::GetNodeFromTree(const AXTreeID& tree_id,
163 const AXNodeID& node_id) {
164 const AXTreeManagerBase* manager = GetManager(tree_id);
165 if (manager)
166 return manager->GetNode(node_id);
167 return nullptr;
168}
169
170AXNode* AXTreeManagerBase::GetNode(const AXNodeID& node_id) const {
171 if (tree_)
172 return tree_->GetFromId(node_id);
173 return nullptr;
174}
175
176AXNode* AXTreeManagerBase::GetRoot() const {
177 if (!tree_)
178 return nullptr;
179 // tree_->root() can be nullptr during `AXTreeObserver` callbacks.
180 return tree_->root();
181}
182
183AXNode* AXTreeManagerBase::GetRootOfChildTree(
184 const AXNodeID& host_node_id) const {
185 const AXNode* host_node = GetNode(host_node_id);
186 if (host_node)
187 return GetRootOfChildTree(*host_node);
188 return nullptr;
189}
190
191AXNode* AXTreeManagerBase::GetRootOfChildTree(const AXNode& host_node) const {
192 const AXTreeID& child_tree_id = AXTreeID::FromString(
193 host_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
194 const AXTreeManagerBase* child_manager = GetManager(child_tree_id);
195 if (!child_manager || !child_manager->GetTree())
196 return nullptr;
197 // `AXTree::root()` can be nullptr during `AXTreeObserver` callbacks.
198 return child_manager->GetTree()->root();
199}
200
201AXNode* AXTreeManagerBase::GetHostNode() const {
202 const AXTreeID& parent_tree_id = GetParentTreeID();
203 const AXTreeManagerBase* parent_manager = GetManager(parent_tree_id);
204 if (!parent_manager || !parent_manager->GetTree())
205 return nullptr; // The parent tree is not present or is empty.
206
207 const std::set<AXNodeID>& host_node_ids =
208 parent_manager->GetTree()->GetNodeIdsForChildTreeId(GetTreeID());
209 if (host_node_ids.empty()) {
210 // The parent tree is present but is still not connected. A connection will
211 // be established when it updates one of its nodes, turning it into a host
212 // node pointing to this child tree.
213 return nullptr;
214 }
215
216 DCHECK_EQ(host_node_ids.size(), 1u)
217 << "Multiple nodes claim the same child tree ID.";
218 const AXNodeID& host_node_id = *host_node_ids.begin();
219 AXNode* parent_node = parent_manager->GetNode(host_node_id);
220 DCHECK(parent_node);
221 DCHECK_EQ(GetTreeID(), AXTreeID::FromString(parent_node->GetStringAttribute(
222 ax::mojom::StringAttribute::kChildTreeId)))
223 << "A node that hosts a child tree should expose its tree ID in its "
224 "`kChildTreeId` attribute.";
225 return parent_node;
226}
227
228bool AXTreeManagerBase::AttachChildTree(const AXNodeID& host_node_id,
229 AXTreeManagerBase& child_manager) {
230 AXNode* host_node = GetNode(host_node_id);
231 if (host_node)
232 return AttachChildTree(*host_node, child_manager);
233 return false;
234}
235
236bool AXTreeManagerBase::AttachChildTree(AXNode& host_node,
237 AXTreeManagerBase& child_manager) {
238 if (!tree_ || !child_manager.GetTree())
239 return false;
240 if (child_manager.GetParentTreeID().type() !=
241 ax::mojom::AXTreeIDType::kUnknown) {
242 return false; // Child manager already attached to another host node.
243 }
244
245 DCHECK_EQ(GetNode(host_node.id()), &host_node)
246 << "`host_node` should belong to this manager.";
247 DCHECK(host_node.tree())
248 << "A node should always be attached to its owning tree.";
249 DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown);
250 DCHECK_NE(child_manager.GetTreeID().type(),
251 ax::mojom::AXTreeIDType::kUnknown);
252 if (!host_node.IsLeaf()) {
253 // For now, child trees can only be attached on leaf nodes, otherwise
254 // behavior would be ambiguous.
255 return false;
256 }
257
258 {
259 AXNodeData host_node_data = host_node.data();
260 DCHECK(
261 !host_node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId))
262 << "`AXNode::IsLeaf()` should mark all nodes with child tree IDs as "
263 "leaves.\n"
264 << host_node;
265 host_node_data.AddChildTreeId(child_manager.GetTreeID());
266 AXTreeUpdate update;
267 update.nodes = {host_node_data};
268 CHECK(ApplyTreeUpdate(update)) << GetTree()->error();
269 }
270
271 {
272 AXTreeData tree_data = child_manager.GetTreeData();
273 tree_data.parent_tree_id = GetTreeID();
274 AXTreeUpdate update;
275 update.has_tree_data = true;
276 update.tree_data = tree_data;
277 CHECK(child_manager.ApplyTreeUpdate(update))
278 << child_manager.GetTree()->error();
279 }
280
281 return true;
282}
283
Nektarios Paisios68900e62022-05-20 14:42:30284absl::optional<AXTreeManagerBase> AXTreeManagerBase::AttachChildTree(
Nektarios Paisios50e579f12022-05-19 19:22:39285 const AXNodeID& host_node_id,
286 const AXTreeUpdate& initial_state) {
287 AXNode* host_node = GetNode(host_node_id);
288 if (host_node)
289 return AttachChildTree(*host_node, initial_state);
Nektarios Paisios68900e62022-05-20 14:42:30290 return absl::nullopt;
Nektarios Paisios50e579f12022-05-19 19:22:39291}
292
Nektarios Paisios68900e62022-05-20 14:42:30293absl::optional<AXTreeManagerBase> AXTreeManagerBase::AttachChildTree(
Nektarios Paisios50e579f12022-05-19 19:22:39294 AXNode& host_node,
295 const AXTreeUpdate& initial_state) {
296 AXTreeManagerBase child_manager(initial_state);
297 if (AttachChildTree(host_node, child_manager))
298 return child_manager;
Nektarios Paisios68900e62022-05-20 14:42:30299 return absl::nullopt;
Nektarios Paisios50e579f12022-05-19 19:22:39300}
301
302AXTreeManagerBase* AXTreeManagerBase::DetachChildTree(
303 const AXNodeID& host_node_id) {
304 AXNode* host_node = GetNode(host_node_id);
305 if (host_node)
306 return DetachChildTree(*host_node);
307 return nullptr;
308}
309
310AXTreeManagerBase* AXTreeManagerBase::DetachChildTree(AXNode& host_node) {
311 if (!tree_)
312 return nullptr;
313
314 DCHECK_EQ(GetNode(host_node.id()), &host_node)
315 << "`host_node` should belong to this manager.";
316 DCHECK(host_node.tree())
317 << "A node should always be attached to its owning tree.";
318 DCHECK_NE(GetTreeID().type(), ax::mojom::AXTreeIDType::kUnknown);
319 if (!host_node.IsLeaf()) {
320 // For now, child trees are only allowed to be attached on leaf nodes,
321 // otherwise behavior would be ambiguous.
322 return nullptr;
323 }
324
325 const AXTreeID& child_tree_id = AXTreeID::FromString(
326 host_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
327 AXTreeManagerBase* child_manager = GetManager(child_tree_id);
328 if (!child_manager || !child_manager->GetTree())
329 return nullptr;
330
331 DCHECK_NE(child_manager->GetTreeID().type(),
332 ax::mojom::AXTreeIDType::kUnknown);
333
334 {
335 AXNodeData host_node_data = host_node.data();
336 DCHECK_NE(child_manager->GetTreeData().parent_tree_id.type(),
337 ax::mojom::AXTreeIDType::kUnknown)
338 << "Child tree should be attached to its host node.\n"
339 << host_node;
340 host_node_data.RemoveStringAttribute(
341 ax::mojom::StringAttribute::kChildTreeId);
342 AXTreeUpdate update;
343 update.nodes = {host_node_data};
344 CHECK(ApplyTreeUpdate(update)) << GetTree()->error();
345 }
346
347 {
348 AXTreeData tree_data = child_manager->GetTreeData();
349 tree_data.parent_tree_id = AXTreeIDUnknown();
350 AXTreeUpdate update;
351 update.has_tree_data = true;
352 update.tree_data = tree_data;
353 CHECK(child_manager->ApplyTreeUpdate(update))
354 << child_manager->GetTree()->error();
355 }
356
357 return child_manager;
358}
359
360} // namespace ui