| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // 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.h" |
| #include "ui/accessibility/ax_tree_combiner.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| |
| namespace ui { |
| |
| AXTreeCombiner::AXTreeCombiner() { |
| } |
| |
| AXTreeCombiner::~AXTreeCombiner() { |
| } |
| |
| void AXTreeCombiner::AddTree(const AXTreeUpdate& tree, bool is_root) { |
| trees_.push_back(tree); |
| if (is_root) { |
| DCHECK_EQ(root_tree_id_, AXTreeIDUnknown()); |
| root_tree_id_ = tree.tree_data.tree_id; |
| } |
| } |
| |
| bool AXTreeCombiner::Combine() { |
| // First create a map from tree ID to tree update. |
| for (const auto& tree : trees_) { |
| AXTreeID tree_id = tree.tree_data.tree_id; |
| if (tree_id_map_.find(tree_id) != tree_id_map_.end()) |
| return false; |
| tree_id_map_[tree.tree_data.tree_id] = &tree; |
| } |
| |
| // Make sure the root tree ID is in the map, otherwise fail. |
| if (tree_id_map_.find(root_tree_id_) == tree_id_map_.end()) |
| return false; |
| |
| // Process the nodes recursively, starting with the root tree. |
| const AXTreeUpdate* root = tree_id_map_.find(root_tree_id_)->second; |
| ProcessTree(root); |
| |
| // Set the root id. |
| combined_.root_id = combined_.nodes.size() > 0 ? combined_.nodes[0].id : 0; |
| |
| // Finally, handle the tree ID, taking into account which subtree might |
| // have focus and mapping IDs from the tree data appropriately. |
| combined_.has_tree_data = true; |
| combined_.tree_data = root->tree_data; |
| AXTreeID focused_tree_id = root->tree_data.focused_tree_id; |
| const AXTreeUpdate* focused_tree = root; |
| if (tree_id_map_.find(focused_tree_id) != tree_id_map_.end()) |
| focused_tree = tree_id_map_[focused_tree_id]; |
| combined_.tree_data.focus_id = |
| MapId(focused_tree_id, focused_tree->tree_data.focus_id); |
| combined_.tree_data.sel_anchor_object_id = |
| MapId(focused_tree_id, focused_tree->tree_data.sel_anchor_object_id); |
| combined_.tree_data.sel_focus_object_id = |
| MapId(focused_tree_id, focused_tree->tree_data.sel_focus_object_id); |
| combined_.tree_data.sel_anchor_offset = |
| focused_tree->tree_data.sel_anchor_offset; |
| combined_.tree_data.sel_focus_offset = |
| focused_tree->tree_data.sel_focus_offset; |
| |
| // Debug-mode check that the resulting combined tree is valid. |
| AXTree tree; |
| DCHECK(tree.Unserialize(combined_)) |
| << combined_.ToString() << "\n" << tree.error(); |
| |
| return true; |
| } |
| |
| int32_t AXTreeCombiner::MapId(AXTreeID tree_id, int32_t node_id) { |
| auto tree_id_node_id = std::make_pair(tree_id, node_id); |
| if (tree_id_node_id_map_[tree_id_node_id] == 0) |
| tree_id_node_id_map_[tree_id_node_id] = next_id_++; |
| return tree_id_node_id_map_[tree_id_node_id]; |
| } |
| |
| void AXTreeCombiner::ProcessTree(const AXTreeUpdate* tree) { |
| AXTreeID tree_id = tree->tree_data.tree_id; |
| for (size_t i = 0; i < tree->nodes.size(); ++i) { |
| AXNodeData node = tree->nodes[i]; |
| AXTreeID child_tree_id = AXTreeID::FromString( |
| node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId)); |
| |
| // Map the node's ID. |
| node.id = MapId(tree_id, node.id); |
| |
| // Map the node's child IDs. |
| for (size_t j = 0; j < node.child_ids.size(); ++j) |
| node.child_ids[j] = MapId(tree_id, node.child_ids[j]); |
| |
| // Map the container id. |
| if (node.offset_container_id > 0) |
| node.offset_container_id = MapId(tree_id, node.offset_container_id); |
| |
| // Map other int attributes that refer to node IDs. |
| for (size_t j = 0; j < node.int_attributes.size(); ++j) { |
| auto& attr = node.int_attributes[j]; |
| if (IsNodeIdIntAttribute(attr.first)) |
| attr.second = MapId(tree_id, attr.second); |
| } |
| |
| // Map other int list attributes that refer to node IDs. |
| for (size_t j = 0; j < node.intlist_attributes.size(); ++j) { |
| auto& attr = node.intlist_attributes[j]; |
| if (IsNodeIdIntListAttribute(attr.first)) { |
| for (size_t k = 0; k < attr.second.size(); k++) |
| attr.second[k] = MapId(tree_id, attr.second[k]); |
| } |
| } |
| |
| // Remove the ax::mojom::StringAttribute::kChildTreeId attribute. |
| for (size_t j = 0; j < node.string_attributes.size(); ++j) { |
| auto& attr = node.string_attributes[j]; |
| if (attr.first == ax::mojom::StringAttribute::kChildTreeId) { |
| attr.first = ax::mojom::StringAttribute::kNone; |
| attr.second = ""; |
| } |
| } |
| |
| // See if this node has a child tree. As a sanity check make sure the |
| // child tree lists this tree as its parent tree id. |
| const AXTreeUpdate* child_tree = nullptr; |
| if (tree_id_map_.find(child_tree_id) != tree_id_map_.end()) { |
| child_tree = tree_id_map_.find(child_tree_id)->second; |
| if (child_tree->tree_data.parent_tree_id != tree_id) |
| child_tree = nullptr; |
| if (child_tree && child_tree->nodes.empty()) |
| child_tree = nullptr; |
| if (child_tree) { |
| node.child_ids.push_back(MapId(child_tree_id, |
| child_tree->nodes[0].id)); |
| } |
| } |
| |
| // Put the rewritten AXNodeData into the output data structure. |
| combined_.nodes.push_back(node); |
| |
| // Recurse into the child tree now, if any. |
| if (child_tree) |
| ProcessTree(child_tree); |
| } |
| } |
| |
| } // namespace ui |