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