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   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