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 NSString* description = [obj role]; 98 id value = [obj value]; 99 id roleDescription = [obj roleDescription]; 100 if (value && ![value isEqual:@""]) { 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]; 107 } 108 return scoped_ptr<base::StringValue>( 109 new base::StringValue(SysNSStringToUTF16(description))).Pass(); 110 } 111 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]]) { 118 std::string str; 119 StringForBrowserAccessibility(value)->GetAsString(&str); 120 return scoped_ptr<base::Value>(StringForBrowserAccessibility( 121 (BrowserAccessibilityCocoa*) value)); 122 } 123 124 return scoped_ptr<base::Value>( 125 new base::StringValue( 126 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))).Pass(); 127 } 128 129 NSArray* BuildAllAttributesArray() { 130 NSArray* array = [NSArray arrayWithObjects: 131 NSAccessibilityRoleDescriptionAttribute, 132 NSAccessibilityTitleAttribute, 133 NSAccessibilityValueAttribute, 134 NSAccessibilityMinValueAttribute, 135 NSAccessibilityMaxValueAttribute, 136 NSAccessibilityValueDescriptionAttribute, 137 NSAccessibilityDescriptionAttribute, 138 NSAccessibilityHelpAttribute, 139 @"AXInvalid", 140 NSAccessibilityDisclosingAttribute, 141 NSAccessibilityDisclosureLevelAttribute, 142 @"AXAccessKey", 143 @"AXARIAAtomic", 144 @"AXARIABusy", 145 @"AXARIALive", 146 @"AXARIARelevant", 147 NSAccessibilityColumnIndexRangeAttribute, 148 NSAccessibilityEnabledAttribute, 149 NSAccessibilityFocusedAttribute, 150 NSAccessibilityIndexAttribute, 151 @"AXLoaded", 152 @"AXLoadingProcess", 153 NSAccessibilityNumberOfCharactersAttribute, 154 NSAccessibilityOrientationAttribute, 155 @"AXRequired", 156 NSAccessibilityRowIndexRangeAttribute, 157 NSAccessibilityTitleUIElementAttribute, 158 NSAccessibilityURLAttribute, 159 NSAccessibilityVisibleCharacterRangeAttribute, 160 @"AXVisited", 161 @"AXLinkedUIElements", 162 nil]; 163 return [array retain]; 164 } 165 166 } // namespace 167 168 void AccessibilityTreeFormatter::Initialize() { 169 } 170 171 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]; 177 178 string role = SysNSStringToUTF8( 179 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]); 180 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role); 181 182 NSString* subrole = 183 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; 184 if (subrole != nil) { 185 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute), 186 SysNSStringToUTF8(subrole)); 187 } 188 189 CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); 190 for (NSString* requestedAttribute in all_attributes) { 191 if (![supportedAttributes containsObject:requestedAttribute]) 192 continue; 193 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; 194 if (value != nil) { 195 dict->Set( 196 SysNSStringToUTF8(requestedAttribute), 197 PopulateObject(value).release()); 198 } 199 } 200 dict->Set(kPositionDictAttr, PopulatePosition(node).release()); 201 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release()); 202 } 203 204 base::string16 AccessibilityTreeFormatter::ToString( 205 const base::DictionaryValue& dict, 206 const base::string16& indent) { 207 base::string16 line; 208 NSArray* defaultAttributes = 209 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute, 210 NSAccessibilityValueAttribute, 211 nil]; 212 string s_value; 213 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value); 214 WriteAttribute(true, base::UTF8ToUTF16(s_value), &line); 215 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()), 221 &line); 222 } 223 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(), 231 s_value.c_str()), 232 &line); 233 continue; 234 } 235 const base::Value* value; 236 if (dict.Get(requestedAttributeUTF8, &value)) { 237 std::string json_value; 238 base::JSONWriter::Write(value, &json_value); 239 WriteAttribute( 240 [defaultAttributes containsObject:requestedAttribute], 241 StringPrintf("%s=%s", 242 requestedAttributeUTF8.c_str(), 243 json_value.c_str()), 244 &line); 245 } 246 } 247 const base::DictionaryValue* d_value = NULL; 248 if (dict.GetDictionary(kPositionDictAttr, &d_value)) { 249 WriteAttribute(false, 250 FormatCoordinates(kPositionDictAttr, 251 kXCoordDictAttr, kYCoordDictAttr, 252 *d_value), 253 &line); 254 } 255 if (dict.GetDictionary(kSizeDictAttr, &d_value)) { 256 WriteAttribute(false, 257 FormatCoordinates(kSizeDictAttr, 258 kWidthDictAttr, kHeightDictAttr, *d_value), 259 &line); 260 } 261 262 return indent + line + base::ASCIIToUTF16("\n"); 263 } 264 265 // static 266 const base::FilePath::StringType 267 AccessibilityTreeFormatter::GetActualFileSuffix() { 268 return FILE_PATH_LITERAL("-actual-mac.txt"); 269 } 270 271 // static 272 const base::FilePath::StringType 273 AccessibilityTreeFormatter::GetExpectedFileSuffix() { 274 return FILE_PATH_LITERAL("-expected-mac.txt"); 275 } 276 277 // static 278 const string AccessibilityTreeFormatter::GetAllowEmptyString() { 279 return "@MAC-ALLOW-EMPTY:"; 280 } 281 282 // static 283 const string AccessibilityTreeFormatter::GetAllowString() { 284 return "@MAC-ALLOW:"; 285 } 286 287 // static 288 const string AccessibilityTreeFormatter::GetDenyString() { 289 return "@MAC-DENY:"; 290 } 291 292 } // namespace content 293