• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium Embedded Framework Authors. Portions copyright
2// 2013 The Chromium Authors. All rights reserved. Use of this source code is
3// governed by a BSD-style license that can be found in the LICENSE file.
4
5// Sample implementation for the NSAccessibility protocol for interacting with
6// VoiceOver and other accessibility clients.
7
8#include "tests/cefclient/browser/osr_accessibility_node.h"
9
10#import <AppKit/NSAccessibility.h>
11#import <Cocoa/Cocoa.h>
12
13#include "tests/cefclient/browser/osr_accessibility_helper.h"
14
15namespace {
16
17NSString* AxRoleToNSAxRole(const std::string& role_string) {
18  if (role_string == "abbr")
19    return NSAccessibilityGroupRole;
20  if (role_string == "alertDialog")
21    return NSAccessibilityGroupRole;
22  if (role_string == "alert")
23    return NSAccessibilityGroupRole;
24  if (role_string == "annotation")
25    return NSAccessibilityUnknownRole;
26  if (role_string == "application")
27    return NSAccessibilityGroupRole;
28  if (role_string == "article")
29    return NSAccessibilityGroupRole;
30  if (role_string == "audio")
31    return NSAccessibilityGroupRole;
32  if (role_string == "banner")
33    return NSAccessibilityGroupRole;
34  if (role_string == "blockquote")
35    return NSAccessibilityGroupRole;
36  if (role_string == "busyIndicator")
37    return NSAccessibilityBusyIndicatorRole;
38  if (role_string == "button")
39    return NSAccessibilityButtonRole;
40  if (role_string == "buttonDropDown")
41    return NSAccessibilityButtonRole;
42  if (role_string == "canvas")
43    return NSAccessibilityImageRole;
44  if (role_string == "caption")
45    return NSAccessibilityGroupRole;
46  if (role_string == "checkBox")
47    return NSAccessibilityCheckBoxRole;
48  if (role_string == "colorWell")
49    return NSAccessibilityColorWellRole;
50  if (role_string == "column")
51    return NSAccessibilityColumnRole;
52  if (role_string == "comboBox")
53    return NSAccessibilityComboBoxRole;
54  if (role_string == "complementary")
55    return NSAccessibilityGroupRole;
56  if (role_string == "contentInfo")
57    return NSAccessibilityGroupRole;
58  if (role_string == "definition")
59    return NSAccessibilityGroupRole;
60  if (role_string == "descriptionListDetail")
61    return NSAccessibilityGroupRole;
62  if (role_string == "descriptionList")
63    return NSAccessibilityListRole;
64  if (role_string == "descriptionListTerm")
65    return NSAccessibilityGroupRole;
66  if (role_string == "details")
67    return NSAccessibilityGroupRole;
68  if (role_string == "dialog")
69    return NSAccessibilityGroupRole;
70  if (role_string == "directory")
71    return NSAccessibilityListRole;
72  if (role_string == "disclosureTriangle")
73    return NSAccessibilityDisclosureTriangleRole;
74  if (role_string == "div")
75    return NSAccessibilityGroupRole;
76  if (role_string == "document")
77    return NSAccessibilityGroupRole;
78  if (role_string == "embeddedObject")
79    return NSAccessibilityGroupRole;
80  if (role_string == "figcaption")
81    return NSAccessibilityGroupRole;
82  if (role_string == "figure")
83    return NSAccessibilityGroupRole;
84  if (role_string == "footer")
85    return NSAccessibilityGroupRole;
86  if (role_string == "form")
87    return NSAccessibilityGroupRole;
88  if (role_string == "genericContainer")
89    return NSAccessibilityGroupRole;
90  if (role_string == "grid")
91    return NSAccessibilityGroupRole;
92  if (role_string == "group")
93    return NSAccessibilityGroupRole;
94  if (role_string == "iframe")
95    return NSAccessibilityGroupRole;
96  if (role_string == "iframePresentational")
97    return NSAccessibilityGroupRole;
98  if (role_string == "ignored")
99    return NSAccessibilityUnknownRole;
100  if (role_string == "imageMapLink")
101    return NSAccessibilityLinkRole;
102  if (role_string == "imageMap")
103    return NSAccessibilityGroupRole;
104  if (role_string == "image")
105    return NSAccessibilityImageRole;
106  if (role_string == "labelText")
107    return NSAccessibilityGroupRole;
108  if (role_string == "legend")
109    return NSAccessibilityGroupRole;
110  if (role_string == "link")
111    return NSAccessibilityLinkRole;
112  if (role_string == "listBoxOption")
113    return NSAccessibilityStaticTextRole;
114  if (role_string == "listBox")
115    return NSAccessibilityListRole;
116  if (role_string == "listItem")
117    return NSAccessibilityGroupRole;
118  if (role_string == "list")
119    return NSAccessibilityListRole;
120  if (role_string == "log")
121    return NSAccessibilityGroupRole;
122  if (role_string == "main")
123    return NSAccessibilityGroupRole;
124  if (role_string == "mark")
125    return NSAccessibilityGroupRole;
126  if (role_string == "marquee")
127    return NSAccessibilityGroupRole;
128  if (role_string == "math")
129    return NSAccessibilityGroupRole;
130  if (role_string == "menu")
131    return NSAccessibilityMenuRole;
132  if (role_string == "menuBar")
133    return NSAccessibilityMenuBarRole;
134  if (role_string == "menuButton")
135    return NSAccessibilityButtonRole;
136  if (role_string == "menuItem")
137    return NSAccessibilityMenuItemRole;
138  if (role_string == "menuItemCheckBox")
139    return NSAccessibilityMenuItemRole;
140  if (role_string == "menuItemRadio")
141    return NSAccessibilityMenuItemRole;
142  if (role_string == "menuListOption")
143    return NSAccessibilityMenuItemRole;
144  if (role_string == "menuListPopup")
145    return NSAccessibilityUnknownRole;
146  if (role_string == "meter")
147    return NSAccessibilityProgressIndicatorRole;
148  if (role_string == "navigation")
149    return NSAccessibilityGroupRole;
150  if (role_string == "note")
151    return NSAccessibilityGroupRole;
152  if (role_string == "outline")
153    return NSAccessibilityOutlineRole;
154  if (role_string == "paragraph")
155    return NSAccessibilityGroupRole;
156  if (role_string == "popUpButton")
157    return NSAccessibilityPopUpButtonRole;
158  if (role_string == "pre")
159    return NSAccessibilityGroupRole;
160  if (role_string == "presentational")
161    return NSAccessibilityGroupRole;
162  if (role_string == "progressIndicator")
163    return NSAccessibilityProgressIndicatorRole;
164  if (role_string == "radioButton")
165    return NSAccessibilityRadioButtonRole;
166  if (role_string == "radioGroup")
167    return NSAccessibilityRadioGroupRole;
168  if (role_string == "region")
169    return NSAccessibilityGroupRole;
170  if (role_string == "row")
171    return NSAccessibilityRowRole;
172  if (role_string == "ruler")
173    return NSAccessibilityRulerRole;
174  if (role_string == "scrollBar")
175    return NSAccessibilityScrollBarRole;
176  if (role_string == "search")
177    return NSAccessibilityGroupRole;
178  if (role_string == "searchBox")
179    return NSAccessibilityTextFieldRole;
180  if (role_string == "slider")
181    return NSAccessibilitySliderRole;
182  if (role_string == "sliderThumb")
183    return NSAccessibilityValueIndicatorRole;
184  if (role_string == "spinButton")
185    return NSAccessibilityIncrementorRole;
186  if (role_string == "splitter")
187    return NSAccessibilitySplitterRole;
188  if (role_string == "staticText")
189    return NSAccessibilityStaticTextRole;
190  if (role_string == "status")
191    return NSAccessibilityGroupRole;
192  if (role_string == "svgRoot")
193    return NSAccessibilityGroupRole;
194  if (role_string == "switch")
195    return NSAccessibilityCheckBoxRole;
196  if (role_string == "tabGroup")
197    return NSAccessibilityTabGroupRole;
198  if (role_string == "tabList")
199    return NSAccessibilityTabGroupRole;
200  if (role_string == "tabPanel")
201    return NSAccessibilityGroupRole;
202  if (role_string == "tab")
203    return NSAccessibilityRadioButtonRole;
204  if (role_string == "tableHeaderContainer")
205    return NSAccessibilityGroupRole;
206  if (role_string == "table")
207    return NSAccessibilityTableRole;
208  if (role_string == "textField")
209    return NSAccessibilityTextFieldRole;
210  if (role_string == "time")
211    return NSAccessibilityGroupRole;
212  if (role_string == "timer")
213    return NSAccessibilityGroupRole;
214  if (role_string == "toggleButton")
215    return NSAccessibilityCheckBoxRole;
216  if (role_string == "toolbar")
217    return NSAccessibilityToolbarRole;
218  if (role_string == "treeGrid")
219    return NSAccessibilityTableRole;
220  if (role_string == "treeItem")
221    return NSAccessibilityRowRole;
222  if (role_string == "tree")
223    return NSAccessibilityOutlineRole;
224  if (role_string == "unknown")
225    return NSAccessibilityUnknownRole;
226  if (role_string == "tooltip")
227    return NSAccessibilityGroupRole;
228  if (role_string == "video")
229    return NSAccessibilityGroupRole;
230  if (role_string == "window")
231    return NSAccessibilityWindowRole;
232  return [NSString stringWithUTF8String:role_string.c_str()];
233}
234
235inline int MiddleX(const CefRect& rect) {
236  return rect.x + rect.width / 2;
237}
238
239inline int MiddleY(const CefRect& rect) {
240  return rect.y + rect.height / 2;
241}
242
243}  // namespace
244
245// OsrAXNodeObject is sample implementation for the NSAccessibility protocol
246// for interacting with VoiceOver and other accessibility clients.
247@interface OsrAXNodeObject : NSObject {
248  // OsrAXNode* proxy object
249  client::OsrAXNode* node_;
250  CefNativeAccessible* parent_;
251}
252
253- (id)init:(client::OsrAXNode*)node;
254+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node;
255@end
256
257@implementation OsrAXNodeObject
258- (id)init:(client::OsrAXNode*)node {
259  node_ = node;
260  parent_ = node_->GetParentAccessibleObject();
261  if (!parent_) {
262    parent_ = node_->GetWindowHandle();
263  }
264  return self;
265}
266
267+ (OsrAXNodeObject*)elementWithNode:(client::OsrAXNode*)node {
268  // We manage the release ourself
269  return [[OsrAXNodeObject alloc] init:node];
270}
271
272- (BOOL)isEqual:(id)object {
273  if ([object isKindOfClass:[OsrAXNodeObject self]]) {
274    OsrAXNodeObject* other = object;
275    return (node_ == other->node_);
276  } else {
277    return NO;
278  }
279}
280
281// Utility methods to map AX information received from renderer
282// to platform properties
283- (NSString*)axRole {
284  // Get the Role from CefAccessibilityHelper and Map to NSRole
285  return AxRoleToNSAxRole(node_->AxRole());
286}
287
288- (NSString*)axDescription {
289  std::string desc = node_->AxDescription();
290  return [NSString stringWithUTF8String:desc.c_str()];
291}
292
293- (NSString*)axName {
294  std::string desc = node_->AxName();
295  return [NSString stringWithUTF8String:desc.c_str()];
296}
297
298- (NSString*)axValue {
299  std::string desc = node_->AxValue();
300  return [NSString stringWithUTF8String:desc.c_str()];
301}
302
303- (void)doMouseClick:(cef_mouse_button_type_t)type {
304  CefRefPtr<CefBrowser> browser = node_->GetBrowser();
305  if (browser) {
306    CefMouseEvent mouse_event;
307    const CefRect& rect = node_->AxLocation();
308    mouse_event.x = MiddleX(rect);
309    mouse_event.y = MiddleY(rect);
310
311    mouse_event.modifiers = 0;
312    browser->GetHost()->SendMouseClickEvent(mouse_event, type, false, 1);
313    browser->GetHost()->SendMouseClickEvent(mouse_event, type, true, 1);
314  }
315}
316
317- (NSMutableArray*)getKids {
318  int numChilds = node_->GetChildCount();
319  if (numChilds > 0) {
320    NSMutableArray* kids = [NSMutableArray arrayWithCapacity:numChilds];
321    for (int index = 0; index < numChilds; index++) {
322      client::OsrAXNode* child = node_->ChildAtIndex(index);
323      [kids addObject:child ? CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(
324                                  child->GetNativeAccessibleObject(node_))
325                            : nil];
326    }
327    return kids;
328  }
329  return nil;
330}
331
332- (NSPoint)position {
333  CefRect cef_rect = node_->AxLocation();
334  NSPoint origin = NSMakePoint(cef_rect.x, cef_rect.y);
335  NSSize size = NSMakeSize(cef_rect.width, cef_rect.height);
336
337  NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(node_->GetWindowHandle());
338  origin.y = NSHeight([view bounds]) - origin.y;
339  NSPoint originInWindow = [view convertPoint:origin toView:nil];
340
341  NSRect point_rect = NSMakeRect(originInWindow.x, originInWindow.y, 0, 0);
342  NSPoint originInScreen =
343      [[view window] convertRectToScreen:point_rect].origin;
344
345  originInScreen.y = originInScreen.y - size.height;
346  return originInScreen;
347}
348
349- (NSSize)size {
350  CefRect cef_rect = node_->AxLocation();
351  NSRect rect =
352      NSMakeRect(cef_rect.x, cef_rect.y, cef_rect.width, cef_rect.height);
353  NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(node_->GetWindowHandle());
354  rect = [[view window] convertRectToScreen:rect];
355  return rect.size;
356}
357
358//
359// accessibility protocol
360//
361
362// attributes
363
364- (BOOL)accessibilityIsIgnored {
365  return NO;
366}
367
368- (NSArray*)accessibilityAttributeNames {
369  static NSArray* attributes = nil;
370  if (attributes == nil) {
371    attributes = [[NSArray alloc]
372        initWithObjects:NSAccessibilityRoleAttribute,
373                        NSAccessibilityRoleDescriptionAttribute,
374                        NSAccessibilityChildrenAttribute,
375                        NSAccessibilityValueAttribute,
376                        NSAccessibilityTitleAttribute,
377                        NSAccessibilityDescriptionAttribute,
378                        NSAccessibilityFocusedAttribute,
379                        NSAccessibilityParentAttribute,
380                        NSAccessibilityWindowAttribute,
381                        NSAccessibilityTopLevelUIElementAttribute,
382                        NSAccessibilityPositionAttribute,
383                        NSAccessibilitySizeAttribute, nil];
384  }
385  return attributes;
386}
387
388- (id)accessibilityAttributeValue:(NSString*)attribute {
389  NSObject* typed_parent = CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(parent_);
390  if (!node_)
391    return nil;
392  if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
393    return [self axRole];
394  } else if ([attribute
395                 isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
396    return NSAccessibilityRoleDescription([self axRole], nil);
397  } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
398    // Just check if the app thinks we're focused.
399    id focusedElement = [NSApp
400        accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute];
401    return [NSNumber numberWithBool:[focusedElement isEqual:self]];
402  } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) {
403    return NSAccessibilityUnignoredAncestor(typed_parent);
404  } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
405    return NSAccessibilityUnignoredChildren([self getKids]);
406  } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) {
407    // We're in the same window as our parent.
408    return [typed_parent
409        accessibilityAttributeValue:NSAccessibilityWindowAttribute];
410  } else if ([attribute
411                 isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) {
412    // We're in the same top level element as our parent.
413    return [typed_parent
414        accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
415  } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) {
416    return [NSValue valueWithPoint:[self position]];
417  } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) {
418    return [NSValue valueWithSize:[self size]];
419  } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) {
420    return [self axDescription];
421  } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
422    return [self axValue];
423  } else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) {
424    return [self axName];
425  }
426  return nil;
427}
428
429- (id)accessibilityHitTest:(NSPoint)point {
430  return NSAccessibilityUnignoredAncestor(self);
431}
432
433- (NSArray*)accessibilityActionNames {
434  return [NSArray arrayWithObject:NSAccessibilityPressAction];
435}
436
437- (NSString*)accessibilityActionDescription:(NSString*)action {
438  return NSAccessibilityActionDescription(action);
439}
440
441- (void)accessibilityPerformAction:(NSString*)action {
442  if ([action isEqualToString:NSAccessibilityPressAction]) {
443    // Do Click on Default action
444    [self doMouseClick:MBT_LEFT];
445  } else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
446    // Right click for Context Menu
447    [self doMouseClick:MBT_RIGHT];
448  }
449}
450
451- (id)accessibilityFocusedUIElement {
452  return NSAccessibilityUnignoredAncestor(self);
453}
454
455- (BOOL)accessibilityNotifiesWhenDestroyed {
456  // Indicate that BrowserAccessibilityCocoa will post a notification when it's
457  // destroyed (see -detach). This allows VoiceOver to do some internal things
458  // more efficiently.
459  return YES;
460}
461
462@end
463
464namespace client {
465
466void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {
467  NSView* view = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(GetWindowHandle());
468  if (event_type == "focus") {
469    NSAccessibilityPostNotification(
470        view, NSAccessibilityFocusedUIElementChangedNotification);
471  } else if (event_type == "textChanged") {
472    NSAccessibilityPostNotification(view,
473                                    NSAccessibilityTitleChangedNotification);
474  } else if (event_type == "valueChanged") {
475    NSAccessibilityPostNotification(view,
476                                    NSAccessibilityValueChangedNotification);
477  } else if (event_type == "textSelectionChanged") {
478    NSAccessibilityPostNotification(view,
479                                    NSAccessibilityValueChangedNotification);
480  }
481}
482
483void OsrAXNode::Destroy() {
484  if (platform_accessibility_) {
485    NSAccessibilityPostNotification(
486        CAST_CEF_NATIVE_ACCESSIBLE_TO_NSOBJECT(platform_accessibility_),
487        NSAccessibilityUIElementDestroyedNotification);
488  }
489
490  delete this;
491}
492
493// Create and return NSAccessibility Implementation Object for Mac
494CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(
495    client::OsrAXNode* parent) {
496  if (!platform_accessibility_) {
497    platform_accessibility_ = CAST_NSOBJECT_TO_CEF_NATIVE_ACCESSIBLE(
498        [OsrAXNodeObject elementWithNode:this]);
499    SetParent(parent);
500  }
501  return platform_accessibility_;
502}
503
504}  // namespace client
505