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