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