dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // 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.h" |
| 6 | #include "ui/accessibility/ax_tree_combiner.h" |
| 7 | #include "ui/gfx/geometry/rect_f.h" |
| 8 | |
| 9 | namespace ui { |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 10 | |
| 11 | AXTreeCombiner::AXTreeCombiner() { |
| 12 | } |
| 13 | |
| 14 | AXTreeCombiner::~AXTreeCombiner() { |
| 15 | } |
| 16 | |
| 17 | void AXTreeCombiner::AddTree(const AXTreeUpdate& tree, bool is_root) { |
| 18 | trees_.push_back(tree); |
| 19 | if (is_root) { |
| 20 | DCHECK_EQ(root_tree_id_, -1); |
| 21 | root_tree_id_ = tree.tree_data.tree_id; |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | bool AXTreeCombiner::Combine() { |
| 26 | // First create a map from tree ID to tree update. |
| 27 | for (const auto& tree : trees_) { |
| 28 | int32_t tree_id = tree.tree_data.tree_id; |
| 29 | if (tree_id_map_.find(tree_id) != tree_id_map_.end()) |
| 30 | return false; |
| 31 | tree_id_map_[tree.tree_data.tree_id] = &tree; |
| 32 | } |
| 33 | |
| 34 | // Make sure the root tree ID is in the map, otherwise fail. |
| 35 | if (tree_id_map_.find(root_tree_id_) == tree_id_map_.end()) |
| 36 | return false; |
| 37 | |
| 38 | // Process the nodes recursively, starting with the root tree. |
| 39 | const AXTreeUpdate* root = tree_id_map_.find(root_tree_id_)->second; |
| 40 | ProcessTree(root); |
| 41 | |
dmazzoni | 67b4db2 | 2016-04-23 00:40:04 | [diff] [blame] | 42 | // Set the root id. |
Dominic Mazzoni | 72482e6 | 2017-06-16 21:29:49 | [diff] [blame] | 43 | combined_.root_id = combined_.nodes.size() > 0 ? combined_.nodes[0].id : 0; |
dmazzoni | 67b4db2 | 2016-04-23 00:40:04 | [diff] [blame] | 44 | |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 45 | // Finally, handle the tree ID, taking into account which subtree might |
| 46 | // have focus and mapping IDs from the tree data appropriately. |
| 47 | combined_.has_tree_data = true; |
| 48 | combined_.tree_data = root->tree_data; |
| 49 | int32_t focused_tree_id = root->tree_data.focused_tree_id; |
| 50 | const AXTreeUpdate* focused_tree = root; |
| 51 | if (tree_id_map_.find(focused_tree_id) != tree_id_map_.end()) |
| 52 | focused_tree = tree_id_map_[focused_tree_id]; |
| 53 | combined_.tree_data.focus_id = |
| 54 | MapId(focused_tree_id, focused_tree->tree_data.focus_id); |
| 55 | combined_.tree_data.sel_anchor_object_id = |
| 56 | MapId(focused_tree_id, focused_tree->tree_data.sel_anchor_object_id); |
| 57 | combined_.tree_data.sel_focus_object_id = |
| 58 | MapId(focused_tree_id, focused_tree->tree_data.sel_focus_object_id); |
| 59 | combined_.tree_data.sel_anchor_offset = |
| 60 | focused_tree->tree_data.sel_anchor_offset; |
| 61 | combined_.tree_data.sel_focus_offset = |
| 62 | focused_tree->tree_data.sel_focus_offset; |
| 63 | |
| 64 | // Debug-mode check that the resulting combined tree is valid. |
| 65 | AXTree tree; |
| 66 | DCHECK(tree.Unserialize(combined_)) |
| 67 | << combined_.ToString() << "\n" << tree.error(); |
| 68 | |
| 69 | return true; |
| 70 | } |
| 71 | |
| 72 | int32_t AXTreeCombiner::MapId(int32_t tree_id, int32_t node_id) { |
| 73 | auto tree_id_node_id = std::make_pair(tree_id, node_id); |
| 74 | if (tree_id_node_id_map_[tree_id_node_id] == 0) |
| 75 | tree_id_node_id_map_[tree_id_node_id] = next_id_++; |
| 76 | return tree_id_node_id_map_[tree_id_node_id]; |
| 77 | } |
| 78 | |
| 79 | void AXTreeCombiner::ProcessTree(const AXTreeUpdate* tree) { |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 80 | int32_t tree_id = tree->tree_data.tree_id; |
| 81 | for (size_t i = 0; i < tree->nodes.size(); ++i) { |
| 82 | AXNodeData node = tree->nodes[i]; |
| 83 | int32_t child_tree_id = node.GetIntAttribute(AX_ATTR_CHILD_TREE_ID); |
| 84 | |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 85 | // Map the node's ID. |
| 86 | node.id = MapId(tree_id, node.id); |
| 87 | |
| 88 | // Map the node's child IDs. |
| 89 | for (size_t j = 0; j < node.child_ids.size(); ++j) |
| 90 | node.child_ids[j] = MapId(tree_id, node.child_ids[j]); |
| 91 | |
dmazzoni | cbc8b96 | 2016-12-14 19:10:14 | [diff] [blame] | 92 | // Map the container id. |
| 93 | if (node.offset_container_id > 0) |
| 94 | node.offset_container_id = MapId(tree_id, node.offset_container_id); |
dmazzoni | 051715a | 2016-08-15 21:36:58 | [diff] [blame] | 95 | |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 96 | // Map other int attributes that refer to node IDs, and remove the |
| 97 | // AX_ATTR_CHILD_TREE_ID attribute. |
| 98 | for (size_t j = 0; j < node.int_attributes.size(); ++j) { |
| 99 | auto& attr = node.int_attributes[j]; |
| 100 | if (IsNodeIdIntAttribute(attr.first)) |
| 101 | attr.second = MapId(tree_id, attr.second); |
| 102 | if (attr.first == AX_ATTR_CHILD_TREE_ID) { |
| 103 | attr.first = AX_INT_ATTRIBUTE_NONE; |
| 104 | attr.second = 0; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // Map other int list attributes that refer to node IDs. |
| 109 | for (size_t j = 0; j < node.intlist_attributes.size(); ++j) { |
| 110 | auto& attr = node.intlist_attributes[j]; |
| 111 | if (IsNodeIdIntListAttribute(attr.first)) { |
| 112 | for (size_t k = 0; k < attr.second.size(); k++) |
| 113 | attr.second[k] = MapId(tree_id, attr.second[k]); |
| 114 | } |
| 115 | } |
| 116 | |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 117 | // See if this node has a child tree. As a sanity check make sure the |
| 118 | // child tree lists this tree as its parent tree id. |
| 119 | const AXTreeUpdate* child_tree = nullptr; |
| 120 | if (tree_id_map_.find(child_tree_id) != tree_id_map_.end()) { |
| 121 | child_tree = tree_id_map_.find(child_tree_id)->second; |
| 122 | if (child_tree->tree_data.parent_tree_id != tree_id) |
| 123 | child_tree = nullptr; |
| 124 | if (child_tree && child_tree->nodes.empty()) |
| 125 | child_tree = nullptr; |
| 126 | if (child_tree) { |
| 127 | node.child_ids.push_back(MapId(child_tree_id, |
| 128 | child_tree->nodes[0].id)); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // Put the rewritten AXNodeData into the output data structure. |
| 133 | combined_.nodes.push_back(node); |
| 134 | |
| 135 | // Recurse into the child tree now, if any. |
| 136 | if (child_tree) |
| 137 | ProcessTree(child_tree); |
| 138 | } |
dmazzoni | d95ae84 | 2016-04-12 21:17:39 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | } // namespace ui |