OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ui/accessibility/platform/ax_platform_node_mac.h" | 5 #import "ui/accessibility/platform/ax_platform_node_mac.h" |
6 | 6 |
7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
8 #include <stddef.h> | 8 #include <stddef.h> |
9 | 9 |
10 #include "base/macros.h" | 10 #include "base/macros.h" |
11 #include "base/strings/sys_string_conversions.h" | 11 #include "base/strings/sys_string_conversions.h" |
12 #include "ui/accessibility/ax_action_data.h" | 12 #include "ui/accessibility/ax_action_data.h" |
13 #include "ui/accessibility/ax_node_data.h" | 13 #include "ui/accessibility/ax_node_data.h" |
14 #include "ui/accessibility/ax_role_properties.h" | 14 #include "ui/accessibility/ax_role_properties.h" |
15 #include "ui/accessibility/platform/ax_platform_node.h" | 15 #include "ui/accessibility/platform/ax_platform_node.h" |
16 #include "ui/accessibility/platform/ax_platform_node_delegate.h" | 16 #include "ui/accessibility/platform/ax_platform_node_delegate.h" |
17 #include "ui/base/l10n/l10n_util.h" | 17 #include "ui/base/l10n/l10n_util.h" |
18 #import "ui/gfx/mac/coordinate_conversion.h" | 18 #import "ui/gfx/mac/coordinate_conversion.h" |
19 #include "ui/strings/grit/ui_strings.h" | 19 #include "ui/strings/grit/ui_strings.h" |
20 | 20 |
21 namespace { | 21 namespace { |
22 | 22 |
23 struct RoleMapEntry { | 23 using RoleMap = std::map<ui::AXRole, NSString*>; |
24 ui::AXRole value; | 24 using EventMap = std::map<ui::AXEvent, NSString*>; |
25 NSString* nativeValue; | 25 using ActionList = std::vector<std::pair<ui::AXAction, NSString*>>; |
26 }; | |
27 | |
28 struct EventMapEntry { | |
29 ui::AXEvent value; | |
30 NSString* nativeValue; | |
31 }; | |
32 | |
33 typedef std::map<ui::AXRole, NSString*> RoleMap; | |
34 typedef std::map<ui::AXEvent, NSString*> EventMap; | |
35 | 26 |
36 RoleMap BuildRoleMap() { | 27 RoleMap BuildRoleMap() { |
37 const RoleMapEntry roles[] = { | 28 const RoleMap::value_type roles[] = { |
38 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, | 29 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, |
39 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, | 30 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, |
40 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, | 31 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, |
41 {ui::AX_ROLE_ANCHOR, NSAccessibilityGroupRole}, | 32 {ui::AX_ROLE_ANCHOR, NSAccessibilityGroupRole}, |
42 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, | 33 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, |
43 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, | 34 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, |
44 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, | 35 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, |
45 {ui::AX_ROLE_AUDIO, NSAccessibilityGroupRole}, | 36 {ui::AX_ROLE_AUDIO, NSAccessibilityGroupRole}, |
46 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, | 37 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, |
47 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole}, | 38 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole}, |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
150 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole}, | 141 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole}, |
151 {ui::AX_ROLE_VIDEO, NSAccessibilityGroupRole}, | 142 {ui::AX_ROLE_VIDEO, NSAccessibilityGroupRole}, |
152 {ui::AX_ROLE_WEB_AREA, @"AXWebArea"}, | 143 {ui::AX_ROLE_WEB_AREA, @"AXWebArea"}, |
153 {ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole}, | 144 {ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole}, |
154 | 145 |
155 // TODO(dtseng): we don't correctly support the attributes for these | 146 // TODO(dtseng): we don't correctly support the attributes for these |
156 // roles. | 147 // roles. |
157 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, | 148 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, |
158 }; | 149 }; |
159 | 150 |
160 RoleMap role_map; | 151 return RoleMap(begin(roles), end(roles)); |
161 for (size_t i = 0; i < arraysize(roles); ++i) | |
162 role_map[roles[i].value] = roles[i].nativeValue; | |
163 return role_map; | |
164 } | 152 } |
165 | 153 |
166 RoleMap BuildSubroleMap() { | 154 RoleMap BuildSubroleMap() { |
167 const RoleMapEntry subroles[] = { | 155 const RoleMap::value_type subroles[] = { |
168 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, | 156 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, |
169 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, | 157 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, |
170 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, | 158 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, |
171 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, | 159 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, |
172 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, | 160 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, |
173 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, | 161 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, |
174 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, | 162 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, |
175 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, | 163 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, |
176 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, | 164 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, |
177 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, | 165 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, |
(...skipping 13 matching lines...) Expand all Loading... |
191 {ui::AX_ROLE_STATUS, @"AXApplicationStatus"}, | 179 {ui::AX_ROLE_STATUS, @"AXApplicationStatus"}, |
192 {ui::AX_ROLE_SWITCH, @"AXSwitch"}, | 180 {ui::AX_ROLE_SWITCH, @"AXSwitch"}, |
193 {ui::AX_ROLE_TAB_PANEL, @"AXTabPanel"}, | 181 {ui::AX_ROLE_TAB_PANEL, @"AXTabPanel"}, |
194 {ui::AX_ROLE_TERM, @"AXTerm"}, | 182 {ui::AX_ROLE_TERM, @"AXTerm"}, |
195 {ui::AX_ROLE_TIMER, @"AXApplicationTimer"}, | 183 {ui::AX_ROLE_TIMER, @"AXApplicationTimer"}, |
196 {ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton"}, | 184 {ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton"}, |
197 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, | 185 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, |
198 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, | 186 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, |
199 }; | 187 }; |
200 | 188 |
201 RoleMap subrole_map; | 189 return RoleMap(begin(subroles), end(subroles)); |
202 for (size_t i = 0; i < arraysize(subroles); ++i) | |
203 subrole_map[subroles[i].value] = subroles[i].nativeValue; | |
204 return subrole_map; | |
205 } | 190 } |
206 | 191 |
207 EventMap BuildEventMap() { | 192 EventMap BuildEventMap() { |
208 const EventMapEntry events[] = { | 193 const EventMap::value_type events[] = { |
209 {ui::AX_EVENT_FOCUS, NSAccessibilityFocusedUIElementChangedNotification}, | 194 {ui::AX_EVENT_FOCUS, NSAccessibilityFocusedUIElementChangedNotification}, |
210 {ui::AX_EVENT_TEXT_CHANGED, NSAccessibilityTitleChangedNotification}, | 195 {ui::AX_EVENT_TEXT_CHANGED, NSAccessibilityTitleChangedNotification}, |
211 {ui::AX_EVENT_VALUE_CHANGED, NSAccessibilityValueChangedNotification}, | 196 {ui::AX_EVENT_VALUE_CHANGED, NSAccessibilityValueChangedNotification}, |
212 {ui::AX_EVENT_TEXT_SELECTION_CHANGED, | 197 {ui::AX_EVENT_TEXT_SELECTION_CHANGED, |
213 NSAccessibilitySelectedTextChangedNotification}, | 198 NSAccessibilitySelectedTextChangedNotification}, |
214 // TODO(patricialor): Add more events. | 199 // TODO(patricialor): Add more events. |
215 }; | 200 }; |
216 | 201 |
217 EventMap event_map; | 202 return EventMap(begin(events), end(events)); |
218 for (size_t i = 0; i < arraysize(events); ++i) | 203 } |
219 event_map[events[i].value] = events[i].nativeValue; | 204 |
220 return event_map; | 205 ActionList BuildActionList() { |
| 206 const ActionList::value_type entries[] = { |
| 207 // NSAccessibilityPressAction must come first in this list. |
| 208 {ui::AX_ACTION_DO_DEFAULT, NSAccessibilityPressAction}, |
| 209 |
| 210 {ui::AX_ACTION_DECREMENT, NSAccessibilityDecrementAction}, |
| 211 {ui::AX_ACTION_INCREMENT, NSAccessibilityIncrementAction}, |
| 212 {ui::AX_ACTION_SHOW_CONTEXT_MENU, NSAccessibilityShowMenuAction}, |
| 213 }; |
| 214 return ActionList(begin(entries), end(entries)); |
| 215 } |
| 216 |
| 217 const ActionList& GetActionList() { |
| 218 CR_DEFINE_STATIC_LOCAL(const ActionList, action_map, (BuildActionList())); |
| 219 return action_map; |
221 } | 220 } |
222 | 221 |
223 void NotifyMacEvent(AXPlatformNodeCocoa* target, ui::AXEvent event_type) { | 222 void NotifyMacEvent(AXPlatformNodeCocoa* target, ui::AXEvent event_type) { |
224 NSAccessibilityPostNotification( | 223 NSAccessibilityPostNotification( |
225 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); | 224 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); |
226 } | 225 } |
227 | 226 |
| 227 // Returns true if |action| should be added implicitly for |data|. |
| 228 bool HasImplicitAction(const ui::AXNodeData& data, ui::AXAction action) { |
| 229 return action == ui::AX_ACTION_DO_DEFAULT && ui::IsRoleClickable(data.role); |
| 230 } |
| 231 |
| 232 // For roles that show a menu for the default action, ensure "show menu" also |
| 233 // appears in available actions, but only if that's not already used for a |
| 234 // context menu. It will be mapped back to the default action when performed. |
| 235 bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) { |
| 236 return HasImplicitAction(data, ui::AX_ACTION_DO_DEFAULT) && |
| 237 !data.HasAction(ui::AX_ACTION_SHOW_CONTEXT_MENU) && |
| 238 data.role == ui::AX_ROLE_POP_UP_BUTTON; |
| 239 } |
| 240 |
228 } // namespace | 241 } // namespace |
229 | 242 |
230 @interface AXPlatformNodeCocoa () | 243 @interface AXPlatformNodeCocoa () |
231 // Helper function for string attributes that don't require extra processing. | 244 // Helper function for string attributes that don't require extra processing. |
232 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; | 245 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; |
233 // Returns AXValue, or nil if AXValue isn't an NSString. | 246 // Returns AXValue, or nil if AXValue isn't an NSString. |
234 - (NSString*)getAXValueAsString; | 247 - (NSString*)getAXValueAsString; |
235 @end | 248 @end |
236 | 249 |
237 @implementation AXPlatformNodeCocoa { | 250 @implementation AXPlatformNodeCocoa { |
238 ui::AXPlatformNodeBase* node_; // Weak. Retains us. | 251 ui::AXPlatformNodeBase* node_; // Weak. Retains us. |
239 } | 252 } |
240 | 253 |
241 @synthesize node = node_; | 254 @synthesize node = node_; |
242 | 255 |
243 // A mapping of AX roles to native roles. | |
244 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { | 256 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { |
245 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); | 257 CR_DEFINE_STATIC_LOCAL(const RoleMap, role_map, (BuildRoleMap())); |
246 RoleMap::iterator it = role_map.find(role); | 258 RoleMap::const_iterator it = role_map.find(role); |
247 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; | 259 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; |
248 } | 260 } |
249 | 261 |
250 // A mapping of AX roles to native subroles. | |
251 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { | 262 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { |
252 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); | 263 CR_DEFINE_STATIC_LOCAL(const RoleMap, subrole_map, (BuildSubroleMap())); |
253 RoleMap::iterator it = subrole_map.find(role); | 264 RoleMap::const_iterator it = subrole_map.find(role); |
254 return it != subrole_map.end() ? it->second : nil; | 265 return it != subrole_map.end() ? it->second : nil; |
255 } | 266 } |
256 | 267 |
257 // A mapping of AX events to native notifications. | |
258 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { | 268 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { |
259 CR_DEFINE_STATIC_LOCAL(EventMap, event_map, (BuildEventMap())); | 269 CR_DEFINE_STATIC_LOCAL(const EventMap, event_map, (BuildEventMap())); |
260 EventMap::iterator it = event_map.find(event); | 270 EventMap::const_iterator it = event_map.find(event); |
261 return it != event_map.end() ? it->second : nil; | 271 return it != event_map.end() ? it->second : nil; |
262 } | 272 } |
263 | 273 |
264 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { | 274 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { |
265 if ((self = [super init])) { | 275 if ((self = [super init])) { |
266 node_ = node; | 276 node_ = node; |
267 } | 277 } |
268 return self; | 278 return self; |
269 } | 279 } |
270 | 280 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 } | 326 } |
317 | 327 |
318 - (id)accessibilityFocusedUIElement { | 328 - (id)accessibilityFocusedUIElement { |
319 return node_->GetDelegate()->GetFocus(); | 329 return node_->GetDelegate()->GetFocus(); |
320 } | 330 } |
321 | 331 |
322 - (NSArray*)accessibilityActionNames { | 332 - (NSArray*)accessibilityActionNames { |
323 base::scoped_nsobject<NSMutableArray> axActions( | 333 base::scoped_nsobject<NSMutableArray> axActions( |
324 [[NSMutableArray alloc] init]); | 334 [[NSMutableArray alloc] init]); |
325 | 335 |
326 // VoiceOver expects the "press" action to be first. | 336 const ui::AXNodeData& data = node_->GetData(); |
327 if (ui::IsRoleClickable(node_->GetData().role)) | 337 const ActionList& action_list = GetActionList(); |
328 [axActions addObject:NSAccessibilityPressAction]; | 338 |
| 339 // VoiceOver expects the "press" action to be first. Note that some roles |
| 340 // should be given a press action implicitly. |
| 341 DCHECK([action_list[0].second isEqualToString:NSAccessibilityPressAction]); |
| 342 for (const auto item : action_list) { |
| 343 if (data.HasAction(item.first) || HasImplicitAction(data, item.first)) |
| 344 [axActions addObject:item.second]; |
| 345 } |
| 346 |
| 347 if (AlsoUseShowMenuActionForDefaultAction(data)) |
| 348 [axActions addObject:NSAccessibilityShowMenuAction]; |
329 | 349 |
330 return axActions.autorelease(); | 350 return axActions.autorelease(); |
331 } | 351 } |
332 | 352 |
333 - (void)accessibilityPerformAction:(NSString*)action { | 353 - (void)accessibilityPerformAction:(NSString*)action { |
334 DCHECK([[self accessibilityActionNames] containsObject:action]); | 354 DCHECK([[self accessibilityActionNames] containsObject:action]); |
335 ui::AXActionData data; | 355 ui::AXActionData data; |
336 if ([action isEqualToString:NSAccessibilityPressAction]) | 356 if ([action isEqualToString:NSAccessibilityShowMenuAction] && |
| 357 AlsoUseShowMenuActionForDefaultAction(node_->GetData())) { |
337 data.action = ui::AX_ACTION_DO_DEFAULT; | 358 data.action = ui::AX_ACTION_DO_DEFAULT; |
| 359 } else { |
| 360 for (const ActionList::value_type& entry : GetActionList()) { |
| 361 if ([action isEqualToString:entry.second]) { |
| 362 data.action = entry.first; |
| 363 break; |
| 364 } |
| 365 } |
| 366 } |
338 | 367 |
339 // Note ui::AX_ACTIONs which are just overwriting an accessibility attribute | 368 // Note ui::AX_ACTIONs which are just overwriting an accessibility attribute |
340 // are already implemented in -accessibilitySetValue:forAttribute:, so ignore | 369 // are already implemented in -accessibilitySetValue:forAttribute:, so ignore |
341 // those here. | 370 // those here. |
342 | 371 |
343 if (data.action != ui::AX_ACTION_NONE) | 372 if (data.action != ui::AX_ACTION_NONE) |
344 node_->GetDelegate()->AccessibilityPerformAction(data); | 373 node_->GetDelegate()->AccessibilityPerformAction(data); |
345 } | 374 } |
346 | 375 |
347 - (NSArray*)accessibilityAttributeNames { | 376 - (NSArray*)accessibilityAttributeNames { |
(...skipping 436 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
784 } | 813 } |
785 NotifyMacEvent(native_node_, event_type); | 814 NotifyMacEvent(native_node_, event_type); |
786 } | 815 } |
787 | 816 |
788 int AXPlatformNodeMac::GetIndexInParent() { | 817 int AXPlatformNodeMac::GetIndexInParent() { |
789 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 818 // TODO(dmazzoni): implement this. http://crbug.com/396137 |
790 return -1; | 819 return -1; |
791 } | 820 } |
792 | 821 |
793 } // namespace ui | 822 } // namespace ui |
OLD | NEW |