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) |
| 209 event_map[events[i].value] = events[i].nativeValue; |
| 210 return event_map; |
| 211 } |
| 212 |
| 213 void NotifyMacEvent(NSView* target, ui::AXEvent event_type) { |
| 214 NSAccessibilityPostNotification( |
| 215 target, [AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type]); |
| 216 } |
| 217 |
192 } // namespace | 218 } // namespace |
193 | 219 |
194 @interface AXPlatformNodeCocoa () | 220 @interface AXPlatformNodeCocoa () |
195 // Helper function for string attributes that don't require extra processing. | 221 // Helper function for string attributes that don't require extra processing. |
196 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; | 222 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute; |
197 @end | 223 @end |
198 | 224 |
199 @implementation AXPlatformNodeCocoa | 225 @implementation AXPlatformNodeCocoa |
200 | 226 |
201 // A mapping of AX roles to native roles. | 227 // A mapping of AX roles to native roles. |
202 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { | 228 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { |
203 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); | 229 CR_DEFINE_STATIC_LOCAL(RoleMap, role_map, (BuildRoleMap())); |
204 RoleMap::iterator it = role_map.find(role); | 230 RoleMap::iterator it = role_map.find(role); |
205 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; | 231 return it != role_map.end() ? it->second : NSAccessibilityUnknownRole; |
206 } | 232 } |
207 | 233 |
208 // A mapping of AX roles to native subroles. | 234 // A mapping of AX roles to native subroles. |
209 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { | 235 + (NSString*)nativeSubroleFromAXRole:(ui::AXRole)role { |
210 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); | 236 CR_DEFINE_STATIC_LOCAL(RoleMap, subrole_map, (BuildSubroleMap())); |
211 RoleMap::iterator it = subrole_map.find(role); | 237 RoleMap::iterator it = subrole_map.find(role); |
212 return it != subrole_map.end() ? it->second : nil; | 238 return it != subrole_map.end() ? it->second : nil; |
213 } | 239 } |
214 | 240 |
| 241 // A mapping of AX events to native notifications. |
| 242 + (NSString*)nativeNotificationFromAXEvent:(ui::AXEvent)event { |
| 243 CR_DEFINE_STATIC_LOCAL(EventMap, event_map, (BuildEventMap())); |
| 244 EventMap::iterator it = event_map.find(event); |
| 245 return it != event_map.end() ? it->second : nil; |
| 246 } |
| 247 |
215 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { | 248 - (instancetype)initWithNode:(ui::AXPlatformNodeBase*)node { |
216 if ((self = [super init])) { | 249 if ((self = [super init])) { |
217 node_ = node; | 250 node_ = node; |
218 } | 251 } |
219 return self; | 252 return self; |
220 } | 253 } |
221 | 254 |
222 - (void)detach { | 255 - (void)detach { |
223 node_ = nil; | 256 node_ = nil; |
224 } | 257 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
263 NSAccessibilityRoleAttribute, | 296 NSAccessibilityRoleAttribute, |
264 NSAccessibilitySizeAttribute, | 297 NSAccessibilitySizeAttribute, |
265 NSAccessibilitySubroleAttribute, | 298 NSAccessibilitySubroleAttribute, |
266 | 299 |
267 // Title is required for most elements. Cocoa asks for the value even if it | 300 // 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. | 301 // is omitted here, but won't present it to accessibility APIs without this. |
269 NSAccessibilityTitleAttribute, | 302 NSAccessibilityTitleAttribute, |
270 | 303 |
271 // Attributes which are not required, but are general to all roles. | 304 // Attributes which are not required, but are general to all roles. |
272 NSAccessibilityRoleDescriptionAttribute, | 305 NSAccessibilityRoleDescriptionAttribute, |
| 306 NSAccessibilityEnabledAttribute, |
| 307 NSAccessibilityFocusedAttribute, |
273 ]; | 308 ]; |
274 | 309 |
275 // Attributes required for user-editable controls. | 310 // Attributes required for user-editable controls. |
276 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ]; | 311 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ]; |
277 | 312 |
| 313 // Attributes required for textfields. |
| 314 NSArray* const kTextfieldAttributes = @[ |
| 315 NSAccessibilityInsertionPointLineNumberAttribute, |
| 316 NSAccessibilityNumberOfCharactersAttribute, |
| 317 NSAccessibilityPlaceholderValueAttribute, |
| 318 NSAccessibilitySelectedTextAttribute, |
| 319 NSAccessibilitySelectedTextRangeAttribute, |
| 320 NSAccessibilityVisibleCharacterRangeAttribute, |
| 321 ]; |
| 322 |
278 base::scoped_nsobject<NSMutableArray> axAttributes( | 323 base::scoped_nsobject<NSMutableArray> axAttributes( |
279 [[NSMutableArray alloc] init]); | 324 [[NSMutableArray alloc] init]); |
280 | 325 |
281 [axAttributes addObjectsFromArray:kAllRoleAttributes]; | 326 [axAttributes addObjectsFromArray:kAllRoleAttributes]; |
282 switch (node_->GetData().role) { | 327 switch (node_->GetData().role) { |
| 328 case ui::AX_ROLE_TEXT_FIELD: |
| 329 [axAttributes addObjectsFromArray:kTextfieldAttributes]; |
| 330 // Fallthrough. |
283 case ui::AX_ROLE_CHECK_BOX: | 331 case ui::AX_ROLE_CHECK_BOX: |
284 case ui::AX_ROLE_COMBO_BOX: | 332 case ui::AX_ROLE_COMBO_BOX: |
285 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: | 333 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: |
286 case ui::AX_ROLE_MENU_ITEM_RADIO: | 334 case ui::AX_ROLE_MENU_ITEM_RADIO: |
287 case ui::AX_ROLE_RADIO_BUTTON: | 335 case ui::AX_ROLE_RADIO_BUTTON: |
288 case ui::AX_ROLE_SEARCH_BOX: | 336 case ui::AX_ROLE_SEARCH_BOX: |
289 case ui::AX_ROLE_SLIDER: | 337 case ui::AX_ROLE_SLIDER: |
290 case ui::AX_ROLE_SLIDER_THUMB: | 338 case ui::AX_ROLE_SLIDER_THUMB: |
291 case ui::AX_ROLE_TOGGLE_BUTTON: | 339 case ui::AX_ROLE_TOGGLE_BUTTON: |
292 case ui::AX_ROLE_TEXT_FIELD: | |
293 [axAttributes addObjectsFromArray:kValueAttributes]; | 340 [axAttributes addObjectsFromArray:kValueAttributes]; |
294 break; | 341 break; |
295 // TODO(tapted): Add additional attributes based on role. | 342 // TODO(tapted): Add additional attributes based on role. |
296 default: | 343 default: |
297 break; | 344 break; |
298 } | 345 } |
299 return axAttributes.autorelease(); | 346 return axAttributes.autorelease(); |
300 } | 347 } |
301 | 348 |
302 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { | 349 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
364 } | 411 } |
365 | 412 |
366 - (NSString*)AXTitle { | 413 - (NSString*)AXTitle { |
367 return [self getStringAttribute:ui::AX_ATTR_NAME]; | 414 return [self getStringAttribute:ui::AX_ATTR_NAME]; |
368 } | 415 } |
369 | 416 |
370 - (NSString*)AXValue { | 417 - (NSString*)AXValue { |
371 return [self getStringAttribute:ui::AX_ATTR_VALUE]; | 418 return [self getStringAttribute:ui::AX_ATTR_VALUE]; |
372 } | 419 } |
373 | 420 |
| 421 - (NSValue*)AXEnabled { |
| 422 return [NSNumber |
| 423 numberWithBool:!ui::AXViewState::IsFlagSet(node_->GetData().state, |
| 424 ui::AX_STATE_DISABLED)]; |
| 425 } |
| 426 |
| 427 - (NSValue*)AXFocused { |
| 428 if (ui::AXViewState::IsFlagSet(node_->GetData().state, |
| 429 ui::AX_STATE_FOCUSABLE)) |
| 430 return [NSNumber numberWithBool:(node_->GetDelegate()->GetFocus() == |
| 431 node_->GetNativeViewAccessible())]; |
| 432 return [NSNumber numberWithBool:NO]; |
| 433 } |
| 434 |
| 435 // Textfield-specific NSAccessibility attributes. |
| 436 |
| 437 - (NSNumber*)AXInsertionPointLineNumber { |
| 438 // Multiline is not supported on views. |
| 439 return [NSNumber numberWithInteger:0]; |
| 440 } |
| 441 |
| 442 - (NSNumber*)AXNumberOfCharacters { |
| 443 return [NSNumber numberWithInteger:[[self AXValue] length]]; |
| 444 } |
| 445 |
| 446 - (NSString*)AXPlaceholderValue { |
| 447 return [self getStringAttribute:ui::AX_ATTR_PLACEHOLDER]; |
| 448 } |
| 449 |
| 450 - (NSString*)AXSelectedText { |
| 451 NSRange selectedTextRange; |
| 452 [[self AXSelectedTextRange] getValue:&selectedTextRange]; |
| 453 return [[self AXValue] substringWithRange:selectedTextRange]; |
| 454 } |
| 455 |
| 456 - (NSValue*)AXSelectedTextRange { |
| 457 int textDir, start, end; |
| 458 node_->GetIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, &textDir); |
| 459 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &start); |
| 460 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &end); |
| 461 // NSRange cannot represent the direction the text was selected in, so make |
| 462 // sure the correct selection index is used when creating a new range, taking |
| 463 // into account the textfield text direction as well. |
| 464 bool isReversed = (textDir == ui::AX_TEXT_DIRECTION_RTL) || |
| 465 (textDir == ui::AX_TEXT_DIRECTION_BTT); |
| 466 int beginSelectionIndex = (end > start && !isReversed) ? start : end; |
| 467 return [NSValue |
| 468 valueWithRange:NSMakeRange(beginSelectionIndex, abs(end - start))]; |
| 469 } |
| 470 |
| 471 - (NSValue*)AXVisibleCharacterRange { |
| 472 return [NSValue |
| 473 valueWithRange:NSMakeRange(0, [[self AXNumberOfCharacters] intValue])]; |
| 474 } |
| 475 |
374 @end | 476 @end |
375 | 477 |
376 namespace ui { | 478 namespace ui { |
377 | 479 |
378 // static | 480 // static |
379 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { | 481 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { |
380 AXPlatformNodeBase* node = new AXPlatformNodeMac(); | 482 AXPlatformNodeBase* node = new AXPlatformNodeMac(); |
381 node->Init(delegate); | 483 node->Init(delegate); |
382 return node; | 484 return node; |
383 } | 485 } |
(...skipping 10 matching lines...) Expand all Loading... |
394 AXPlatformNodeBase::Destroy(); | 496 AXPlatformNodeBase::Destroy(); |
395 } | 497 } |
396 | 498 |
397 gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { | 499 gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() { |
398 if (!native_node_) | 500 if (!native_node_) |
399 native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]); | 501 native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]); |
400 return native_node_.get(); | 502 return native_node_.get(); |
401 } | 503 } |
402 | 504 |
403 void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) { | 505 void AXPlatformNodeMac::NotifyAccessibilityEvent(ui::AXEvent event_type) { |
404 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 506 NSView* target = GetDelegate()->GetTargetForNativeAccessibilityEvent(); |
| 507 |
| 508 // Add mappings between ui::AXEvent and NSAccessibility notifications using |
| 509 // the EventMap above. This switch contains exceptions to those mappings. |
| 510 switch (event_type) { |
| 511 case ui::AX_EVENT_TEXT_CHANGED: |
| 512 // If the view is a user-editable textfield, this should change the value. |
| 513 if (GetData().role == ui::AX_ROLE_TEXT_FIELD) { |
| 514 NotifyMacEvent(target, ui::AX_EVENT_VALUE_CHANGED); |
| 515 return; |
| 516 } |
| 517 break; |
| 518 default: |
| 519 break; |
| 520 } |
| 521 NotifyMacEvent(target, event_type); |
405 } | 522 } |
406 | 523 |
407 int AXPlatformNodeMac::GetIndexInParent() { | 524 int AXPlatformNodeMac::GetIndexInParent() { |
408 // TODO(dmazzoni): implement this. http://crbug.com/396137 | 525 // TODO(dmazzoni): implement this. http://crbug.com/396137 |
409 return -1; | 526 return -1; |
410 } | 527 } |
411 | 528 |
412 } // namespace ui | 529 } // namespace ui |
OLD | NEW |