Home | History | Annotate | Download | only in accessibility
      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