1 // Copyright 2014 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.
5 #import "ui/accessibility/platform/ax_platform_node_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/strings/sys_string_conversions.h"
10 #import "ui/accessibility/ax_node_data.h"
11 #import "ui/accessibility/platform/ax_platform_node_delegate.h"
12 #import "ui/gfx/mac/coordinate_conversion.h"
18 NSString* nativeValue;
21 typedef std::map<ui::AXRole, NSString*> RoleMap;
23 RoleMap BuildRoleMap() {
24 const MapEntry roles[] = {
25 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole},
26 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole},
27 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole},
28 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole},
29 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole},
30 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole},
31 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole},
32 {ui::AX_ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole},
33 {ui::AX_ROLE_BUTTON, NSAccessibilityButtonRole},
34 {ui::AX_ROLE_CANVAS, NSAccessibilityImageRole},
35 {ui::AX_ROLE_CAPTION, NSAccessibilityGroupRole},
36 {ui::AX_ROLE_CELL, @"AXCell"},
37 {ui::AX_ROLE_CHECK_BOX, NSAccessibilityCheckBoxRole},
38 {ui::AX_ROLE_COLOR_WELL, NSAccessibilityColorWellRole},
39 {ui::AX_ROLE_COLUMN, NSAccessibilityColumnRole},
40 {ui::AX_ROLE_COLUMN_HEADER, @"AXCell"},
41 {ui::AX_ROLE_COMBO_BOX, NSAccessibilityComboBoxRole},
42 {ui::AX_ROLE_COMPLEMENTARY, NSAccessibilityGroupRole},
43 {ui::AX_ROLE_CONTENT_INFO, NSAccessibilityGroupRole},
44 {ui::AX_ROLE_DATE, @"AXDateField"},
45 {ui::AX_ROLE_DATE_TIME, NSAccessibilityTextFieldRole},
46 {ui::AX_ROLE_DEFINITION, NSAccessibilityGroupRole},
47 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, NSAccessibilityGroupRole},
48 {ui::AX_ROLE_DESCRIPTION_LIST, NSAccessibilityListRole},
49 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, NSAccessibilityGroupRole},
50 {ui::AX_ROLE_DIALOG, NSAccessibilityGroupRole},
51 {ui::AX_ROLE_DETAILS, NSAccessibilityGroupRole},
52 {ui::AX_ROLE_DIRECTORY, NSAccessibilityListRole},
53 {ui::AX_ROLE_DISCLOSURE_TRIANGLE, NSAccessibilityDisclosureTriangleRole},
54 {ui::AX_ROLE_DIV, NSAccessibilityGroupRole},
55 {ui::AX_ROLE_DOCUMENT, NSAccessibilityGroupRole},
56 {ui::AX_ROLE_FIGCAPTION, NSAccessibilityGroupRole},
57 {ui::AX_ROLE_FIGURE, NSAccessibilityGroupRole},
58 {ui::AX_ROLE_FOOTER, NSAccessibilityGroupRole},
59 {ui::AX_ROLE_FORM, NSAccessibilityGroupRole},
60 {ui::AX_ROLE_GRID, NSAccessibilityGridRole},
61 {ui::AX_ROLE_GROUP, NSAccessibilityGroupRole},
62 {ui::AX_ROLE_HEADING, @"AXHeading"},
63 {ui::AX_ROLE_IFRAME, NSAccessibilityGroupRole},
64 {ui::AX_ROLE_IFRAME_PRESENTATIONAL, NSAccessibilityGroupRole},
65 {ui::AX_ROLE_IGNORED, NSAccessibilityUnknownRole},
66 {ui::AX_ROLE_IMAGE, NSAccessibilityImageRole},
67 {ui::AX_ROLE_IMAGE_MAP, NSAccessibilityGroupRole},
68 {ui::AX_ROLE_IMAGE_MAP_LINK, NSAccessibilityLinkRole},
69 {ui::AX_ROLE_INPUT_TIME, @"AXTimeField"},
70 {ui::AX_ROLE_LABEL_TEXT, NSAccessibilityGroupRole},
71 {ui::AX_ROLE_LEGEND, NSAccessibilityGroupRole},
72 {ui::AX_ROLE_LINK, NSAccessibilityLinkRole},
73 {ui::AX_ROLE_LIST, NSAccessibilityListRole},
74 {ui::AX_ROLE_LIST_BOX, NSAccessibilityListRole},
75 {ui::AX_ROLE_LIST_BOX_OPTION, NSAccessibilityStaticTextRole},
76 {ui::AX_ROLE_LIST_ITEM, NSAccessibilityGroupRole},
77 {ui::AX_ROLE_LIST_MARKER, @"AXListMarker"},
78 {ui::AX_ROLE_LOG, NSAccessibilityGroupRole},
79 {ui::AX_ROLE_MAIN, NSAccessibilityGroupRole},
80 {ui::AX_ROLE_MARK, NSAccessibilityGroupRole},
81 {ui::AX_ROLE_MARQUEE, NSAccessibilityGroupRole},
82 {ui::AX_ROLE_MATH, NSAccessibilityGroupRole},
83 {ui::AX_ROLE_MENU, NSAccessibilityMenuRole},
84 {ui::AX_ROLE_MENU_BAR, NSAccessibilityMenuBarRole},
85 {ui::AX_ROLE_MENU_BUTTON, NSAccessibilityButtonRole},
86 {ui::AX_ROLE_MENU_ITEM, NSAccessibilityMenuItemRole},
87 {ui::AX_ROLE_MENU_ITEM_CHECK_BOX, NSAccessibilityMenuItemRole},
88 {ui::AX_ROLE_MENU_ITEM_RADIO, NSAccessibilityMenuItemRole},
89 {ui::AX_ROLE_MENU_LIST_OPTION, NSAccessibilityMenuItemRole},
90 {ui::AX_ROLE_MENU_LIST_POPUP, NSAccessibilityUnknownRole},
91 {ui::AX_ROLE_METER, NSAccessibilityProgressIndicatorRole},
92 {ui::AX_ROLE_NAVIGATION, NSAccessibilityGroupRole},
93 {ui::AX_ROLE_NONE, NSAccessibilityGroupRole},
94 {ui::AX_ROLE_NOTE, NSAccessibilityGroupRole},
95 {ui::AX_ROLE_OUTLINE, NSAccessibilityOutlineRole},
96 {ui::AX_ROLE_PARAGRAPH, NSAccessibilityGroupRole},
97 {ui::AX_ROLE_POP_UP_BUTTON, NSAccessibilityPopUpButtonRole},
98 {ui::AX_ROLE_PRE, NSAccessibilityGroupRole},
99 {ui::AX_ROLE_PRESENTATIONAL, NSAccessibilityGroupRole},
100 {ui::AX_ROLE_PROGRESS_INDICATOR, NSAccessibilityProgressIndicatorRole},
101 {ui::AX_ROLE_RADIO_BUTTON, NSAccessibilityRadioButtonRole},
102 {ui::AX_ROLE_RADIO_GROUP, NSAccessibilityRadioGroupRole},
103 {ui::AX_ROLE_REGION, NSAccessibilityGroupRole},
104 {ui::AX_ROLE_ROOT_WEB_AREA, @"AXWebArea"},
105 {ui::AX_ROLE_ROW, NSAccessibilityRowRole},
106 {ui::AX_ROLE_ROW_HEADER, @"AXCell"},
107 {ui::AX_ROLE_RULER, NSAccessibilityRulerRole},
108 {ui::AX_ROLE_SCROLL_BAR, NSAccessibilityScrollBarRole},
109 {ui::AX_ROLE_SEARCH, NSAccessibilityGroupRole},
110 {ui::AX_ROLE_SEARCH_BOX, NSAccessibilityTextFieldRole},
111 {ui::AX_ROLE_SLIDER, NSAccessibilitySliderRole},
112 {ui::AX_ROLE_SLIDER_THUMB, NSAccessibilityValueIndicatorRole},
113 {ui::AX_ROLE_SPIN_BUTTON, NSAccessibilityIncrementorRole},
114 {ui::AX_ROLE_SPLITTER, NSAccessibilitySplitterRole},
115 {ui::AX_ROLE_STATIC_TEXT, NSAccessibilityStaticTextRole},
116 {ui::AX_ROLE_STATUS, NSAccessibilityGroupRole},
117 {ui::AX_ROLE_SVG_ROOT, NSAccessibilityGroupRole},
118 {ui::AX_ROLE_SWITCH, NSAccessibilityCheckBoxRole},
119 {ui::AX_ROLE_TAB, NSAccessibilityRadioButtonRole},
120 {ui::AX_ROLE_TABLE, NSAccessibilityTableRole},
121 {ui::AX_ROLE_TABLE_HEADER_CONTAINER, NSAccessibilityGroupRole},
122 {ui::AX_ROLE_TAB_LIST, NSAccessibilityTabGroupRole},
123 {ui::AX_ROLE_TAB_PANEL, NSAccessibilityGroupRole},
124 {ui::AX_ROLE_TEXT_FIELD, NSAccessibilityTextFieldRole},
125 {ui::AX_ROLE_TIME, NSAccessibilityGroupRole},
126 {ui::AX_ROLE_TIMER, NSAccessibilityGroupRole},
127 {ui::AX_ROLE_TOGGLE_BUTTON, NSAccessibilityCheckBoxRole},
128 {ui::AX_ROLE_TOOLBAR, NSAccessibilityToolbarRole},
129 {ui::AX_ROLE_TOOLTIP, NSAccessibilityGroupRole},
130 {ui::AX_ROLE_TREE, NSAccessibilityOutlineRole},
131 {ui::AX_ROLE_TREE_GRID, NSAccessibilityTableRole},
132 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole},
133 {ui::AX_ROLE_WEB_AREA, @"AXWebArea"},
134 {ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole},
136 // TODO(dtseng): we don't correctly support the attributes for these
138 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole },
142 for (size_t i = 0; i < arraysize(roles); ++i)
143 role_map[roles[i].value] = roles[i].nativeValue;
147 RoleMap BuildSubroleMap() {
148 const MapEntry subroles[] = {
149 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"},
150 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"},
151 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"},
152 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"},
153 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"},
154 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"},
155 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"},
156 {ui::AX_ROLE_DEFINITION, @"AXDefinition"},
157 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"},
158 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"},
159 {ui::AX_ROLE_DIALOG, @"AXApplicationDialog"},
160 {ui::AX_ROLE_DOCUMENT, @"AXDocument"},
161 {ui::AX_ROLE_FOOTER, @"AXLandmarkContentInfo"},
162 {ui::AX_ROLE_FORM, @"AXLandmarkForm"},
163 {ui::AX_ROLE_LOG, @"AXApplicationLog"},
164 {ui::AX_ROLE_MAIN, @"AXLandmarkMain"},
165 {ui::AX_ROLE_MARQUEE, @"AXApplicationMarquee"},
166 {ui::AX_ROLE_MATH, @"AXDocumentMath"},
167 {ui::AX_ROLE_NAVIGATION, @"AXLandmarkNavigation"},
168 {ui::AX_ROLE_NOTE, @"AXDocumentNote"},
169 {ui::AX_ROLE_REGION, @"AXDocumentRegion"},
170 {ui::AX_ROLE_SEARCH, @"AXLandmarkSearch"},
171 {ui::AX_ROLE_SEARCH_BOX, @"AXSearchField"},
172 {ui::AX_ROLE_STATUS, @"AXApplicationStatus"},
173 {ui::AX_ROLE_SWITCH, @"AXSwitch"},
174 {ui::AX_ROLE_TAB_PANEL, @"AXTabPanel"},
175 {ui::AX_ROLE_TIMER, @"AXApplicationTimer"},
176 {ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton"},
177 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"},
178 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole},
182 for (size_t i = 0; i < arraysize(subroles); ++i)
183 subrole_map[subroles[i].value] = subroles[i].nativeValue;
189 @implementation AXPlatformNodeCocoa
191 // A mapping of AX roles to native roles.
192 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role {
193 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap()));
194 RoleMap::iterator it = role_map.find(role);
195 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole;
198 // A mapping of AX roles to native subroles.
199 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role {
200 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap()));
201 RoleMap::iterator it = subrole_map.find(role);
202 return it != subrole_map.end() ? it->second : nil;
205 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node {
206 if ((self = [super init])) {
216 - (NSRect)boundsInScreen {
219 return gfx::ScreenRectToNSRect(node_->GetBoundsInScreen());
222 - (NSArray*)AXChildren {
225 int count = node_->GetChildCount();
226 NSMutableArray* children = [NSMutableArray arrayWithCapacity:count];
227 for (int i = 0; i < count; ++i)
228 [children addObject:node_->ChildAtIndex(i)];
229 return NSAccessibilityUnignoredChildren(children);
235 return NSAccessibilityUnignoredAncestor(node_->GetParent());
238 - (NSValue*)AXPosition {
239 return [NSValue valueWithPoint:self.boundsInScreen.origin];
242 - (NSString*)AXRole {
245 return [[self class] nativeRoleFromAXRole:node_->GetData().role];
249 return [NSValue valueWithSize:self.boundsInScreen.size];
252 - (NSString*)AXTitle {
254 if (node_->GetStringAttribute(ui::AX_ATTR_NAME, &value))
255 return base::SysUTF8ToNSString(value);
259 // NSAccessibility informal protocol implementation.
261 - (BOOL)accessibilityIsIgnored {
262 return [[self AXRole] isEqualToString:NSAccessibilityUnknownRole];
265 - (id)accessibilityHitTest:(NSPoint)point {
266 for (AXPlatformNodeCocoa* child in [self AXChildren]) {
267 if (NSPointInRect(point, child.boundsInScreen))
268 return [child accessibilityHitTest:point];
270 return NSAccessibilityUnignoredAncestor(self);
273 - (NSArray*)accessibilityActionNames {
277 - (NSArray*)accessibilityAttributeNames {
278 // These attributes are required on all accessibility objects.
280 NSAccessibilityChildrenAttribute,
281 NSAccessibilityParentAttribute,
282 NSAccessibilityPositionAttribute,
283 NSAccessibilityRoleAttribute,
284 NSAccessibilitySizeAttribute,
286 // Title is required for most elements. Cocoa asks for the value even if it
287 // is omitted here, but won't present it to accessibility APIs without this.
288 NSAccessibilityTitleAttribute,
290 // TODO(tapted): Add additional attributes based on role.
293 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
297 - (id)accessibilityAttributeValue:(NSString*)attribute {
298 SEL selector = NSSelectorFromString(attribute);
299 if ([self respondsToSelector:selector])
300 return [self performSelector:selector];
309 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) {
310 AXPlatformNodeBase* node = new AXPlatformNodeMac();
311 node->Init(delegate);
315 AXPlatformNodeMac::AXPlatformNodeMac() {
318 AXPlatformNodeMac::~AXPlatformNodeMac() {
321 void AXPlatformNodeMac::Destroy() {
323 [native_node_ detach];
328 gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() {
330 native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]);
331 return native_node_.get();
334 void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) {
335 // TODO(dmazzoni): implement this. http://crbug.com/396137
338 int AXPlatformNodeMac::GetIndexInParent() {
339 // TODO(dmazzoni): implement this. http://crbug.com/396137