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