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