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

Side by Side Diff: content/renderer/accessibility/accessibility_node_serializer.cc

Issue 222073002: Revert of Refactor content/renderer/accessibility to use AXTreeSerializer. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "content/renderer/accessibility/accessibility_node_serializer.h"
6
7 #include <set>
8
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "content/renderer/accessibility/blink_ax_enum_conversion.h"
13 #include "third_party/WebKit/public/platform/WebRect.h"
14 #include "third_party/WebKit/public/platform/WebSize.h"
15 #include "third_party/WebKit/public/platform/WebString.h"
16 #include "third_party/WebKit/public/platform/WebVector.h"
17 #include "third_party/WebKit/public/web/WebAXEnums.h"
18 #include "third_party/WebKit/public/web/WebAXObject.h"
19 #include "third_party/WebKit/public/web/WebDocument.h"
20 #include "third_party/WebKit/public/web/WebDocumentType.h"
21 #include "third_party/WebKit/public/web/WebElement.h"
22 #include "third_party/WebKit/public/web/WebFormControlElement.h"
23 #include "third_party/WebKit/public/web/WebFrame.h"
24 #include "third_party/WebKit/public/web/WebInputElement.h"
25 #include "third_party/WebKit/public/web/WebNode.h"
26
27 using base::UTF16ToUTF8;
28 using blink::WebAXObject;
29 using blink::WebDocument;
30 using blink::WebDocumentType;
31 using blink::WebElement;
32 using blink::WebNode;
33 using blink::WebVector;
34
35 namespace content {
36 namespace {
37
38 // Returns true if |ancestor| is the first unignored parent of |child|,
39 // which means that when walking up the parent chain from |child|,
40 // |ancestor| is the *first* ancestor that isn't marked as
41 // accessibilityIsIgnored().
42 bool IsParentUnignoredOf(const WebAXObject& ancestor,
43 const WebAXObject& child) {
44 WebAXObject parent = child.parentObject();
45 while (!parent.isDetached() && parent.accessibilityIsIgnored())
46 parent = parent.parentObject();
47 return parent.equals(ancestor);
48 }
49
50 bool IsTrue(std::string html_value) {
51 return LowerCaseEqualsASCII(html_value, "true");
52 }
53
54 std::string GetEquivalentAriaRoleString(const ui::AXRole role) {
55 switch (role) {
56 case ui::AX_ROLE_ARTICLE:
57 return "article";
58 case ui::AX_ROLE_BANNER:
59 return "banner";
60 case ui::AX_ROLE_COMPLEMENTARY:
61 return "complementary";
62 case ui::AX_ROLE_CONTENT_INFO:
63 case ui::AX_ROLE_FOOTER:
64 return "contentinfo";
65 case ui::AX_ROLE_MAIN:
66 return "main";
67 case ui::AX_ROLE_NAVIGATION:
68 return "navigation";
69 case ui::AX_ROLE_REGION:
70 return "region";
71 default:
72 break;
73 }
74
75 return std::string();
76 }
77
78 void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr,
79 WebVector<WebAXObject> objects,
80 ui::AXNodeData* dst) {
81 std::vector<int32> ids;
82 for(size_t i = 0; i < objects.size(); i++)
83 ids.push_back(objects[i].axID());
84 if (ids.size() > 0)
85 dst->AddIntListAttribute(attr, ids);
86 }
87
88
89 } // Anonymous namespace
90
91 void SerializeAccessibilityNode(
92 const WebAXObject& src,
93 ui::AXNodeData* dst) {
94 dst->role = AXRoleFromBlink(src.role());
95 dst->state = AXStateFromBlink(src);
96 dst->location = src.boundingBoxRect();
97 dst->id = src.axID();
98 std::string name = base::UTF16ToUTF8(src.title());
99
100 std::string value;
101 if (src.valueDescription().length()) {
102 dst->AddStringAttribute(ui::AX_ATTR_VALUE,
103 UTF16ToUTF8(src.valueDescription()));
104 } else {
105 dst->AddStringAttribute(ui::AX_ATTR_VALUE, UTF16ToUTF8(src.stringValue()));
106 }
107
108 if (dst->role == ui::AX_ROLE_COLOR_WELL) {
109 int r, g, b;
110 src.colorValue(r, g, b);
111 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_RED, r);
112 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_GREEN, g);
113 dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE_BLUE, b);
114 }
115
116 if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) {
117 dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
118 AXTextDirectionFromBlink(src.textDirection()));
119
120 WebVector<int> src_character_offsets;
121 src.characterOffsets(src_character_offsets);
122 std::vector<int32> character_offsets;
123 character_offsets.reserve(src_character_offsets.size());
124 for (size_t i = 0; i < src_character_offsets.size(); ++i)
125 character_offsets.push_back(src_character_offsets[i]);
126 dst->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets);
127
128 WebVector<int> src_word_starts;
129 WebVector<int> src_word_ends;
130 src.wordBoundaries(src_word_starts, src_word_ends);
131 std::vector<int32> word_starts;
132 std::vector<int32> word_ends;
133 word_starts.reserve(src_word_starts.size());
134 word_ends.reserve(src_word_starts.size());
135 for (size_t i = 0; i < src_word_starts.size(); ++i) {
136 word_starts.push_back(src_word_starts[i]);
137 word_ends.push_back(src_word_ends[i]);
138 }
139 dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts);
140 dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends);
141 }
142
143 if (src.accessKey().length())
144 dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY,
145 UTF16ToUTF8(src.accessKey()));
146 if (src.actionVerb().length())
147 dst->AddStringAttribute(ui::AX_ATTR_ACTION, UTF16ToUTF8(src.actionVerb()));
148 if (src.isAriaReadOnly())
149 dst->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY, true);
150 if (src.isButtonStateMixed())
151 dst->AddBoolAttribute(ui::AX_ATTR_BUTTON_MIXED, true);
152 if (src.canSetValueAttribute())
153 dst->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE, true);
154 if (src.accessibilityDescription().length()) {
155 dst->AddStringAttribute(ui::AX_ATTR_DESCRIPTION,
156 UTF16ToUTF8(src.accessibilityDescription()));
157 }
158 if (src.hasComputedStyle()) {
159 dst->AddStringAttribute(ui::AX_ATTR_DISPLAY,
160 UTF16ToUTF8(src.computedStyleDisplay()));
161 }
162 if (src.helpText().length())
163 dst->AddStringAttribute(ui::AX_ATTR_HELP, UTF16ToUTF8(src.helpText()));
164 if (src.keyboardShortcut().length()) {
165 dst->AddStringAttribute(ui::AX_ATTR_SHORTCUT,
166 UTF16ToUTF8(src.keyboardShortcut()));
167 }
168 if (!src.titleUIElement().isDetached()) {
169 dst->AddIntAttribute(ui::AX_ATTR_TITLE_UI_ELEMENT,
170 src.titleUIElement().axID());
171 }
172 if (!src.ariaActiveDescendant().isDetached()) {
173 dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID,
174 src.ariaActiveDescendant().axID());
175 }
176
177 if (!src.url().isEmpty())
178 dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().spec());
179
180 if (dst->role == ui::AX_ROLE_HEADING)
181 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, src.headingLevel());
182 else if ((dst->role == ui::AX_ROLE_TREE_ITEM ||
183 dst->role == ui::AX_ROLE_ROW) &&
184 src.hierarchicalLevel() > 0) {
185 dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL,
186 src.hierarchicalLevel());
187 }
188
189 // Treat the active list box item as focused.
190 if (dst->role == ui::AX_ROLE_LIST_BOX_OPTION &&
191 src.isSelectedOptionActive()) {
192 dst->state |= (1 << ui::AX_STATE_FOCUSED);
193 }
194
195 if (src.canvasHasFallbackContent())
196 dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true);
197
198 WebNode node = src.node();
199 bool is_iframe = false;
200 std::string live_atomic;
201 std::string live_busy;
202 std::string live_status;
203 std::string live_relevant;
204
205 if (!node.isNull() && node.isElementNode()) {
206 WebElement element = node.to<WebElement>();
207 is_iframe = (element.tagName() == base::ASCIIToUTF16("IFRAME"));
208
209 if (LowerCaseEqualsASCII(element.getAttribute("aria-expanded"), "true"))
210 dst->state |= (1 << ui::AX_STATE_EXPANDED);
211
212 // TODO(ctguil): The tagName in WebKit is lower cased but
213 // HTMLElement::nodeName calls localNameUpper. Consider adding
214 // a WebElement method that returns the original lower cased tagName.
215 dst->AddStringAttribute(
216 ui::AX_ATTR_HTML_TAG,
217 StringToLowerASCII(UTF16ToUTF8(element.tagName())));
218 for (unsigned i = 0; i < element.attributeCount(); ++i) {
219 std::string name = StringToLowerASCII(base::UTF16ToUTF8(
220 element.attributeLocalName(i)));
221 std::string value = base::UTF16ToUTF8(element.attributeValue(i));
222 dst->html_attributes.push_back(std::make_pair(name, value));
223 }
224
225 if (dst->role == ui::AX_ROLE_EDITABLE_TEXT ||
226 dst->role == ui::AX_ROLE_TEXT_AREA ||
227 dst->role == ui::AX_ROLE_TEXT_FIELD) {
228 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, src.selectionStart());
229 dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, src.selectionEnd());
230
231 WebVector<int> src_line_breaks;
232 src.lineBreaks(src_line_breaks);
233 if (src_line_breaks.size() > 0) {
234 std::vector<int32> line_breaks;
235 line_breaks.reserve(src_line_breaks.size());
236 for (size_t i = 0; i < src_line_breaks.size(); ++i)
237 line_breaks.push_back(src_line_breaks[i]);
238 dst->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS, line_breaks);
239 }
240 }
241
242 // ARIA role.
243 if (element.hasAttribute("role")) {
244 dst->AddStringAttribute(ui::AX_ATTR_ROLE,
245 UTF16ToUTF8(element.getAttribute("role")));
246 } else {
247 std::string role = GetEquivalentAriaRoleString(dst->role);
248 if (!role.empty())
249 dst->AddStringAttribute(ui::AX_ATTR_ROLE, role);
250 }
251
252 // Live region attributes
253 live_atomic = base::UTF16ToUTF8(element.getAttribute("aria-atomic"));
254 live_busy = base::UTF16ToUTF8(element.getAttribute("aria-busy"));
255 live_status = base::UTF16ToUTF8(element.getAttribute("aria-live"));
256 live_relevant = base::UTF16ToUTF8(element.getAttribute("aria-relevant"));
257 }
258
259 // Walk up the parent chain to set live region attributes of containers
260 std::string container_live_atomic;
261 std::string container_live_busy;
262 std::string container_live_status;
263 std::string container_live_relevant;
264 WebAXObject container_accessible = src;
265 while (!container_accessible.isDetached()) {
266 WebNode container_node = container_accessible.node();
267 if (!container_node.isNull() && container_node.isElementNode()) {
268 WebElement container_elem = container_node.to<WebElement>();
269 if (container_elem.hasAttribute("aria-atomic") &&
270 container_live_atomic.empty()) {
271 container_live_atomic =
272 base::UTF16ToUTF8(container_elem.getAttribute("aria-atomic"));
273 }
274 if (container_elem.hasAttribute("aria-busy") &&
275 container_live_busy.empty()) {
276 container_live_busy =
277 base::UTF16ToUTF8(container_elem.getAttribute("aria-busy"));
278 }
279 if (container_elem.hasAttribute("aria-live") &&
280 container_live_status.empty()) {
281 container_live_status =
282 base::UTF16ToUTF8(container_elem.getAttribute("aria-live"));
283 }
284 if (container_elem.hasAttribute("aria-relevant") &&
285 container_live_relevant.empty()) {
286 container_live_relevant =
287 base::UTF16ToUTF8(container_elem.getAttribute("aria-relevant"));
288 }
289 }
290 container_accessible = container_accessible.parentObject();
291 }
292
293 if (!live_atomic.empty())
294 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC, IsTrue(live_atomic));
295 if (!live_busy.empty())
296 dst->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY, IsTrue(live_busy));
297 if (!live_status.empty())
298 dst->AddStringAttribute(ui::AX_ATTR_LIVE_STATUS, live_status);
299 if (!live_relevant.empty())
300 dst->AddStringAttribute(ui::AX_ATTR_LIVE_RELEVANT, live_relevant);
301
302 if (!container_live_atomic.empty()) {
303 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC,
304 IsTrue(container_live_atomic));
305 }
306 if (!container_live_busy.empty()) {
307 dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY,
308 IsTrue(container_live_busy));
309 }
310 if (!container_live_status.empty()) {
311 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_STATUS,
312 container_live_status);
313 }
314 if (!container_live_relevant.empty()) {
315 dst->AddStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_RELEVANT,
316 container_live_relevant);
317 }
318
319 if (dst->role == ui::AX_ROLE_PROGRESS_INDICATOR ||
320 dst->role == ui::AX_ROLE_SCROLL_BAR ||
321 dst->role == ui::AX_ROLE_SLIDER ||
322 dst->role == ui::AX_ROLE_SPIN_BUTTON) {
323 dst->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, src.valueForRange());
324 dst->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE,
325 src.maxValueForRange());
326 dst->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE,
327 src.minValueForRange());
328 }
329
330 if (dst->role == ui::AX_ROLE_DOCUMENT ||
331 dst->role == ui::AX_ROLE_WEB_AREA) {
332 dst->AddStringAttribute(ui::AX_ATTR_HTML_TAG, "#document");
333 const WebDocument& document = src.document();
334 if (name.empty())
335 name = UTF16ToUTF8(document.title());
336 dst->AddStringAttribute(ui::AX_ATTR_DOC_TITLE,
337 UTF16ToUTF8(document.title()));
338 dst->AddStringAttribute(ui::AX_ATTR_DOC_URL, document.url().spec());
339 dst->AddStringAttribute(
340 ui::AX_ATTR_DOC_MIMETYPE,
341 document.isXHTMLDocument() ? "text/xhtml" : "text/html");
342 dst->AddBoolAttribute(ui::AX_ATTR_DOC_LOADED, src.isLoaded());
343 dst->AddFloatAttribute(ui::AX_ATTR_DOC_LOADING_PROGRESS,
344 src.estimatedLoadingProgress());
345
346 const WebDocumentType& doctype = document.doctype();
347 if (!doctype.isNull()) {
348 dst->AddStringAttribute(ui::AX_ATTR_DOC_DOCTYPE,
349 UTF16ToUTF8(doctype.name()));
350 }
351
352 const gfx::Size& scroll_offset = document.frame()->scrollOffset();
353 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X, scroll_offset.width());
354 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y, scroll_offset.height());
355
356 const gfx::Size& min_offset = document.frame()->minimumScrollOffset();
357 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN, min_offset.width());
358 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN, min_offset.height());
359
360 const gfx::Size& max_offset = document.frame()->maximumScrollOffset();
361 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, max_offset.width());
362 dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, max_offset.height());
363 }
364
365 if (dst->role == ui::AX_ROLE_TABLE) {
366 int column_count = src.columnCount();
367 int row_count = src.rowCount();
368 if (column_count > 0 && row_count > 0) {
369 std::set<int32> unique_cell_id_set;
370 std::vector<int32> cell_ids;
371 std::vector<int32> unique_cell_ids;
372 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT, column_count);
373 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT, row_count);
374 WebAXObject header = src.headerContainerObject();
375 if (!header.isDetached())
376 dst->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID, header.axID());
377 for (int i = 0; i < column_count * row_count; ++i) {
378 WebAXObject cell = src.cellForColumnAndRow(
379 i % column_count, i / column_count);
380 int cell_id = -1;
381 if (!cell.isDetached()) {
382 cell_id = cell.axID();
383 if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) {
384 unique_cell_id_set.insert(cell_id);
385 unique_cell_ids.push_back(cell_id);
386 }
387 }
388 cell_ids.push_back(cell_id);
389 }
390 dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids);
391 dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids);
392 }
393 }
394
395 if (dst->role == ui::AX_ROLE_ROW) {
396 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX, src.rowIndex());
397 WebAXObject header = src.rowHeader();
398 if (!header.isDetached())
399 dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID, header.axID());
400 }
401
402 if (dst->role == ui::AX_ROLE_COLUMN) {
403 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX, src.columnIndex());
404 WebAXObject header = src.columnHeader();
405 if (!header.isDetached())
406 dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, header.axID());
407 }
408
409 if (dst->role == ui::AX_ROLE_CELL ||
410 dst->role == ui::AX_ROLE_ROW_HEADER ||
411 dst->role == ui::AX_ROLE_COLUMN_HEADER) {
412 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
413 src.cellColumnIndex());
414 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN,
415 src.cellColumnSpan());
416 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX, src.cellRowIndex());
417 dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN, src.cellRowSpan());
418 }
419
420 dst->AddStringAttribute(ui::AX_ATTR_NAME, name);
421
422 // Add the ids of *indirect* children - those who are children of this node,
423 // but whose parent is *not* this node. One example is a table
424 // cell, which is a child of both a row and a column. Because the cell's
425 // parent is the row, the row adds it as a child, and the column adds it
426 // as an indirect child.
427 int child_count = src.childCount();
428 std::vector<int32> indirect_child_ids;
429 for (int i = 0; i < child_count; ++i) {
430 WebAXObject child = src.childAt(i);
431 if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child))
432 indirect_child_ids.push_back(child.axID());
433 }
434 if (indirect_child_ids.size() > 0) {
435 dst->AddIntListAttribute(ui::AX_ATTR_INDIRECT_CHILD_IDS,
436 indirect_child_ids);
437 }
438
439 WebVector<WebAXObject> controls;
440 if (src.ariaControls(controls))
441 AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, controls, dst);
442
443 WebVector<WebAXObject> describedby;
444 if (src.ariaDescribedby(describedby)) {
445 AddIntListAttributeFromWebObjects(
446 ui::AX_ATTR_DESCRIBEDBY_IDS, describedby, dst);
447 }
448
449 WebVector<WebAXObject> flowTo;
450 if (src.ariaFlowTo(flowTo))
451 AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, flowTo, dst);
452
453 WebVector<WebAXObject> labelledby;
454 if (src.ariaLabelledby(labelledby)) {
455 AddIntListAttributeFromWebObjects(
456 ui::AX_ATTR_LABELLEDBY_IDS, labelledby, dst);
457 }
458
459 WebVector<WebAXObject> owns;
460 if (src.ariaOwns(owns))
461 AddIntListAttributeFromWebObjects(ui::AX_ATTR_OWNS_IDS, owns, dst);
462 }
463
464 bool ShouldIncludeChildNode(
465 const WebAXObject& parent,
466 const WebAXObject& child) {
467 // The child may be invalid due to issues in webkit accessibility code.
468 // Don't add children that are invalid thus preventing a crash.
469 // https://bugs.webkit.org/show_bug.cgi?id=44149
470 // TODO(ctguil): We may want to remove this check as webkit stabilizes.
471 if (child.isDetached())
472 return false;
473
474 // Skip children whose parent isn't this - see indirect_child_ids, above.
475 // As an exception, include children of an iframe element.
476 bool is_iframe = false;
477 WebNode node = parent.node();
478 if (!node.isNull() && node.isElementNode()) {
479 WebElement element = node.to<WebElement>();
480 is_iframe = (element.tagName() == base::ASCIIToUTF16("IFRAME"));
481 }
482
483 return (is_iframe || IsParentUnignoredOf(parent, child));
484 }
485
486 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/accessibility/accessibility_node_serializer.h ('k') | content/renderer/accessibility/blink_ax_tree_source.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698