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.
5 #include "content/browser/accessibility/accessibility_tree_formatter.h"
11 #include "base/files/file_path.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/win/scoped_bstr.h"
18 #include "base/win/scoped_comptr.h"
19 #include "content/browser/accessibility/accessibility_tree_formatter_utils_win.h"
20 #include "content/browser/accessibility/browser_accessibility_manager.h"
21 #include "content/browser/accessibility/browser_accessibility_win.h"
22 #include "third_party/iaccessible2/ia2_api_all.h"
23 #include "ui/base/win/atl_module.h"
28 const char* ALL_ATTRIBUTES
[] = {
46 "similar_items_in_group",
61 base::string16
GetIA2Hypertext(BrowserAccessibilityWin
& ax_object
) {
62 base::win::ScopedBstr text_bstr
;
65 hr
= ax_object
.get_text(0, IA2_TEXT_OFFSET_LENGTH
, text_bstr
.Receive());
67 return base::string16();
69 base::string16
ia2_hypertext(text_bstr
, text_bstr
.Length());
70 // IA2 Spec calls embedded objects hyperlinks. We stick to embeds for clarity.
71 LONG number_of_embeds
;
72 hr
= ax_object
.get_nHyperlinks(&number_of_embeds
);
73 if (FAILED(hr
) || number_of_embeds
== 0)
76 // Replace all embedded characters with the child indices of the accessibility
77 // objects they refer to.
78 base::string16
embedded_character(1,
79 BrowserAccessibilityWin::kEmbeddedCharacter
);
80 size_t character_index
= 0;
81 size_t hypertext_index
= 0;
82 while (hypertext_index
< ia2_hypertext
.length()) {
83 if (ia2_hypertext
[hypertext_index
] !=
84 BrowserAccessibilityWin::kEmbeddedCharacter
) {
91 hr
= ax_object
.get_hyperlinkIndex(character_index
, &index_of_embed
);
92 // S_FALSE will be returned if no embedded object is found at the given
93 // embedded character offset. Exclude child index from such cases.
94 LONG child_index
= -1;
96 DCHECK_GE(index_of_embed
, 0);
97 base::win::ScopedComPtr
<IAccessibleHyperlink
> embedded_object
;
98 hr
= ax_object
.get_hyperlink(
99 index_of_embed
, embedded_object
.Receive());
100 DCHECK(SUCCEEDED(hr
));
101 base::win::ScopedComPtr
<IAccessible2
> ax_embed
;
102 hr
= embedded_object
.QueryInterface(ax_embed
.Receive());
103 DCHECK(SUCCEEDED(hr
));
104 hr
= ax_embed
->get_indexInParent(&child_index
);
105 DCHECK(SUCCEEDED(hr
));
108 base::string16
child_index_str(L
"<obj");
109 if (child_index
>= 0) {
110 base::StringAppendF(&child_index_str
, L
"%d>", child_index
);
112 base::StringAppendF(&child_index_str
, L
">");
114 base::ReplaceFirstSubstringAfterOffset(&ia2_hypertext
, hypertext_index
,
115 embedded_character
, child_index_str
);
117 hypertext_index
+= child_index_str
.length();
120 DCHECK_EQ(number_of_embeds
, 0);
122 return ia2_hypertext
;
127 void AccessibilityTreeFormatter::Initialize() {
128 ui::win::CreateATLModuleIfNeeded();
131 void AccessibilityTreeFormatter::AddProperties(
132 const BrowserAccessibility
& node
, base::DictionaryValue
* dict
) {
133 dict
->SetInteger("id", node
.GetId());
134 BrowserAccessibilityWin
* ax_object
=
135 const_cast<BrowserAccessibility
*>(&node
)->ToBrowserAccessibilityWin();
138 VARIANT variant_self
;
139 variant_self
.vt
= VT_I4
;
140 variant_self
.lVal
= CHILDID_SELF
;
142 dict
->SetString("role", IAccessible2RoleToString(ax_object
->ia2_role()));
144 base::win::ScopedBstr temp_bstr
;
145 if (SUCCEEDED(ax_object
->get_accName(variant_self
, temp_bstr
.Receive())))
146 dict
->SetString("name", base::string16(temp_bstr
, temp_bstr
.Length()));
149 if (SUCCEEDED(ax_object
->get_accValue(variant_self
, temp_bstr
.Receive())))
150 dict
->SetString("value", base::string16(temp_bstr
, temp_bstr
.Length()));
153 std::vector
<base::string16
> state_strings
;
154 int32 ia_state
= ax_object
->ia_state();
156 // Avoid flakiness: these states depend on whether the window is focused
157 // and the position of the mouse cursor.
158 ia_state
&= ~STATE_SYSTEM_HOTTRACKED
;
159 ia_state
&= ~STATE_SYSTEM_OFFSCREEN
;
161 IAccessibleStateToStringVector(ia_state
, &state_strings
);
162 IAccessible2StateToStringVector(ax_object
->ia2_state(), &state_strings
);
163 base::ListValue
* states
= new base::ListValue
;
164 for (const auto& state_string
: state_strings
)
165 states
->AppendString(base::UTF16ToUTF8(state_string
));
166 dict
->Set("states", states
);
168 const std::vector
<base::string16
>& ia2_attributes
=
169 ax_object
->ia2_attributes();
170 base::ListValue
* attributes
= new base::ListValue
;
171 for (const auto& ia2_attribute
: ia2_attributes
)
172 attributes
->AppendString(base::UTF16ToUTF8(ia2_attribute
));
173 dict
->Set("attributes", attributes
);
175 dict
->SetString("role_name", ax_object
->role_name());
176 dict
->SetString("ia2_hypertext", GetIA2Hypertext(*ax_object
));
178 VARIANT currentValue
;
179 if (ax_object
->get_currentValue(¤tValue
) == S_OK
)
180 dict
->SetDouble("currentValue", V_R8(¤tValue
));
182 VARIANT minimumValue
;
183 if (ax_object
->get_minimumValue(&minimumValue
) == S_OK
)
184 dict
->SetDouble("minimumValue", V_R8(&minimumValue
));
186 VARIANT maximumValue
;
187 if (ax_object
->get_maximumValue(&maximumValue
) == S_OK
)
188 dict
->SetDouble("maximumValue", V_R8(&maximumValue
));
190 if (SUCCEEDED(ax_object
->get_accDescription(variant_self
,
191 temp_bstr
.Receive()))) {
192 dict
->SetString("description", base::string16(temp_bstr
,
193 temp_bstr
.Length()));
197 if (SUCCEEDED(ax_object
->get_accDefaultAction(variant_self
,
198 temp_bstr
.Receive()))) {
199 dict
->SetString("default_action", base::string16(temp_bstr
,
200 temp_bstr
.Length()));
205 ax_object
->get_accKeyboardShortcut(variant_self
, temp_bstr
.Receive()))) {
206 dict
->SetString("keyboard_shortcut", base::string16(temp_bstr
,
207 temp_bstr
.Length()));
211 if (SUCCEEDED(ax_object
->get_accHelp(variant_self
, temp_bstr
.Receive())))
212 dict
->SetString("help", base::string16(temp_bstr
, temp_bstr
.Length()));
215 BrowserAccessibility
* root
= node
.manager()->GetRoot();
216 LONG left
, top
, width
, height
;
217 LONG root_left
, root_top
, root_width
, root_height
;
218 if (SUCCEEDED(ax_object
->accLocation(
219 &left
, &top
, &width
, &height
, variant_self
)) &&
220 SUCCEEDED(root
->ToBrowserAccessibilityWin()->accLocation(
221 &root_left
, &root_top
, &root_width
, &root_height
, variant_self
))) {
222 base::DictionaryValue
* location
= new base::DictionaryValue
;
223 location
->SetInteger("x", left
- root_left
);
224 location
->SetInteger("y", top
- root_top
);
225 dict
->Set("location", location
);
227 base::DictionaryValue
* size
= new base::DictionaryValue
;
228 size
->SetInteger("width", width
);
229 size
->SetInteger("height", height
);
230 dict
->Set("size", size
);
233 LONG index_in_parent
;
234 if (SUCCEEDED(ax_object
->get_indexInParent(&index_in_parent
)))
235 dict
->SetInteger("index_in_parent", index_in_parent
);
238 if (SUCCEEDED(ax_object
->get_nRelations(&n_relations
)))
239 dict
->SetInteger("n_relations", n_relations
);
241 LONG group_level
, similar_items_in_group
, position_in_group
;
242 if (SUCCEEDED(ax_object
->get_groupPosition(&group_level
,
243 &similar_items_in_group
,
244 &position_in_group
))) {
245 dict
->SetInteger("group_level", group_level
);
246 dict
->SetInteger("similar_items_in_group", similar_items_in_group
);
247 dict
->SetInteger("position_in_group", position_in_group
);
251 if (SUCCEEDED(ax_object
->get_nRows(&table_rows
)))
252 dict
->SetInteger("table_rows", table_rows
);
255 if (SUCCEEDED(ax_object
->get_nRows(&table_columns
)))
256 dict
->SetInteger("table_columns", table_columns
);
259 if (SUCCEEDED(ax_object
->get_rowIndex(&row_index
)))
260 dict
->SetInteger("row_index", row_index
);
263 if (SUCCEEDED(ax_object
->get_columnIndex(&column_index
)))
264 dict
->SetInteger("column_index", column_index
);
267 if (SUCCEEDED(ax_object
->get_nCharacters(&n_characters
)))
268 dict
->SetInteger("n_characters", n_characters
);
271 if (ax_object
->get_caretOffset(&caret_offset
) == S_OK
)
272 dict
->SetInteger("caret_offset", caret_offset
);
275 if (SUCCEEDED(ax_object
->get_nSelections(&n_selections
))) {
276 dict
->SetInteger("n_selections", n_selections
);
277 if (n_selections
> 0) {
279 if (SUCCEEDED(ax_object
->get_selection(0, &start
, &end
))) {
280 dict
->SetInteger("selection_start", start
);
281 dict
->SetInteger("selection_end", end
);
287 base::string16
AccessibilityTreeFormatter::ToString(
288 const base::DictionaryValue
& dict
) {
293 dict
.GetInteger("id", &id_value
);
294 WriteAttribute(true, base::IntToString16(id_value
), &line
);
297 base::string16 role_value
;
298 dict
.GetString("role", &role_value
);
299 WriteAttribute(true, base::UTF16ToUTF8(role_value
), &line
);
301 for (int i
= 0; i
< arraysize(ALL_ATTRIBUTES
); i
++) {
302 const char* attribute_name
= ALL_ATTRIBUTES
[i
];
303 const base::Value
* value
;
304 if (!dict
.Get(attribute_name
, &value
))
307 switch (value
->GetType()) {
308 case base::Value::TYPE_STRING
: {
309 base::string16 string_value
;
310 value
->GetAsString(&string_value
);
311 WriteAttribute(false,
312 base::StringPrintf(L
"%ls='%ls'",
313 base::UTF8ToUTF16(attribute_name
).c_str(),
314 string_value
.c_str()),
318 case base::Value::TYPE_INTEGER
: {
320 value
->GetAsInteger(&int_value
);
321 WriteAttribute(false,
322 base::StringPrintf(L
"%ls=%d",
324 attribute_name
).c_str(),
329 case base::Value::TYPE_DOUBLE
: {
331 value
->GetAsDouble(&double_value
);
332 WriteAttribute(false,
333 base::StringPrintf(L
"%ls=%.2f",
335 attribute_name
).c_str(),
340 case base::Value::TYPE_LIST
: {
341 // Currently all list values are string and are written without
343 const base::ListValue
* list_value
;
344 value
->GetAsList(&list_value
);
345 for (base::ListValue::const_iterator it
= list_value
->begin();
346 it
!= list_value
->end();
348 base::string16 string_value
;
349 if ((*it
)->GetAsString(&string_value
))
350 WriteAttribute(false, string_value
, &line
);
354 case base::Value::TYPE_DICTIONARY
: {
355 // Currently all dictionary values are coordinates.
356 // Revisit this if that changes.
357 const base::DictionaryValue
* dict_value
;
358 value
->GetAsDictionary(&dict_value
);
359 if (strcmp(attribute_name
, "size") == 0) {
360 WriteAttribute(false,
361 FormatCoordinates("size", "width", "height",
364 } else if (strcmp(attribute_name
, "location") == 0) {
365 WriteAttribute(false,
366 FormatCoordinates("location", "x", "y", *dict_value
),
381 const base::FilePath::StringType
382 AccessibilityTreeFormatter::GetActualFileSuffix() {
383 return FILE_PATH_LITERAL("-actual-win.txt");
387 const base::FilePath::StringType
388 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
389 return FILE_PATH_LITERAL("-expected-win.txt");
393 const std::string
AccessibilityTreeFormatter::GetAllowEmptyString() {
394 return "@WIN-ALLOW-EMPTY:";
398 const std::string
AccessibilityTreeFormatter::GetAllowString() {
399 return "@WIN-ALLOW:";
403 const std::string
AccessibilityTreeFormatter::GetDenyString() {
407 } // namespace content