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