Add ICU message format support
[chromium-blink-merge.git] / content / browser / accessibility / accessibility_tree_formatter_mac.mm
blob62462428ec0240317808db0c9b4f41bd17327bfe
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;
23 using std::string;
25 namespace content {
27 namespace {
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));
70   return size.Pass();
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]])
83     return false;
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());
93   return list.Pass();
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];
110   }
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];
122   }
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]]) {
135     std::string str;
136     StringForBrowserAccessibility(value)->GetAsString(&str);
137     return scoped_ptr<base::Value>(StringForBrowserAccessibility(
138         (BrowserAccessibilityCocoa*) value));
139   }
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,
156       @"AXInvalid",
157       NSAccessibilityDisclosingAttribute,
158       NSAccessibilityDisclosureLevelAttribute,
159       @"AXAccessKey",
160       @"AXARIAAtomic",
161       @"AXARIABusy",
162       @"AXARIALive",
163       @"AXARIARelevant",
164       @"AXARIASetSize",
165       @"AXARIAPosInSet",
166       NSAccessibilityColumnIndexRangeAttribute,
167       @"AXDropEffects",
168       NSAccessibilityEnabledAttribute,
169       NSAccessibilityExpandedAttribute,
170       NSAccessibilityFocusedAttribute,
171       @"AXGrabbed",
172       NSAccessibilityIndexAttribute,
173       @"AXLoaded",
174       @"AXLoadingProcess",
175       NSAccessibilityNumberOfCharactersAttribute,
176       NSAccessibilitySortDirectionAttribute,
177       NSAccessibilityOrientationAttribute,
178       NSAccessibilityPlaceholderValueAttribute,
179       @"AXRequired",
180       NSAccessibilityRowIndexRangeAttribute,
181       NSAccessibilitySelectedChildrenAttribute,
182       NSAccessibilityTitleUIElementAttribute,
183       NSAccessibilityURLAttribute,
184       NSAccessibilityVisibleCharacterRangeAttribute,
185       NSAccessibilityVisibleChildrenAttribute,
186       @"AXVisited",
187       @"AXLinkedUIElements",
188       nil];
189   return [array retain];
192 }  // namespace
194 void AccessibilityTreeFormatter::Initialize() {
198 void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
199                                                base::DictionaryValue* dict) {
200   dict->SetInteger("id", node.GetId());
201   BrowserAccessibilityCocoa* cocoa_node =
202       const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
203   NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
205   string role = SysNSStringToUTF8(
206       [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]);
207   dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role);
209   NSString* subrole =
210       [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute];
211   if (subrole != nil) {
212     dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute),
213                     SysNSStringToUTF8(subrole));
214   }
216   CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
217   for (NSString* requestedAttribute in all_attributes) {
218     if (![supportedAttributes containsObject:requestedAttribute])
219       continue;
220     id value = [cocoa_node accessibilityAttributeValue:requestedAttribute];
221     if (value != nil) {
222       dict->Set(
223           SysNSStringToUTF8(requestedAttribute),
224           PopulateObject(value).release());
225     }
226   }
227   dict->Set(kPositionDictAttr, PopulatePosition(node).release());
228   dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
231 base::string16 AccessibilityTreeFormatter::ToString(
232     const base::DictionaryValue& dict) {
233   base::string16 line;
234   if (show_ids_) {
235     int id_value;
236     dict.GetInteger("id", &id_value);
237     WriteAttribute(true, base::IntToString16(id_value), &line);
238   }
240   NSArray* defaultAttributes =
241       [NSArray arrayWithObjects:NSAccessibilityTitleAttribute,
242                                 NSAccessibilityValueAttribute,
243                                 nil];
244   string s_value;
245   dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value);
246   WriteAttribute(true, base::UTF8ToUTF16(s_value), &line);
248   string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute);
249   if (dict.GetString(subroleAttribute, &s_value)) {
250     WriteAttribute(false,
251                    StringPrintf("%s=%s",
252                                 subroleAttribute.c_str(), s_value.c_str()),
253                    &line);
254   }
256   CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray()));
257   for (NSString* requestedAttribute in all_attributes) {
258     string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute);
259     if (dict.GetString(requestedAttributeUTF8, &s_value)) {
260       WriteAttribute([defaultAttributes containsObject:requestedAttribute],
261                      StringPrintf("%s='%s'",
262                                   requestedAttributeUTF8.c_str(),
263                                   s_value.c_str()),
264                      &line);
265       continue;
266     }
267     const base::Value* value;
268     if (dict.Get(requestedAttributeUTF8, &value)) {
269       std::string json_value;
270       base::JSONWriter::Write(*value, &json_value);
271       WriteAttribute(
272           [defaultAttributes containsObject:requestedAttribute],
273           StringPrintf("%s=%s",
274                        requestedAttributeUTF8.c_str(),
275                        json_value.c_str()),
276           &line);
277     }
278   }
279   const base::DictionaryValue* d_value = NULL;
280   if (dict.GetDictionary(kPositionDictAttr, &d_value)) {
281     WriteAttribute(false,
282                    FormatCoordinates(kPositionDictAttr,
283                                      kXCoordDictAttr, kYCoordDictAttr,
284                                      *d_value),
285                    &line);
286   }
287   if (dict.GetDictionary(kSizeDictAttr, &d_value)) {
288     WriteAttribute(false,
289                    FormatCoordinates(kSizeDictAttr,
290                                      kWidthDictAttr, kHeightDictAttr, *d_value),
291                    &line);
292   }
294   return line;
297 // static
298 const base::FilePath::StringType
299 AccessibilityTreeFormatter::GetActualFileSuffix() {
300   return FILE_PATH_LITERAL("-actual-mac.txt");
303 // static
304 const base::FilePath::StringType
305 AccessibilityTreeFormatter::GetExpectedFileSuffix() {
306   return FILE_PATH_LITERAL("-expected-mac.txt");
309 // static
310 const string AccessibilityTreeFormatter::GetAllowEmptyString() {
311   return "@MAC-ALLOW-EMPTY:";
314 // static
315 const string AccessibilityTreeFormatter::GetAllowString() {
316   return "@MAC-ALLOW:";
319 // static
320 const string AccessibilityTreeFormatter::GetDenyString() {
321   return "@MAC-DENY:";
324 }  // namespace content