Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(253)

Side by Side Diff: ui/accessibility/platform/ax_platform_node_mac.mm

Issue 2944083004: MacViews a11y: Support the "Show menu" action in Textfield and Combobox. (Closed)
Patch Set: base off crrev/2946783003 Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698