Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 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 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 9 | #include "base/basictypes.h" |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 10 | #include "base/files/file_path.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 11 | #include "base/json/json_writer.h" |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 12 | #include "base/strings/stringprintf.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 13 | #include "base/strings/sys_string_conversions.h" |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 14 | #include "base/strings/utf_string_conversions.h" |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 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 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 19 | using base::StringPrintf; |
| 20 | using base::SysNSStringToUTF8; |
| 21 | using base::SysNSStringToUTF16; |
| 22 | using std::string; |
| 23 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 24 | namespace content { |
| 25 | |
| 26 | namespace { |
| 27 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 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"; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 36 | |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 37 | scoped_ptr<base::DictionaryValue> PopulatePosition( |
| 38 | const BrowserAccessibility& node) { |
| 39 | scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 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. |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 44 | BrowserAccessibility* root = node.manager()->GetRoot(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 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 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 51 | BrowserAccessibilityCocoa* cocoa_node = |
| 52 | const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 53 | NSPoint node_position = [[cocoa_node position] pointValue]; |
| 54 | NSSize node_size = [[cocoa_node size] sizeValue]; |
| 55 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 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(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 61 | } |
| 62 | |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 63 | scoped_ptr<base::DictionaryValue> |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 64 | PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) { |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 65 | scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 66 | NSSize node_size = [[cocoa_node size] sizeValue]; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 67 | size->SetInteger(kHeightDictAttr, static_cast<int>(node_size.height)); |
| 68 | size->SetInteger(kWidthDictAttr, static_cast<int>(node_size.width)); |
| 69 | return size.Pass(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 70 | } |
| 71 | |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 72 | scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) { |
| 73 | scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 74 | rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location)); |
| 75 | rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length)); |
| 76 | return rangeDict.Pass(); |
| 77 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 78 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 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 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 85 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 86 | NSArray* BuildAllAttributesArray() { |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 87 | NSArray* array = [NSArray arrayWithObjects: |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 88 | NSAccessibilityRoleDescriptionAttribute, |
| 89 | NSAccessibilityTitleAttribute, |
| 90 | NSAccessibilityValueAttribute, |
| 91 | NSAccessibilityMinValueAttribute, |
| 92 | NSAccessibilityMaxValueAttribute, |
| 93 | NSAccessibilityValueDescriptionAttribute, |
| 94 | NSAccessibilityDescriptionAttribute, |
| 95 | NSAccessibilityHelpAttribute, |
| 96 | @"AXInvalid", |
| 97 | NSAccessibilityDisclosingAttribute, |
| 98 | NSAccessibilityDisclosureLevelAttribute, |
| 99 | @"AXAccessKey", |
| 100 | @"AXARIAAtomic", |
| 101 | @"AXARIABusy", |
| 102 | @"AXARIALive", |
| 103 | @"AXARIARelevant", |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 104 | NSAccessibilityColumnIndexRangeAttribute, |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 105 | NSAccessibilityEnabledAttribute, |
| 106 | NSAccessibilityFocusedAttribute, |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 107 | NSAccessibilityIndexAttribute, |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 108 | @"AXLoaded", |
| 109 | @"AXLoadingProcess", |
| 110 | NSAccessibilityNumberOfCharactersAttribute, |
| 111 | NSAccessibilityOrientationAttribute, |
| 112 | @"AXRequired", |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 113 | NSAccessibilityRowIndexRangeAttribute, |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 114 | NSAccessibilityURLAttribute, |
| 115 | NSAccessibilityVisibleCharacterRangeAttribute, |
| 116 | @"AXVisited", |
| 117 | nil]; |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 118 | return [array retain]; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 119 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 120 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 121 | } // namespace |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 122 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 123 | void AccessibilityTreeFormatter::Initialize() { |
| 124 | } |
| 125 | |
| 126 | |
| 127 | void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 128 | base::DictionaryValue* dict) { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 129 | BrowserAccessibilityCocoa* cocoa_node = |
| 130 | const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 131 | NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames]; |
| 132 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 133 | string role = SysNSStringToUTF8( |
| 134 | [cocoa_node accessibilityAttributeValue:NSAccessibilityRoleAttribute]); |
| 135 | dict->SetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), role); |
| 136 | |
| 137 | NSString* subrole = |
| 138 | [cocoa_node accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; |
| 139 | if (subrole != nil) { |
| 140 | dict->SetString(SysNSStringToUTF8(NSAccessibilitySubroleAttribute), |
| 141 | SysNSStringToUTF8(subrole)); |
| 142 | } |
| 143 | |
| 144 | CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); |
| 145 | for (NSString* requestedAttribute in all_attributes) { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 146 | if (![supportedAttributes containsObject:requestedAttribute]) { |
| 147 | continue; |
| 148 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 149 | id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; |
| 150 | if (IsRangeValue(value)) { |
| 151 | dict->Set( |
| 152 | SysNSStringToUTF8(requestedAttribute), |
| 153 | PopulateRange([value rangeValue]).release()); |
| 154 | } else if (value != nil) { |
| 155 | dict->SetString( |
| 156 | SysNSStringToUTF8(requestedAttribute), |
| 157 | SysNSStringToUTF16([NSString stringWithFormat:@"%@", value])); |
| 158 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 159 | } |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 160 | dict->Set(kPositionDictAttr, PopulatePosition(node).release()); |
| 161 | dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release()); |
| 162 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 163 | |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 164 | string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict, |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 165 | const string16& indent) { |
| 166 | string16 line; |
| 167 | NSArray* defaultAttributes = |
| 168 | [NSArray arrayWithObjects:NSAccessibilityTitleAttribute, |
| 169 | NSAccessibilityValueAttribute, |
| 170 | nil]; |
| 171 | string s_value; |
| 172 | dict.GetString(SysNSStringToUTF8(NSAccessibilityRoleAttribute), &s_value); |
| 173 | WriteAttribute(true, UTF8ToUTF16(s_value), &line); |
| 174 | |
| 175 | string subroleAttribute = SysNSStringToUTF8(NSAccessibilitySubroleAttribute); |
| 176 | if (dict.GetString(subroleAttribute, &s_value)) { |
| 177 | WriteAttribute(false, |
| 178 | StringPrintf("%s=%s", |
| 179 | subroleAttribute.c_str(), s_value.c_str()), |
| 180 | &line); |
| 181 | } |
| 182 | |
| 183 | CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); |
| 184 | for (NSString* requestedAttribute in all_attributes) { |
| 185 | string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute); |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 186 | const base::DictionaryValue* d_value; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 187 | if (dict.GetDictionary(requestedAttributeUTF8, &d_value)) { |
| 188 | std::string json_value; |
| 189 | base::JSONWriter::Write(d_value, &json_value); |
| 190 | WriteAttribute( |
| 191 | [defaultAttributes containsObject:requestedAttribute], |
| 192 | StringPrintf("%s=%s", |
| 193 | requestedAttributeUTF8.c_str(), |
| 194 | json_value.c_str()), |
| 195 | &line); |
| 196 | } |
| 197 | if (!dict.GetString(requestedAttributeUTF8, &s_value)) |
| 198 | continue; |
| 199 | WriteAttribute([defaultAttributes containsObject:requestedAttribute], |
| 200 | StringPrintf("%s='%s'", |
| 201 | requestedAttributeUTF8.c_str(), |
| 202 | s_value.c_str()), |
| 203 | &line); |
| 204 | } |
Torne (Richard Coles) | 7d4cd47 | 2013-06-19 11:58:07 +0100 | [diff] [blame] | 205 | const base::DictionaryValue* d_value = NULL; |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 206 | if (dict.GetDictionary(kPositionDictAttr, &d_value)) { |
| 207 | WriteAttribute(false, |
| 208 | FormatCoordinates(kPositionDictAttr, |
| 209 | kXCoordDictAttr, kYCoordDictAttr, |
| 210 | *d_value), |
| 211 | &line); |
| 212 | } |
| 213 | if (dict.GetDictionary(kSizeDictAttr, &d_value)) { |
| 214 | WriteAttribute(false, |
| 215 | FormatCoordinates(kSizeDictAttr, |
| 216 | kWidthDictAttr, kHeightDictAttr, *d_value), |
| 217 | &line); |
| 218 | } |
| 219 | |
| 220 | return indent + line + ASCIIToUTF16("\n"); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | // static |
| 224 | const base::FilePath::StringType |
| 225 | AccessibilityTreeFormatter::GetActualFileSuffix() { |
| 226 | return FILE_PATH_LITERAL("-actual-mac.txt"); |
| 227 | } |
| 228 | |
| 229 | // static |
| 230 | const base::FilePath::StringType |
| 231 | AccessibilityTreeFormatter::GetExpectedFileSuffix() { |
| 232 | return FILE_PATH_LITERAL("-expected-mac.txt"); |
| 233 | } |
| 234 | |
| 235 | // static |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 236 | const string AccessibilityTreeFormatter::GetAllowEmptyString() { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 237 | return "@MAC-ALLOW-EMPTY:"; |
| 238 | } |
| 239 | |
| 240 | // static |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 241 | const string AccessibilityTreeFormatter::GetAllowString() { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 242 | return "@MAC-ALLOW:"; |
| 243 | } |
| 244 | |
| 245 | // static |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 246 | const string AccessibilityTreeFormatter::GetDenyString() { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 247 | return "@MAC-DENY:"; |
| 248 | } |
| 249 | |
| 250 | } // namespace content |