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,
166 NSAccessibilityEnabledAttribute,
167 NSAccessibilityExpandedAttribute,
168 NSAccessibilityFocusedAttribute,
170 NSAccessibilityIndexAttribute,
173 NSAccessibilityNumberOfCharactersAttribute,
174 NSAccessibilitySortDirectionAttribute,
175 NSAccessibilityOrientationAttribute,
178 NSAccessibilityRowIndexRangeAttribute,
179 NSAccessibilitySelectedChildrenAttribute,
180 NSAccessibilityTitleUIElementAttribute,
181 NSAccessibilityURLAttribute,
182 NSAccessibilityVisibleCharacterRangeAttribute,
183 NSAccessibilityVisibleChildrenAttribute,
185 @"AXLinkedUIElements",
187 return [array retain];
192 void AccessibilityTreeFormatter::Initialize() {
196 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
197 base::DictionaryValue* dict) {
198 dict->SetInteger("id", node.GetId());
199 BrowserAccessibilityCocoa* cocoa_node =
200 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
201 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
203 string role = SysNSStringToUTF8(
204 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
205 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
208 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
209 if (subrole != nil) {
210 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
211 SysNSStringToUTF8(subrole));
214 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
215 for (NSString* requestedAttribute in all_attributes) {
216 if (![supportedAttributes containsObject:requestedAttribute])
218 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
221 SysNSStringToUTF8(requestedAttribute),
222 PopulateObject(value).release());
225 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
226 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
229 base::string16 AccessibilityTreeFormatter::ToString(
230 const base::DictionaryValue& dict) {
234 dict.GetInteger("id", &id_value);
235 WriteAttribute(true, base::IntToString16(id_value), &line);
238 NSArray* defaultAttributes =
239 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
240 NSAccessibilityValueAttribute,
243 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
244 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
246 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
247 if (dict.GetString(subroleAttribute, &s_value)) {
248 WriteAttribute(false,
249 StringPrintf("%s=%s",
250 subroleAttribute.c_str(), s_value.c_str()),
254 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
255 for (NSString* requestedAttribute in all_attributes) {
256 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
257 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
258 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
259 StringPrintf("%s='%s'",
260 requestedAttributeUTF8.c_str(),
265 const base::Value* value;
266 if (dict.Get(requestedAttributeUTF8, &value)) {
267 std::string json_value;
268 base::JSONWriter::Write(value, &json_value);
270 [defaultAttributes containsObject:requestedAttribute],
271 StringPrintf("%s=%s",
272 requestedAttributeUTF8.c_str(),
277 const base::DictionaryValue* d_value = NULL;
278 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
279 WriteAttribute(false,
280 FormatCoordinates(kPositionDictAttr,
281 kXCoordDictAttr, kYCoordDictAttr,
285 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
286 WriteAttribute(false,
287 FormatCoordinates(kSizeDictAttr,
288 kWidthDictAttr, kHeightDictAttr, *d_value),
296 const base::FilePath::StringType
297 AccessibilityTreeFormatter::GetActualFileSuffix() {
298 return FILE_PATH_LITERAL("-actual-mac.txt");
302 const base::FilePath::StringType
303 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
304 return FILE_PATH_LITERAL("-expected-mac.txt");
308 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
309 return "@MAC-ALLOW-EMPTY:";
313 const string AccessibilityTreeFormatter::GetAllowString() {
314 return "@MAC-ALLOW:";
318 const string AccessibilityTreeFormatter::GetDenyString() {
322 } // namespace content