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,
164 NSAccessibilityColumnIndexRangeAttribute,
165 NSAccessibilityEnabledAttribute,
166 NSAccessibilityExpandedAttribute,
167 NSAccessibilityFocusedAttribute,
169 NSAccessibilityIndexAttribute,
172 NSAccessibilityNumberOfCharactersAttribute,
173 NSAccessibilityOrientationAttribute,
176 NSAccessibilityRowIndexRangeAttribute,
177 NSAccessibilitySelectedChildrenAttribute,
178 NSAccessibilityTitleUIElementAttribute,
179 NSAccessibilityURLAttribute,
180 NSAccessibilityVisibleCharacterRangeAttribute,
181 NSAccessibilityVisibleChildrenAttribute,
183 @"AXLinkedUIElements",
185 return [array retain];
190 void AccessibilityTreeFormatter::Initialize() {
194 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
195 base::DictionaryValue* dict) {
196 dict->SetInteger("id", node.GetId());
197 BrowserAccessibilityCocoa* cocoa_node =
198 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
199 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
201 string role = SysNSStringToUTF8(
202 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
203 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
206 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
207 if (subrole != nil) {
208 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
209 SysNSStringToUTF8(subrole));
212 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
213 for (NSString* requestedAttribute in all_attributes) {
214 if (![supportedAttributes containsObject:requestedAttribute])
216 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
219 SysNSStringToUTF8(requestedAttribute),
220 PopulateObject(value).release());
223 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
224 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
227 base::string16 AccessibilityTreeFormatter::ToString(
228 const base::DictionaryValue& dict) {
232 dict.GetInteger("id", &id_value);
233 WriteAttribute(true, base::IntToString16(id_value), &line);
236 NSArray* defaultAttributes =
237 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
238 NSAccessibilityValueAttribute,
241 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
242 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
244 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
245 if (dict.GetString(subroleAttribute, &s_value)) {
246 WriteAttribute(false,
247 StringPrintf("%s=%s",
248 subroleAttribute.c_str(), s_value.c_str()),
252 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
253 for (NSString* requestedAttribute in all_attributes) {
254 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
255 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
256 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
257 StringPrintf("%s='%s'",
258 requestedAttributeUTF8.c_str(),
263 const base::Value* value;
264 if (dict.Get(requestedAttributeUTF8, &value)) {
265 std::string json_value;
266 base::JSONWriter::Write(value, &json_value);
268 [defaultAttributes containsObject:requestedAttribute],
269 StringPrintf("%s=%s",
270 requestedAttributeUTF8.c_str(),
275 const base::DictionaryValue* d_value = NULL;
276 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
277 WriteAttribute(false,
278 FormatCoordinates(kPositionDictAttr,
279 kXCoordDictAttr, kYCoordDictAttr,
283 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
284 WriteAttribute(false,
285 FormatCoordinates(kSizeDictAttr,
286 kWidthDictAttr, kHeightDictAttr, *d_value),
294 const base::FilePath::StringType
295 AccessibilityTreeFormatter::GetActualFileSuffix() {
296 return FILE_PATH_LITERAL("-actual-mac.txt");
300 const base::FilePath::StringType
301 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
302 return FILE_PATH_LITERAL("-expected-mac.txt");
306 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
307 return "@MAC-ALLOW-EMPTY:";
311 const string AccessibilityTreeFormatter::GetAllowString() {
312 return "@MAC-ALLOW:";
316 const string AccessibilityTreeFormatter::GetDenyString() {
320 } // namespace content