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