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 #import <Cocoa/Cocoa.h>
9 #include "base/basictypes.h"
10 #include "base/files/file_path.h"
11 #include "base/json/json_writer.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "content/browser/accessibility/browser_accessibility_cocoa.h"
17 #include "content/browser/accessibility/browser_accessibility_mac.h"
18 #include "content/browser/accessibility/browser_accessibility_manager.h"
20 using base::StringPrintf;
21 using base::SysNSStringToUTF8;
22 using base::SysNSStringToUTF16;
29 const char* kPositionDictAttr = "position";
30 const char* kXCoordDictAttr = "x";
31 const char* kYCoordDictAttr = "y";
32 const char* kSizeDictAttr = "size";
33 const char* kWidthDictAttr = "width";
34 const char* kHeightDictAttr = "height";
35 const char* kRangeLocDictAttr = "loc";
36 const char* kRangeLenDictAttr = "len";
38 scoped_ptr<base::DictionaryValue> PopulatePosition(
39 const BrowserAccessibility& node) {
40 scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue);
41 // The NSAccessibility position of an object is in global coordinates and
42 // based on the lower-left corner of the object. To make this easier and less
43 // confusing, convert it to local window coordinates using the top-left
44 // corner when dumping the position.
45 BrowserAccessibility* root = node.manager()->GetRoot();
46 BrowserAccessibilityCocoa* cocoa_root = root->ToBrowserAccessibilityCocoa();
47 NSPoint root_position = [[cocoa_root position] pointValue];
48 NSSize root_size = [[cocoa_root size] sizeValue];
49 int root_top = -static_cast<int>(root_position.y + root_size.height);
50 int root_left = static_cast<int>(root_position.x);
52 BrowserAccessibilityCocoa* cocoa_node =
53 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
54 NSPoint node_position = [[cocoa_node position] pointValue];
55 NSSize node_size = [[cocoa_node size] sizeValue];
57 position->SetInteger(kXCoordDictAttr,
58 static_cast<int>(node_position.x - root_left));
59 position->SetInteger(kYCoordDictAttr,
60 static_cast<int>(-node_position.y - node_size.height - root_top));
61 return position.Pass();
64 scoped_ptr<base::DictionaryValue>
65 PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) {
66 scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue);
67 NSSize node_size = [[cocoa_node size] sizeValue];
68 size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height));
69 size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width));
73 scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) {
74 scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue);
75 rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location));
76 rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length));
77 return rangeDict.Pass();
80 // Returns true if |value| is an NSValue containing a NSRange.
81 bool IsRangeValue(id value) {
82 if (![value isKindOfClass:[NSValue class]])
84 return 0 == strcmp([value objCType], @encode(NSRange));
87 scoped_ptr<base::Value> PopulateObject(id value);
89 scoped_ptr<base::ListValue> PopulateArray(NSArray* array) {
90 scoped_ptr<base::ListValue> list(new base::ListValue);
91 for (NSUInteger i = 0; i < [array count]; i++)
92 list->Append(PopulateObject([array objectAtIndex:i]).release());
96 scoped_ptr<base::StringValue> StringForBrowserAccessibility(
97 BrowserAccessibilityCocoa* obj) {
98 NSMutableArray* tokens = [[NSMutableArray alloc] init];
100 // Always include the role
101 id role = [obj role];
102 [tokens addObject:role];
104 // If the role is "group", include the role description as well.
105 id roleDescription = [obj roleDescription];
106 if ([role isEqualToString:NSAccessibilityGroupRole] &&
107 roleDescription != nil &&
108 ![roleDescription isEqualToString:@""]) {
109 [tokens addObject:roleDescription];
112 // Include the description, title, or value - the first one not empty.
113 id title = [obj title];
114 id description = [obj description];
115 id value = [obj value];
116 if (description && ![description isEqual:@""]) {
117 [tokens addObject:description];
118 } else if (title && ![title isEqual:@""]) {
119 [tokens addObject:title];
120 } else if (value && ![value isEqual:@""]) {
121 [tokens addObject:value];
124 NSString* result = [tokens componentsJoinedByString:@" "];
125 return scoped_ptr<base::StringValue>(
126 new base::StringValue(SysNSStringToUTF16(result))).Pass();
129 scoped_ptr<base::Value> PopulateObject(id value) {
130 if ([value isKindOfClass:[NSArray class]])
131 return scoped_ptr<base::Value>(PopulateArray((NSArray*) value));
132 if (IsRangeValue(value))
133 return scoped_ptr<base::Value>(PopulateRange([value rangeValue]));
134 if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
136 StringForBrowserAccessibility(value)->GetAsString(&str);
137 return scoped_ptr<base::Value>(StringForBrowserAccessibility(
138 (BrowserAccessibilityCocoa*) value));
141 return scoped_ptr<base::Value>(
142 new base::StringValue(
143 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))).Pass();
146 NSArray* BuildAllAttributesArray() {
147 NSArray* array = [NSArray arrayWithObjects:
148 NSAccessibilityRoleDescriptionAttribute,
149 NSAccessibilityTitleAttribute,
150 NSAccessibilityValueAttribute,
151 NSAccessibilityMinValueAttribute,
152 NSAccessibilityMaxValueAttribute,
153 NSAccessibilityValueDescriptionAttribute,
154 NSAccessibilityDescriptionAttribute,
155 NSAccessibilityHelpAttribute,
157 NSAccessibilityDisclosingAttribute,
158 NSAccessibilityDisclosureLevelAttribute,
166 NSAccessibilityColumnIndexRangeAttribute,
168 NSAccessibilityEnabledAttribute,
169 NSAccessibilityExpandedAttribute,
170 NSAccessibilityFocusedAttribute,
172 NSAccessibilityIndexAttribute,
175 NSAccessibilityNumberOfCharactersAttribute,
176 NSAccessibilitySortDirectionAttribute,
177 NSAccessibilityOrientationAttribute,
178 NSAccessibilityPlaceholderValueAttribute,
180 NSAccessibilityRowIndexRangeAttribute,
181 NSAccessibilitySelectedChildrenAttribute,
182 NSAccessibilityTitleUIElementAttribute,
183 NSAccessibilityURLAttribute,
184 NSAccessibilityVisibleCharacterRangeAttribute,
185 NSAccessibilityVisibleChildrenAttribute,
187 @"AXLinkedUIElements",
189 return [array retain];
194 void AccessibilityTreeFormatter::Initialize() {
198 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
199 base::DictionaryValue* dict) {
200 dict->SetInteger("id", node.GetId());
201 BrowserAccessibilityCocoa* cocoa_node =
202 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
203 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
205 string role = SysNSStringToUTF8(
206 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
207 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
210 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
211 if (subrole != nil) {
212 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
213 SysNSStringToUTF8(subrole));
216 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
217 for (NSString* requestedAttribute in all_attributes) {
218 if (![supportedAttributes containsObject:requestedAttribute])
220 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
223 SysNSStringToUTF8(requestedAttribute),
224 PopulateObject(value).release());
227 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
228 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
231 base::string16 AccessibilityTreeFormatter::ToString(
232 const base::DictionaryValue& dict) {
236 dict.GetInteger("id", &id_value);
237 WriteAttribute(true, base::IntToString16(id_value), &line);
240 NSArray* defaultAttributes =
241 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
242 NSAccessibilityValueAttribute,
245 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
246 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
248 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
249 if (dict.GetString(subroleAttribute, &s_value)) {
250 WriteAttribute(false,
251 StringPrintf("%s=%s",
252 subroleAttribute.c_str(), s_value.c_str()),
256 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
257 for (NSString* requestedAttribute in all_attributes) {
258 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
259 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
260 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
261 StringPrintf("%s='%s'",
262 requestedAttributeUTF8.c_str(),
267 const base::Value* value;
268 if (dict.Get(requestedAttributeUTF8, &value)) {
269 std::string json_value;
270 base::JSONWriter::Write(*value, &json_value);
272 [defaultAttributes containsObject:requestedAttribute],
273 StringPrintf("%s=%s",
274 requestedAttributeUTF8.c_str(),
279 const base::DictionaryValue* d_value = NULL;
280 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
281 WriteAttribute(false,
282 FormatCoordinates(kPositionDictAttr,
283 kXCoordDictAttr, kYCoordDictAttr,
287 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
288 WriteAttribute(false,
289 FormatCoordinates(kSizeDictAttr,
290 kWidthDictAttr, kHeightDictAttr, *d_value),
298 const base::FilePath::StringType
299 AccessibilityTreeFormatter::GetActualFileSuffix() {
300 return FILE_PATH_LITERAL("-actual-mac.txt");
304 const base::FilePath::StringType
305 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
306 return FILE_PATH_LITERAL("-expected-mac.txt");
310 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
311 return "@MAC-ALLOW-EMPTY:";
315 const string AccessibilityTreeFormatter::GetAllowString() {
316 return "@MAC-ALLOW:";
320 const string AccessibilityTreeFormatter::GetDenyString() {
324 } // namespace content