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_browser.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include "base/files/file_path.h" 10#include "base/json/json_writer.h" 11#include "base/strings/string_number_conversions.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 "base/values.h" 16#include "content/browser/accessibility/accessibility_tree_formatter_blink.h" 17#include "content/browser/accessibility/browser_accessibility_cocoa.h" 18#include "content/browser/accessibility/browser_accessibility_mac.h" 19#include "content/browser/accessibility/browser_accessibility_manager.h" 20 21// This file uses the deprecated NSObject accessibility interface. 22// TODO(crbug.com/948844): Migrate to the new NSAccessibility interface. 23#pragma clang diagnostic push 24#pragma clang diagnostic ignored "-Wdeprecated-declarations" 25 26using base::StringPrintf; 27using base::SysNSStringToUTF8; 28using base::SysNSStringToUTF16; 29using std::string; 30 31namespace content { 32 33namespace { 34 35const char kPositionDictAttr[] = "position"; 36const char kXCoordDictAttr[] = "x"; 37const char kYCoordDictAttr[] = "y"; 38const char kSizeDictAttr[] = "size"; 39const char kWidthDictAttr[] = "width"; 40const char kHeightDictAttr[] = "height"; 41const char kRangeLocDictAttr[] = "loc"; 42const char kRangeLenDictAttr[] = "len"; 43 44std::unique_ptr<base::DictionaryValue> PopulatePosition( 45 const BrowserAccessibility& node) { 46 DCHECK(node.instance_active()); 47 BrowserAccessibilityManager* root_manager = node.manager()->GetRootManager(); 48 DCHECK(root_manager); 49 50 std::unique_ptr<base::DictionaryValue> position(new base::DictionaryValue); 51 // The NSAccessibility position of an object is in global coordinates and 52 // based on the lower-left corner of the object. To make this easier and less 53 // confusing, convert it to local window coordinates using the top-left 54 // corner when dumping the position. 55 BrowserAccessibility* root = root_manager->GetRoot(); 56 BrowserAccessibilityCocoa* cocoa_root = ToBrowserAccessibilityCocoa(root); 57 NSPoint root_position = [[cocoa_root position] pointValue]; 58 NSSize root_size = [[cocoa_root size] sizeValue]; 59 int root_top = -static_cast<int>(root_position.y + root_size.height); 60 int root_left = static_cast<int>(root_position.x); 61 62 BrowserAccessibilityCocoa* cocoa_node = 63 ToBrowserAccessibilityCocoa(const_cast<BrowserAccessibility*>(&node)); 64 NSPoint node_position = [[cocoa_node position] pointValue]; 65 NSSize node_size = [[cocoa_node size] sizeValue]; 66 67 position->SetInteger(kXCoordDictAttr, 68 static_cast<int>(node_position.x - root_left)); 69 position->SetInteger( 70 kYCoordDictAttr, 71 static_cast<int>(-node_position.y - node_size.height - root_top)); 72 return position; 73} 74 75std::unique_ptr<base::DictionaryValue> PopulateSize( 76 const BrowserAccessibilityCocoa* cocoa_node) { 77 std::unique_ptr<base::DictionaryValue> size(new base::DictionaryValue); 78 NSSize node_size = [[cocoa_node size] sizeValue]; 79 size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height)); 80 size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width)); 81 return size; 82} 83 84std::unique_ptr<base::DictionaryValue> PopulateRange(NSRange range) { 85 std::unique_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue); 86 rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location)); 87 rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length)); 88 return rangeDict; 89} 90 91// Returns true if |value| is an NSValue containing a NSRange. 92bool IsRangeValue(id value) { 93 if (![value isKindOfClass:[NSValue class]]) 94 return false; 95 return 0 == strcmp([value objCType], @encode(NSRange)); 96} 97 98std::unique_ptr<base::Value> PopulateObject(id value); 99 100std::unique_ptr<base::ListValue> PopulateArray(NSArray* array) { 101 std::unique_ptr<base::ListValue> list(new base::ListValue); 102 for (NSUInteger i = 0; i < [array count]; i++) 103 list->Append(PopulateObject([array objectAtIndex:i])); 104 return list; 105} 106 107std::unique_ptr<base::Value> StringForBrowserAccessibility( 108 BrowserAccessibilityCocoa* obj) { 109 NSMutableArray* tokens = [[NSMutableArray alloc] init]; 110 111 // Always include the role 112 id role = [obj role]; 113 [tokens addObject:role]; 114 115 // If the role is "group", include the role description as well. 116 id roleDescription = [obj roleDescription]; 117 if ([role isEqualToString:NSAccessibilityGroupRole] && 118 roleDescription != nil && ![roleDescription isEqualToString:@""] && 119 ![roleDescription isEqualToString:@"group"]) { 120 [tokens addObject:roleDescription]; 121 } 122 123 // Include the description, title, or value - the first one not empty. 124 id title = [obj title]; 125 id description = [obj descriptionForAccessibility]; 126 id value = [obj value]; 127 if (description && ![description isEqual:@""]) { 128 [tokens addObject:description]; 129 } else if (title && ![title isEqual:@""]) { 130 [tokens addObject:title]; 131 } else if (value && ![value isEqual:@""]) { 132 [tokens addObject:value]; 133 } 134 135 NSString* result = [tokens componentsJoinedByString:@" "]; 136 return std::unique_ptr<base::Value>( 137 new base::Value(SysNSStringToUTF16(result))); 138} 139 140std::unique_ptr<base::Value> PopulateObject(id value) { 141 if ([value isKindOfClass:[NSArray class]]) 142 return std::unique_ptr<base::Value>(PopulateArray((NSArray*)value)); 143 if (IsRangeValue(value)) 144 return std::unique_ptr<base::Value>(PopulateRange([value rangeValue])); 145 if ([value isKindOfClass:[BrowserAccessibilityCocoa class]]) { 146 std::string str; 147 StringForBrowserAccessibility(value)->GetAsString(&str); 148 return std::unique_ptr<base::Value>( 149 StringForBrowserAccessibility((BrowserAccessibilityCocoa*)value)); 150 } 151 152 return std::unique_ptr<base::Value>(new base::Value( 153 SysNSStringToUTF16([NSString stringWithFormat:@"%@", value]))); 154} 155 156NSArray* AllAttributesArray() { 157 static NSArray* all_attributes = [@[ 158 NSAccessibilityRoleDescriptionAttribute, 159 NSAccessibilityTitleAttribute, 160 NSAccessibilityValueAttribute, 161 NSAccessibilityMinValueAttribute, 162 NSAccessibilityMaxValueAttribute, 163 NSAccessibilityValueDescriptionAttribute, 164 NSAccessibilityDescriptionAttribute, 165 NSAccessibilityHelpAttribute, 166 @"AXInvalid", 167 NSAccessibilityDisclosingAttribute, 168 NSAccessibilityDisclosureLevelAttribute, 169 @"AXAccessKey", 170 @"AXARIAAtomic", 171 @"AXARIABusy", 172 @"AXARIAColumnCount", 173 @"AXARIAColumnIndex", 174 @"AXARIALive", 175 @"AXARIARelevant", 176 @"AXARIARowCount", 177 @"AXARIARowIndex", 178 @"AXARIASetSize", 179 @"AXARIAPosInSet", 180 @"AXAutocomplete", 181 @"AXAutocompleteValue", 182 @"AXBlockQuoteLevel", 183 NSAccessibilityColumnHeaderUIElementsAttribute, 184 NSAccessibilityColumnIndexRangeAttribute, 185 @"AXDOMIdentifier", 186 @"AXDropEffects", 187 @"AXEditableAncestor", 188 NSAccessibilityEnabledAttribute, 189 NSAccessibilityExpandedAttribute, 190 @"AXFocusableAncestor", 191 NSAccessibilityFocusedAttribute, 192 @"AXGrabbed", 193 NSAccessibilityHeaderAttribute, 194 @"AXHasPopup", 195 @"AXHasPopupValue", 196 @"AXHighestEditableAncestor", 197 NSAccessibilityIndexAttribute, 198 @"AXLanguage", 199 @"AXLoaded", 200 @"AXLoadingProcess", 201 NSAccessibilityNumberOfCharactersAttribute, 202 NSAccessibilitySortDirectionAttribute, 203 NSAccessibilityOrientationAttribute, 204 NSAccessibilityPlaceholderValueAttribute, 205 @"AXRequired", 206 NSAccessibilityRowHeaderUIElementsAttribute, 207 NSAccessibilityRowIndexRangeAttribute, 208 NSAccessibilitySelectedAttribute, 209 NSAccessibilitySelectedChildrenAttribute, 210 NSAccessibilityTitleUIElementAttribute, 211 NSAccessibilityURLAttribute, 212 NSAccessibilityVisibleCharacterRangeAttribute, 213 NSAccessibilityVisibleChildrenAttribute, 214 @"AXVisited", 215 @"AXLinkedUIElements" 216 ] retain]; 217 218 return all_attributes; 219} 220 221} // namespace 222 223class AccessibilityTreeFormatterMac : public AccessibilityTreeFormatterBrowser { 224 public: 225 explicit AccessibilityTreeFormatterMac(); 226 ~AccessibilityTreeFormatterMac() override; 227 228 void AddDefaultFilters( 229 std::vector<PropertyFilter>* property_filters) override; 230 231 private: 232 base::FilePath::StringType GetExpectedFileSuffix() override; 233 const std::string GetAllowEmptyString() override; 234 const std::string GetAllowString() override; 235 const std::string GetDenyString() override; 236 const std::string GetDenyNodeString() override; 237 void AddProperties(const BrowserAccessibility& node, 238 base::DictionaryValue* dict) override; 239 base::string16 ProcessTreeForOutput( 240 const base::DictionaryValue& node, 241 base::DictionaryValue* filtered_dict_result = nullptr) override; 242}; 243 244// static 245std::unique_ptr<AccessibilityTreeFormatter> 246AccessibilityTreeFormatter::Create() { 247 return std::make_unique<AccessibilityTreeFormatterMac>(); 248} 249 250// static 251std::vector<AccessibilityTreeFormatter::TestPass> 252AccessibilityTreeFormatter::GetTestPasses() { 253 return { 254 {"blink", &AccessibilityTreeFormatterBlink::CreateBlink}, 255 {"mac", &AccessibilityTreeFormatter::Create}, 256 }; 257} 258 259AccessibilityTreeFormatterMac::AccessibilityTreeFormatterMac() {} 260 261AccessibilityTreeFormatterMac::~AccessibilityTreeFormatterMac() {} 262 263void AccessibilityTreeFormatterMac::AddDefaultFilters( 264 std::vector<PropertyFilter>* property_filters) { 265 AddPropertyFilter(property_filters, "AXValueAutofill*"); 266 AddPropertyFilter(property_filters, "AXAutocomplete*"); 267} 268void AccessibilityTreeFormatterMac::AddProperties( 269 const BrowserAccessibility& node, 270 base::DictionaryValue* dict) { 271 dict->SetInteger("id", node.GetId()); 272 BrowserAccessibilityCocoa* cocoa_node = 273 ToBrowserAccessibilityCocoa(const_cast<BrowserAccessibility*>(&node)); 274 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames]; 275 276 string role = SysNSStringToUTF8( 277 [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]); 278 dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role); 279 280 NSString* subrole = 281 [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; 282 if (subrole != nil) { 283 dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute), 284 SysNSStringToUTF8(subrole)); 285 } 286 287 for (NSString* requestedAttribute in AllAttributesArray()) { 288 if (![supportedAttributes containsObject:requestedAttribute]) 289 continue; 290 id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; 291 if (value != nil) { 292 dict->Set(SysNSStringToUTF8(requestedAttribute), PopulateObject(value)); 293 } 294 } 295 dict->Set(kPositionDictAttr, PopulatePosition(node)); 296 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node)); 297} 298 299base::string16 AccessibilityTreeFormatterMac::ProcessTreeForOutput( 300 const base::DictionaryValue& dict, 301 base::DictionaryValue* filtered_dict_result) { 302 base::string16 error_value; 303 if (dict.GetString("error", &error_value)) 304 return error_value; 305 306 base::string16 line; 307 if (show_ids()) { 308 int id_value; 309 dict.GetInteger("id", &id_value); 310 WriteAttribute(true, base::NumberToString16(id_value), &line); 311 } 312 313 NSArray* defaultAttributes = 314 [NSArray arrayWithObjects:NSAccessibilityTitleAttribute, 315 NSAccessibilityTitleUIElementAttribute, 316 NSAccessibilityDescriptionAttribute, 317 NSAccessibilityHelpAttribute, 318 NSAccessibilityValueAttribute, nil]; 319 string s_value; 320 dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value); 321 WriteAttribute(true, s_value, &line); 322 323 string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute); 324 if (dict.GetString(subroleAttribute, &s_value)) { 325 WriteAttribute( 326 false, StringPrintf("%s=%s", subroleAttribute.c_str(), s_value.c_str()), 327 &line); 328 } 329 330 for (NSString* requestedAttribute in AllAttributesArray()) { 331 string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute); 332 if (dict.GetString(requestedAttributeUTF8, &s_value)) { 333 WriteAttribute([defaultAttributes containsObject:requestedAttribute], 334 StringPrintf("%s='%s'", requestedAttributeUTF8.c_str(), 335 s_value.c_str()), 336 &line); 337 continue; 338 } 339 const base::Value* value; 340 if (dict.Get(requestedAttributeUTF8, &value)) { 341 std::string json_value; 342 base::JSONWriter::Write(*value, &json_value); 343 WriteAttribute([defaultAttributes containsObject:requestedAttribute], 344 StringPrintf("%s=%s", requestedAttributeUTF8.c_str(), 345 json_value.c_str()), 346 &line); 347 } 348 } 349 const base::DictionaryValue* d_value = NULL; 350 if (dict.GetDictionary(kPositionDictAttr, &d_value)) { 351 WriteAttribute(false, 352 FormatCoordinates(*d_value, kPositionDictAttr, 353 kXCoordDictAttr, kYCoordDictAttr), 354 &line); 355 } 356 if (dict.GetDictionary(kSizeDictAttr, &d_value)) { 357 WriteAttribute(false, 358 FormatCoordinates(*d_value, kSizeDictAttr, kWidthDictAttr, 359 kHeightDictAttr), 360 &line); 361 } 362 363 return line; 364} 365 366base::FilePath::StringType 367AccessibilityTreeFormatterMac::GetExpectedFileSuffix() { 368 return FILE_PATH_LITERAL("-expected-mac.txt"); 369} 370 371const string AccessibilityTreeFormatterMac::GetAllowEmptyString() { 372 return "@MAC-ALLOW-EMPTY:"; 373} 374 375const string AccessibilityTreeFormatterMac::GetAllowString() { 376 return "@MAC-ALLOW:"; 377} 378 379const string AccessibilityTreeFormatterMac::GetDenyString() { 380 return "@MAC-DENY:"; 381} 382 383const string AccessibilityTreeFormatterMac::GetDenyNodeString() { 384 return "@MAC-DENY-NODE:"; 385} 386 387} // namespace content 388 389#pragma clang diagnostic pop 390