blob: 58881b4af4b18beb7bd0388ee7eff9102c897668 [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001// 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)c2e0dbd2013-05-09 18:35:53 +01009#include "base/basictypes.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000010#include "base/files/file_path.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010011#include "base/json/json_writer.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010012#include "base/strings/stringprintf.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010013#include "base/strings/sys_string_conversions.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010014#include "base/strings/utf_string_conversions.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000015#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)c2e0dbd2013-05-09 18:35:53 +010019using base::StringPrintf;
20using base::SysNSStringToUTF8;
21using base::SysNSStringToUTF16;
22using std::string;
23
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000024namespace content {
25
26namespace {
27
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010028const char* kPositionDictAttr = "position";
29const char* kXCoordDictAttr = "x";
30const char* kYCoordDictAttr = "y";
31const char* kSizeDictAttr = "size";
32const char* kWidthDictAttr = "width";
33const char* kHeightDictAttr = "height";
34const char* kRangeLocDictAttr = "loc";
35const char* kRangeLenDictAttr = "len";
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000036
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010037scoped_ptr<base::DictionaryValue> PopulatePosition(
38 const BrowserAccessibility& node) {
39 scoped_ptr<base::DictionaryValue> position(new base::DictionaryValue);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000040 // 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)c2e0dbd2013-05-09 18:35:53 +010044 BrowserAccessibility* root = node.manager()->GetRoot();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000045 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)c2e0dbd2013-05-09 18:35:53 +010051 BrowserAccessibilityCocoa* cocoa_node =
52 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000053 NSPoint node_position = [[cocoa_node position] pointValue];
54 NSSize node_size = [[cocoa_node size] sizeValue];
55
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010056 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)2a99a7e2013-03-28 15:31:22 +000061}
62
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010063scoped_ptr<base::DictionaryValue>
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010064PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010065 scoped_ptr<base::DictionaryValue> size(new base::DictionaryValue);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000066 NSSize node_size = [[cocoa_node size] sizeValue];
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010067 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)2a99a7e2013-03-28 15:31:22 +000070}
71
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010072scoped_ptr<base::DictionaryValue> PopulateRange(NSRange range) {
73 scoped_ptr<base::DictionaryValue> rangeDict(new base::DictionaryValue);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010074 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)2a99a7e2013-03-28 15:31:22 +000078
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010079// Returns true if |value| is an NSValue containing a NSRange.
80bool IsRangeValue(id value) {
81 if (![value isKindOfClass:[NSValue class]])
82 return false;
83 return 0 == strcmp([value objCType], @encode(NSRange));
84}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000085
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010086NSArray* BuildAllAttributesArray() {
Ben Murdochbb1529c2013-08-08 10:24:53 +010087 NSArray* array = [NSArray arrayWithObjects:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000088 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)c2e0dbd2013-05-09 18:35:53 +0100104 NSAccessibilityColumnIndexRangeAttribute,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000105 NSAccessibilityEnabledAttribute,
106 NSAccessibilityFocusedAttribute,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100107 NSAccessibilityIndexAttribute,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000108 @"AXLoaded",
109 @"AXLoadingProcess",
110 NSAccessibilityNumberOfCharactersAttribute,
111 NSAccessibilityOrientationAttribute,
112 @"AXRequired",
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100113 NSAccessibilityRowIndexRangeAttribute,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000114 NSAccessibilityURLAttribute,
115 NSAccessibilityVisibleCharacterRangeAttribute,
116 @"AXVisited",
117 nil];
Ben Murdochbb1529c2013-08-08 10:24:53 +0100118 return [array retain];
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100119}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000120
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100121} // namespace
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000122
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100123void AccessibilityTreeFormatter::Initialize() {
124}
125
126
127void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100128 base::DictionaryValue* dict) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100129 BrowserAccessibilityCocoa* cocoa_node =
130 const_cast<BrowserAccessibility*>(&node)->ToBrowserAccessibilityCocoa();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000131 NSArray* supportedAttributes = [cocoa_node accessibilityAttributeNames];
132
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100133 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)2a99a7e2013-03-28 15:31:22 +0000146 if (![supportedAttributes containsObject:requestedAttribute]) {
147 continue;
148 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100149 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)2a99a7e2013-03-28 15:31:22 +0000159 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100160 dict->Set(kPositionDictAttr, PopulatePosition(node).release());
161 dict->Set(kSizeDictAttr, PopulateSize(cocoa_node).release());
162}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000163
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100164string16 AccessibilityTreeFormatter::ToString(const base::DictionaryValue& dict,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100165 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)7d4cd472013-06-19 11:58:07 +0100186 const base::DictionaryValue* d_value;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100187 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)7d4cd472013-06-19 11:58:07 +0100205 const base::DictionaryValue* d_value = NULL;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100206 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)2a99a7e2013-03-28 15:31:22 +0000221}
222
223// static
224const base::FilePath::StringType
225AccessibilityTreeFormatter::GetActualFileSuffix() {
226 return FILE_PATH_LITERAL("-actual-mac.txt");
227}
228
229// static
230const base::FilePath::StringType
231AccessibilityTreeFormatter::GetExpectedFileSuffix() {
232 return FILE_PATH_LITERAL("-expected-mac.txt");
233}
234
235// static
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100236const string AccessibilityTreeFormatter::GetAllowEmptyString() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000237 return "@MAC-ALLOW-EMPTY:";
238}
239
240// static
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100241const string AccessibilityTreeFormatter::GetAllowString() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000242 return "@MAC-ALLOW:";
243}
244
245// static
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100246const string AccessibilityTreeFormatter::GetDenyString() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000247 return "@MAC-DENY:";
248}
249
250} // namespace content