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