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

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: more interesting test Created 3 years, 5 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 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
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
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
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
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
OLDNEW
« no previous file with comments | « ui/accessibility/platform/ax_platform_node_mac.h ('k') | ui/views/accessibility/native_view_accessibility_base.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698