1 /*
2 * Copyright (C) 2008, 2009, 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "core/accessibility/AXObject.h"
31
32 #include "core/accessibility/AXObjectCache.h"
33 #include "core/dom/NodeTraversal.h"
34 #include "core/editing/VisibleUnits.h"
35 #include "core/editing/htmlediting.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/rendering/RenderListItem.h"
38 #include "core/rendering/RenderTheme.h"
39 #include "core/rendering/RenderView.h"
40 #include "platform/UserGestureIndicator.h"
41 #include "platform/text/PlatformLocale.h"
42 #include "wtf/StdLibExtras.h"
43 #include "wtf/text/WTFString.h"
44
45 using blink::WebLocalizedString;
46
47 namespace blink {
48
49 using namespace HTMLNames;
50
51 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
52
53 struct RoleEntry {
54 String ariaRole;
55 AccessibilityRole webcoreRole;
56 };
57
createARIARoleMap()58 static ARIARoleMap* createARIARoleMap()
59 {
60 const RoleEntry roles[] = {
61 { "alert", AlertRole },
62 { "alertdialog", AlertDialogRole },
63 { "application", ApplicationRole },
64 { "article", ArticleRole },
65 { "banner", BannerRole },
66 { "button", ButtonRole },
67 { "checkbox", CheckBoxRole },
68 { "complementary", ComplementaryRole },
69 { "contentinfo", ContentInfoRole },
70 { "dialog", DialogRole },
71 { "directory", DirectoryRole },
72 { "grid", GridRole },
73 { "gridcell", CellRole },
74 { "columnheader", ColumnHeaderRole },
75 { "combobox", ComboBoxRole },
76 { "definition", DefinitionRole },
77 { "document", DocumentRole },
78 { "rowheader", RowHeaderRole },
79 { "group", GroupRole },
80 { "heading", HeadingRole },
81 { "img", ImageRole },
82 { "link", LinkRole },
83 { "list", ListRole },
84 { "listitem", ListItemRole },
85 { "listbox", ListBoxRole },
86 { "log", LogRole },
87 // "option" isn't here because it may map to different roles depending on the parent element's role
88 { "main", MainRole },
89 { "marquee", MarqueeRole },
90 { "math", MathRole },
91 { "menu", MenuRole },
92 { "menubar", MenuBarRole },
93 { "menuitem", MenuItemRole },
94 { "menuitemcheckbox", MenuItemRole },
95 { "menuitemradio", MenuItemRole },
96 { "note", NoteRole },
97 { "navigation", NavigationRole },
98 { "none", NoneRole },
99 { "option", ListBoxOptionRole },
100 { "presentation", PresentationalRole },
101 { "progressbar", ProgressIndicatorRole },
102 { "radio", RadioButtonRole },
103 { "radiogroup", RadioGroupRole },
104 { "region", RegionRole },
105 { "row", RowRole },
106 { "scrollbar", ScrollBarRole },
107 { "search", SearchRole },
108 { "separator", SplitterRole },
109 { "slider", SliderRole },
110 { "spinbutton", SpinButtonRole },
111 { "status", StatusRole },
112 { "tab", TabRole },
113 { "tablist", TabListRole },
114 { "tabpanel", TabPanelRole },
115 { "text", StaticTextRole },
116 { "textbox", TextAreaRole },
117 { "timer", TimerRole },
118 { "toolbar", ToolbarRole },
119 { "tooltip", UserInterfaceTooltipRole },
120 { "tree", TreeRole },
121 { "treegrid", TreeGridRole },
122 { "treeitem", TreeItemRole }
123 };
124 ARIARoleMap* roleMap = new ARIARoleMap;
125
126 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i)
127 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
128 return roleMap;
129 }
130
AXObject()131 AXObject::AXObject()
132 : m_id(0)
133 , m_haveChildren(false)
134 , m_role(UnknownRole)
135 , m_lastKnownIsIgnoredValue(DefaultBehavior)
136 , m_detached(false)
137 {
138 }
139
~AXObject()140 AXObject::~AXObject()
141 {
142 ASSERT(isDetached());
143 }
144
detach()145 void AXObject::detach()
146 {
147 // Clear any children and call detachFromParent on them so that
148 // no children are left with dangling pointers to their parent.
149 clearChildren();
150
151 m_detached = true;
152 }
153
isDetached() const154 bool AXObject::isDetached() const
155 {
156 return m_detached;
157 }
158
axObjectCache() const159 AXObjectCache* AXObject::axObjectCache() const
160 {
161 Document* doc = document();
162 if (doc)
163 return doc->axObjectCache();
164 return 0;
165 }
166
isARIATextControl() const167 bool AXObject::isARIATextControl() const
168 {
169 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole;
170 }
171
isButton() const172 bool AXObject::isButton() const
173 {
174 AccessibilityRole role = roleValue();
175
176 return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole;
177 }
178
isLandmarkRelated() const179 bool AXObject::isLandmarkRelated() const
180 {
181 switch (roleValue()) {
182 case ApplicationRole:
183 case ArticleRole:
184 case BannerRole:
185 case ComplementaryRole:
186 case ContentInfoRole:
187 case FooterRole:
188 case MainRole:
189 case NavigationRole:
190 case RegionRole:
191 case SearchRole:
192 return true;
193 default:
194 return false;
195 }
196 }
197
isMenuRelated() const198 bool AXObject::isMenuRelated() const
199 {
200 switch (roleValue()) {
201 case MenuRole:
202 case MenuBarRole:
203 case MenuButtonRole:
204 case MenuItemRole:
205 return true;
206 default:
207 return false;
208 }
209 }
210
isTextControl() const211 bool AXObject::isTextControl() const
212 {
213 switch (roleValue()) {
214 case TextAreaRole:
215 case TextFieldRole:
216 case ComboBoxRole:
217 return true;
218 default:
219 return false;
220 }
221 }
222
isClickable() const223 bool AXObject::isClickable() const
224 {
225 switch (roleValue()) {
226 case ButtonRole:
227 case CheckBoxRole:
228 case ColorWellRole:
229 case ComboBoxRole:
230 case EditableTextRole:
231 case ImageMapLinkRole:
232 case LinkRole:
233 case ListBoxOptionRole:
234 case MenuButtonRole:
235 case PopUpButtonRole:
236 case RadioButtonRole:
237 case TabRole:
238 case TextAreaRole:
239 case TextFieldRole:
240 case ToggleButtonRole:
241 return true;
242 default:
243 return false;
244 }
245 }
246
isExpanded() const247 bool AXObject::isExpanded() const
248 {
249 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
250 return true;
251
252 return false;
253 }
254
accessibilityIsIgnored() const255 bool AXObject::accessibilityIsIgnored() const
256 {
257 AXObjectCache* cache = axObjectCache();
258 if (!cache)
259 return true;
260
261 AXComputedObjectAttributeCache* attributeCache = cache->computedObjectAttributeCache();
262 if (attributeCache) {
263 AXObjectInclusion ignored = attributeCache->getIgnored(axObjectID());
264 switch (ignored) {
265 case IgnoreObject:
266 return true;
267 case IncludeObject:
268 return false;
269 case DefaultBehavior:
270 break;
271 }
272 }
273
274 bool result = computeAccessibilityIsIgnored();
275
276 if (attributeCache)
277 attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject);
278
279 return result;
280 }
281
accessibilityIsIgnoredByDefault() const282 bool AXObject::accessibilityIsIgnoredByDefault() const
283 {
284 return defaultObjectInclusion() == IgnoreObject;
285 }
286
accessibilityPlatformIncludesObject() const287 AXObjectInclusion AXObject::accessibilityPlatformIncludesObject() const
288 {
289 if (isMenuListPopup() || isMenuListOption())
290 return IncludeObject;
291
292 return DefaultBehavior;
293 }
294
defaultObjectInclusion() const295 AXObjectInclusion AXObject::defaultObjectInclusion() const
296 {
297 if (isInertOrAriaHidden())
298 return IgnoreObject;
299
300 if (isPresentationalChildOfAriaRole())
301 return IgnoreObject;
302
303 return accessibilityPlatformIncludesObject();
304 }
305
isInertOrAriaHidden() const306 bool AXObject::isInertOrAriaHidden() const
307 {
308 bool mightBeInInertSubtree = true;
309 for (const AXObject* object = this; object; object = object->parentObject()) {
310 if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true"))
311 return true;
312 if (mightBeInInertSubtree && object->node()) {
313 if (object->node()->isInert())
314 return true;
315 mightBeInInertSubtree = false;
316 }
317 }
318
319 return false;
320 }
321
lastKnownIsIgnoredValue()322 bool AXObject::lastKnownIsIgnoredValue()
323 {
324 if (m_lastKnownIsIgnoredValue == DefaultBehavior)
325 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject;
326
327 return m_lastKnownIsIgnoredValue == IgnoreObject;
328 }
329
setLastKnownIsIgnoredValue(bool isIgnored)330 void AXObject::setLastKnownIsIgnoredValue(bool isIgnored)
331 {
332 m_lastKnownIsIgnoredValue = isIgnored ? IgnoreObject : IncludeObject;
333 }
334
335 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
orientation() const336 AccessibilityOrientation AXObject::orientation() const
337 {
338 LayoutRect bounds = elementRect();
339 if (bounds.size().width() > bounds.size().height())
340 return AccessibilityOrientationHorizontal;
341 if (bounds.size().height() > bounds.size().width())
342 return AccessibilityOrientationVertical;
343
344 // A tie goes to horizontal.
345 return AccessibilityOrientationHorizontal;
346 }
347
queryString(WebLocalizedString::Name name)348 static String queryString(WebLocalizedString::Name name)
349 {
350 return Locale::defaultLocale().queryString(name);
351 }
352
actionVerb() const353 String AXObject::actionVerb() const
354 {
355 // FIXME: Need to add verbs for select elements.
356
357 switch (roleValue()) {
358 case ButtonRole:
359 case ToggleButtonRole:
360 return queryString(WebLocalizedString::AXButtonActionVerb);
361 case TextFieldRole:
362 case TextAreaRole:
363 return queryString(WebLocalizedString::AXTextFieldActionVerb);
364 case RadioButtonRole:
365 return queryString(WebLocalizedString::AXRadioButtonActionVerb);
366 case CheckBoxRole:
367 return queryString(isChecked() ? WebLocalizedString::AXCheckedCheckBoxActionVerb : WebLocalizedString::AXUncheckedCheckBoxActionVerb);
368 case LinkRole:
369 return queryString(WebLocalizedString::AXLinkActionVerb);
370 case PopUpButtonRole:
371 // FIXME: Implement.
372 return String();
373 case MenuListPopupRole:
374 // FIXME: Implement.
375 return String();
376 default:
377 return emptyString();
378 }
379 }
380
checkboxOrRadioValue() const381 AccessibilityButtonState AXObject::checkboxOrRadioValue() const
382 {
383 // If this is a real checkbox or radio button, AXRenderObject will handle.
384 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used.
385
386 const AtomicString& result = getAttribute(aria_checkedAttr);
387 if (equalIgnoringCase(result, "true"))
388 return ButtonStateOn;
389 if (equalIgnoringCase(result, "mixed"))
390 return ButtonStateMixed;
391
392 return ButtonStateOff;
393 }
394
ariaIsMultiline() const395 bool AXObject::ariaIsMultiline() const
396 {
397 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
398 }
399
ariaPressedIsPresent() const400 bool AXObject::ariaPressedIsPresent() const
401 {
402 return !getAttribute(aria_pressedAttr).isEmpty();
403 }
404
supportsARIAAttributes() const405 bool AXObject::supportsARIAAttributes() const
406 {
407 return supportsARIALiveRegion()
408 || supportsARIADragging()
409 || supportsARIADropping()
410 || supportsARIAFlowTo()
411 || supportsARIAOwns()
412 || hasAttribute(aria_labelAttr);
413 }
414
supportsRangeValue() const415 bool AXObject::supportsRangeValue() const
416 {
417 return isProgressIndicator()
418 || isSlider()
419 || isScrollbar()
420 || isSpinButton();
421 }
422
ariaTreeRows(AccessibilityChildrenVector & result)423 void AXObject::ariaTreeRows(AccessibilityChildrenVector& result)
424 {
425 AccessibilityChildrenVector axChildren = children();
426 unsigned count = axChildren.size();
427 for (unsigned k = 0; k < count; ++k) {
428 AXObject* obj = axChildren[k].get();
429
430 // Add tree items as the rows.
431 if (obj->roleValue() == TreeItemRole)
432 result.append(obj);
433
434 // Now see if this item also has rows hiding inside of it.
435 obj->ariaTreeRows(result);
436 }
437 }
438
supportsARIALiveRegion() const439 bool AXObject::supportsARIALiveRegion() const
440 {
441 const AtomicString& liveRegion = ariaLiveRegionStatus();
442 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
443 }
444
markCachedElementRectDirty() const445 void AXObject::markCachedElementRectDirty() const
446 {
447 for (unsigned i = 0; i < m_children.size(); ++i)
448 m_children[i].get()->markCachedElementRectDirty();
449 }
450
clickPoint()451 IntPoint AXObject::clickPoint()
452 {
453 LayoutRect rect = elementRect();
454 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2));
455 }
456
boundingBoxForQuads(RenderObject * obj,const Vector<FloatQuad> & quads)457 IntRect AXObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads)
458 {
459 ASSERT(obj);
460 if (!obj)
461 return IntRect();
462
463 size_t count = quads.size();
464 if (!count)
465 return IntRect();
466
467 IntRect result;
468 for (size_t i = 0; i < count; ++i) {
469 IntRect r = quads[i].enclosingBoundingBox();
470 if (!r.isEmpty()) {
471 if (obj->style()->hasAppearance())
472 RenderTheme::theme().adjustPaintInvalidationRect(obj, r);
473 result.unite(r);
474 }
475 }
476 return result;
477 }
478
elementAccessibilityHitTest(const IntPoint & point) const479 AXObject* AXObject::elementAccessibilityHitTest(const IntPoint& point) const
480 {
481 // Send the hit test back into the sub-frame if necessary.
482 if (isAttachment()) {
483 Widget* widget = widgetForAttachmentView();
484 // Normalize the point for the widget's bounds.
485 if (widget && widget->isFrameView())
486 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location()));
487 }
488
489 // Check if there are any mock elements that need to be handled.
490 size_t count = m_children.size();
491 for (size_t k = 0; k < count; k++) {
492 if (m_children[k]->isMockObject() && m_children[k]->elementRect().contains(point))
493 return m_children[k]->elementAccessibilityHitTest(point);
494 }
495
496 return const_cast<AXObject*>(this);
497 }
498
children()499 const AXObject::AccessibilityChildrenVector& AXObject::children()
500 {
501 updateChildrenIfNecessary();
502
503 return m_children;
504 }
505
parentObjectUnignored() const506 AXObject* AXObject::parentObjectUnignored() const
507 {
508 AXObject* parent;
509 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
510 }
511
512 return parent;
513 }
514
firstAccessibleObjectFromNode(const Node * node)515 AXObject* AXObject::firstAccessibleObjectFromNode(const Node* node)
516 {
517 if (!node)
518 return 0;
519
520 AXObjectCache* cache = node->document().axObjectCache();
521 AXObject* accessibleObject = cache->getOrCreate(node->renderer());
522 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
523 node = NodeTraversal::next(*node);
524
525 while (node && !node->renderer())
526 node = NodeTraversal::nextSkippingChildren(*node);
527
528 if (!node)
529 return 0;
530
531 accessibleObject = cache->getOrCreate(node->renderer());
532 }
533
534 return accessibleObject;
535 }
536
updateChildrenIfNecessary()537 void AXObject::updateChildrenIfNecessary()
538 {
539 if (!hasChildren())
540 addChildren();
541 }
542
clearChildren()543 void AXObject::clearChildren()
544 {
545 // Some objects have weak pointers to their parents and those associations need to be detached.
546 size_t length = m_children.size();
547 for (size_t i = 0; i < length; i++)
548 m_children[i]->detachFromParent();
549
550 m_children.clear();
551 m_haveChildren = false;
552 }
553
focusedUIElement() const554 AXObject* AXObject::focusedUIElement() const
555 {
556 Document* doc = document();
557 if (!doc)
558 return 0;
559
560 Page* page = doc->page();
561 if (!page)
562 return 0;
563
564 return AXObjectCache::focusedUIElementForPage(page);
565 }
566
document() const567 Document* AXObject::document() const
568 {
569 FrameView* frameView = documentFrameView();
570 if (!frameView)
571 return 0;
572
573 return frameView->frame().document();
574 }
575
documentFrameView() const576 FrameView* AXObject::documentFrameView() const
577 {
578 const AXObject* object = this;
579 while (object && !object->isAXRenderObject())
580 object = object->parentObject();
581
582 if (!object)
583 return 0;
584
585 return object->documentFrameView();
586 }
587
language() const588 String AXObject::language() const
589 {
590 const AtomicString& lang = getAttribute(langAttr);
591 if (!lang.isEmpty())
592 return lang;
593
594 AXObject* parent = parentObject();
595
596 // as a last resort, fall back to the content language specified in the meta tag
597 if (!parent) {
598 Document* doc = document();
599 if (doc)
600 return doc->contentLanguage();
601 return nullAtom;
602 }
603
604 return parent->language();
605 }
606
hasAttribute(const QualifiedName & attribute) const607 bool AXObject::hasAttribute(const QualifiedName& attribute) const
608 {
609 Node* elementNode = node();
610 if (!elementNode)
611 return false;
612
613 if (!elementNode->isElementNode())
614 return false;
615
616 Element* element = toElement(elementNode);
617 return element->fastHasAttribute(attribute);
618 }
619
getAttribute(const QualifiedName & attribute) const620 const AtomicString& AXObject::getAttribute(const QualifiedName& attribute) const
621 {
622 Node* elementNode = node();
623 if (!elementNode)
624 return nullAtom;
625
626 if (!elementNode->isElementNode())
627 return nullAtom;
628
629 Element* element = toElement(elementNode);
630 return element->fastGetAttribute(attribute);
631 }
632
press() const633 bool AXObject::press() const
634 {
635 Element* actionElem = actionElement();
636 if (!actionElem)
637 return false;
638 UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
639 actionElem->accessKeyAction(true);
640 return true;
641 }
642
scrollToMakeVisible() const643 void AXObject::scrollToMakeVisible() const
644 {
645 IntRect objectRect = pixelSnappedIntRect(elementRect());
646 objectRect.setLocation(IntPoint());
647 scrollToMakeVisibleWithSubFocus(objectRect);
648 }
649
650 // This is a 1-dimensional scroll offset helper function that's applied
651 // separately in the horizontal and vertical directions, because the
652 // logic is the same. The goal is to compute the best scroll offset
653 // in order to make an object visible within a viewport.
654 //
655 // In case the whole object cannot fit, you can specify a
656 // subfocus - a smaller region within the object that should
657 // be prioritized. If the whole object can fit, the subfocus is
658 // ignored.
659 //
660 // Example: the viewport is scrolled to the right just enough
661 // that the object is in view.
662 // Before:
663 // +----------Viewport---------+
664 // +---Object---+
665 // +--SubFocus--+
666 //
667 // After:
668 // +----------Viewport---------+
669 // +---Object---+
670 // +--SubFocus--+
671 //
672 // When constraints cannot be fully satisfied, the min
673 // (left/top) position takes precedence over the max (right/bottom).
674 //
675 // Note that the return value represents the ideal new scroll offset.
676 // This may be out of range - the calling function should clip this
677 // to the available range.
computeBestScrollOffset(int currentScrollOffset,int subfocusMin,int subfocusMax,int objectMin,int objectMax,int viewportMin,int viewportMax)678 static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax)
679 {
680 int viewportSize = viewportMax - viewportMin;
681
682 // If the focus size is larger than the viewport size, shrink it in the
683 // direction of subfocus.
684 if (objectMax - objectMin > viewportSize) {
685 // Subfocus must be within focus:
686 subfocusMin = std::max(subfocusMin, objectMin);
687 subfocusMax = std::min(subfocusMax, objectMax);
688
689 // Subfocus must be no larger than the viewport size; favor top/left.
690 if (subfocusMax - subfocusMin > viewportSize)
691 subfocusMax = subfocusMin + viewportSize;
692
693 if (subfocusMin + viewportSize > objectMax) {
694 objectMin = objectMax - viewportSize;
695 } else {
696 objectMin = subfocusMin;
697 objectMax = subfocusMin + viewportSize;
698 }
699 }
700
701 // Exit now if the focus is already within the viewport.
702 if (objectMin - currentScrollOffset >= viewportMin
703 && objectMax - currentScrollOffset <= viewportMax)
704 return currentScrollOffset;
705
706 // Scroll left if we're too far to the right.
707 if (objectMax - currentScrollOffset > viewportMax)
708 return objectMax - viewportMax;
709
710 // Scroll right if we're too far to the left.
711 if (objectMin - currentScrollOffset < viewportMin)
712 return objectMin - viewportMin;
713
714 ASSERT_NOT_REACHED();
715
716 // This shouldn't happen.
717 return currentScrollOffset;
718 }
719
scrollToMakeVisibleWithSubFocus(const IntRect & subfocus) const720 void AXObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const
721 {
722 // Search up the parent chain until we find the first one that's scrollable.
723 AXObject* scrollParent = parentObject();
724 ScrollableArea* scrollableArea = 0;
725 while (scrollParent) {
726 scrollableArea = scrollParent->getScrollableAreaIfScrollable();
727 if (scrollableArea)
728 break;
729 scrollParent = scrollParent->parentObject();
730 }
731 if (!scrollableArea)
732 return;
733
734 IntRect objectRect = pixelSnappedIntRect(elementRect());
735 IntPoint scrollPosition = scrollableArea->scrollPosition();
736 IntRect scrollVisibleRect = scrollableArea->visibleContentRect();
737
738 int desiredX = computeBestScrollOffset(
739 scrollPosition.x(),
740 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(),
741 objectRect.x(), objectRect.maxX(),
742 0, scrollVisibleRect.width());
743 int desiredY = computeBestScrollOffset(
744 scrollPosition.y(),
745 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(),
746 objectRect.y(), objectRect.maxY(),
747 0, scrollVisibleRect.height());
748
749 scrollParent->scrollTo(IntPoint(desiredX, desiredY));
750
751 // Recursively make sure the scroll parent itself is visible.
752 if (scrollParent->parentObject())
753 scrollParent->scrollToMakeVisible();
754 }
755
scrollToGlobalPoint(const IntPoint & globalPoint) const756 void AXObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
757 {
758 // Search up the parent chain and create a vector of all scrollable parent objects
759 // and ending with this object itself.
760 Vector<const AXObject*> objects;
761 AXObject* parentObject;
762 for (parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
763 if (parentObject->getScrollableAreaIfScrollable())
764 objects.prepend(parentObject);
765 }
766 objects.append(this);
767
768 // Start with the outermost scrollable (the main window) and try to scroll the
769 // next innermost object to the given point.
770 int offsetX = 0, offsetY = 0;
771 IntPoint point = globalPoint;
772 size_t levels = objects.size() - 1;
773 for (size_t i = 0; i < levels; i++) {
774 const AXObject* outer = objects[i];
775 const AXObject* inner = objects[i + 1];
776
777 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable();
778
779 IntRect innerRect = inner->isAXScrollView() ? pixelSnappedIntRect(inner->parentObject()->elementRect()) : pixelSnappedIntRect(inner->elementRect());
780 IntRect objectRect = innerRect;
781 IntPoint scrollPosition = scrollableArea->scrollPosition();
782
783 // Convert the object rect into local coordinates.
784 objectRect.move(offsetX, offsetY);
785 if (!outer->isAXScrollView())
786 objectRect.move(scrollPosition.x(), scrollPosition.y());
787
788 int desiredX = computeBestScrollOffset(
789 0,
790 objectRect.x(), objectRect.maxX(),
791 objectRect.x(), objectRect.maxX(),
792 point.x(), point.x());
793 int desiredY = computeBestScrollOffset(
794 0,
795 objectRect.y(), objectRect.maxY(),
796 objectRect.y(), objectRect.maxY(),
797 point.y(), point.y());
798 outer->scrollTo(IntPoint(desiredX, desiredY));
799
800 if (outer->isAXScrollView() && !inner->isAXScrollView()) {
801 // If outer object we just scrolled is a scroll view (main window or iframe) but the
802 // inner object is not, keep track of the coordinate transformation to apply to
803 // future nested calculations.
804 scrollPosition = scrollableArea->scrollPosition();
805 offsetX -= (scrollPosition.x() + point.x());
806 offsetY -= (scrollPosition.y() + point.y());
807 point.move(scrollPosition.x() - innerRect.x(), scrollPosition.y() - innerRect.y());
808 } else if (inner->isAXScrollView()) {
809 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation.
810 offsetX = 0;
811 offsetY = 0;
812 }
813 }
814 }
815
notifyIfIgnoredValueChanged()816 void AXObject::notifyIfIgnoredValueChanged()
817 {
818 bool isIgnored = accessibilityIsIgnored();
819 if (lastKnownIsIgnoredValue() != isIgnored) {
820 axObjectCache()->childrenChanged(parentObject());
821 setLastKnownIsIgnoredValue(isIgnored);
822 }
823 }
824
selectionChanged()825 void AXObject::selectionChanged()
826 {
827 if (AXObject* parent = parentObjectIfExists())
828 parent->selectionChanged();
829 }
830
lineForPosition(const VisiblePosition & visiblePos) const831 int AXObject::lineForPosition(const VisiblePosition& visiblePos) const
832 {
833 if (visiblePos.isNull() || !node())
834 return -1;
835
836 // If the position is not in the same editable region as this AX object, return -1.
837 Node* containerNode = visiblePos.deepEquivalent().containerNode();
838 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode))
839 return -1;
840
841 int lineCount = -1;
842 VisiblePosition currentVisiblePos = visiblePos;
843 VisiblePosition savedVisiblePos;
844
845 // move up until we get to the top
846 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
847 // top document.
848 do {
849 savedVisiblePos = currentVisiblePos;
850 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole);
851 currentVisiblePos = prevVisiblePos;
852 ++lineCount;
853 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos)));
854
855 return lineCount;
856 }
857
isARIAControl(AccessibilityRole ariaRole)858 bool AXObject::isARIAControl(AccessibilityRole ariaRole)
859 {
860 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
861 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
862 }
863
isARIAInput(AccessibilityRole ariaRole)864 bool AXObject::isARIAInput(AccessibilityRole ariaRole)
865 {
866 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
867 }
868
ariaRoleToWebCoreRole(const String & value)869 AccessibilityRole AXObject::ariaRoleToWebCoreRole(const String& value)
870 {
871 ASSERT(!value.isEmpty());
872
873 static const ARIARoleMap* roleMap = createARIARoleMap();
874
875 Vector<String> roleVector;
876 value.split(' ', roleVector);
877 AccessibilityRole role = UnknownRole;
878 unsigned size = roleVector.size();
879 for (unsigned i = 0; i < size; ++i) {
880 String roleName = roleVector[i];
881 role = roleMap->get(roleName);
882 if (role)
883 return role;
884 }
885
886 return role;
887 }
888
buttonRoleType() const889 AccessibilityRole AXObject::buttonRoleType() const
890 {
891 // If aria-pressed is present, then it should be exposed as a toggle button.
892 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
893 if (ariaPressedIsPresent())
894 return ToggleButtonRole;
895 if (ariaHasPopup())
896 return PopUpButtonRole;
897 // We don't contemplate RadioButtonRole, as it depends on the input
898 // type.
899
900 return ButtonRole;
901 }
902
903 } // namespace blink
904