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