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