OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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/platform/ax_android_snapshot.h" |
| 6 |
| 7 #include <memory> |
| 8 #include <string> |
| 9 |
| 10 #include "base/logging.h" |
| 11 #include "base/memory/ptr_util.h" |
| 12 #include "ui/accessibility/ax_enums.h" |
| 13 #include "ui/accessibility/ax_node.h" |
| 14 #include "ui/accessibility/ax_serializable_tree.h" |
| 15 #include "ui/accessibility/platform/ax_android_constants.h" |
| 16 #include "ui/gfx/geometry/rect_conversions.h" |
| 17 #include "ui/gfx/transform.h" |
| 18 |
| 19 namespace ui { |
| 20 |
| 21 namespace { |
| 22 |
| 23 bool HasFocusableChild(const AXNode* node) { |
| 24 for (auto* child : node->children()) { |
| 25 if ((child->data().state & ui::AX_STATE_FOCUSABLE) != 0 || |
| 26 HasFocusableChild(child)) { |
| 27 return true; |
| 28 } |
| 29 } |
| 30 return false; |
| 31 } |
| 32 |
| 33 bool IsLeaf(const AXNode* node) { |
| 34 if (node->child_count() == 0) |
| 35 return true; |
| 36 |
| 37 if (node->IsTextNode()) { |
| 38 return true; |
| 39 } |
| 40 |
| 41 switch (node->data().role) { |
| 42 case ui::AX_ROLE_IMAGE: |
| 43 case ui::AX_ROLE_METER: |
| 44 case ui::AX_ROLE_SCROLL_BAR: |
| 45 case ui::AX_ROLE_SLIDER: |
| 46 case ui::AX_ROLE_SPLITTER: |
| 47 case ui::AX_ROLE_PROGRESS_INDICATOR: |
| 48 case ui::AX_ROLE_DATE: |
| 49 case ui::AX_ROLE_DATE_TIME: |
| 50 case ui::AX_ROLE_INPUT_TIME: |
| 51 return true; |
| 52 default: |
| 53 return false; |
| 54 } |
| 55 } |
| 56 |
| 57 std::unique_ptr<AXSnapshotNodeAndroid> WalkAXTreeDepthFirst( |
| 58 const AXNode* node, |
| 59 gfx::Rect rect, |
| 60 const ui::AXTreeUpdate& update, |
| 61 const AXTree* tree) { |
| 62 auto result = base::MakeUnique<AXSnapshotNodeAndroid>(); |
| 63 if (node->IsTextNode()) |
| 64 result->text = GetText(node); |
| 65 result->class_name = GetClassName(node); |
| 66 |
| 67 result->text_size = -1.0; |
| 68 result->bgcolor = 0; |
| 69 result->color = 0; |
| 70 result->bold = 0; |
| 71 result->italic = 0; |
| 72 result->line_through = 0; |
| 73 result->underline = 0; |
| 74 |
| 75 if (node->data().HasFloatAttribute(ui::AX_ATTR_FONT_SIZE)) { |
| 76 gfx::RectF text_size_rect( |
| 77 0, 0, 1, node->data().GetFloatAttribute(ui::AX_ATTR_FONT_SIZE)); |
| 78 gfx::Rect scaled_text_size_rect = |
| 79 RelativeToAbsoluteBounds(node, text_size_rect, tree); |
| 80 result->text_size = scaled_text_size_rect.height(); |
| 81 |
| 82 const int text_style = node->data().GetIntAttribute(ui::AX_ATTR_TEXT_STYLE); |
| 83 result->color = node->data().GetIntAttribute(ui::AX_ATTR_COLOR); |
| 84 result->bgcolor = |
| 85 node->data().GetIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR); |
| 86 result->bold = (text_style & ui::AX_TEXT_STYLE_BOLD) != 0; |
| 87 result->italic = (text_style & ui::AX_TEXT_STYLE_ITALIC) != 0; |
| 88 result->line_through = (text_style & ui::AX_TEXT_STYLE_LINE_THROUGH) != 0; |
| 89 result->underline = (text_style & ui::AX_TEXT_STYLE_ITALIC) != 0; |
| 90 } |
| 91 |
| 92 const gfx::Rect& absolute_rect = GetPageBoundsRect(node, tree); |
| 93 gfx::Rect parent_relative_rect = absolute_rect; |
| 94 bool is_root = node->parent() == nullptr; |
| 95 if (!is_root) { |
| 96 parent_relative_rect.Offset(-rect.OffsetFromOrigin()); |
| 97 } |
| 98 result->rect = gfx::Rect(parent_relative_rect.x(), parent_relative_rect.y(), |
| 99 absolute_rect.width(), absolute_rect.height()); |
| 100 result->has_selection = false; |
| 101 |
| 102 if (IsLeaf(node)) { |
| 103 int start_selection = 0; |
| 104 int end_selection = 0; |
| 105 if (update.tree_data.sel_anchor_object_id == node->data().id) { |
| 106 start_selection = update.tree_data.sel_anchor_offset; |
| 107 end_selection = GetText(node).length(); |
| 108 } |
| 109 if (update.tree_data.sel_focus_object_id == node->data().id) { |
| 110 end_selection = update.tree_data.sel_focus_offset; |
| 111 } |
| 112 if (end_selection > 0) { |
| 113 result->has_selection = true; |
| 114 result->start_selection = start_selection; |
| 115 result->end_selection = end_selection; |
| 116 } |
| 117 } |
| 118 |
| 119 for (auto* child : node->children()) { |
| 120 result->children.push_back( |
| 121 WalkAXTreeDepthFirst(child, absolute_rect, update, tree)); |
| 122 } |
| 123 |
| 124 return result; |
| 125 } |
| 126 } |
| 127 |
| 128 AX_EXPORT AXSnapshotNodeAndroid::AXSnapshotNodeAndroid() = default; |
| 129 AX_EXPORT AXSnapshotNodeAndroid::~AXSnapshotNodeAndroid() = default; |
| 130 |
| 131 AX_EXPORT std::unique_ptr<AXSnapshotNodeAndroid> AXSnapshotNodeAndroid::Create( |
| 132 const AXTreeUpdate& update) { |
| 133 auto tree = base::MakeUnique<ui::AXSerializableTree>(); |
| 134 if (!tree->Unserialize(update)) { |
| 135 LOG(FATAL) << tree->error(); |
| 136 } |
| 137 return WalkAXTreeDepthFirst(tree->root(), gfx::Rect(), update, tree.get()); |
| 138 } |
| 139 |
| 140 std::string GetClassName(const AXNode* node) { |
| 141 switch (node->data().role) { |
| 142 case ui::AX_ROLE_SEARCH_BOX: |
| 143 case ui::AX_ROLE_SPIN_BUTTON: |
| 144 case ui::AX_ROLE_TEXT_FIELD: |
| 145 return ui::kAXEditTextClassname; |
| 146 case ui::AX_ROLE_SLIDER: |
| 147 return ui::kAXSeekBarClassname; |
| 148 case ui::AX_ROLE_COLOR_WELL: |
| 149 case ui::AX_ROLE_COMBO_BOX: |
| 150 case ui::AX_ROLE_DATE: |
| 151 case ui::AX_ROLE_POP_UP_BUTTON: |
| 152 case ui::AX_ROLE_INPUT_TIME: |
| 153 return ui::kAXSpinnerClassname; |
| 154 case ui::AX_ROLE_BUTTON: |
| 155 case ui::AX_ROLE_MENU_BUTTON: |
| 156 return ui::kAXButtonClassname; |
| 157 case ui::AX_ROLE_CHECK_BOX: |
| 158 case ui::AX_ROLE_SWITCH: |
| 159 return ui::kAXCheckBoxClassname; |
| 160 case ui::AX_ROLE_RADIO_BUTTON: |
| 161 return ui::kAXRadioButtonClassname; |
| 162 case ui::AX_ROLE_TOGGLE_BUTTON: |
| 163 return ui::kAXToggleButtonClassname; |
| 164 case ui::AX_ROLE_CANVAS: |
| 165 case ui::AX_ROLE_IMAGE: |
| 166 case ui::AX_ROLE_SVG_ROOT: |
| 167 return ui::kAXImageClassname; |
| 168 case ui::AX_ROLE_METER: |
| 169 case ui::AX_ROLE_PROGRESS_INDICATOR: |
| 170 return ui::kAXProgressBarClassname; |
| 171 case ui::AX_ROLE_TAB_LIST: |
| 172 return ui::kAXTabWidgetClassname; |
| 173 case ui::AX_ROLE_GRID: |
| 174 case ui::AX_ROLE_TABLE: |
| 175 return ui::kAXGridViewClassname; |
| 176 case ui::AX_ROLE_LIST: |
| 177 case ui::AX_ROLE_LIST_BOX: |
| 178 case ui::AX_ROLE_DESCRIPTION_LIST: |
| 179 return ui::kAXListViewClassname; |
| 180 case ui::AX_ROLE_DIALOG: |
| 181 return ui::kAXDialogClassname; |
| 182 case ui::AX_ROLE_ROOT_WEB_AREA: |
| 183 return node->parent() == nullptr ? ui::kAXWebViewClassname |
| 184 : ui::kAXViewClassname; |
| 185 case ui::AX_ROLE_MENU_ITEM: |
| 186 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: |
| 187 case ui::AX_ROLE_MENU_ITEM_RADIO: |
| 188 return ui::kAXMenuItemClassname; |
| 189 default: |
| 190 return ui::kAXViewClassname; |
| 191 } |
| 192 } |
| 193 |
| 194 base::string16 GetText(const AXNode* node) { |
| 195 if (node->IsTextNode()) { |
| 196 return node->data().GetString16Attribute(ui::AX_ATTR_NAME); |
| 197 } |
| 198 base::string16 text; |
| 199 for (auto* child : node->children()) { |
| 200 text += GetText(child); |
| 201 } |
| 202 return text; |
| 203 } |
| 204 |
| 205 gfx::Rect GetPageBoundsRect(const AXNode* node, const AXTree* tree) { |
| 206 gfx::RectF bounds = node->data().location; |
| 207 FixEmptyBounds(node, &bounds, tree); |
| 208 return RelativeToAbsoluteBounds(node, bounds, tree); |
| 209 } |
| 210 |
| 211 void FixEmptyBounds(const AXNode* node, |
| 212 gfx::RectF* bounds, |
| 213 const AXTree* tree) { |
| 214 if (bounds->width() > 0 && bounds->height() > 0) |
| 215 return; |
| 216 for (auto* child : node->children()) { |
| 217 gfx::Rect child_bounds = GetPageBoundsRect(child, tree); |
| 218 if (child_bounds.width() == 0 || child_bounds.height() == 0) |
| 219 continue; |
| 220 if (bounds->width() == 0 || bounds->height() == 0) { |
| 221 *bounds = gfx::RectF(child_bounds); |
| 222 continue; |
| 223 } |
| 224 bounds->Union(gfx::RectF(child_bounds)); |
| 225 } |
| 226 } |
| 227 |
| 228 gfx::Rect RelativeToAbsoluteBounds(const AXNode* node, |
| 229 gfx::RectF bounds, |
| 230 const AXTree* tree) { |
| 231 const AXNode* current = node; |
| 232 while (current != nullptr) { |
| 233 if (current->data().transform) |
| 234 current->data().transform->TransformRect(&bounds); |
| 235 auto* container = tree->GetFromId(current->data().offset_container_id); |
| 236 if (!container) { |
| 237 if (current == tree->root()) |
| 238 container = current->parent(); |
| 239 else |
| 240 container = tree->root(); |
| 241 } |
| 242 if (!container || container == current) |
| 243 break; |
| 244 |
| 245 gfx::RectF container_bounds = container->data().location; |
| 246 bounds.Offset(container_bounds.x(), container_bounds.y()); |
| 247 current = container; |
| 248 } |
| 249 return gfx::ToEnclosingRect(bounds); |
| 250 } |
| 251 |
| 252 } // namespace ui |
OLD | NEW |