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_node_data.h" | 12 #include "ui/accessibility/ax_node_data.h" |
13 #include "ui/accessibility/ax_view_state.h" | 13 #include "ui/accessibility/ax_view_state.h" |
14 #include "ui/accessibility/platform/ax_platform_node_delegate.h" | 14 #include "ui/accessibility/platform/ax_platform_node_delegate.h" |
15 #import "ui/gfx/mac/coordinate_conversion.h" | 15 #import "ui/gfx/mac/coordinate_conversion.h" |
16 | 16 |
17 namespace { | 17 namespace { |
18 | 18 |
19 struct MapEntry { | 19 struct RoleMapEntry { |
20 ui::AXRole value; | 20 ui::AXRole value; |
21 NSString* nativeValue; | 21 NSString* nativeValue; |
22 }; | 22 }; |
23 | 23 |
24 struct EventMapEntry { | |
25 ui::AXEvent value; | |
26 NSString* nativeValue; | |
27 }; | |
28 | |
24 typedef std::map<ui::AXRole, NSString*> RoleMap; | 29 typedef std::map<ui::AXRole, NSString*> RoleMap; |
30 typedef std::map<ui::AXEvent, NSString*> EventMap; | |
25 | 31 |
26 RoleMap BuildRoleMap() { | 32 RoleMap BuildRoleMap() { |
27 const MapEntry roles[] = { | 33 const RoleMapEntry roles[] = { |
28 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, | 34 {ui::AX_ROLE_ABBR, NSAccessibilityGroupRole}, |
29 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, | 35 {ui::AX_ROLE_ALERT, NSAccessibilityGroupRole}, |
30 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, | 36 {ui::AX_ROLE_ALERT_DIALOG, NSAccessibilityGroupRole}, |
31 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, | 37 {ui::AX_ROLE_ANNOTATION, NSAccessibilityUnknownRole}, |
32 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, | 38 {ui::AX_ROLE_APPLICATION, NSAccessibilityGroupRole}, |
33 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, | 39 {ui::AX_ROLE_ARTICLE, NSAccessibilityGroupRole}, |
34 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, | 40 {ui::AX_ROLE_BANNER, NSAccessibilityGroupRole}, |
35 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole}, | 41 {ui::AX_ROLE_BLOCKQUOTE, NSAccessibilityGroupRole}, |
36 {ui::AX_ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole}, | 42 {ui::AX_ROLE_BUSY_INDICATOR, NSAccessibilityBusyIndicatorRole}, |
37 {ui::AX_ROLE_BUTTON, NSAccessibilityButtonRole}, | 43 {ui::AX_ROLE_BUTTON, NSAccessibilityButtonRole}, |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
143 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, | 149 // { ui::AX_ROLE_SCROLL_AREA, NSAccessibilityScrollAreaRole }, |
144 }; | 150 }; |
145 | 151 |
146 RoleMap role_map; | 152 RoleMap role_map; |
147 for (size_t i = 0; i < arraysize(roles); ++i) | 153 for (size_t i = 0; i < arraysize(roles); ++i) |
148 role_map[roles[i].value] = roles[i].nativeValue; | 154 role_map[roles[i].value] = roles[i].nativeValue; |
149 return role_map; | 155 return role_map; |
150 } | 156 } |
151 | 157 |
152 RoleMap BuildSubroleMap() { | 158 RoleMap BuildSubroleMap() { |
153 const MapEntry subroles[] = { | 159 const RoleMapEntry subroles[] = { |
154 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, | 160 {ui::AX_ROLE_ALERT, @"AXApplicationAlert"}, |
155 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, | 161 {ui::AX_ROLE_ALERT_DIALOG, @"AXApplicationAlertDialog"}, |
156 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, | 162 {ui::AX_ROLE_APPLICATION, @"AXLandmarkApplication"}, |
157 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, | 163 {ui::AX_ROLE_ARTICLE, @"AXDocumentArticle"}, |
158 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, | 164 {ui::AX_ROLE_BANNER, @"AXLandmarkBanner"}, |
159 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, | 165 {ui::AX_ROLE_COMPLEMENTARY, @"AXLandmarkComplementary"}, |
160 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, | 166 {ui::AX_ROLE_CONTENT_INFO, @"AXLandmarkContentInfo"}, |
161 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, | 167 {ui::AX_ROLE_DEFINITION, @"AXDefinition"}, |
162 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, | 168 {ui::AX_ROLE_DESCRIPTION_LIST_DETAIL, @"AXDefinition"}, |
163 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, | 169 {ui::AX_ROLE_DESCRIPTION_LIST_TERM, @"AXTerm"}, |
(...skipping 18 matching lines...) Expand all Loading... | |
182 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, | 188 {ui::AX_ROLE_TOOLTIP, @"AXUserInterfaceTooltip"}, |
183 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, | 189 {ui::AX_ROLE_TREE_ITEM, NSAccessibilityOutlineRowSubrole}, |
184 }; | 190 }; |
185 | 191 |
186 RoleMap subrole_map; | 192 RoleMap subrole_map; |
187 for (size_t i = 0; i < arraysize(subroles); ++i) | 193 for (size_t i = 0; i < arraysize(subroles); ++i) |
188 subrole_map[subroles[i].value] = subroles[i].nativeValue; | 194 subrole_map[subroles[i].value] = subroles[i].nativeValue; |
189 return subrole_map; | 195 return subrole_map; |
190 } | 196 } |
191 | 197 |
198 EventMap BuildEventMap() { | |
199 const EventMapEntry events[] = { | |
200 {ui::AX_EVENT_TEXT_CHANGED, NSAccessibilityTitleChangedNotification}, | |
201 {ui::AX_EVENT_VALUE_CHANGED, NSAccessibilityValueChangedNotification}, | |
202 {ui::AX_EVENT_TEXT_SELECTION_CHANGED, | |
203 NSAccessibilitySelectedTextChangedNotification}, | |
204 // TODO(patricialor): Add more events. | |
205 }; | |
206 | |
207 EventMap event_map; | |
208 for (size_t i = 0; i < arraysize(events); ++i) { | |
tapted
2016/06/30 01:58:57
nit: no curlies (consistent with the others)
Patti Lor
2016/06/30 05:29:54
Done.
| |
209 event_map[events[i].value] = events[i].nativeValue; | |
210 } | |
211 return event_map; | |
212 } | |
213 | |
214 void NotifyMacEvent(ui::AXEvent event_type, gfx::AcceleratedWidget target) { | |
tapted
2016/06/30 01:58:57
AcceleratedWidget -> NSView* (it's easier to read
Patti Lor
2016/06/30 05:29:54
Done.
| |
215 NSAccessibilityPostNotification( | |
216 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); | |
217 } | |
218 | |
192 } // namespace | 219 } // namespace |
193 | 220 |
194 @interface AXPlatformNodeCocoa () | 221 @interface AXPlatformNodeCocoa () |
195 // Helper function for string attributes that don't require extra processing. | 222 // Helper function for string attributes that don't require extra processing. |
196 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; | 223 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; |
197 @end | 224 @end |
198 | 225 |
199 @implementation AXPlatformNodeCocoa | 226 @implementation AXPlatformNodeCocoa |
200 | 227 |
201 // A mapping of AX roles to native roles. | 228 // A mapping of AX roles to native roles. |
202 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { | 229 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { |
203 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); | 230 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); |
204 RoleMap::iterator it = role_map.find(role); | 231 RoleMap::iterator it = role_map.find(role); |
205 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; | 232 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; |
206 } | 233 } |
207 | 234 |
208 // A mapping of AX roles to native subroles. | 235 // A mapping of AX roles to native subroles. |
209 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { | 236 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { |
210 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); | 237 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); |
211 RoleMap::iterator it = subrole_map.find(role); | 238 RoleMap::iterator it = subrole_map.find(role); |
212 return it != subrole_map.end() ? it->second : nil; | 239 return it != subrole_map.end() ? it->second : nil; |
213 } | 240 } |
214 | 241 |
242 // A mapping of AX events to native notifications. | |
243 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { | |
244 CR_DEFINE_STATIC_LOCAL(EventMap, event_map, (BuildEventMap())); | |
245 EventMap::iterator it = event_map.find(event); | |
246 return it != event_map.end() ? it->second : nil; | |
247 } | |
248 | |
215 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { | 249 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { |
216 if ((self = [super init])) { | 250 if ((self = [super init])) { |
217 node_ = node; | 251 node_ = node; |
218 } | 252 } |
219 return self; | 253 return self; |
220 } | 254 } |
221 | 255 |
222 - (void)detach { | 256 - (void)detach { |
223 node_ = nil; | 257 node_ = nil; |
224 } | 258 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
263 NSAccessibilityRoleAttribute, | 297 NSAccessibilityRoleAttribute, |
264 NSAccessibilitySizeAttribute, | 298 NSAccessibilitySizeAttribute, |
265 NSAccessibilitySubroleAttribute, | 299 NSAccessibilitySubroleAttribute, |
266 | 300 |
267 // Title is required for most elements. Cocoa asks for the value even if it | 301 // Title is required for most elements. Cocoa asks for the value even if it |
268 // is omitted here, but won't present it to accessibility APIs without this. | 302 // is omitted here, but won't present it to accessibility APIs without this. |
269 NSAccessibilityTitleAttribute, | 303 NSAccessibilityTitleAttribute, |
270 | 304 |
271 // Attributes which are not required, but are general to all roles. | 305 // Attributes which are not required, but are general to all roles. |
272 NSAccessibilityRoleDescriptionAttribute, | 306 NSAccessibilityRoleDescriptionAttribute, |
307 NSAccessibilityEnabledAttribute, | |
308 NSAccessibilityFocusedAttribute, | |
273 ]; | 309 ]; |
274 | 310 |
275 // Attributes required for user-editable controls. | 311 // Attributes required for user-editable controls. |
276 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ]; | 312 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ]; |
277 | 313 |
314 // Attributes required for textfields. | |
315 NSArray* const kTextfieldAttributes = @[ | |
316 NSAccessibilityInsertionPointLineNumberAttribute, | |
317 NSAccessibilityNumberOfCharactersAttribute, | |
318 NSAccessibilityPlaceholderValueAttribute, | |
319 NSAccessibilitySelectedTextAttribute, | |
320 NSAccessibilitySelectedTextRangeAttribute, | |
321 NSAccessibilityVisibleCharacterRangeAttribute, | |
322 ]; | |
323 | |
278 base::scoped_nsobject<NSMutableArray> axAttributes( | 324 base::scoped_nsobject<NSMutableArray> axAttributes( |
279 [[NSMutableArray alloc] init]); | 325 [[NSMutableArray alloc] init]); |
280 | 326 |
281 [axAttributes addObjectsFromArray:kAllRoleAttributes]; | 327 [axAttributes addObjectsFromArray:kAllRoleAttributes]; |
282 switch (node_->GetData().role) { | 328 switch (node_->GetData().role) { |
329 case ui::AX_ROLE_TEXT_FIELD: | |
330 [axAttributes addObjectsFromArray:kTextfieldAttributes]; | |
331 // Fallthrough. | |
283 case ui::AX_ROLE_CHECK_BOX: | 332 case ui::AX_ROLE_CHECK_BOX: |
284 case ui::AX_ROLE_COMBO_BOX: | 333 case ui::AX_ROLE_COMBO_BOX: |
285 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: | 334 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: |
286 case ui::AX_ROLE_MENU_ITEM_RADIO: | 335 case ui::AX_ROLE_MENU_ITEM_RADIO: |
287 case ui::AX_ROLE_RADIO_BUTTON: | 336 case ui::AX_ROLE_RADIO_BUTTON: |
288 case ui::AX_ROLE_SEARCH_BOX: | 337 case ui::AX_ROLE_SEARCH_BOX: |
289 case ui::AX_ROLE_SLIDER: | 338 case ui::AX_ROLE_SLIDER: |
290 case ui::AX_ROLE_SLIDER_THUMB: | 339 case ui::AX_ROLE_SLIDER_THUMB: |
291 case ui::AX_ROLE_TOGGLE_BUTTON: | 340 case ui::AX_ROLE_TOGGLE_BUTTON: |
292 case ui::AX_ROLE_TEXT_FIELD: | |
293 [axAttributes addObjectsFromArray:kValueAttributes]; | 341 [axAttributes addObjectsFromArray:kValueAttributes]; |
294 break; | 342 break; |
295 // TODO(tapted): Add additional attributes based on role. | 343 // TODO(tapted): Add additional attributes based on role. |
296 default: | 344 default: |
297 break; | 345 break; |
298 } | 346 } |
299 return axAttributes.autorelease(); | 347 return axAttributes.autorelease(); |
300 } | 348 } |
301 | 349 |
302 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { | 350 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
364 } | 412 } |
365 | 413 |
366 - (NSString*)AXTitle { | 414 - (NSString*)AXTitle { |
367 return [self getStringAttribute:ui::AX_ATTR_NAME]; | 415 return [self getStringAttribute:ui::AX_ATTR_NAME]; |
368 } | 416 } |
369 | 417 |
370 - (NSString*)AXValue { | 418 - (NSString*)AXValue { |
371 return [self getStringAttribute:ui::AX_ATTR_VALUE]; | 419 return [self getStringAttribute:ui::AX_ATTR_VALUE]; |
372 } | 420 } |
373 | 421 |
422 - (NSValue*)AXEnabled { | |
423 return [NSNumber | |
424 numberWithBool:!ui::AXViewState::IsFlagSet(node_->GetData().state, | |
425 ui::AX_STATE_DISABLED)]; | |
426 } | |
427 | |
428 - (NSValue*)AXFocused { | |
429 if (ui::AXViewState::IsFlagSet(node_->GetData().state, | |
430 ui::AX_STATE_FOCUSABLE)) | |
431 return [NSNumber numberWithBool:(node_->GetDelegate()->GetFocus() == | |
432 node_->GetNativeViewAccessible())]; | |
433 return [NSNumber numberWithBool:NO]; | |
434 } | |
435 | |
436 // Textfield-specific NSAccessibility attributes. | |
437 | |
438 - (NSNumber*)AXInsertionPointLineNumber { | |
439 // Multiline is not supported on views. | |
440 return [NSNumber numberWithInteger:0]; | |
441 } | |
442 | |
443 - (NSNumber*)AXNumberOfCharacters { | |
444 return [NSNumber numberWithInteger:[[self AXValue] length]]; | |
445 } | |
446 | |
447 - (NSString*)AXPlaceholderValue { | |
448 return [self getStringAttribute:ui::AX_ATTR_PLACEHOLDER]; | |
449 } | |
450 | |
451 - (NSString*)AXSelectedText { | |
452 NSRange selectedTextRange; | |
453 [[self AXSelectedTextRange] getValue:&selectedTextRange]; | |
454 return [[self AXValue] substringWithRange:selectedTextRange]; | |
455 } | |
456 | |
457 - (NSValue*)AXSelectedTextRange { | |
458 int textDir, start, end; | |
459 node_->GetIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, &textDir); | |
460 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &start); | |
461 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &end); | |
462 // NSRange cannot represent the direction the text was selected in, so make | |
463 // sure the correct selection index is used when creating a new range, taking | |
464 // into account the textfield text direction as well. | |
465 bool isReversed = (textDir == ui::AX_TEXT_DIRECTION_RTL) || | |
466 (textDir == ui::AX_TEXT_DIRECTION_BTT); | |
467 int beginSelectionIndex = (end > start && !isReversed) ? start : end; | |
468 return [NSValue | |
469 valueWithRange:NSMakeRange(beginSelectionIndex, abs(end - start))]; | |
470 } | |
471 | |
472 - (NSValue*)AXVisibleCharacterRange { | |
473 return [NSValue | |
474 valueWithRange:NSMakeRange(0, [[self AXNumberOfCharacters] intValue])]; | |
475 } | |
476 | |
374 @end | 477 @end |
375 | 478 |
376 namespace ui { | 479 namespace ui { |
377 | 480 |
378 // static | 481 // static |
379 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { | 482 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { |
380 AXPlatformNodeBase* node = new AXPlatformNodeMac(); | 483 AXPlatformNodeBase* node = new AXPlatformNodeMac(); |
381 node->Init(delegate); | 484 node->Init(delegate); |
382 return node; | 485 return node; |
383 } | 486 } |
(...skipping 10 matching lines...) Expand all Loading... | |
394 AXPlatformNodeBase::Destroy(); | 497 AXPlatformNodeBase::Destroy(); |
395 } | 498 } |
396 | 499 |
397 gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { | 500 gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { |
398 if (!native_node_) | 501 if (!native_node_) |
399 native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]); | 502 native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]); |
400 return native_node_.get(); | 503 return native_node_.get(); |
401 } | 504 } |
402 | 505 |
403 void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) { | 506 void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) { |
404 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 507 gfx::AcceleratedWidget target = |
tapted
2016/06/30 01:58:57
nit: NSView*
Patti Lor
2016/06/30 05:29:54
Done.
| |
508 GetDelegate()->GetTargetForNativeAccessibilityEvent(); | |
509 | |
510 // Add mappings between ui::AXEvent and NSAccessibility notifications using | |
511 // the EventMap above. This switch contains exceptions to those mappings. | |
512 switch (event_type) { | |
513 case ui::AX_EVENT_TEXT_CHANGED: | |
514 // If the view is a user-editable textfield, this should change the value. | |
515 if (GetData().role == ui::AX_ROLE_TEXT_FIELD) { | |
516 NSAccessibilityPostNotification( | |
tapted
2016/06/30 01:58:57
I'd go with
NotifyMacEvent(target, ui::AX_EVENT_V
Patti Lor
2016/06/30 05:29:54
Done, thanks!
| |
517 target, NSAccessibilityValueChangedNotification); | |
518 return; | |
519 } | |
520 break; | |
521 default: | |
522 break; | |
523 } | |
524 NotifyMacEvent(event_type, target); | |
405 } | 525 } |
406 | 526 |
407 int AXPlatformNodeMac::GetIndexInParent() { | 527 int AXPlatformNodeMac::GetIndexInParent() { |
408 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 528 // TODO(dmazzoni): implement this. http://crbug.com/396137 |
409 return -1; | 529 return -1; |
410 } | 530 } |
411 | 531 |
412 } // namespace ui | 532 } // namespace ui |
OLD | NEW |