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/AccessibilityObject.h" 31 32 #include "core/accessibility/AXObjectCache.h" 33 #include "core/dom/NodeTraversal.h" 34 #include "core/dom/UserGestureIndicator.h" 35 #include "core/editing/VisibleUnits.h" 36 #include "core/editing/htmlediting.h" 37 #include "core/platform/LocalizedStrings.h" 38 #include "core/rendering/RenderListItem.h" 39 #include "core/rendering/RenderTheme.h" 40 #include "core/rendering/RenderView.h" 41 #include "wtf/StdLibExtras.h" 42 #include "wtf/text/WTFString.h" 43 44 using namespace std; 45 46 namespace WebCore { 47 48 using namespace HTMLNames; 49 50 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; 51 52 struct RoleEntry { 53 String ariaRole; 54 AccessibilityRole webcoreRole; 55 }; 56 57 static ARIARoleMap* createARIARoleMap() 58 { 59 const RoleEntry roles[] = { 60 { "alert", ApplicationAlertRole }, 61 { "alertdialog", ApplicationAlertDialogRole }, 62 { "application", LandmarkApplicationRole }, 63 { "article", DocumentArticleRole }, 64 { "banner", LandmarkBannerRole }, 65 { "button", ButtonRole }, 66 { "checkbox", CheckBoxRole }, 67 { "complementary", LandmarkComplementaryRole }, 68 { "contentinfo", LandmarkContentInfoRole }, 69 { "dialog", ApplicationDialogRole }, 70 { "directory", DirectoryRole }, 71 { "grid", TableRole }, 72 { "gridcell", CellRole }, 73 { "columnheader", ColumnHeaderRole }, 74 { "combobox", ComboBoxRole }, 75 { "definition", DefinitionRole }, 76 { "document", DocumentRole }, 77 { "rowheader", RowHeaderRole }, 78 { "group", GroupRole }, 79 { "heading", HeadingRole }, 80 { "img", ImageRole }, 81 { "link", WebCoreLinkRole }, 82 { "list", ListRole }, 83 { "listitem", ListItemRole }, 84 { "listbox", ListBoxRole }, 85 { "log", ApplicationLogRole }, 86 // "option" isn't here because it may map to different roles depending on the parent element's role 87 { "main", LandmarkMainRole }, 88 { "marquee", ApplicationMarqueeRole }, 89 { "math", DocumentMathRole }, 90 { "menu", MenuRole }, 91 { "menubar", MenuBarRole }, 92 { "menuitem", MenuItemRole }, 93 { "menuitemcheckbox", MenuItemRole }, 94 { "menuitemradio", MenuItemRole }, 95 { "note", DocumentNoteRole }, 96 { "navigation", LandmarkNavigationRole }, 97 { "option", ListBoxOptionRole }, 98 { "presentation", PresentationalRole }, 99 { "progressbar", ProgressIndicatorRole }, 100 { "radio", RadioButtonRole }, 101 { "radiogroup", RadioGroupRole }, 102 { "region", DocumentRegionRole }, 103 { "row", RowRole }, 104 { "scrollbar", ScrollBarRole }, 105 { "search", LandmarkSearchRole }, 106 { "separator", SplitterRole }, 107 { "slider", SliderRole }, 108 { "spinbutton", SpinButtonRole }, 109 { "status", ApplicationStatusRole }, 110 { "tab", TabRole }, 111 { "tablist", TabListRole }, 112 { "tabpanel", TabPanelRole }, 113 { "text", StaticTextRole }, 114 { "textbox", TextAreaRole }, 115 { "timer", ApplicationTimerRole }, 116 { "toolbar", ToolbarRole }, 117 { "tooltip", UserInterfaceTooltipRole }, 118 { "tree", TreeRole }, 119 { "treegrid", TreeGridRole }, 120 { "treeitem", TreeItemRole } 121 }; 122 ARIARoleMap* roleMap = new ARIARoleMap; 123 124 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i) 125 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); 126 return roleMap; 127 } 128 129 AccessibilityObject::AccessibilityObject() 130 : m_id(0) 131 , m_haveChildren(false) 132 , m_role(UnknownRole) 133 , m_lastKnownIsIgnoredValue(DefaultBehavior) 134 , m_detached(false) 135 { 136 } 137 138 AccessibilityObject::~AccessibilityObject() 139 { 140 ASSERT(isDetached()); 141 } 142 143 void AccessibilityObject::detach() 144 { 145 // Clear any children and call detachFromParent on them so that 146 // no children are left with dangling pointers to their parent. 147 clearChildren(); 148 149 m_detached = true; 150 } 151 152 bool AccessibilityObject::isDetached() const 153 { 154 return m_detached; 155 } 156 157 AXObjectCache* AccessibilityObject::axObjectCache() const 158 { 159 Document* doc = document(); 160 if (doc) 161 return doc->axObjectCache(); 162 return 0; 163 } 164 165 #if HAVE(ACCESSIBILITY) 166 void AccessibilityObject::updateBackingStore() 167 { 168 // Updating the layout may delete this object. 169 if (Document* document = this->document()) 170 document->updateLayoutIgnorePendingStylesheets(); 171 } 172 #endif 173 174 bool AccessibilityObject::isARIATextControl() const 175 { 176 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole; 177 } 178 179 bool AccessibilityObject::isButton() const 180 { 181 AccessibilityRole role = roleValue(); 182 183 return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole; 184 } 185 186 bool AccessibilityObject::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 AccessibilityObject::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 AccessibilityObject::isExpanded() const 212 { 213 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) 214 return true; 215 216 return false; 217 } 218 219 bool AccessibilityObject::accessibilityIsIgnored() const 220 { 221 AXComputedObjectAttributeCache* attributeCache = axObjectCache()->computedObjectAttributeCache(); 222 if (attributeCache) { 223 AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID()); 224 switch (ignored) { 225 case IgnoreObject: 226 return true; 227 case IncludeObject: 228 return false; 229 case DefaultBehavior: 230 break; 231 } 232 } 233 234 bool result = computeAccessibilityIsIgnored(); 235 236 if (attributeCache) 237 attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject); 238 239 return result; 240 } 241 242 bool AccessibilityObject::accessibilityIsIgnoredByDefault() const 243 { 244 return defaultObjectInclusion() == IgnoreObject; 245 } 246 247 AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const 248 { 249 if (isMenuListPopup() || isMenuListOption()) 250 return IncludeObject; 251 252 return DefaultBehavior; 253 } 254 255 AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const 256 { 257 if (ariaIsHidden()) 258 return IgnoreObject; 259 260 if (isPresentationalChildOfAriaRole()) 261 return IgnoreObject; 262 263 return accessibilityPlatformIncludesObject(); 264 } 265 266 bool AccessibilityObject::lastKnownIsIgnoredValue() 267 { 268 if (m_lastKnownIsIgnoredValue == DefaultBehavior) 269 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject; 270 271 return m_lastKnownIsIgnoredValue == IgnoreObject; 272 } 273 274 void AccessibilityObject::setLastKnownIsIgnoredValue(bool isIgnored) 275 { 276 m_lastKnownIsIgnoredValue = isIgnored ? IgnoreObject : IncludeObject; 277 } 278 279 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; 280 AccessibilityOrientation AccessibilityObject::orientation() const 281 { 282 LayoutRect bounds = elementRect(); 283 if (bounds.size().width() > bounds.size().height()) 284 return AccessibilityOrientationHorizontal; 285 if (bounds.size().height() > bounds.size().width()) 286 return AccessibilityOrientationVertical; 287 288 // A tie goes to horizontal. 289 return AccessibilityOrientationHorizontal; 290 } 291 292 #if HAVE(ACCESSIBILITY) 293 String AccessibilityObject::actionVerb() const 294 { 295 // FIXME: Need to add verbs for select elements. 296 297 switch (roleValue()) { 298 case ButtonRole: 299 case ToggleButtonRole: 300 return AXButtonActionVerb(); 301 case TextFieldRole: 302 case TextAreaRole: 303 return AXTextFieldActionVerb(); 304 case RadioButtonRole: 305 return AXRadioButtonActionVerb(); 306 case CheckBoxRole: 307 return isChecked() ? AXCheckedCheckBoxActionVerb() : AXUncheckedCheckBoxActionVerb(); 308 case LinkRole: 309 case WebCoreLinkRole: 310 return AXLinkActionVerb(); 311 case PopUpButtonRole: 312 return AXMenuListActionVerb(); 313 case MenuListPopupRole: 314 return AXMenuListPopupActionVerb(); 315 default: 316 return emptyString(); 317 } 318 } 319 #endif 320 321 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const 322 { 323 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. 324 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. 325 326 const AtomicString& result = getAttribute(aria_checkedAttr); 327 if (equalIgnoringCase(result, "true")) 328 return ButtonStateOn; 329 if (equalIgnoringCase(result, "mixed")) 330 return ButtonStateMixed; 331 332 return ButtonStateOff; 333 } 334 335 const AtomicString& AccessibilityObject::placeholderValue() const 336 { 337 const AtomicString& placeholder = getAttribute(placeholderAttr); 338 if (!placeholder.isEmpty()) 339 return placeholder; 340 341 return nullAtom; 342 } 343 344 bool AccessibilityObject::ariaIsMultiline() const 345 { 346 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); 347 } 348 349 bool AccessibilityObject::ariaPressedIsPresent() const 350 { 351 return !getAttribute(aria_pressedAttr).isEmpty(); 352 } 353 354 const AtomicString& AccessibilityObject::invalidStatus() const 355 { 356 DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false", AtomicString::ConstructFromLiteral)); 357 358 // aria-invalid can return false (default), grammer, spelling, or true. 359 const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr); 360 361 // If empty or not present, it should return false. 362 if (ariaInvalid.isEmpty()) 363 return invalidStatusFalse; 364 365 return ariaInvalid; 366 } 367 368 bool AccessibilityObject::supportsARIAAttributes() const 369 { 370 return supportsARIALiveRegion() 371 || supportsARIADragging() 372 || supportsARIADropping() 373 || supportsARIAFlowTo() 374 || supportsARIAOwns() 375 || hasAttribute(aria_labelAttr); 376 } 377 378 bool AccessibilityObject::supportsRangeValue() const 379 { 380 return isProgressIndicator() 381 || isSlider() 382 || isScrollbar() 383 || isSpinButton(); 384 } 385 386 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) 387 { 388 AccessibilityChildrenVector axChildren = children(); 389 unsigned count = axChildren.size(); 390 for (unsigned k = 0; k < count; ++k) { 391 AccessibilityObject* obj = axChildren[k].get(); 392 393 // Add tree items as the rows. 394 if (obj->roleValue() == TreeItemRole) 395 result.append(obj); 396 397 // Now see if this item also has rows hiding inside of it. 398 obj->ariaTreeRows(result); 399 } 400 } 401 402 bool AccessibilityObject::supportsARIALiveRegion() const 403 { 404 const AtomicString& liveRegion = ariaLiveRegionStatus(); 405 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); 406 } 407 408 void AccessibilityObject::markCachedElementRectDirty() const 409 { 410 for (unsigned i = 0; i < m_children.size(); ++i) 411 m_children[i].get()->markCachedElementRectDirty(); 412 } 413 414 IntPoint AccessibilityObject::clickPoint() 415 { 416 LayoutRect rect = elementRect(); 417 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2)); 418 } 419 420 IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads) 421 { 422 ASSERT(obj); 423 if (!obj) 424 return IntRect(); 425 426 size_t count = quads.size(); 427 if (!count) 428 return IntRect(); 429 430 IntRect result; 431 for (size_t i = 0; i < count; ++i) { 432 IntRect r = quads[i].enclosingBoundingBox(); 433 if (!r.isEmpty()) { 434 if (obj->style()->hasAppearance()) 435 obj->theme()->adjustRepaintRect(obj, r); 436 result.unite(r); 437 } 438 } 439 return result; 440 } 441 442 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const 443 { 444 // Send the hit test back into the sub-frame if necessary. 445 if (isAttachment()) { 446 Widget* widget = widgetForAttachmentView(); 447 // Normalize the point for the widget's bounds. 448 if (widget && widget->isFrameView()) 449 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); 450 } 451 452 // Check if there are any mock elements that need to be handled. 453 size_t count = m_children.size(); 454 for (size_t k = 0; k < count; k++) { 455 if (m_children[k]->isMockObject() && m_children[k]->elementRect().contains(point)) 456 return m_children[k]->elementAccessibilityHitTest(point); 457 } 458 459 return const_cast<AccessibilityObject*>(this); 460 } 461 462 #if HAVE(ACCESSIBILITY) 463 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children() 464 { 465 updateChildrenIfNecessary(); 466 467 return m_children; 468 } 469 #endif 470 471 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const 472 { 473 AccessibilityObject* parent; 474 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { 475 } 476 477 return parent; 478 } 479 480 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) 481 { 482 if (!node) 483 return 0; 484 485 Document* document = node->document(); 486 if (!document) 487 return 0; 488 489 AXObjectCache* cache = document->axObjectCache(); 490 491 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); 492 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { 493 node = NodeTraversal::next(node); 494 495 while (node && !node->renderer()) 496 node = NodeTraversal::nextSkippingChildren(node); 497 498 if (!node) 499 return 0; 500 501 accessibleObject = cache->getOrCreate(node->renderer()); 502 } 503 504 return accessibleObject; 505 } 506 507 void AccessibilityObject::updateChildrenIfNecessary() 508 { 509 if (!hasChildren()) 510 addChildren(); 511 } 512 513 void AccessibilityObject::clearChildren() 514 { 515 // Some objects have weak pointers to their parents and those associations need to be detached. 516 size_t length = m_children.size(); 517 for (size_t i = 0; i < length; i++) 518 m_children[i]->detachFromParent(); 519 520 m_children.clear(); 521 m_haveChildren = false; 522 } 523 524 AccessibilityObject* AccessibilityObject::focusedUIElement() const 525 { 526 Document* doc = document(); 527 if (!doc) 528 return 0; 529 530 Page* page = doc->page(); 531 if (!page) 532 return 0; 533 534 return AXObjectCache::focusedUIElementForPage(page); 535 } 536 537 Document* AccessibilityObject::document() const 538 { 539 FrameView* frameView = documentFrameView(); 540 if (!frameView) 541 return 0; 542 543 return frameView->frame()->document(); 544 } 545 546 FrameView* AccessibilityObject::documentFrameView() const 547 { 548 const AccessibilityObject* object = this; 549 while (object && !object->isAccessibilityRenderObject()) 550 object = object->parentObject(); 551 552 if (!object) 553 return 0; 554 555 return object->documentFrameView(); 556 } 557 558 Page* AccessibilityObject::page() const 559 { 560 Document* document = this->document(); 561 if (!document) 562 return 0; 563 return document->page(); 564 } 565 566 String AccessibilityObject::language() const 567 { 568 const AtomicString& lang = getAttribute(langAttr); 569 if (!lang.isEmpty()) 570 return lang; 571 572 AccessibilityObject* parent = parentObject(); 573 574 // as a last resort, fall back to the content language specified in the meta tag 575 if (!parent) { 576 Document* doc = document(); 577 if (doc) 578 return doc->contentLanguage(); 579 return nullAtom; 580 } 581 582 return parent->language(); 583 } 584 585 bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const 586 { 587 Node* elementNode = node(); 588 if (!elementNode) 589 return false; 590 591 if (!elementNode->isElementNode()) 592 return false; 593 594 Element* element = toElement(elementNode); 595 return element->fastHasAttribute(attribute); 596 } 597 598 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const 599 { 600 Node* elementNode = node(); 601 if (!elementNode) 602 return nullAtom; 603 604 if (!elementNode->isElementNode()) 605 return nullAtom; 606 607 Element* element = toElement(elementNode); 608 return element->fastGetAttribute(attribute); 609 } 610 611 TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() const 612 { 613 return TextIteratorIgnoresStyleVisibility; 614 } 615 616 bool AccessibilityObject::press() const 617 { 618 Element* actionElem = actionElement(); 619 if (!actionElem) 620 return false; 621 if (Frame* f = actionElem->document()->frame()) 622 f->loader()->resetMultipleFormSubmissionProtection(); 623 624 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); 625 actionElem->accessKeyAction(true); 626 return true; 627 } 628 629 void AccessibilityObject::scrollToMakeVisible() const 630 { 631 IntRect objectRect = pixelSnappedIntRect(elementRect()); 632 objectRect.setLocation(IntPoint()); 633 scrollToMakeVisibleWithSubFocus(objectRect); 634 } 635 636 // This is a 1-dimensional scroll offset helper function that's applied 637 // separately in the horizontal and vertical directions, because the 638 // logic is the same. The goal is to compute the best scroll offset 639 // in order to make an object visible within a viewport. 640 // 641 // In case the whole object cannot fit, you can specify a 642 // subfocus - a smaller region within the object that should 643 // be prioritized. If the whole object can fit, the subfocus is 644 // ignored. 645 // 646 // Example: the viewport is scrolled to the right just enough 647 // that the object is in view. 648 // Before: 649 // +----------Viewport---------+ 650 // +---Object---+ 651 // +--SubFocus--+ 652 // 653 // After: 654 // +----------Viewport---------+ 655 // +---Object---+ 656 // +--SubFocus--+ 657 // 658 // When constraints cannot be fully satisfied, the min 659 // (left/top) position takes precedence over the max (right/bottom). 660 // 661 // Note that the return value represents the ideal new scroll offset. 662 // This may be out of range - the calling function should clip this 663 // to the available range. 664 static int computeBestScrollOffset(int currentScrollOffset, 665 int subfocusMin, int subfocusMax, 666 int objectMin, int objectMax, 667 int viewportMin, int viewportMax) { 668 int viewportSize = viewportMax - viewportMin; 669 670 // If the focus size is larger than the viewport size, shrink it in the 671 // direction of subfocus. 672 if (objectMax - objectMin > viewportSize) { 673 // Subfocus must be within focus: 674 subfocusMin = std::max(subfocusMin, objectMin); 675 subfocusMax = std::min(subfocusMax, objectMax); 676 677 // Subfocus must be no larger than the viewport size; favor top/left. 678 if (subfocusMax - subfocusMin > viewportSize) 679 subfocusMax = subfocusMin + viewportSize; 680 681 if (subfocusMin + viewportSize > objectMax) 682 objectMin = objectMax - viewportSize; 683 else { 684 objectMin = subfocusMin; 685 objectMax = subfocusMin + viewportSize; 686 } 687 } 688 689 // Exit now if the focus is already within the viewport. 690 if (objectMin - currentScrollOffset >= viewportMin 691 && objectMax - currentScrollOffset <= viewportMax) 692 return currentScrollOffset; 693 694 // Scroll left if we're too far to the right. 695 if (objectMax - currentScrollOffset > viewportMax) 696 return objectMax - viewportMax; 697 698 // Scroll right if we're too far to the left. 699 if (objectMin - currentScrollOffset < viewportMin) 700 return objectMin - viewportMin; 701 702 ASSERT_NOT_REACHED(); 703 704 // This shouldn't happen. 705 return currentScrollOffset; 706 } 707 708 void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const 709 { 710 // Search up the parent chain until we find the first one that's scrollable. 711 AccessibilityObject* scrollParent = parentObject(); 712 ScrollableArea* scrollableArea; 713 for (scrollableArea = 0; 714 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); 715 scrollParent = scrollParent->parentObject()) { } 716 if (!scrollableArea) 717 return; 718 719 LayoutRect objectRect = elementRect(); 720 IntPoint scrollPosition = scrollableArea->scrollPosition(); 721 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(); 722 723 int desiredX = computeBestScrollOffset( 724 scrollPosition.x(), 725 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), 726 objectRect.x(), objectRect.maxX(), 727 0, scrollVisibleRect.width()); 728 int desiredY = computeBestScrollOffset( 729 scrollPosition.y(), 730 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(), 731 objectRect.y(), objectRect.maxY(), 732 0, scrollVisibleRect.height()); 733 734 scrollParent->scrollTo(IntPoint(desiredX, desiredY)); 735 736 // Recursively make sure the scroll parent itself is visible. 737 if (scrollParent->parentObject()) 738 scrollParent->scrollToMakeVisible(); 739 } 740 741 void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const 742 { 743 // Search up the parent chain and create a vector of all scrollable parent objects 744 // and ending with this object itself. 745 Vector<const AccessibilityObject*> objects; 746 AccessibilityObject* parentObject; 747 for (parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) { 748 if (parentObject->getScrollableAreaIfScrollable()) 749 objects.prepend(parentObject); 750 } 751 objects.append(this); 752 753 // Start with the outermost scrollable (the main window) and try to scroll the 754 // next innermost object to the given point. 755 int offsetX = 0, offsetY = 0; 756 IntPoint point = globalPoint; 757 size_t levels = objects.size() - 1; 758 for (size_t i = 0; i < levels; i++) { 759 const AccessibilityObject* outer = objects[i]; 760 const AccessibilityObject* inner = objects[i + 1]; 761 762 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable(); 763 764 LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->elementRect() : inner->elementRect(); 765 LayoutRect objectRect = innerRect; 766 IntPoint scrollPosition = scrollableArea->scrollPosition(); 767 768 // Convert the object rect into local coordinates. 769 objectRect.move(offsetX, offsetY); 770 if (!outer->isAccessibilityScrollView()) 771 objectRect.move(scrollPosition.x(), scrollPosition.y()); 772 773 int desiredX = computeBestScrollOffset( 774 0, 775 objectRect.x(), objectRect.maxX(), 776 objectRect.x(), objectRect.maxX(), 777 point.x(), point.x()); 778 int desiredY = computeBestScrollOffset( 779 0, 780 objectRect.y(), objectRect.maxY(), 781 objectRect.y(), objectRect.maxY(), 782 point.y(), point.y()); 783 outer->scrollTo(IntPoint(desiredX, desiredY)); 784 785 if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) { 786 // If outer object we just scrolled is a scroll view (main window or iframe) but the 787 // inner object is not, keep track of the coordinate transformation to apply to 788 // future nested calculations. 789 scrollPosition = scrollableArea->scrollPosition(); 790 offsetX -= (scrollPosition.x() + point.x()); 791 offsetY -= (scrollPosition.y() + point.y()); 792 point.move(scrollPosition.x() - innerRect.x(), 793 scrollPosition.y() - innerRect.y()); 794 } else if (inner->isAccessibilityScrollView()) { 795 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation. 796 offsetX = 0; 797 offsetY = 0; 798 } 799 } 800 } 801 802 void AccessibilityObject::notifyIfIgnoredValueChanged() 803 { 804 bool isIgnored = accessibilityIsIgnored(); 805 if (lastKnownIsIgnoredValue() != isIgnored) { 806 axObjectCache()->childrenChanged(parentObject()); 807 setLastKnownIsIgnoredValue(isIgnored); 808 } 809 } 810 811 void AccessibilityObject::selectionChanged() 812 { 813 if (AccessibilityObject* parent = parentObjectIfExists()) 814 parent->selectionChanged(); 815 } 816 817 static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos) 818 { 819 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); 820 RenderObject* startRenderer = renderer; 821 RenderStyle* style = renderer->style(); 822 823 // traverse backward by renderer to look for style change 824 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { 825 // skip non-leaf nodes 826 if (r->firstChild()) 827 continue; 828 829 // stop at style change 830 if (r->style() != style) 831 break; 832 833 // remember match 834 startRenderer = r; 835 } 836 837 return firstPositionInOrBeforeNode(startRenderer->node()); 838 } 839 840 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos) 841 { 842 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); 843 RenderObject* endRenderer = renderer; 844 RenderStyle* style = renderer->style(); 845 846 // traverse forward by renderer to look for style change 847 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { 848 // skip non-leaf nodes 849 if (r->firstChild()) 850 continue; 851 852 // stop at style change 853 if (r->style() != style) 854 break; 855 856 // remember match 857 endRenderer = r; 858 } 859 860 return lastPositionInOrAfterNode(endRenderer->node()); 861 } 862 863 static bool replacedNodeNeedsCharacter(Node* replacedNode) 864 { 865 // we should always be given a rendered node and a replaced node, but be safe 866 // replaced nodes are either attachments (widgets) or images 867 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) 868 return false; 869 870 // create an AX object, but skip it if it is not supposed to be seen 871 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode); 872 if (object->accessibilityIsIgnored()) 873 return false; 874 875 return true; 876 } 877 878 #if HAVE(ACCESSIBILITY) 879 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const 880 { 881 if (visiblePos.isNull() || !node()) 882 return -1; 883 884 // If the position is not in the same editable region as this AX object, return -1. 885 Node* containerNode = visiblePos.deepEquivalent().containerNode(); 886 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode)) 887 return -1; 888 889 int lineCount = -1; 890 VisiblePosition currentVisiblePos = visiblePos; 891 VisiblePosition savedVisiblePos; 892 893 // move up until we get to the top 894 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the 895 // top document. 896 do { 897 savedVisiblePos = currentVisiblePos; 898 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole); 899 currentVisiblePos = prevVisiblePos; 900 ++lineCount; 901 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))); 902 903 return lineCount; 904 } 905 #endif 906 907 // Finds a RenderListItem parent give a node. 908 static RenderListItem* renderListItemContainerForNode(Node* node) 909 { 910 for (; node; node = node->parentNode()) { 911 RenderBoxModelObject* renderer = node->renderBoxModelObject(); 912 if (renderer && renderer->isListItem()) 913 return toRenderListItem(renderer); 914 } 915 return 0; 916 } 917 918 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) 919 { 920 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole 921 || ariaRole == ComboBoxRole || ariaRole == SliderRole; 922 } 923 924 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) 925 { 926 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; 927 } 928 929 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) 930 { 931 ASSERT(!value.isEmpty()); 932 933 static const ARIARoleMap* roleMap = createARIARoleMap(); 934 935 Vector<String> roleVector; 936 value.split(' ', roleVector); 937 AccessibilityRole role = UnknownRole; 938 unsigned size = roleVector.size(); 939 for (unsigned i = 0; i < size; ++i) { 940 String roleName = roleVector[i]; 941 role = roleMap->get(roleName); 942 if (role) 943 return role; 944 } 945 946 return role; 947 } 948 949 AccessibilityRole AccessibilityObject::buttonRoleType() const 950 { 951 // If aria-pressed is present, then it should be exposed as a toggle button. 952 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed 953 if (ariaPressedIsPresent()) 954 return ToggleButtonRole; 955 if (ariaHasPopup()) 956 return PopUpButtonRole; 957 // We don't contemplate RadioButtonRole, as it depends on the input 958 // type. 959 960 return ButtonRole; 961 } 962 963 bool AccessibilityObject::ariaIsHidden() const 964 { 965 if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "true")) 966 return true; 967 968 for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { 969 if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true")) 970 return true; 971 } 972 973 return false; 974 } 975 976 } // namespace WebCore 977