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