1/* 2 * Copyright (C) 2008, Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "WebAccessibilityObjectWrapperIOS.h" 28 29#if HAVE(ACCESSIBILITY) && PLATFORM(IOS) 30 31#import "AccessibilityRenderObject.h" 32#import "AccessibilityScrollView.h" 33#import "AccessibilityTable.h" 34#import "AccessibilityTableCell.h" 35#import "Chrome.h" 36#import "ChromeClient.h" 37#import "Font.h" 38#import "Frame.h" 39#import "FrameSelection.h" 40#import "FrameView.h" 41#import "HitTestResult.h" 42#import "HTMLFrameOwnerElement.h" 43#import "HTMLInputElement.h" 44#import "HTMLNames.h" 45#import "IntRect.h" 46#import "IntSize.h" 47#import "LocalizedStrings.h" 48#import "Page.h" 49#import "Range.h" 50#import "RenderView.h" 51#import "RuntimeApplicationChecksIOS.h" 52#import "SVGNames.h" 53#import "SVGElement.h" 54#import "TextIterator.h" 55#import "WAKScrollView.h" 56#import "WAKView.h" 57#import "WAKWindow.h" 58#import "WebCoreThread.h" 59#import "VisibleUnits.h" 60 61#import <CoreText/CoreText.h> 62 63@interface NSObject (AccessibilityPrivate) 64- (void)_accessibilityUnregister; 65- (NSString *)accessibilityLabel; 66- (NSString *)accessibilityValue; 67- (BOOL)isAccessibilityElement; 68- (NSInteger)accessibilityElementCount; 69- (id)accessibilityElementAtIndex:(NSInteger)index; 70- (NSInteger)indexOfAccessibilityElement:(id)element; 71@end 72 73@interface WebAccessibilityObjectWrapper (AccessibilityPrivate) 74- (id)_accessibilityWebDocumentView; 75- (id)accessibilityContainer; 76- (void)setAccessibilityLabel:(NSString *)label; 77- (void)setAccessibilityValue:(NSString *)value; 78- (BOOL)containsUnnaturallySegmentedChildren; 79- (NSInteger)positionForTextMarker:(id)marker; 80@end 81 82@interface WAKView (iOSAccessibility) 83- (BOOL)accessibilityIsIgnored; 84@end 85 86using namespace WebCore; 87using namespace HTMLNames; 88 89typedef NS_ENUM(NSInteger, UIAccessibilityScrollDirection) { 90 UIAccessibilityScrollDirectionRight = 1, 91 UIAccessibilityScrollDirectionLeft, 92 UIAccessibilityScrollDirectionUp, 93 UIAccessibilityScrollDirectionDown, 94 UIAccessibilityScrollDirectionNext, 95 UIAccessibilityScrollDirectionPrevious 96}; 97 98// These are tokens accessibility uses to denote attributes. 99static NSString * const UIAccessibilityTokenBlockquoteLevel = @"UIAccessibilityTokenBlockquoteLevel"; 100static NSString * const UIAccessibilityTokenHeadingLevel = @"UIAccessibilityTokenHeadingLevel"; 101static NSString * const UIAccessibilityTokenFontName = @"UIAccessibilityTokenFontName"; 102static NSString * const UIAccessibilityTokenFontFamily = @"UIAccessibilityTokenFontFamily"; 103static NSString * const UIAccessibilityTokenFontSize = @"UIAccessibilityTokenFontSize"; 104static NSString * const UIAccessibilityTokenBold = @"UIAccessibilityTokenBold"; 105static NSString * const UIAccessibilityTokenItalic = @"UIAccessibilityTokenItalic"; 106static NSString * const UIAccessibilityTokenUnderline = @"UIAccessibilityTokenUnderline"; 107static NSString * const UIAccessibilityTokenLanguage = @"UIAccessibilityTokenLanguage"; 108 109static AccessibilityObjectWrapper* AccessibilityUnignoredAncestor(AccessibilityObjectWrapper *wrapper) 110{ 111 while (wrapper && ![wrapper isAccessibilityElement]) { 112 AccessibilityObject* object = [wrapper accessibilityObject]; 113 if (!object) 114 break; 115 116 if ([wrapper isAttachment] && ![[wrapper attachmentView] accessibilityIsIgnored]) 117 break; 118 119 AccessibilityObject* parentObject = object->parentObjectUnignored(); 120 if (!parentObject) 121 break; 122 123 wrapper = parentObject->wrapper(); 124 } 125 return wrapper; 126} 127 128#pragma mark Accessibility Text Marker 129 130@interface WebAccessibilityTextMarker : NSObject 131{ 132 AXObjectCache* _cache; 133 TextMarkerData _textMarkerData; 134} 135 136+ (WebAccessibilityTextMarker *)textMarkerWithVisiblePosition:(VisiblePosition&)visiblePos cache:(AXObjectCache*)cache; 137 138@end 139 140@implementation WebAccessibilityTextMarker 141 142- (id)initWithTextMarker:(TextMarkerData *)data cache:(AXObjectCache*)cache 143{ 144 if (!(self = [super init])) 145 return nil; 146 147 _cache = cache; 148 memcpy(&_textMarkerData, data, sizeof(TextMarkerData)); 149 return self; 150} 151 152- (id)initWithData:(NSData *)data cache:(AXObjectCache*)cache 153{ 154 if (!(self = [super init])) 155 return nil; 156 157 _cache = cache; 158 [data getBytes:&_textMarkerData length:sizeof(TextMarkerData)]; 159 160 return self; 161} 162 163// This is needed for external clients to be able to create a text marker without having a pointer to the cache. 164- (id)initWithData:(NSData *)data accessibilityObject:(AccessibilityObjectWrapper *)wrapper 165{ 166 WebCore::AccessibilityObject* axObject = [wrapper accessibilityObject]; 167 if (!axObject) 168 return nil; 169 170 return [self initWithData:data cache:axObject->axObjectCache()]; 171} 172 173+ (WebAccessibilityTextMarker *)textMarkerWithVisiblePosition:(VisiblePosition&)visiblePos cache:(AXObjectCache*)cache 174{ 175 TextMarkerData textMarkerData; 176 cache->textMarkerDataForVisiblePosition(textMarkerData, visiblePos); 177 178 return [[[WebAccessibilityTextMarker alloc] initWithTextMarker:&textMarkerData cache:cache] autorelease]; 179} 180 181- (NSData *)dataRepresentation 182{ 183 return [NSData dataWithBytes:&_textMarkerData length:sizeof(TextMarkerData)]; 184} 185 186- (VisiblePosition)visiblePosition 187{ 188 return _cache->visiblePositionForTextMarkerData(_textMarkerData); 189} 190 191- (NSString *)description 192{ 193 return [NSString stringWithFormat:@"[AXTextMarker %p] = node: %p offset: %d", self, _textMarkerData.node, _textMarkerData.offset]; 194} 195 196@end 197 198@implementation WebAccessibilityObjectWrapper 199 200- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject 201{ 202 self = [super initWithAccessibilityObject:axObject]; 203 if (!self) 204 return nil; 205 206 // Initialize to a sentinel value. 207 m_accessibilityTraitsFromAncestor = ULLONG_MAX; 208 m_isAccessibilityElement = -1; 209 210 return self; 211} 212 213- (void)detach 214{ 215 // rdar://8798960 Make sure the object is gone early, so that anything _accessibilityUnregister 216 // does can't call back into the render tree. 217 m_object = 0; 218 219 if ([self respondsToSelector:@selector(_accessibilityUnregister)]) 220 [self _accessibilityUnregister]; 221} 222 223- (void)dealloc 224{ 225 // We should have been detached before deallocated. 226 ASSERT(!m_object); 227 [super dealloc]; 228} 229 230- (BOOL)_prepareAccessibilityCall 231{ 232 // rdar://7980318 if we start a call, then block in WebThreadLock(), then we're dealloced on another, thread, we could 233 // crash, so we should retain ourself for the duration of usage here. 234 [[self retain] autorelease]; 235 236 WebThreadLock(); 237 238 // If we came back from our thread lock and we were detached, we will no longer have an m_object. 239 if (!m_object) 240 return NO; 241 242 m_object->updateBackingStore(); 243 if (!m_object) 244 return NO; 245 246 return YES; 247} 248 249// These are here so that we don't have to import AXRuntime. 250// The methods will be swizzled when the accessibility bundle is loaded. 251 252- (uint64_t)_axLinkTrait { return (1 << 0); } 253- (uint64_t)_axVisitedTrait { return (1 << 1); } 254- (uint64_t)_axHeaderTrait { return (1 << 2); } 255- (uint64_t)_axContainedByListTrait { return (1 << 3); } 256- (uint64_t)_axContainedByTableTrait { return (1 << 4); } 257- (uint64_t)_axContainedByLandmarkTrait { return (1 << 5); } 258- (uint64_t)_axWebContentTrait { return (1 << 6); } 259- (uint64_t)_axSecureTextFieldTrait { return (1 << 7); } 260- (uint64_t)_axTextEntryTrait { return (1 << 8); } 261- (uint64_t)_axHasTextCursorTrait { return (1 << 9); } 262- (uint64_t)_axTextOperationsAvailableTrait { return (1 << 10); } 263- (uint64_t)_axImageTrait { return (1 << 11); } 264- (uint64_t)_axTabButtonTrait { return (1 << 12); } 265- (uint64_t)_axButtonTrait { return (1 << 13); } 266- (uint64_t)_axToggleTrait { return (1 << 14); } 267- (uint64_t)_axPopupButtonTrait { return (1 << 15); } 268- (uint64_t)_axStaticTextTrait { return (1 << 16); } 269- (uint64_t)_axAdjustableTrait { return (1 << 17); } 270- (uint64_t)_axMenuItemTrait { return (1 << 18); } 271- (uint64_t)_axSelectedTrait { return (1 << 19); } 272- (uint64_t)_axNotEnabledTrait { return (1 << 20); } 273- (uint64_t)_axRadioButtonTrait { return (1 << 21); } 274 275- (BOOL)accessibilityCanFuzzyHitTest 276{ 277 if (![self _prepareAccessibilityCall]) 278 return false; 279 280 AccessibilityRole role = m_object->roleValue(); 281 // Elements that can be returned when performing fuzzy hit testing. 282 switch (role) { 283 case ButtonRole: 284 case CheckBoxRole: 285 case ComboBoxRole: 286 case DisclosureTriangleRole: 287 case HeadingRole: 288 case ImageMapLinkRole: 289 case ImageRole: 290 case LinkRole: 291 case ListBoxRole: 292 case ListBoxOptionRole: 293 case MenuButtonRole: 294 case MenuItemRole: 295 case MenuItemCheckboxRole: 296 case MenuItemRadioRole: 297 case PopUpButtonRole: 298 case RadioButtonRole: 299 case ScrollBarRole: 300 case SliderRole: 301 case StaticTextRole: 302 case TabRole: 303 case TextFieldRole: 304 case ToggleButtonRole: 305 return !m_object->accessibilityIsIgnored(); 306 default: 307 return false; 308 } 309} 310 311- (AccessibilityObjectWrapper *)accessibilityPostProcessHitTest:(CGPoint)point 312{ 313 UNUSED_PARAM(point); 314 // The UIKit accessibility wrapper will override this and perform the post process hit test. 315 return nil; 316} 317 318- (id)accessibilityHitTest:(CGPoint)point 319{ 320 if (![self _prepareAccessibilityCall]) 321 return nil; 322 323 // Try a fuzzy hit test first to find an accessible element. 324 RefPtr<AccessibilityObject> axObject; 325 { 326 AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); 327 axObject = m_object->accessibilityHitTest(IntPoint(point)); 328 } 329 330 if (!axObject) 331 return nil; 332 333 // If this is a good accessible object to return, no extra work is required. 334 if ([axObject->wrapper() accessibilityCanFuzzyHitTest]) 335 return AccessibilityUnignoredAncestor(axObject->wrapper()); 336 337 // Check to see if we can post-process this hit test to find a better candidate. 338 AccessibilityObjectWrapper* wrapper = [axObject->wrapper() accessibilityPostProcessHitTest:point]; 339 if (wrapper) 340 return AccessibilityUnignoredAncestor(wrapper); 341 342 // Fall back to default behavior. 343 return AccessibilityUnignoredAncestor(axObject->wrapper()); 344} 345 346- (void)enableAttributeCaching 347{ 348 if (AXObjectCache* cache = m_object->axObjectCache()) 349 cache->startCachingComputedObjectAttributesUntilTreeMutates(); 350} 351 352- (void)disableAttributeCaching 353{ 354 if (AXObjectCache* cache = m_object->axObjectCache()) 355 cache->stopCachingComputedObjectAttributes(); 356} 357 358- (NSInteger)accessibilityElementCount 359{ 360 if (![self _prepareAccessibilityCall]) 361 return 0; 362 363 if ([self isAttachment]) 364 return [[self attachmentView] accessibilityElementCount]; 365 366 return m_object->children().size(); 367} 368 369- (id)accessibilityElementAtIndex:(NSInteger)index 370{ 371 if (![self _prepareAccessibilityCall]) 372 return nil; 373 374 if ([self isAttachment]) 375 return [[self attachmentView] accessibilityElementAtIndex:index]; 376 377 const auto& children = m_object->children(); 378 if (static_cast<unsigned>(index) >= children.size()) 379 return nil; 380 381 AccessibilityObjectWrapper* wrapper = children[index]->wrapper(); 382 if (children[index]->isAttachment()) 383 return [wrapper attachmentView]; 384 385 return wrapper; 386} 387 388- (NSInteger)indexOfAccessibilityElement:(id)element 389{ 390 if (![self _prepareAccessibilityCall]) 391 return NSNotFound; 392 393 if ([self isAttachment]) 394 return [[self attachmentView] indexOfAccessibilityElement:element]; 395 396 const auto& children = m_object->children(); 397 unsigned count = children.size(); 398 for (unsigned k = 0; k < count; ++k) { 399 AccessibilityObjectWrapper* wrapper = children[k]->wrapper(); 400 if (wrapper == element || (children[k]->isAttachment() && [wrapper attachmentView] == element)) 401 return k; 402 } 403 404 return NSNotFound; 405} 406 407- (CGPathRef)_accessibilityPath 408{ 409 if (![self _prepareAccessibilityCall]) 410 return NULL; 411 412 if (!m_object->supportsPath()) 413 return NULL; 414 415 Path path = m_object->elementPath(); 416 if (path.isEmpty()) 417 return NULL; 418 419 return [self convertPathToScreenSpace:path]; 420} 421 422- (NSString *)accessibilityLanguage 423{ 424 if (![self _prepareAccessibilityCall]) 425 return nil; 426 427 return m_object->language(); 428} 429 430- (BOOL)_accessibilityIsLandmarkRole:(AccessibilityRole)role 431{ 432 switch (role) { 433 case LandmarkApplicationRole: 434 case LandmarkBannerRole: 435 case LandmarkComplementaryRole: 436 case LandmarkContentInfoRole: 437 case LandmarkMainRole: 438 case LandmarkNavigationRole: 439 case LandmarkSearchRole: 440 return YES; 441 default: 442 return NO; 443 } 444} 445 446- (AccessibilityObjectWrapper*)_accessibilityListAncestor 447{ 448 for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { 449 AccessibilityRole role = parent->roleValue(); 450 if (role == ListRole || role == ListBoxRole) 451 return parent->wrapper(); 452 } 453 454 return nil; 455} 456 457- (AccessibilityObjectWrapper*)_accessibilityLandmarkAncestor 458{ 459 for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { 460 if ([self _accessibilityIsLandmarkRole:parent->roleValue()]) 461 return parent->wrapper(); 462 } 463 464 return nil; 465} 466 467- (AccessibilityObjectWrapper*)_accessibilityTableAncestor 468{ 469 for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { 470 if (parent->roleValue() == TableRole) 471 return parent->wrapper(); 472 } 473 474 return nil; 475} 476 477- (uint64_t)_accessibilityTraitsFromAncestors 478{ 479 uint64_t traits = 0; 480 AccessibilityRole role = m_object->roleValue(); 481 482 // Trait information also needs to be gathered from the parents above the object. 483 // The parentObject is needed instead of the unignoredParentObject, because a table might be ignored, but information still needs to be gathered from it. 484 for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { 485 AccessibilityRole parentRole = parent->roleValue(); 486 if (parentRole == WebAreaRole) 487 break; 488 489 switch (parentRole) { 490 case LinkRole: 491 case WebCoreLinkRole: 492 traits |= [self _axLinkTrait]; 493 if (parent->isVisited()) 494 traits |= [self _axVisitedTrait]; 495 break; 496 case HeadingRole: 497 { 498 traits |= [self _axHeaderTrait]; 499 // If this object has the header trait, we should set the value 500 // to the heading level. If it was a static text element, we need to store 501 // the value as the label, because the heading level needs to the value. 502 AccessibilityObjectWrapper* wrapper = parent->wrapper(); 503 if (role == StaticTextRole) 504 [self setAccessibilityLabel:m_object->stringValue()]; 505 [self setAccessibilityValue:[wrapper accessibilityValue]]; 506 break; 507 } 508 case ListBoxRole: 509 case ListRole: 510 traits |= [self _axContainedByListTrait]; 511 break; 512 case TableRole: 513 traits |= [self _axContainedByTableTrait]; 514 break; 515 default: 516 if ([self _accessibilityIsLandmarkRole:parentRole]) 517 traits |= [self _axContainedByLandmarkTrait]; 518 break; 519 } 520 } 521 522 return traits; 523} 524 525- (uint64_t)accessibilityTraits 526{ 527 if (![self _prepareAccessibilityCall]) 528 return 0; 529 530 AccessibilityRole role = m_object->roleValue(); 531 uint64_t traits = [self _axWebContentTrait]; 532 switch (role) { 533 case LinkRole: 534 case WebCoreLinkRole: 535 traits |= [self _axLinkTrait]; 536 if (m_object->isVisited()) 537 traits |= [self _axVisitedTrait]; 538 break; 539 // TextFieldRole is intended to fall through to TextAreaRole, in order to pick up the text entry and text cursor traits. 540 case TextFieldRole: 541 if (m_object->isPasswordField()) 542 traits |= [self _axSecureTextFieldTrait]; 543 FALLTHROUGH; 544 case TextAreaRole: 545 traits |= [self _axTextEntryTrait]; 546 if (m_object->isFocused()) 547 traits |= ([self _axHasTextCursorTrait] | [self _axTextOperationsAvailableTrait]); 548 break; 549 case ImageRole: 550 traits |= [self _axImageTrait]; 551 break; 552 case TabRole: 553 traits |= [self _axTabButtonTrait]; 554 break; 555 case ButtonRole: 556 traits |= [self _axButtonTrait]; 557 if (m_object->isPressed()) 558 traits |= [self _axToggleTrait]; 559 break; 560 case PopUpButtonRole: 561 traits |= [self _axPopupButtonTrait]; 562 break; 563 case RadioButtonRole: 564 traits |= [self _axRadioButtonTrait] | [self _axToggleTrait]; 565 break; 566 case ToggleButtonRole: 567 case CheckBoxRole: 568 traits |= ([self _axButtonTrait] | [self _axToggleTrait]); 569 break; 570 case HeadingRole: 571 traits |= [self _axHeaderTrait]; 572 break; 573 case StaticTextRole: 574 traits |= [self _axStaticTextTrait]; 575 break; 576 case SliderRole: 577 traits |= [self _axAdjustableTrait]; 578 break; 579 case MenuButtonRole: 580 case MenuItemRole: 581 case MenuItemCheckboxRole: 582 case MenuItemRadioRole: 583 traits |= [self _axMenuItemTrait]; 584 break; 585 default: 586 break; 587 } 588 589 if (m_object->isSelected()) 590 traits |= [self _axSelectedTrait]; 591 592 if (!m_object->isEnabled()) 593 traits |= [self _axNotEnabledTrait]; 594 595 if (m_accessibilityTraitsFromAncestor == ULLONG_MAX) 596 m_accessibilityTraitsFromAncestor = [self _accessibilityTraitsFromAncestors]; 597 598 traits |= m_accessibilityTraitsFromAncestor; 599 600 return traits; 601} 602 603- (BOOL)isSVGGroupElement 604{ 605 // If an SVG group element has a title, it should be an accessible element on iOS. 606 Node* node = m_object->node(); 607 if (node && node->hasTagName(SVGNames::gTag) && [[self accessibilityLabel] length] > 0) 608 return YES; 609 610 return NO; 611} 612 613- (BOOL)determineIsAccessibilityElement 614{ 615 if (!m_object) 616 return false; 617 618 // Honor when something explicitly makes this an element (super will contain that logic) 619 if ([super isAccessibilityElement]) 620 return YES; 621 622 m_object->updateBackingStore(); 623 624 switch (m_object->roleValue()) { 625 case TextFieldRole: 626 case TextAreaRole: 627 case ButtonRole: 628 case ToggleButtonRole: 629 case PopUpButtonRole: 630 case CheckBoxRole: 631 case RadioButtonRole: 632 case SliderRole: 633 case MenuButtonRole: 634 case ValueIndicatorRole: 635 case ImageRole: 636 case ProgressIndicatorRole: 637 case MenuItemRole: 638 case IncrementorRole: 639 case ComboBoxRole: 640 case DisclosureTriangleRole: 641 case ImageMapRole: 642 case ListMarkerRole: 643 case ListBoxOptionRole: 644 case TabRole: 645 case DocumentMathRole: 646 case HorizontalRuleRole: 647 return true; 648 case StaticTextRole: 649 { 650 // Many text elements only contain a space. 651 if (![[[self accessibilityLabel] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]) 652 return false; 653 654 // Text elements that are just pieces of links or headers should not be exposed. 655 if ([AccessibilityUnignoredAncestor([self accessibilityContainer]) containsUnnaturallySegmentedChildren]) 656 return false; 657 return true; 658 } 659 660 // Don't expose headers as elements; instead expose their children as elements, with the header trait (unless they have no children) 661 case HeadingRole: 662 if (![self accessibilityElementCount]) 663 return true; 664 return false; 665 666 // Links can sometimes be elements (when they only contain static text or don't contain anything). 667 // They should not be elements when containing text and other types. 668 case WebCoreLinkRole: 669 case LinkRole: 670 if ([self containsUnnaturallySegmentedChildren] || ![self accessibilityElementCount]) 671 return true; 672 return false; 673 case GroupRole: 674 if ([self isSVGGroupElement]) 675 return true; 676 FALLTHROUGH; 677 // All other elements are ignored on the iphone. 678 default: 679 case UnknownRole: 680 case TabGroupRole: 681 case ScrollAreaRole: 682 case TableRole: 683 case ApplicationRole: 684 case RadioGroupRole: 685 case ListRole: 686 case ListBoxRole: 687 case ScrollBarRole: 688 case MenuBarRole: 689 case MenuRole: 690 case ColumnRole: 691 case RowRole: 692 case ToolbarRole: 693 case BusyIndicatorRole: 694 case WindowRole: 695 case DrawerRole: 696 case SystemWideRole: 697 case OutlineRole: 698 case BrowserRole: 699 case SplitGroupRole: 700 case SplitterRole: 701 case ColorWellRole: 702 case GrowAreaRole: 703 case SheetRole: 704 case HelpTagRole: 705 case MatteRole: 706 case RulerRole: 707 case RulerMarkerRole: 708 case GridRole: 709 case WebAreaRole: 710 return false; 711 } 712} 713 714- (BOOL)isAccessibilityElement 715{ 716 if (![self _prepareAccessibilityCall]) 717 return NO; 718 719 if (m_isAccessibilityElement == -1) 720 m_isAccessibilityElement = [self determineIsAccessibilityElement]; 721 722 return m_isAccessibilityElement; 723} 724 725- (BOOL)stringValueShouldBeUsedInLabel 726{ 727 if (m_object->isTextControl()) 728 return NO; 729 if (m_object->roleValue() == PopUpButtonRole) 730 return NO; 731 if (m_object->isFileUploadButton()) 732 return NO; 733 734 return YES; 735} 736 737- (BOOL)fileUploadButtonReturnsValueInTitle 738{ 739 return NO; 740} 741 742static void appendStringToResult(NSMutableString *result, NSString *string) 743{ 744 ASSERT(result); 745 if (![string length]) 746 return; 747 if ([result length]) 748 [result appendString:@", "]; 749 [result appendString:string]; 750} 751 752- (CGFloat)_accessibilityMinValue 753{ 754 return m_object->minValueForRange(); 755} 756 757- (CGFloat)_accessibilityMaxValue 758{ 759 return m_object->maxValueForRange(); 760} 761 762- (NSString *)accessibilityLabel 763{ 764 if (![self _prepareAccessibilityCall]) 765 return nil; 766 767 // check if the label was overriden 768 NSString *label = [super accessibilityLabel]; 769 if (label) 770 return label; 771 772 // iOS doesn't distinguish between a title and description field, 773 // so concatentation will yield the best result. 774 NSString *axTitle = [self accessibilityTitle]; 775 NSString *axDescription = [self accessibilityDescription]; 776 NSString *landmarkDescription = [self ariaLandmarkRoleDescription]; 777 778 NSMutableString *result = [NSMutableString string]; 779 if (m_object->roleValue() == HorizontalRuleRole) 780 appendStringToResult(result, AXHorizontalRuleDescriptionText()); 781 782 appendStringToResult(result, axTitle); 783 appendStringToResult(result, axDescription); 784 if ([self stringValueShouldBeUsedInLabel]) { 785 NSString *valueLabel = m_object->stringValue(); 786 valueLabel = [valueLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 787 appendStringToResult(result, valueLabel); 788 } 789 appendStringToResult(result, landmarkDescription); 790 791 return [result length] ? result : nil; 792} 793 794- (AccessibilityTableCell*)tableCellParent 795{ 796 // Find if this element is in a table cell. 797 AccessibilityObject* cell = 0; 798 for (cell = m_object; cell && !cell->isTableCell(); cell = cell->parentObject()) 799 { } 800 801 if (!cell) 802 return 0; 803 804 return static_cast<AccessibilityTableCell*>(cell); 805} 806 807- (AccessibilityTable*)tableParent 808{ 809 // Find if the parent table for the table cell. 810 AccessibilityObject* parentTable = 0; 811 for (parentTable = m_object; parentTable && !parentTable->isDataTable(); parentTable = parentTable->parentObject()) 812 { } 813 814 if (!parentTable) 815 return 0; 816 817 return static_cast<AccessibilityTable*>(parentTable); 818} 819 820- (id)accessibilityTitleElement 821{ 822 if (![self _prepareAccessibilityCall]) 823 return nil; 824 825 AccessibilityObject* titleElement = m_object->titleUIElement(); 826 if (titleElement) 827 return titleElement->wrapper(); 828 829 return nil; 830} 831 832// Meant to return row or column headers (or other things as the future permits). 833- (NSArray *)accessibilityHeaderElements 834{ 835 if (![self _prepareAccessibilityCall]) 836 return nil; 837 838 AccessibilityTableCell* tableCell = [self tableCellParent]; 839 if (!tableCell) 840 return nil; 841 842 AccessibilityTable* table = [self tableParent]; 843 if (!table) 844 return nil; 845 846 // Get the row and column range, so we can use them to find the headers. 847 std::pair<unsigned, unsigned> rowRange; 848 std::pair<unsigned, unsigned> columnRange; 849 tableCell->rowIndexRange(rowRange); 850 tableCell->columnIndexRange(columnRange); 851 852 AccessibilityObject::AccessibilityChildrenVector rowHeaders; 853 AccessibilityObject::AccessibilityChildrenVector columnHeaders; 854 table->rowHeaders(rowHeaders); 855 table->columnHeaders(columnHeaders); 856 857 NSMutableArray *headers = [NSMutableArray array]; 858 859 unsigned columnRangeIndex = static_cast<unsigned>(columnRange.first); 860 if (columnRangeIndex < columnHeaders.size()) { 861 RefPtr<AccessibilityObject> columnHeader = columnHeaders[columnRange.first]; 862 AccessibilityObjectWrapper* wrapper = columnHeader->wrapper(); 863 if (wrapper) 864 [headers addObject:wrapper]; 865 } 866 867 unsigned rowRangeIndex = static_cast<unsigned>(rowRange.first); 868 if (rowRangeIndex < rowHeaders.size()) { 869 RefPtr<AccessibilityObject> rowHeader = rowHeaders[rowRange.first]; 870 AccessibilityObjectWrapper* wrapper = rowHeader->wrapper(); 871 if (wrapper) 872 [headers addObject:wrapper]; 873 } 874 875 return headers; 876} 877 878- (id)accessibilityElementForRow:(NSInteger)row andColumn:(NSInteger)column 879{ 880 if (![self _prepareAccessibilityCall]) 881 return nil; 882 883 AccessibilityTable* table = [self tableParent]; 884 if (!table) 885 return nil; 886 887 AccessibilityTableCell* cell = table->cellForColumnAndRow(column, row); 888 if (!cell) 889 return nil; 890 return cell->wrapper(); 891} 892 893- (NSRange)accessibilityRowRange 894{ 895 if (![self _prepareAccessibilityCall]) 896 return NSMakeRange(NSNotFound, 0); 897 898 if (m_object->isRadioButton()) { 899 AccessibilityObject::AccessibilityChildrenVector radioButtonSiblings; 900 m_object->linkedUIElements(radioButtonSiblings); 901 if (radioButtonSiblings.size() <= 1) 902 return NSMakeRange(NSNotFound, 0); 903 904 return NSMakeRange(radioButtonSiblings.find(m_object), radioButtonSiblings.size()); 905 } 906 907 AccessibilityTableCell* tableCell = [self tableCellParent]; 908 if (!tableCell) 909 return NSMakeRange(NSNotFound, 0); 910 911 std::pair<unsigned, unsigned> rowRange; 912 tableCell->rowIndexRange(rowRange); 913 return NSMakeRange(rowRange.first, rowRange.second); 914} 915 916- (NSRange)accessibilityColumnRange 917{ 918 if (![self _prepareAccessibilityCall]) 919 return NSMakeRange(NSNotFound, 0); 920 921 AccessibilityTableCell* tableCell = [self tableCellParent]; 922 if (!tableCell) 923 return NSMakeRange(NSNotFound, 0); 924 925 std::pair<unsigned, unsigned> columnRange; 926 tableCell->columnIndexRange(columnRange); 927 return NSMakeRange(columnRange.first, columnRange.second); 928} 929 930- (NSString *)accessibilityPlaceholderValue 931{ 932 if (![self _prepareAccessibilityCall]) 933 return nil; 934 935 return m_object->placeholderValue(); 936} 937 938- (NSString *)accessibilityValue 939{ 940 if (![self _prepareAccessibilityCall]) 941 return nil; 942 943 // check if the value was overriden 944 NSString *value = [super accessibilityValue]; 945 if (value) 946 return value; 947 948 if (m_object->isCheckboxOrRadio()) { 949 switch (m_object->checkboxOrRadioValue()) { 950 case ButtonStateOff: 951 return [NSString stringWithFormat:@"%d", 0]; 952 case ButtonStateOn: 953 return [NSString stringWithFormat:@"%d", 1]; 954 case ButtonStateMixed: 955 return [NSString stringWithFormat:@"%d", 2]; 956 } 957 ASSERT_NOT_REACHED(); 958 return [NSString stringWithFormat:@"%d", 0]; 959 } 960 961 if (m_object->isButton() && m_object->isPressed()) 962 return [NSString stringWithFormat:@"%d", 1]; 963 964 // rdar://8131388 WebKit should expose the same info as UIKit for its password fields. 965 if (m_object->isPasswordField()) { 966 int passwordLength = m_object->accessibilityPasswordFieldLength(); 967 NSMutableString* string = [NSMutableString string]; 968 for (int k = 0; k < passwordLength; ++k) 969 [string appendString:@"•"]; 970 return string; 971 } 972 973 // A text control should return its text data as the axValue (per iPhone AX API). 974 if (![self stringValueShouldBeUsedInLabel]) 975 return m_object->stringValue(); 976 977 if (m_object->isProgressIndicator() || m_object->isSlider()) { 978 // Prefer a valueDescription if provided by the author (through aria-valuetext). 979 String valueDescription = m_object->valueDescription(); 980 if (!valueDescription.isEmpty()) 981 return valueDescription; 982 983 return [NSString stringWithFormat:@"%.2f", m_object->valueForRange()]; 984 } 985 986 if (m_object->isHeading()) 987 return [NSString stringWithFormat:@"%d", m_object->headingLevel()]; 988 989 return nil; 990} 991 992- (BOOL)accessibilityIsComboBox 993{ 994 if (![self _prepareAccessibilityCall]) 995 return NO; 996 997 return m_object->roleValue() == ComboBoxRole; 998} 999 1000- (NSString *)accessibilityHint 1001{ 1002 if (![self _prepareAccessibilityCall]) 1003 return nil; 1004 1005 return [self accessibilityHelpText]; 1006} 1007 1008- (NSURL *)accessibilityURL 1009{ 1010 if (![self _prepareAccessibilityCall]) 1011 return nil; 1012 1013 URL url = m_object->url(); 1014 if (url.isNull()) 1015 return nil; 1016 return (NSURL*)url; 1017} 1018 1019- (CGPoint)_accessibilityConvertPointToViewSpace:(CGPoint)point 1020{ 1021 if (![self _prepareAccessibilityCall]) 1022 return point; 1023 1024 FloatPoint floatPoint = FloatPoint(point); 1025 return [self convertPointToScreenSpace:floatPoint]; 1026} 1027 1028- (BOOL)_accessibilityScrollToVisible 1029{ 1030 if (![self _prepareAccessibilityCall]) 1031 return NO; 1032 1033 m_object->scrollToMakeVisible(); 1034 return YES; 1035} 1036 1037 1038- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction 1039{ 1040 if (![self _prepareAccessibilityCall]) 1041 return NO; 1042 1043 ScrollView* scrollView = m_object->scrollViewAncestor(); 1044 if (!scrollView) 1045 return NO; 1046 1047 IntPoint scrollPosition = scrollView->scrollPosition(); 1048 IntPoint newScrollPosition = scrollPosition; 1049 IntSize scrollSize = scrollView->contentsSize(); 1050 IntRect scrollVisibleRect = scrollView->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); 1051 switch (direction) { 1052 case UIAccessibilityScrollDirectionRight: { 1053 int scrollAmount = scrollVisibleRect.size().width(); 1054 int newX = scrollPosition.x() - scrollAmount; 1055 newScrollPosition.setX(std::max(newX, 0)); 1056 break; 1057 } 1058 case UIAccessibilityScrollDirectionLeft: { 1059 int scrollAmount = scrollVisibleRect.size().width(); 1060 int newX = scrollAmount + scrollPosition.x(); 1061 int maxX = scrollSize.width() - scrollAmount; 1062 newScrollPosition.setX(std::min(newX, maxX)); 1063 break; 1064 } 1065 case UIAccessibilityScrollDirectionUp: { 1066 int scrollAmount = scrollVisibleRect.size().height(); 1067 int newY = scrollPosition.y() - scrollAmount; 1068 newScrollPosition.setY(std::max(newY, 0)); 1069 break; 1070 } 1071 case UIAccessibilityScrollDirectionDown: { 1072 int scrollAmount = scrollVisibleRect.size().height(); 1073 int newY = scrollAmount + scrollPosition.y(); 1074 int maxY = scrollSize.height() - scrollAmount; 1075 newScrollPosition.setY(std::min(newY, maxY)); 1076 break; 1077 } 1078 default: 1079 break; 1080 } 1081 1082 if (newScrollPosition != scrollPosition) { 1083 scrollView->setScrollPosition(newScrollPosition); 1084 m_object->document()->updateLayoutIgnorePendingStylesheets(); 1085 } 1086 1087 [self postScrollStatusChangeNotification]; 1088 1089 // This means that this object handled the scroll and no other ancestor should attempt scrolling. 1090 return YES; 1091} 1092 1093- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point 1094{ 1095 if (!m_object) 1096 return CGPointZero; 1097 1098 CGPoint cgPoint = CGPointMake(point.x(), point.y()); 1099 1100 FrameView* frameView = m_object->documentFrameView(); 1101 WAKView* documentView = frameView ? frameView->documentView() : nullptr; 1102 if (documentView) { 1103 cgPoint = [documentView convertPoint:cgPoint toView:nil]; 1104 1105 // we need the web document view to give us our final screen coordinates 1106 // because that can take account of the scroller 1107 id webDocument = [self _accessibilityWebDocumentView]; 1108 if (webDocument) 1109 cgPoint = [webDocument convertPoint:cgPoint toView:nil]; 1110 } 1111 else { 1112 // Find the appropriate scroll view to use to convert the contents to the window. 1113 ScrollView* scrollView = 0; 1114 AccessibilityObject* parent = 0; 1115 for (parent = m_object->parentObject(); parent; parent = parent->parentObject()) { 1116 if (parent->isAccessibilityScrollView()) { 1117 scrollView = toAccessibilityScrollView(parent)->scrollView(); 1118 break; 1119 } 1120 } 1121 1122 IntPoint intPoint = flooredIntPoint(point); 1123 if (scrollView) 1124 intPoint = scrollView->contentsToRootView(intPoint); 1125 1126 Page* page = m_object->page(); 1127 1128 // If we have an empty chrome client (like SVG) then we should use the page 1129 // of the scroll view parent to help us get to the screen rect. 1130 if (parent && page && page->chrome().client().isEmptyChromeClient()) 1131 page = parent->page(); 1132 1133 if (page) { 1134 IntRect rect = IntRect(intPoint, IntSize(0, 0)); 1135 intPoint = page->chrome().rootViewToAccessibilityScreen(rect).location(); 1136 } 1137 1138 cgPoint = (CGPoint)intPoint; 1139 } 1140 1141 return cgPoint; 1142} 1143 1144- (CGRect)convertRectToScreenSpace:(IntRect &)rect 1145{ 1146 if (!m_object) 1147 return CGRectZero; 1148 1149 CGSize size = CGSizeMake(rect.size().width(), rect.size().height()); 1150 CGPoint point = CGPointMake(rect.x(), rect.y()); 1151 1152 CGRect frame = CGRectMake(point.x, point.y, size.width, size.height); 1153 1154 FrameView* frameView = m_object->documentFrameView(); 1155 WAKView* documentView = frameView ? frameView->documentView() : nil; 1156 if (documentView) { 1157 frame = [documentView convertRect:frame toView:nil]; 1158 1159 // we need the web document view to give us our final screen coordinates 1160 // because that can take account of the scroller 1161 id webDocument = [self _accessibilityWebDocumentView]; 1162 if (webDocument) 1163 frame = [webDocument convertRect:frame toView:nil]; 1164 1165 } else { 1166 // Find the appropriate scroll view to use to convert the contents to the window. 1167 ScrollView* scrollView = 0; 1168 AccessibilityObject* parent = 0; 1169 for (parent = m_object->parentObject(); parent; parent = parent->parentObject()) { 1170 if (parent->isAccessibilityScrollView()) { 1171 scrollView = toAccessibilityScrollView(parent)->scrollView(); 1172 break; 1173 } 1174 } 1175 1176 if (scrollView) 1177 rect = scrollView->contentsToRootView(rect); 1178 1179 Page* page = m_object->page(); 1180 1181 // If we have an empty chrome client (like SVG) then we should use the page 1182 // of the scroll view parent to help us get to the screen rect. 1183 if (parent && page && page->chrome().client().isEmptyChromeClient()) 1184 page = parent->page(); 1185 1186 if (page) 1187 rect = page->chrome().rootViewToAccessibilityScreen(rect); 1188 1189 frame = (CGRect)rect; 1190 } 1191 1192 return frame; 1193} 1194 1195// Used by UIKit accessibility bundle to help determine distance during a hit-test. 1196- (CGRect)accessibilityElementRect 1197{ 1198 if (![self _prepareAccessibilityCall]) 1199 return CGRectZero; 1200 1201 LayoutRect rect = m_object->elementRect(); 1202 return CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); 1203} 1204 1205// The "center point" is where VoiceOver will "press" an object. This may not be the actual 1206// center of the accessibilityFrame 1207- (CGPoint)accessibilityActivationPoint 1208{ 1209 if (![self _prepareAccessibilityCall]) 1210 return CGPointZero; 1211 1212 IntRect rect = pixelSnappedIntRect(m_object->boundingBoxRect()); 1213 CGRect cgRect = [self convertRectToScreenSpace:rect]; 1214 return CGPointMake(CGRectGetMidX(cgRect), CGRectGetMidY(cgRect)); 1215} 1216 1217- (CGRect)accessibilityFrame 1218{ 1219 if (![self _prepareAccessibilityCall]) 1220 return CGRectZero; 1221 1222 IntRect rect = pixelSnappedIntRect(m_object->elementRect()); 1223 return [self convertRectToScreenSpace:rect]; 1224} 1225 1226// Checks whether a link contains only static text and images (and has been divided unnaturally by <spans> and other nefarious mechanisms). 1227- (BOOL)containsUnnaturallySegmentedChildren 1228{ 1229 if (!m_object) 1230 return NO; 1231 1232 AccessibilityRole role = m_object->roleValue(); 1233 if (role != LinkRole && role != WebCoreLinkRole) 1234 return NO; 1235 1236 const auto& children = m_object->children(); 1237 unsigned childrenSize = children.size(); 1238 1239 // If there's only one child, then it doesn't have segmented children. 1240 if (childrenSize == 1) 1241 return NO; 1242 1243 for (unsigned i = 0; i < childrenSize; ++i) { 1244 AccessibilityRole role = children[i]->roleValue(); 1245 if (role != StaticTextRole && role != ImageRole && role != GroupRole) 1246 return NO; 1247 } 1248 1249 return YES; 1250} 1251 1252- (id)accessibilityContainer 1253{ 1254 if (![self _prepareAccessibilityCall]) 1255 return nil; 1256 1257 AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); 1258 1259 // As long as there's a parent wrapper, that's the correct chain to climb. 1260 AccessibilityObject* parent = m_object->parentObjectUnignored(); 1261 if (parent) 1262 return parent->wrapper(); 1263 1264 // The only object without a parent wrapper should be a scroll view. 1265 ASSERT(m_object->isAccessibilityScrollView()); 1266 1267 // Verify this is the top document. If not, we might need to go through the platform widget. 1268 FrameView* frameView = m_object->documentFrameView(); 1269 Document* document = m_object->document(); 1270 if (document && frameView && document != &document->topDocument()) 1271 return frameView->platformWidget(); 1272 1273 // The top scroll view's parent is the web document view. 1274 return [self _accessibilityWebDocumentView]; 1275} 1276 1277- (id)accessibilityFocusedUIElement 1278{ 1279 if (![self _prepareAccessibilityCall]) 1280 return nil; 1281 1282 AccessibilityObject* focusedObj = m_object->focusedUIElement(); 1283 1284 if (!focusedObj) 1285 return nil; 1286 1287 return focusedObj->wrapper(); 1288} 1289 1290- (id)_accessibilityWebDocumentView 1291{ 1292 if (![self _prepareAccessibilityCall]) 1293 return nil; 1294 1295 // This method performs the crucial task of connecting to the UIWebDocumentView. 1296 // This is needed to correctly calculate the screen position of the AX object. 1297 static Class webViewClass = nil; 1298 if (!webViewClass) 1299 webViewClass = NSClassFromString(@"WebView"); 1300 1301 if (!webViewClass) 1302 return nil; 1303 1304 FrameView* frameView = m_object->documentFrameView(); 1305 1306 if (!frameView) 1307 return nil; 1308 1309 // If this is the top level frame, the UIWebDocumentView should be returned. 1310 id parentView = frameView->documentView(); 1311 while (parentView && ![parentView isKindOfClass:webViewClass]) 1312 parentView = [parentView superview]; 1313 1314 // The parentView should have an accessibilityContainer, if the UIKit accessibility bundle was loaded. 1315 // The exception is DRT, which tests accessibility without the entire system turning accessibility on. Hence, 1316 // this check should be valid for everything except DRT. 1317 ASSERT([parentView accessibilityContainer] || applicationIsDumpRenderTree()); 1318 1319 return [parentView accessibilityContainer]; 1320} 1321 1322- (NSArray *)_accessibilityNextElementsWithCount:(UInt32)count 1323{ 1324 if (![self _prepareAccessibilityCall]) 1325 return nil; 1326 1327 return [[self _accessibilityWebDocumentView] _accessibilityNextElementsWithCount:count]; 1328} 1329 1330- (NSArray *)_accessibilityPreviousElementsWithCount:(UInt32)count 1331{ 1332 if (![self _prepareAccessibilityCall]) 1333 return nil; 1334 1335 return [[self _accessibilityWebDocumentView] _accessibilityPreviousElementsWithCount:count]; 1336} 1337 1338- (BOOL)accessibilityRequired 1339{ 1340 if (![self _prepareAccessibilityCall]) 1341 return NO; 1342 1343 return m_object->isRequired(); 1344} 1345 1346- (NSArray *)accessibilityFlowToElements 1347{ 1348 if (![self _prepareAccessibilityCall]) 1349 return nil; 1350 1351 AccessibilityObject::AccessibilityChildrenVector children; 1352 m_object->ariaFlowToElements(children); 1353 1354 unsigned length = children.size(); 1355 NSMutableArray* array = [NSMutableArray arrayWithCapacity:length]; 1356 for (unsigned i = 0; i < length; ++i) { 1357 AccessibilityObjectWrapper* wrapper = children[i]->wrapper(); 1358 ASSERT(wrapper); 1359 if (!wrapper) 1360 continue; 1361 1362 if (children[i]->isAttachment() && [wrapper attachmentView]) 1363 [array addObject:[wrapper attachmentView]]; 1364 else 1365 [array addObject:wrapper]; 1366 } 1367 return array; 1368} 1369 1370- (id)accessibilityLinkedElement 1371{ 1372 if (![self _prepareAccessibilityCall]) 1373 return nil; 1374 1375 // If this static text inside of a link, it should use its parent's linked element. 1376 AccessibilityObject* element = m_object; 1377 if (m_object->roleValue() == StaticTextRole && m_object->parentObjectUnignored()->isLink()) 1378 element = m_object->parentObjectUnignored(); 1379 1380 AccessibilityObject::AccessibilityChildrenVector children; 1381 element->linkedUIElements(children); 1382 if (children.size() == 0) 1383 return nil; 1384 1385 return children[0]->wrapper(); 1386} 1387 1388 1389- (BOOL)isAttachment 1390{ 1391 if (!m_object) 1392 return NO; 1393 1394 return m_object->isAttachment(); 1395} 1396 1397- (void)_accessibilityActivate 1398{ 1399 if (![self _prepareAccessibilityCall]) 1400 return; 1401 1402 m_object->press(); 1403} 1404 1405- (id)attachmentView 1406{ 1407 if (![self _prepareAccessibilityCall]) 1408 return nil; 1409 1410 ASSERT([self isAttachment]); 1411 Widget* widget = m_object->widgetForAttachmentView(); 1412 if (!widget) 1413 return nil; 1414 return widget->platformWidget(); 1415} 1416 1417static RenderObject* rendererForView(WAKView* view) 1418{ 1419 if (![view conformsToProtocol:@protocol(WebCoreFrameView)]) 1420 return 0; 1421 1422 WAKView<WebCoreFrameView>* frameView = (WAKView<WebCoreFrameView>*)view; 1423 Frame* frame = [frameView _web_frame]; 1424 if (!frame) 1425 return 0; 1426 1427 Node* node = frame->document()->ownerElement(); 1428 if (!node) 1429 return 0; 1430 1431 return node->renderer(); 1432} 1433 1434- (id)_accessibilityParentForSubview:(id)subview 1435{ 1436 RenderObject* renderer = rendererForView(subview); 1437 if (!renderer) 1438 return nil; 1439 1440 AccessibilityObject* obj = renderer->document().axObjectCache()->getOrCreate(renderer); 1441 if (obj) 1442 return obj->parentObjectUnignored()->wrapper(); 1443 return nil; 1444} 1445 1446- (void)postFocusChangeNotification 1447{ 1448 // The UIKit accessibility wrapper will override and post appropriate notification. 1449} 1450 1451- (void)postSelectedTextChangeNotification 1452{ 1453 // The UIKit accessibility wrapper will override and post appropriate notification. 1454} 1455 1456- (void)postLayoutChangeNotification 1457{ 1458 // The UIKit accessibility wrapper will override and post appropriate notification. 1459} 1460 1461- (void)postLiveRegionChangeNotification 1462{ 1463 // The UIKit accessibility wrapper will override and post appropriate notification. 1464} 1465 1466- (void)postLiveRegionCreatedNotification 1467{ 1468 // The UIKit accessibility wrapper will override and post appropriate notification. 1469} 1470 1471- (void)postLoadCompleteNotification 1472{ 1473 // The UIKit accessibility wrapper will override and post appropriate notification. 1474} 1475 1476- (void)postChildrenChangedNotification 1477{ 1478 // The UIKit accessibility wrapper will override and post appropriate notification. 1479} 1480 1481- (void)postInvalidStatusChangedNotification 1482{ 1483 // The UIKit accessibility wrapper will override and post appropriate notification. 1484} 1485 1486- (void)postValueChangedNotification 1487{ 1488 // The UIKit accessibility wrapper will override and post appropriate notification. 1489} 1490 1491- (void)postScrollStatusChangeNotification 1492{ 1493 // The UIKit accessibility wrapper will override and post appropriate notification. 1494} 1495 1496// These will be used by the UIKit wrapper to calculate an appropriate description of scroll status. 1497- (CGPoint)_accessibilityScrollPosition 1498{ 1499 if (![self _prepareAccessibilityCall]) 1500 return CGPointZero; 1501 1502 ScrollView* scrollView = m_object->scrollViewAncestor(); 1503 if (!scrollView) 1504 return CGPointZero; 1505 return scrollView->scrollPosition(); 1506} 1507 1508- (CGSize)_accessibilityScrollSize 1509{ 1510 if (![self _prepareAccessibilityCall]) 1511 return CGSizeZero; 1512 1513 ScrollView* scrollView = m_object->scrollViewAncestor(); 1514 if (!scrollView) 1515 return CGSizeZero; 1516 return scrollView->contentsSize(); 1517} 1518 1519- (CGRect)_accessibilityScrollVisibleRect 1520{ 1521 if (![self _prepareAccessibilityCall]) 1522 return CGRectZero; 1523 1524 ScrollView* scrollView = m_object->scrollViewAncestor(); 1525 if (!scrollView) 1526 return CGRectZero; 1527 return scrollView->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); 1528} 1529 1530- (void)accessibilityElementDidBecomeFocused 1531{ 1532 if (![self _prepareAccessibilityCall]) 1533 return; 1534 1535 // The focused VoiceOver element might be the text inside a link. 1536 // In those cases we should focus on the link itself. 1537 for (AccessibilityObject* object = m_object; object != nil; object = object->parentObject()) { 1538 if (object->roleValue() == WebAreaRole) 1539 break; 1540 1541 if (object->canSetFocusAttribute()) { 1542 object->setFocused(true); 1543 break; 1544 } 1545 } 1546} 1547 1548- (void)accessibilityModifySelection:(TextGranularity)granularity increase:(BOOL)increase 1549{ 1550 if (![self _prepareAccessibilityCall]) 1551 return; 1552 1553 FrameSelection& frameSelection = m_object->document()->frame()->selection(); 1554 VisibleSelection selection = m_object->selection(); 1555 VisiblePositionRange range = m_object->visiblePositionRange(); 1556 1557 // Before a selection with length exists, the cursor position needs to move to the right starting place. 1558 // That should be the beginning of this element (range.start). However, if the cursor is already within the 1559 // range of this element (the cursor is represented by selection), then the cursor does not need to move. 1560 if (frameSelection.isNone() && (selection.visibleStart() < range.start || selection.visibleEnd() > range.end)) 1561 frameSelection.moveTo(range.start, UserTriggered); 1562 1563 frameSelection.modify(FrameSelection::AlterationExtend, (increase) ? DirectionRight : DirectionLeft, granularity, UserTriggered); 1564} 1565 1566- (void)accessibilityIncreaseSelection:(TextGranularity)granularity 1567{ 1568 [self accessibilityModifySelection:granularity increase:YES]; 1569} 1570 1571- (void)accessibilityDecreaseSelection:(TextGranularity)granularity 1572{ 1573 [self accessibilityModifySelection:granularity increase:NO]; 1574} 1575 1576- (void)accessibilityMoveSelectionToMarker:(WebAccessibilityTextMarker *)marker 1577{ 1578 if (![self _prepareAccessibilityCall]) 1579 return; 1580 1581 VisiblePosition visiblePosition = [marker visiblePosition]; 1582 if (visiblePosition.isNull()) 1583 return; 1584 1585 FrameSelection& frameSelection = m_object->document()->frame()->selection(); 1586 frameSelection.moveTo(visiblePosition, UserTriggered); 1587} 1588 1589- (void)accessibilityIncrement 1590{ 1591 if (![self _prepareAccessibilityCall]) 1592 return; 1593 1594 m_object->increment(); 1595} 1596 1597- (void)accessibilityDecrement 1598{ 1599 if (![self _prepareAccessibilityCall]) 1600 return; 1601 1602 m_object->decrement(); 1603} 1604 1605#pragma mark Accessibility Text Marker Handlers 1606 1607- (BOOL)_addAccessibilityObject:(AccessibilityObject*)axObject toTextMarkerArray:(NSMutableArray *)array 1608{ 1609 if (!axObject) 1610 return NO; 1611 1612 AccessibilityObjectWrapper* wrapper = axObject->wrapper(); 1613 if (!wrapper) 1614 return NO; 1615 1616 // Don't add the same object twice, but since this has already been added, we should return 1617 // YES because we want to inform that it's in the array 1618 if ([array containsObject:wrapper]) 1619 return YES; 1620 1621 // Explicity set that this is now an element (in case other logic tries to override). 1622 [wrapper setValue:[NSNumber numberWithBool:YES] forKey:@"isAccessibilityElement"]; 1623 [array addObject:wrapper]; 1624 return YES; 1625} 1626 1627- (NSString *)stringForTextMarkers:(NSArray *)markers 1628{ 1629 if (![self _prepareAccessibilityCall]) 1630 return nil; 1631 1632 if ([markers count] != 2) 1633 return nil; 1634 1635 WebAccessibilityTextMarker* startMarker = [markers objectAtIndex:0]; 1636 WebAccessibilityTextMarker* endMarker = [markers objectAtIndex:1]; 1637 if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) 1638 return nil; 1639 1640 // extract the start and end VisiblePosition 1641 VisiblePosition startVisiblePosition = [startMarker visiblePosition]; 1642 if (startVisiblePosition.isNull()) 1643 return nil; 1644 1645 VisiblePosition endVisiblePosition = [endMarker visiblePosition]; 1646 if (endVisiblePosition.isNull()) 1647 return nil; 1648 1649 VisiblePositionRange visiblePosRange = VisiblePositionRange(startVisiblePosition, endVisiblePosition); 1650 return m_object->stringForVisiblePositionRange(visiblePosRange); 1651} 1652 1653static int blockquoteLevel(RenderObject* renderer) 1654{ 1655 if (!renderer) 1656 return 0; 1657 1658 int result = 0; 1659 for (Node* node = renderer->node(); node; node = node->parentNode()) { 1660 if (node->hasTagName(blockquoteTag)) 1661 result += 1; 1662 } 1663 1664 return result; 1665} 1666 1667static void AXAttributeStringSetLanguage(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) 1668{ 1669 if (!renderer) 1670 return; 1671 1672 AccessibilityObject* axObject = renderer->document().axObjectCache()->getOrCreate(renderer); 1673 NSString *language = axObject->language(); 1674 if ([language length]) 1675 [attrString addAttribute:UIAccessibilityTokenLanguage value:language range:range]; 1676 else 1677 [attrString removeAttribute:UIAccessibilityTokenLanguage range:range]; 1678} 1679 1680static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) 1681{ 1682 int quoteLevel = blockquoteLevel(renderer); 1683 1684 if (quoteLevel) 1685 [attrString addAttribute:UIAccessibilityTokenBlockquoteLevel value:[NSNumber numberWithInt:quoteLevel] range:range]; 1686 else 1687 [attrString removeAttribute:UIAccessibilityTokenBlockquoteLevel range:range]; 1688} 1689 1690static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) 1691{ 1692 if (!renderer) 1693 return; 1694 1695 AccessibilityObject* parentObject = renderer->document().axObjectCache()->getOrCreate(renderer->parent()); 1696 int parentHeadingLevel = parentObject->headingLevel(); 1697 1698 if (parentHeadingLevel) 1699 [attrString addAttribute:UIAccessibilityTokenHeadingLevel value:[NSNumber numberWithInt:parentHeadingLevel] range:range]; 1700 else 1701 [attrString removeAttribute:UIAccessibilityTokenHeadingLevel range:range]; 1702} 1703 1704static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, CTFontRef font, NSRange range) 1705{ 1706 if (!font) 1707 return; 1708 1709 RetainPtr<CFStringRef> fullName = adoptCF(CTFontCopyFullName(font)); 1710 RetainPtr<CFStringRef> familyName = adoptCF(CTFontCopyFamilyName(font)); 1711 1712 NSNumber* size = [NSNumber numberWithFloat:CTFontGetSize(font)]; 1713 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font); 1714 NSNumber* bold = [NSNumber numberWithBool:(traits & kCTFontTraitBold)]; 1715 if (fullName) 1716 [attrString addAttribute:UIAccessibilityTokenFontName value:(NSString*)fullName.get() range:range]; 1717 if (familyName) 1718 [attrString addAttribute:UIAccessibilityTokenFontFamily value:(NSString*)familyName.get() range:range]; 1719 if ([size boolValue]) 1720 [attrString addAttribute:UIAccessibilityTokenFontSize value:size range:range]; 1721 if ([bold boolValue] || (traits & kCTFontTraitBold)) 1722 [attrString addAttribute:UIAccessibilityTokenBold value:[NSNumber numberWithBool:YES] range:range]; 1723 if (traits & kCTFontTraitItalic) 1724 [attrString addAttribute:UIAccessibilityTokenItalic value:[NSNumber numberWithBool:YES] range:range]; 1725 1726} 1727 1728static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range) 1729{ 1730 if (number) 1731 [attrString addAttribute:attribute value:number range:range]; 1732 else 1733 [attrString removeAttribute:attribute range:range]; 1734} 1735 1736static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) 1737{ 1738 RenderStyle& style = renderer->style(); 1739 1740 // set basic font info 1741 AXAttributeStringSetFont(attrString, style.font().primaryFont()->getCTFont(), range); 1742 1743 int decor = style.textDecorationsInEffect(); 1744 if ((decor & (TextDecorationUnderline | TextDecorationLineThrough)) != 0) { 1745 // find colors using quirk mode approach (strict mode would use current 1746 // color for all but the root line box, which would use getTextDecorationColors) 1747 Color underline, overline, linethrough; 1748 renderer->getTextDecorationColors(decor, underline, overline, linethrough); 1749 1750 if (decor & TextDecorationUnderline) 1751 AXAttributeStringSetNumber(attrString, UIAccessibilityTokenUnderline, [NSNumber numberWithBool:YES], range); 1752 } 1753} 1754 1755static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, NSString *text) 1756{ 1757 // skip invisible text 1758 if (!node->renderer()) 1759 return; 1760 1761 // easier to calculate the range before appending the string 1762 NSRange attrStringRange = NSMakeRange([attrString length], [text length]); 1763 1764 // append the string from this node 1765 [[attrString mutableString] appendString:text]; 1766 1767 // set new attributes 1768 AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange); 1769 AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange); 1770 AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange); 1771 AXAttributeStringSetLanguage(attrString, node->renderer(), attrStringRange); 1772} 1773 1774 1775// This method is intended to return an array of strings and accessibility elements that 1776// represent the objects on one line of rendered web content. The array of markers sent 1777// in should be ordered and contain only a start and end marker. 1778- (NSArray *)arrayOfTextForTextMarkers:(NSArray *)markers attributed:(BOOL)attributed 1779{ 1780 if (![self _prepareAccessibilityCall]) 1781 return nil; 1782 1783 if ([markers count] != 2) 1784 return nil; 1785 1786 WebAccessibilityTextMarker* startMarker = [markers objectAtIndex:0]; 1787 WebAccessibilityTextMarker* endMarker = [markers objectAtIndex:1]; 1788 if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) 1789 return nil; 1790 1791 // extract the start and end VisiblePosition 1792 VisiblePosition startVisiblePosition = [startMarker visiblePosition]; 1793 if (startVisiblePosition.isNull()) 1794 return nil; 1795 1796 VisiblePosition endVisiblePosition = [endMarker visiblePosition]; 1797 if (endVisiblePosition.isNull()) 1798 return nil; 1799 1800 // iterate over the range to build the AX attributed string 1801 NSMutableArray* array = [[NSMutableArray alloc] init]; 1802 TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); 1803 for (; !it.atEnd(); it.advance()) { 1804 // locate the node and starting offset for this range 1805 int exception = 0; 1806 Node* node = it.range()->startContainer(exception); 1807 ASSERT(node == it.range()->endContainer(exception)); 1808 int offset = it.range()->startOffset(exception); 1809 1810 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) 1811 if (it.text().length() != 0) { 1812 if (!attributed) { 1813 // First check if this is represented by a link. 1814 AccessibilityObject* linkObject = AccessibilityObject::anchorElementForNode(node); 1815 if ([self _addAccessibilityObject:linkObject toTextMarkerArray:array]) 1816 continue; 1817 1818 // Next check if this region is represented by a heading. 1819 AccessibilityObject* headingObject = AccessibilityObject::headingElementForNode(node); 1820 if ([self _addAccessibilityObject:headingObject toTextMarkerArray:array]) 1821 continue; 1822 1823 String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); 1824 1825 if (!listMarkerText.isEmpty()) 1826 [array addObject:listMarkerText]; 1827 // There was not an element representation, so just return the text. 1828 [array addObject:it.text().createNSString().get()]; 1829 } 1830 else 1831 { 1832 String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); 1833 1834 if (!listMarkerText.isEmpty()) { 1835 NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init]; 1836 AXAttributedStringAppendText(attrString, node, listMarkerText); 1837 [array addObject:attrString]; 1838 [attrString release]; 1839 } 1840 1841 NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; 1842 AXAttributedStringAppendText(attrString, node, it.text().createNSStringWithoutCopying().get()); 1843 [array addObject:attrString]; 1844 [attrString release]; 1845 } 1846 } else { 1847 Node* replacedNode = node->childNode(offset); 1848 if (replacedNode) { 1849 AccessibilityObject* obj = m_object->axObjectCache()->getOrCreate(replacedNode->renderer()); 1850 if (obj && !obj->accessibilityIsIgnored()) 1851 [self _addAccessibilityObject:obj toTextMarkerArray:array]; 1852 } 1853 } 1854 } 1855 1856 return [array autorelease]; 1857} 1858 1859- (NSRange)_convertToNSRange:(Range *)range 1860{ 1861 if (!range || !range->startContainer()) 1862 return NSMakeRange(NSNotFound, 0); 1863 1864 Document* document = m_object->document(); 1865 Element* selectionRoot = document->frame()->selection().selection().rootEditableElement(); 1866 Element* scope = selectionRoot ? selectionRoot : document->documentElement(); 1867 1868 // Mouse events may cause TSM to attempt to create an NSRange for a portion of the view 1869 // that is not inside the current editable region. These checks ensure we don't produce 1870 // potentially invalid data when responding to such requests. 1871 if (range->startContainer() != scope && !range->startContainer()->isDescendantOf(scope)) 1872 return NSMakeRange(NSNotFound, 0); 1873 if (range->endContainer() != scope && !range->endContainer()->isDescendantOf(scope)) 1874 return NSMakeRange(NSNotFound, 0); 1875 1876 RefPtr<Range> testRange = Range::create(scope->document(), scope, 0, range->startContainer(), range->startOffset()); 1877 ASSERT(testRange->startContainer() == scope); 1878 int startPosition = TextIterator::rangeLength(testRange.get()); 1879 1880 ExceptionCode ec; 1881 testRange->setEnd(range->endContainer(), range->endOffset(), ec); 1882 ASSERT(testRange->startContainer() == scope); 1883 int endPosition = TextIterator::rangeLength(testRange.get()); 1884 return NSMakeRange(startPosition, endPosition - startPosition); 1885} 1886 1887- (PassRefPtr<Range>)_convertToDOMRange:(NSRange)nsrange 1888{ 1889 if (nsrange.location > INT_MAX) 1890 return 0; 1891 if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) 1892 nsrange.length = INT_MAX - nsrange.location; 1893 1894 // our critical assumption is that we are only called by input methods that 1895 // concentrate on a given area containing the selection 1896 // We have to do this because of text fields and textareas. The DOM for those is not 1897 // directly in the document DOM, so serialization is problematic. Our solution is 1898 // to use the root editable element of the selection start as the positional base. 1899 // That fits with AppKit's idea of an input context. 1900 Document* document = m_object->document(); 1901 Element* selectionRoot = document->frame()->selection().selection().rootEditableElement(); 1902 Element* scope = selectionRoot ? selectionRoot : document->documentElement(); 1903 return TextIterator::rangeFromLocationAndLength(scope, nsrange.location, nsrange.length); 1904} 1905 1906// This method is intended to take a text marker representing a VisiblePosition and convert it 1907// into a normalized location within the document. 1908- (NSInteger)positionForTextMarker:(WebAccessibilityTextMarker *)marker 1909{ 1910 if (![self _prepareAccessibilityCall]) 1911 return NSNotFound; 1912 1913 if (!marker) 1914 return NSNotFound; 1915 1916 VisibleSelection selection([marker visiblePosition]); 1917 RefPtr<Range> range = selection.toNormalizedRange(); 1918 NSRange nsRange = [self _convertToNSRange:range.get()]; 1919 return nsRange.location; 1920} 1921 1922- (NSArray *)textMarkerRange 1923{ 1924 if (![self _prepareAccessibilityCall]) 1925 return nil; 1926 1927 VisiblePositionRange range = m_object->visiblePositionRange(); 1928 VisiblePosition startPosition = range.start; 1929 VisiblePosition endPosition = range.end; 1930 WebAccessibilityTextMarker* start = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:startPosition cache:m_object->axObjectCache()]; 1931 WebAccessibilityTextMarker* end = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:endPosition cache:m_object->axObjectCache()]; 1932 1933 return [NSArray arrayWithObjects:start, end, nil]; 1934} 1935 1936// A method to get the normalized text cursor range of an element. Used in DumpRenderTree. 1937- (NSRange)elementTextRange 1938{ 1939 if (![self _prepareAccessibilityCall]) 1940 return NSMakeRange(NSNotFound, 0); 1941 1942 NSArray *markers = [self textMarkerRange]; 1943 if ([markers count] != 2) 1944 return NSMakeRange(NSNotFound, 0); 1945 1946 WebAccessibilityTextMarker *startMarker = [markers objectAtIndex:0]; 1947 WebAccessibilityTextMarker *endMarker = [markers objectAtIndex:1]; 1948 1949 NSInteger startPosition = [self positionForTextMarker:startMarker]; 1950 NSInteger endPosition = [self positionForTextMarker:endMarker]; 1951 1952 return NSMakeRange(startPosition, endPosition - startPosition); 1953} 1954 1955- (AccessibilityObjectWrapper *)accessibilityObjectForTextMarker:(WebAccessibilityTextMarker *)marker 1956{ 1957 if (![self _prepareAccessibilityCall]) 1958 return nil; 1959 1960 if (!marker) 1961 return nil; 1962 1963 VisiblePosition visiblePosition = [marker visiblePosition]; 1964 AccessibilityObject* obj = m_object->accessibilityObjectForPosition(visiblePosition); 1965 if (!obj) 1966 return nil; 1967 1968 return AccessibilityUnignoredAncestor(obj->wrapper()); 1969} 1970 1971- (NSArray *)textMarkerRangeForSelection 1972{ 1973 if (![self _prepareAccessibilityCall]) 1974 return nil; 1975 1976 VisibleSelection selection = m_object->selection(); 1977 if (selection.isNone()) 1978 return nil; 1979 VisiblePosition startPosition = selection.visibleStart(); 1980 VisiblePosition endPosition = selection.visibleEnd(); 1981 1982 WebAccessibilityTextMarker* startMarker = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:startPosition cache:m_object->axObjectCache()]; 1983 WebAccessibilityTextMarker* endMarker = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:endPosition cache:m_object->axObjectCache()]; 1984 if (!startMarker || !endMarker) 1985 return nil; 1986 1987 return [NSArray arrayWithObjects:startMarker, endMarker, nil]; 1988} 1989 1990- (WebAccessibilityTextMarker *)textMarkerForPosition:(NSInteger)position 1991{ 1992 if (![self _prepareAccessibilityCall]) 1993 return nil; 1994 1995 PassRefPtr<Range> range = [self _convertToDOMRange:NSMakeRange(position, 0)]; 1996 if (!range) 1997 return nil; 1998 1999 VisibleSelection selection = VisibleSelection(range.get(), DOWNSTREAM); 2000 2001 VisiblePosition visiblePosition = selection.visibleStart(); 2002 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:visiblePosition cache:m_object->axObjectCache()]; 2003} 2004 2005- (id)_stringForRange:(NSRange)range attributed:(BOOL)attributed 2006{ 2007 if (![self _prepareAccessibilityCall]) 2008 return nil; 2009 2010 WebAccessibilityTextMarker* startMarker = [self textMarkerForPosition:range.location]; 2011 WebAccessibilityTextMarker* endMarker = [self textMarkerForPosition:NSMaxRange(range)]; 2012 2013 // Clients don't always know the exact range, rather than force them to compute it, 2014 // allow clients to overshoot and use the max text marker range. 2015 if (!startMarker || !endMarker) { 2016 NSArray *markers = [self textMarkerRange]; 2017 if ([markers count] != 2) 2018 return nil; 2019 if (!startMarker) 2020 startMarker = [markers objectAtIndex:0]; 2021 if (!endMarker) 2022 endMarker = [markers objectAtIndex:1]; 2023 } 2024 2025 NSArray* array = [self arrayOfTextForTextMarkers:[NSArray arrayWithObjects:startMarker, endMarker, nil] attributed:attributed]; 2026 Class returnClass = attributed ? [NSMutableAttributedString class] : [NSMutableString class]; 2027 id returnValue = [[[returnClass alloc] init] autorelease]; 2028 2029 NSInteger count = [array count]; 2030 for (NSInteger k = 0; k < count; ++k) { 2031 id object = [array objectAtIndex:k]; 2032 2033 if (![object isKindOfClass:returnClass]) 2034 continue; 2035 2036 if (attributed) 2037 [(NSMutableAttributedString *)returnValue appendAttributedString:object]; 2038 else 2039 [(NSMutableString *)returnValue appendString:object]; 2040 } 2041 return returnValue; 2042} 2043 2044 2045// A convenience method for getting the text of a NSRange. Currently used only by DRT. 2046- (NSString *)stringForRange:(NSRange)range 2047{ 2048 return [self _stringForRange:range attributed:NO]; 2049} 2050 2051- (NSAttributedString *)attributedStringForRange:(NSRange)range 2052{ 2053 return [self _stringForRange:range attributed:YES]; 2054} 2055 2056- (NSRange)_accessibilitySelectedTextRange 2057{ 2058 if (![self _prepareAccessibilityCall] || !m_object->isTextControl()) 2059 return NSMakeRange(NSNotFound, 0); 2060 2061 PlainTextRange textRange = m_object->selectedTextRange(); 2062 if (textRange.isNull()) 2063 return NSMakeRange(NSNotFound, 0); 2064 return NSMakeRange(textRange.start, textRange.length); 2065} 2066 2067- (void)_accessibilitySetSelectedTextRange:(NSRange)range 2068{ 2069 if (![self _prepareAccessibilityCall] || !m_object->isTextControl()) 2070 return; 2071 2072 m_object->setSelectedTextRange(PlainTextRange(range.location, range.length)); 2073} 2074 2075// A convenience method for getting the accessibility objects of a NSRange. Currently used only by DRT. 2076- (NSArray *)elementsForRange:(NSRange)range 2077{ 2078 if (![self _prepareAccessibilityCall]) 2079 return nil; 2080 2081 WebAccessibilityTextMarker* startMarker = [self textMarkerForPosition:range.location]; 2082 WebAccessibilityTextMarker* endMarker = [self textMarkerForPosition:NSMaxRange(range)]; 2083 if (!startMarker || !endMarker) 2084 return nil; 2085 2086 NSArray* array = [self arrayOfTextForTextMarkers:[NSArray arrayWithObjects:startMarker, endMarker, nil] attributed:NO]; 2087 NSMutableArray* elements = [NSMutableArray array]; 2088 for (id element in array) { 2089 if (![element isKindOfClass:[AccessibilityObjectWrapper class]]) 2090 continue; 2091 [elements addObject:element]; 2092 } 2093 return elements; 2094} 2095 2096- (NSString *)selectionRangeString 2097{ 2098 NSArray *markers = [self textMarkerRangeForSelection]; 2099 return [self stringForTextMarkers:markers]; 2100} 2101 2102- (WebAccessibilityTextMarker *)selectedTextMarker 2103{ 2104 if (![self _prepareAccessibilityCall]) 2105 return nil; 2106 2107 VisibleSelection selection = m_object->selection(); 2108 VisiblePosition position = selection.visibleStart(); 2109 2110 // if there's no selection, start at the top of the document 2111 if (position.isNull()) 2112 position = startOfDocument(m_object->document()); 2113 2114 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:position cache:m_object->axObjectCache()]; 2115} 2116 2117// This method is intended to return the marker at the end of the line starting at 2118// the marker that is passed into the method. 2119- (WebAccessibilityTextMarker *)lineEndMarkerForMarker:(WebAccessibilityTextMarker *)marker 2120{ 2121 if (![self _prepareAccessibilityCall]) 2122 return nil; 2123 2124 if (!marker) 2125 return nil; 2126 2127 VisiblePosition start = [marker visiblePosition]; 2128 VisiblePosition lineEnd = m_object->nextLineEndPosition(start); 2129 2130 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:lineEnd cache:m_object->axObjectCache()]; 2131} 2132 2133// This method is intended to return the marker at the start of the line starting at 2134// the marker that is passed into the method. 2135- (WebAccessibilityTextMarker *)lineStartMarkerForMarker:(WebAccessibilityTextMarker *)marker 2136{ 2137 if (![self _prepareAccessibilityCall]) 2138 return nil; 2139 2140 if (!marker) 2141 return nil; 2142 2143 VisiblePosition start = [marker visiblePosition]; 2144 VisiblePosition lineStart = m_object->previousLineStartPosition(start); 2145 2146 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:lineStart cache:m_object->axObjectCache()]; 2147} 2148 2149- (WebAccessibilityTextMarker *)nextMarkerForMarker:(WebAccessibilityTextMarker *)marker 2150{ 2151 if (![self _prepareAccessibilityCall]) 2152 return nil; 2153 2154 if (!marker) 2155 return nil; 2156 2157 VisiblePosition start = [marker visiblePosition]; 2158 VisiblePosition nextMarker = m_object->nextVisiblePosition(start); 2159 2160 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:nextMarker cache:m_object->axObjectCache()]; 2161} 2162 2163// This method is intended to return the marker at the start of the line starting at 2164// the marker that is passed into the method. 2165- (WebAccessibilityTextMarker *)previousMarkerForMarker:(WebAccessibilityTextMarker *)marker 2166{ 2167 if (![self _prepareAccessibilityCall]) 2168 return nil; 2169 2170 if (!marker) 2171 return nil; 2172 2173 VisiblePosition start = [marker visiblePosition]; 2174 VisiblePosition previousMarker = m_object->previousVisiblePosition(start); 2175 2176 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:previousMarker cache:m_object->axObjectCache()]; 2177} 2178 2179// This method is intended to return the bounds of a text marker range in screen coordinates. 2180- (CGRect)frameForTextMarkers:(NSArray *)array 2181{ 2182 if (![self _prepareAccessibilityCall]) 2183 return CGRectZero; 2184 2185 if ([array count] != 2) 2186 return CGRectZero; 2187 2188 WebAccessibilityTextMarker* startMarker = [array objectAtIndex:0]; 2189 WebAccessibilityTextMarker* endMarker = [array objectAtIndex:1]; 2190 if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) 2191 return CGRectZero; 2192 2193 IntRect rect = m_object->boundsForVisiblePositionRange(VisiblePositionRange([startMarker visiblePosition], [endMarker visiblePosition])); 2194 return [self convertRectToScreenSpace:rect]; 2195} 2196 2197- (WebAccessibilityTextMarker *)textMarkerForPoint:(CGPoint)point 2198{ 2199 if (![self _prepareAccessibilityCall]) 2200 return nil; 2201 2202 VisiblePosition pos = m_object->visiblePositionForPoint(IntPoint(point)); 2203 return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:pos cache:m_object->axObjectCache()]; 2204} 2205 2206- (NSString *)accessibilityIdentifier 2207{ 2208 if (![self _prepareAccessibilityCall]) 2209 return nil; 2210 2211 return m_object->getAttribute(HTMLNames::idAttr); 2212} 2213 2214- (NSString *)accessibilitySpeechHint 2215{ 2216 if (![self _prepareAccessibilityCall]) 2217 return nil; 2218 2219 switch (m_object->speakProperty()) { 2220 default: 2221 case SpeakNormal: 2222 return @"normal"; 2223 case SpeakNone: 2224 return @"none"; 2225 case SpeakSpellOut: 2226 return @"spell-out"; 2227 case SpeakDigits: 2228 return @"digits"; 2229 case SpeakLiteralPunctuation: 2230 return @"literal-punctuation"; 2231 case SpeakNoPunctuation: 2232 return @"no-punctuation"; 2233 } 2234 2235 return nil; 2236} 2237 2238- (BOOL)accessibilityARIAIsBusy 2239{ 2240 if (![self _prepareAccessibilityCall]) 2241 return NO; 2242 2243 return m_object->ariaLiveRegionBusy(); 2244} 2245 2246- (NSString *)accessibilityARIALiveRegionStatus 2247{ 2248 if (![self _prepareAccessibilityCall]) 2249 return nil; 2250 2251 return m_object->ariaLiveRegionStatus(); 2252} 2253 2254- (NSString *)accessibilityARIARelevantStatus 2255{ 2256 if (![self _prepareAccessibilityCall]) 2257 return nil; 2258 2259 return m_object->ariaLiveRegionRelevant(); 2260} 2261 2262- (BOOL)accessibilityARIALiveRegionIsAtomic 2263{ 2264 if (![self _prepareAccessibilityCall]) 2265 return NO; 2266 2267 return m_object->ariaLiveRegionAtomic(); 2268} 2269 2270- (BOOL)accessibilitySupportsARIAExpanded 2271{ 2272 if (![self _prepareAccessibilityCall]) 2273 return NO; 2274 2275 return m_object->supportsARIAExpanded(); 2276} 2277 2278- (BOOL)accessibilityIsExpanded 2279{ 2280 if (![self _prepareAccessibilityCall]) 2281 return NO; 2282 2283 return m_object->isExpanded(); 2284} 2285 2286- (NSString *)accessibilityInvalidStatus 2287{ 2288 if (![self _prepareAccessibilityCall]) 2289 return nil; 2290 2291 return m_object->invalidStatus(); 2292} 2293 2294- (WebAccessibilityObjectWrapper *)accessibilityMathRootIndexObject 2295{ 2296 if (![self _prepareAccessibilityCall]) 2297 return nil; 2298 2299 return m_object->mathRootIndexObject() ? m_object->mathRootIndexObject()->wrapper() : 0; 2300} 2301 2302- (WebAccessibilityObjectWrapper *)accessibilityMathRadicandObject 2303{ 2304 if (![self _prepareAccessibilityCall]) 2305 return nil; 2306 2307 return m_object->mathRadicandObject() ? m_object->mathRadicandObject()->wrapper() : 0; 2308} 2309 2310- (WebAccessibilityObjectWrapper *)accessibilityMathNumeratorObject 2311{ 2312 if (![self _prepareAccessibilityCall]) 2313 return nil; 2314 2315 return m_object->mathNumeratorObject() ? m_object->mathNumeratorObject()->wrapper() : 0; 2316} 2317 2318- (WebAccessibilityObjectWrapper *)accessibilityMathDenominatorObject 2319{ 2320 if (![self _prepareAccessibilityCall]) 2321 return nil; 2322 2323 return m_object->mathDenominatorObject() ? m_object->mathDenominatorObject()->wrapper() : 0; 2324} 2325 2326- (WebAccessibilityObjectWrapper *)accessibilityMathBaseObject 2327{ 2328 if (![self _prepareAccessibilityCall]) 2329 return nil; 2330 2331 return m_object->mathBaseObject() ? m_object->mathBaseObject()->wrapper() : 0; 2332} 2333 2334- (WebAccessibilityObjectWrapper *)accessibilityMathSubscriptObject 2335{ 2336 if (![self _prepareAccessibilityCall]) 2337 return nil; 2338 2339 return m_object->mathSubscriptObject() ? m_object->mathSubscriptObject()->wrapper() : 0; 2340} 2341 2342- (WebAccessibilityObjectWrapper *)accessibilityMathSuperscriptObject 2343{ 2344 if (![self _prepareAccessibilityCall]) 2345 return nil; 2346 2347 return m_object->mathSuperscriptObject() ? m_object->mathSuperscriptObject()->wrapper() : 0; 2348} 2349 2350- (WebAccessibilityObjectWrapper *)accessibilityMathUnderObject 2351{ 2352 if (![self _prepareAccessibilityCall]) 2353 return nil; 2354 2355 return m_object->mathUnderObject() ? m_object->mathUnderObject()->wrapper() : 0; 2356} 2357 2358- (WebAccessibilityObjectWrapper *)accessibilityMathOverObject 2359{ 2360 if (![self _prepareAccessibilityCall]) 2361 return nil; 2362 2363 return m_object->mathOverObject() ? m_object->mathOverObject()->wrapper() : 0; 2364} 2365 2366- (NSString *)accessibilityPlatformMathSubscriptKey 2367{ 2368 return @"AXMSubscriptObject"; 2369} 2370 2371- (NSString *)accessibilityPlatformMathSuperscriptKey 2372{ 2373 return @"AXMSuperscriptObject"; 2374} 2375 2376- (NSArray *)accessibilityMathPostscripts 2377{ 2378 if (![self _prepareAccessibilityCall]) 2379 return nil; 2380 2381 return [self accessibilityMathPostscriptPairs]; 2382} 2383 2384- (NSArray *)accessibilityMathPrescripts 2385{ 2386 if (![self _prepareAccessibilityCall]) 2387 return nil; 2388 2389 return [self accessibilityMathPrescriptPairs]; 2390} 2391 2392- (NSString *)accessibilityMathFencedOpenString 2393{ 2394 if (![self _prepareAccessibilityCall]) 2395 return nil; 2396 2397 return m_object->mathFencedOpenString(); 2398} 2399 2400- (NSString *)accessibilityMathFencedCloseString 2401{ 2402 if (![self _prepareAccessibilityCall]) 2403 return nil; 2404 2405 return m_object->mathFencedCloseString(); 2406} 2407 2408- (BOOL)accessibilityIsMathTopObject 2409{ 2410 if (![self _prepareAccessibilityCall]) 2411 return NO; 2412 2413 return m_object->roleValue() == DocumentMathRole; 2414} 2415 2416- (NSInteger)accessibilityMathLineThickness 2417{ 2418 if (![self _prepareAccessibilityCall]) 2419 return 0; 2420 2421 return m_object->mathLineThickness(); 2422} 2423 2424- (NSString *)accessibilityMathType 2425{ 2426 if (![self _prepareAccessibilityCall]) 2427 return nil; 2428 2429 if (m_object->roleValue() == MathElementRole) { 2430 if (m_object->isMathFraction()) 2431 return @"AXMathFraction"; 2432 if (m_object->isMathFenced()) 2433 return @"AXMathFenced"; 2434 if (m_object->isMathSubscriptSuperscript()) 2435 return @"AXMathSubscriptSuperscript"; 2436 if (m_object->isMathRow()) 2437 return @"AXMathRow"; 2438 if (m_object->isMathUnderOver()) 2439 return @"AXMathUnderOver"; 2440 if (m_object->isMathSquareRoot()) 2441 return @"AXMathSquareRoot"; 2442 if (m_object->isMathRoot()) 2443 return @"AXMathRoot"; 2444 if (m_object->isMathText()) 2445 return @"AXMathText"; 2446 if (m_object->isMathNumber()) 2447 return @"AXMathNumber"; 2448 if (m_object->isMathIdentifier()) 2449 return @"AXMathIdentifier"; 2450 if (m_object->isMathTable()) 2451 return @"AXMathTable"; 2452 if (m_object->isMathTableRow()) 2453 return @"AXMathTableRow"; 2454 if (m_object->isMathTableCell()) 2455 return @"AXMathTableCell"; 2456 if (m_object->isMathFenceOperator()) 2457 return @"AXMathFenceOperator"; 2458 if (m_object->isMathSeparatorOperator()) 2459 return @"AXMathSeparatorOperator"; 2460 if (m_object->isMathOperator()) 2461 return @"AXMathOperator"; 2462 if (m_object->isMathMultiscript()) 2463 return @"AXMathMultiscript"; 2464 } 2465 2466 return nil; 2467} 2468 2469- (CGPoint)accessibilityClickPoint 2470{ 2471 return m_object->clickPoint(); 2472} 2473 2474// These are used by DRT so that it can know when notifications are sent. 2475// Since they are static, only one callback can be installed at a time (that's all DRT should need). 2476typedef void (*AXPostedNotificationCallback)(id element, NSString* notification, void* context); 2477static AXPostedNotificationCallback AXNotificationCallback = 0; 2478static void* AXPostedNotificationContext = 0; 2479 2480- (void)accessibilitySetPostedNotificationCallback:(AXPostedNotificationCallback)function withContext:(void*)context 2481{ 2482 AXNotificationCallback = function; 2483 AXPostedNotificationContext = context; 2484} 2485 2486- (void)accessibilityPostedNotification:(NSString *)notificationName 2487{ 2488 if (AXNotificationCallback && notificationName) 2489 AXNotificationCallback(self, notificationName, AXPostedNotificationContext); 2490} 2491 2492#ifndef NDEBUG 2493- (NSString *)description 2494{ 2495 CGRect frame = [self accessibilityFrame]; 2496 return [NSString stringWithFormat:@"Role: (%d) - Text: %@: Value: %@ -- Frame: %f %f %f %f", m_object ? m_object->roleValue() : 0, [self accessibilityLabel], [self accessibilityValue], frame.origin.x, frame.origin.y, frame.size.width, frame.size.height]; 2497} 2498#endif 2499 2500@end 2501 2502#endif // HAVE(ACCESSIBILITY) && PLATFORM(IOS) 2503