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. 4 5 #include "content/browser/accessibility/accessibility_tree_formatter.h" 6 7 #import <Cocoa/Cocoa.h> 8 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" 18 19 using base::StringPrintf; 20 using base::SysNSStringToUTF8; 21 using base::SysNSStringToUTF16; 22 using std::string; 23 24 namespace content { 25 26 namespace { 27 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"; 36 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); 50 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]; 55 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(); 61 } 62 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(); 70 } 71 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(); 77 } 78 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)); 84 } 85 86 scoped_ptr<base::Value> PopulateObject(id value); 87 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(); 93 } 94 95 scoped_ptr<base::StringValue> StringForBrowserAccessibility( 96 BrowserAccessibilityCocoa* obj) { 97 NSMutableArray* tokens = [[NSMutableArray alloc] init]; 98 99 // Always include the role 100 id role = [obj role]; 101 [tokens addObject:role]; 102 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 } 110 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 } 122 123 NSString* result = [tokens componentsJoinedByString:@" "]; 124 return scoped_ptr<base::StringValue>( 125 new base::StringValue(SysNSStringToUTF16(result))).Pass(); 126 } 127 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 } 139 140 return scoped_ptr<base::Value>( 141 new base::StringValue( 142 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))).Pass(); 143 } 144 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 nil]; 181 return [array retain]; 182 } 183 184 } // namespace 185 186 void AccessibilityTreeFormatter::Initialize() { 187 } 188 189 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]; 195 196 string role = SysNSStringToUTF8( 197 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]); 198 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role); 199 200 NSString* subrole = 201 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; 202 if (subrole != nil) { 203 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute), 204 SysNSStringToUTF8(subrole)); 205 } 206 207 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); 208 for (NSString* requestedAttribute in all_attributes) { 209 if (![supportedAttributes containsObject:requestedAttribute]) 210 continue; 211 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; 212 if (value != nil) { 213 dict->Set( 214 SysNSStringToUTF8(requestedAttribute), 215 PopulateObject(value).release()); 216 } 217 } 218 dict->Set(kPositionDictAttr, PopulatePosition(node).release()); 219 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release()); 220 } 221 222 base::string16 AccessibilityTreeFormatter::ToString( 223 const base::DictionaryValue& dict, 224 const base::string16& indent) { 225 base::string16 line; 226 NSArray* defaultAttributes = 227 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute, 228 NSAccessibilityValueAttribute, 229 nil]; 230 string s_value; 231 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value); 232 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line); 233 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()), 239 &line); 240 } 241 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(), 249 s_value.c_str()), 250 &line); 251 continue; 252 } 253 const base::Value* value; 254 if (dict.Get(requestedAttributeUTF8, &value)) { 255 std::string json_value; 256 base::JSONWriter::Write(value, &json_value); 257 WriteAttribute( 258 [defaultAttributes containsObject:requestedAttribute], 259 StringPrintf("%s=%s", 260 requestedAttributeUTF8.c_str(), 261 json_value.c_str()), 262 &line); 263 } 264 } 265 const base::DictionaryValue* d_value = NULL; 266 if (dict.GetDictionary(kPositionDictAttr, &d_value)) { 267 WriteAttribute(false, 268 FormatCoordinates(kPositionDictAttr, 269 kXCoordDictAttr, kYCoordDictAttr, 270 *d_value), 271 &line); 272 } 273 if (dict.GetDictionary(kSizeDictAttr, &d_value)) { 274 WriteAttribute(false, 275 FormatCoordinates(kSizeDictAttr, 276 kWidthDictAttr, kHeightDictAttr, *d_value), 277 &line); 278 } 279 280 return indent + line + base::ASCIIToUTF16("\n"); 281 } 282 283 // static 284 const base::FilePath::StringType 285 AccessibilityTreeFormatter::GetActualFileSuffix() { 286 return FILE_PATH_LITERAL("-actual-mac.txt"); 287 } 288 289 // static 290 const base::FilePath::StringType 291 AccessibilityTreeFormatter::GetExpectedFileSuffix() { 292 return FILE_PATH_LITERAL("-expected-mac.txt"); 293 } 294 295 // static 296 const string AccessibilityTreeFormatter::GetAllowEmptyString() { 297 return "@MAC-ALLOW-EMPTY:"; 298 } 299 300 // static 301 const string AccessibilityTreeFormatter::GetAllowString() { 302 return "@MAC-ALLOW:"; 303 } 304 305 // static 306 const string AccessibilityTreeFormatter::GetDenyString() { 307 return "@MAC-DENY:"; 308 } 309 310 } // namespace content 311