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/stringprintf.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "content/browser/accessibility/browser_accessibility_cocoa.h"
16 #include "content/browser/accessibility/browser_accessibility_mac.h"
17 #include "content/browser/accessibility/browser_accessibility_manager.h"
19 using base::StringPrintf;
20 using base::SysNSStringToUTF8;
21 using base::SysNSStringToUTF16;
28 const char* kPositionDictAttr = "position";
29 const char* kXCoordDictAttr = "x";
30 const char* kYCoordDictAttr = "y";
31 const char* kSizeDictAttr = "size";
32 const char* kWidthDictAttr = "width";
33 const char* kHeightDictAttr = "height";
34 const char* kRangeLocDictAttr = "loc";
35 const char* kRangeLenDictAttr = "len";
37 scoped_ptr<base::DictionaryValue> PopulatePosition(
38 const BrowserAccessibility& node) {
39 scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue);
40 // The NSAccessibility position of an object is in global coordinates and
41 // based on the lower-left corner of the object. To make this easier and less
42 // confusing, convert it to local window coordinates using the top-left
43 // corner when dumping the position.
44 BrowserAccessibility* root = node.manager()->GetRoot();
45 BrowserAccessibilityCocoa* cocoa_root = root->ToBrowserAccessibilityCocoa();
46 NSPoint root_position = [[cocoa_root position] pointValue];
47 NSSize root_size = [[cocoa_root size] sizeValue];
48 int root_top = -static_cast<int>(root_position.y + root_size.height);
49 int root_left = static_cast<int>(root_position.x);
51 BrowserAccessibilityCocoa* cocoa_node =
52 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
53 NSPoint node_position = [[cocoa_node position] pointValue];
54 NSSize node_size = [[cocoa_node size] sizeValue];
56 position->SetInteger(kXCoordDictAttr,
57 static_cast<int>(node_position.x - root_left));
58 position->SetInteger(kYCoordDictAttr,
59 static_cast<int>(-node_position.y - node_size.height - root_top));
60 return position.Pass();
63 scoped_ptr<base::DictionaryValue>
64 PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) {
65 scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue);
66 NSSize node_size = [[cocoa_node size] sizeValue];
67 size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height));
68 size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width));
72 scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) {
73 scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue);
74 rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location));
75 rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length));
76 return rangeDict.Pass();
79 // Returns true if |value| is an NSValue containing a NSRange.
80 bool IsRangeValue(id value) {
81 if (![value isKindOfClass:[NSValue class]])
83 return 0 == strcmp([value objCType], @encode(NSRange));
86 scoped_ptr<base::Value> PopulateObject(id value);
88 scoped_ptr<base::ListValue> PopulateArray(NSArray* array) {
89 scoped_ptr<base::ListValue> list(new base::ListValue);
90 for (NSUInteger i = 0; i < [array count]; i++)
91 list->Append(PopulateObject([array objectAtIndex:i]).release());
95 scoped_ptr<base::StringValue> StringForBrowserAccessibility(
96 BrowserAccessibilityCocoa* obj) {
97 NSMutableArray* tokens = [[NSMutableArray alloc] init];
99 // Always include the role
100 id role = [obj role];
101 [tokens addObject:role];
103 // If the role is "group", include the role description as well.
104 id roleDescription = [obj roleDescription];
105 if ([role isEqualToString:NSAccessibilityGroupRole] &&
106 roleDescription != nil &&
107 ![roleDescription isEqualToString:@""]) {
108 [tokens addObject:roleDescription];
111 // Include the description, title, or value - the first one not empty.
112 id title = [obj title];
113 id description = [obj description];
114 id value = [obj value];
115 if (description && ![description isEqual:@""]) {
116 [tokens addObject:description];
117 } else if (title && ![title isEqual:@""]) {
118 [tokens addObject:title];
119 } else if (value && ![value isEqual:@""]) {
120 [tokens addObject:value];
123 NSString* result = [tokens componentsJoinedByString:@" "];
124 return scoped_ptr<base::StringValue>(
125 new base::StringValue(SysNSStringToUTF16(result))).Pass();
128 scoped_ptr<base::Value> PopulateObject(id value) {
129 if ([value isKindOfClass:[NSArray class]])
130 return scoped_ptr<base::Value>(PopulateArray((NSArray*) value));
131 if (IsRangeValue(value))
132 return scoped_ptr<base::Value>(PopulateRange([value rangeValue]));
133 if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
135 StringForBrowserAccessibility(value)->GetAsString(&str);
136 return scoped_ptr<base::Value>(StringForBrowserAccessibility(
137 (BrowserAccessibilityCocoa*) value));
140 return scoped_ptr<base::Value>(
141 new base::StringValue(
142 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))).Pass();
145 NSArray* BuildAllAttributesArray() {
146 NSArray* array = [NSArray arrayWithObjects:
147 NSAccessibilityRoleDescriptionAttribute,
148 NSAccessibilityTitleAttribute,
149 NSAccessibilityValueAttribute,
150 NSAccessibilityMinValueAttribute,
151 NSAccessibilityMaxValueAttribute,
152 NSAccessibilityValueDescriptionAttribute,
153 NSAccessibilityDescriptionAttribute,
154 NSAccessibilityHelpAttribute,
156 NSAccessibilityDisclosingAttribute,
157 NSAccessibilityDisclosureLevelAttribute,
163 NSAccessibilityColumnIndexRangeAttribute,
164 NSAccessibilityEnabledAttribute,
165 NSAccessibilityFocusedAttribute,
166 NSAccessibilityIndexAttribute,
169 NSAccessibilityNumberOfCharactersAttribute,
170 NSAccessibilityOrientationAttribute,
172 NSAccessibilityRowIndexRangeAttribute,
173 NSAccessibilitySelectedChildrenAttribute,
174 NSAccessibilityTitleUIElementAttribute,
175 NSAccessibilityURLAttribute,
176 NSAccessibilityVisibleCharacterRangeAttribute,
177 NSAccessibilityVisibleChildrenAttribute,
179 @"AXLinkedUIElements",
180 NSAccessibilityExpandedAttribute,
182 return [array retain];
187 void AccessibilityTreeFormatter::Initialize() {
191 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
192 base::DictionaryValue* dict) {
193 BrowserAccessibilityCocoa* cocoa_node =
194 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
195 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
197 string role = SysNSStringToUTF8(
198 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
199 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
202 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
203 if (subrole != nil) {
204 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
205 SysNSStringToUTF8(subrole));
208 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
209 for (NSString* requestedAttribute in all_attributes) {
210 if (![supportedAttributes containsObject:requestedAttribute])
212 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
215 SysNSStringToUTF8(requestedAttribute),
216 PopulateObject(value).release());
219 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
220 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
223 base::string16 AccessibilityTreeFormatter::ToString(
224 const base::DictionaryValue& dict,
225 const base::string16& indent) {
227 NSArray* defaultAttributes =
228 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
229 NSAccessibilityValueAttribute,
232 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
233 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
235 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
236 if (dict.GetString(subroleAttribute, &s_value)) {
237 WriteAttribute(false,
238 StringPrintf("%s=%s",
239 subroleAttribute.c_str(), s_value.c_str()),
243 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
244 for (NSString* requestedAttribute in all_attributes) {
245 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
246 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
247 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
248 StringPrintf("%s='%s'",
249 requestedAttributeUTF8.c_str(),
254 const base::Value* value;
255 if (dict.Get(requestedAttributeUTF8, &value)) {
256 std::string json_value;
257 base::JSONWriter::Write(value, &json_value);
259 [defaultAttributes containsObject:requestedAttribute],
260 StringPrintf("%s=%s",
261 requestedAttributeUTF8.c_str(),
266 const base::DictionaryValue* d_value = NULL;
267 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
268 WriteAttribute(false,
269 FormatCoordinates(kPositionDictAttr,
270 kXCoordDictAttr, kYCoordDictAttr,
274 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
275 WriteAttribute(false,
276 FormatCoordinates(kSizeDictAttr,
277 kWidthDictAttr, kHeightDictAttr, *d_value),
281 return indent + line + base::ASCIIToUTF16("\n");
285 const base::FilePath::StringType
286 AccessibilityTreeFormatter::GetActualFileSuffix() {
287 return FILE_PATH_LITERAL("-actual-mac.txt");
291 const base::FilePath::StringType
292 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
293 return FILE_PATH_LITERAL("-expected-mac.txt");
297 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
298 return "@MAC-ALLOW-EMPTY:";
302 const string AccessibilityTreeFormatter::GetAllowString() {
303 return "@MAC-ALLOW:";
307 const string AccessibilityTreeFormatter::GetDenyString() {
311 } // namespace content