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