accessibility_node_serializer.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 "third_party/WebKit/public/platform/WebRect.h"
13#include "third_party/WebKit/public/platform/WebSize.h"
14#include "third_party/WebKit/public/platform/WebString.h"
15#include "third_party/WebKit/public/platform/WebVector.h"
16#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityObject.h"
17#include "third_party/WebKit/Source/WebKit/chromium/public/WebAccessibilityRole.h"
18#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
19#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocumentType.h"
20#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
21#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h"
22#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
23#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h"
24#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h"
25
26using WebKit::WebAccessibilityRole;
27using WebKit::WebAccessibilityObject;
28using WebKit::WebDocument;
29using WebKit::WebDocumentType;
30using WebKit::WebElement;
31using WebKit::WebNode;
32using WebKit::WebVector;
33
34namespace content {
35namespace {
36
37// Returns true if |ancestor| is the first unignored parent of |child|,
38// which means that when walking up the parent chain from |child|,
39// |ancestor| is the *first* ancestor that isn't marked as
40// accessibilityIsIgnored().
41bool IsParentUnignoredOf(const WebAccessibilityObject& ancestor,
42                         const WebAccessibilityObject& child) {
43  WebAccessibilityObject parent = child.parentObject();
44  while (!parent.isDetached() && parent.accessibilityIsIgnored())
45    parent = parent.parentObject();
46  return parent.equals(ancestor);
47}
48
49// Provides a conversion between the WebKit::WebAccessibilityRole and a role
50// supported on the Browser side. Listed alphabetically by the
51// WebKit::WebAccessibilityRole (except for default role).
52AccessibilityNodeData::Role ConvertRole(WebKit::WebAccessibilityRole role) {
53  switch (role) {
54    case WebKit::WebAccessibilityRoleAnnotation:
55      return AccessibilityNodeData::ROLE_ANNOTATION;
56    case WebKit::WebAccessibilityRoleApplication:
57      return AccessibilityNodeData::ROLE_APPLICATION;
58    case WebKit::WebAccessibilityRoleApplicationAlert:
59      return AccessibilityNodeData::ROLE_ALERT;
60    case WebKit::WebAccessibilityRoleApplicationAlertDialog:
61      return AccessibilityNodeData::ROLE_ALERT_DIALOG;
62    case WebKit::WebAccessibilityRoleApplicationDialog:
63      return AccessibilityNodeData::ROLE_DIALOG;
64    case WebKit::WebAccessibilityRoleApplicationLog:
65      return AccessibilityNodeData::ROLE_LOG;
66    case WebKit::WebAccessibilityRoleApplicationMarquee:
67      return AccessibilityNodeData::ROLE_MARQUEE;
68    case WebKit::WebAccessibilityRoleApplicationStatus:
69      return AccessibilityNodeData::ROLE_STATUS;
70    case WebKit::WebAccessibilityRoleApplicationTimer:
71      return AccessibilityNodeData::ROLE_TIMER;
72    case WebKit::WebAccessibilityRoleBrowser:
73      return AccessibilityNodeData::ROLE_BROWSER;
74    case WebKit::WebAccessibilityRoleBusyIndicator:
75      return AccessibilityNodeData::ROLE_BUSY_INDICATOR;
76    case WebKit::WebAccessibilityRoleButton:
77      return AccessibilityNodeData::ROLE_BUTTON;
78    case WebKit::WebAccessibilityRoleCanvas:
79      return AccessibilityNodeData::ROLE_CANVAS;
80    case WebKit::WebAccessibilityRoleCell:
81      return AccessibilityNodeData::ROLE_CELL;
82    case WebKit::WebAccessibilityRoleCheckBox:
83      return AccessibilityNodeData::ROLE_CHECKBOX;
84    case WebKit::WebAccessibilityRoleColorWell:
85      return AccessibilityNodeData::ROLE_COLOR_WELL;
86    case WebKit::WebAccessibilityRoleColumn:
87      return AccessibilityNodeData::ROLE_COLUMN;
88    case WebKit::WebAccessibilityRoleColumnHeader:
89      return AccessibilityNodeData::ROLE_COLUMN_HEADER;
90    case WebKit::WebAccessibilityRoleComboBox:
91      return AccessibilityNodeData::ROLE_COMBO_BOX;
92    case WebKit::WebAccessibilityRoleDefinition:
93      return AccessibilityNodeData::ROLE_DEFINITION;
94    case WebKit::WebAccessibilityRoleDescriptionListTerm:
95      return AccessibilityNodeData::ROLE_DESCRIPTION_LIST_TERM;
96    case WebKit::WebAccessibilityRoleDescriptionListDetail:
97      return AccessibilityNodeData::ROLE_DESCRIPTION_LIST_DETAIL;
98    case WebKit::WebAccessibilityRoleDirectory:
99      return AccessibilityNodeData::ROLE_DIRECTORY;
100    case WebKit::WebAccessibilityRoleDisclosureTriangle:
101      return AccessibilityNodeData::ROLE_DISCLOSURE_TRIANGLE;
102    case WebKit::WebAccessibilityRoleDiv:
103      return AccessibilityNodeData::ROLE_DIV;
104    case WebKit::WebAccessibilityRoleDocument:
105      return AccessibilityNodeData::ROLE_DOCUMENT;
106    case WebKit::WebAccessibilityRoleDocumentArticle:
107      return AccessibilityNodeData::ROLE_ARTICLE;
108    case WebKit::WebAccessibilityRoleDocumentMath:
109      return AccessibilityNodeData::ROLE_MATH;
110    case WebKit::WebAccessibilityRoleDocumentNote:
111      return AccessibilityNodeData::ROLE_NOTE;
112    case WebKit::WebAccessibilityRoleDocumentRegion:
113      return AccessibilityNodeData::ROLE_REGION;
114    case WebKit::WebAccessibilityRoleDrawer:
115      return AccessibilityNodeData::ROLE_DRAWER;
116    case WebKit::WebAccessibilityRoleEditableText:
117      return AccessibilityNodeData::ROLE_EDITABLE_TEXT;
118    case WebKit::WebAccessibilityRoleFooter:
119      return AccessibilityNodeData::ROLE_FOOTER;
120    case WebKit::WebAccessibilityRoleForm:
121      return AccessibilityNodeData::ROLE_FORM;
122    case WebKit::WebAccessibilityRoleGrid:
123      return AccessibilityNodeData::ROLE_GRID;
124    case WebKit::WebAccessibilityRoleGroup:
125      return AccessibilityNodeData::ROLE_GROUP;
126    case WebKit::WebAccessibilityRoleGrowArea:
127      return AccessibilityNodeData::ROLE_GROW_AREA;
128    case WebKit::WebAccessibilityRoleHeading:
129      return AccessibilityNodeData::ROLE_HEADING;
130    case WebKit::WebAccessibilityRoleHelpTag:
131      return AccessibilityNodeData::ROLE_HELP_TAG;
132    case WebKit::WebAccessibilityRoleHorizontalRule:
133      return AccessibilityNodeData::ROLE_HORIZONTAL_RULE;
134    case WebKit::WebAccessibilityRoleIgnored:
135      return AccessibilityNodeData::ROLE_IGNORED;
136    case WebKit::WebAccessibilityRoleImage:
137      return AccessibilityNodeData::ROLE_IMAGE;
138    case WebKit::WebAccessibilityRoleImageMap:
139      return AccessibilityNodeData::ROLE_IMAGE_MAP;
140    case WebKit::WebAccessibilityRoleImageMapLink:
141      return AccessibilityNodeData::ROLE_IMAGE_MAP_LINK;
142    case WebKit::WebAccessibilityRoleIncrementor:
143      return AccessibilityNodeData::ROLE_INCREMENTOR;
144    case WebKit::WebAccessibilityRoleLabel:
145      return AccessibilityNodeData::ROLE_LABEL;
146    case WebKit::WebAccessibilityRoleLandmarkApplication:
147      return AccessibilityNodeData::ROLE_LANDMARK_APPLICATION;
148    case WebKit::WebAccessibilityRoleLandmarkBanner:
149      return AccessibilityNodeData::ROLE_LANDMARK_BANNER;
150    case WebKit::WebAccessibilityRoleLandmarkComplementary:
151      return AccessibilityNodeData::ROLE_LANDMARK_COMPLEMENTARY;
152    case WebKit::WebAccessibilityRoleLandmarkContentInfo:
153      return AccessibilityNodeData::ROLE_LANDMARK_CONTENTINFO;
154    case WebKit::WebAccessibilityRoleLandmarkMain:
155      return AccessibilityNodeData::ROLE_LANDMARK_MAIN;
156    case WebKit::WebAccessibilityRoleLandmarkNavigation:
157      return AccessibilityNodeData::ROLE_LANDMARK_NAVIGATION;
158    case WebKit::WebAccessibilityRoleLandmarkSearch:
159      return AccessibilityNodeData::ROLE_LANDMARK_SEARCH;
160    case WebKit::WebAccessibilityRoleLink:
161      return AccessibilityNodeData::ROLE_LINK;
162    case WebKit::WebAccessibilityRoleList:
163      return AccessibilityNodeData::ROLE_LIST;
164    case WebKit::WebAccessibilityRoleListBox:
165      return AccessibilityNodeData::ROLE_LISTBOX;
166    case WebKit::WebAccessibilityRoleListBoxOption:
167      return AccessibilityNodeData::ROLE_LISTBOX_OPTION;
168    case WebKit::WebAccessibilityRoleListItem:
169      return AccessibilityNodeData::ROLE_LIST_ITEM;
170    case WebKit::WebAccessibilityRoleListMarker:
171      return AccessibilityNodeData::ROLE_LIST_MARKER;
172    case WebKit::WebAccessibilityRoleMatte:
173      return AccessibilityNodeData::ROLE_MATTE;
174    case WebKit::WebAccessibilityRoleMenu:
175      return AccessibilityNodeData::ROLE_MENU;
176    case WebKit::WebAccessibilityRoleMenuBar:
177      return AccessibilityNodeData::ROLE_MENU_BAR;
178    case WebKit::WebAccessibilityRoleMenuButton:
179      return AccessibilityNodeData::ROLE_MENU_BUTTON;
180    case WebKit::WebAccessibilityRoleMenuItem:
181      return AccessibilityNodeData::ROLE_MENU_ITEM;
182    case WebKit::WebAccessibilityRoleMenuListOption:
183      return AccessibilityNodeData::ROLE_MENU_LIST_OPTION;
184    case WebKit::WebAccessibilityRoleMenuListPopup:
185      return AccessibilityNodeData::ROLE_MENU_LIST_POPUP;
186    case WebKit::WebAccessibilityRoleOutline:
187      return AccessibilityNodeData::ROLE_OUTLINE;
188    case WebKit::WebAccessibilityRoleParagraph:
189      return AccessibilityNodeData::ROLE_PARAGRAPH;
190    case WebKit::WebAccessibilityRolePopUpButton:
191      return AccessibilityNodeData::ROLE_POPUP_BUTTON;
192    case WebKit::WebAccessibilityRolePresentational:
193      return AccessibilityNodeData::ROLE_PRESENTATIONAL;
194    case WebKit::WebAccessibilityRoleProgressIndicator:
195      return AccessibilityNodeData::ROLE_PROGRESS_INDICATOR;
196    case WebKit::WebAccessibilityRoleRadioButton:
197      return AccessibilityNodeData::ROLE_RADIO_BUTTON;
198    case WebKit::WebAccessibilityRoleRadioGroup:
199      return AccessibilityNodeData::ROLE_RADIO_GROUP;
200    case WebKit::WebAccessibilityRoleRow:
201      return AccessibilityNodeData::ROLE_ROW;
202    case WebKit::WebAccessibilityRoleRowHeader:
203      return AccessibilityNodeData::ROLE_ROW_HEADER;
204    case WebKit::WebAccessibilityRoleRuler:
205      return AccessibilityNodeData::ROLE_RULER;
206    case WebKit::WebAccessibilityRoleRulerMarker:
207      return AccessibilityNodeData::ROLE_RULER_MARKER;
208    case WebKit::WebAccessibilityRoleScrollArea:
209      return AccessibilityNodeData::ROLE_SCROLLAREA;
210    case WebKit::WebAccessibilityRoleScrollBar:
211      return AccessibilityNodeData::ROLE_SCROLLBAR;
212    case WebKit::WebAccessibilityRoleSheet:
213      return AccessibilityNodeData::ROLE_SHEET;
214    case WebKit::WebAccessibilityRoleSlider:
215      return AccessibilityNodeData::ROLE_SLIDER;
216    case WebKit::WebAccessibilityRoleSliderThumb:
217      return AccessibilityNodeData::ROLE_SLIDER_THUMB;
218    case WebKit::WebAccessibilityRoleSpinButton:
219      return AccessibilityNodeData::ROLE_SPIN_BUTTON;
220    case WebKit::WebAccessibilityRoleSpinButtonPart:
221      return AccessibilityNodeData::ROLE_SPIN_BUTTON_PART;
222    case WebKit::WebAccessibilityRoleSplitGroup:
223      return AccessibilityNodeData::ROLE_SPLIT_GROUP;
224    case WebKit::WebAccessibilityRoleSplitter:
225      return AccessibilityNodeData::ROLE_SPLITTER;
226    case WebKit::WebAccessibilityRoleStaticText:
227      return AccessibilityNodeData::ROLE_STATIC_TEXT;
228    case WebKit::WebAccessibilityRoleSVGRoot:
229      return AccessibilityNodeData::ROLE_SVG_ROOT;
230    case WebKit::WebAccessibilityRoleSystemWide:
231      return AccessibilityNodeData::ROLE_SYSTEM_WIDE;
232    case WebKit::WebAccessibilityRoleTab:
233      return AccessibilityNodeData::ROLE_TAB;
234    case WebKit::WebAccessibilityRoleTabGroup:
235      return AccessibilityNodeData::ROLE_TAB_GROUP_UNUSED;
236    case WebKit::WebAccessibilityRoleTabList:
237      return AccessibilityNodeData::ROLE_TAB_LIST;
238    case WebKit::WebAccessibilityRoleTabPanel:
239      return AccessibilityNodeData::ROLE_TAB_PANEL;
240    case WebKit::WebAccessibilityRoleTable:
241      return AccessibilityNodeData::ROLE_TABLE;
242    case WebKit::WebAccessibilityRoleTableHeaderContainer:
243      return AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER;
244    case WebKit::WebAccessibilityRoleTextArea:
245      return AccessibilityNodeData::ROLE_TEXTAREA;
246    case WebKit::WebAccessibilityRoleTextField:
247      return AccessibilityNodeData::ROLE_TEXT_FIELD;
248    case WebKit::WebAccessibilityRoleToggleButton:
249      return AccessibilityNodeData::ROLE_TOGGLE_BUTTON;
250    case WebKit::WebAccessibilityRoleToolbar:
251      return AccessibilityNodeData::ROLE_TOOLBAR;
252    case WebKit::WebAccessibilityRoleTreeGrid:
253      return AccessibilityNodeData::ROLE_TREE_GRID;
254    case WebKit::WebAccessibilityRoleTreeItemRole:
255      return AccessibilityNodeData::ROLE_TREE_ITEM;
256    case WebKit::WebAccessibilityRoleTreeRole:
257      return AccessibilityNodeData::ROLE_TREE;
258    case WebKit::WebAccessibilityRoleUserInterfaceTooltip:
259      return AccessibilityNodeData::ROLE_TOOLTIP;
260    case WebKit::WebAccessibilityRoleValueIndicator:
261      return AccessibilityNodeData::ROLE_VALUE_INDICATOR;
262    case WebKit::WebAccessibilityRoleWebArea:
263      return AccessibilityNodeData::ROLE_WEB_AREA;
264    case WebKit::WebAccessibilityRoleWebCoreLink:
265      return AccessibilityNodeData::ROLE_WEBCORE_LINK;
266    case WebKit::WebAccessibilityRoleWindow:
267      return AccessibilityNodeData::ROLE_WINDOW;
268
269    default:
270      return AccessibilityNodeData::ROLE_UNKNOWN;
271  }
272}
273
274// Provides a conversion between the WebAccessibilityObject state
275// accessors and a state bitmask that can be serialized and sent to the
276// Browser process. Rare state are sent as boolean attributes instead.
277uint32 ConvertState(const WebAccessibilityObject& o) {
278  uint32 state = 0;
279  if (o.isChecked())
280    state |= (1 << AccessibilityNodeData::STATE_CHECKED);
281
282  if (o.isCollapsed())
283    state |= (1 << AccessibilityNodeData::STATE_COLLAPSED);
284
285  if (o.canSetFocusAttribute())
286    state |= (1 << AccessibilityNodeData::STATE_FOCUSABLE);
287
288  if (o.isFocused())
289    state |= (1 << AccessibilityNodeData::STATE_FOCUSED);
290
291  if (o.roleValue() == WebKit::WebAccessibilityRolePopUpButton ||
292      o.ariaHasPopup()) {
293    state |= (1 << AccessibilityNodeData::STATE_HASPOPUP);
294    if (!o.isCollapsed())
295      state |= (1 << AccessibilityNodeData::STATE_EXPANDED);
296  }
297
298  if (o.isHovered())
299    state |= (1 << AccessibilityNodeData::STATE_HOTTRACKED);
300
301  if (o.isIndeterminate())
302    state |= (1 << AccessibilityNodeData::STATE_INDETERMINATE);
303
304  if (!o.isVisible())
305    state |= (1 << AccessibilityNodeData::STATE_INVISIBLE);
306
307  if (o.isLinked())
308    state |= (1 << AccessibilityNodeData::STATE_LINKED);
309
310  if (o.isMultiSelectable())
311    state |= (1 << AccessibilityNodeData::STATE_MULTISELECTABLE);
312
313  if (o.isOffScreen())
314    state |= (1 << AccessibilityNodeData::STATE_OFFSCREEN);
315
316  if (o.isPressed())
317    state |= (1 << AccessibilityNodeData::STATE_PRESSED);
318
319  if (o.isPasswordField())
320    state |= (1 << AccessibilityNodeData::STATE_PROTECTED);
321
322  if (o.isReadOnly())
323    state |= (1 << AccessibilityNodeData::STATE_READONLY);
324
325  if (o.isRequired())
326    state |= (1 << AccessibilityNodeData::STATE_REQUIRED);
327
328  if (o.canSetSelectedAttribute())
329    state |= (1 << AccessibilityNodeData::STATE_SELECTABLE);
330
331  if (o.isSelected())
332    state |= (1 << AccessibilityNodeData::STATE_SELECTED);
333
334  if (o.isVisited())
335    state |= (1 << AccessibilityNodeData::STATE_TRAVERSED);
336
337  if (!o.isEnabled())
338    state |= (1 << AccessibilityNodeData::STATE_UNAVAILABLE);
339
340  if (o.isVertical())
341    state |= (1 << AccessibilityNodeData::STATE_VERTICAL);
342
343  if (o.isVisited())
344    state |= (1 << AccessibilityNodeData::STATE_VISITED);
345
346  return state;
347}
348
349}  // Anonymous namespace
350
351void SerializeAccessibilityNode(
352    const WebAccessibilityObject& src,
353    AccessibilityNodeData* dst) {
354  dst->name = src.title();
355  dst->role = ConvertRole(src.roleValue());
356  dst->state = ConvertState(src);
357  dst->location = src.boundingBoxRect();
358  dst->id = src.axID();
359
360  if (src.valueDescription().length())
361    dst->value = src.valueDescription();
362  else
363    dst->value = src.stringValue();
364
365  if (dst->role == AccessibilityNodeData::ROLE_COLOR_WELL) {
366    int r, g, b;
367    src.colorValue(r, g, b);
368    dst->int_attributes[dst->ATTR_COLOR_VALUE_RED] = r;
369    dst->int_attributes[dst->ATTR_COLOR_VALUE_GREEN] = g;
370    dst->int_attributes[dst->ATTR_COLOR_VALUE_BLUE] = b;
371  }
372
373  if (src.accessKey().length())
374    dst->string_attributes[dst->ATTR_ACCESS_KEY] = src.accessKey();
375  if (src.actionVerb().length())
376    dst->string_attributes[dst->ATTR_ACTION] = src.actionVerb();
377  if (src.isAriaReadOnly())
378    dst->bool_attributes[dst->ATTR_ARIA_READONLY] = true;
379  if (src.isButtonStateMixed())
380    dst->bool_attributes[dst->ATTR_BUTTON_MIXED] = true;
381  if (src.canSetValueAttribute())
382    dst->bool_attributes[dst->ATTR_CAN_SET_VALUE] = true;
383  if (src.accessibilityDescription().length())
384    dst->string_attributes[dst->ATTR_DESCRIPTION] =
385        src.accessibilityDescription();
386  if (src.hasComputedStyle())
387    dst->string_attributes[dst->ATTR_DISPLAY] = src.computedStyleDisplay();
388  if (src.helpText().length())
389    dst->string_attributes[dst->ATTR_HELP] = src.helpText();
390  if (src.keyboardShortcut().length())
391    dst->string_attributes[dst->ATTR_SHORTCUT] = src.keyboardShortcut();
392  if (!src.titleUIElement().isDetached()) {
393    dst->int_attributes[dst->ATTR_TITLE_UI_ELEMENT] =
394        src.titleUIElement().axID();
395  }
396  if (!src.url().isEmpty())
397    dst->string_attributes[dst->ATTR_URL] = src.url().spec().utf16();
398
399  if (dst->role == dst->ROLE_HEADING)
400    dst->int_attributes[dst->ATTR_HIERARCHICAL_LEVEL] = src.headingLevel();
401  else if ((dst->role == dst->ROLE_TREE_ITEM || dst->role == dst->ROLE_ROW) &&
402           src.hierarchicalLevel() > 0) {
403    dst->int_attributes[dst->ATTR_HIERARCHICAL_LEVEL] = src.hierarchicalLevel();
404  }
405
406  // Treat the active list box item as focused.
407  if (dst->role == dst->ROLE_LISTBOX_OPTION && src.isSelectedOptionActive())
408    dst->state |= (1 << AccessibilityNodeData::STATE_FOCUSED);
409
410  if (src.canvasHasFallbackContent())
411    dst->role = AccessibilityNodeData::ROLE_CANVAS_WITH_FALLBACK_CONTENT;
412
413  WebNode node = src.node();
414  bool is_iframe = false;
415
416  if (!node.isNull() && node.isElementNode()) {
417    WebElement element = node.to<WebElement>();
418    is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
419
420    if (LowerCaseEqualsASCII(element.getAttribute("aria-expanded"), "true"))
421      dst->state |= (1 << AccessibilityNodeData::STATE_EXPANDED);
422
423    // TODO(ctguil): The tagName in WebKit is lower cased but
424    // HTMLElement::nodeName calls localNameUpper. Consider adding
425    // a WebElement method that returns the original lower cased tagName.
426    dst->string_attributes[dst->ATTR_HTML_TAG] =
427        StringToLowerASCII(string16(element.tagName()));
428    for (unsigned i = 0; i < element.attributeCount(); ++i) {
429      string16 name = StringToLowerASCII(string16(
430          element.attributeLocalName(i)));
431      string16 value = element.attributeValue(i);
432      dst->html_attributes.push_back(
433          std::pair<string16, string16>(name, value));
434    }
435
436    if (dst->role == dst->ROLE_EDITABLE_TEXT ||
437        dst->role == dst->ROLE_TEXTAREA ||
438        dst->role == dst->ROLE_TEXT_FIELD) {
439      dst->int_attributes[dst->ATTR_TEXT_SEL_START] = src.selectionStart();
440      dst->int_attributes[dst->ATTR_TEXT_SEL_END] = src.selectionEnd();
441
442      WebVector<int> src_line_breaks;
443      src.lineBreaks(src_line_breaks);
444      dst->line_breaks.reserve(src_line_breaks.size());
445      for (size_t i = 0; i < src_line_breaks.size(); ++i)
446        dst->line_breaks.push_back(src_line_breaks[i]);
447    }
448
449    // ARIA role.
450    if (element.hasAttribute("role")) {
451      dst->string_attributes[dst->ATTR_ROLE] = element.getAttribute("role");
452    }
453
454    // Live region attributes
455    if (element.hasAttribute("aria-atomic")) {
456      dst->bool_attributes[dst->ATTR_LIVE_ATOMIC] =
457          LowerCaseEqualsASCII(element.getAttribute("aria-atomic"), "true");
458    }
459    if (element.hasAttribute("aria-busy")) {
460      dst->bool_attributes[dst->ATTR_LIVE_BUSY] =
461          LowerCaseEqualsASCII(element.getAttribute("aria-busy"), "true");
462    }
463    if (element.hasAttribute("aria-live")) {
464      dst->string_attributes[dst->ATTR_LIVE_STATUS] =
465          element.getAttribute("aria-live");
466    }
467    if (element.hasAttribute("aria-relevant")) {
468      dst->string_attributes[dst->ATTR_LIVE_RELEVANT] =
469          element.getAttribute("aria-relevant");
470    }
471  }
472
473  // Walk up the parent chain to set live region attributes of containers
474
475  WebAccessibilityObject container_accessible = src;
476  while (!container_accessible.isDetached()) {
477    WebNode container_node = container_accessible.node();
478    if (!container_node.isNull() && container_node.isElementNode()) {
479      WebElement container_elem =
480          container_node.to<WebElement>();
481      if (container_elem.hasAttribute("aria-atomic") &&
482          dst->bool_attributes.find(dst->ATTR_CONTAINER_LIVE_ATOMIC) ==
483          dst->bool_attributes.end()) {
484        dst->bool_attributes[dst->ATTR_CONTAINER_LIVE_ATOMIC] =
485            LowerCaseEqualsASCII(container_elem.getAttribute("aria-atomic"),
486                                 "true");
487      }
488      if (container_elem.hasAttribute("aria-busy") &&
489          dst->bool_attributes.find(dst->ATTR_CONTAINER_LIVE_BUSY) ==
490          dst->bool_attributes.end()) {
491        dst->bool_attributes[dst->ATTR_CONTAINER_LIVE_BUSY] =
492            LowerCaseEqualsASCII(container_elem.getAttribute("aria-busy"),
493                                 "true");
494      }
495      if (container_elem.hasAttribute("aria-live") &&
496          dst->string_attributes.find(dst->ATTR_CONTAINER_LIVE_STATUS) ==
497          dst->string_attributes.end()) {
498        dst->string_attributes[dst->ATTR_CONTAINER_LIVE_STATUS] =
499            container_elem.getAttribute("aria-live");
500      }
501      if (container_elem.hasAttribute("aria-relevant") &&
502          dst->string_attributes.find(dst->ATTR_CONTAINER_LIVE_RELEVANT) ==
503          dst->string_attributes.end()) {
504        dst->string_attributes[dst->ATTR_CONTAINER_LIVE_RELEVANT] =
505            container_elem.getAttribute("aria-relevant");
506      }
507    }
508    container_accessible = container_accessible.parentObject();
509  }
510
511  if (dst->role == dst->ROLE_PROGRESS_INDICATOR ||
512      dst->role == dst->ROLE_SCROLLBAR ||
513      dst->role == dst->ROLE_SLIDER ||
514      dst->role == dst->ROLE_SPIN_BUTTON) {
515    dst->float_attributes[dst->ATTR_VALUE_FOR_RANGE] = src.valueForRange();
516    dst->float_attributes[dst->ATTR_MAX_VALUE_FOR_RANGE] =
517        src.maxValueForRange();
518    dst->float_attributes[dst->ATTR_MIN_VALUE_FOR_RANGE] =
519        src.minValueForRange();
520  }
521
522  if (dst->role == dst->ROLE_DOCUMENT ||
523      dst->role == dst->ROLE_WEB_AREA) {
524    dst->string_attributes[dst->ATTR_HTML_TAG] = ASCIIToUTF16("#document");
525    const WebDocument& document = src.document();
526    if (dst->name.empty())
527      dst->name = document.title();
528    dst->string_attributes[dst->ATTR_DOC_TITLE] = document.title();
529    dst->string_attributes[dst->ATTR_DOC_URL] = document.url().spec().utf16();
530    dst->string_attributes[dst->ATTR_DOC_MIMETYPE] =
531        ASCIIToUTF16(document.isXHTMLDocument() ? "text/xhtml" : "text/html");
532    dst->bool_attributes[dst->ATTR_DOC_LOADED] = src.isLoaded();
533    dst->float_attributes[dst->ATTR_DOC_LOADING_PROGRESS] =
534        src.estimatedLoadingProgress();
535
536    const WebDocumentType& doctype = document.doctype();
537    if (!doctype.isNull())
538      dst->string_attributes[dst->ATTR_DOC_DOCTYPE] = doctype.name();
539
540    const gfx::Size& scroll_offset = document.frame()->scrollOffset();
541    dst->int_attributes[dst->ATTR_SCROLL_X] = scroll_offset.width();
542    dst->int_attributes[dst->ATTR_SCROLL_Y] = scroll_offset.height();
543
544    const gfx::Size& min_offset = document.frame()->minimumScrollOffset();
545    dst->int_attributes[dst->ATTR_SCROLL_X_MIN] = min_offset.width();
546    dst->int_attributes[dst->ATTR_SCROLL_Y_MIN] = min_offset.height();
547
548    const gfx::Size& max_offset = document.frame()->maximumScrollOffset();
549    dst->int_attributes[dst->ATTR_SCROLL_X_MAX] = max_offset.width();
550    dst->int_attributes[dst->ATTR_SCROLL_Y_MAX] = max_offset.height();
551  }
552
553  if (dst->role == dst->ROLE_TABLE) {
554    int column_count = src.columnCount();
555    int row_count = src.rowCount();
556    if (column_count > 0 && row_count > 0) {
557      std::set<int> unique_cell_id_set;
558      dst->int_attributes[dst->ATTR_TABLE_COLUMN_COUNT] = column_count;
559      dst->int_attributes[dst->ATTR_TABLE_ROW_COUNT] = row_count;
560      WebAccessibilityObject header = src.headerContainerObject();
561      if (!header.isDetached())
562        dst->int_attributes[dst->ATTR_TABLE_HEADER_ID] = header.axID();
563      for (int i = 0; i < column_count * row_count; ++i) {
564        WebAccessibilityObject cell = src.cellForColumnAndRow(
565            i % column_count, i / column_count);
566        int cell_id = -1;
567        if (!cell.isDetached()) {
568          cell_id = cell.axID();
569          if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) {
570            unique_cell_id_set.insert(cell_id);
571            dst->unique_cell_ids.push_back(cell_id);
572          }
573        }
574        dst->cell_ids.push_back(cell_id);
575      }
576    }
577  }
578
579  if (dst->role == dst->ROLE_ROW) {
580    dst->int_attributes[dst->ATTR_TABLE_ROW_INDEX] = src.rowIndex();
581    WebAccessibilityObject header = src.rowHeader();
582    if (!header.isDetached())
583      dst->int_attributes[dst->ATTR_TABLE_ROW_HEADER_ID] = header.axID();
584  }
585
586  if (dst->role == dst->ROLE_COLUMN) {
587    dst->int_attributes[dst->ATTR_TABLE_COLUMN_INDEX] = src.columnIndex();
588    WebAccessibilityObject header = src.columnHeader();
589    if (!header.isDetached())
590      dst->int_attributes[dst->ATTR_TABLE_COLUMN_HEADER_ID] = header.axID();
591  }
592
593  if (dst->role == dst->ROLE_CELL ||
594      dst->role == dst->ROLE_ROW_HEADER ||
595      dst->role == dst->ROLE_COLUMN_HEADER) {
596    dst->int_attributes[dst->ATTR_TABLE_CELL_COLUMN_INDEX] =
597        src.cellColumnIndex();
598    dst->int_attributes[dst->ATTR_TABLE_CELL_COLUMN_SPAN] =
599        src.cellColumnSpan();
600    dst->int_attributes[dst->ATTR_TABLE_CELL_ROW_INDEX] = src.cellRowIndex();
601    dst->int_attributes[dst->ATTR_TABLE_CELL_ROW_SPAN] = src.cellRowSpan();
602  }
603
604  // Add the ids of *indirect* children - those who are children of this node,
605  // but whose parent is *not* this node. One example is a table
606  // cell, which is a child of both a row and a column. Because the cell's
607  // parent is the row, the row adds it as a child, and the column adds it
608  // as an indirect child.
609  int child_count = src.childCount();
610  for (int i = 0; i < child_count; ++i) {
611    WebAccessibilityObject child = src.childAt(i);
612    if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child))
613      dst->indirect_child_ids.push_back(child.axID());
614  }
615}
616
617bool ShouldIncludeChildNode(
618    const WebAccessibilityObject& parent,
619    const WebAccessibilityObject& child) {
620  switch(parent.roleValue()) {
621  case WebKit::WebAccessibilityRoleSlider:
622  case WebKit::WebAccessibilityRoleEditableText:
623  case WebKit::WebAccessibilityRoleTextArea:
624  case WebKit::WebAccessibilityRoleTextField:
625    return false;
626  default:
627    break;
628  }
629
630  // The child may be invalid due to issues in webkit accessibility code.
631  // Don't add children that are invalid thus preventing a crash.
632  // https://bugs.webkit.org/show_bug.cgi?id=44149
633  // TODO(ctguil): We may want to remove this check as webkit stabilizes.
634  if (child.isDetached())
635    return false;
636
637  // Skip children whose parent isn't this - see indirect_child_ids, above.
638  // As an exception, include children of an iframe element.
639  bool is_iframe = false;
640  WebNode node = parent.node();
641  if (!node.isNull() && node.isElementNode()) {
642    WebElement element = node.to<WebElement>();
643    is_iframe = (element.tagName() == ASCIIToUTF16("IFRAME"));
644  }
645
646  return (is_iframe || IsParentUnignoredOf(parent, child));
647}
648
649}  // namespace content
650