Home | History | Annotate | Download | only in accessibility
      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