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 NSAccessibilityFocusedAttribute,
167 NSAccessibilityIndexAttribute,
170 NSAccessibilityNumberOfCharactersAttribute,
171 NSAccessibilityOrientationAttribute,
174 NSAccessibilityRowIndexRangeAttribute,
175 NSAccessibilitySelectedChildrenAttribute,
176 NSAccessibilityTitleUIElementAttribute,
177 NSAccessibilityURLAttribute,
178 NSAccessibilityVisibleCharacterRangeAttribute,
179 NSAccessibilityVisibleChildrenAttribute,
181 @"AXLinkedUIElements",
182 NSAccessibilityExpandedAttribute,
184 return [array retain];
189 void AccessibilityTreeFormatter::Initialize() {
193 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
194 base::DictionaryValue* dict) {
195 dict->SetInteger("id", node.GetId());
196 BrowserAccessibilityCocoa* cocoa_node =
197 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
198 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
200 string role = SysNSStringToUTF8(
201 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
202 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
205 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
206 if (subrole != nil) {
207 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
208 SysNSStringToUTF8(subrole));
211 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
212 for (NSString* requestedAttribute in all_attributes) {
213 if (![supportedAttributes containsObject:requestedAttribute])
215 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
218 SysNSStringToUTF8(requestedAttribute),
219 PopulateObject(value).release());
222 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
223 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
226 base::string16 AccessibilityTreeFormatter::ToString(
227 const base::DictionaryValue& dict) {
231 dict.GetInteger("id", &id_value);
232 WriteAttribute(true, base::IntToString16(id_value), &line);
235 NSArray* defaultAttributes =
236 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
237 NSAccessibilityValueAttribute,
240 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
241 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
243 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
244 if (dict.GetString(subroleAttribute, &s_value)) {
245 WriteAttribute(false,
246 StringPrintf("%s=%s",
247 subroleAttribute.c_str(), s_value.c_str()),
251 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
252 for (NSString* requestedAttribute in all_attributes) {
253 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
254 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
255 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
256 StringPrintf("%s='%s'",
257 requestedAttributeUTF8.c_str(),
262 const base::Value* value;
263 if (dict.Get(requestedAttributeUTF8, &value)) {
264 std::string json_value;
265 base::JSONWriter::Write(value, &json_value);
267 [defaultAttributes containsObject:requestedAttribute],
268 StringPrintf("%s=%s",
269 requestedAttributeUTF8.c_str(),
274 const base::DictionaryValue* d_value = NULL;
275 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
276 WriteAttribute(false,
277 FormatCoordinates(kPositionDictAttr,
278 kXCoordDictAttr, kYCoordDictAttr,
282 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
283 WriteAttribute(false,
284 FormatCoordinates(kSizeDictAttr,
285 kWidthDictAttr, kHeightDictAttr, *d_value),
293 const base::FilePath::StringType
294 AccessibilityTreeFormatter::GetActualFileSuffix() {
295 return FILE_PATH_LITERAL("-actual-mac.txt");
299 const base::FilePath::StringType
300 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
301 return FILE_PATH_LITERAL("-expected-mac.txt");
305 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
306 return "@MAC-ALLOW-EMPTY:";
310 const string AccessibilityTreeFormatter::GetAllowString() {
311 return "@MAC-ALLOW:";
315 const string AccessibilityTreeFormatter::GetDenyString() {
319 } // namespace content