• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
createARIARoleMap()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 
AXObject()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 
~AXObject()140 AXObject::~AXObject()
141 {
142     ASSERT(isDetached());
143 }
144 
detach()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 
isDetached() const154 bool AXObject::isDetached() const
155 {
156     return m_detached;
157 }
158 
axObjectCache() const159 AXObjectCache* AXObject::axObjectCache() const
160 {
161     Document* doc = document();
162     if (doc)
163         return doc->axObjectCache();
164     return 0;
165 }
166 
isARIATextControl() const167 bool AXObject::isARIATextControl() const
168 {
169     return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole;
170 }
171 
isButton() const172 bool AXObject::isButton() const
173 {
174     AccessibilityRole role = roleValue();
175 
176     return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole;
177 }
178 
isLandmarkRelated() const179 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 
isMenuRelated() const198 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 
isTextControl() const211 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 
isClickable() const223 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 
isExpanded() const247 bool AXObject::isExpanded() const
248 {
249     if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
250         return true;
251 
252     return false;
253 }
254 
accessibilityIsIgnored() const255 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 
accessibilityIsIgnoredByDefault() const282 bool AXObject::accessibilityIsIgnoredByDefault() const
283 {
284     return defaultObjectInclusion() == IgnoreObject;
285 }
286 
accessibilityPlatformIncludesObject() const287 AXObjectInclusion AXObject::accessibilityPlatformIncludesObject() const
288 {
289     if (isMenuListPopup() || isMenuListOption())
290         return IncludeObject;
291 
292     return DefaultBehavior;
293 }
294 
defaultObjectInclusion() const295 AXObjectInclusion AXObject::defaultObjectInclusion() const
296 {
297     if (isInertOrAriaHidden())
298         return IgnoreObject;
299 
300     if (isPresentationalChildOfAriaRole())
301         return IgnoreObject;
302 
303     return accessibilityPlatformIncludesObject();
304 }
305 
isInertOrAriaHidden() const306 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 
lastKnownIsIgnoredValue()322 bool AXObject::lastKnownIsIgnoredValue()
323 {
324     if (m_lastKnownIsIgnoredValue == DefaultBehavior)
325         m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject;
326 
327     return m_lastKnownIsIgnoredValue == IgnoreObject;
328 }
329 
setLastKnownIsIgnoredValue(bool isIgnored)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;
orientation() const336 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 
queryString(WebLocalizedString::Name name)348 static String queryString(WebLocalizedString::Name name)
349 {
350     return Locale::defaultLocale().queryString(name);
351 }
352 
actionVerb() const353 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 
checkboxOrRadioValue() const381 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 
ariaIsMultiline() const395 bool AXObject::ariaIsMultiline() const
396 {
397     return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
398 }
399 
ariaPressedIsPresent() const400 bool AXObject::ariaPressedIsPresent() const
401 {
402     return !getAttribute(aria_pressedAttr).isEmpty();
403 }
404 
supportsARIAAttributes() const405 bool AXObject::supportsARIAAttributes() const
406 {
407     return supportsARIALiveRegion()
408         || supportsARIADragging()
409         || supportsARIADropping()
410         || supportsARIAFlowTo()
411         || supportsARIAOwns()
412         || hasAttribute(aria_labelAttr);
413 }
414 
supportsRangeValue() const415 bool AXObject::supportsRangeValue() const
416 {
417     return isProgressIndicator()
418         || isSlider()
419         || isScrollbar()
420         || isSpinButton();
421 }
422 
ariaTreeRows(AccessibilityChildrenVector & result)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 
supportsARIALiveRegion() const439 bool AXObject::supportsARIALiveRegion() const
440 {
441     const AtomicString& liveRegion = ariaLiveRegionStatus();
442     return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
443 }
444 
markCachedElementRectDirty() const445 void AXObject::markCachedElementRectDirty() const
446 {
447     for (unsigned i = 0; i < m_children.size(); ++i)
448         m_children[i].get()->markCachedElementRectDirty();
449 }
450 
clickPoint()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 
boundingBoxForQuads(RenderObject * obj,const Vector<FloatQuad> & quads)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 
elementAccessibilityHitTest(const IntPoint & point) const479 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 
children()499 const AXObject::AccessibilityChildrenVector& AXObject::children()
500 {
501     updateChildrenIfNecessary();
502 
503     return m_children;
504 }
505 
parentObjectUnignored() const506 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 
firstAccessibleObjectFromNode(const Node * node)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 
updateChildrenIfNecessary()537 void AXObject::updateChildrenIfNecessary()
538 {
539     if (!hasChildren())
540         addChildren();
541 }
542 
clearChildren()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 
focusedUIElement() const554 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 
document() const567 Document* AXObject::document() const
568 {
569     FrameView* frameView = documentFrameView();
570     if (!frameView)
571         return 0;
572 
573     return frameView->frame().document();
574 }
575 
documentFrameView() const576 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 
language() const588 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 
hasAttribute(const QualifiedName & attribute) const607 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 
getAttribute(const QualifiedName & attribute) const620 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 
press() const633 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 
scrollToMakeVisible() const643 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.
computeBestScrollOffset(int currentScrollOffset,int subfocusMin,int subfocusMax,int objectMin,int objectMax,int viewportMin,int viewportMax)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 
scrollToMakeVisibleWithSubFocus(const IntRect & subfocus) const720 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 
scrollToGlobalPoint(const IntPoint & globalPoint) const756 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 
notifyIfIgnoredValueChanged()816 void AXObject::notifyIfIgnoredValueChanged()
817 {
818     bool isIgnored = accessibilityIsIgnored();
819     if (lastKnownIsIgnoredValue() != isIgnored) {
820         axObjectCache()->childrenChanged(parentObject());
821         setLastKnownIsIgnoredValue(isIgnored);
822     }
823 }
824 
selectionChanged()825 void AXObject::selectionChanged()
826 {
827     if (AXObject* parent = parentObjectIfExists())
828         parent->selectionChanged();
829 }
830 
lineForPosition(const VisiblePosition & visiblePos) const831 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 
isARIAControl(AccessibilityRole ariaRole)858 bool AXObject::isARIAControl(AccessibilityRole ariaRole)
859 {
860     return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
861     || ariaRole == ComboBoxRole || ariaRole == SliderRole;
862 }
863 
isARIAInput(AccessibilityRole ariaRole)864 bool AXObject::isARIAInput(AccessibilityRole ariaRole)
865 {
866     return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
867 }
868 
ariaRoleToWebCoreRole(const String & value)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 
buttonRoleType() const889 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