• 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/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 
createARIARoleMap()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 
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 
updateBackingStore()167 void AXObject::updateBackingStore()
168 {
169     // Updating the layout may delete this object.
170     if (Document* document = this->document())
171         document->updateLayoutIgnorePendingStylesheets();
172 }
173 
isARIATextControl() const174 bool AXObject::isARIATextControl() const
175 {
176     return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole;
177 }
178 
isButton() const179 bool AXObject::isButton() const
180 {
181     AccessibilityRole role = roleValue();
182 
183     return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole;
184 }
185 
isMenuRelated() const186 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 
isTextControl() const199 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 
isClickable() const211 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 
isExpanded() const235 bool AXObject::isExpanded() const
236 {
237     if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
238         return true;
239 
240     return false;
241 }
242 
accessibilityIsIgnored() const243 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 
accessibilityIsIgnoredByDefault() const266 bool AXObject::accessibilityIsIgnoredByDefault() const
267 {
268     return defaultObjectInclusion() == IgnoreObject;
269 }
270 
accessibilityPlatformIncludesObject() const271 AXObjectInclusion AXObject::accessibilityPlatformIncludesObject() const
272 {
273     if (isMenuListPopup() || isMenuListOption())
274         return IncludeObject;
275 
276     return DefaultBehavior;
277 }
278 
defaultObjectInclusion() const279 AXObjectInclusion AXObject::defaultObjectInclusion() const
280 {
281     if (isInertOrAriaHidden())
282         return IgnoreObject;
283 
284     if (isPresentationalChildOfAriaRole())
285         return IgnoreObject;
286 
287     return accessibilityPlatformIncludesObject();
288 }
289 
isInertOrAriaHidden() const290 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 
lastKnownIsIgnoredValue()306 bool AXObject::lastKnownIsIgnoredValue()
307 {
308     if (m_lastKnownIsIgnoredValue == DefaultBehavior)
309         m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject;
310 
311     return m_lastKnownIsIgnoredValue == IgnoreObject;
312 }
313 
setLastKnownIsIgnoredValue(bool isIgnored)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;
orientation() const320 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 
queryString(WebLocalizedString::Name name)332 static String queryString(WebLocalizedString::Name name)
333 {
334     return Locale::defaultLocale().queryString(name);
335 }
336 
actionVerb() const337 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 
checkboxOrRadioValue() const365 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 
placeholderValue() const379 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 
ariaIsMultiline() const388 bool AXObject::ariaIsMultiline() const
389 {
390     return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
391 }
392 
ariaPressedIsPresent() const393 bool AXObject::ariaPressedIsPresent() const
394 {
395     return !getAttribute(aria_pressedAttr).isEmpty();
396 }
397 
invalidStatus() const398 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 
supportsARIAAttributes() const412 bool AXObject::supportsARIAAttributes() const
413 {
414     return supportsARIALiveRegion()
415         || supportsARIADragging()
416         || supportsARIADropping()
417         || supportsARIAFlowTo()
418         || supportsARIAOwns()
419         || hasAttribute(aria_labelAttr);
420 }
421 
supportsRangeValue() const422 bool AXObject::supportsRangeValue() const
423 {
424     return isProgressIndicator()
425         || isSlider()
426         || isScrollbar()
427         || isSpinButton();
428 }
429 
ariaTreeRows(AccessibilityChildrenVector & result)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 
supportsARIALiveRegion() const446 bool AXObject::supportsARIALiveRegion() const
447 {
448     const AtomicString& liveRegion = ariaLiveRegionStatus();
449     return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
450 }
451 
markCachedElementRectDirty() const452 void AXObject::markCachedElementRectDirty() const
453 {
454     for (unsigned i = 0; i < m_children.size(); ++i)
455         m_children[i].get()->markCachedElementRectDirty();
456 }
457 
clickPoint()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 
boundingBoxForQuads(RenderObject * obj,const Vector<FloatQuad> & quads)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 
elementAccessibilityHitTest(const IntPoint & point) const486 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 
children()506 const AXObject::AccessibilityChildrenVector& AXObject::children()
507 {
508     updateChildrenIfNecessary();
509 
510     return m_children;
511 }
512 
parentObjectUnignored() const513 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 
firstAccessibleObjectFromNode(const Node * node)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 
updateChildrenIfNecessary()544 void AXObject::updateChildrenIfNecessary()
545 {
546     if (!hasChildren())
547         addChildren();
548 }
549 
clearChildren()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 
focusedUIElement() const561 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 
document() const574 Document* AXObject::document() const
575 {
576     FrameView* frameView = documentFrameView();
577     if (!frameView)
578         return 0;
579 
580     return frameView->frame().document();
581 }
582 
documentFrameView() const583 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 
language() const595 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 
hasAttribute(const QualifiedName & attribute) const614 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 
getAttribute(const QualifiedName & attribute) const627 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 
press() const640 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 
scrollToMakeVisible() const650 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.
computeBestScrollOffset(int currentScrollOffset,int subfocusMin,int subfocusMax,int objectMin,int objectMax,int viewportMin,int viewportMax)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 
scrollToMakeVisibleWithSubFocus(const IntRect & subfocus) const727 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 
scrollToGlobalPoint(const IntPoint & globalPoint) const760 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 
notifyIfIgnoredValueChanged()820 void AXObject::notifyIfIgnoredValueChanged()
821 {
822     bool isIgnored = accessibilityIsIgnored();
823     if (lastKnownIsIgnoredValue() != isIgnored) {
824         axObjectCache()->childrenChanged(parentObject());
825         setLastKnownIsIgnoredValue(isIgnored);
826     }
827 }
828 
selectionChanged()829 void AXObject::selectionChanged()
830 {
831     if (AXObject* parent = parentObjectIfExists())
832         parent->selectionChanged();
833 }
834 
lineForPosition(const VisiblePosition & visiblePos) const835 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 
isARIAControl(AccessibilityRole ariaRole)862 bool AXObject::isARIAControl(AccessibilityRole ariaRole)
863 {
864     return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
865     || ariaRole == ComboBoxRole || ariaRole == SliderRole;
866 }
867 
isARIAInput(AccessibilityRole ariaRole)868 bool AXObject::isARIAInput(AccessibilityRole ariaRole)
869 {
870     return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
871 }
872 
ariaRoleToWebCoreRole(const String & value)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 
buttonRoleType() const893 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