• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/common/accessibility_node_data.h"
6 
7 #include <set>
8 
9 #include "base/containers/hash_tables.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 
14 using base::DoubleToString;
15 using base::IntToString;
16 
17 namespace {
18 
19 #ifndef NDEBUG
IntVectorToString(const std::vector<int> & items)20 std::string IntVectorToString(const std::vector<int>& items) {
21   std::string str;
22   for (size_t i = 0; i < items.size(); ++i) {
23     if (i > 0)
24       str += ",";
25     str += IntToString(items[i]);
26   }
27   return str;
28 }
29 #endif
30 
31 }  // Anonymous namespace
32 
33 namespace content {
34 
AccessibilityNodeData()35 AccessibilityNodeData::AccessibilityNodeData()
36     : id(-1),
37       role(blink::WebAXRoleUnknown),
38       state(-1) {
39 }
40 
~AccessibilityNodeData()41 AccessibilityNodeData::~AccessibilityNodeData() {
42 }
43 
AddStringAttribute(StringAttribute attribute,const std::string & value)44 void AccessibilityNodeData::AddStringAttribute(
45     StringAttribute attribute, const std::string& value) {
46   string_attributes.push_back(std::make_pair(attribute, value));
47 }
48 
AddIntAttribute(IntAttribute attribute,int value)49 void AccessibilityNodeData::AddIntAttribute(
50     IntAttribute attribute, int value) {
51   int_attributes.push_back(std::make_pair(attribute, value));
52 }
53 
AddFloatAttribute(FloatAttribute attribute,float value)54 void AccessibilityNodeData::AddFloatAttribute(
55     FloatAttribute attribute, float value) {
56   float_attributes.push_back(std::make_pair(attribute, value));
57 }
58 
AddBoolAttribute(BoolAttribute attribute,bool value)59 void AccessibilityNodeData::AddBoolAttribute(
60     BoolAttribute attribute, bool value) {
61   bool_attributes.push_back(std::make_pair(attribute, value));
62 }
63 
AddIntListAttribute(IntListAttribute attribute,const std::vector<int32> & value)64 void AccessibilityNodeData::AddIntListAttribute(
65     IntListAttribute attribute, const std::vector<int32>& value) {
66   intlist_attributes.push_back(std::make_pair(attribute, value));
67 }
68 
SetName(std::string name)69 void AccessibilityNodeData::SetName(std::string name) {
70   string_attributes.push_back(std::make_pair(ATTR_NAME, name));
71 }
72 
SetValue(std::string value)73 void AccessibilityNodeData::SetValue(std::string value) {
74   string_attributes.push_back(std::make_pair(ATTR_VALUE, value));
75 }
76 
AccessibilityNodeDataTreeNode()77 AccessibilityNodeDataTreeNode::AccessibilityNodeDataTreeNode()
78   : AccessibilityNodeData() {
79 }
80 
~AccessibilityNodeDataTreeNode()81 AccessibilityNodeDataTreeNode::~AccessibilityNodeDataTreeNode() {
82 }
83 
operator =(const AccessibilityNodeData & src)84 AccessibilityNodeDataTreeNode& AccessibilityNodeDataTreeNode::operator=(
85     const AccessibilityNodeData& src) {
86   AccessibilityNodeData::operator=(src);
87   return *this;
88 }
89 
MakeAccessibilityNodeDataTree(const std::vector<AccessibilityNodeData> & src_vector,AccessibilityNodeDataTreeNode * dst_root)90 void MakeAccessibilityNodeDataTree(
91     const std::vector<AccessibilityNodeData>& src_vector,
92     AccessibilityNodeDataTreeNode* dst_root) {
93   // This method assumes |src_vector| contains all of the nodes of
94   // an accessibility tree, and that each parent comes before its
95   // children. Each node has an id, and the ids of its children.
96   // The output is a tree where each node contains its children.
97 
98   // Initialize a hash map with all of the ids in |src_vector|.
99   base::hash_map<int32, AccessibilityNodeDataTreeNode*> id_map;
100   for (size_t i = 0; i < src_vector.size(); ++i)
101     id_map[src_vector[i].id] = NULL;
102 
103   // Copy the nodes to the output tree one at a time.
104   for (size_t i = 0; i < src_vector.size(); ++i) {
105     const AccessibilityNodeData& src_node = src_vector[i];
106     AccessibilityNodeDataTreeNode* dst_node;
107 
108     // If it's the first element in the vector, assume it's
109     // the root.  For any other element, look for it in our
110     // hash map, and skip it if not there (meaning there was
111     // an extranous node, or the nodes were sent in the wrong
112     // order).
113     if (i == 0) {
114       dst_node = dst_root;
115     } else {
116       dst_node = id_map[src_node.id];
117       if (!dst_node)
118         continue;
119     }
120 
121     // Copy the node data.
122     *dst_node = src_node;
123 
124     // Add placeholders for all of the node's children in the tree,
125     // and add them to the hash map so we can find them when we
126     // encounter them in |src_vector|.
127     dst_node->children.reserve(src_node.child_ids.size());
128     for (size_t j = 0; j < src_node.child_ids.size(); ++j) {
129       int child_id = src_node.child_ids[j];
130       if (id_map.find(child_id) != id_map.end()) {
131         dst_node->children.push_back(AccessibilityNodeDataTreeNode());
132         id_map[child_id] = &dst_node->children.back();
133       }
134     }
135   }
136 }
137 
138 #ifndef NDEBUG
DebugString(bool recursive) const139 std::string AccessibilityNodeData::DebugString(bool recursive) const {
140   std::string result;
141 
142   result += "id=" + IntToString(id);
143 
144   switch (role) {
145     case blink::WebAXRoleAlert: result += " ALERT"; break;
146     case blink::WebAXRoleAlertDialog: result += " ALERT_DIALOG"; break;
147     case blink::WebAXRoleAnnotation: result += " ANNOTATION"; break;
148     case blink::WebAXRoleApplication: result += " APPLICATION"; break;
149     case blink::WebAXRoleArticle: result += " ARTICLE"; break;
150     case blink::WebAXRoleBanner: result += " L_BANNER"; break;
151     case blink::WebAXRoleBrowser: result += " BROWSER"; break;
152     case blink::WebAXRoleBusyIndicator: result += " BUSY_INDICATOR"; break;
153     case blink::WebAXRoleButton: result += " BUTTON"; break;
154     case blink::WebAXRoleCanvas: result += " CANVAS"; break;
155     case blink::WebAXRoleCell: result += " CELL"; break;
156     case blink::WebAXRoleCheckBox: result += " CHECKBOX"; break;
157     case blink::WebAXRoleColorWell: result += " COLOR_WELL"; break;
158     case blink::WebAXRoleColumn: result += " COLUMN"; break;
159     case blink::WebAXRoleColumnHeader: result += " COLUMN_HEADER"; break;
160     case blink::WebAXRoleComboBox: result += " COMBO_BOX"; break;
161     case blink::WebAXRoleComplementary: result += " L_COMPLEMENTARY"; break;
162     case blink::WebAXRoleContentInfo: result += " L_CONTENTINFO"; break;
163     case blink::WebAXRoleDefinition: result += " DEFINITION"; break;
164     case blink::WebAXRoleDescriptionListDetail: result += " DD"; break;
165     case blink::WebAXRoleDescriptionListTerm: result += " DT"; break;
166     case blink::WebAXRoleDialog: result += " DIALOG"; break;
167     case blink::WebAXRoleDirectory: result += " DIRECTORY"; break;
168     case blink::WebAXRoleDisclosureTriangle:
169         result += " DISCLOSURE_TRIANGLE"; break;
170     case blink::WebAXRoleDiv: result += " DIV"; break;
171     case blink::WebAXRoleDocument: result += " DOCUMENT"; break;
172     case blink::WebAXRoleDrawer: result += " DRAWER"; break;
173     case blink::WebAXRoleEditableText: result += " EDITABLE_TEXT"; break;
174     case blink::WebAXRoleFooter: result += " FOOTER"; break;
175     case blink::WebAXRoleForm: result += " FORM"; break;
176     case blink::WebAXRoleGrid: result += " GRID"; break;
177     case blink::WebAXRoleGroup: result += " GROUP"; break;
178     case blink::WebAXRoleGrowArea: result += " GROW_AREA"; break;
179     case blink::WebAXRoleHeading: result += " HEADING"; break;
180     case blink::WebAXRoleHelpTag: result += " HELP_TAG"; break;
181     case blink::WebAXRoleHorizontalRule: result += " HORIZONTAL_RULE"; break;
182     case blink::WebAXRoleIgnored: result += " IGNORED"; break;
183     case blink::WebAXRoleImage: result += " IMAGE"; break;
184     case blink::WebAXRoleImageMap: result += " IMAGE_MAP"; break;
185     case blink::WebAXRoleImageMapLink: result += " IMAGE_MAP_LINK"; break;
186     case blink::WebAXRoleIncrementor: result += " INCREMENTOR"; break;
187     case blink::WebAXRoleInlineTextBox: result += " INLINE_TEXT_BOX"; break;
188     case blink::WebAXRoleLabel: result += " LABEL"; break;
189     case blink::WebAXRoleLink: result += " LINK"; break;
190     case blink::WebAXRoleList: result += " LIST"; break;
191     case blink::WebAXRoleListBox: result += " LISTBOX"; break;
192     case blink::WebAXRoleListBoxOption: result += " LISTBOX_OPTION"; break;
193     case blink::WebAXRoleListItem: result += " LIST_ITEM"; break;
194     case blink::WebAXRoleListMarker: result += " LIST_MARKER"; break;
195     case blink::WebAXRoleLog: result += " LOG"; break;
196     case blink::WebAXRoleMain: result += " L_MAIN"; break;
197     case blink::WebAXRoleMarquee: result += " MARQUEE"; break;
198     case blink::WebAXRoleMath: result += " MATH"; break;
199     case blink::WebAXRoleMatte: result += " MATTE"; break;
200     case blink::WebAXRoleMenu: result += " MENU"; break;
201     case blink::WebAXRoleMenuBar: result += " MENU_BAR"; break;
202     case blink::WebAXRoleMenuButton: result += " MENU_BUTTON"; break;
203     case blink::WebAXRoleMenuItem: result += " MENU_ITEM"; break;
204     case blink::WebAXRoleMenuListOption: result += " MENU_LIST_OPTION"; break;
205     case blink::WebAXRoleMenuListPopup: result += " MENU_LIST_POPUP"; break;
206     case blink::WebAXRoleNavigation: result += " L_NAVIGATION"; break;
207     case blink::WebAXRoleNote: result += " NOTE"; break;
208     case blink::WebAXRoleOutline: result += " OUTLINE"; break;
209     case blink::WebAXRoleParagraph: result += " PARAGRAPH"; break;
210     case blink::WebAXRolePopUpButton: result += " POPUP_BUTTON"; break;
211     case blink::WebAXRolePresentational: result += " PRESENTATIONAL"; break;
212     case blink::WebAXRoleProgressIndicator:
213         result += " PROGRESS_INDICATOR"; break;
214     case blink::WebAXRoleRadioButton: result += " RADIO_BUTTON"; break;
215     case blink::WebAXRoleRadioGroup: result += " RADIO_GROUP"; break;
216     case blink::WebAXRoleRegion: result += " REGION"; break;
217     case blink::WebAXRoleRootWebArea: result += " ROOT_WEB_AREA"; break;
218     case blink::WebAXRoleRow: result += " ROW"; break;
219     case blink::WebAXRoleRowHeader: result += " ROW_HEADER"; break;
220     case blink::WebAXRoleRuler: result += " RULER"; break;
221     case blink::WebAXRoleRulerMarker: result += " RULER_MARKER"; break;
222     case blink::WebAXRoleSVGRoot: result += " SVG_ROOT"; break;
223     case blink::WebAXRoleScrollArea: result += " SCROLLAREA"; break;
224     case blink::WebAXRoleScrollBar: result += " SCROLLBAR"; break;
225     case blink::WebAXRoleSearch: result += " L_SEARCH"; break;
226     case blink::WebAXRoleSheet: result += " SHEET"; break;
227     case blink::WebAXRoleSlider: result += " SLIDER"; break;
228     case blink::WebAXRoleSliderThumb: result += " SLIDER_THUMB"; break;
229     case blink::WebAXRoleSpinButton: result += " SPIN_BUTTON"; break;
230     case blink::WebAXRoleSpinButtonPart: result += " SPIN_BUTTON_PART"; break;
231     case blink::WebAXRoleSplitGroup: result += " SPLIT_GROUP"; break;
232     case blink::WebAXRoleSplitter: result += " SPLITTER"; break;
233     case blink::WebAXRoleStaticText: result += " STATIC_TEXT"; break;
234     case blink::WebAXRoleStatus: result += " STATUS"; break;
235     case blink::WebAXRoleSystemWide: result += " SYSTEM_WIDE"; break;
236     case blink::WebAXRoleTab: result += " TAB"; break;
237     case blink::WebAXRoleTabList: result += " TAB_LIST"; break;
238     case blink::WebAXRoleTabPanel: result += " TAB_PANEL"; break;
239     case blink::WebAXRoleTable: result += " TABLE"; break;
240     case blink::WebAXRoleTableHeaderContainer:
241         result += " TABLE_HDR_CONTAINER"; break;
242     case blink::WebAXRoleTextArea: result += " TEXTAREA"; break;
243     case blink::WebAXRoleTextField: result += " TEXT_FIELD"; break;
244     case blink::WebAXRoleTimer: result += " TIMER"; break;
245     case blink::WebAXRoleToggleButton: result += " TOGGLE_BUTTON"; break;
246     case blink::WebAXRoleToolbar: result += " TOOLBAR"; break;
247     case blink::WebAXRoleTree: result += " TREE"; break;
248     case blink::WebAXRoleTreeGrid: result += " TREE_GRID"; break;
249     case blink::WebAXRoleTreeItem: result += " TREE_ITEM"; break;
250     case blink::WebAXRoleUnknown: result += " UNKNOWN"; break;
251     case blink::WebAXRoleUserInterfaceTooltip: result += " TOOLTIP"; break;
252     case blink::WebAXRoleValueIndicator: result += " VALUE_INDICATOR"; break;
253     case blink::WebAXRoleWebArea: result += " WEB_AREA"; break;
254     case blink::WebAXRoleWindow: result += " WINDOW"; break;
255     default:
256       assert(false);
257   }
258 
259   if (state & (1 << blink::WebAXStateBusy))
260     result += " BUSY";
261   if (state & (1 << blink::WebAXStateChecked))
262     result += " CHECKED";
263   if (state & (1 << blink::WebAXStateCollapsed))
264     result += " COLLAPSED";
265   if (state & (1 << blink::WebAXStateExpanded))
266     result += " EXPANDED";
267   if (state & (1 << blink::WebAXStateFocusable))
268     result += " FOCUSABLE";
269   if (state & (1 << blink::WebAXStateFocused))
270     result += " FOCUSED";
271   if (state & (1 << blink::WebAXStateHaspopup))
272     result += " HASPOPUP";
273   if (state & (1 << blink::WebAXStateHovered))
274     result += " HOTTRACKED";
275   if (state & (1 << blink::WebAXStateIndeterminate))
276     result += " INDETERMINATE";
277   if (state & (1 << blink::WebAXStateInvisible))
278     result += " INVISIBLE";
279   if (state & (1 << blink::WebAXStateLinked))
280     result += " LINKED";
281   if (state & (1 << blink::WebAXStateMultiselectable))
282     result += " MULTISELECTABLE";
283   if (state & (1 << blink::WebAXStateOffscreen))
284     result += " OFFSCREEN";
285   if (state & (1 << blink::WebAXStatePressed))
286     result += " PRESSED";
287   if (state & (1 << blink::WebAXStateProtected))
288     result += " PROTECTED";
289   if (state & (1 << blink::WebAXStateReadonly))
290     result += " READONLY";
291   if (state & (1 << blink::WebAXStateRequired))
292     result += " REQUIRED";
293   if (state & (1 << blink::WebAXStateSelectable))
294     result += " SELECTABLE";
295   if (state & (1 << blink::WebAXStateSelected))
296     result += " SELECTED";
297   if (state & (1 << blink::WebAXStateVertical))
298     result += " VERTICAL";
299   if (state & (1 << blink::WebAXStateVisited))
300     result += " VISITED";
301 
302   result += " (" + IntToString(location.x()) + ", " +
303                    IntToString(location.y()) + ")-(" +
304                    IntToString(location.width()) + ", " +
305                    IntToString(location.height()) + ")";
306 
307   for (size_t i = 0; i < int_attributes.size(); ++i) {
308     std::string value = IntToString(int_attributes[i].second);
309     switch (int_attributes[i].first) {
310       case ATTR_SCROLL_X:
311         result += " scroll_x=" + value;
312         break;
313       case ATTR_SCROLL_X_MIN:
314         result += " scroll_x_min=" + value;
315         break;
316       case ATTR_SCROLL_X_MAX:
317         result += " scroll_x_max=" + value;
318         break;
319       case ATTR_SCROLL_Y:
320         result += " scroll_y=" + value;
321         break;
322       case ATTR_SCROLL_Y_MIN:
323         result += " scroll_y_min=" + value;
324         break;
325       case ATTR_SCROLL_Y_MAX:
326         result += " scroll_y_max=" + value;
327         break;
328       case ATTR_HIERARCHICAL_LEVEL:
329         result += " level=" + value;
330         break;
331       case ATTR_TEXT_SEL_START:
332         result += " sel_start=" + value;
333         break;
334       case ATTR_TEXT_SEL_END:
335         result += " sel_end=" + value;
336         break;
337       case ATTR_TABLE_ROW_COUNT:
338         result += " rows=" + value;
339         break;
340       case ATTR_TABLE_COLUMN_COUNT:
341         result += " cols=" + value;
342         break;
343       case ATTR_TABLE_CELL_COLUMN_INDEX:
344         result += " col=" + value;
345         break;
346       case ATTR_TABLE_CELL_ROW_INDEX:
347         result += " row=" + value;
348         break;
349       case ATTR_TABLE_CELL_COLUMN_SPAN:
350         result += " colspan=" + value;
351         break;
352       case ATTR_TABLE_CELL_ROW_SPAN:
353         result += " rowspan=" + value;
354         break;
355       case ATTR_TABLE_COLUMN_HEADER_ID:
356         result += " column_header_id=" + value;
357         break;
358       case ATTR_TABLE_COLUMN_INDEX:
359         result += " column_index=" + value;
360         break;
361       case ATTR_TABLE_HEADER_ID:
362         result += " header_id=" + value;
363         break;
364       case ATTR_TABLE_ROW_HEADER_ID:
365         result += " row_header_id=" + value;
366         break;
367       case ATTR_TABLE_ROW_INDEX:
368         result += " row_index=" + value;
369         break;
370       case ATTR_TITLE_UI_ELEMENT:
371         result += " title_elem=" + value;
372         break;
373       case ATTR_COLOR_VALUE_RED:
374         result += " color_value_red=" + value;
375         break;
376       case ATTR_COLOR_VALUE_GREEN:
377         result += " color_value_green=" + value;
378         break;
379       case ATTR_COLOR_VALUE_BLUE:
380         result += " color_value_blue=" + value;
381         break;
382       case ATTR_TEXT_DIRECTION:
383         switch (int_attributes[i].second) {
384           case blink::WebAXTextDirectionLR:
385           default:
386             result += " text_direction=lr";
387             break;
388           case blink::WebAXTextDirectionRL:
389             result += " text_direction=rl";
390             break;
391           case blink::WebAXTextDirectionTB:
392             result += " text_direction=tb";
393             break;
394           case blink::WebAXTextDirectionBT:
395             result += " text_direction=bt";
396             break;
397         }
398         break;
399     }
400   }
401 
402   for (size_t i = 0; i < string_attributes.size(); ++i) {
403     std::string value = string_attributes[i].second;
404     switch (string_attributes[i].first) {
405       case ATTR_DOC_URL:
406         result += " doc_url=" + value;
407         break;
408       case ATTR_DOC_TITLE:
409         result += " doc_title=" + value;
410         break;
411       case ATTR_DOC_MIMETYPE:
412         result += " doc_mimetype=" + value;
413         break;
414       case ATTR_DOC_DOCTYPE:
415         result += " doc_doctype=" + value;
416         break;
417       case ATTR_ACCESS_KEY:
418         result += " access_key=" + value;
419         break;
420       case ATTR_ACTION:
421         result += " action=" + value;
422         break;
423       case ATTR_DESCRIPTION:
424         result += " description=" + value;
425         break;
426       case ATTR_DISPLAY:
427         result += " display=" + value;
428         break;
429       case ATTR_HELP:
430         result += " help=" + value;
431         break;
432       case ATTR_HTML_TAG:
433         result += " html_tag=" + value;
434         break;
435       case ATTR_LIVE_RELEVANT:
436         result += " relevant=" + value;
437         break;
438       case ATTR_LIVE_STATUS:
439         result += " live=" + value;
440         break;
441       case ATTR_CONTAINER_LIVE_RELEVANT:
442         result += " container_relevant=" + value;
443         break;
444       case ATTR_CONTAINER_LIVE_STATUS:
445         result += " container_live=" + value;
446         break;
447       case ATTR_ROLE:
448         result += " role=" + value;
449         break;
450       case ATTR_SHORTCUT:
451         result += " shortcut=" + value;
452         break;
453       case ATTR_URL:
454         result += " url=" + value;
455         break;
456       case ATTR_NAME:
457         result += " name=" + value;
458         break;
459       case ATTR_VALUE:
460         result += " value=" + value;
461         break;
462     }
463   }
464 
465   for (size_t i = 0; i < float_attributes.size(); ++i) {
466     std::string value = DoubleToString(float_attributes[i].second);
467     switch (float_attributes[i].first) {
468       case ATTR_DOC_LOADING_PROGRESS:
469         result += " doc_progress=" + value;
470         break;
471       case ATTR_VALUE_FOR_RANGE:
472         result += " value_for_range=" + value;
473         break;
474       case ATTR_MAX_VALUE_FOR_RANGE:
475         result += " max_value=" + value;
476         break;
477       case ATTR_MIN_VALUE_FOR_RANGE:
478         result += " min_value=" + value;
479         break;
480     }
481   }
482 
483   for (size_t i = 0; i < bool_attributes.size(); ++i) {
484     std::string value = bool_attributes[i].second ? "true" : "false";
485     switch (bool_attributes[i].first) {
486       case ATTR_DOC_LOADED:
487         result += " doc_loaded=" + value;
488         break;
489       case ATTR_BUTTON_MIXED:
490         result += " mixed=" + value;
491         break;
492       case ATTR_LIVE_ATOMIC:
493         result += " atomic=" + value;
494         break;
495       case ATTR_LIVE_BUSY:
496         result += " busy=" + value;
497         break;
498       case ATTR_CONTAINER_LIVE_ATOMIC:
499         result += " container_atomic=" + value;
500         break;
501       case ATTR_CONTAINER_LIVE_BUSY:
502         result += " container_busy=" + value;
503         break;
504       case ATTR_ARIA_READONLY:
505         result += " aria_readonly=" + value;
506         break;
507       case ATTR_CAN_SET_VALUE:
508         result += " can_set_value=" + value;
509         break;
510       case ATTR_UPDATE_LOCATION_ONLY:
511         result += " update_location_only=" + value;
512         break;
513       case ATTR_CANVAS_HAS_FALLBACK:
514         result += " has_fallback=" + value;
515         break;
516     }
517   }
518 
519   for (size_t i = 0; i < intlist_attributes.size(); ++i) {
520     const std::vector<int32>& values = intlist_attributes[i].second;
521     switch (intlist_attributes[i].first) {
522       case ATTR_INDIRECT_CHILD_IDS:
523         result += " indirect_child_ids=" + IntVectorToString(values);
524         break;
525       case ATTR_LINE_BREAKS:
526         result += " line_breaks=" + IntVectorToString(values);
527         break;
528       case ATTR_CELL_IDS:
529         result += " cell_ids=" + IntVectorToString(values);
530         break;
531       case ATTR_UNIQUE_CELL_IDS:
532         result += " unique_cell_ids=" + IntVectorToString(values);
533         break;
534       case ATTR_CHARACTER_OFFSETS:
535         result += " character_offsets=" + IntVectorToString(values);
536         break;
537       case ATTR_WORD_STARTS:
538         result += " word_starts=" + IntVectorToString(values);
539         break;
540       case ATTR_WORD_ENDS:
541         result += " word_ends=" + IntVectorToString(values);
542         break;
543     }
544   }
545 
546   if (!child_ids.empty())
547     result += " child_ids=" + IntVectorToString(child_ids);
548 
549   return result;
550 }
551 
DebugString(bool recursive) const552 std::string AccessibilityNodeDataTreeNode::DebugString(bool recursive) const {
553   std::string result;
554 
555   static int indent = 0;
556   result += "\n";
557   for (int i = 0; i < indent; ++i)
558     result += "  ";
559 
560   result += AccessibilityNodeData::DebugString(recursive);
561 
562   if (recursive) {
563     result += "\n";
564     ++indent;
565     for (size_t i = 0; i < children.size(); ++i)
566       result += children[i].DebugString(true);
567     --indent;
568   }
569 
570   return result;
571 }
572 
573 #endif  // ifndef NDEBUG
574 
575 }  // namespace content
576