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",
181 return [array retain];
186 void AccessibilityTreeFormatter::Initialize() {
190 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
191 base::DictionaryValue* dict) {
192 BrowserAccessibilityCocoa* cocoa_node =
193 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
194 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
196 string role = SysNSStringToUTF8(
197 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
198 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
201 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
202 if (subrole != nil) {
203 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
204 SysNSStringToUTF8(subrole));
207 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
208 for (NSString* requestedAttribute in all_attributes) {
209 if (![supportedAttributes containsObject:requestedAttribute])
211 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
214 SysNSStringToUTF8(requestedAttribute),
215 PopulateObject(value).release());
218 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
219 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
222 base::string16 AccessibilityTreeFormatter::ToString(
223 const base::DictionaryValue& dict,
224 const base::string16& indent) {
226 NSArray* defaultAttributes =
227 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
228 NSAccessibilityValueAttribute,
231 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
232 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
234 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
235 if (dict.GetString(subroleAttribute, &s_value)) {
236 WriteAttribute(false,
237 StringPrintf("%s=%s",
238 subroleAttribute.c_str(), s_value.c_str()),
242 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
243 for (NSString* requestedAttribute in all_attributes) {
244 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
245 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
246 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
247 StringPrintf("%s='%s'",
248 requestedAttributeUTF8.c_str(),
253 const base::Value* value;
254 if (dict.Get(requestedAttributeUTF8, &value)) {
255 std::string json_value;
256 base::JSONWriter::Write(value, &json_value);
258 [defaultAttributes containsObject:requestedAttribute],
259 StringPrintf("%s=%s",
260 requestedAttributeUTF8.c_str(),
265 const base::DictionaryValue* d_value = NULL;
266 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
267 WriteAttribute(false,
268 FormatCoordinates(kPositionDictAttr,
269 kXCoordDictAttr, kYCoordDictAttr,
273 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
274 WriteAttribute(false,
275 FormatCoordinates(kSizeDictAttr,
276 kWidthDictAttr, kHeightDictAttr, *d_value),
280 return indent + line + base::ASCIIToUTF16("\n");
284 const base::FilePath::StringType
285 AccessibilityTreeFormatter::GetActualFileSuffix() {
286 return FILE_PATH_LITERAL("-actual-mac.txt");
290 const base::FilePath::StringType
291 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
292 return FILE_PATH_LITERAL("-expected-mac.txt");
296 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
297 return "@MAC-ALLOW-EMPTY:";
301 const string AccessibilityTreeFormatter::GetAllowString() {
302 return "@MAC-ALLOW:";
306 const string AccessibilityTreeFormatter::GetDenyString() {
310 } // namespace content