Roll src/third_party/WebKit f298044:aa8346d (svn 202628:202629)
[chromium-blink-merge.git] / content / browser / accessibility / accessibility_tree_formatter_win.cc
blobc7ef471a616becfcf2ff70b0de13b0bae9f0cf6d
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"
7 #include <oleacc.h>
9 #include <string>
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"
26 namespace content {
28 const char* ALL_ATTRIBUTES[] = {
29 "name",
30 "value",
31 "states",
32 "attributes",
33 "role_name",
34 "ia2_hypertext",
35 "currentValue",
36 "minimumValue",
37 "maximumValue",
38 "description",
39 "default_action",
40 "keyboard_shortcut",
41 "location",
42 "size",
43 "index_in_parent",
44 "n_relations",
45 "group_level",
46 "similar_items_in_group",
47 "position_in_group",
48 "table_rows",
49 "table_columns",
50 "row_index",
51 "column_index",
52 "n_characters",
53 "caret_offset",
54 "n_selections",
55 "selection_start",
56 "selection_end"
59 namespace {
61 base::string16 GetIA2Hypertext(BrowserAccessibilityWin& ax_object) {
62 base::win::ScopedBstr text_bstr;
63 HRESULT hr;
65 hr = ax_object.get_text(0, IA2_TEXT_OFFSET_LENGTH, text_bstr.Receive());
66 if (FAILED(hr))
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)
74 return ia2_hypertext;
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) {
85 ++character_index;
86 ++hypertext_index;
87 continue;
90 LONG index_of_embed;
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;
95 if (hr == S_OK) {
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);
111 } else {
112 base::StringAppendF(&child_index_str, L">");
114 base::ReplaceFirstSubstringAfterOffset(&ia2_hypertext, hypertext_index,
115 embedded_character, child_index_str);
116 ++character_index;
117 hypertext_index += child_index_str.length();
118 --number_of_embeds;
120 DCHECK_EQ(number_of_embeds, 0);
122 return ia2_hypertext;
125 } // Namespace
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();
136 DCHECK(ax_object);
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()));
147 temp_bstr.Reset();
149 if (SUCCEEDED(ax_object->get_accValue(variant_self, temp_bstr.Receive())))
150 dict->SetString("value", base::string16(temp_bstr, temp_bstr.Length()));
151 temp_bstr.Reset();
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(&currentValue) == S_OK)
180 dict->SetDouble("currentValue", V_R8(&currentValue));
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()));
195 temp_bstr.Reset();
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()));
202 temp_bstr.Reset();
204 if (SUCCEEDED(
205 ax_object->get_accKeyboardShortcut(variant_self, temp_bstr.Receive()))) {
206 dict->SetString("keyboard_shortcut", base::string16(temp_bstr,
207 temp_bstr.Length()));
209 temp_bstr.Reset();
211 if (SUCCEEDED(ax_object->get_accHelp(variant_self, temp_bstr.Receive())))
212 dict->SetString("help", base::string16(temp_bstr, temp_bstr.Length()));
213 temp_bstr.Reset();
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);
237 LONG n_relations;
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);
250 LONG table_rows;
251 if (SUCCEEDED(ax_object->get_nRows(&table_rows)))
252 dict->SetInteger("table_rows", table_rows);
254 LONG table_columns;
255 if (SUCCEEDED(ax_object->get_nRows(&table_columns)))
256 dict->SetInteger("table_columns", table_columns);
258 LONG row_index;
259 if (SUCCEEDED(ax_object->get_rowIndex(&row_index)))
260 dict->SetInteger("row_index", row_index);
262 LONG column_index;
263 if (SUCCEEDED(ax_object->get_columnIndex(&column_index)))
264 dict->SetInteger("column_index", column_index);
266 LONG n_characters;
267 if (SUCCEEDED(ax_object->get_nCharacters(&n_characters)))
268 dict->SetInteger("n_characters", n_characters);
270 LONG caret_offset;
271 if (ax_object->get_caretOffset(&caret_offset) == S_OK)
272 dict->SetInteger("caret_offset", caret_offset);
274 LONG n_selections;
275 if (SUCCEEDED(ax_object->get_nSelections(&n_selections))) {
276 dict->SetInteger("n_selections", n_selections);
277 if (n_selections > 0) {
278 LONG start, end;
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) {
289 base::string16 line;
291 if (show_ids_) {
292 int id_value;
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))
305 continue;
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()),
315 &line);
316 break;
318 case base::Value::TYPE_INTEGER: {
319 int int_value;
320 value->GetAsInteger(&int_value);
321 WriteAttribute(false,
322 base::StringPrintf(L"%ls=%d",
323 base::UTF8ToUTF16(
324 attribute_name).c_str(),
325 int_value),
326 &line);
327 break;
329 case base::Value::TYPE_DOUBLE: {
330 double double_value;
331 value->GetAsDouble(&double_value);
332 WriteAttribute(false,
333 base::StringPrintf(L"%ls=%.2f",
334 base::UTF8ToUTF16(
335 attribute_name).c_str(),
336 double_value),
337 &line);
338 break;
340 case base::Value::TYPE_LIST: {
341 // Currently all list values are string and are written without
342 // attribute names.
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();
347 ++it) {
348 base::string16 string_value;
349 if ((*it)->GetAsString(&string_value))
350 WriteAttribute(false, string_value, &line);
352 break;
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",
362 *dict_value),
363 &line);
364 } else if (strcmp(attribute_name, "location") == 0) {
365 WriteAttribute(false,
366 FormatCoordinates("location", "x", "y", *dict_value),
367 &line);
369 break;
371 default:
372 NOTREACHED();
373 break;
377 return line;
380 // static
381 const base::FilePath::StringType
382 AccessibilityTreeFormatter::GetActualFileSuffix() {
383 return FILE_PATH_LITERAL("-actual-win.txt");
386 // static
387 const base::FilePath::StringType
388 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
389 return FILE_PATH_LITERAL("-expected-win.txt");
392 // static
393 const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
394 return "@WIN-ALLOW-EMPTY:";
397 // static
398 const std::string AccessibilityTreeFormatter::GetAllowString() {
399 return "@WIN-ALLOW:";
402 // static
403 const std::string AccessibilityTreeFormatter::GetDenyString() {
404 return "@WIN-DENY:";
407 } // namespace content