Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / browser / accessibility / accessibility_tree_formatter_mac.mm
blob7d5c1bf1ee1de5e011aca22c046aa94206c07e9d
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;
22 using std::string;
24 namespace content {
26 namespace {
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));
69   return size.Pass();
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]])
82     return false;
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());
92   return list.Pass();
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];
109   }
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];
121   }
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]]) {
134     std::string str;
135     StringForBrowserAccessibility(value)->GetAsString(&str);
136     return scoped_ptr<base::Value>(StringForBrowserAccessibility(
137         (BrowserAccessibilityCocoa*) value));
138   }
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,
155       @"AXInvalid",
156       NSAccessibilityDisclosingAttribute,
157       NSAccessibilityDisclosureLevelAttribute,
158       @"AXAccessKey",
159       @"AXARIAAtomic",
160       @"AXARIABusy",
161       @"AXARIALive",
162       @"AXARIARelevant",
163       NSAccessibilityColumnIndexRangeAttribute,
164       NSAccessibilityEnabledAttribute,
165       NSAccessibilityFocusedAttribute,
166       NSAccessibilityIndexAttribute,
167       @"AXLoaded",
168       @"AXLoadingProcess",
169       NSAccessibilityNumberOfCharactersAttribute,
170       NSAccessibilityOrientationAttribute,
171       @"AXRequired",
172       NSAccessibilityRowIndexRangeAttribute,
173       NSAccessibilitySelectedChildrenAttribute,
174       NSAccessibilityTitleUIElementAttribute,
175       NSAccessibilityURLAttribute,
176       NSAccessibilityVisibleCharacterRangeAttribute,
177       NSAccessibilityVisibleChildrenAttribute,
178       @"AXVisited",
179       @"AXLinkedUIElements",
180       NSAccessibilityExpandedAttribute,
181       nil];
182   return [array retain];
185 }  // namespace
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);
201   NSString* subrole =
202       [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
203   if (subrole != nil) {
204     dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
205                     SysNSStringToUTF8(subrole));
206   }
208   CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
209   for (NSString* requestedAttribute in all_attributes) {
210     if (![supportedAttributes containsObject:requestedAttribute])
211       continue;
212     id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
213     if (value != nil) {
214       dict->Set(
215           SysNSStringToUTF8(requestedAttribute),
216           PopulateObject(value).release());
217     }
218   }
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) {
226   base::string16 line;
227   NSArray* defaultAttributes =
228       [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
229                                 NSAccessibilityValueAttribute,
230                                 nil];
231   string s_value;
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()),
240                    &line);
241   }
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(),
250                                   s_value.c_str()),
251                      &line);
252       continue;
253     }
254     const base::Value* value;
255     if (dict.Get(requestedAttributeUTF8, &value)) {
256       std::string json_value;
257       base::JSONWriter::Write(value, &json_value);
258       WriteAttribute(
259           [defaultAttributes containsObject:requestedAttribute],
260           StringPrintf("%s=%s",
261                        requestedAttributeUTF8.c_str(),
262                        json_value.c_str()),
263           &line);
264     }
265   }
266   const base::DictionaryValue* d_value = NULL;
267   if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
268     WriteAttribute(false,
269                    FormatCoordinates(kPositionDictAttr,
270                                      kXCoordDictAttr, kYCoordDictAttr,
271                                      *d_value),
272                    &line);
273   }
274   if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
275     WriteAttribute(false,
276                    FormatCoordinates(kSizeDictAttr,
277                                      kWidthDictAttr, kHeightDictAttr, *d_value),
278                    &line);
279   }
281   return indent + line + base::ASCIIToUTF16("\n");
284 // static
285 const base::FilePath::StringType
286 AccessibilityTreeFormatter::GetActualFileSuffix() {
287   return FILE_PATH_LITERAL("-actual-mac.txt");
290 // static
291 const base::FilePath::StringType
292 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
293   return FILE_PATH_LITERAL("-expected-mac.txt");
296 // static
297 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
298   return "@MAC-ALLOW-EMPTY:";
301 // static
302 const string AccessibilityTreeFormatter::GetAllowString() {
303   return "@MAC-ALLOW:";
306 // static
307 const string AccessibilityTreeFormatter::GetDenyString() {
308   return "@MAC-DENY:";
311 }  // namespace content