1 // Copyright 2013 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/ax_node_data.h"
6 
7 #include <stddef.h>
8 
9 #include <algorithm>
10 #include <set>
11 
12 #include "base/no_destructor.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "ui/accessibility/ax_enum_util.h"
19 #include "ui/accessibility/ax_enums.mojom.h"
20 #include "ui/accessibility/ax_role_properties.h"
21 #include "ui/accessibility/ax_text_utils.h"
22 #include "ui/gfx/transform.h"
23 
24 namespace ui {
25 
26 namespace {
27 
IsFlagSet(uint32_t bitfield,uint32_t flag)28 bool IsFlagSet(uint32_t bitfield, uint32_t flag) {
29   return (bitfield & (1U << flag)) != 0;
30 }
31 
IsFlagSet(uint64_t bitfield,uint32_t flag)32 bool IsFlagSet(uint64_t bitfield, uint32_t flag) {
33   return (bitfield & (1ULL << flag)) != 0;
34 }
35 
ModifyFlag(uint32_t bitfield,uint32_t flag,bool set)36 uint32_t ModifyFlag(uint32_t bitfield, uint32_t flag, bool set) {
37   return set ? (bitfield |= (1U << flag)) : (bitfield &= ~(1U << flag));
38 }
39 
ModifyFlag(uint64_t bitfield,uint32_t flag,bool set)40 uint64_t ModifyFlag(uint64_t bitfield, uint32_t flag, bool set) {
41   return set ? (bitfield |= (1ULL << flag)) : (bitfield &= ~(1ULL << flag));
42 }
43 
StateBitfieldToString(uint32_t state_enum)44 std::string StateBitfieldToString(uint32_t state_enum) {
45   std::string str;
46   for (uint32_t i = static_cast<uint32_t>(ax::mojom::State::kNone) + 1;
47        i <= static_cast<uint32_t>(ax::mojom::State::kMaxValue); ++i) {
48     if (IsFlagSet(state_enum, i))
49       str += " " +
50              base::ToUpperASCII(ui::ToString(static_cast<ax::mojom::State>(i)));
51   }
52   return str;
53 }
54 
ActionsBitfieldToString(uint64_t actions)55 std::string ActionsBitfieldToString(uint64_t actions) {
56   std::string str;
57   for (uint32_t i = static_cast<uint32_t>(ax::mojom::Action::kNone) + 1;
58        i <= static_cast<uint32_t>(ax::mojom::Action::kMaxValue); ++i) {
59     if (IsFlagSet(actions, i)) {
60       str += ui::ToString(static_cast<ax::mojom::Action>(i));
61       actions = ModifyFlag(actions, i, false);
62       str += actions ? "," : "";
63     }
64   }
65   return str;
66 }
67 
IntVectorToString(const std::vector<int> & items)68 std::string IntVectorToString(const std::vector<int>& items) {
69   std::string str;
70   for (size_t i = 0; i < items.size(); ++i) {
71     if (i > 0)
72       str += ",";
73     str += base::NumberToString(items[i]);
74   }
75   return str;
76 }
77 
78 // Predicate that returns true if the first value of a pair is |first|.
79 template <typename FirstType, typename SecondType>
80 struct FirstIs {
FirstIsui::__anon4a6c57cf0111::FirstIs81   explicit FirstIs(FirstType first) : first_(first) {}
operator ()ui::__anon4a6c57cf0111::FirstIs82   bool operator()(std::pair<FirstType, SecondType> const& p) {
83     return p.first == first_;
84   }
85   FirstType first_;
86 };
87 
88 // Helper function that finds a key in a vector of pairs by matching on the
89 // first value, and returns an iterator.
90 template <typename FirstType, typename SecondType>
91 typename std::vector<std::pair<FirstType, SecondType>>::const_iterator
FindInVectorOfPairs(FirstType first,const std::vector<std::pair<FirstType,SecondType>> & vector)92 FindInVectorOfPairs(
93     FirstType first,
94     const std::vector<std::pair<FirstType, SecondType>>& vector) {
95   return std::find_if(vector.begin(), vector.end(),
96                       FirstIs<FirstType, SecondType>(first));
97 }
98 
99 }  // namespace
100 
101 // Return true if |attr| is a node ID that would need to be mapped when
102 // renumbering the ids in a combined tree.
IsNodeIdIntAttribute(ax::mojom::IntAttribute attr)103 bool IsNodeIdIntAttribute(ax::mojom::IntAttribute attr) {
104   switch (attr) {
105     case ax::mojom::IntAttribute::kActivedescendantId:
106     case ax::mojom::IntAttribute::kErrormessageId:
107     case ax::mojom::IntAttribute::kInPageLinkTargetId:
108     case ax::mojom::IntAttribute::kMemberOfId:
109     case ax::mojom::IntAttribute::kNextOnLineId:
110     case ax::mojom::IntAttribute::kPopupForId:
111     case ax::mojom::IntAttribute::kPreviousOnLineId:
112     case ax::mojom::IntAttribute::kTableHeaderId:
113     case ax::mojom::IntAttribute::kTableColumnHeaderId:
114     case ax::mojom::IntAttribute::kTableRowHeaderId:
115     case ax::mojom::IntAttribute::kNextFocusId:
116     case ax::mojom::IntAttribute::kPreviousFocusId:
117       return true;
118 
119     // Note: all of the attributes are included here explicitly,
120     // rather than using "default:", so that it's a compiler error to
121     // add a new attribute without explicitly considering whether it's
122     // a node id attribute or not.
123     case ax::mojom::IntAttribute::kNone:
124     case ax::mojom::IntAttribute::kDefaultActionVerb:
125     case ax::mojom::IntAttribute::kScrollX:
126     case ax::mojom::IntAttribute::kScrollXMin:
127     case ax::mojom::IntAttribute::kScrollXMax:
128     case ax::mojom::IntAttribute::kScrollY:
129     case ax::mojom::IntAttribute::kScrollYMin:
130     case ax::mojom::IntAttribute::kScrollYMax:
131     case ax::mojom::IntAttribute::kTextSelStart:
132     case ax::mojom::IntAttribute::kTextSelEnd:
133     case ax::mojom::IntAttribute::kTableRowCount:
134     case ax::mojom::IntAttribute::kTableColumnCount:
135     case ax::mojom::IntAttribute::kTableRowIndex:
136     case ax::mojom::IntAttribute::kTableColumnIndex:
137     case ax::mojom::IntAttribute::kTableCellColumnIndex:
138     case ax::mojom::IntAttribute::kTableCellColumnSpan:
139     case ax::mojom::IntAttribute::kTableCellRowIndex:
140     case ax::mojom::IntAttribute::kTableCellRowSpan:
141     case ax::mojom::IntAttribute::kSortDirection:
142     case ax::mojom::IntAttribute::kHierarchicalLevel:
143     case ax::mojom::IntAttribute::kNameFrom:
144     case ax::mojom::IntAttribute::kDescriptionFrom:
145     case ax::mojom::IntAttribute::kSetSize:
146     case ax::mojom::IntAttribute::kPosInSet:
147     case ax::mojom::IntAttribute::kColorValue:
148     case ax::mojom::IntAttribute::kAriaCurrentState:
149     case ax::mojom::IntAttribute::kHasPopup:
150     case ax::mojom::IntAttribute::kBackgroundColor:
151     case ax::mojom::IntAttribute::kColor:
152     case ax::mojom::IntAttribute::kInvalidState:
153     case ax::mojom::IntAttribute::kCheckedState:
154     case ax::mojom::IntAttribute::kRestriction:
155     case ax::mojom::IntAttribute::kListStyle:
156     case ax::mojom::IntAttribute::kTextDirection:
157     case ax::mojom::IntAttribute::kTextPosition:
158     case ax::mojom::IntAttribute::kTextStyle:
159     case ax::mojom::IntAttribute::kTextOverlineStyle:
160     case ax::mojom::IntAttribute::kTextStrikethroughStyle:
161     case ax::mojom::IntAttribute::kTextUnderlineStyle:
162     case ax::mojom::IntAttribute::kAriaColumnCount:
163     case ax::mojom::IntAttribute::kAriaCellColumnIndex:
164     case ax::mojom::IntAttribute::kAriaCellColumnSpan:
165     case ax::mojom::IntAttribute::kAriaRowCount:
166     case ax::mojom::IntAttribute::kAriaCellRowIndex:
167     case ax::mojom::IntAttribute::kAriaCellRowSpan:
168     case ax::mojom::IntAttribute::kImageAnnotationStatus:
169     case ax::mojom::IntAttribute::kDropeffect:
170     case ax::mojom::IntAttribute::kDOMNodeId:
171       return false;
172   }
173 
174   NOTREACHED();
175   return false;
176 }
177 
178 // Return true if |attr| contains a vector of node ids that would need
179 // to be mapped when renumbering the ids in a combined tree.
IsNodeIdIntListAttribute(ax::mojom::IntListAttribute attr)180 bool IsNodeIdIntListAttribute(ax::mojom::IntListAttribute attr) {
181   switch (attr) {
182     case ax::mojom::IntListAttribute::kControlsIds:
183     case ax::mojom::IntListAttribute::kDetailsIds:
184     case ax::mojom::IntListAttribute::kDescribedbyIds:
185     case ax::mojom::IntListAttribute::kFlowtoIds:
186     case ax::mojom::IntListAttribute::kIndirectChildIds:
187     case ax::mojom::IntListAttribute::kLabelledbyIds:
188     case ax::mojom::IntListAttribute::kRadioGroupIds:
189       return true;
190 
191     // Note: all of the attributes are included here explicitly,
192     // rather than using "default:", so that it's a compiler error to
193     // add a new attribute without explicitly considering whether it's
194     // a node id attribute or not.
195     case ax::mojom::IntListAttribute::kNone:
196     case ax::mojom::IntListAttribute::kMarkerTypes:
197     case ax::mojom::IntListAttribute::kMarkerStarts:
198     case ax::mojom::IntListAttribute::kMarkerEnds:
199     case ax::mojom::IntListAttribute::kCharacterOffsets:
200     case ax::mojom::IntListAttribute::kCachedLineStarts:
201     case ax::mojom::IntListAttribute::kWordStarts:
202     case ax::mojom::IntListAttribute::kWordEnds:
203     case ax::mojom::IntListAttribute::kCustomActionIds:
204       return false;
205   }
206 
207   NOTREACHED();
208   return false;
209 }
210 
AXNodeData()211 AXNodeData::AXNodeData()
212     : role(ax::mojom::Role::kUnknown), state(0U), actions(0ULL) {}
213 
214 AXNodeData::~AXNodeData() = default;
215 
AXNodeData(const AXNodeData & other)216 AXNodeData::AXNodeData(const AXNodeData& other) {
217   id = other.id;
218   role = other.role;
219   state = other.state;
220   actions = other.actions;
221   string_attributes = other.string_attributes;
222   int_attributes = other.int_attributes;
223   float_attributes = other.float_attributes;
224   bool_attributes = other.bool_attributes;
225   intlist_attributes = other.intlist_attributes;
226   stringlist_attributes = other.stringlist_attributes;
227   html_attributes = other.html_attributes;
228   child_ids = other.child_ids;
229   relative_bounds = other.relative_bounds;
230 }
231 
AXNodeData(AXNodeData && other)232 AXNodeData::AXNodeData(AXNodeData&& other) {
233   id = other.id;
234   role = other.role;
235   state = other.state;
236   actions = other.actions;
237   string_attributes.swap(other.string_attributes);
238   int_attributes.swap(other.int_attributes);
239   float_attributes.swap(other.float_attributes);
240   bool_attributes.swap(other.bool_attributes);
241   intlist_attributes.swap(other.intlist_attributes);
242   stringlist_attributes.swap(other.stringlist_attributes);
243   html_attributes.swap(other.html_attributes);
244   child_ids.swap(other.child_ids);
245   relative_bounds = other.relative_bounds;
246 }
247 
operator =(AXNodeData other)248 AXNodeData& AXNodeData::operator=(AXNodeData other) {
249   id = other.id;
250   role = other.role;
251   state = other.state;
252   actions = other.actions;
253   string_attributes = other.string_attributes;
254   int_attributes = other.int_attributes;
255   float_attributes = other.float_attributes;
256   bool_attributes = other.bool_attributes;
257   intlist_attributes = other.intlist_attributes;
258   stringlist_attributes = other.stringlist_attributes;
259   html_attributes = other.html_attributes;
260   child_ids = other.child_ids;
261   relative_bounds = other.relative_bounds;
262   return *this;
263 }
264 
HasBoolAttribute(ax::mojom::BoolAttribute attribute) const265 bool AXNodeData::HasBoolAttribute(ax::mojom::BoolAttribute attribute) const {
266   auto iter = FindInVectorOfPairs(attribute, bool_attributes);
267   return iter != bool_attributes.end();
268 }
269 
GetBoolAttribute(ax::mojom::BoolAttribute attribute) const270 bool AXNodeData::GetBoolAttribute(ax::mojom::BoolAttribute attribute) const {
271   bool result;
272   if (GetBoolAttribute(attribute, &result))
273     return result;
274   return false;
275 }
276 
GetBoolAttribute(ax::mojom::BoolAttribute attribute,bool * value) const277 bool AXNodeData::GetBoolAttribute(ax::mojom::BoolAttribute attribute,
278                                   bool* value) const {
279   auto iter = FindInVectorOfPairs(attribute, bool_attributes);
280   if (iter != bool_attributes.end()) {
281     *value = iter->second;
282     return true;
283   }
284 
285   return false;
286 }
287 
HasFloatAttribute(ax::mojom::FloatAttribute attribute) const288 bool AXNodeData::HasFloatAttribute(ax::mojom::FloatAttribute attribute) const {
289   auto iter = FindInVectorOfPairs(attribute, float_attributes);
290   return iter != float_attributes.end();
291 }
292 
GetFloatAttribute(ax::mojom::FloatAttribute attribute) const293 float AXNodeData::GetFloatAttribute(ax::mojom::FloatAttribute attribute) const {
294   float result;
295   if (GetFloatAttribute(attribute, &result))
296     return result;
297   return 0.0;
298 }
299 
GetFloatAttribute(ax::mojom::FloatAttribute attribute,float * value) const300 bool AXNodeData::GetFloatAttribute(ax::mojom::FloatAttribute attribute,
301                                    float* value) const {
302   auto iter = FindInVectorOfPairs(attribute, float_attributes);
303   if (iter != float_attributes.end()) {
304     *value = iter->second;
305     return true;
306   }
307 
308   return false;
309 }
310 
HasIntAttribute(ax::mojom::IntAttribute attribute) const311 bool AXNodeData::HasIntAttribute(ax::mojom::IntAttribute attribute) const {
312   auto iter = FindInVectorOfPairs(attribute, int_attributes);
313   return iter != int_attributes.end();
314 }
315 
GetIntAttribute(ax::mojom::IntAttribute attribute) const316 int AXNodeData::GetIntAttribute(ax::mojom::IntAttribute attribute) const {
317   int result;
318   if (GetIntAttribute(attribute, &result))
319     return result;
320   return 0;
321 }
322 
GetIntAttribute(ax::mojom::IntAttribute attribute,int * value) const323 bool AXNodeData::GetIntAttribute(ax::mojom::IntAttribute attribute,
324                                  int* value) const {
325   auto iter = FindInVectorOfPairs(attribute, int_attributes);
326   if (iter != int_attributes.end()) {
327     *value = int{iter->second};
328     return true;
329   }
330 
331   return false;
332 }
333 
HasStringAttribute(ax::mojom::StringAttribute attribute) const334 bool AXNodeData::HasStringAttribute(
335     ax::mojom::StringAttribute attribute) const {
336   auto iter = FindInVectorOfPairs(attribute, string_attributes);
337   return iter != string_attributes.end();
338 }
339 
GetStringAttribute(ax::mojom::StringAttribute attribute) const340 const std::string& AXNodeData::GetStringAttribute(
341     ax::mojom::StringAttribute attribute) const {
342   auto iter = FindInVectorOfPairs(attribute, string_attributes);
343   return iter != string_attributes.end() ? iter->second : base::EmptyString();
344 }
345 
GetStringAttribute(ax::mojom::StringAttribute attribute,std::string * value) const346 bool AXNodeData::GetStringAttribute(ax::mojom::StringAttribute attribute,
347                                     std::string* value) const {
348   auto iter = FindInVectorOfPairs(attribute, string_attributes);
349   if (iter != string_attributes.end()) {
350     *value = iter->second;
351     return true;
352   }
353 
354   return false;
355 }
356 
GetString16Attribute(ax::mojom::StringAttribute attribute) const357 base::string16 AXNodeData::GetString16Attribute(
358     ax::mojom::StringAttribute attribute) const {
359   std::string value_utf8;
360   if (!GetStringAttribute(attribute, &value_utf8))
361     return base::string16();
362   return base::UTF8ToUTF16(value_utf8);
363 }
364 
GetString16Attribute(ax::mojom::StringAttribute attribute,base::string16 * value) const365 bool AXNodeData::GetString16Attribute(ax::mojom::StringAttribute attribute,
366                                       base::string16* value) const {
367   std::string value_utf8;
368   if (!GetStringAttribute(attribute, &value_utf8))
369     return false;
370   *value = base::UTF8ToUTF16(value_utf8);
371   return true;
372 }
373 
HasIntListAttribute(ax::mojom::IntListAttribute attribute) const374 bool AXNodeData::HasIntListAttribute(
375     ax::mojom::IntListAttribute attribute) const {
376   auto iter = FindInVectorOfPairs(attribute, intlist_attributes);
377   return iter != intlist_attributes.end();
378 }
379 
GetIntListAttribute(ax::mojom::IntListAttribute attribute) const380 const std::vector<int32_t>& AXNodeData::GetIntListAttribute(
381     ax::mojom::IntListAttribute attribute) const {
382   static const base::NoDestructor<std::vector<int32_t>> empty_vector;
383   auto iter = FindInVectorOfPairs(attribute, intlist_attributes);
384   if (iter != intlist_attributes.end())
385     return iter->second;
386   return *empty_vector;
387 }
388 
GetIntListAttribute(ax::mojom::IntListAttribute attribute,std::vector<int32_t> * value) const389 bool AXNodeData::GetIntListAttribute(ax::mojom::IntListAttribute attribute,
390                                      std::vector<int32_t>* value) const {
391   auto iter = FindInVectorOfPairs(attribute, intlist_attributes);
392   if (iter != intlist_attributes.end()) {
393     *value = iter->second;
394     return true;
395   }
396 
397   return false;
398 }
399 
HasStringListAttribute(ax::mojom::StringListAttribute attribute) const400 bool AXNodeData::HasStringListAttribute(
401     ax::mojom::StringListAttribute attribute) const {
402   auto iter = FindInVectorOfPairs(attribute, stringlist_attributes);
403   return iter != stringlist_attributes.end();
404 }
405 
GetStringListAttribute(ax::mojom::StringListAttribute attribute) const406 const std::vector<std::string>& AXNodeData::GetStringListAttribute(
407     ax::mojom::StringListAttribute attribute) const {
408   static const base::NoDestructor<std::vector<std::string>> empty_vector;
409   auto iter = FindInVectorOfPairs(attribute, stringlist_attributes);
410   if (iter != stringlist_attributes.end())
411     return iter->second;
412   return *empty_vector;
413 }
414 
GetStringListAttribute(ax::mojom::StringListAttribute attribute,std::vector<std::string> * value) const415 bool AXNodeData::GetStringListAttribute(
416     ax::mojom::StringListAttribute attribute,
417     std::vector<std::string>* value) const {
418   auto iter = FindInVectorOfPairs(attribute, stringlist_attributes);
419   if (iter != stringlist_attributes.end()) {
420     *value = iter->second;
421     return true;
422   }
423 
424   return false;
425 }
426 
GetHtmlAttribute(const char * html_attr,std::string * value) const427 bool AXNodeData::GetHtmlAttribute(const char* html_attr,
428                                   std::string* value) const {
429   for (const std::pair<std::string, std::string>& html_attribute :
430        html_attributes) {
431     const std::string& attr = html_attribute.first;
432     if (base::LowerCaseEqualsASCII(attr, html_attr)) {
433       *value = html_attribute.second;
434       return true;
435     }
436   }
437 
438   return false;
439 }
440 
GetHtmlAttribute(const char * html_attr,base::string16 * value) const441 bool AXNodeData::GetHtmlAttribute(const char* html_attr,
442                                   base::string16* value) const {
443   std::string value_utf8;
444   if (!GetHtmlAttribute(html_attr, &value_utf8))
445     return false;
446   *value = base::UTF8ToUTF16(value_utf8);
447   return true;
448 }
449 
AddStringAttribute(ax::mojom::StringAttribute attribute,const std::string & value)450 void AXNodeData::AddStringAttribute(ax::mojom::StringAttribute attribute,
451                                     const std::string& value) {
452   DCHECK_NE(attribute, ax::mojom::StringAttribute::kNone);
453   if (HasStringAttribute(attribute))
454     RemoveStringAttribute(attribute);
455   string_attributes.push_back(std::make_pair(attribute, value));
456 }
457 
AddIntAttribute(ax::mojom::IntAttribute attribute,int value)458 void AXNodeData::AddIntAttribute(ax::mojom::IntAttribute attribute, int value) {
459   DCHECK_NE(attribute, ax::mojom::IntAttribute::kNone);
460   if (HasIntAttribute(attribute))
461     RemoveIntAttribute(attribute);
462   int_attributes.push_back(std::make_pair(attribute, value));
463 }
464 
AddFloatAttribute(ax::mojom::FloatAttribute attribute,float value)465 void AXNodeData::AddFloatAttribute(ax::mojom::FloatAttribute attribute,
466                                    float value) {
467   DCHECK_NE(attribute, ax::mojom::FloatAttribute::kNone);
468   if (HasFloatAttribute(attribute))
469     RemoveFloatAttribute(attribute);
470   float_attributes.push_back(std::make_pair(attribute, value));
471 }
472 
AddBoolAttribute(ax::mojom::BoolAttribute attribute,bool value)473 void AXNodeData::AddBoolAttribute(ax::mojom::BoolAttribute attribute,
474                                   bool value) {
475   DCHECK_NE(attribute, ax::mojom::BoolAttribute::kNone);
476   if (HasBoolAttribute(attribute))
477     RemoveBoolAttribute(attribute);
478   bool_attributes.push_back(std::make_pair(attribute, value));
479 }
480 
AddIntListAttribute(ax::mojom::IntListAttribute attribute,const std::vector<int32_t> & value)481 void AXNodeData::AddIntListAttribute(ax::mojom::IntListAttribute attribute,
482                                      const std::vector<int32_t>& value) {
483   DCHECK_NE(attribute, ax::mojom::IntListAttribute::kNone);
484   if (HasIntListAttribute(attribute))
485     RemoveIntListAttribute(attribute);
486   intlist_attributes.push_back(std::make_pair(attribute, value));
487 }
488 
AddStringListAttribute(ax::mojom::StringListAttribute attribute,const std::vector<std::string> & value)489 void AXNodeData::AddStringListAttribute(
490     ax::mojom::StringListAttribute attribute,
491     const std::vector<std::string>& value) {
492   DCHECK_NE(attribute, ax::mojom::StringListAttribute::kNone);
493   if (HasStringListAttribute(attribute))
494     RemoveStringListAttribute(attribute);
495   stringlist_attributes.push_back(std::make_pair(attribute, value));
496 }
497 
RemoveStringAttribute(ax::mojom::StringAttribute attribute)498 void AXNodeData::RemoveStringAttribute(ax::mojom::StringAttribute attribute) {
499   DCHECK_NE(attribute, ax::mojom::StringAttribute::kNone);
500   base::EraseIf(string_attributes, [attribute](const auto& string_attribute) {
501     return string_attribute.first == attribute;
502   });
503 }
504 
RemoveIntAttribute(ax::mojom::IntAttribute attribute)505 void AXNodeData::RemoveIntAttribute(ax::mojom::IntAttribute attribute) {
506   DCHECK_NE(attribute, ax::mojom::IntAttribute::kNone);
507   base::EraseIf(int_attributes, [attribute](const auto& int_attribute) {
508     return int_attribute.first == attribute;
509   });
510 }
511 
RemoveFloatAttribute(ax::mojom::FloatAttribute attribute)512 void AXNodeData::RemoveFloatAttribute(ax::mojom::FloatAttribute attribute) {
513   DCHECK_NE(attribute, ax::mojom::FloatAttribute::kNone);
514   base::EraseIf(float_attributes, [attribute](const auto& float_attribute) {
515     return float_attribute.first == attribute;
516   });
517 }
518 
RemoveBoolAttribute(ax::mojom::BoolAttribute attribute)519 void AXNodeData::RemoveBoolAttribute(ax::mojom::BoolAttribute attribute) {
520   DCHECK_NE(attribute, ax::mojom::BoolAttribute::kNone);
521   base::EraseIf(bool_attributes, [attribute](const auto& bool_attribute) {
522     return bool_attribute.first == attribute;
523   });
524 }
525 
RemoveIntListAttribute(ax::mojom::IntListAttribute attribute)526 void AXNodeData::RemoveIntListAttribute(ax::mojom::IntListAttribute attribute) {
527   DCHECK_NE(attribute, ax::mojom::IntListAttribute::kNone);
528   base::EraseIf(intlist_attributes, [attribute](const auto& intlist_attribute) {
529     return intlist_attribute.first == attribute;
530   });
531 }
532 
RemoveStringListAttribute(ax::mojom::StringListAttribute attribute)533 void AXNodeData::RemoveStringListAttribute(
534     ax::mojom::StringListAttribute attribute) {
535   DCHECK_NE(attribute, ax::mojom::StringListAttribute::kNone);
536   base::EraseIf(stringlist_attributes,
537                 [attribute](const auto& stringlist_attribute) {
538                   return stringlist_attribute.first == attribute;
539                 });
540 }
541 
GetTextStyles() const542 AXNodeTextStyles AXNodeData::GetTextStyles() const {
543   AXNodeTextStyles style_attributes;
544 
545   GetIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
546                   &style_attributes.background_color);
547   GetIntAttribute(ax::mojom::IntAttribute::kColor, &style_attributes.color);
548   GetIntAttribute(ax::mojom::IntAttribute::kInvalidState,
549                   &style_attributes.invalid_state);
550   GetIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle,
551                   &style_attributes.overline_style);
552   GetIntAttribute(ax::mojom::IntAttribute::kTextDirection,
553                   &style_attributes.text_direction);
554   GetIntAttribute(ax::mojom::IntAttribute::kTextPosition,
555                   &style_attributes.text_position);
556   GetIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle,
557                   &style_attributes.strikethrough_style);
558   GetIntAttribute(ax::mojom::IntAttribute::kTextStyle,
559                   &style_attributes.text_style);
560   GetIntAttribute(ax::mojom::IntAttribute::kTextUnderlineStyle,
561                   &style_attributes.underline_style);
562   GetFloatAttribute(ax::mojom::FloatAttribute::kFontSize,
563                     &style_attributes.font_size);
564   GetFloatAttribute(ax::mojom::FloatAttribute::kFontWeight,
565                     &style_attributes.font_weight);
566   GetStringAttribute(ax::mojom::StringAttribute::kFontFamily,
567                      &style_attributes.font_family);
568 
569   return style_attributes;
570 }
571 
SetName(const std::string & name)572 void AXNodeData::SetName(const std::string& name) {
573   auto iter = std::find_if(string_attributes.begin(), string_attributes.end(),
574                            [](const auto& string_attribute) {
575                              return string_attribute.first ==
576                                     ax::mojom::StringAttribute::kName;
577                            });
578   if (iter == string_attributes.end()) {
579     string_attributes.push_back(
580         std::make_pair(ax::mojom::StringAttribute::kName, name));
581   } else {
582     iter->second = name;
583   }
584 }
585 
SetName(const base::string16 & name)586 void AXNodeData::SetName(const base::string16& name) {
587   SetName(base::UTF16ToUTF8(name));
588 }
589 
SetNameExplicitlyEmpty()590 void AXNodeData::SetNameExplicitlyEmpty() {
591   SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
592 }
593 
SetDescription(const std::string & description)594 void AXNodeData::SetDescription(const std::string& description) {
595   AddStringAttribute(ax::mojom::StringAttribute::kDescription, description);
596 }
597 
SetDescription(const base::string16 & description)598 void AXNodeData::SetDescription(const base::string16& description) {
599   SetDescription(base::UTF16ToUTF8(description));
600 }
601 
SetValue(const std::string & value)602 void AXNodeData::SetValue(const std::string& value) {
603   AddStringAttribute(ax::mojom::StringAttribute::kValue, value);
604 }
605 
SetValue(const base::string16 & value)606 void AXNodeData::SetValue(const base::string16& value) {
607   SetValue(base::UTF16ToUTF8(value));
608 }
609 
HasState(ax::mojom::State state_enum) const610 bool AXNodeData::HasState(ax::mojom::State state_enum) const {
611   return IsFlagSet(state, static_cast<uint32_t>(state_enum));
612 }
613 
HasAction(ax::mojom::Action action) const614 bool AXNodeData::HasAction(ax::mojom::Action action) const {
615   return IsFlagSet(actions, static_cast<uint32_t>(action));
616 }
617 
HasTextStyle(ax::mojom::TextStyle text_style_enum) const618 bool AXNodeData::HasTextStyle(ax::mojom::TextStyle text_style_enum) const {
619   int32_t style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle);
620   return IsFlagSet(static_cast<uint32_t>(style),
621                    static_cast<uint32_t>(text_style_enum));
622 }
623 
HasDropeffect(ax::mojom::Dropeffect dropeffect_enum) const624 bool AXNodeData::HasDropeffect(ax::mojom::Dropeffect dropeffect_enum) const {
625   int32_t dropeffect = GetIntAttribute(ax::mojom::IntAttribute::kDropeffect);
626   return IsFlagSet(static_cast<uint32_t>(dropeffect),
627                    static_cast<uint32_t>(dropeffect_enum));
628 }
629 
AddState(ax::mojom::State state_enum)630 void AXNodeData::AddState(ax::mojom::State state_enum) {
631   DCHECK_GT(static_cast<int>(state_enum),
632             static_cast<int>(ax::mojom::State::kNone));
633   DCHECK_LE(static_cast<int>(state_enum),
634             static_cast<int>(ax::mojom::State::kMaxValue));
635   state = ModifyFlag(state, static_cast<uint32_t>(state_enum), true);
636 }
637 
RemoveState(ax::mojom::State state_enum)638 void AXNodeData::RemoveState(ax::mojom::State state_enum) {
639   DCHECK_GT(static_cast<int>(state_enum),
640             static_cast<int>(ax::mojom::State::kNone));
641   DCHECK_LE(static_cast<int>(state_enum),
642             static_cast<int>(ax::mojom::State::kMaxValue));
643   state = ModifyFlag(state, static_cast<uint32_t>(state_enum), false);
644 }
645 
AddAction(ax::mojom::Action action_enum)646 void AXNodeData::AddAction(ax::mojom::Action action_enum) {
647   switch (action_enum) {
648     case ax::mojom::Action::kNone:
649       NOTREACHED();
650       break;
651 
652     // Note: all of the attributes are included here explicitly, rather than
653     // using "default:", so that it's a compiler error to add a new action
654     // without explicitly considering whether there are mutually exclusive
655     // actions that can be performed on a UI control at the same time.
656     case ax::mojom::Action::kBlur:
657     case ax::mojom::Action::kFocus: {
658       ax::mojom::Action excluded_action =
659           (action_enum == ax::mojom::Action::kBlur) ? ax::mojom::Action::kFocus
660                                                     : ax::mojom::Action::kBlur;
661       DCHECK(!HasAction(excluded_action)) << excluded_action;
662       break;
663     }
664 
665     case ax::mojom::Action::kClearAccessibilityFocus:
666     case ax::mojom::Action::kCollapse:
667     case ax::mojom::Action::kCustomAction:
668     case ax::mojom::Action::kDecrement:
669     case ax::mojom::Action::kDoDefault:
670     case ax::mojom::Action::kExpand:
671     case ax::mojom::Action::kGetImageData:
672     case ax::mojom::Action::kHitTest:
673     case ax::mojom::Action::kIncrement:
674     case ax::mojom::Action::kInternalInvalidateTree:
675     case ax::mojom::Action::kLoadInlineTextBoxes:
676     case ax::mojom::Action::kReplaceSelectedText:
677     case ax::mojom::Action::kScrollToMakeVisible:
678     case ax::mojom::Action::kScrollToPoint:
679     case ax::mojom::Action::kSetAccessibilityFocus:
680     case ax::mojom::Action::kSetScrollOffset:
681     case ax::mojom::Action::kSetSelection:
682     case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint:
683     case ax::mojom::Action::kSetValue:
684     case ax::mojom::Action::kShowContextMenu:
685     case ax::mojom::Action::kScrollBackward:
686     case ax::mojom::Action::kScrollForward:
687     case ax::mojom::Action::kScrollUp:
688     case ax::mojom::Action::kScrollDown:
689     case ax::mojom::Action::kScrollLeft:
690     case ax::mojom::Action::kScrollRight:
691     case ax::mojom::Action::kGetTextLocation:
692     case ax::mojom::Action::kAnnotatePageImages:
693     case ax::mojom::Action::kSignalEndOfTest:
694     case ax::mojom::Action::kHideTooltip:
695     case ax::mojom::Action::kShowTooltip:
696       break;
697   }
698 
699   actions = ModifyFlag(actions, static_cast<uint32_t>(action_enum), true);
700 }
701 
AddTextStyle(ax::mojom::TextStyle text_style_enum)702 void AXNodeData::AddTextStyle(ax::mojom::TextStyle text_style_enum) {
703   DCHECK_GE(static_cast<int>(text_style_enum),
704             static_cast<int>(ax::mojom::TextStyle::kMinValue));
705   DCHECK_LE(static_cast<int>(text_style_enum),
706             static_cast<int>(ax::mojom::TextStyle::kMaxValue));
707   int32_t style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle);
708   style = ModifyFlag(static_cast<uint32_t>(style),
709                      static_cast<uint32_t>(text_style_enum), true);
710   RemoveIntAttribute(ax::mojom::IntAttribute::kTextStyle);
711   AddIntAttribute(ax::mojom::IntAttribute::kTextStyle, style);
712 }
713 
AddDropeffect(ax::mojom::Dropeffect dropeffect_enum)714 void AXNodeData::AddDropeffect(ax::mojom::Dropeffect dropeffect_enum) {
715   DCHECK_GE(static_cast<int>(dropeffect_enum),
716             static_cast<int>(ax::mojom::Dropeffect::kMinValue));
717   DCHECK_LE(static_cast<int>(dropeffect_enum),
718             static_cast<int>(ax::mojom::Dropeffect::kMaxValue));
719   int32_t dropeffect = GetIntAttribute(ax::mojom::IntAttribute::kDropeffect);
720   dropeffect = ModifyFlag(static_cast<uint32_t>(dropeffect),
721                           static_cast<uint32_t>(dropeffect_enum), true);
722   RemoveIntAttribute(ax::mojom::IntAttribute::kDropeffect);
723   AddIntAttribute(ax::mojom::IntAttribute::kDropeffect, dropeffect);
724 }
725 
GetCheckedState() const726 ax::mojom::CheckedState AXNodeData::GetCheckedState() const {
727   return static_cast<ax::mojom::CheckedState>(
728       GetIntAttribute(ax::mojom::IntAttribute::kCheckedState));
729 }
730 
SetCheckedState(ax::mojom::CheckedState checked_state)731 void AXNodeData::SetCheckedState(ax::mojom::CheckedState checked_state) {
732   if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
733     RemoveIntAttribute(ax::mojom::IntAttribute::kCheckedState);
734   if (checked_state != ax::mojom::CheckedState::kNone) {
735     AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
736                     static_cast<int32_t>(checked_state));
737   }
738 }
739 
GetDefaultActionVerb() const740 ax::mojom::DefaultActionVerb AXNodeData::GetDefaultActionVerb() const {
741   return static_cast<ax::mojom::DefaultActionVerb>(
742       GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb));
743 }
744 
SetDefaultActionVerb(ax::mojom::DefaultActionVerb default_action_verb)745 void AXNodeData::SetDefaultActionVerb(
746     ax::mojom::DefaultActionVerb default_action_verb) {
747   if (HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb))
748     RemoveIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb);
749   if (default_action_verb != ax::mojom::DefaultActionVerb::kNone) {
750     AddIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
751                     static_cast<int32_t>(default_action_verb));
752   }
753 }
754 
GetHasPopup() const755 ax::mojom::HasPopup AXNodeData::GetHasPopup() const {
756   return static_cast<ax::mojom::HasPopup>(
757       GetIntAttribute(ax::mojom::IntAttribute::kHasPopup));
758 }
759 
SetHasPopup(ax::mojom::HasPopup has_popup)760 void AXNodeData::SetHasPopup(ax::mojom::HasPopup has_popup) {
761   if (HasIntAttribute(ax::mojom::IntAttribute::kHasPopup))
762     RemoveIntAttribute(ax::mojom::IntAttribute::kHasPopup);
763   if (has_popup != ax::mojom::HasPopup::kFalse) {
764     AddIntAttribute(ax::mojom::IntAttribute::kHasPopup,
765                     static_cast<int32_t>(has_popup));
766   }
767 }
768 
GetInvalidState() const769 ax::mojom::InvalidState AXNodeData::GetInvalidState() const {
770   return static_cast<ax::mojom::InvalidState>(
771       GetIntAttribute(ax::mojom::IntAttribute::kInvalidState));
772 }
773 
SetInvalidState(ax::mojom::InvalidState invalid_state)774 void AXNodeData::SetInvalidState(ax::mojom::InvalidState invalid_state) {
775   if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState))
776     RemoveIntAttribute(ax::mojom::IntAttribute::kInvalidState);
777   if (invalid_state != ax::mojom::InvalidState::kNone) {
778     AddIntAttribute(ax::mojom::IntAttribute::kInvalidState,
779                     static_cast<int32_t>(invalid_state));
780   }
781 }
782 
GetNameFrom() const783 ax::mojom::NameFrom AXNodeData::GetNameFrom() const {
784   return static_cast<ax::mojom::NameFrom>(
785       GetIntAttribute(ax::mojom::IntAttribute::kNameFrom));
786 }
787 
SetNameFrom(ax::mojom::NameFrom name_from)788 void AXNodeData::SetNameFrom(ax::mojom::NameFrom name_from) {
789   if (HasIntAttribute(ax::mojom::IntAttribute::kNameFrom))
790     RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom);
791   if (name_from != ax::mojom::NameFrom::kNone) {
792     AddIntAttribute(ax::mojom::IntAttribute::kNameFrom,
793                     static_cast<int32_t>(name_from));
794   }
795 }
796 
GetDescriptionFrom() const797 ax::mojom::DescriptionFrom AXNodeData::GetDescriptionFrom() const {
798   return static_cast<ax::mojom::DescriptionFrom>(
799       GetIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom));
800 }
801 
SetDescriptionFrom(ax::mojom::DescriptionFrom description_from)802 void AXNodeData::SetDescriptionFrom(
803     ax::mojom::DescriptionFrom description_from) {
804   if (HasIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom))
805     RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom);
806   if (description_from != ax::mojom::DescriptionFrom::kNone) {
807     AddIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom,
808                     static_cast<int32_t>(description_from));
809   }
810 }
811 
GetTextPosition() const812 ax::mojom::TextPosition AXNodeData::GetTextPosition() const {
813   return static_cast<ax::mojom::TextPosition>(
814       GetIntAttribute(ax::mojom::IntAttribute::kTextPosition));
815 }
816 
SetTextPosition(ax::mojom::TextPosition text_position)817 void AXNodeData::SetTextPosition(ax::mojom::TextPosition text_position) {
818   if (HasIntAttribute(ax::mojom::IntAttribute::kTextPosition))
819     RemoveIntAttribute(ax::mojom::IntAttribute::kTextPosition);
820   if (text_position != ax::mojom::TextPosition::kNone) {
821     AddIntAttribute(ax::mojom::IntAttribute::kTextPosition,
822                     static_cast<int32_t>(text_position));
823   }
824 }
825 
GetImageAnnotationStatus() const826 ax::mojom::ImageAnnotationStatus AXNodeData::GetImageAnnotationStatus() const {
827   return static_cast<ax::mojom::ImageAnnotationStatus>(
828       GetIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus));
829 }
830 
SetImageAnnotationStatus(ax::mojom::ImageAnnotationStatus status)831 void AXNodeData::SetImageAnnotationStatus(
832     ax::mojom::ImageAnnotationStatus status) {
833   if (HasIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus))
834     RemoveIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus);
835   if (status != ax::mojom::ImageAnnotationStatus::kNone) {
836     AddIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus,
837                     static_cast<int32_t>(status));
838   }
839 }
840 
GetRestriction() const841 ax::mojom::Restriction AXNodeData::GetRestriction() const {
842   return static_cast<ax::mojom::Restriction>(
843       GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
844 }
845 
SetRestriction(ax::mojom::Restriction restriction)846 void AXNodeData::SetRestriction(ax::mojom::Restriction restriction) {
847   if (HasIntAttribute(ax::mojom::IntAttribute::kRestriction))
848     RemoveIntAttribute(ax::mojom::IntAttribute::kRestriction);
849   if (restriction != ax::mojom::Restriction::kNone) {
850     AddIntAttribute(ax::mojom::IntAttribute::kRestriction,
851                     static_cast<int32_t>(restriction));
852   }
853 }
854 
GetListStyle() const855 ax::mojom::ListStyle AXNodeData::GetListStyle() const {
856   return static_cast<ax::mojom::ListStyle>(
857       GetIntAttribute(ax::mojom::IntAttribute::kListStyle));
858 }
859 
SetListStyle(ax::mojom::ListStyle list_style)860 void AXNodeData::SetListStyle(ax::mojom::ListStyle list_style) {
861   if (HasIntAttribute(ax::mojom::IntAttribute::kListStyle))
862     RemoveIntAttribute(ax::mojom::IntAttribute::kListStyle);
863   if (list_style != ax::mojom::ListStyle::kNone) {
864     AddIntAttribute(ax::mojom::IntAttribute::kListStyle,
865                     static_cast<int32_t>(list_style));
866   }
867 }
868 
GetTextDirection() const869 ax::mojom::TextDirection AXNodeData::GetTextDirection() const {
870   return static_cast<ax::mojom::TextDirection>(
871       GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
872 }
873 
SetTextDirection(ax::mojom::TextDirection text_direction)874 void AXNodeData::SetTextDirection(ax::mojom::TextDirection text_direction) {
875   if (HasIntAttribute(ax::mojom::IntAttribute::kTextDirection))
876     RemoveIntAttribute(ax::mojom::IntAttribute::kTextDirection);
877   if (text_direction != ax::mojom::TextDirection::kNone) {
878     AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
879                     static_cast<int32_t>(text_direction));
880   }
881 }
882 
IsClickable() const883 bool AXNodeData::IsClickable() const {
884   // If it has a custom default action verb except for
885   // ax::mojom::DefaultActionVerb::kClickAncestor, it's definitely clickable.
886   // ax::mojom::DefaultActionVerb::kClickAncestor is used when an element with a
887   // click listener is present in its ancestry chain.
888   if (HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb) &&
889       (GetDefaultActionVerb() != ax::mojom::DefaultActionVerb::kClickAncestor))
890     return true;
891 
892   return ui::IsClickable(role);
893 }
894 
IsIgnored() const895 bool AXNodeData::IsIgnored() const {
896   if (HasState(ax::mojom::State::kIgnored) || role == ax::mojom::Role::kIgnored)
897     return true;
898   return false;
899 }
900 
IsInvocable() const901 bool AXNodeData::IsInvocable() const {
902   // A control is "invocable" if it initiates an action when activated but
903   // does not maintain any state. A control that maintains state when activated
904   // would be considered a toggle or expand-collapse element - these elements
905   // are "clickable" but not "invocable".
906   return IsClickable() && !SupportsExpandCollapse() &&
907          !ui::SupportsToggle(role);
908 }
909 
IsPlainTextField() const910 bool AXNodeData::IsPlainTextField() const {
911   // We need to check both the role and editable state, because some ARIA text
912   // fields may in fact not be editable, whilst some editable fields might not
913   // have the role.
914   return !HasState(ax::mojom::State::kRichlyEditable) &&
915          (role == ax::mojom::Role::kTextField ||
916           role == ax::mojom::Role::kTextFieldWithComboBox ||
917           role == ax::mojom::Role::kSearchBox ||
918           GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot));
919 }
920 
IsReadOnlyOrDisabled() const921 bool AXNodeData::IsReadOnlyOrDisabled() const {
922   switch (GetRestriction()) {
923     case ax::mojom::Restriction::kReadOnly:
924     case ax::mojom::Restriction::kDisabled:
925       return true;
926     case ax::mojom::Restriction::kNone: {
927       if (HasState(ax::mojom::State::kEditable) ||
928           HasState(ax::mojom::State::kRichlyEditable)) {
929         return false;
930       }
931 
932       // By default, when readonly is not supported, we assume the node is never
933       // editable - then always readonly.
934       return ShouldHaveReadonlyStateByDefault(role) ||
935              !IsReadOnlySupported(role);
936     }
937   }
938 }
939 
IsRangeValueSupported() const940 bool AXNodeData::IsRangeValueSupported() const {
941   // https://www.w3.org/TR/wai-aria-1.1/#aria-valuenow
942   // https://www.w3.org/TR/wai-aria-1.1/#aria-valuetext
943   // Roles that support aria-valuetext / aria-valuenow
944   switch (role) {
945     case ax::mojom::Role::kMeter:
946     case ax::mojom::Role::kProgressIndicator:
947     case ax::mojom::Role::kScrollBar:
948     case ax::mojom::Role::kSlider:
949     case ax::mojom::Role::kSpinButton:
950       return true;
951     case ax::mojom::Role::kSplitter:
952       // According to the ARIA spec, role="separator" acts as a splitter only
953       // when focusable, and supports a range only in that case.
954       return HasState(ax::mojom::State::kFocusable);
955     default:
956       return false;
957   }
958 }
959 
SupportsExpandCollapse() const960 bool AXNodeData::SupportsExpandCollapse() const {
961   if (GetHasPopup() != ax::mojom::HasPopup::kFalse ||
962       HasState(ax::mojom::State::kExpanded) ||
963       HasState(ax::mojom::State::kCollapsed))
964     return true;
965 
966   return ui::SupportsExpandCollapse(role);
967 }
968 
IsContainedInActiveLiveRegion() const969 bool AXNodeData::IsContainedInActiveLiveRegion() const {
970   if (!HasStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus))
971     return false;
972 
973   if (base::CompareCaseInsensitiveASCII(
974           GetStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus),
975           "off") == 0)
976     return false;
977 
978   if (GetBoolAttribute(ax::mojom::BoolAttribute::kContainerLiveBusy))
979     return false;
980 
981   return true;
982 }
983 
ToString() const984 std::string AXNodeData::ToString() const {
985   std::string result;
986 
987   result += "id=" + base::NumberToString(id);
988   result += " ";
989   result += ui::ToString(role);
990 
991   result += StateBitfieldToString(state);
992 
993   result += " " + relative_bounds.ToString();
994 
995   for (const std::pair<ax::mojom::IntAttribute, int32_t>& int_attribute :
996        int_attributes) {
997     std::string value = base::NumberToString(int_attribute.second);
998     switch (int_attribute.first) {
999       case ax::mojom::IntAttribute::kDefaultActionVerb:
1000         result += " action=" + base::UTF16ToUTF8(ActionVerbToUnlocalizedString(
1001                                    static_cast<ax::mojom::DefaultActionVerb>(
1002                                        int_attribute.second)));
1003         break;
1004       case ax::mojom::IntAttribute::kScrollX:
1005         result += " scroll_x=" + value;
1006         break;
1007       case ax::mojom::IntAttribute::kScrollXMin:
1008         result += " scroll_x_min=" + value;
1009         break;
1010       case ax::mojom::IntAttribute::kScrollXMax:
1011         result += " scroll_x_max=" + value;
1012         break;
1013       case ax::mojom::IntAttribute::kScrollY:
1014         result += " scroll_y=" + value;
1015         break;
1016       case ax::mojom::IntAttribute::kScrollYMin:
1017         result += " scroll_y_min=" + value;
1018         break;
1019       case ax::mojom::IntAttribute::kScrollYMax:
1020         result += " scroll_y_max=" + value;
1021         break;
1022       case ax::mojom::IntAttribute::kHierarchicalLevel:
1023         result += " level=" + value;
1024         break;
1025       case ax::mojom::IntAttribute::kTextSelStart:
1026         result += " sel_start=" + value;
1027         break;
1028       case ax::mojom::IntAttribute::kTextSelEnd:
1029         result += " sel_end=" + value;
1030         break;
1031       case ax::mojom::IntAttribute::kAriaColumnCount:
1032         result += " aria_column_count=" + value;
1033         break;
1034       case ax::mojom::IntAttribute::kAriaCellColumnIndex:
1035         result += " aria_cell_column_index=" + value;
1036         break;
1037       case ax::mojom::IntAttribute::kAriaCellColumnSpan:
1038         result += " aria_cell_column_span=" + value;
1039         break;
1040       case ax::mojom::IntAttribute::kAriaRowCount:
1041         result += " aria_row_count=" + value;
1042         break;
1043       case ax::mojom::IntAttribute::kAriaCellRowIndex:
1044         result += " aria_cell_row_index=" + value;
1045         break;
1046       case ax::mojom::IntAttribute::kAriaCellRowSpan:
1047         result += " aria_cell_row_span=" + value;
1048         break;
1049       case ax::mojom::IntAttribute::kTableRowCount:
1050         result += " rows=" + value;
1051         break;
1052       case ax::mojom::IntAttribute::kTableColumnCount:
1053         result += " cols=" + value;
1054         break;
1055       case ax::mojom::IntAttribute::kTableCellColumnIndex:
1056         result += " col=" + value;
1057         break;
1058       case ax::mojom::IntAttribute::kTableCellRowIndex:
1059         result += " row=" + value;
1060         break;
1061       case ax::mojom::IntAttribute::kTableCellColumnSpan:
1062         result += " colspan=" + value;
1063         break;
1064       case ax::mojom::IntAttribute::kTableCellRowSpan:
1065         result += " rowspan=" + value;
1066         break;
1067       case ax::mojom::IntAttribute::kTableColumnHeaderId:
1068         result += " column_header_id=" + value;
1069         break;
1070       case ax::mojom::IntAttribute::kTableColumnIndex:
1071         result += " column_index=" + value;
1072         break;
1073       case ax::mojom::IntAttribute::kTableHeaderId:
1074         result += " header_id=" + value;
1075         break;
1076       case ax::mojom::IntAttribute::kTableRowHeaderId:
1077         result += " row_header_id=" + value;
1078         break;
1079       case ax::mojom::IntAttribute::kTableRowIndex:
1080         result += " row_index=" + value;
1081         break;
1082       case ax::mojom::IntAttribute::kSortDirection:
1083         switch (static_cast<ax::mojom::SortDirection>(int_attribute.second)) {
1084           case ax::mojom::SortDirection::kUnsorted:
1085             result += " sort_direction=none";
1086             break;
1087           case ax::mojom::SortDirection::kAscending:
1088             result += " sort_direction=ascending";
1089             break;
1090           case ax::mojom::SortDirection::kDescending:
1091             result += " sort_direction=descending";
1092             break;
1093           case ax::mojom::SortDirection::kOther:
1094             result += " sort_direction=other";
1095             break;
1096           default:
1097             break;
1098         }
1099         break;
1100       case ax::mojom::IntAttribute::kNameFrom:
1101         result += " name_from=";
1102         result += ui::ToString(
1103             static_cast<ax::mojom::NameFrom>(int_attribute.second));
1104         break;
1105       case ax::mojom::IntAttribute::kDescriptionFrom:
1106         result += " description_from=";
1107         result += ui::ToString(
1108             static_cast<ax::mojom::DescriptionFrom>(int_attribute.second));
1109         break;
1110       case ax::mojom::IntAttribute::kActivedescendantId:
1111         result += " activedescendant=" + value;
1112         break;
1113       case ax::mojom::IntAttribute::kErrormessageId:
1114         result += " errormessage=" + value;
1115         break;
1116       case ax::mojom::IntAttribute::kInPageLinkTargetId:
1117         result += " in_page_link_target_id=" + value;
1118         break;
1119       case ax::mojom::IntAttribute::kMemberOfId:
1120         result += " member_of_id=" + value;
1121         break;
1122       case ax::mojom::IntAttribute::kNextOnLineId:
1123         result += " next_on_line_id=" + value;
1124         break;
1125       case ax::mojom::IntAttribute::kPopupForId:
1126         result += " popup_for_id=" + value;
1127         break;
1128       case ax::mojom::IntAttribute::kPreviousOnLineId:
1129         result += " previous_on_line_id=" + value;
1130         break;
1131       case ax::mojom::IntAttribute::kColorValue:
1132         result += base::StringPrintf(" color_value=&%X", int_attribute.second);
1133         break;
1134       case ax::mojom::IntAttribute::kAriaCurrentState:
1135         switch (
1136             static_cast<ax::mojom::AriaCurrentState>(int_attribute.second)) {
1137           case ax::mojom::AriaCurrentState::kFalse:
1138             result += " aria_current_state=false";
1139             break;
1140           case ax::mojom::AriaCurrentState::kTrue:
1141             result += " aria_current_state=true";
1142             break;
1143           case ax::mojom::AriaCurrentState::kPage:
1144             result += " aria_current_state=page";
1145             break;
1146           case ax::mojom::AriaCurrentState::kStep:
1147             result += " aria_current_state=step";
1148             break;
1149           case ax::mojom::AriaCurrentState::kLocation:
1150             result += " aria_current_state=location";
1151             break;
1152           case ax::mojom::AriaCurrentState::kDate:
1153             result += " aria_current_state=date";
1154             break;
1155           case ax::mojom::AriaCurrentState::kTime:
1156             result += " aria_current_state=time";
1157             break;
1158           default:
1159             break;
1160         }
1161         break;
1162       case ax::mojom::IntAttribute::kBackgroundColor:
1163         result +=
1164             base::StringPrintf(" background_color=&%X", int_attribute.second);
1165         break;
1166       case ax::mojom::IntAttribute::kColor:
1167         result += base::StringPrintf(" color=&%X", int_attribute.second);
1168         break;
1169       case ax::mojom::IntAttribute::kListStyle:
1170         switch (static_cast<ax::mojom::ListStyle>(int_attribute.second)) {
1171           case ax::mojom::ListStyle::kCircle:
1172             result += " list_style=circle";
1173             break;
1174           case ax::mojom::ListStyle::kDisc:
1175             result += " list_style=disc";
1176             break;
1177           case ax::mojom::ListStyle::kImage:
1178             result += " list_style=image";
1179             break;
1180           case ax::mojom::ListStyle::kNumeric:
1181             result += " list_style=numeric";
1182             break;
1183           case ax::mojom::ListStyle::kOther:
1184             result += " list_style=other";
1185             break;
1186           case ax::mojom::ListStyle::kSquare:
1187             result += " list_style=square";
1188             break;
1189           default:
1190             break;
1191         }
1192         break;
1193       case ax::mojom::IntAttribute::kTextDirection:
1194         switch (static_cast<ax::mojom::TextDirection>(int_attribute.second)) {
1195           case ax::mojom::TextDirection::kLtr:
1196             result += " text_direction=ltr";
1197             break;
1198           case ax::mojom::TextDirection::kRtl:
1199             result += " text_direction=rtl";
1200             break;
1201           case ax::mojom::TextDirection::kTtb:
1202             result += " text_direction=ttb";
1203             break;
1204           case ax::mojom::TextDirection::kBtt:
1205             result += " text_direction=btt";
1206             break;
1207           default:
1208             break;
1209         }
1210         break;
1211       case ax::mojom::IntAttribute::kTextPosition:
1212         switch (static_cast<ax::mojom::TextPosition>(int_attribute.second)) {
1213           case ax::mojom::TextPosition::kNone:
1214             result += " text_position=none";
1215             break;
1216           case ax::mojom::TextPosition::kSubscript:
1217             result += " text_position=subscript";
1218             break;
1219           case ax::mojom::TextPosition::kSuperscript:
1220             result += " text_position=superscript";
1221             break;
1222           default:
1223             break;
1224         }
1225         break;
1226       case ax::mojom::IntAttribute::kTextStyle: {
1227         std::string text_style_value;
1228         if (HasTextStyle(ax::mojom::TextStyle::kBold))
1229           text_style_value += "bold,";
1230         if (HasTextStyle(ax::mojom::TextStyle::kItalic))
1231           text_style_value += "italic,";
1232         if (HasTextStyle(ax::mojom::TextStyle::kUnderline))
1233           text_style_value += "underline,";
1234         if (HasTextStyle(ax::mojom::TextStyle::kLineThrough))
1235           text_style_value += "line-through,";
1236         if (HasTextStyle(ax::mojom::TextStyle::kOverline))
1237           text_style_value += "overline,";
1238         result += text_style_value.substr(0, text_style_value.size() - 1);
1239         break;
1240       }
1241       case ax::mojom::IntAttribute::kTextOverlineStyle:
1242         result += std::string(" text_overline_style=") +
1243                   ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
1244                       int_attribute.second));
1245         break;
1246       case ax::mojom::IntAttribute::kTextStrikethroughStyle:
1247         result += std::string(" text_strikethrough_style=") +
1248                   ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
1249                       int_attribute.second));
1250         break;
1251       case ax::mojom::IntAttribute::kTextUnderlineStyle:
1252         result += std::string(" text_underline_style=") +
1253                   ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
1254                       int_attribute.second));
1255         break;
1256       case ax::mojom::IntAttribute::kSetSize:
1257         result += " setsize=" + value;
1258         break;
1259       case ax::mojom::IntAttribute::kPosInSet:
1260         result += " posinset=" + value;
1261         break;
1262       case ax::mojom::IntAttribute::kHasPopup:
1263         switch (static_cast<ax::mojom::HasPopup>(int_attribute.second)) {
1264           case ax::mojom::HasPopup::kTrue:
1265             result += " haspopup=true";
1266             break;
1267           case ax::mojom::HasPopup::kMenu:
1268             result += " haspopup=menu";
1269             break;
1270           case ax::mojom::HasPopup::kListbox:
1271             result += " haspopup=listbox";
1272             break;
1273           case ax::mojom::HasPopup::kTree:
1274             result += " haspopup=tree";
1275             break;
1276           case ax::mojom::HasPopup::kGrid:
1277             result += " haspopup=grid";
1278             break;
1279           case ax::mojom::HasPopup::kDialog:
1280             result += " haspopup=dialog";
1281             break;
1282           case ax::mojom::HasPopup::kFalse:
1283           default:
1284             break;
1285         }
1286         break;
1287       case ax::mojom::IntAttribute::kInvalidState:
1288         switch (static_cast<ax::mojom::InvalidState>(int_attribute.second)) {
1289           case ax::mojom::InvalidState::kFalse:
1290             result += " invalid_state=false";
1291             break;
1292           case ax::mojom::InvalidState::kTrue:
1293             result += " invalid_state=true";
1294             break;
1295           case ax::mojom::InvalidState::kOther:
1296             result += " invalid_state=other";
1297             break;
1298           default:
1299             break;
1300         }
1301         break;
1302       case ax::mojom::IntAttribute::kCheckedState:
1303         switch (static_cast<ax::mojom::CheckedState>(int_attribute.second)) {
1304           case ax::mojom::CheckedState::kFalse:
1305             result += " checked_state=false";
1306             break;
1307           case ax::mojom::CheckedState::kTrue:
1308             result += " checked_state=true";
1309             break;
1310           case ax::mojom::CheckedState::kMixed:
1311             result += " checked_state=mixed";
1312             break;
1313           default:
1314             break;
1315         }
1316         break;
1317       case ax::mojom::IntAttribute::kRestriction:
1318         switch (static_cast<ax::mojom::Restriction>(int_attribute.second)) {
1319           case ax::mojom::Restriction::kReadOnly:
1320             result += " restriction=readonly";
1321             break;
1322           case ax::mojom::Restriction::kDisabled:
1323             result += " restriction=disabled";
1324             break;
1325           default:
1326             break;
1327         }
1328         break;
1329       case ax::mojom::IntAttribute::kNextFocusId:
1330         result += " next_focus_id=" + value;
1331         break;
1332       case ax::mojom::IntAttribute::kPreviousFocusId:
1333         result += " previous_focus_id=" + value;
1334         break;
1335       case ax::mojom::IntAttribute::kImageAnnotationStatus:
1336         result += std::string(" image_annotation_status=") +
1337                   ui::ToString(static_cast<ax::mojom::ImageAnnotationStatus>(
1338                       int_attribute.second));
1339         break;
1340       case ax::mojom::IntAttribute::kDropeffect:
1341         result += " dropeffect=" + value;
1342         break;
1343       case ax::mojom::IntAttribute::kDOMNodeId:
1344         result += " dom_node_id=" + value;
1345         break;
1346       case ax::mojom::IntAttribute::kNone:
1347         break;
1348     }
1349   }
1350 
1351   for (const std::pair<ax::mojom::StringAttribute, std::string>&
1352            string_attribute : string_attributes) {
1353     std::string value = string_attribute.second;
1354     switch (string_attribute.first) {
1355       case ax::mojom::StringAttribute::kAccessKey:
1356         result += " access_key=" + value;
1357         break;
1358       case ax::mojom::StringAttribute::kAriaInvalidValue:
1359         result += " aria_invalid_value=" + value;
1360         break;
1361       case ax::mojom::StringAttribute::kAutoComplete:
1362         result += " autocomplete=" + value;
1363         break;
1364       case ax::mojom::StringAttribute::kChildTreeId:
1365         result += " child_tree_id=" + value.substr(0, 8);
1366         break;
1367       case ax::mojom::StringAttribute::kClassName:
1368         result += " class_name=" + value;
1369         break;
1370       case ax::mojom::StringAttribute::kDescription:
1371         result += " description=" + value;
1372         break;
1373       case ax::mojom::StringAttribute::kDisplay:
1374         result += " display=" + value;
1375         break;
1376       case ax::mojom::StringAttribute::kFontFamily:
1377         result += " font-family=" + value;
1378         break;
1379       case ax::mojom::StringAttribute::kHtmlTag:
1380         result += " html_tag=" + value;
1381         break;
1382       case ax::mojom::StringAttribute::kImageAnnotation:
1383         result += " image_annotation=" + value;
1384         break;
1385       case ax::mojom::StringAttribute::kImageDataUrl:
1386         result += " image_data_url=(" +
1387                   base::NumberToString(static_cast<int>(value.size())) +
1388                   " bytes)";
1389         break;
1390       case ax::mojom::StringAttribute::kInnerHtml:
1391         result += " inner_html=" + value;
1392         break;
1393       case ax::mojom::StringAttribute::kInputType:
1394         result += " input_type=" + value;
1395         break;
1396       case ax::mojom::StringAttribute::kKeyShortcuts:
1397         result += " key_shortcuts=" + value;
1398         break;
1399       case ax::mojom::StringAttribute::kLanguage:
1400         result += " language=" + value;
1401         break;
1402       case ax::mojom::StringAttribute::kLiveRelevant:
1403         result += " relevant=" + value;
1404         break;
1405       case ax::mojom::StringAttribute::kLiveStatus:
1406         result += " live=" + value;
1407         break;
1408       case ax::mojom::StringAttribute::kContainerLiveRelevant:
1409         result += " container_relevant=" + value;
1410         break;
1411       case ax::mojom::StringAttribute::kContainerLiveStatus:
1412         result += " container_live=" + value;
1413         break;
1414       case ax::mojom::StringAttribute::kPlaceholder:
1415         result += " placeholder=" + value;
1416         break;
1417       case ax::mojom::StringAttribute::kRole:
1418         result += " role=" + value;
1419         break;
1420       case ax::mojom::StringAttribute::kRoleDescription:
1421         result += " role_description=" + value;
1422         break;
1423       case ax::mojom::StringAttribute::kTooltip:
1424         result += " tooltip=" + value;
1425         break;
1426       case ax::mojom::StringAttribute::kUrl:
1427         result += " url=" + value;
1428         break;
1429       case ax::mojom::StringAttribute::kName:
1430         result += " name=" + value;
1431         break;
1432       case ax::mojom::StringAttribute::kValue:
1433         result += " value=" + value;
1434         break;
1435       case ax::mojom::StringAttribute::kNone:
1436         break;
1437     }
1438   }
1439 
1440   for (const std::pair<ax::mojom::FloatAttribute, float>& float_attribute :
1441        float_attributes) {
1442     std::string value = base::NumberToString(float_attribute.second);
1443     switch (float_attribute.first) {
1444       case ax::mojom::FloatAttribute::kValueForRange:
1445         result += " value_for_range=" + value;
1446         break;
1447       case ax::mojom::FloatAttribute::kMaxValueForRange:
1448         result += " max_value=" + value;
1449         break;
1450       case ax::mojom::FloatAttribute::kMinValueForRange:
1451         result += " min_value=" + value;
1452         break;
1453       case ax::mojom::FloatAttribute::kStepValueForRange:
1454         result += " step_value=" + value;
1455         break;
1456       case ax::mojom::FloatAttribute::kFontSize:
1457         result += " font_size=" + value;
1458         break;
1459       case ax::mojom::FloatAttribute::kFontWeight:
1460         result += " font_weight=" + value;
1461         break;
1462       case ax::mojom::FloatAttribute::kNone:
1463         break;
1464     }
1465   }
1466 
1467   for (const std::pair<ax::mojom::BoolAttribute, bool>& bool_attribute :
1468        bool_attributes) {
1469     std::string value = bool_attribute.second ? "true" : "false";
1470     switch (bool_attribute.first) {
1471       case ax::mojom::BoolAttribute::kEditableRoot:
1472         result += " editable_root=" + value;
1473         break;
1474       case ax::mojom::BoolAttribute::kLiveAtomic:
1475         result += " atomic=" + value;
1476         break;
1477       case ax::mojom::BoolAttribute::kBusy:
1478         result += " busy=" + value;
1479         break;
1480       case ax::mojom::BoolAttribute::kContainerLiveAtomic:
1481         result += " container_atomic=" + value;
1482         break;
1483       case ax::mojom::BoolAttribute::kContainerLiveBusy:
1484         result += " container_busy=" + value;
1485         break;
1486       case ax::mojom::BoolAttribute::kUpdateLocationOnly:
1487         result += " update_location_only=" + value;
1488         break;
1489       case ax::mojom::BoolAttribute::kCanvasHasFallback:
1490         result += " has_fallback=" + value;
1491         break;
1492       case ax::mojom::BoolAttribute::kModal:
1493         result += " modal=" + value;
1494         break;
1495       case ax::mojom::BoolAttribute::kScrollable:
1496         result += " scrollable=" + value;
1497         break;
1498       case ax::mojom::BoolAttribute::kClickable:
1499         result += " clickable=" + value;
1500         break;
1501       case ax::mojom::BoolAttribute::kClipsChildren:
1502         result += " clips_children=" + value;
1503         break;
1504       case ax::mojom::BoolAttribute::kSelected:
1505         result += " selected=" + value;
1506         break;
1507       case ax::mojom::BoolAttribute::kSupportsTextLocation:
1508         result += " supports_text_location=" + value;
1509         break;
1510       case ax::mojom::BoolAttribute::kGrabbed:
1511         result += " grabbed=" + value;
1512         break;
1513       case ax::mojom::BoolAttribute::kIsLineBreakingObject:
1514         result += " is_line_breaking_object=" + value;
1515         break;
1516       case ax::mojom::BoolAttribute::kIsPageBreakingObject:
1517         result += " is_page_breaking_object=" + value;
1518         break;
1519       case ax::mojom::BoolAttribute::kHasAriaAttribute:
1520         result += " has_aria_attribute=" + value;
1521         break;
1522       case ax::mojom::BoolAttribute::kNone:
1523         break;
1524     }
1525   }
1526 
1527   for (const std::pair<ax::mojom::IntListAttribute, std::vector<int32_t>>&
1528            intlist_attribute : intlist_attributes) {
1529     const std::vector<int32_t>& values = intlist_attribute.second;
1530     switch (intlist_attribute.first) {
1531       case ax::mojom::IntListAttribute::kIndirectChildIds:
1532         result += " indirect_child_ids=" + IntVectorToString(values);
1533         break;
1534       case ax::mojom::IntListAttribute::kControlsIds:
1535         result += " controls_ids=" + IntVectorToString(values);
1536         break;
1537       case ax::mojom::IntListAttribute::kDescribedbyIds:
1538         result += " describedby_ids=" + IntVectorToString(values);
1539         break;
1540       case ax::mojom::IntListAttribute::kDetailsIds:
1541         result += " details_ids=" + IntVectorToString(values);
1542         break;
1543       case ax::mojom::IntListAttribute::kFlowtoIds:
1544         result += " flowto_ids=" + IntVectorToString(values);
1545         break;
1546       case ax::mojom::IntListAttribute::kLabelledbyIds:
1547         result += " labelledby_ids=" + IntVectorToString(values);
1548         break;
1549       case ax::mojom::IntListAttribute::kRadioGroupIds:
1550         result += " radio_group_ids=" + IntVectorToString(values);
1551         break;
1552       case ax::mojom::IntListAttribute::kMarkerTypes: {
1553         std::string types_str;
1554         for (size_t i = 0; i < values.size(); ++i) {
1555           int32_t type = values[i];
1556           if (type == static_cast<int32_t>(ax::mojom::MarkerType::kNone))
1557             continue;
1558 
1559           if (i > 0)
1560             types_str += ',';
1561 
1562           if (type & static_cast<int32_t>(ax::mojom::MarkerType::kSpelling))
1563             types_str += "spelling&";
1564           if (type & static_cast<int32_t>(ax::mojom::MarkerType::kGrammar))
1565             types_str += "grammar&";
1566           if (type & static_cast<int32_t>(ax::mojom::MarkerType::kTextMatch))
1567             types_str += "text_match&";
1568           if (type &
1569               static_cast<int32_t>(ax::mojom::MarkerType::kActiveSuggestion))
1570             types_str += "active_suggestion&";
1571           if (type & static_cast<int32_t>(ax::mojom::MarkerType::kSuggestion))
1572             types_str += "suggestion&";
1573 
1574           if (!types_str.empty())
1575             types_str = types_str.substr(0, types_str.size() - 1);
1576         }
1577 
1578         if (!types_str.empty())
1579           result += " marker_types=" + types_str;
1580         break;
1581       }
1582       case ax::mojom::IntListAttribute::kMarkerStarts:
1583         result += " marker_starts=" + IntVectorToString(values);
1584         break;
1585       case ax::mojom::IntListAttribute::kMarkerEnds:
1586         result += " marker_ends=" + IntVectorToString(values);
1587         break;
1588       case ax::mojom::IntListAttribute::kCharacterOffsets:
1589         result += " character_offsets=" + IntVectorToString(values);
1590         break;
1591       case ax::mojom::IntListAttribute::kCachedLineStarts:
1592         result += " cached_line_start_offsets=" + IntVectorToString(values);
1593         break;
1594       case ax::mojom::IntListAttribute::kWordStarts:
1595         result += " word_starts=" + IntVectorToString(values);
1596         break;
1597       case ax::mojom::IntListAttribute::kWordEnds:
1598         result += " word_ends=" + IntVectorToString(values);
1599         break;
1600       case ax::mojom::IntListAttribute::kCustomActionIds:
1601         result += " custom_action_ids=" + IntVectorToString(values);
1602         break;
1603       case ax::mojom::IntListAttribute::kNone:
1604         break;
1605     }
1606   }
1607 
1608   for (const std::pair<ax::mojom::StringListAttribute,
1609                        std::vector<std::string>>& stringlist_attribute :
1610        stringlist_attributes) {
1611     const std::vector<std::string>& values = stringlist_attribute.second;
1612     switch (stringlist_attribute.first) {
1613       case ax::mojom::StringListAttribute::kCustomActionDescriptions:
1614         result +=
1615             " custom_action_descriptions: " + base::JoinString(values, ",");
1616         break;
1617       case ax::mojom::StringListAttribute::kNone:
1618         break;
1619     }
1620   }
1621 
1622   if (actions)
1623     result += " actions=" + ActionsBitfieldToString(actions);
1624 
1625   if (!child_ids.empty())
1626     result += " child_ids=" + IntVectorToString(child_ids);
1627 
1628   return result;
1629 }
1630 
DropeffectBitfieldToString() const1631 std::string AXNodeData::DropeffectBitfieldToString() const {
1632   if (!HasIntAttribute(ax::mojom::IntAttribute::kDropeffect))
1633     return "";
1634 
1635   std::string str;
1636   for (int dropeffect_idx = static_cast<int>(ax::mojom::Dropeffect::kMinValue);
1637        dropeffect_idx <= static_cast<int>(ax::mojom::Dropeffect::kMaxValue);
1638        ++dropeffect_idx) {
1639     ax::mojom::Dropeffect dropeffect_enum =
1640         static_cast<ax::mojom::Dropeffect>(dropeffect_idx);
1641     if (HasDropeffect(dropeffect_enum))
1642       str += " " + std::string(ui::ToString(dropeffect_enum));
1643   }
1644 
1645   // Removing leading space in final string.
1646   return str.substr(1);
1647 }
1648 
1649 }  // namespace ui
1650