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 NSString* description = [obj role];
98 id value = [obj value];
99 id roleDescription = [obj roleDescription];
100 if (value != nil && ![value isEqualToString:@""]) {
101 description = [NSString stringWithFormat:@"%@ %@", description, value];
102 } else if ([description isEqualToString:NSAccessibilityGroupRole] &&
103 roleDescription != nil &&
104 ![roleDescription isEqualToString:@""]) {
105 description = [NSString stringWithFormat:@"%@ %@",
106 description, roleDescription];
108 return scoped_ptr<base::StringValue>(
109 new base::StringValue(SysNSStringToUTF16(description))).Pass();
112 scoped_ptr<base::Value> PopulateObject(id value) {
113 if ([value isKindOfClass:[NSArray class]])
114 return scoped_ptr<base::Value>(PopulateArray((NSArray*) value));
115 if (IsRangeValue(value))
116 return scoped_ptr<base::Value>(PopulateRange([value rangeValue]));
117 if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) {
119 StringForBrowserAccessibility(value)->GetAsString(&str);
120 return scoped_ptr<base::Value>(StringForBrowserAccessibility(
121 (BrowserAccessibilityCocoa*) value));
124 return scoped_ptr<base::Value>(
125 new base::StringValue(
126 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))).Pass();
129 NSArray* BuildAllAttributesArray() {
130 NSArray* array = [NSArray arrayWithObjects:
131 NSAccessibilityRoleDescriptionAttribute,
132 NSAccessibilityTitleAttribute,
133 NSAccessibilityValueAttribute,
134 NSAccessibilityMinValueAttribute,
135 NSAccessibilityMaxValueAttribute,
136 NSAccessibilityValueDescriptionAttribute,
137 NSAccessibilityDescriptionAttribute,
138 NSAccessibilityHelpAttribute,
140 NSAccessibilityDisclosingAttribute,
141 NSAccessibilityDisclosureLevelAttribute,
147 NSAccessibilityColumnIndexRangeAttribute,
148 NSAccessibilityEnabledAttribute,
149 NSAccessibilityFocusedAttribute,
150 NSAccessibilityIndexAttribute,
153 NSAccessibilityNumberOfCharactersAttribute,
154 NSAccessibilityOrientationAttribute,
156 NSAccessibilityRowIndexRangeAttribute,
157 NSAccessibilityTitleUIElementAttribute,
158 NSAccessibilityURLAttribute,
159 NSAccessibilityVisibleCharacterRangeAttribute,
161 @"AXLinkedUIElements",
163 return [array retain];
168 void AccessibilityTreeFormatter::Initialize() {
172 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
173 base::DictionaryValue* dict) {
174 BrowserAccessibilityCocoa* cocoa_node =
175 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
176 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
178 string role = SysNSStringToUTF8(
179 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
180 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
183 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
184 if (subrole != nil) {
185 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
186 SysNSStringToUTF8(subrole));
189 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
190 for (NSString* requestedAttribute in all_attributes) {
191 if (![supportedAttributes containsObject:requestedAttribute])
193 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
196 SysNSStringToUTF8(requestedAttribute),
197 PopulateObject(value).release());
200 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
201 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
204 base::string16 AccessibilityTreeFormatter::ToString(
205 const base::DictionaryValue& dict,
206 const base::string16& indent) {
208 NSArray* defaultAttributes =
209 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
210 NSAccessibilityValueAttribute,
213 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
214 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
216 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
217 if (dict.GetString(subroleAttribute, &s_value)) {
218 WriteAttribute(false,
219 StringPrintf("%s=%s",
220 subroleAttribute.c_str(), s_value.c_str()),
224 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
225 for (NSString* requestedAttribute in all_attributes) {
226 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
227 if (dict.GetString(requestedAttributeUTF8, &s_value)) {
228 WriteAttribute([defaultAttributes containsObject:requestedAttribute],
229 StringPrintf("%s='%s'",
230 requestedAttributeUTF8.c_str(),
235 const base::Value* value;
236 if (dict.Get(requestedAttributeUTF8, &value)) {
237 std::string json_value;
238 base::JSONWriter::Write(value, &json_value);
240 [defaultAttributes containsObject:requestedAttribute],
241 StringPrintf("%s=%s",
242 requestedAttributeUTF8.c_str(),
247 const base::DictionaryValue* d_value = NULL;
248 if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
249 WriteAttribute(false,
250 FormatCoordinates(kPositionDictAttr,
251 kXCoordDictAttr, kYCoordDictAttr,
255 if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
256 WriteAttribute(false,
257 FormatCoordinates(kSizeDictAttr,
258 kWidthDictAttr, kHeightDictAttr, *d_value),
262 return indent + line + base::ASCIIToUTF16("\n");
266 const base::FilePath::StringType
267 AccessibilityTreeFormatter::GetActualFileSuffix() {
268 return FILE_PATH_LITERAL("-actual-mac.txt");
272 const base::FilePath::StringType
273 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
274 return FILE_PATH_LITERAL("-expected-mac.txt");
278 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
279 return "@MAC-ALLOW-EMPTY:";
283 const string AccessibilityTreeFormatter::GetAllowString() {
284 return "@MAC-ALLOW:";
288 const string AccessibilityTreeFormatter::GetDenyString() {
292 } // namespace content