1 /* 2 * Copyright (C) 2008, 2009, 2011 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 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "core/accessibility/AXObject.h" 31 32 #include "core/accessibility/AXObjectCache.h" 33 #include "core/dom/NodeTraversal.h" 34 #include "core/editing/VisibleUnits.h" 35 #include "core/editing/htmlediting.h" 36 #include "core/frame/LocalFrame.h" 37 #include "core/rendering/RenderListItem.h" 38 #include "core/rendering/RenderTheme.h" 39 #include "core/rendering/RenderView.h" 40 #include "platform/UserGestureIndicator.h" 41 #include "platform/text/PlatformLocale.h" 42 #include "wtf/StdLibExtras.h" 43 #include "wtf/text/WTFString.h" 44 45 using blink::WebLocalizedString; 46 47 namespace WebCore { 48 49 using namespace HTMLNames; 50 51 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; 52 53 struct RoleEntry { 54 String ariaRole; 55 AccessibilityRole webcoreRole; 56 }; 57 58 static ARIARoleMap* createARIARoleMap() 59 { 60 const RoleEntry roles[] = { 61 { "alert", AlertRole }, 62 { "alertdialog", AlertDialogRole }, 63 { "application", ApplicationRole }, 64 { "article", ArticleRole }, 65 { "banner", BannerRole }, 66 { "button", ButtonRole }, 67 { "checkbox", CheckBoxRole }, 68 { "complementary", ComplementaryRole }, 69 { "contentinfo", ContentInfoRole }, 70 { "dialog", DialogRole }, 71 { "directory", DirectoryRole }, 72 { "grid", GridRole }, 73 { "gridcell", CellRole }, 74 { "columnheader", ColumnHeaderRole }, 75 { "combobox", ComboBoxRole }, 76 { "definition", DefinitionRole }, 77 { "document", DocumentRole }, 78 { "rowheader", RowHeaderRole }, 79 { "group", GroupRole }, 80 { "heading", HeadingRole }, 81 { "img", ImageRole }, 82 { "link", LinkRole }, 83 { "list", ListRole }, 84 { "listitem", ListItemRole }, 85 { "listbox", ListBoxRole }, 86 { "log", LogRole }, 87 // "option" isn't here because it may map to different roles depending on the parent element's role 88 { "main", MainRole }, 89 { "marquee", MarqueeRole }, 90 { "math", MathRole }, 91 { "menu", MenuRole }, 92 { "menubar", MenuBarRole }, 93 { "menuitem", MenuItemRole }, 94 { "menuitemcheckbox", MenuItemRole }, 95 { "menuitemradio", MenuItemRole }, 96 { "note", NoteRole }, 97 { "navigation", NavigationRole }, 98 { "option", ListBoxOptionRole }, 99 { "presentation", PresentationalRole }, 100 { "progressbar", ProgressIndicatorRole }, 101 { "radio", RadioButtonRole }, 102 { "radiogroup", RadioGroupRole }, 103 { "region", RegionRole }, 104 { "row", RowRole }, 105 { "scrollbar", ScrollBarRole }, 106 { "search", SearchRole }, 107 { "separator", SplitterRole }, 108 { "slider", SliderRole }, 109 { "spinbutton", SpinButtonRole }, 110 { "status", StatusRole }, 111 { "tab", TabRole }, 112 { "tablist", TabListRole }, 113 { "tabpanel", TabPanelRole }, 114 { "text", StaticTextRole }, 115 { "textbox", TextAreaRole }, 116 { "timer", TimerRole }, 117 { "toolbar", ToolbarRole }, 118 { "tooltip", UserInterfaceTooltipRole }, 119 { "tree", TreeRole }, 120 { "treegrid", TreeGridRole }, 121 { "treeitem", TreeItemRole } 122 }; 123 ARIARoleMap* roleMap = new ARIARoleMap; 124 125 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i) 126 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); 127 return roleMap; 128 } 129 130 AXObject::AXObject() 131 : m_id(0) 132 , m_haveChildren(false) 133 , m_role(UnknownRole) 134 , m_lastKnownIsIgnoredValue(DefaultBehavior) 135 , m_detached(false) 136 { 137 } 138 139 AXObject::~AXObject() 140 { 141 ASSERT(isDetached()); 142 } 143 144 void AXObject::detach() 145 { 146 // Clear any children and call detachFromParent on them so that 147 // no children are left with dangling pointers to their parent. 148 clearChildren(); 149 150 m_detached = true; 151 } 152 153 bool AXObject::isDetached() const 154 { 155 return m_detached; 156 } 157 158 AXObjectCache* AXObject::axObjectCache() const 159 { 160 Document* doc = document(); 161 if (doc) 162 return doc->axObjectCache(); 163 return 0; 164 } 165 166 bool AXObject::isARIATextControl() const 167 { 168 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole; 169 } 170 171 bool AXObject::isButton() const 172 { 173 AccessibilityRole role = roleValue(); 174 175 return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole; 176 } 177 178 bool AXObject::isLandmarkRelated() const 179 { 180 switch (roleValue()) { 181 case ApplicationRole: 182 case ArticleRole: 183 case BannerRole: 184 case ComplementaryRole: 185 case ContentInfoRole: 186 case FooterRole: 187 case MainRole: 188 case NavigationRole: 189 case RegionRole: 190 case SearchRole: 191 return true; 192 default: 193 return false; 194 } 195 } 196 197 bool AXObject::isMenuRelated() const 198 { 199 switch (roleValue()) { 200 case MenuRole: 201 case MenuBarRole: 202 case MenuButtonRole: 203 case MenuItemRole: 204 return true; 205 default: 206 return false; 207 } 208 } 209 210 bool AXObject::isTextControl() const 211 { 212 switch (roleValue()) { 213 case TextAreaRole: 214 case TextFieldRole: 215 case ComboBoxRole: 216 return true; 217 default: 218 return false; 219 } 220 } 221 222 bool AXObject::isClickable() const 223 { 224 switch (roleValue()) { 225 case ButtonRole: 226 case CheckBoxRole: 227 case ColorWellRole: 228 case ComboBoxRole: 229 case EditableTextRole: 230 case ImageMapLinkRole: 231 case LinkRole: 232 case ListBoxOptionRole: 233 case MenuButtonRole: 234 case PopUpButtonRole: 235 case RadioButtonRole: 236 case TabRole: 237 case TextAreaRole: 238 case TextFieldRole: 239 case ToggleButtonRole: 240 return true; 241 default: 242 return false; 243 } 244 } 245 246 bool AXObject::isExpanded() const 247 { 248 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) 249 return true; 250 251 return false; 252 } 253 254 bool AXObject::accessibilityIsIgnored() const 255 { 256 AXObjectCache* cache = axObjectCache(); 257 if (!cache) 258 return true; 259 260 AXComputedObjectAttributeCache* attributeCache = cache->computedObjectAttributeCache(); 261 if (attributeCache) { 262 AXObjectInclusion ignored = attributeCache->getIgnored(axObjectID()); 263 switch (ignored) { 264 case IgnoreObject: 265 return true; 266 case IncludeObject: 267 return false; 268 case DefaultBehavior: 269 break; 270 } 271 } 272 273 bool result = computeAccessibilityIsIgnored(); 274 275 if (attributeCache) 276 attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject); 277 278 return result; 279 } 280 281 bool AXObject::accessibilityIsIgnoredByDefault() const 282 { 283 return defaultObjectInclusion() == IgnoreObject; 284 } 285 286 AXObjectInclusion AXObject::accessibilityPlatformIncludesObject() const 287 { 288 if (isMenuListPopup() || isMenuListOption()) 289 return IncludeObject; 290 291 return DefaultBehavior; 292 } 293 294 AXObjectInclusion AXObject::defaultObjectInclusion() const 295 { 296 if (isInertOrAriaHidden()) 297 return IgnoreObject; 298 299 if (isPresentationalChildOfAriaRole()) 300 return IgnoreObject; 301 302 return accessibilityPlatformIncludesObject(); 303 } 304 305 bool AXObject::isInertOrAriaHidden() const 306 { 307 bool mightBeInInertSubtree = true; 308 for (const AXObject* object = this; object; object = object->parentObject()) { 309 if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true")) 310 return true; 311 if (mightBeInInertSubtree && object->node()) { 312 if (object->node()->isInert()) 313 return true; 314 mightBeInInertSubtree = false; 315 } 316 } 317 318 return false; 319 } 320 321 bool AXObject::lastKnownIsIgnoredValue() 322 { 323 if (m_lastKnownIsIgnoredValue == DefaultBehavior) 324 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject; 325 326 return m_lastKnownIsIgnoredValue == IgnoreObject; 327 } 328 329 void AXObject::setLastKnownIsIgnoredValue(bool isIgnored) 330 { 331 m_lastKnownIsIgnoredValue = isIgnored ? IgnoreObject : IncludeObject; 332 } 333 334 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; 335 AccessibilityOrientation AXObject::orientation() const 336 { 337 LayoutRect bounds = elementRect(); 338 if (bounds.size().width() > bounds.size().height()) 339 return AccessibilityOrientationHorizontal; 340 if (bounds.size().height() > bounds.size().width()) 341 return AccessibilityOrientationVertical; 342 343 // A tie goes to horizontal. 344 return AccessibilityOrientationHorizontal; 345 } 346 347 static String queryString(WebLocalizedString::Name name) 348 { 349 return Locale::defaultLocale().queryString(name); 350 } 351 352 String AXObject::actionVerb() const 353 { 354 // FIXME: Need to add verbs for select elements. 355 356 switch (roleValue()) { 357 case ButtonRole: 358 case ToggleButtonRole: 359 return queryString(WebLocalizedString::AXButtonActionVerb); 360 case TextFieldRole: 361 case TextAreaRole: 362 return queryString(WebLocalizedString::AXTextFieldActionVerb); 363 case RadioButtonRole: 364 return queryString(WebLocalizedString::AXRadioButtonActionVerb); 365 case CheckBoxRole: 366 return queryString(isChecked() ? WebLocalizedString::AXCheckedCheckBoxActionVerb : WebLocalizedString::AXUncheckedCheckBoxActionVerb); 367 case LinkRole: 368 return queryString(WebLocalizedString::AXLinkActionVerb); 369 case PopUpButtonRole: 370 // FIXME: Implement. 371 return String(); 372 case MenuListPopupRole: 373 // FIXME: Implement. 374 return String(); 375 default: 376 return emptyString(); 377 } 378 } 379 380 AccessibilityButtonState AXObject::checkboxOrRadioValue() const 381 { 382 // If this is a real checkbox or radio button, AXRenderObject will handle. 383 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. 384 385 const AtomicString& result = getAttribute(aria_checkedAttr); 386 if (equalIgnoringCase(result, "true")) 387 return ButtonStateOn; 388 if (equalIgnoringCase(result, "mixed")) 389 return ButtonStateMixed; 390 391 return ButtonStateOff; 392 } 393 394 bool AXObject::ariaIsMultiline() const 395 { 396 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); 397 } 398 399 bool AXObject::ariaPressedIsPresent() const 400 { 401 return !getAttribute(aria_pressedAttr).isEmpty(); 402 } 403 404 bool AXObject::supportsARIAAttributes() const 405 { 406 return supportsARIALiveRegion() 407 || supportsARIADragging() 408 || supportsARIADropping() 409 || supportsARIAFlowTo() 410 || supportsARIAOwns() 411 || hasAttribute(aria_labelAttr); 412 } 413 414 bool AXObject::supportsRangeValue() const 415 { 416 return isProgressIndicator() 417 || isSlider() 418 || isScrollbar() 419 || isSpinButton(); 420 } 421 422 void AXObject::ariaTreeRows(AccessibilityChildrenVector& result) 423 { 424 AccessibilityChildrenVector axChildren = children(); 425 unsigned count = axChildren.size(); 426 for (unsigned k = 0; k < count; ++k) { 427 AXObject* obj = axChildren[k].get(); 428 429 // Add tree items as the rows. 430 if (obj->roleValue() == TreeItemRole) 431 result.append(obj); 432 433 // Now see if this item also has rows hiding inside of it. 434 obj->ariaTreeRows(result); 435 } 436 } 437 438 bool AXObject::supportsARIALiveRegion() const 439 { 440 const AtomicString& liveRegion = ariaLiveRegionStatus(); 441 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); 442 } 443 444 void AXObject::markCachedElementRectDirty() const 445 { 446 for (unsigned i = 0; i < m_children.size(); ++i) 447 m_children[i].get()->markCachedElementRectDirty(); 448 } 449 450 IntPoint AXObject::clickPoint() 451 { 452 LayoutRect rect = elementRect(); 453 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2)); 454 } 455 456 IntRect AXObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads) 457 { 458 ASSERT(obj); 459 if (!obj) 460 return IntRect(); 461 462 size_t count = quads.size(); 463 if (!count) 464 return IntRect(); 465 466 IntRect result; 467 for (size_t i = 0; i < count; ++i) { 468 IntRect r = quads[i].enclosingBoundingBox(); 469 if (!r.isEmpty()) { 470 if (obj->style()->hasAppearance()) 471 RenderTheme::theme().adjustRepaintRect(obj, r); 472 result.unite(r); 473 } 474 } 475 return result; 476 } 477 478 AXObject* AXObject::elementAccessibilityHitTest(const IntPoint& point) const 479 { 480 // Send the hit test back into the sub-frame if necessary. 481 if (isAttachment()) { 482 Widget* widget = widgetForAttachmentView(); 483 // Normalize the point for the widget's bounds. 484 if (widget && widget->isFrameView()) 485 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); 486 } 487 488 // Check if there are any mock elements that need to be handled. 489 size_t count = m_children.size(); 490 for (size_t k = 0; k < count; k++) { 491 if (m_children[k]->isMockObject() && m_children[k]->elementRect().contains(point)) 492 return m_children[k]->elementAccessibilityHitTest(point); 493 } 494 495 return const_cast<AXObject*>(this); 496 } 497 498 const AXObject::AccessibilityChildrenVector& AXObject::children() 499 { 500 updateChildrenIfNecessary(); 501 502 return m_children; 503 } 504 505 AXObject* AXObject::parentObjectUnignored() const 506 { 507 AXObject* parent; 508 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { 509 } 510 511 return parent; 512 } 513 514 AXObject* AXObject::firstAccessibleObjectFromNode(const Node* node) 515 { 516 if (!node) 517 return 0; 518 519 AXObjectCache* cache = node->document().axObjectCache(); 520 AXObject* accessibleObject = cache->getOrCreate(node->renderer()); 521 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { 522 node = NodeTraversal::next(*node); 523 524 while (node && !node->renderer()) 525 node = NodeTraversal::nextSkippingChildren(*node); 526 527 if (!node) 528 return 0; 529 530 accessibleObject = cache->getOrCreate(node->renderer()); 531 } 532 533 return accessibleObject; 534 } 535 536 void AXObject::updateChildrenIfNecessary() 537 { 538 if (!hasChildren()) 539 addChildren(); 540 } 541 542 void AXObject::clearChildren() 543 { 544 // Some objects have weak pointers to their parents and those associations need to be detached. 545 size_t length = m_children.size(); 546 for (size_t i = 0; i < length; i++) 547 m_children[i]->detachFromParent(); 548 549 m_children.clear(); 550 m_haveChildren = false; 551 } 552 553 AXObject* AXObject::focusedUIElement() const 554 { 555 Document* doc = document(); 556 if (!doc) 557 return 0; 558 559 Page* page = doc->page(); 560 if (!page) 561 return 0; 562 563 return AXObjectCache::focusedUIElementForPage(page); 564 } 565 566 Document* AXObject::document() const 567 { 568 FrameView* frameView = documentFrameView(); 569 if (!frameView) 570 return 0; 571 572 return frameView->frame().document(); 573 } 574 575 FrameView* AXObject::documentFrameView() const 576 { 577 const AXObject* object = this; 578 while (object && !object->isAXRenderObject()) 579 object = object->parentObject(); 580 581 if (!object) 582 return 0; 583 584 return object->documentFrameView(); 585 } 586 587 String AXObject::language() const 588 { 589 const AtomicString& lang = getAttribute(langAttr); 590 if (!lang.isEmpty()) 591 return lang; 592 593 AXObject* parent = parentObject(); 594 595 // as a last resort, fall back to the content language specified in the meta tag 596 if (!parent) { 597 Document* doc = document(); 598 if (doc) 599 return doc->contentLanguage(); 600 return nullAtom; 601 } 602 603 return parent->language(); 604 } 605 606 bool AXObject::hasAttribute(const QualifiedName& attribute) const 607 { 608 Node* elementNode = node(); 609 if (!elementNode) 610 return false; 611 612 if (!elementNode->isElementNode()) 613 return false; 614 615 Element* element = toElement(elementNode); 616 return element->fastHasAttribute(attribute); 617 } 618 619 const AtomicString& AXObject::getAttribute(const QualifiedName& attribute) const 620 { 621 Node* elementNode = node(); 622 if (!elementNode) 623 return nullAtom; 624 625 if (!elementNode->isElementNode()) 626 return nullAtom; 627 628 Element* element = toElement(elementNode); 629 return element->fastGetAttribute(attribute); 630 } 631 632 bool AXObject::press() const 633 { 634 Element* actionElem = actionElement(); 635 if (!actionElem) 636 return false; 637 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); 638 actionElem->accessKeyAction(true); 639 return true; 640 } 641 642 void AXObject::scrollToMakeVisible() const 643 { 644 IntRect objectRect = pixelSnappedIntRect(elementRect()); 645 objectRect.setLocation(IntPoint()); 646 scrollToMakeVisibleWithSubFocus(objectRect); 647 } 648 649 // This is a 1-dimensional scroll offset helper function that's applied 650 // separately in the horizontal and vertical directions, because the 651 // logic is the same. The goal is to compute the best scroll offset 652 // in order to make an object visible within a viewport. 653 // 654 // In case the whole object cannot fit, you can specify a 655 // subfocus - a smaller region within the object that should 656 // be prioritized. If the whole object can fit, the subfocus is 657 // ignored. 658 // 659 // Example: the viewport is scrolled to the right just enough 660 // that the object is in view. 661 // Before: 662 // +----------Viewport---------+ 663 // +---Object---+ 664 // +--SubFocus--+ 665 // 666 // After: 667 // +----------Viewport---------+ 668 // +---Object---+ 669 // +--SubFocus--+ 670 // 671 // When constraints cannot be fully satisfied, the min 672 // (left/top) position takes precedence over the max (right/bottom). 673 // 674 // Note that the return value represents the ideal new scroll offset. 675 // This may be out of range - the calling function should clip this 676 // to the available range. 677 static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax) 678 { 679 int viewportSize = viewportMax - viewportMin; 680 681 // If the focus size is larger than the viewport size, shrink it in the 682 // direction of subfocus. 683 if (objectMax - objectMin > viewportSize) { 684 // Subfocus must be within focus: 685 subfocusMin = std::max(subfocusMin, objectMin); 686 subfocusMax = std::min(subfocusMax, objectMax); 687 688 // Subfocus must be no larger than the viewport size; favor top/left. 689 if (subfocusMax - subfocusMin > viewportSize) 690 subfocusMax = subfocusMin + viewportSize; 691 692 if (subfocusMin + viewportSize > objectMax) { 693 objectMin = objectMax - viewportSize; 694 } else { 695 objectMin = subfocusMin; 696 objectMax = subfocusMin + viewportSize; 697 } 698 } 699 700 // Exit now if the focus is already within the viewport. 701 if (objectMin - currentScrollOffset >= viewportMin 702 && objectMax - currentScrollOffset <= viewportMax) 703 return currentScrollOffset; 704 705 // Scroll left if we're too far to the right. 706 if (objectMax - currentScrollOffset > viewportMax) 707 return objectMax - viewportMax; 708 709 // Scroll right if we're too far to the left. 710 if (objectMin - currentScrollOffset < viewportMin) 711 return objectMin - viewportMin; 712 713 ASSERT_NOT_REACHED(); 714 715 // This shouldn't happen. 716 return currentScrollOffset; 717 } 718 719 void AXObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const 720 { 721 // Search up the parent chain until we find the first one that's scrollable. 722 AXObject* scrollParent = parentObject(); 723 ScrollableArea* scrollableArea; 724 for (scrollableArea = 0; 725 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); 726 scrollParent = scrollParent->parentObject()) { } 727 if (!scrollableArea) 728 return; 729 730 IntRect objectRect = pixelSnappedIntRect(elementRect()); 731 IntPoint scrollPosition = scrollableArea->scrollPosition(); 732 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(); 733 734 int desiredX = computeBestScrollOffset( 735 scrollPosition.x(), 736 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), 737 objectRect.x(), objectRect.maxX(), 738 0, scrollVisibleRect.width()); 739 int desiredY = computeBestScrollOffset( 740 scrollPosition.y(), 741 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(), 742 objectRect.y(), objectRect.maxY(), 743 0, scrollVisibleRect.height()); 744 745 scrollParent->scrollTo(IntPoint(desiredX, desiredY)); 746 747 // Recursively make sure the scroll parent itself is visible. 748 if (scrollParent->parentObject()) 749 scrollParent->scrollToMakeVisible(); 750 } 751 752 void AXObject::scrollToGlobalPoint(const IntPoint& globalPoint) const 753 { 754 // Search up the parent chain and create a vector of all scrollable parent objects 755 // and ending with this object itself. 756 Vector<const AXObject*> objects; 757 AXObject* parentObject; 758 for (parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) { 759 if (parentObject->getScrollableAreaIfScrollable()) 760 objects.prepend(parentObject); 761 } 762 objects.append(this); 763 764 // Start with the outermost scrollable (the main window) and try to scroll the 765 // next innermost object to the given point. 766 int offsetX = 0, offsetY = 0; 767 IntPoint point = globalPoint; 768 size_t levels = objects.size() - 1; 769 for (size_t i = 0; i < levels; i++) { 770 const AXObject* outer = objects[i]; 771 const AXObject* inner = objects[i + 1]; 772 773 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable(); 774 775 IntRect innerRect = inner->isAXScrollView() ? pixelSnappedIntRect(inner->parentObject()->elementRect()) : pixelSnappedIntRect(inner->elementRect()); 776 IntRect objectRect = innerRect; 777 IntPoint scrollPosition = scrollableArea->scrollPosition(); 778 779 // Convert the object rect into local coordinates. 780 objectRect.move(offsetX, offsetY); 781 if (!outer->isAXScrollView()) 782 objectRect.move(scrollPosition.x(), scrollPosition.y()); 783 784 int desiredX = computeBestScrollOffset( 785 0, 786 objectRect.x(), objectRect.maxX(), 787 objectRect.x(), objectRect.maxX(), 788 point.x(), point.x()); 789 int desiredY = computeBestScrollOffset( 790 0, 791 objectRect.y(), objectRect.maxY(), 792 objectRect.y(), objectRect.maxY(), 793 point.y(), point.y()); 794 outer->scrollTo(IntPoint(desiredX, desiredY)); 795 796 if (outer->isAXScrollView() && !inner->isAXScrollView()) { 797 // If outer object we just scrolled is a scroll view (main window or iframe) but the 798 // inner object is not, keep track of the coordinate transformation to apply to 799 // future nested calculations. 800 scrollPosition = scrollableArea->scrollPosition(); 801 offsetX -= (scrollPosition.x() + point.x()); 802 offsetY -= (scrollPosition.y() + point.y()); 803 point.move(scrollPosition.x() - innerRect.x(), scrollPosition.y() - innerRect.y()); 804 } else if (inner->isAXScrollView()) { 805 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation. 806 offsetX = 0; 807 offsetY = 0; 808 } 809 } 810 } 811 812 void AXObject::notifyIfIgnoredValueChanged() 813 { 814 bool isIgnored = accessibilityIsIgnored(); 815 if (lastKnownIsIgnoredValue() != isIgnored) { 816 axObjectCache()->childrenChanged(parentObject()); 817 setLastKnownIsIgnoredValue(isIgnored); 818 } 819 } 820 821 void AXObject::selectionChanged() 822 { 823 if (AXObject* parent = parentObjectIfExists()) 824 parent->selectionChanged(); 825 } 826 827 int AXObject::lineForPosition(const VisiblePosition& visiblePos) const 828 { 829 if (visiblePos.isNull() || !node()) 830 return -1; 831 832 // If the position is not in the same editable region as this AX object, return -1. 833 Node* containerNode = visiblePos.deepEquivalent().containerNode(); 834 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode)) 835 return -1; 836 837 int lineCount = -1; 838 VisiblePosition currentVisiblePos = visiblePos; 839 VisiblePosition savedVisiblePos; 840 841 // move up until we get to the top 842 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the 843 // top document. 844 do { 845 savedVisiblePos = currentVisiblePos; 846 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole); 847 currentVisiblePos = prevVisiblePos; 848 ++lineCount; 849 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))); 850 851 return lineCount; 852 } 853 854 bool AXObject::isARIAControl(AccessibilityRole ariaRole) 855 { 856 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole 857 || ariaRole == ComboBoxRole || ariaRole == SliderRole; 858 } 859 860 bool AXObject::isARIAInput(AccessibilityRole ariaRole) 861 { 862 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; 863 } 864 865 AccessibilityRole AXObject::ariaRoleToWebCoreRole(const String& value) 866 { 867 ASSERT(!value.isEmpty()); 868 869 static const ARIARoleMap* roleMap = createARIARoleMap(); 870 871 Vector<String> roleVector; 872 value.split(' ', roleVector); 873 AccessibilityRole role = UnknownRole; 874 unsigned size = roleVector.size(); 875 for (unsigned i = 0; i < size; ++i) { 876 String roleName = roleVector[i]; 877 role = roleMap->get(roleName); 878 if (role) 879 return role; 880 } 881 882 return role; 883 } 884 885 AccessibilityRole AXObject::buttonRoleType() const 886 { 887 // If aria-pressed is present, then it should be exposed as a toggle button. 888 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed 889 if (ariaPressedIsPresent()) 890 return ToggleButtonRole; 891 if (ariaHasPopup()) 892 return PopUpButtonRole; 893 // We don't contemplate RadioButtonRole, as it depends on the input 894 // type. 895 896 return ButtonRole; 897 } 898 899 } // namespace WebCore 900