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

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

Issue 2934993002: MacViews a11y: Allow VoiceOver to read out views::Label word-by-word. (Closed)
Patch Set: Fix TextfieldTest.AccessiblePasswordTest 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
« no previous file with comments | « no previous file | ui/views/controls/textfield/textfield.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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"
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after
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.
234 - (NSString*)getAXValueAsString;
233 @end 235 @end
234 236
235 @implementation AXPlatformNodeCocoa { 237 @implementation AXPlatformNodeCocoa {
236 ui::AXPlatformNodeBase* node_; // Weak. Retains us. 238 ui::AXPlatformNodeBase* node_; // Weak. Retains us.
237 } 239 }
238 240
239 @synthesize node = node_; 241 @synthesize node = node_;
240 242
241 // A mapping of AX roles to native roles. 243 // A mapping of AX roles to native roles.
242 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role { 244 + (NSString*)nativeRoleFromAXRole:(ui::AXRole)role {
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
280 return gfx::ScreenRectToNSRect(node_->GetBoundsInScreen()); 282 return gfx::ScreenRectToNSRect(node_->GetBoundsInScreen());
281 } 283 }
282 284
283 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute { 285 - (NSString*)getStringAttribute:(ui::AXStringAttribute)attribute {
284 std::string attributeValue; 286 std::string attributeValue;
285 if (node_->GetStringAttribute(attribute, &attributeValue)) 287 if (node_->GetStringAttribute(attribute, &attributeValue))
286 return base::SysUTF8ToNSString(attributeValue); 288 return base::SysUTF8ToNSString(attributeValue);
287 return nil; 289 return nil;
288 } 290 }
289 291
292 - (NSString*)getAXValueAsString {
293 id value = [self AXValue];
294 return [value isKindOfClass:[NSString class]] ? value : nil;
295 }
296
290 // NSAccessibility informal protocol implementation. 297 // NSAccessibility informal protocol implementation.
291 298
292 - (BOOL)accessibilityIsIgnored { 299 - (BOOL)accessibilityIsIgnored {
293 return [[self AXRole] isEqualToString:NSAccessibilityUnknownRole] || 300 return [[self AXRole] isEqualToString:NSAccessibilityUnknownRole] ||
294 node_->GetData().HasState(ui::AX_STATE_INVISIBLE); 301 node_->GetData().HasState(ui::AX_STATE_INVISIBLE);
295 } 302 }
296 303
297 - (id)accessibilityHitTest:(NSPoint)point { 304 - (id)accessibilityHitTest:(NSPoint)point {
298 for (AXPlatformNodeCocoa* child in [self AXChildren]) { 305 for (AXPlatformNodeCocoa* child in [self AXChildren]) {
299 if (![child accessibilityIsIgnored] && 306 if (![child accessibilityIsIgnored] &&
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 NSAccessibilityEnabledAttribute, 363 NSAccessibilityEnabledAttribute,
357 NSAccessibilityFocusedAttribute, 364 NSAccessibilityFocusedAttribute,
358 NSAccessibilityHelpAttribute, 365 NSAccessibilityHelpAttribute,
359 NSAccessibilityTopLevelUIElementAttribute, 366 NSAccessibilityTopLevelUIElementAttribute,
360 NSAccessibilityWindowAttribute, 367 NSAccessibilityWindowAttribute,
361 ]; 368 ];
362 369
363 // Attributes required for user-editable controls. 370 // Attributes required for user-editable controls.
364 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ]; 371 NSArray* const kValueAttributes = @[ NSAccessibilityValueAttribute ];
365 372
366 // Attributes required for unprotected textfields. 373 // Attributes required for unprotected textfields and labels.
367 NSArray* const kUnprotectedTextfieldAttributes = @[ 374 NSArray* const kUnprotectedTextAttributes = @[
368 NSAccessibilityInsertionPointLineNumberAttribute, 375 NSAccessibilityInsertionPointLineNumberAttribute,
369 NSAccessibilityNumberOfCharactersAttribute, 376 NSAccessibilityNumberOfCharactersAttribute,
370 NSAccessibilitySelectedTextAttribute, 377 NSAccessibilitySelectedTextAttribute,
371 NSAccessibilitySelectedTextRangeAttribute, 378 NSAccessibilitySelectedTextRangeAttribute,
372 NSAccessibilityVisibleCharacterRangeAttribute, 379 NSAccessibilityVisibleCharacterRangeAttribute,
373 ]; 380 ];
374 381
375 // Required for all textfields, including protected ones. 382 // Required for all text, including protected textfields.
376 NSString* const kTextfieldAttributes = 383 NSString* const kTextAttributes = NSAccessibilityPlaceholderValueAttribute;
377 NSAccessibilityPlaceholderValueAttribute;
378 384
379 base::scoped_nsobject<NSMutableArray> axAttributes( 385 base::scoped_nsobject<NSMutableArray> axAttributes(
380 [[NSMutableArray alloc] init]); 386 [[NSMutableArray alloc] init]);
381 387
382 [axAttributes addObjectsFromArray:kAllRoleAttributes]; 388 [axAttributes addObjectsFromArray:kAllRoleAttributes];
389
383 switch (node_->GetData().role) { 390 switch (node_->GetData().role) {
384 case ui::AX_ROLE_TEXT_FIELD: 391 case ui::AX_ROLE_TEXT_FIELD:
385 [axAttributes addObject:kTextfieldAttributes]; 392 case ui::AX_ROLE_STATIC_TEXT:
393 [axAttributes addObject:kTextAttributes];
386 if (!node_->GetData().HasState(ui::AX_STATE_PROTECTED)) 394 if (!node_->GetData().HasState(ui::AX_STATE_PROTECTED))
387 [axAttributes addObjectsFromArray:kUnprotectedTextfieldAttributes]; 395 [axAttributes addObjectsFromArray:kUnprotectedTextAttributes];
388 // Fallthrough. 396 // Fallthrough.
389 case ui::AX_ROLE_CHECK_BOX: 397 case ui::AX_ROLE_CHECK_BOX:
390 case ui::AX_ROLE_COMBO_BOX: 398 case ui::AX_ROLE_COMBO_BOX:
391 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: 399 case ui::AX_ROLE_MENU_ITEM_CHECK_BOX:
392 case ui::AX_ROLE_MENU_ITEM_RADIO: 400 case ui::AX_ROLE_MENU_ITEM_RADIO:
393 case ui::AX_ROLE_RADIO_BUTTON: 401 case ui::AX_ROLE_RADIO_BUTTON:
394 case ui::AX_ROLE_SEARCH_BOX: 402 case ui::AX_ROLE_SEARCH_BOX:
395 case ui::AX_ROLE_SLIDER: 403 case ui::AX_ROLE_SLIDER:
396 case ui::AX_ROLE_SLIDER_THUMB: 404 case ui::AX_ROLE_SLIDER_THUMB:
397 case ui::AX_ROLE_TOGGLE_BUTTON: 405 case ui::AX_ROLE_TOGGLE_BUTTON:
(...skipping 14 matching lines...) Expand all
412 // writable attribute will only appear as such if the accessibility element 420 // writable attribute will only appear as such if the accessibility element
413 // has a value set for that attribute. 421 // has a value set for that attribute.
414 if ([attributeName 422 if ([attributeName
415 isEqualToString:NSAccessibilitySelectedChildrenAttribute] || 423 isEqualToString:NSAccessibilitySelectedChildrenAttribute] ||
416 [attributeName 424 [attributeName
417 isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { 425 isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
418 return NO; 426 return NO;
419 } 427 }
420 428
421 if ([attributeName isEqualToString:NSAccessibilityValueAttribute]) { 429 if ([attributeName isEqualToString:NSAccessibilityValueAttribute]) {
422 // NSSecureTextField doesn't allow values to be edited (despite showing up
423 // as editable), match its behavior.
424 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED))
425 return NO;
426 // Since tabs use the Radio Button role on Mac, the standard way to set 430 // Since tabs use the Radio Button role on Mac, the standard way to set
427 // them is via the value attribute rather than the selected attribute. 431 // them is via the value attribute rather than the selected attribute.
428 if (node_->GetData().role == ui::AX_ROLE_TAB) 432 if (node_->GetData().role == ui::AX_ROLE_TAB)
429 return !node_->GetData().HasState(ui::AX_STATE_SELECTED); 433 return !node_->GetData().HasState(ui::AX_STATE_SELECTED);
430 } 434 }
431 435
432 if ([attributeName isEqualToString:NSAccessibilityValueAttribute] || 436 if ([attributeName isEqualToString:NSAccessibilityValueAttribute] ||
433 [attributeName isEqualToString:NSAccessibilitySelectedTextAttribute] || 437 [attributeName isEqualToString:NSAccessibilitySelectedTextAttribute] ||
434 [attributeName 438 [attributeName
435 isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { 439 isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
436 return !node_->GetData().HasState(ui::AX_STATE_READ_ONLY); 440 return !node_->GetData().HasState(ui::AX_STATE_READ_ONLY);
437 } 441 }
438 442
439 if ([attributeName isEqualToString:NSAccessibilityFocusedAttribute]) { 443 if ([attributeName isEqualToString:NSAccessibilityFocusedAttribute]) {
440 return node_->GetData().HasState(ui::AX_STATE_FOCUSABLE); 444 return node_->GetData().HasState(ui::AX_STATE_FOCUSABLE);
441 } 445 }
442 446
443 // TODO(patricialor): Add callbacks for updating the above attributes except 447 // TODO(patricialor): Add callbacks for updating the above attributes except
444 // NSAccessibilityValueAttribute and return YES. 448 // NSAccessibilityValueAttribute and return YES.
445 return NO; 449 return NO;
446 } 450 }
447 451
448 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute { 452 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
449 ui::AXActionData data; 453 ui::AXActionData data;
450 454
451 // Check for attributes first. Only the |data.action| should be set here - any 455 // Check for attributes first. Only the |data.action| should be set here - any
452 // type-specific information, if needed, should be set below. 456 // type-specific information, if needed, should be set below.
453 if ([attribute isEqualToString:NSAccessibilityValueAttribute] && 457 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
454 !node_->GetData().HasState(ui::AX_STATE_PROTECTED)) {
455 data.action = node_->GetData().role == ui::AX_ROLE_TAB 458 data.action = node_->GetData().role == ui::AX_ROLE_TAB
456 ? ui::AX_ACTION_SET_SELECTION 459 ? ui::AX_ACTION_SET_SELECTION
457 : ui::AX_ACTION_SET_VALUE; 460 : ui::AX_ACTION_SET_VALUE;
458 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { 461 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
459 data.action = ui::AX_ACTION_REPLACE_SELECTED_TEXT; 462 data.action = ui::AX_ACTION_REPLACE_SELECTED_TEXT;
460 } else if ([attribute 463 } else if ([attribute
461 isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { 464 isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
462 data.action = ui::AX_ACTION_SET_SELECTION; 465 data.action = ui::AX_ACTION_SET_SELECTION;
463 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { 466 } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
464 if ([value isKindOfClass:[NSNumber class]]) { 467 if ([value isKindOfClass:[NSNumber class]]) {
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
524 break; 527 break;
525 } 528 }
526 return [AXPlatformNodeCocoa nativeSubroleFromAXRole:role]; 529 return [AXPlatformNodeCocoa nativeSubroleFromAXRole:role];
527 } 530 }
528 531
529 - (NSString*)AXHelp { 532 - (NSString*)AXHelp {
530 return [self getStringAttribute:ui::AX_ATTR_DESCRIPTION]; 533 return [self getStringAttribute:ui::AX_ATTR_DESCRIPTION];
531 } 534 }
532 535
533 - (id)AXValue { 536 - (id)AXValue {
534 if (node_->GetData().role == ui::AX_ROLE_TAB) 537 switch (node_->GetData().role) {
535 return [self AXSelected]; 538 case ui::AX_ROLE_TAB:
539 return [self AXSelected];
540 case ui::AX_ROLE_STATIC_TEXT:
541 return [self AXTitle];
542 default:
543 break;
544 }
536 return [self getStringAttribute:ui::AX_ATTR_VALUE]; 545 return [self getStringAttribute:ui::AX_ATTR_VALUE];
537 } 546 }
538 547
539 - (NSNumber*)AXEnabled { 548 - (NSNumber*)AXEnabled {
540 return @(!node_->GetData().HasState(ui::AX_STATE_DISABLED)); 549 return @(!node_->GetData().HasState(ui::AX_STATE_DISABLED));
541 } 550 }
542 551
543 - (NSNumber*)AXFocused { 552 - (NSNumber*)AXFocused {
544 if (node_->GetData().HasState(ui::AX_STATE_FOCUSABLE)) 553 if (node_->GetData().HasState(ui::AX_STATE_FOCUSABLE))
545 return 554 return
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
589 return @(node_->GetData().HasState(ui::AX_STATE_SELECTED)); 598 return @(node_->GetData().HasState(ui::AX_STATE_SELECTED));
590 } 599 }
591 600
592 - (NSString*)AXPlaceholderValue { 601 - (NSString*)AXPlaceholderValue {
593 return [self getStringAttribute:ui::AX_ATTR_PLACEHOLDER]; 602 return [self getStringAttribute:ui::AX_ATTR_PLACEHOLDER];
594 } 603 }
595 604
596 // Text-specific attributes. 605 // Text-specific attributes.
597 606
598 - (NSString*)AXSelectedText { 607 - (NSString*)AXSelectedText {
599 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED))
600 return nil;
601
602 NSRange selectedTextRange; 608 NSRange selectedTextRange;
603 [[self AXSelectedTextRange] getValue:&selectedTextRange]; 609 [[self AXSelectedTextRange] getValue:&selectedTextRange];
604 return [[self AXValue] substringWithRange:selectedTextRange]; 610 return [[self getAXValueAsString] substringWithRange:selectedTextRange];
605 } 611 }
606 612
607 - (NSValue*)AXSelectedTextRange { 613 - (NSValue*)AXSelectedTextRange {
608 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED)) 614 // Selection might not be supported. Return (NSRange){0,0} in that case.
609 return nil; 615 int start = 0, end = 0;
610
611 int textDir, start, end;
612 node_->GetIntAttribute(ui::AX_ATTR_TEXT_DIRECTION, &textDir);
613 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &start); 616 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &start);
614 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &end); 617 node_->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &end);
615 // NSRange cannot represent the direction the text was selected in, so make 618
616 // sure the correct selection index is used when creating a new range, taking 619 // NSRange cannot represent the direction the text was selected in.
617 // into account the textfield text direction as well. 620 return [NSValue valueWithRange:{std::min(start, end), abs(end - start)}];
618 bool isReversed = (textDir == ui::AX_TEXT_DIRECTION_RTL) ||
619 (textDir == ui::AX_TEXT_DIRECTION_BTT);
620 int beginSelectionIndex = (end > start && !isReversed) ? start : end;
621 return [NSValue valueWithRange:{beginSelectionIndex, abs(end - start)}];
622 } 621 }
623 622
624 - (NSNumber*)AXNumberOfCharacters { 623 - (NSNumber*)AXNumberOfCharacters {
625 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED)) 624 return @([[self getAXValueAsString] length]);
626 return nil;
627 return @([[self AXValue] length]);
628 } 625 }
629 626
630 - (NSValue*)AXVisibleCharacterRange { 627 - (NSValue*)AXVisibleCharacterRange {
631 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED)) 628 return [NSValue valueWithRange:{0, [[self getAXValueAsString] length]}];
632 return nil;
633 return [NSValue valueWithRange:{0, [[self AXNumberOfCharacters] intValue]}];
634 } 629 }
635 630
636 - (NSNumber*)AXInsertionPointLineNumber { 631 - (NSNumber*)AXInsertionPointLineNumber {
637 if (node_->GetData().HasState(ui::AX_STATE_PROTECTED))
638 return nil;
639 // Multiline is not supported on views. 632 // Multiline is not supported on views.
640 return @0; 633 return @0;
641 } 634 }
642 635
643 @end 636 @end
644 637
645 namespace ui { 638 namespace ui {
646 639
647 // static 640 // static
648 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { 641 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) {
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
694 } 687 }
695 NotifyMacEvent(native_node_, event_type); 688 NotifyMacEvent(native_node_, event_type);
696 } 689 }
697 690
698 int AXPlatformNodeMac::GetIndexInParent() { 691 int AXPlatformNodeMac::GetIndexInParent() {
699 // TODO(dmazzoni): implement this. http://crbug.com/396137 692 // TODO(dmazzoni): implement this. http://crbug.com/396137
700 return -1; 693 return -1;
701 } 694 }
702 695
703 } // namespace ui 696 } // namespace ui
OLDNEW
« no previous file with comments | « no previous file | ui/views/controls/textfield/textfield.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698