Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(391)

Side by Side Diff: ui/accessibility/platform/ax_snapshot_node_android_platform.cc

Issue 2808383004: Refactor and send voice interaction structure (Closed)
Patch Set: fix window build Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/accessibility/platform/ax_snapshot_node_android_platform.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 <string>
8
9 #include "base/logging.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.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 gfx::Rect RelativeToAbsoluteBounds(const AXNode* node,
34 gfx::RectF bounds,
35 const AXTree* tree) {
36 const AXNode* current = node;
37 while (current != nullptr) {
38 if (current->data().transform)
39 current->data().transform->TransformRect(&bounds);
40 auto* container = tree->GetFromId(current->data().offset_container_id);
41 if (!container) {
42 if (current == tree->root())
43 container = current->parent();
44 else
45 container = tree->root();
46 }
47 if (!container || container == current)
48 break;
49
50 gfx::RectF container_bounds = container->data().location;
51 bounds.Offset(container_bounds.x(), container_bounds.y());
52 current = container;
53 }
54 return gfx::ToEnclosingRect(bounds);
55 }
56
57 void FixEmptyBounds(const AXNode* node, gfx::RectF* bounds, const AXTree* tree);
58
59 gfx::Rect GetPageBoundsRect(const AXNode* node, const AXTree* tree) {
60 gfx::RectF bounds = node->data().location;
61 FixEmptyBounds(node, &bounds, tree);
62 return RelativeToAbsoluteBounds(node, bounds, tree);
63 }
64
65 void FixEmptyBounds(const AXNode* node,
66 gfx::RectF* bounds,
67 const AXTree* tree) {
68 if (bounds->width() > 0 && bounds->height() > 0)
69 return;
70 for (auto* child : node->children()) {
71 gfx::Rect child_bounds = GetPageBoundsRect(child, tree);
72 if (child_bounds.width() == 0 || child_bounds.height() == 0)
73 continue;
74 if (bounds->width() == 0 || bounds->height() == 0) {
75 *bounds = gfx::RectF(child_bounds);
76 continue;
77 }
78 bounds->Union(gfx::RectF(child_bounds));
79 }
80 }
81
82 bool HasOnlyTextChildren(const AXNode* node) {
83 for (auto* child : node->children()) {
84 if (!child->IsTextNode())
85 return false;
86 }
87 return true;
88 }
89
90 // TODO(muyuanli): share with BrowserAccessibility.
91 bool IsSimpleTextControl(AXRole role, uint32_t state) {
92 switch (role) {
93 case ui::AX_ROLE_COMBO_BOX:
94 case ui::AX_ROLE_SEARCH_BOX:
95 return true;
96 case ui::AX_ROLE_TEXT_FIELD:
97 return (state & ui::AX_STATE_RICHLY_EDITABLE) == 0;
98 default:
99 return false;
100 }
101 }
102
103 bool IsRichTextEditable(const AXNode* node) {
104 const AXNode* parent = node->parent();
105 return (node->data().state & ui::AX_STATE_RICHLY_EDITABLE) != 0 &&
106 (!parent ||
107 (parent->data().state & ui::AX_STATE_RICHLY_EDITABLE) == 0);
108 }
109
110 bool IsNativeTextControl(const AXNode* node) {
111 const std::string& html_tag =
112 node->data().GetStringAttribute(ui::AX_ATTR_HTML_TAG);
113 if (html_tag == "input") {
114 std::string input_type;
115 if (!node->data().GetHtmlAttribute("type", &input_type))
116 return true;
117 return input_type.empty() || input_type == "email" ||
118 input_type == "password" || input_type == "search" ||
119 input_type == "tel" || input_type == "text" || input_type == "url" ||
120 input_type == "number";
121 }
122 return html_tag == "textarea";
123 }
124
125 bool IsLeaf(const AXNode* node) {
126 if (node->child_count() == 0)
127 return true;
128
129 if (IsNativeTextControl(node) || node->IsTextNode()) {
130 return true;
131 }
132
133 switch (node->data().role) {
134 case ui::AX_ROLE_IMAGE:
135 case ui::AX_ROLE_METER:
136 case ui::AX_ROLE_SCROLL_BAR:
137 case ui::AX_ROLE_SLIDER:
138 case ui::AX_ROLE_SPLITTER:
139 case ui::AX_ROLE_PROGRESS_INDICATOR:
140 case ui::AX_ROLE_DATE:
141 case ui::AX_ROLE_DATE_TIME:
142 case ui::AX_ROLE_INPUT_TIME:
143 return true;
144 default:
145 return false;
146 }
147 }
148
149 base::string16 GetInnerText(const AXNode* node) {
150 if (node->IsTextNode()) {
151 return node->data().GetString16Attribute(ui::AX_ATTR_NAME);
152 }
153 base::string16 text;
154 for (auto* child : node->children()) {
155 text += GetInnerText(child);
156 }
157 return text;
158 }
159
160 base::string16 GetValue(const AXNode* node, bool show_password) {
161 base::string16 value = node->data().GetString16Attribute(ui::AX_ATTR_VALUE);
162
163 if (value.empty() &&
164 (IsSimpleTextControl(node->data().role, node->data().state) ||
165 IsRichTextEditable(node)) &&
166 !IsNativeTextControl(node)) {
167 value = GetInnerText(node);
168 }
169
170 if ((node->data().state & ui::AX_STATE_PROTECTED) != 0) {
171 if (!show_password) {
172 value = base::string16(value.size(), kSecurePasswordBullet);
173 }
174 }
175
176 return value;
177 }
178
179 bool HasOnlyTextAndImageChildren(const AXNode* node) {
180 for (auto* child : node->children()) {
181 if (child->data().role != ui::AX_ROLE_STATIC_TEXT &&
182 child->data().role != ui::AX_ROLE_IMAGE) {
183 return false;
184 }
185 }
186 return true;
187 }
188
189 bool IsFocusable(const AXNode* node) {
190 if (node->data().role == ui::AX_ROLE_IFRAME ||
191 node->data().role == ui::AX_ROLE_IFRAME_PRESENTATIONAL ||
192 (node->data().role == ui::AX_ROLE_ROOT_WEB_AREA && node->parent())) {
193 return node->data().HasStringAttribute(ui::AX_ATTR_NAME);
194 }
195 return (node->data().state & ui::AX_STATE_FOCUSABLE) != 0;
196 }
197
198 base::string16 GetText(const AXNode* node, bool show_password) {
199 if (node->data().role == ui::AX_ROLE_WEB_AREA ||
200 node->data().role == ui::AX_ROLE_IFRAME ||
201 node->data().role == ui::AX_ROLE_IFRAME_PRESENTATIONAL) {
202 return base::string16();
203 }
204
205 if (node->data().role == ui::AX_ROLE_LIST_ITEM &&
206 node->data().GetIntAttribute(ui::AX_ATTR_NAME_FROM) ==
207 ui::AX_NAME_FROM_CONTENTS) {
208 if (node->child_count() > 0 && !HasOnlyTextChildren(node))
209 return base::string16();
210 }
211
212 base::string16 value = GetValue(node, show_password);
213
214 if (!value.empty()) {
215 if ((node->data().state & ui::AX_STATE_EDITABLE) != 0)
216 return value;
217
218 switch (node->data().role) {
219 case ui::AX_ROLE_COMBO_BOX:
220 case ui::AX_ROLE_POP_UP_BUTTON:
221 case ui::AX_ROLE_TEXT_FIELD:
222 return value;
223 default:
224 break;
225 }
226 }
227
228 if (node->data().role == ui::AX_ROLE_COLOR_WELL) {
229 unsigned int color = static_cast<unsigned int>(
230 node->data().GetIntAttribute(ui::AX_ATTR_COLOR_VALUE));
231 unsigned int red = color >> 16 & 0xFF;
232 unsigned int green = color >> 8 & 0xFF;
233 unsigned int blue = color >> 0 & 0xFF;
234 return base::UTF8ToUTF16(
235 base::StringPrintf("#%02X%02X%02X", red, green, blue));
236 }
237
238 base::string16 text = node->data().GetString16Attribute(ui::AX_ATTR_NAME);
239 base::string16 description =
240 node->data().GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
241 if (!description.empty()) {
242 if (!text.empty())
243 text += base::ASCIIToUTF16(" ");
244 text += description;
245 }
246
247 if (text.empty())
248 text = value;
249
250 if (node->data().role == ui::AX_ROLE_ROOT_WEB_AREA)
251 return text;
252
253 if (text.empty() &&
254 (HasOnlyTextChildren(node) ||
255 (IsFocusable(node) && HasOnlyTextAndImageChildren(node)))) {
256 for (auto* child : node->children()) {
257 text += GetText(child, show_password);
258 }
259 }
260
261 if (text.empty() && (AXSnapshotNodeAndroid::AXRoleIsLink(node->data().role) ||
262 node->data().role == ui::AX_ROLE_IMAGE)) {
263 base::string16 url = node->data().GetString16Attribute(ui::AX_ATTR_URL);
264 text = AXSnapshotNodeAndroid::AXUrlBaseText(url);
265 }
266 return text;
267 }
268
269 } // namespace
270
271 AXSnapshotNodeAndroid::AXSnapshotNodeAndroid() = default;
272 AX_EXPORT AXSnapshotNodeAndroid::~AXSnapshotNodeAndroid() = default;
273
274 // static
275 AX_EXPORT std::unique_ptr<AXSnapshotNodeAndroid> AXSnapshotNodeAndroid::Create(
276 const AXTreeUpdate& update,
277 bool show_password) {
278 auto tree = base::MakeUnique<ui::AXSerializableTree>();
279 if (!tree->Unserialize(update)) {
280 LOG(FATAL) << tree->error();
281 }
282
283 WalkAXTreeConfig config{
284 false, // should_select_leaf
285 show_password // show_password
286 };
287 return WalkAXTreeDepthFirst(tree->root(), gfx::Rect(), update, tree.get(),
288 config);
289 }
290
291 // static
292 AX_EXPORT bool AXSnapshotNodeAndroid::AXRoleIsLink(AXRole role) {
293 return role == ui::AX_ROLE_LINK || role == ui::AX_ROLE_IMAGE_MAP_LINK;
294 }
295
296 // static
297 AX_EXPORT base::string16 AXSnapshotNodeAndroid::AXUrlBaseText(
298 base::string16 url) {
299 // Given a url like http://foo.com/bar/baz.png, just return the
300 // base text, e.g., "baz".
301 int trailing_slashes = 0;
302 while (url.size() - trailing_slashes > 0 &&
303 url[url.size() - trailing_slashes - 1] == '/') {
304 trailing_slashes++;
305 }
306 if (trailing_slashes)
307 url = url.substr(0, url.size() - trailing_slashes);
308 size_t slash_index = url.rfind('/');
309 if (slash_index != std::string::npos)
310 url = url.substr(slash_index + 1);
311 size_t dot_index = url.rfind('.');
312 if (dot_index != std::string::npos)
313 url = url.substr(0, dot_index);
314 return url;
315 }
316
317 // static
318 AX_EXPORT const char* AXSnapshotNodeAndroid::AXRoleToAndroidClassName(
319 AXRole role,
320 bool has_parent) {
321 switch (role) {
322 case ui::AX_ROLE_SEARCH_BOX:
323 case ui::AX_ROLE_SPIN_BUTTON:
324 case ui::AX_ROLE_TEXT_FIELD:
325 return ui::kAXEditTextClassname;
326 case ui::AX_ROLE_SLIDER:
327 return ui::kAXSeekBarClassname;
328 case ui::AX_ROLE_COLOR_WELL:
329 case ui::AX_ROLE_COMBO_BOX:
330 case ui::AX_ROLE_DATE:
331 case ui::AX_ROLE_POP_UP_BUTTON:
332 case ui::AX_ROLE_INPUT_TIME:
333 return ui::kAXSpinnerClassname;
334 case ui::AX_ROLE_BUTTON:
335 case ui::AX_ROLE_MENU_BUTTON:
336 return ui::kAXButtonClassname;
337 case ui::AX_ROLE_CHECK_BOX:
338 case ui::AX_ROLE_SWITCH:
339 return ui::kAXCheckBoxClassname;
340 case ui::AX_ROLE_RADIO_BUTTON:
341 return ui::kAXRadioButtonClassname;
342 case ui::AX_ROLE_TOGGLE_BUTTON:
343 return ui::kAXToggleButtonClassname;
344 case ui::AX_ROLE_CANVAS:
345 case ui::AX_ROLE_IMAGE:
346 case ui::AX_ROLE_SVG_ROOT:
347 return ui::kAXImageClassname;
348 case ui::AX_ROLE_METER:
349 case ui::AX_ROLE_PROGRESS_INDICATOR:
350 return ui::kAXProgressBarClassname;
351 case ui::AX_ROLE_TAB_LIST:
352 return ui::kAXTabWidgetClassname;
353 case ui::AX_ROLE_GRID:
354 case ui::AX_ROLE_TREE_GRID:
355 case ui::AX_ROLE_TABLE:
356 return ui::kAXGridViewClassname;
357 case ui::AX_ROLE_LIST:
358 case ui::AX_ROLE_LIST_BOX:
359 case ui::AX_ROLE_DESCRIPTION_LIST:
360 return ui::kAXListViewClassname;
361 case ui::AX_ROLE_DIALOG:
362 return ui::kAXDialogClassname;
363 case ui::AX_ROLE_ROOT_WEB_AREA:
364 return has_parent ? ui::kAXViewClassname : ui::kAXWebViewClassname;
365 case ui::AX_ROLE_MENU_ITEM:
366 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX:
367 case ui::AX_ROLE_MENU_ITEM_RADIO:
368 return ui::kAXMenuItemClassname;
369 default:
370 return ui::kAXViewClassname;
371 }
372 }
373
374 // static
375 std::unique_ptr<AXSnapshotNodeAndroid>
376 AXSnapshotNodeAndroid::WalkAXTreeDepthFirst(
377 const AXNode* node,
378 gfx::Rect rect,
379 const ui::AXTreeUpdate& update,
380 const AXTree* tree,
381 AXSnapshotNodeAndroid::WalkAXTreeConfig& config) {
382 auto result =
383 std::unique_ptr<AXSnapshotNodeAndroid>(new AXSnapshotNodeAndroid());
384 result->text = GetText(node, config.show_password);
385 result->class_name = AXSnapshotNodeAndroid::AXRoleToAndroidClassName(
386 node->data().role, node->parent() != nullptr);
387
388 result->text_size = -1.0;
389 result->bgcolor = 0;
390 result->color = 0;
391 result->bold = 0;
392 result->italic = 0;
393 result->line_through = 0;
394 result->underline = 0;
395
396 if (node->data().HasFloatAttribute(ui::AX_ATTR_FONT_SIZE)) {
397 gfx::RectF text_size_rect(
398 0, 0, 1, node->data().GetFloatAttribute(ui::AX_ATTR_FONT_SIZE));
399 gfx::Rect scaled_text_size_rect =
400 RelativeToAbsoluteBounds(node, text_size_rect, tree);
401 result->text_size = scaled_text_size_rect.height();
402
403 const int text_style = node->data().GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);
404 result->color = node->data().GetIntAttribute(ui::AX_ATTR_COLOR);
405 result->bgcolor =
406 node->data().GetIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR);
407 result->bold = (text_style & ui::AX_TEXT_STYLE_BOLD) != 0;
408 result->italic = (text_style & ui::AX_TEXT_STYLE_ITALIC) != 0;
409 result->line_through = (text_style & ui::AX_TEXT_STYLE_LINE_THROUGH) != 0;
410 result->underline = (text_style & ui::AX_TEXT_STYLE_UNDERLINE) != 0;
411 }
412
413 const gfx::Rect& absolute_rect = GetPageBoundsRect(node, tree);
414 gfx::Rect parent_relative_rect = absolute_rect;
415 bool is_root = node->parent() == nullptr;
416 if (!is_root) {
417 parent_relative_rect.Offset(-rect.OffsetFromOrigin());
418 }
419 result->rect = gfx::Rect(parent_relative_rect.x(), parent_relative_rect.y(),
420 absolute_rect.width(), absolute_rect.height());
421 result->has_selection = false;
422
423 if (IsLeaf(node) && update.has_tree_data) {
424 int start_selection = 0;
425 int end_selection = 0;
426 if (update.tree_data.sel_anchor_object_id == node->id()) {
427 start_selection = update.tree_data.sel_anchor_offset;
428 config.should_select_leaf = true;
429 }
430
431 if (config.should_select_leaf) {
432 end_selection =
433 static_cast<int32_t>(GetText(node, config.show_password).length());
434 }
435
436 if (update.tree_data.sel_focus_object_id == node->id()) {
437 end_selection = update.tree_data.sel_focus_offset;
438 config.should_select_leaf = false;
439 }
440 if (end_selection > 0) {
441 result->has_selection = true;
442 result->start_selection = start_selection;
443 result->end_selection = end_selection;
444 }
445 }
446
447 for (auto* child : node->children()) {
448 result->children.push_back(
449 WalkAXTreeDepthFirst(child, absolute_rect, update, tree, config));
450 }
451
452 return result;
453 }
454
455 } // namespace ui
OLDNEW
« no previous file with comments | « ui/accessibility/platform/ax_snapshot_node_android_platform.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698