2 * Copyright (C) 2008, 2009, 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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 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.
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.
30 #include "AccessibilityObject.h"
32 #include "AXObjectCache.h"
33 #include "AccessibilityRenderObject.h"
34 #include "AccessibilityScrollView.h"
35 #include "AccessibilityTable.h"
36 #include "DOMTokenList.h"
38 #include "EventHandler.h"
39 #include "FloatRect.h"
40 #include "FocusController.h"
42 #include "FrameLoader.h"
43 #include "FrameSelection.h"
44 #include "HTMLInputElement.h"
45 #include "HTMLNames.h"
46 #include "HTMLParserIdioms.h"
47 #include "HitTestResult.h"
48 #include "LocalizedStrings.h"
49 #include "MainFrame.h"
50 #include "MathMLNames.h"
52 #include "NodeTraversal.h"
54 #include "RenderImage.h"
55 #include "RenderLayer.h"
56 #include "RenderListItem.h"
57 #include "RenderListMarker.h"
58 #include "RenderMenuList.h"
59 #include "RenderText.h"
60 #include "RenderTextControl.h"
61 #include "RenderTheme.h"
62 #include "RenderView.h"
63 #include "RenderWidget.h"
64 #include "RenderedPosition.h"
66 #include "TextCheckerClient.h"
67 #include "TextCheckingHelper.h"
68 #include "TextIterator.h"
69 #include "UserGestureIndicator.h"
70 #include "VisibleUnits.h"
71 #include "htmlediting.h"
72 #include <wtf/NeverDestroyed.h>
73 #include <wtf/StdLibExtras.h>
74 #include <wtf/text/StringBuilder.h>
75 #include <wtf/text/WTFString.h>
76 #include <wtf/unicode/CharacterNames.h>
80 using namespace HTMLNames;
82 AccessibilityObject::AccessibilityObject()
84 , m_haveChildren(false)
86 , m_lastKnownIsIgnoredValue(DefaultBehavior)
87 #if PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY))
93 AccessibilityObject::~AccessibilityObject()
98 void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
100 // Menu close events need to notify the platform. No element is used in the notification because it's a destruction event.
101 if (detachmentType == ElementDestroyed && roleValue() == MenuRole && cache)
102 cache->postNotification(nullptr, &cache->document(), AXObjectCache::AXMenuClosed);
104 // Clear any children and call detachFromParent on them so that
105 // no children are left with dangling pointers to their parent.
108 #if HAVE(ACCESSIBILITY)
113 bool AccessibilityObject::isDetached() const
115 #if HAVE(ACCESSIBILITY)
122 bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria, size_t index)
124 switch (criteria->searchKeys[index]) {
125 // The AnyTypeSearchKey matches any non-null AccessibilityObject.
126 case AnyTypeSearchKey:
129 case BlockquoteSameLevelSearchKey:
130 return criteria->startObject
131 && axObject->isBlockquote()
132 && axObject->blockquoteLevel() == criteria->startObject->blockquoteLevel();
134 case BlockquoteSearchKey:
135 return axObject->isBlockquote();
137 case BoldFontSearchKey:
138 return axObject->hasBoldFont();
140 case ButtonSearchKey:
141 return axObject->isButton();
143 case CheckBoxSearchKey:
144 return axObject->isCheckbox();
146 case ControlSearchKey:
147 return axObject->isControl();
149 case DifferentTypeSearchKey:
150 return criteria->startObject
151 && axObject->roleValue() != criteria->startObject->roleValue();
153 case FontChangeSearchKey:
154 return criteria->startObject
155 && !axObject->hasSameFont(criteria->startObject->renderer());
157 case FontColorChangeSearchKey:
158 return criteria->startObject
159 && !axObject->hasSameFontColor(criteria->startObject->renderer());
162 return axObject->isWebArea();
164 case GraphicSearchKey:
165 return axObject->isImage();
167 case HeadingLevel1SearchKey:
168 return axObject->headingLevel() == 1;
170 case HeadingLevel2SearchKey:
171 return axObject->headingLevel() == 2;
173 case HeadingLevel3SearchKey:
174 return axObject->headingLevel() == 3;
176 case HeadingLevel4SearchKey:
177 return axObject->headingLevel() == 4;
179 case HeadingLevel5SearchKey:
180 return axObject->headingLevel() == 5;
182 case HeadingLevel6SearchKey:
183 return axObject->headingLevel() == 6;
185 case HeadingSameLevelSearchKey:
186 return criteria->startObject
187 && axObject->isHeading()
188 && axObject->headingLevel() == criteria->startObject->headingLevel();
190 case HeadingSearchKey:
191 return axObject->isHeading();
193 case HighlightedSearchKey:
194 return axObject->hasHighlighting();
196 case ItalicFontSearchKey:
197 return axObject->hasItalicFont();
199 case LandmarkSearchKey:
200 return axObject->isLandmark();
203 return axObject->isLink();
206 return axObject->isList();
208 case LiveRegionSearchKey:
209 return axObject->supportsARIALiveRegion();
211 case MisspelledWordSearchKey:
212 return axObject->hasMisspelling();
214 case OutlineSearchKey:
215 return axObject->isTree();
217 case PlainTextSearchKey:
218 return axObject->hasPlainText();
220 case RadioGroupSearchKey:
221 return axObject->isRadioGroup();
223 case SameTypeSearchKey:
224 return criteria->startObject
225 && axObject->roleValue() == criteria->startObject->roleValue();
227 case StaticTextSearchKey:
228 return axObject->isStaticText();
230 case StyleChangeSearchKey:
231 return criteria->startObject
232 && !axObject->hasSameStyle(criteria->startObject->renderer());
234 case TableSameLevelSearchKey:
235 return criteria->startObject
236 && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility()
237 && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel();
240 return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility();
242 case TextFieldSearchKey:
243 return axObject->isTextControl();
245 case UnderlineSearchKey:
246 return axObject->hasUnderline();
248 case UnvisitedLinkSearchKey:
249 return axObject->isUnvisited();
251 case VisitedLinkSearchKey:
252 return axObject->isVisited();
259 bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
261 if (!axObject || !criteria)
264 size_t length = criteria->searchKeys.size();
265 for (size_t i = 0; i < length; ++i) {
266 if (isAccessibilityObjectSearchMatchAtIndex(axObject, criteria, i)) {
267 if (criteria->visibleOnly && !axObject->isOnscreen())
275 bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
277 if (!axObject || !criteria)
280 return axObject->accessibilityObjectContainsText(&criteria->searchText);
283 bool AccessibilityObject::accessibilityObjectContainsText(String* text) const
285 // If text is null or empty we return true.
288 || title().contains(*text, false)
289 || accessibilityDescription().contains(*text, false)
290 || stringValue().contains(*text, false);
293 // ARIA marks elements as having their accessible name derive from either their contents, or their author provide name.
294 bool AccessibilityObject::accessibleNameDerivesFromContent() const
296 // First check for objects specifically identified by ARIA.
297 switch (ariaRoleAttribute()) {
298 case ApplicationAlertRole:
299 case ApplicationAlertDialogRole:
300 case ApplicationDialogRole:
301 case ApplicationLogRole:
302 case ApplicationMarqueeRole:
303 case ApplicationStatusRole:
304 case ApplicationTimerRole:
308 case DocumentArticleRole:
309 case DocumentMathRole:
310 case DocumentNoteRole:
311 case DocumentRegionRole:
318 case LandmarkApplicationRole:
319 case LandmarkBannerRole:
320 case LandmarkComplementaryRole:
321 case LandmarkContentInfoRole:
322 case LandmarkNavigationRole:
323 case LandmarkMainRole:
324 case LandmarkSearchRole:
327 case ProgressIndicatorRole:
346 // Now check for generically derived elements now that we know the element does not match a specific ARIA role.
347 switch (roleValue()) {
357 String AccessibilityObject::computedLabel()
359 // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now.
360 // Also hold onto this object in case updateBackingStore deletes this node.
361 RefPtr<AccessibilityObject> protector(this);
362 updateBackingStore();
363 Vector<AccessibilityText> text;
364 accessibilityText(text);
370 bool AccessibilityObject::isBlockquote() const
372 return roleValue() == BlockquoteRole;
375 bool AccessibilityObject::isTextControl() const
377 switch (roleValue()) {
387 bool AccessibilityObject::isARIATextControl() const
389 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole;
392 bool AccessibilityObject::isLandmark() const
394 AccessibilityRole role = roleValue();
396 return role == LandmarkApplicationRole
397 || role == LandmarkBannerRole
398 || role == LandmarkComplementaryRole
399 || role == LandmarkContentInfoRole
400 || role == LandmarkMainRole
401 || role == LandmarkNavigationRole
402 || role == LandmarkSearchRole;
405 bool AccessibilityObject::hasMisspelling() const
410 Frame* frame = node()->document().frame();
414 Editor& editor = frame->editor();
416 TextCheckerClient* textChecker = editor.textChecker();
420 bool isMisspelled = false;
422 if (unifiedTextCheckerEnabled(frame)) {
423 Vector<TextCheckingResult> results;
424 checkTextOfParagraph(*textChecker, stringValue(), TextCheckingTypeSpelling, results);
425 if (!results.isEmpty())
430 int misspellingLength = 0;
431 int misspellingLocation = -1;
432 textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength);
433 if (misspellingLength || misspellingLocation != -1)
439 int AccessibilityObject::blockquoteLevel() const
442 for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) {
443 if (elementNode->hasTagName(blockquoteTag))
450 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
452 AccessibilityObject* parent;
453 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
459 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
464 AXObjectCache* cache = node->document().axObjectCache();
468 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
469 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
470 node = NodeTraversal::next(*node);
472 while (node && !node->renderer())
473 node = NodeTraversal::nextSkippingChildren(*node);
478 accessibleObject = cache->getOrCreate(node->renderer());
481 return accessibleObject;
484 static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results)
486 // Find the next descendant of this attachment object so search can continue through frames.
487 if (object->isAttachment()) {
488 Widget* widget = object->widgetForAttachmentView();
489 if (!is<FrameView>(widget))
492 Document* document = downcast<FrameView>(*widget).frame().document();
493 if (!document || !document->hasLivingRenderTree())
496 object = object->axObjectCache()->getOrCreate(document);
500 results.append(object);
503 static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results)
505 // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables).
506 // The rows from the table should be queried, since those are direct descendants of the table, and they contain content.
507 const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children();
509 size_t childrenSize = searchChildren.size();
511 size_t startIndex = isForward ? childrenSize : 0;
512 size_t endIndex = isForward ? 0 : childrenSize;
514 size_t searchPosition = startObject ? searchChildren.find(startObject) : WTF::notFound;
515 if (searchPosition != WTF::notFound) {
517 endIndex = searchPosition + 1;
519 endIndex = searchPosition;
522 // This is broken into two statements so that it's easier read.
524 for (size_t i = startIndex; i > endIndex; i--)
525 appendAccessibilityObject(searchChildren.at(i - 1).get(), results);
527 for (size_t i = startIndex; i < endIndex; i++)
528 appendAccessibilityObject(searchChildren.at(i).get(), results);
532 // Returns true if the number of results is now >= the number of results desired.
533 bool AccessibilityObject::objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject* object, AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
535 if (isAccessibilityObjectSearchMatch(object, criteria) && isAccessibilityTextSearchMatch(object, criteria)) {
536 results.append(object);
538 // Enough results were found to stop searching.
539 if (results.size() >= criteria->resultsLimit)
546 void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
553 if (AXObjectCache* cache = axObjectCache())
554 cache->startCachingComputedObjectAttributesUntilTreeMutates();
556 // This search mechanism only searches the elements before/after the starting object.
557 // It does this by stepping up the parent chain and at each level doing a DFS.
559 // If there's no start object, it means we want to search everything.
560 AccessibilityObject* startObject = criteria->startObject;
564 bool isForward = criteria->searchDirection == SearchDirectionNext;
566 // The first iteration of the outer loop will examine the children of the start object for matches. However, when
567 // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an
568 // exception when no start object was specified because we want to search everything regardless of search direction.
569 AccessibilityObject* previousObject = nullptr;
570 if (!isForward && startObject != this) {
571 previousObject = startObject;
572 startObject = startObject->parentObjectUnignored();
575 // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice)
576 for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) {
578 // Only append the children after/before the previous element, so that the search does not check elements that are
579 // already behind/ahead of start element.
580 AccessibilityChildrenVector searchStack;
581 if (!criteria->immediateDescendantsOnly || startObject == this)
582 appendChildrenToArray(startObject, isForward, previousObject, searchStack);
584 // This now does a DFS at the current level of the parent.
585 while (!searchStack.isEmpty()) {
586 AccessibilityObject* searchObject = searchStack.last().get();
587 searchStack.removeLast();
589 if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results))
592 if (!criteria->immediateDescendantsOnly)
593 appendChildrenToArray(searchObject, isForward, 0, searchStack);
596 if (results.size() >= criteria->resultsLimit)
599 // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element.
600 if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results))
603 previousObject = startObject;
607 // Returns the range that is fewer positions away from the reference range.
608 // NOTE: The after range is expected to ACTUALLY be after the reference range and the before
609 // range is expected to ACTUALLY be before. These are not checked for performance reasons.
610 static PassRefPtr<Range> rangeClosestToRange(Range* referenceRange, PassRefPtr<Range> afterRange, PassRefPtr<Range> beforeRange)
612 ASSERT(referenceRange);
613 ASSERT(!afterRange || afterRange->startPosition() >= referenceRange->endPosition());
614 ASSERT(!beforeRange || beforeRange->endPosition() <= referenceRange->startPosition());
616 if (!referenceRange || (!afterRange && !beforeRange))
618 if (afterRange && !beforeRange)
620 if (!afterRange && beforeRange)
623 unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition());
624 unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition());
626 return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange;
629 PassRefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String>& searchStrings) const
631 Frame* frame = this->frame();
638 bool isBackwardSearch = searchDirection == SearchDirectionPrevious;
639 FindOptions findOptions = AtWordStarts | AtWordEnds | CaseInsensitive | StartInSelection;
640 if (isBackwardSearch)
641 findOptions |= Backwards;
643 RefPtr<Range> closestStringRange = nullptr;
644 for (const auto& searchString : searchStrings) {
645 if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) {
646 if (!closestStringRange)
647 closestStringRange = searchStringRange;
649 // If searching backward, use the trailing range edges to correctly determine which
650 // range is closest. Similarly, if searching forward, use the leading range edges.
651 Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition();
652 Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition();
654 int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode();
655 int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode();
656 Node* closestContainerNode = closestStringPosition.containerNode();
657 Node* searchContainerNode = searchStringPosition.containerNode();
659 short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset, ASSERT_NO_EXCEPTION);
660 if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0))
661 closestStringRange = searchStringRange;
665 return closestStringRange;
668 // Returns the range of the entire document if there is no selection.
669 PassRefPtr<Range> AccessibilityObject::selectionRange() const
671 Frame* frame = this->frame();
675 const VisibleSelection& selection = frame->selection().selection();
676 if (!selection.isNone())
677 return selection.firstRange();
679 return Range::create(*frame->document());
682 String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria)
689 Frame* frame = this->frame();
693 AccessibilitySelectTextActivity& activity = criteria->activity;
694 AccessibilitySelectTextAmbiguityResolution& ambiguityResolution = criteria->ambiguityResolution;
695 String& replacementString = criteria->replacementString;
696 Vector<String>& searchStrings = criteria->searchStrings;
698 RefPtr<Range> selectedStringRange = selectionRange();
699 // When starting our search again, make this a zero length range so that search forwards will find this selected range if its appropriate.
700 selectedStringRange->setEnd(selectedStringRange->startContainer(), selectedStringRange->startOffset());
702 RefPtr<Range> closestAfterStringRange = nullptr;
703 RefPtr<Range> closestBeforeStringRange = nullptr;
704 // Search forward if necessary.
705 if (ambiguityResolution == ClosestAfterSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution)
706 closestAfterStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionNext, searchStrings);
707 // Search backward if necessary.
708 if (ambiguityResolution == ClosestBeforeSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution)
709 closestBeforeStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionPrevious, searchStrings);
711 // Determine which candidate is closest to the selection and perform the activity.
712 if (RefPtr<Range> closestStringRange = rangeClosestToRange(selectedStringRange.get(), closestAfterStringRange, closestBeforeStringRange)) {
713 String closestString = closestStringRange->text();
714 bool replaceSelection = false;
715 if (frame->selection().setSelectedRange(closestStringRange.get(), DOWNSTREAM, true)) {
717 case FindAndCapitalize: {
718 replacementString = closestString;
719 makeCapitalized(&replacementString, 0);
720 replaceSelection = true;
723 case FindAndUppercase:
724 replacementString = closestString.upper();
725 replaceSelection = true;
727 case FindAndLowercase:
728 replacementString = closestString.lower();
729 replaceSelection = true;
731 case FindAndReplaceActivity: {
732 replaceSelection = true;
734 // When applying find and replace activities, we want to match the capitalization of the replaced text,
735 // (unless we're replacing with an abbreviation.)
736 String uppercaseReplacementString = replacementString.upper();
737 if (closestString.length() > 0 && replacementString.length() > 2 && replacementString != uppercaseReplacementString) {
738 if (closestString[0] == closestString.upper()[0])
739 makeCapitalized(&replacementString, 0);
741 replacementString = replacementString.lower();
745 case FindAndSelectActivity:
749 // A bit obvious, but worth noting the API contract for this method is that we should
750 // return the replacement string when replacing, but the selected string if not.
751 if (replaceSelection) {
752 frame->editor().replaceSelectionWithText(replacementString, true, true);
753 return replacementString;
756 return closestString;
763 bool AccessibilityObject::hasAttributesRequiredForInclusion() const
765 // These checks are simplified in the interest of execution speed.
766 if (!getAttribute(aria_helpAttr).isEmpty()
767 || !getAttribute(aria_describedbyAttr).isEmpty()
768 || !getAttribute(altAttr).isEmpty()
769 || !getAttribute(titleAttr).isEmpty())
773 if (!getAttribute(MathMLNames::alttextAttr).isEmpty())
780 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
782 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
785 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
787 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
788 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
791 bool AccessibilityObject::isRangeControl() const
793 switch (roleValue()) {
794 case ProgressIndicatorRole:
804 bool AccessibilityObject::isMeter() const
806 #if ENABLE(METER_ELEMENT)
807 RenderObject* renderer = this->renderer();
808 return renderer && renderer->isMeter();
814 IntPoint AccessibilityObject::clickPoint()
816 LayoutRect rect = elementRect();
817 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2));
820 IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads)
827 for (const auto& quad : quads) {
828 FloatRect r = quad.enclosingBoundingBox();
830 if (obj->style().hasAppearance())
831 obj->theme().adjustRepaintRect(*obj, r);
835 return snappedIntRect(LayoutRect(result));
838 bool AccessibilityObject::press()
840 // The presence of the actionElement will confirm whether we should even attempt a press.
841 Element* actionElem = actionElement();
844 if (Frame* f = actionElem->document().frame())
845 f->loader().resetMultipleFormSubmissionProtection();
847 // Hit test at this location to determine if there is a sub-node element that should act
848 // as the target of the action.
849 Element* hitTestElement = nullptr;
850 Document* document = this->document();
852 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest);
853 HitTestResult hitTestResult(clickPoint());
854 document->renderView()->hitTest(request, hitTestResult);
855 if (hitTestResult.innerNode()) {
856 Node* innerNode = hitTestResult.innerNode()->deprecatedShadowAncestorNode();
857 if (is<Element>(*innerNode))
858 hitTestElement = downcast<Element>(innerNode);
860 hitTestElement = innerNode->parentElement();
865 // Prefer the actionElement instead of this node, if the actionElement is inside this node.
866 Element* pressElement = this->element();
867 if (!pressElement || actionElem->isDescendantOf(pressElement))
868 pressElement = actionElem;
870 // Prefer the hit test element, if it is inside the target element.
871 if (hitTestElement && hitTestElement->isDescendantOf(pressElement))
872 pressElement = hitTestElement;
874 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document);
876 bool dispatchedTouchEvent = dispatchTouchEvent();
877 if (!dispatchedTouchEvent)
878 pressElement->accessKeyAction(true);
883 bool AccessibilityObject::dispatchTouchEvent()
885 bool handled = false;
886 #if ENABLE(IOS_TOUCH_EVENTS)
887 MainFrame* frame = mainFrame();
891 frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint());
896 Frame* AccessibilityObject::frame() const
898 Node* node = this->node();
902 return node->document().frame();
905 MainFrame* AccessibilityObject::mainFrame() const
907 Document* document = topDocument();
911 Frame* frame = document->frame();
915 return &frame->mainFrame();
918 Document* AccessibilityObject::topDocument() const
922 return &document()->topDocument();
925 String AccessibilityObject::language() const
927 const AtomicString& lang = getAttribute(langAttr);
931 AccessibilityObject* parent = parentObject();
933 // as a last resort, fall back to the content language specified in the meta tag
935 Document* doc = document();
937 return doc->contentLanguage();
941 return parent->language();
944 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
946 if (visiblePos1.isNull() || visiblePos2.isNull())
947 return VisiblePositionRange();
949 // If there's no common tree scope between positions, return early.
950 if (!commonTreeScope(visiblePos1.deepEquivalent().deprecatedNode(), visiblePos2.deepEquivalent().deprecatedNode()))
951 return VisiblePositionRange();
953 VisiblePosition startPos;
954 VisiblePosition endPos;
957 // upstream is ordered before downstream for the same position
958 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
959 alreadyInOrder = false;
961 // use selection order to see if the positions are in order
963 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
965 if (alreadyInOrder) {
966 startPos = visiblePos1;
967 endPos = visiblePos2;
969 startPos = visiblePos2;
970 endPos = visiblePos1;
973 return VisiblePositionRange(startPos, endPos);
976 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
978 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
979 VisiblePosition endPosition = endOfWord(startPosition);
980 return VisiblePositionRange(startPosition, endPosition);
983 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
985 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
986 VisiblePosition endPosition = endOfWord(startPosition);
987 return VisiblePositionRange(startPosition, endPosition);
990 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
992 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
993 // So let's update the position to include that.
994 VisiblePosition tempPosition;
995 VisiblePosition startPosition = visiblePosition;
997 tempPosition = startPosition.previous();
998 if (tempPosition.isNull())
1000 Position p = tempPosition.deepEquivalent();
1001 RenderObject* renderer = p.deprecatedNode()->renderer();
1002 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
1004 if (!RenderedPosition(tempPosition).isNull())
1006 startPosition = tempPosition;
1009 return startPosition;
1012 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1014 if (visiblePos.isNull())
1015 return VisiblePositionRange();
1017 // make a caret selection for the position before marker position (to make sure
1018 // we move off of a line start)
1019 VisiblePosition prevVisiblePos = visiblePos.previous();
1020 if (prevVisiblePos.isNull())
1021 return VisiblePositionRange();
1023 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1025 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
1026 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
1027 // since floating object doesn't really belong to any line.
1028 // This check will reposition the marker before the floating object, to ensure we get a line start.
1029 if (startPosition.isNull()) {
1030 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1031 prevVisiblePos = prevVisiblePos.previous();
1032 startPosition = startOfLine(prevVisiblePos);
1035 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1037 VisiblePosition endPosition = endOfLine(prevVisiblePos);
1038 return VisiblePositionRange(startPosition, endPosition);
1041 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1043 if (visiblePos.isNull())
1044 return VisiblePositionRange();
1046 // make sure we move off of a line end
1047 VisiblePosition nextVisiblePos = visiblePos.next();
1048 if (nextVisiblePos.isNull())
1049 return VisiblePositionRange();
1051 VisiblePosition startPosition = startOfLine(nextVisiblePos);
1053 // fetch for a valid line start position
1054 if (startPosition.isNull()) {
1055 startPosition = visiblePos;
1056 nextVisiblePos = nextVisiblePos.next();
1058 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1060 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1062 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1063 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
1064 // return null for position by a floating object, since floating object doesn't really belong to any line.
1065 // This check will reposition the marker after the floating object, to ensure we get a line end.
1066 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1067 nextVisiblePos = nextVisiblePos.next();
1068 endPosition = endOfLine(nextVisiblePos);
1071 return VisiblePositionRange(startPosition, endPosition);
1074 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
1076 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1077 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1078 VisiblePosition startPosition = startOfSentence(visiblePos);
1079 VisiblePosition endPosition = endOfSentence(startPosition);
1080 return VisiblePositionRange(startPosition, endPosition);
1083 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
1085 VisiblePosition startPosition = startOfParagraph(visiblePos);
1086 VisiblePosition endPosition = endOfParagraph(startPosition);
1087 return VisiblePositionRange(startPosition, endPosition);
1090 static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos)
1092 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1093 RenderObject* startRenderer = renderer;
1094 RenderStyle* style = &renderer->style();
1096 // traverse backward by renderer to look for style change
1097 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
1098 // skip non-leaf nodes
1099 if (r->firstChildSlow())
1102 // stop at style change
1103 if (&r->style() != style)
1110 return firstPositionInOrBeforeNode(startRenderer->node());
1113 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
1115 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1116 RenderObject* endRenderer = renderer;
1117 const RenderStyle& style = renderer->style();
1119 // traverse forward by renderer to look for style change
1120 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
1121 // skip non-leaf nodes
1122 if (r->firstChildSlow())
1125 // stop at style change
1126 if (&r->style() != &style)
1133 return lastPositionInOrAfterNode(endRenderer->node());
1136 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
1138 if (visiblePos.isNull())
1139 return VisiblePositionRange();
1141 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
1144 // NOTE: Consider providing this utility method as AX API
1145 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
1147 unsigned textLength = getLengthForTextRange();
1148 if (range.start + range.length > textLength)
1149 return VisiblePositionRange();
1151 VisiblePosition startPosition = visiblePositionForIndex(range.start);
1152 startPosition.setAffinity(DOWNSTREAM);
1153 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
1154 return VisiblePositionRange(startPosition, endPosition);
1157 VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const
1159 VisiblePosition startPosition = startOfLine(visiblePosition);
1160 VisiblePosition endPosition = endOfLine(visiblePosition);
1161 return VisiblePositionRange(startPosition, endPosition);
1164 static bool replacedNodeNeedsCharacter(Node* replacedNode)
1166 // we should always be given a rendered node and a replaced node, but be safe
1167 // replaced nodes are either attachments (widgets) or images
1168 if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode())
1171 // create an AX object, but skip it if it is not supposed to be seen
1172 AccessibilityObject* object = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode);
1173 if (object->accessibilityIsIgnored())
1179 // Finds a RenderListItem parent give a node.
1180 static RenderListItem* renderListItemContainerForNode(Node* node)
1182 for (; node; node = node->parentNode()) {
1183 RenderBoxModelObject* renderer = node->renderBoxModelObject();
1184 if (is<RenderListItem>(renderer))
1185 return downcast<RenderListItem>(renderer);
1190 // Returns the text associated with a list marker if this node is contained within a list item.
1191 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
1193 // If the range does not contain the start of the line, the list marker text should not be included.
1194 if (!isStartOfLine(visiblePositionStart))
1197 RenderListItem* listItem = renderListItemContainerForNode(node);
1201 // If this is in a list item, we need to manually add the text for the list marker
1202 // because a RenderListMarker does not have a Node equivalent and thus does not appear
1203 // when iterating text.
1204 return listItem->markerTextWithSuffix();
1207 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1209 if (visiblePositionRange.isNull())
1212 StringBuilder builder;
1213 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1214 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1215 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1216 if (it.text().length()) {
1217 // Add a textual representation for list marker text.
1218 builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start));
1219 it.appendTextToStringBuilder(builder);
1221 // locate the node and starting offset for this replaced range
1222 Node* node = it.range()->startContainer();
1223 ASSERT(node == it.range()->endContainer());
1224 int offset = it.range()->startOffset();
1225 if (replacedNodeNeedsCharacter(node->traverseToChildAt(offset)))
1226 builder.append(objectReplacementCharacter);
1230 return builder.toString();
1233 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1235 // FIXME: Multi-byte support
1236 if (visiblePositionRange.isNull())
1240 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1241 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1242 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1243 if (it.text().length())
1244 length += it.text().length();
1246 // locate the node and starting offset for this replaced range
1248 Node* node = it.range()->startContainer(exception);
1249 ASSERT(node == it.range()->endContainer(exception));
1250 int offset = it.range()->startOffset(exception);
1252 if (replacedNodeNeedsCharacter(node->traverseToChildAt(offset)))
1260 VisiblePosition AccessibilityObject::visiblePositionForBounds(const IntRect& rect, AccessibilityVisiblePositionForBounds visiblePositionForBounds) const
1263 return VisiblePosition();
1265 MainFrame* mainFrame = this->mainFrame();
1267 return VisiblePosition();
1269 // FIXME: Add support for right-to-left languages.
1270 IntPoint corner = (visiblePositionForBounds == FirstVisiblePositionForBounds) ? rect.minXMinYCorner() : rect.maxXMaxYCorner();
1271 VisiblePosition position = mainFrame->visiblePositionForPoint(corner);
1273 if (rect.contains(position.absoluteCaretBounds().center()))
1276 // If the initial position is located outside the bounds adjust it incrementally as needed.
1277 VisiblePosition nextPosition = position.next();
1278 VisiblePosition previousPosition = position.previous();
1279 while (nextPosition.isNotNull() || previousPosition.isNotNull()) {
1280 if (rect.contains(nextPosition.absoluteCaretBounds().center()))
1281 return nextPosition;
1282 if (rect.contains(previousPosition.absoluteCaretBounds().center()))
1283 return previousPosition;
1285 nextPosition = nextPosition.next();
1286 previousPosition = previousPosition.previous();
1289 return VisiblePosition();
1292 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
1294 if (visiblePos.isNull())
1295 return VisiblePosition();
1297 // make sure we move off of a word end
1298 VisiblePosition nextVisiblePos = visiblePos.next();
1299 if (nextVisiblePos.isNull())
1300 return VisiblePosition();
1302 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
1305 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
1307 if (visiblePos.isNull())
1308 return VisiblePosition();
1310 // make sure we move off of a word start
1311 VisiblePosition prevVisiblePos = visiblePos.previous();
1312 if (prevVisiblePos.isNull())
1313 return VisiblePosition();
1315 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
1318 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
1320 if (visiblePos.isNull())
1321 return VisiblePosition();
1323 // to make sure we move off of a line end
1324 VisiblePosition nextVisiblePos = visiblePos.next();
1325 if (nextVisiblePos.isNull())
1326 return VisiblePosition();
1328 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1330 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1331 // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null.
1332 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1333 nextVisiblePos = nextVisiblePos.next();
1334 endPosition = endOfLine(nextVisiblePos);
1340 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
1342 if (visiblePos.isNull())
1343 return VisiblePosition();
1345 // make sure we move off of a line start
1346 VisiblePosition prevVisiblePos = visiblePos.previous();
1347 if (prevVisiblePos.isNull())
1348 return VisiblePosition();
1350 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1352 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
1353 // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null.
1354 if (startPosition.isNull()) {
1355 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1356 prevVisiblePos = prevVisiblePos.previous();
1357 startPosition = startOfLine(prevVisiblePos);
1360 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1362 return startPosition;
1365 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
1367 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1368 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1369 if (visiblePos.isNull())
1370 return VisiblePosition();
1372 // make sure we move off of a sentence end
1373 VisiblePosition nextVisiblePos = visiblePos.next();
1374 if (nextVisiblePos.isNull())
1375 return VisiblePosition();
1377 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
1378 // see this empty line. Instead, return the end position of the empty line.
1379 VisiblePosition endPosition;
1381 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
1382 if (lineString.isEmpty())
1383 endPosition = nextVisiblePos;
1385 endPosition = endOfSentence(nextVisiblePos);
1390 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
1392 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1393 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1394 if (visiblePos.isNull())
1395 return VisiblePosition();
1397 // make sure we move off of a sentence start
1398 VisiblePosition previousVisiblePos = visiblePos.previous();
1399 if (previousVisiblePos.isNull())
1400 return VisiblePosition();
1402 // treat empty line as a separate sentence.
1403 VisiblePosition startPosition;
1405 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
1406 if (lineString.isEmpty())
1407 startPosition = previousVisiblePos;
1409 startPosition = startOfSentence(previousVisiblePos);
1411 return startPosition;
1414 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
1416 if (visiblePos.isNull())
1417 return VisiblePosition();
1419 // make sure we move off of a paragraph end
1420 VisiblePosition nextPos = visiblePos.next();
1421 if (nextPos.isNull())
1422 return VisiblePosition();
1424 return endOfParagraph(nextPos);
1427 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
1429 if (visiblePos.isNull())
1430 return VisiblePosition();
1432 // make sure we move off of a paragraph start
1433 VisiblePosition previousPos = visiblePos.previous();
1434 if (previousPos.isNull())
1435 return VisiblePosition();
1437 return startOfParagraph(previousPos);
1440 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
1442 if (visiblePos.isNull())
1445 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1449 return obj->document().axObjectCache()->getOrCreate(obj);
1452 // If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
1453 // This only returns true if this is the element that actually has the contentEditable attribute set.
1454 bool AccessibilityObject::hasContentEditableAttributeSet() const
1456 return contentEditableAttributeIsEnabled(element());
1459 bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element)
1464 const AtomicString& contentEditableValue = element->fastGetAttribute(contenteditableAttr);
1465 if (contentEditableValue.isNull())
1468 // Both "true" (case-insensitive) and the empty string count as true.
1469 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
1472 #if HAVE(ACCESSIBILITY)
1473 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
1475 if (visiblePos.isNull() || !node())
1478 // If the position is not in the same editable region as this AX object, return -1.
1479 Node* containerNode = visiblePos.deepEquivalent().containerNode();
1480 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode))
1484 VisiblePosition currentVisiblePos = visiblePos;
1485 VisiblePosition savedVisiblePos;
1487 // move up until we get to the top
1488 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
1491 savedVisiblePos = currentVisiblePos;
1492 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole);
1493 currentVisiblePos = prevVisiblePos;
1495 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos)));
1501 // NOTE: Consider providing this utility method as AX API
1502 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
1504 int index1 = index(positionRange.start);
1505 int index2 = index(positionRange.end);
1506 if (index1 < 0 || index2 < 0 || index1 > index2)
1507 return PlainTextRange();
1509 return PlainTextRange(index1, index2 - index1);
1512 // The composed character range in the text associated with this accessibility object that
1513 // is specified by the given screen coordinates. This parameterized attribute returns the
1514 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
1515 // screen coordinates.
1516 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
1517 // an error in that case. We return textControl->text().length(), 1. Does this matter?
1518 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
1520 int i = index(visiblePositionForPoint(point));
1522 return PlainTextRange();
1524 return PlainTextRange(i, 1);
1527 // Given a character index, the range of text associated with this accessibility object
1528 // over which the style in effect at that character index applies.
1529 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
1531 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
1532 return plainTextRangeForVisiblePositionRange(range);
1535 // Given an indexed character, the line number of the text associated with this accessibility
1536 // object that contains the character.
1537 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
1539 return lineForPosition(visiblePositionForIndex(index, false));
1542 #if HAVE(ACCESSIBILITY)
1543 void AccessibilityObject::updateBackingStore()
1545 // Updating the layout may delete this object.
1546 RefPtr<AccessibilityObject> protector(this);
1548 if (Document* document = this->document()) {
1549 if (!document->view()->isInLayout())
1550 document->updateLayoutIgnorePendingStylesheets();
1553 updateChildrenIfNecessary();
1557 ScrollView* AccessibilityObject::scrollViewAncestor() const
1559 for (const AccessibilityObject* scrollParent = this; scrollParent; scrollParent = scrollParent->parentObject()) {
1560 if (is<AccessibilityScrollView>(*scrollParent))
1561 return downcast<AccessibilityScrollView>(*scrollParent).scrollView();
1567 Document* AccessibilityObject::document() const
1569 FrameView* frameView = documentFrameView();
1573 return frameView->frame().document();
1576 Page* AccessibilityObject::page() const
1578 Document* document = this->document();
1581 return document->page();
1584 FrameView* AccessibilityObject::documentFrameView() const
1586 const AccessibilityObject* object = this;
1587 while (object && !object->isAccessibilityRenderObject())
1588 object = object->parentObject();
1593 return object->documentFrameView();
1596 #if HAVE(ACCESSIBILITY)
1597 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool updateChildrenIfNeeded)
1599 if (updateChildrenIfNeeded)
1600 updateChildrenIfNecessary();
1606 void AccessibilityObject::updateChildrenIfNecessary()
1608 if (!hasChildren()) {
1609 // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time.
1610 AXAttributeCacheEnabler enableCache(axObjectCache());
1615 void AccessibilityObject::clearChildren()
1617 // Some objects have weak pointers to their parents and those associations need to be detached.
1618 for (const auto& child : m_children)
1619 child->detachFromParent();
1622 m_haveChildren = false;
1625 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
1627 RenderObject* obj = node->renderer();
1631 RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj);
1632 Element* anchor = axObj->anchorElement();
1636 RenderObject* anchorRenderer = anchor->renderer();
1637 if (!anchorRenderer)
1640 return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer);
1643 AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node)
1648 RenderObject* renderObject = node->renderer();
1652 AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject);
1653 for (; axObject && axObject->roleValue() != HeadingRole; axObject = axObject->parentObject()) { }
1658 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
1660 for (const auto& child : children()) {
1661 // Add tree items as the rows.
1662 if (child->roleValue() == TreeItemRole)
1663 result.append(child);
1665 // Now see if this item also has rows hiding inside of it.
1666 child->ariaTreeRows(result);
1670 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
1672 // The ARIA tree item content are the item that are not other tree items or their containing groups.
1673 for (const auto& child : children()) {
1674 AccessibilityRole role = child->roleValue();
1675 if (role == TreeItemRole || role == GroupRole)
1678 result.append(child);
1682 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
1684 for (const auto& obj : children()) {
1685 // Add tree items as the rows.
1686 if (obj->roleValue() == TreeItemRole)
1688 // If it's not a tree item, then descend into the group to find more tree items.
1690 obj->ariaTreeRows(result);
1694 const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role)
1697 case ApplicationAlertDialogRole:
1698 case ApplicationAlertRole:
1699 return ASCIILiteral("assertive");
1700 case ApplicationLogRole:
1701 case ApplicationStatusRole:
1702 return ASCIILiteral("polite");
1703 case ApplicationTimerRole:
1704 case ApplicationMarqueeRole:
1705 return ASCIILiteral("off");
1711 #if HAVE(ACCESSIBILITY)
1712 const String& AccessibilityObject::actionVerb() const
1715 // FIXME: Need to add verbs for select elements.
1716 static NeverDestroyed<const String> buttonAction(AXButtonActionVerb());
1717 static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb());
1718 static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb());
1719 static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb());
1720 static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb());
1721 static NeverDestroyed<const String> linkAction(AXLinkActionVerb());
1722 static NeverDestroyed<const String> menuListAction(AXMenuListActionVerb());
1723 static NeverDestroyed<const String> menuListPopupAction(AXMenuListPopupActionVerb());
1724 static NeverDestroyed<const String> listItemAction(AXListItemActionVerb());
1726 switch (roleValue()) {
1728 case ToggleButtonRole:
1729 return buttonAction;
1732 return textFieldAction;
1733 case RadioButtonRole:
1734 return radioButtonAction;
1736 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
1738 case WebCoreLinkRole:
1740 case PopUpButtonRole:
1741 return menuListAction;
1742 case MenuListPopupRole:
1743 return menuListPopupAction;
1745 return listItemAction;
1755 bool AccessibilityObject::ariaIsMultiline() const
1757 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
1760 String AccessibilityObject::invalidStatus() const
1762 String grammarValue = ASCIILiteral("grammar");
1763 String falseValue = ASCIILiteral("false");
1764 String spellingValue = ASCIILiteral("spelling");
1765 String trueValue = ASCIILiteral("true");
1766 String undefinedValue = ASCIILiteral("undefined");
1768 // aria-invalid can return false (default), grammar, spelling, or true.
1769 String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr));
1771 // If "false", "undefined" [sic, string value], empty, or missing, return "false".
1772 if (ariaInvalid.isEmpty() || ariaInvalid == falseValue || ariaInvalid == undefinedValue)
1774 // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0...
1775 // ...for @aria-invalid are "grammar" and "spelling".
1776 if (ariaInvalid == grammarValue)
1777 return grammarValue;
1778 if (ariaInvalid == spellingValue)
1779 return spellingValue;
1780 // Any other non empty string should be treated as "true".
1784 bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const
1786 Node* node = this->node();
1787 return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName);
1790 bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const
1792 Node* node = this->node();
1793 if (!is<Element>(node))
1796 return downcast<Element>(*node).fastHasAttribute(attribute);
1799 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
1801 if (Element* element = this->element())
1802 return element->fastGetAttribute(attribute);
1806 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
1807 AccessibilityOrientation AccessibilityObject::orientation() const
1809 LayoutRect bounds = elementRect();
1810 if (bounds.size().width() > bounds.size().height())
1811 return AccessibilityOrientationHorizontal;
1812 if (bounds.size().height() > bounds.size().width())
1813 return AccessibilityOrientationVertical;
1815 // A tie goes to horizontal.
1816 return AccessibilityOrientationHorizontal;
1819 bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const
1821 if (!axObject || !axObject->hasChildren())
1824 for (const AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1825 if (parent == axObject)
1831 bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const
1836 return this == axObject || axObject->isDescendantOfObject(this);
1839 AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const
1841 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1842 if (child->renderer() && child->renderer()->isAnonymousBlock())
1848 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
1849 typedef HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>> ARIAReverseRoleMap;
1851 static ARIARoleMap* gAriaRoleMap = nullptr;
1852 static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr;
1856 AccessibilityRole webcoreRole;
1859 static void initializeRoleMap()
1863 ASSERT(!gAriaReverseRoleMap);
1865 const RoleEntry roles[] = {
1866 { "alert", ApplicationAlertRole },
1867 { "alertdialog", ApplicationAlertDialogRole },
1868 { "application", LandmarkApplicationRole },
1869 { "article", DocumentArticleRole },
1870 { "banner", LandmarkBannerRole },
1871 { "button", ButtonRole },
1872 { "checkbox", CheckBoxRole },
1873 { "complementary", LandmarkComplementaryRole },
1874 { "contentinfo", LandmarkContentInfoRole },
1875 { "dialog", ApplicationDialogRole },
1876 { "directory", DirectoryRole },
1877 { "grid", TableRole },
1878 { "gridcell", CellRole },
1879 { "columnheader", ColumnHeaderRole },
1880 { "combobox", ComboBoxRole },
1881 { "definition", DefinitionRole },
1882 { "document", DocumentRole },
1883 { "form", FormRole },
1884 { "rowheader", RowHeaderRole },
1885 { "group", GroupRole },
1886 { "heading", HeadingRole },
1887 { "img", ImageRole },
1888 { "link", WebCoreLinkRole },
1889 { "list", ListRole },
1890 { "listitem", ListItemRole },
1891 { "listbox", ListBoxRole },
1892 { "log", ApplicationLogRole },
1893 // "option" isn't here because it may map to different roles depending on the parent element's role
1894 { "main", LandmarkMainRole },
1895 { "marquee", ApplicationMarqueeRole },
1896 { "math", DocumentMathRole },
1897 { "menu", MenuRole },
1898 { "menubar", MenuBarRole },
1899 { "menuitem", MenuItemRole },
1900 { "menuitemcheckbox", MenuItemCheckboxRole },
1901 { "menuitemradio", MenuItemRadioRole },
1902 { "none", PresentationalRole },
1903 { "note", DocumentNoteRole },
1904 { "navigation", LandmarkNavigationRole },
1905 { "option", ListBoxOptionRole },
1906 { "presentation", PresentationalRole },
1907 { "progressbar", ProgressIndicatorRole },
1908 { "radio", RadioButtonRole },
1909 { "radiogroup", RadioGroupRole },
1910 { "region", DocumentRegionRole },
1912 { "scrollbar", ScrollBarRole },
1913 { "search", LandmarkSearchRole },
1914 { "separator", SplitterRole },
1915 { "slider", SliderRole },
1916 { "spinbutton", SpinButtonRole },
1917 { "status", ApplicationStatusRole },
1919 { "tablist", TabListRole },
1920 { "tabpanel", TabPanelRole },
1921 { "text", StaticTextRole },
1922 { "textbox", TextAreaRole },
1923 { "timer", ApplicationTimerRole },
1924 { "toolbar", ToolbarRole },
1925 { "tooltip", UserInterfaceTooltipRole },
1926 { "tree", TreeRole },
1927 { "treegrid", TreeGridRole },
1928 { "treeitem", TreeItemRole }
1931 gAriaRoleMap = new ARIARoleMap;
1932 gAriaReverseRoleMap = new ARIAReverseRoleMap;
1933 size_t roleLength = WTF_ARRAY_LENGTH(roles);
1934 for (size_t i = 0; i < roleLength; ++i) {
1935 gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
1936 gAriaReverseRoleMap->set(roles[i].webcoreRole, roles[i].ariaRole);
1940 static ARIARoleMap& ariaRoleMap()
1942 initializeRoleMap();
1943 return *gAriaRoleMap;
1946 static ARIAReverseRoleMap& reverseAriaRoleMap()
1948 initializeRoleMap();
1949 return *gAriaReverseRoleMap;
1952 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
1954 ASSERT(!value.isEmpty());
1956 Vector<String> roleVector;
1957 value.split(' ', roleVector);
1958 AccessibilityRole role = UnknownRole;
1959 for (const auto& roleName : roleVector) {
1960 role = ariaRoleMap().get(roleName);
1968 String AccessibilityObject::computedRoleString() const
1970 // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296
1971 AccessibilityRole role = roleValue();
1972 if (role == HorizontalRuleRole)
1973 role = SplitterRole;
1975 return reverseAriaRoleMap().get(role);
1978 bool AccessibilityObject::hasHighlighting() const
1980 for (Node* node = this->node(); node; node = node->parentNode()) {
1981 if (node->hasTagName(markTag))
1988 static bool nodeHasPresentationRole(Node* node)
1990 return nodeHasRole(node, "presentation") || nodeHasRole(node, "none");
1993 bool AccessibilityObject::supportsPressAction() const
1998 Element* actionElement = this->actionElement();
2002 // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action.
2003 return !nodeHasPresentationRole(actionElement);
2006 bool AccessibilityObject::supportsDatetimeAttribute() const
2008 return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag);
2011 Element* AccessibilityObject::element() const
2013 Node* node = this->node();
2014 if (is<Element>(node))
2015 return downcast<Element>(node);
2019 bool AccessibilityObject::isValueAutofilled() const
2021 if (!isNativeTextControl())
2024 Node* node = this->node();
2025 if (!node || !is<HTMLInputElement>(*node))
2028 return downcast<HTMLInputElement>(*node).isAutofilled();
2031 const AtomicString& AccessibilityObject::placeholderValue() const
2033 const AtomicString& placeholder = getAttribute(placeholderAttr);
2034 if (!placeholder.isEmpty())
2040 bool AccessibilityObject::isInsideARIALiveRegion() const
2042 if (supportsARIALiveRegion())
2045 for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
2046 if (axParent->supportsARIALiveRegion())
2053 bool AccessibilityObject::supportsARIAAttributes() const
2055 // This returns whether the element supports any global ARIA attributes.
2056 return supportsARIALiveRegion()
2057 || supportsARIADragging()
2058 || supportsARIADropping()
2059 || supportsARIAFlowTo()
2060 || supportsARIAOwns()
2061 || hasAttribute(aria_atomicAttr)
2062 || hasAttribute(aria_busyAttr)
2063 || hasAttribute(aria_controlsAttr)
2064 || hasAttribute(aria_describedbyAttr)
2065 || hasAttribute(aria_disabledAttr)
2066 || hasAttribute(aria_haspopupAttr)
2067 || hasAttribute(aria_invalidAttr)
2068 || hasAttribute(aria_labelAttr)
2069 || hasAttribute(aria_labelledbyAttr)
2070 || hasAttribute(aria_relevantAttr);
2073 bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomicString& liveRegionStatus)
2075 return equalIgnoringCase(liveRegionStatus, "polite") || equalIgnoringCase(liveRegionStatus, "assertive");
2078 bool AccessibilityObject::supportsARIALiveRegion() const
2080 return liveRegionStatusIsEnabled(ariaLiveRegionStatus());
2083 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const
2085 // Send the hit test back into the sub-frame if necessary.
2086 if (isAttachment()) {
2087 Widget* widget = widgetForAttachmentView();
2088 // Normalize the point for the widget's bounds.
2089 if (widget && widget->isFrameView()) {
2090 if (AXObjectCache* cache = axObjectCache())
2091 return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location()));
2095 // Check if there are any mock elements that need to be handled.
2096 for (const auto& child : m_children) {
2097 if (child->isMockObject() && child->elementRect().contains(point))
2098 return child->elementAccessibilityHitTest(point);
2101 return const_cast<AccessibilityObject*>(this);
2104 AXObjectCache* AccessibilityObject::axObjectCache() const
2106 Document* doc = document();
2108 return doc->axObjectCache();
2112 AccessibilityObject* AccessibilityObject::focusedUIElement() const
2114 Document* doc = document();
2118 Page* page = doc->page();
2122 return AXObjectCache::focusedUIElementForPage(page);
2125 AccessibilitySortDirection AccessibilityObject::sortDirection() const
2127 const AtomicString& sortAttribute = getAttribute(aria_sortAttr);
2128 if (equalIgnoringCase(sortAttribute, "ascending"))
2129 return SortDirectionAscending;
2130 if (equalIgnoringCase(sortAttribute, "descending"))
2131 return SortDirectionDescending;
2132 if (equalIgnoringCase(sortAttribute, "other"))
2133 return SortDirectionOther;
2135 return SortDirectionNone;
2138 bool AccessibilityObject::supportsRangeValue() const
2140 return isProgressIndicator()
2146 bool AccessibilityObject::supportsARIASetSize() const
2148 return hasAttribute(aria_setsizeAttr);
2151 bool AccessibilityObject::supportsARIAPosInSet() const
2153 return hasAttribute(aria_posinsetAttr);
2156 int AccessibilityObject::ariaSetSize() const
2158 return getAttribute(aria_setsizeAttr).toInt();
2161 int AccessibilityObject::ariaPosInSet() const
2163 return getAttribute(aria_posinsetAttr).toInt();
2166 String AccessibilityObject::identifierAttribute() const
2168 return getAttribute(idAttr);
2171 void AccessibilityObject::classList(Vector<String>& classList) const
2173 Node* node = this->node();
2174 if (!is<Element>(node))
2177 Element* element = downcast<Element>(node);
2178 DOMTokenList& list = element->classList();
2179 unsigned length = list.length();
2180 for (unsigned k = 0; k < length; k++)
2181 classList.append(list.item(k).string());
2185 bool AccessibilityObject::supportsARIAExpanded() const
2187 // Undefined values should not result in this attribute being exposed to ATs according to ARIA.
2188 const AtomicString& expanded = getAttribute(aria_expandedAttr);
2189 if (equalIgnoringCase(expanded, "true") || equalIgnoringCase(expanded, "false"))
2191 switch (roleValue()) {
2193 case DisclosureTriangleRole:
2200 bool AccessibilityObject::isExpanded() const
2202 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
2208 bool AccessibilityObject::supportsChecked() const
2210 switch (roleValue()) {
2212 case MenuItemCheckboxRole:
2213 case MenuItemRadioRole:
2214 case RadioButtonRole:
2221 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
2223 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
2224 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used.
2226 const AtomicString& result = getAttribute(aria_checkedAttr);
2227 if (equalIgnoringCase(result, "true"))
2228 return ButtonStateOn;
2229 if (equalIgnoringCase(result, "mixed")) {
2230 // ARIA says that radio and menuitemradio elements must NOT expose button state mixed.
2231 AccessibilityRole ariaRole = ariaRoleAttribute();
2232 if (ariaRole == RadioButtonRole || ariaRole == MenuItemRadioRole)
2233 return ButtonStateOff;
2234 return ButtonStateMixed;
2237 if (equalIgnoringCase(getAttribute(indeterminateAttr), "true"))
2238 return ButtonStateMixed;
2240 return ButtonStateOff;
2243 // This is a 1-dimensional scroll offset helper function that's applied
2244 // separately in the horizontal and vertical directions, because the
2245 // logic is the same. The goal is to compute the best scroll offset
2246 // in order to make an object visible within a viewport.
2248 // In case the whole object cannot fit, you can specify a
2249 // subfocus - a smaller region within the object that should
2250 // be prioritized. If the whole object can fit, the subfocus is
2253 // Example: the viewport is scrolled to the right just enough
2254 // that the object is in view.
2256 // +----------Viewport---------+
2261 // +----------Viewport---------+
2265 // When constraints cannot be fully satisfied, the min
2266 // (left/top) position takes precedence over the max (right/bottom).
2268 // Note that the return value represents the ideal new scroll offset.
2269 // This may be out of range - the calling function should clip this
2270 // to the available range.
2271 static int computeBestScrollOffset(int currentScrollOffset,
2272 int subfocusMin, int subfocusMax,
2273 int objectMin, int objectMax,
2274 int viewportMin, int viewportMax) {
2275 int viewportSize = viewportMax - viewportMin;
2277 // If the focus size is larger than the viewport size, shrink it in the
2278 // direction of subfocus.
2279 if (objectMax - objectMin > viewportSize) {
2280 // Subfocus must be within focus:
2281 subfocusMin = std::max(subfocusMin, objectMin);
2282 subfocusMax = std::min(subfocusMax, objectMax);
2284 // Subfocus must be no larger than the viewport size; favor top/left.
2285 if (subfocusMax - subfocusMin > viewportSize)
2286 subfocusMax = subfocusMin + viewportSize;
2288 if (subfocusMin + viewportSize > objectMax)
2289 objectMin = objectMax - viewportSize;
2291 objectMin = subfocusMin;
2292 objectMax = subfocusMin + viewportSize;
2296 // Exit now if the focus is already within the viewport.
2297 if (objectMin - currentScrollOffset >= viewportMin
2298 && objectMax - currentScrollOffset <= viewportMax)
2299 return currentScrollOffset;
2301 // Scroll left if we're too far to the right.
2302 if (objectMax - currentScrollOffset > viewportMax)
2303 return objectMax - viewportMax;
2305 // Scroll right if we're too far to the left.
2306 if (objectMin - currentScrollOffset < viewportMin)
2307 return objectMin - viewportMin;
2309 ASSERT_NOT_REACHED();
2311 // This shouldn't happen.
2312 return currentScrollOffset;
2315 bool AccessibilityObject::isOnscreen() const
2317 bool isOnscreen = true;
2319 // To figure out if the element is onscreen, we start by building of a stack starting with the
2320 // element, and then include every scrollable parent in the hierarchy.
2321 Vector<const AccessibilityObject*> objects;
2323 objects.append(this);
2324 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
2325 if (parentObject->getScrollableAreaIfScrollable())
2326 objects.append(parentObject);
2329 // Now, go back through that chain and make sure each inner object is within the
2330 // visible bounds of the outer object.
2331 size_t levels = objects.size() - 1;
2333 for (size_t i = levels; i >= 1; i--) {
2334 const AccessibilityObject* outer = objects[i];
2335 const AccessibilityObject* inner = objects[i - 1];
2336 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
2337 const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
2338 const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect());
2340 if (!outerRect.intersects(innerRect)) {
2349 void AccessibilityObject::scrollToMakeVisible() const
2351 IntRect objectRect = snappedIntRect(boundingBoxRect());
2352 objectRect.setLocation(IntPoint());
2353 scrollToMakeVisibleWithSubFocus(objectRect);
2356 void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const
2358 // Search up the parent chain until we find the first one that's scrollable.
2359 AccessibilityObject* scrollParent = parentObject();
2360 ScrollableArea* scrollableArea;
2361 for (scrollableArea = nullptr;
2362 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable());
2363 scrollParent = scrollParent->parentObject()) { }
2364 if (!scrollableArea)
2367 LayoutRect objectRect = boundingBoxRect();
2368 IntPoint scrollPosition = scrollableArea->scrollPosition();
2369 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
2370 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
2372 int desiredX = computeBestScrollOffset(
2374 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(),
2375 objectRect.x(), objectRect.maxX(),
2376 0, scrollVisibleRect.width());
2377 int desiredY = computeBestScrollOffset(
2379 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(),
2380 objectRect.y(), objectRect.maxY(),
2381 0, scrollVisibleRect.height());
2383 scrollParent->scrollTo(IntPoint(desiredX, desiredY));
2385 // Recursively make sure the scroll parent itself is visible.
2386 if (scrollParent->parentObject())
2387 scrollParent->scrollToMakeVisible();
2390 void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
2392 // Search up the parent chain and create a vector of all scrollable parent objects
2393 // and ending with this object itself.
2394 Vector<const AccessibilityObject*> objects;
2396 objects.append(this);
2397 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
2398 if (parentObject->getScrollableAreaIfScrollable())
2399 objects.append(parentObject);
2404 // Start with the outermost scrollable (the main window) and try to scroll the
2405 // next innermost object to the given point.
2406 int offsetX = 0, offsetY = 0;
2407 IntPoint point = globalPoint;
2408 size_t levels = objects.size() - 1;
2409 for (size_t i = 0; i < levels; i++) {
2410 const AccessibilityObject* outer = objects[i];
2411 const AccessibilityObject* inner = objects[i + 1];
2413 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable();
2415 LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect();
2416 LayoutRect objectRect = innerRect;
2417 IntPoint scrollPosition = scrollableArea->scrollPosition();
2419 // Convert the object rect into local coordinates.
2420 objectRect.move(offsetX, offsetY);
2421 if (!outer->isAccessibilityScrollView())
2422 objectRect.move(scrollPosition.x(), scrollPosition.y());
2424 int desiredX = computeBestScrollOffset(
2426 objectRect.x(), objectRect.maxX(),
2427 objectRect.x(), objectRect.maxX(),
2428 point.x(), point.x());
2429 int desiredY = computeBestScrollOffset(
2431 objectRect.y(), objectRect.maxY(),
2432 objectRect.y(), objectRect.maxY(),
2433 point.y(), point.y());
2434 outer->scrollTo(IntPoint(desiredX, desiredY));
2436 if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) {
2437 // If outer object we just scrolled is a scroll view (main window or iframe) but the
2438 // inner object is not, keep track of the coordinate transformation to apply to
2439 // future nested calculations.
2440 scrollPosition = scrollableArea->scrollPosition();
2441 offsetX -= (scrollPosition.x() + point.x());
2442 offsetY -= (scrollPosition.y() + point.y());
2443 point.move(scrollPosition.x() - innerRect.x(),
2444 scrollPosition.y() - innerRect.y());
2445 } else if (inner->isAccessibilityScrollView()) {
2446 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation.
2453 bool AccessibilityObject::lastKnownIsIgnoredValue()
2455 if (m_lastKnownIsIgnoredValue == DefaultBehavior)
2456 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject;
2458 return m_lastKnownIsIgnoredValue == IgnoreObject;
2461 void AccessibilityObject::setLastKnownIsIgnoredValue(bool isIgnored)
2463 m_lastKnownIsIgnoredValue = isIgnored ? IgnoreObject : IncludeObject;
2466 void AccessibilityObject::notifyIfIgnoredValueChanged()
2468 bool isIgnored = accessibilityIsIgnored();
2469 if (lastKnownIsIgnoredValue() != isIgnored) {
2470 if (AXObjectCache* cache = axObjectCache())
2471 cache->childrenChanged(parentObject());
2472 setLastKnownIsIgnoredValue(isIgnored);
2476 bool AccessibilityObject::ariaPressedIsPresent() const
2478 return !getAttribute(aria_pressedAttr).isEmpty();
2481 TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() const
2483 TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility;
2485 #if PLATFORM(GTK) || PLATFORM(EFL)
2486 // We need to emit replaced elements for GTK, and present
2487 // them with the 'object replacement character' (0xFFFC).
2488 behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters);
2494 AccessibilityRole AccessibilityObject::buttonRoleType() const
2496 // If aria-pressed is present, then it should be exposed as a toggle button.
2497 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
2498 if (ariaPressedIsPresent())
2499 return ToggleButtonRole;
2501 return PopUpButtonRole;
2502 // We don't contemplate RadioButtonRole, as it depends on the input
2508 bool AccessibilityObject::isButton() const
2510 AccessibilityRole role = roleValue();
2512 return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole;
2515 bool AccessibilityObject::accessibilityIsIgnoredByDefault() const
2517 return defaultObjectInclusion() == IgnoreObject;
2520 // ARIA component of hidden definition.
2521 // http://www.w3.org/TR/wai-aria/terms#def_hidden
2522 bool AccessibilityObject::isARIAHidden() const
2524 for (const AccessibilityObject* object = this; object; object = object->parentObject()) {
2525 if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true"))
2531 // DOM component of hidden definition.
2532 // http://www.w3.org/TR/wai-aria/terms#def_hidden
2533 bool AccessibilityObject::isDOMHidden() const
2535 RenderObject* renderer = this->renderer();
2539 const RenderStyle& style = renderer->style();
2540 return style.display() == NONE || style.visibility() != VISIBLE;
2543 AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const
2546 return IgnoreObject;
2548 if (isPresentationalChildOfAriaRole())
2549 return IgnoreObject;
2551 return accessibilityPlatformIncludesObject();
2554 bool AccessibilityObject::accessibilityIsIgnored() const
2556 AXComputedObjectAttributeCache* attributeCache = nullptr;
2557 AXObjectCache* cache = axObjectCache();
2559 attributeCache = cache->computedObjectAttributeCache();
2561 if (attributeCache) {
2562 AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID());
2568 case DefaultBehavior:
2573 bool result = computeAccessibilityIsIgnored();
2575 // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists.
2576 if (cache && (attributeCache = cache->computedObjectAttributeCache()))
2577 attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject);
2582 void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
2584 Node* node = this->node();
2585 if (!node || !node->isElementNode())
2588 TreeScope& treeScope = node->treeScope();
2590 String idList = getAttribute(attribute).string();
2591 if (idList.isEmpty())
2594 idList.replace('\n', ' ');
2595 Vector<String> idVector;
2596 idList.split(' ', idVector);
2598 for (const auto& idName : idVector) {
2599 if (Element* idElement = treeScope.getElementById(idName))
2600 elements.append(idElement);
2604 } // namespace WebCore