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 ActionMap = std::map<ui::AXAction, NSString*>; |
26 }; | |
27 | 26 |
28 struct EventMapEntry { | 27 // The AXAction for NSAccessibilityPressAction, which needs special handling. |
29 ui::AXEvent value; | 28 constexpr ui::AXAction kActionForPress = ui::AX_ACTION_DO_DEFAULT; |
30 NSString* nativeValue; | |
31 }; | |
32 | |
33 typedef std::map<ui::AXRole, NSString*> RoleMap; | |
34 typedef std::map<ui::AXEvent, NSString*> EventMap; | |
35 | 29 |
36 RoleMap BuildRoleMap() { | 30 RoleMap BuildRoleMap() { |
37 const RoleMapEntry roles[] = { | 31 const RoleMap::value_type roles[] = { |
38 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, | 32 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, |
39 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, | 33 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, |
40 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, | 34 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, |
41 {ui::AX_ROLE_ANCHOR, NSAccessibilityGroupRole}, | 35 {ui::AX_ROLE_ANCHOR, NSAccessibilityGroupRole}, |
42 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, | 36 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, |
43 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, | 37 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, |
44 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, | 38 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, |
45 {ui::AX_ROLE_AUDIO, NSAccessibilityGroupRole}, | 39 {ui::AX_ROLE_AUDIO, NSAccessibilityGroupRole}, |
46 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, | 40 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, |
47 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole}, | 41 {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}, | 144 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityRowRole}, |
151 {ui::AX_ROLE_VIDEO, NSAccessibilityGroupRole}, | 145 {ui::AX_ROLE_VIDEO, NSAccessibilityGroupRole}, |
152 {ui::AX_ROLE_WEB_AREA, @"AXWebArea"}, | 146 {ui::AX_ROLE_WEB_AREA, @"AXWebArea"}, |
153 {ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole}, | 147 {ui::AX_ROLE_WINDOW, NSAccessibilityWindowRole}, |
154 | 148 |
155 // TODO(dtseng): we don't correctly support the attributes for these | 149 // TODO(dtseng): we don't correctly support the attributes for these |
156 // roles. | 150 // roles. |
157 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, | 151 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, |
158 }; | 152 }; |
159 | 153 |
160 RoleMap role_map; | 154 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 } | 155 } |
165 | 156 |
166 RoleMap BuildSubroleMap() { | 157 RoleMap BuildSubroleMap() { |
167 const RoleMapEntry subroles[] = { | 158 const RoleMap::value_type subroles[] = { |
168 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, | 159 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, |
169 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, | 160 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, |
170 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, | 161 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, |
171 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, | 162 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, |
172 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, | 163 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, |
173 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, | 164 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, |
174 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, | 165 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, |
175 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, | 166 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, |
176 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, | 167 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, |
177 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, | 168 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, |
(...skipping 13 matching lines...) Expand all Loading... | |
191 {ui::AX_ROLE_STATUS, @"AXApplicationStatus"}, | 182 {ui::AX_ROLE_STATUS, @"AXApplicationStatus"}, |
192 {ui::AX_ROLE_SWITCH, @"AXSwitch"}, | 183 {ui::AX_ROLE_SWITCH, @"AXSwitch"}, |
193 {ui::AX_ROLE_TAB_PANEL, @"AXTabPanel"}, | 184 {ui::AX_ROLE_TAB_PANEL, @"AXTabPanel"}, |
194 {ui::AX_ROLE_TERM, @"AXTerm"}, | 185 {ui::AX_ROLE_TERM, @"AXTerm"}, |
195 {ui::AX_ROLE_TIMER, @"AXApplicationTimer"}, | 186 {ui::AX_ROLE_TIMER, @"AXApplicationTimer"}, |
196 {ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton"}, | 187 {ui::AX_ROLE_TOGGLE_BUTTON, @"AXToggleButton"}, |
197 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, | 188 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, |
198 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, | 189 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, |
199 }; | 190 }; |
200 | 191 |
201 RoleMap subrole_map; | 192 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 } | 193 } |
206 | 194 |
207 EventMap BuildEventMap() { | 195 EventMap BuildEventMap() { |
208 const EventMapEntry events[] = { | 196 const EventMap::value_type events[] = { |
209 {ui::AX_EVENT_FOCUS, NSAccessibilityFocusedUIElementChangedNotification}, | 197 {ui::AX_EVENT_FOCUS, NSAccessibilityFocusedUIElementChangedNotification}, |
210 {ui::AX_EVENT_TEXT_CHANGED, NSAccessibilityTitleChangedNotification}, | 198 {ui::AX_EVENT_TEXT_CHANGED, NSAccessibilityTitleChangedNotification}, |
211 {ui::AX_EVENT_VALUE_CHANGED, NSAccessibilityValueChangedNotification}, | 199 {ui::AX_EVENT_VALUE_CHANGED, NSAccessibilityValueChangedNotification}, |
212 {ui::AX_EVENT_TEXT_SELECTION_CHANGED, | 200 {ui::AX_EVENT_TEXT_SELECTION_CHANGED, |
213 NSAccessibilitySelectedTextChangedNotification}, | 201 NSAccessibilitySelectedTextChangedNotification}, |
214 // TODO(patricialor): Add more events. | 202 // TODO(patricialor): Add more events. |
215 }; | 203 }; |
216 | 204 |
217 EventMap event_map; | 205 return EventMap(begin(events), end(events)); |
218 for (size_t i = 0; i < arraysize(events); ++i) | 206 } |
219 event_map[events[i].value] = events[i].nativeValue; | 207 |
220 return event_map; | 208 ActionMap BuildActionMap() { |
209 const ActionMap::value_type entries[] = { | |
210 {ui::AX_ACTION_DECREMENT, NSAccessibilityDecrementAction}, | |
211 {kActionForPress, NSAccessibilityPressAction}, | |
212 {ui::AX_ACTION_INCREMENT, NSAccessibilityIncrementAction}, | |
213 {ui::AX_ACTION_SHOW_CONTEXT_MENU, NSAccessibilityShowMenuAction}, | |
214 }; | |
215 return ActionMap(begin(entries), end(entries)); | |
216 } | |
217 | |
218 const ActionMap& GetActionMap() { | |
219 CR_DEFINE_STATIC_LOCAL(const ActionMap, action_map, (BuildActionMap())); | |
220 return action_map; | |
221 } | 221 } |
222 | 222 |
223 void NotifyMacEvent(AXPlatformNodeCocoa* target, ui::AXEvent event_type) { | 223 void NotifyMacEvent(AXPlatformNodeCocoa* target, ui::AXEvent event_type) { |
224 NSAccessibilityPostNotification( | 224 NSAccessibilityPostNotification( |
225 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); | 225 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); |
226 } | 226 } |
227 | 227 |
228 } // namespace | 228 } // namespace |
229 | 229 |
230 @interface AXPlatformNodeCocoa () | 230 @interface AXPlatformNodeCocoa () |
231 // Helper function for string attributes that don't require extra processing. | 231 // Helper function for string attributes that don't require extra processing. |
232 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; | 232 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; |
233 // Returns AXValue, or nil if AXValue isn't an NSString. | 233 // Returns AXValue, or nil if AXValue isn't an NSString. |
234 - (NSString*)getAXValueAsString; | 234 - (NSString*)getAXValueAsString; |
235 @end | 235 @end |
236 | 236 |
237 @implementation AXPlatformNodeCocoa { | 237 @implementation AXPlatformNodeCocoa { |
238 ui::AXPlatformNodeBase* node_; // Weak. Retains us. | 238 ui::AXPlatformNodeBase* node_; // Weak. Retains us. |
239 } | 239 } |
240 | 240 |
241 @synthesize node = node_; | 241 @synthesize node = node_; |
242 | 242 |
243 // A mapping of AX roles to native roles. | |
244 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { | 243 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { |
245 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); | 244 CR_DEFINE_STATIC_LOCAL(const RoleMap, role_map, (BuildRoleMap())); |
246 RoleMap::iterator it = role_map.find(role); | 245 RoleMap::const_iterator it = role_map.find(role); |
247 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; | 246 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; |
248 } | 247 } |
249 | 248 |
250 // A mapping of AX roles to native subroles. | |
251 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { | 249 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { |
252 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); | 250 CR_DEFINE_STATIC_LOCAL(const RoleMap, subrole_map, (BuildSubroleMap())); |
253 RoleMap::iterator it = subrole_map.find(role); | 251 RoleMap::const_iterator it = subrole_map.find(role); |
254 return it != subrole_map.end() ? it->second : nil; | 252 return it != subrole_map.end() ? it->second : nil; |
255 } | 253 } |
256 | 254 |
257 // A mapping of AX events to native notifications. | |
258 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { | 255 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { |
259 CR_DEFINE_STATIC_LOCAL(EventMap, event_map, (BuildEventMap())); | 256 CR_DEFINE_STATIC_LOCAL(const EventMap, event_map, (BuildEventMap())); |
260 EventMap::iterator it = event_map.find(event); | 257 EventMap::const_iterator it = event_map.find(event); |
261 return it != event_map.end() ? it->second : nil; | 258 return it != event_map.end() ? it->second : nil; |
262 } | 259 } |
263 | 260 |
264 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { | 261 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { |
265 if ((self = [super init])) { | 262 if ((self = [super init])) { |
266 node_ = node; | 263 node_ = node; |
267 } | 264 } |
268 return self; | 265 return self; |
269 } | 266 } |
270 | 267 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
316 } | 313 } |
317 | 314 |
318 - (id)accessibilityFocusedUIElement { | 315 - (id)accessibilityFocusedUIElement { |
319 return node_->GetDelegate()->GetFocus(); | 316 return node_->GetDelegate()->GetFocus(); |
320 } | 317 } |
321 | 318 |
322 - (NSArray*)accessibilityActionNames { | 319 - (NSArray*)accessibilityActionNames { |
323 base::scoped_nsobject<NSMutableArray> axActions( | 320 base::scoped_nsobject<NSMutableArray> axActions( |
324 [[NSMutableArray alloc] init]); | 321 [[NSMutableArray alloc] init]); |
325 | 322 |
323 const ui::AXNodeData& data = node_->GetData(); | |
324 | |
326 // VoiceOver expects the "press" action to be first. | 325 // VoiceOver expects the "press" action to be first. |
327 if (ui::IsRoleClickable(node_->GetData().role)) | 326 if (ui::IsRoleClickable(data.role) || data.HasAction(kActionForPress)) |
328 [axActions addObject:NSAccessibilityPressAction]; | 327 [axActions addObject:NSAccessibilityPressAction]; |
329 | 328 |
329 for (const ActionMap::value_type& entry : GetActionMap()) { | |
330 if (entry.first == kActionForPress) | |
331 continue; | |
332 | |
333 DCHECK(![entry.second isEqualToString:NSAccessibilityPressAction]); | |
334 | |
335 if (data.HasAction(entry.first)) | |
336 [axActions addObject:entry.second]; | |
337 } | |
338 | |
330 return axActions.autorelease(); | 339 return axActions.autorelease(); |
331 } | 340 } |
332 | 341 |
333 - (void)accessibilityPerformAction:(NSString*)action { | 342 - (void)accessibilityPerformAction:(NSString*)action { |
334 DCHECK([[self accessibilityActionNames] containsObject:action]); | 343 DCHECK([[self accessibilityActionNames] containsObject:action]); |
335 ui::AXActionData data; | 344 ui::AXActionData data; |
336 if ([action isEqualToString:NSAccessibilityPressAction]) | 345 for (const ActionMap::value_type& entry : GetActionMap()) { |
dmazzoni
2017/06/21 06:09:35
It doesn't look like you're ever using ActionMap a
tapted
2017/06/21 06:52:55
Indeed! good catch. and it's as easy as changing t
tapted
2017/06/21 11:13:11
Well.. everything _compiled_ fine with just the ty
| |
337 data.action = ui::AX_ACTION_DO_DEFAULT; | 346 if ([action isEqualToString:entry.second]) { |
347 data.action = entry.first; | |
348 break; | |
349 } | |
350 } | |
338 | 351 |
339 // Note ui::AX_ACTIONs which are just overwriting an accessibility attribute | 352 // Note ui::AX_ACTIONs which are just overwriting an accessibility attribute |
340 // are already implemented in -accessibilitySetValue:forAttribute:, so ignore | 353 // are already implemented in -accessibilitySetValue:forAttribute:, so ignore |
341 // those here. | 354 // those here. |
342 | 355 |
343 if (data.action != ui::AX_ACTION_NONE) | 356 if (data.action != ui::AX_ACTION_NONE) |
344 node_->GetDelegate()->AccessibilityPerformAction(data); | 357 node_->GetDelegate()->AccessibilityPerformAction(data); |
345 } | 358 } |
346 | 359 |
347 - (NSArray*)accessibilityAttributeNames { | 360 - (NSArray*)accessibilityAttributeNames { |
(...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
687 } | 700 } |
688 NotifyMacEvent(native_node_, event_type); | 701 NotifyMacEvent(native_node_, event_type); |
689 } | 702 } |
690 | 703 |
691 int AXPlatformNodeMac::GetIndexInParent() { | 704 int AXPlatformNodeMac::GetIndexInParent() { |
692 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 705 // TODO(dmazzoni): implement this. http://crbug.com/396137 |
693 return -1; | 706 return -1; |
694 } | 707 } |
695 | 708 |
696 } // namespace ui | 709 } // namespace ui |
OLD | NEW |