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 "HTMLDetailsElement.h"
45 #include "HTMLInputElement.h"
46 #include "HTMLNames.h"
47 #include "HTMLParserIdioms.h"
48 #include "HitTestResult.h"
49 #include "LocalizedStrings.h"
50 #include "MainFrame.h"
51 #include "MathMLNames.h"
53 #include "NodeTraversal.h"
55 #include "RenderImage.h"
56 #include "RenderLayer.h"
57 #include "RenderListItem.h"
58 #include "RenderListMarker.h"
59 #include "RenderMenuList.h"
60 #include "RenderText.h"
61 #include "RenderTextControl.h"
62 #include "RenderTheme.h"
63 #include "RenderView.h"
64 #include "RenderWidget.h"
65 #include "RenderedPosition.h"
67 #include "TextCheckerClient.h"
68 #include "TextCheckingHelper.h"
69 #include "TextIterator.h"
70 #include "UserGestureIndicator.h"
71 #include "VisibleUnits.h"
72 #include "htmlediting.h"
73 #include <wtf/NeverDestroyed.h>
74 #include <wtf/StdLibExtras.h>
75 #include <wtf/text/StringBuilder.h>
76 #include <wtf/text/WTFString.h>
77 #include <wtf/unicode/CharacterNames.h>
81 using namespace HTMLNames;
83 AccessibilityObject::AccessibilityObject()
85 , m_haveChildren(false)
87 , m_lastKnownIsIgnoredValue(DefaultBehavior)
88 #if PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY))
94 AccessibilityObject::~AccessibilityObject()
99 void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
101 // Menu close events need to notify the platform. No element is used in the notification because it's a destruction event.
102 if (detachmentType == ElementDestroyed && roleValue() == MenuRole && cache)
103 cache->postNotification(nullptr, &cache->document(), AXObjectCache::AXMenuClosed);
105 // Clear any children and call detachFromParent on them so that
106 // no children are left with dangling pointers to their parent.
109 #if HAVE(ACCESSIBILITY)
114 bool AccessibilityObject::isDetached() const
116 #if HAVE(ACCESSIBILITY)
123 bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria, size_t index)
125 switch (criteria->searchKeys[index]) {
126 // The AnyTypeSearchKey matches any non-null AccessibilityObject.
127 case AnyTypeSearchKey:
130 case BlockquoteSameLevelSearchKey:
131 return criteria->startObject
132 && axObject->isBlockquote()
133 && axObject->blockquoteLevel() == criteria->startObject->blockquoteLevel();
135 case BlockquoteSearchKey:
136 return axObject->isBlockquote();
138 case BoldFontSearchKey:
139 return axObject->hasBoldFont();
141 case ButtonSearchKey:
142 return axObject->isButton();
144 case CheckBoxSearchKey:
145 return axObject->isCheckbox();
147 case ControlSearchKey:
148 return axObject->isControl();
150 case DifferentTypeSearchKey:
151 return criteria->startObject
152 && axObject->roleValue() != criteria->startObject->roleValue();
154 case FontChangeSearchKey:
155 return criteria->startObject
156 && !axObject->hasSameFont(criteria->startObject->renderer());
158 case FontColorChangeSearchKey:
159 return criteria->startObject
160 && !axObject->hasSameFontColor(criteria->startObject->renderer());
163 return axObject->isWebArea();
165 case GraphicSearchKey:
166 return axObject->isImage();
168 case HeadingLevel1SearchKey:
169 return axObject->headingLevel() == 1;
171 case HeadingLevel2SearchKey:
172 return axObject->headingLevel() == 2;
174 case HeadingLevel3SearchKey:
175 return axObject->headingLevel() == 3;
177 case HeadingLevel4SearchKey:
178 return axObject->headingLevel() == 4;
180 case HeadingLevel5SearchKey:
181 return axObject->headingLevel() == 5;
183 case HeadingLevel6SearchKey:
184 return axObject->headingLevel() == 6;
186 case HeadingSameLevelSearchKey:
187 return criteria->startObject
188 && axObject->isHeading()
189 && axObject->headingLevel() == criteria->startObject->headingLevel();
191 case HeadingSearchKey:
192 return axObject->isHeading();
194 case HighlightedSearchKey:
195 return axObject->hasHighlighting();
197 case ItalicFontSearchKey:
198 return axObject->hasItalicFont();
200 case LandmarkSearchKey:
201 return axObject->isLandmark();
204 return axObject->isLink();
207 return axObject->isList();
209 case LiveRegionSearchKey:
210 return axObject->supportsARIALiveRegion();
212 case MisspelledWordSearchKey:
213 return axObject->hasMisspelling();
215 case OutlineSearchKey:
216 return axObject->isTree();
218 case PlainTextSearchKey:
219 return axObject->hasPlainText();
221 case RadioGroupSearchKey:
222 return axObject->isRadioGroup();
224 case SameTypeSearchKey:
225 return criteria->startObject
226 && axObject->roleValue() == criteria->startObject->roleValue();
228 case StaticTextSearchKey:
229 return axObject->isStaticText();
231 case StyleChangeSearchKey:
232 return criteria->startObject
233 && !axObject->hasSameStyle(criteria->startObject->renderer());
235 case TableSameLevelSearchKey:
236 return criteria->startObject
237 && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility()
238 && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel();
241 return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility();
243 case TextFieldSearchKey:
244 return axObject->isTextControl();
246 case UnderlineSearchKey:
247 return axObject->hasUnderline();
249 case UnvisitedLinkSearchKey:
250 return axObject->isUnvisited();
252 case VisitedLinkSearchKey:
253 return axObject->isVisited();
260 bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
262 if (!axObject || !criteria)
265 size_t length = criteria->searchKeys.size();
266 for (size_t i = 0; i < length; ++i) {
267 if (isAccessibilityObjectSearchMatchAtIndex(axObject, criteria, i)) {
268 if (criteria->visibleOnly && !axObject->isOnscreen())
276 bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
278 if (!axObject || !criteria)
281 return axObject->accessibilityObjectContainsText(&criteria->searchText);
284 bool AccessibilityObject::accessibilityObjectContainsText(String* text) const
286 // If text is null or empty we return true.
289 || title().contains(*text, false)
290 || accessibilityDescription().contains(*text, false)
291 || stringValue().contains(*text, false);
294 // ARIA marks elements as having their accessible name derive from either their contents, or their author provide name.
295 bool AccessibilityObject::accessibleNameDerivesFromContent() const
297 // First check for objects specifically identified by ARIA.
298 switch (ariaRoleAttribute()) {
299 case ApplicationAlertRole:
300 case ApplicationAlertDialogRole:
301 case ApplicationDialogRole:
302 case ApplicationLogRole:
303 case ApplicationMarqueeRole:
304 case ApplicationStatusRole:
305 case ApplicationTimerRole:
309 case DocumentArticleRole:
310 case DocumentMathRole:
311 case DocumentNoteRole:
312 case DocumentRegionRole:
319 case LandmarkApplicationRole:
320 case LandmarkBannerRole:
321 case LandmarkComplementaryRole:
322 case LandmarkContentInfoRole:
323 case LandmarkNavigationRole:
324 case LandmarkMainRole:
325 case LandmarkSearchRole:
328 case ProgressIndicatorRole:
347 // Now check for generically derived elements now that we know the element does not match a specific ARIA role.
348 switch (roleValue()) {
358 String AccessibilityObject::computedLabel()
360 // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now.
361 // Also hold onto this object in case updateBackingStore deletes this node.
362 RefPtr<AccessibilityObject> protector(this);
363 updateBackingStore();
364 Vector<AccessibilityText> text;
365 accessibilityText(text);
371 bool AccessibilityObject::isBlockquote() const
373 return roleValue() == BlockquoteRole;
376 bool AccessibilityObject::isTextControl() const
378 switch (roleValue()) {
388 bool AccessibilityObject::isARIATextControl() const
390 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole || ariaRoleAttribute() == SearchFieldRole;
393 bool AccessibilityObject::isLandmark() const
395 AccessibilityRole role = roleValue();
397 return role == LandmarkApplicationRole
398 || role == LandmarkBannerRole
399 || role == LandmarkComplementaryRole
400 || role == LandmarkContentInfoRole
401 || role == LandmarkMainRole
402 || role == LandmarkNavigationRole
403 || role == LandmarkSearchRole;
406 bool AccessibilityObject::hasMisspelling() const
411 Frame* frame = node()->document().frame();
415 Editor& editor = frame->editor();
417 TextCheckerClient* textChecker = editor.textChecker();
421 bool isMisspelled = false;
423 if (unifiedTextCheckerEnabled(frame)) {
424 Vector<TextCheckingResult> results;
425 checkTextOfParagraph(*textChecker, stringValue(), TextCheckingTypeSpelling, results);
426 if (!results.isEmpty())
431 int misspellingLength = 0;
432 int misspellingLocation = -1;
433 textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength);
434 if (misspellingLength || misspellingLocation != -1)
440 int AccessibilityObject::blockquoteLevel() const
443 for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) {
444 if (elementNode->hasTagName(blockquoteTag))
451 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
453 AccessibilityObject* parent;
454 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
460 AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const
462 AccessibilityObject* previous;
464 for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) {
472 AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const
474 AccessibilityObject* next;
476 for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) {
484 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
489 AXObjectCache* cache = node->document().axObjectCache();
493 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
494 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
495 node = NodeTraversal::next(*node);
497 while (node && !node->renderer())
498 node = NodeTraversal::nextSkippingChildren(*node);
503 accessibleObject = cache->getOrCreate(node->renderer());
506 return accessibleObject;
509 static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results)
511 // Find the next descendant of this attachment object so search can continue through frames.
512 if (object->isAttachment()) {
513 Widget* widget = object->widgetForAttachmentView();
514 if (!is<FrameView>(widget))
517 Document* document = downcast<FrameView>(*widget).frame().document();
518 if (!document || !document->hasLivingRenderTree())
521 object = object->axObjectCache()->getOrCreate(document);
525 results.append(object);
528 static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results)
530 // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables).
531 // The rows from the table should be queried, since those are direct descendants of the table, and they contain content.
532 const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children();
534 size_t childrenSize = searchChildren.size();
536 size_t startIndex = isForward ? childrenSize : 0;
537 size_t endIndex = isForward ? 0 : childrenSize;
539 size_t searchPosition = startObject ? searchChildren.find(startObject) : WTF::notFound;
540 if (searchPosition != WTF::notFound) {
542 endIndex = searchPosition + 1;
544 endIndex = searchPosition;
547 // This is broken into two statements so that it's easier read.
549 for (size_t i = startIndex; i > endIndex; i--)
550 appendAccessibilityObject(searchChildren.at(i - 1).get(), results);
552 for (size_t i = startIndex; i < endIndex; i++)
553 appendAccessibilityObject(searchChildren.at(i).get(), results);
557 // Returns true if the number of results is now >= the number of results desired.
558 bool AccessibilityObject::objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject* object, AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
560 if (isAccessibilityObjectSearchMatch(object, criteria) && isAccessibilityTextSearchMatch(object, criteria)) {
561 results.append(object);
563 // Enough results were found to stop searching.
564 if (results.size() >= criteria->resultsLimit)
571 void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
578 if (AXObjectCache* cache = axObjectCache())
579 cache->startCachingComputedObjectAttributesUntilTreeMutates();
581 // This search mechanism only searches the elements before/after the starting object.
582 // It does this by stepping up the parent chain and at each level doing a DFS.
584 // If there's no start object, it means we want to search everything.
585 AccessibilityObject* startObject = criteria->startObject;
589 bool isForward = criteria->searchDirection == SearchDirectionNext;
591 // The first iteration of the outer loop will examine the children of the start object for matches. However, when
592 // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an
593 // exception when no start object was specified because we want to search everything regardless of search direction.
594 AccessibilityObject* previousObject = nullptr;
595 if (!isForward && startObject != this) {
596 previousObject = startObject;
597 startObject = startObject->parentObjectUnignored();
600 // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice)
601 for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) {
603 // Only append the children after/before the previous element, so that the search does not check elements that are
604 // already behind/ahead of start element.
605 AccessibilityChildrenVector searchStack;
606 if (!criteria->immediateDescendantsOnly || startObject == this)
607 appendChildrenToArray(startObject, isForward, previousObject, searchStack);
609 // This now does a DFS at the current level of the parent.
610 while (!searchStack.isEmpty()) {
611 AccessibilityObject* searchObject = searchStack.last().get();
612 searchStack.removeLast();
614 if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results))
617 if (!criteria->immediateDescendantsOnly)
618 appendChildrenToArray(searchObject, isForward, 0, searchStack);
621 if (results.size() >= criteria->resultsLimit)
624 // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element.
625 if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results))
628 previousObject = startObject;
632 // Returns the range that is fewer positions away from the reference range.
633 // NOTE: The after range is expected to ACTUALLY be after the reference range and the before
634 // range is expected to ACTUALLY be before. These are not checked for performance reasons.
635 static PassRefPtr<Range> rangeClosestToRange(Range* referenceRange, PassRefPtr<Range> afterRange, PassRefPtr<Range> beforeRange)
637 ASSERT(referenceRange);
639 // The treeScope for shadow nodes may not be the same scope as another element in a document.
640 // Comparisons may fail in that case, which are expected behavior and should not assert.
641 if (afterRange && ((afterRange->startPosition().anchorNode()->compareDocumentPosition(referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))
643 ASSERT(!afterRange || afterRange->startPosition() >= referenceRange->endPosition());
645 if (beforeRange && ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))
647 ASSERT(!beforeRange || beforeRange->endPosition() <= referenceRange->startPosition());
649 if (!referenceRange || (!afterRange && !beforeRange))
651 if (afterRange && !beforeRange)
653 if (!afterRange && beforeRange)
656 unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition());
657 unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition());
659 return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange;
662 PassRefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String>& searchStrings) const
664 Frame* frame = this->frame();
671 bool isBackwardSearch = searchDirection == SearchDirectionPrevious;
672 FindOptions findOptions = AtWordStarts | AtWordEnds | CaseInsensitive | StartInSelection;
673 if (isBackwardSearch)
674 findOptions |= Backwards;
676 RefPtr<Range> closestStringRange = nullptr;
677 for (const auto& searchString : searchStrings) {
678 if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) {
679 if (!closestStringRange)
680 closestStringRange = searchStringRange;
682 // If searching backward, use the trailing range edges to correctly determine which
683 // range is closest. Similarly, if searching forward, use the leading range edges.
684 Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition();
685 Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition();
687 int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode();
688 int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode();
689 Node* closestContainerNode = closestStringPosition.containerNode();
690 Node* searchContainerNode = searchStringPosition.containerNode();
692 short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset, ASSERT_NO_EXCEPTION);
693 if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0))
694 closestStringRange = searchStringRange;
698 return closestStringRange;
701 // Returns the range of the entire document if there is no selection.
702 PassRefPtr<Range> AccessibilityObject::selectionRange() const
704 Frame* frame = this->frame();
708 const VisibleSelection& selection = frame->selection().selection();
709 if (!selection.isNone())
710 return selection.firstRange();
712 return Range::create(*frame->document());
715 String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria)
722 Frame* frame = this->frame();
726 AccessibilitySelectTextActivity& activity = criteria->activity;
727 AccessibilitySelectTextAmbiguityResolution& ambiguityResolution = criteria->ambiguityResolution;
728 String& replacementString = criteria->replacementString;
729 Vector<String>& searchStrings = criteria->searchStrings;
731 RefPtr<Range> selectedStringRange = selectionRange();
732 // When starting our search again, make this a zero length range so that search forwards will find this selected range if its appropriate.
733 selectedStringRange->setEnd(selectedStringRange->startContainer(), selectedStringRange->startOffset());
735 RefPtr<Range> closestAfterStringRange = nullptr;
736 RefPtr<Range> closestBeforeStringRange = nullptr;
737 // Search forward if necessary.
738 if (ambiguityResolution == ClosestAfterSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution)
739 closestAfterStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionNext, searchStrings);
740 // Search backward if necessary.
741 if (ambiguityResolution == ClosestBeforeSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution)
742 closestBeforeStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionPrevious, searchStrings);
744 // Determine which candidate is closest to the selection and perform the activity.
745 if (RefPtr<Range> closestStringRange = rangeClosestToRange(selectedStringRange.get(), closestAfterStringRange, closestBeforeStringRange)) {
746 // If the search started within a text control, ensure that the result is inside that element.
747 if (element() && element()->isTextFormControl()) {
748 if (!closestStringRange->startContainer()->isDescendantOrShadowDescendantOf(element()) || !closestStringRange->endContainer()->isDescendantOrShadowDescendantOf(element()))
752 String closestString = closestStringRange->text();
753 bool replaceSelection = false;
754 if (frame->selection().setSelectedRange(closestStringRange.get(), DOWNSTREAM, true)) {
756 case FindAndCapitalize: {
757 replacementString = closestString;
758 makeCapitalized(&replacementString, 0);
759 replaceSelection = true;
762 case FindAndUppercase:
763 replacementString = closestString.upper();
764 replaceSelection = true;
766 case FindAndLowercase:
767 replacementString = closestString.lower();
768 replaceSelection = true;
770 case FindAndReplaceActivity: {
771 replaceSelection = true;
773 // When applying find and replace activities, we want to match the capitalization of the replaced text,
774 // (unless we're replacing with an abbreviation.)
775 String uppercaseReplacementString = replacementString.upper();
776 if (closestString.length() > 0 && replacementString.length() > 2 && replacementString != uppercaseReplacementString) {
777 if (closestString[0] == closestString.upper()[0])
778 makeCapitalized(&replacementString, 0);
780 replacementString = replacementString.lower();
784 case FindAndSelectActivity:
788 // A bit obvious, but worth noting the API contract for this method is that we should
789 // return the replacement string when replacing, but the selected string if not.
790 if (replaceSelection) {
791 frame->editor().replaceSelectionWithText(replacementString, true, true);
792 return replacementString;
795 return closestString;
802 bool AccessibilityObject::hasAttributesRequiredForInclusion() const
804 // These checks are simplified in the interest of execution speed.
805 if (!getAttribute(aria_helpAttr).isEmpty()
806 || !getAttribute(aria_describedbyAttr).isEmpty()
807 || !getAttribute(altAttr).isEmpty()
808 || !getAttribute(titleAttr).isEmpty())
812 if (!getAttribute(MathMLNames::alttextAttr).isEmpty())
819 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
821 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole || ariaRole == SwitchRole || ariaRole == SearchFieldRole;
824 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
826 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
827 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
830 bool AccessibilityObject::isRangeControl() const
832 switch (roleValue()) {
833 case ProgressIndicatorRole:
843 bool AccessibilityObject::isMeter() const
845 #if ENABLE(METER_ELEMENT)
846 RenderObject* renderer = this->renderer();
847 return renderer && renderer->isMeter();
853 IntPoint AccessibilityObject::clickPoint()
855 LayoutRect rect = elementRect();
856 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2));
859 IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads)
866 for (const auto& quad : quads) {
867 FloatRect r = quad.enclosingBoundingBox();
869 if (obj->style().hasAppearance())
870 obj->theme().adjustRepaintRect(*obj, r);
874 return snappedIntRect(LayoutRect(result));
877 bool AccessibilityObject::press()
879 // The presence of the actionElement will confirm whether we should even attempt a press.
880 Element* actionElem = actionElement();
883 if (Frame* f = actionElem->document().frame())
884 f->loader().resetMultipleFormSubmissionProtection();
886 // Hit test at this location to determine if there is a sub-node element that should act
887 // as the target of the action.
888 Element* hitTestElement = nullptr;
889 Document* document = this->document();
891 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest);
892 HitTestResult hitTestResult(clickPoint());
893 document->renderView()->hitTest(request, hitTestResult);
894 if (hitTestResult.innerNode()) {
895 Node* innerNode = hitTestResult.innerNode()->deprecatedShadowAncestorNode();
896 if (is<Element>(*innerNode))
897 hitTestElement = downcast<Element>(innerNode);
899 hitTestElement = innerNode->parentElement();
904 // Prefer the actionElement instead of this node, if the actionElement is inside this node.
905 Element* pressElement = this->element();
906 if (!pressElement || actionElem->isDescendantOf(pressElement))
907 pressElement = actionElem;
909 // Prefer the hit test element, if it is inside the target element.
910 if (hitTestElement && hitTestElement->isDescendantOf(pressElement))
911 pressElement = hitTestElement;
913 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document);
915 bool dispatchedTouchEvent = dispatchTouchEvent();
916 if (!dispatchedTouchEvent)
917 pressElement->accessKeyAction(true);
922 bool AccessibilityObject::dispatchTouchEvent()
924 bool handled = false;
925 #if ENABLE(IOS_TOUCH_EVENTS)
926 MainFrame* frame = mainFrame();
930 frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint());
935 Frame* AccessibilityObject::frame() const
937 Node* node = this->node();
941 return node->document().frame();
944 MainFrame* AccessibilityObject::mainFrame() const
946 Document* document = topDocument();
950 Frame* frame = document->frame();
954 return &frame->mainFrame();
957 Document* AccessibilityObject::topDocument() const
961 return &document()->topDocument();
964 String AccessibilityObject::language() const
966 const AtomicString& lang = getAttribute(langAttr);
970 AccessibilityObject* parent = parentObject();
972 // as a last resort, fall back to the content language specified in the meta tag
974 Document* doc = document();
976 return doc->contentLanguage();
980 return parent->language();
983 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
985 if (visiblePos1.isNull() || visiblePos2.isNull())
986 return VisiblePositionRange();
988 // If there's no common tree scope between positions, return early.
989 if (!commonTreeScope(visiblePos1.deepEquivalent().deprecatedNode(), visiblePos2.deepEquivalent().deprecatedNode()))
990 return VisiblePositionRange();
992 VisiblePosition startPos;
993 VisiblePosition endPos;
996 // upstream is ordered before downstream for the same position
997 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
998 alreadyInOrder = false;
1000 // use selection order to see if the positions are in order
1002 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
1004 if (alreadyInOrder) {
1005 startPos = visiblePos1;
1006 endPos = visiblePos2;
1008 startPos = visiblePos2;
1009 endPos = visiblePos1;
1012 return VisiblePositionRange(startPos, endPos);
1015 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
1017 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
1018 VisiblePosition endPosition = endOfWord(startPosition);
1019 return VisiblePositionRange(startPosition, endPosition);
1022 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
1024 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
1025 VisiblePosition endPosition = endOfWord(startPosition);
1026 return VisiblePositionRange(startPosition, endPosition);
1029 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
1031 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
1032 // So let's update the position to include that.
1033 VisiblePosition tempPosition;
1034 VisiblePosition startPosition = visiblePosition;
1036 tempPosition = startPosition.previous();
1037 if (tempPosition.isNull())
1039 Position p = tempPosition.deepEquivalent();
1040 RenderObject* renderer = p.deprecatedNode()->renderer();
1041 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
1043 if (!RenderedPosition(tempPosition).isNull())
1045 startPosition = tempPosition;
1048 return startPosition;
1051 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1053 if (visiblePos.isNull())
1054 return VisiblePositionRange();
1056 // make a caret selection for the position before marker position (to make sure
1057 // we move off of a line start)
1058 VisiblePosition prevVisiblePos = visiblePos.previous();
1059 if (prevVisiblePos.isNull())
1060 return VisiblePositionRange();
1062 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1064 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
1065 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
1066 // since floating object doesn't really belong to any line.
1067 // This check will reposition the marker before the floating object, to ensure we get a line start.
1068 if (startPosition.isNull()) {
1069 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1070 prevVisiblePos = prevVisiblePos.previous();
1071 startPosition = startOfLine(prevVisiblePos);
1074 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1076 VisiblePosition endPosition = endOfLine(prevVisiblePos);
1077 return VisiblePositionRange(startPosition, endPosition);
1080 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
1082 if (visiblePos.isNull())
1083 return VisiblePositionRange();
1085 // make sure we move off of a line end
1086 VisiblePosition nextVisiblePos = visiblePos.next();
1087 if (nextVisiblePos.isNull())
1088 return VisiblePositionRange();
1090 VisiblePosition startPosition = startOfLine(nextVisiblePos);
1092 // fetch for a valid line start position
1093 if (startPosition.isNull()) {
1094 startPosition = visiblePos;
1095 nextVisiblePos = nextVisiblePos.next();
1097 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1099 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1101 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1102 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
1103 // return null for position by a floating object, since floating object doesn't really belong to any line.
1104 // This check will reposition the marker after the floating object, to ensure we get a line end.
1105 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1106 nextVisiblePos = nextVisiblePos.next();
1107 endPosition = endOfLine(nextVisiblePos);
1110 return VisiblePositionRange(startPosition, endPosition);
1113 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
1115 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1116 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1117 VisiblePosition startPosition = startOfSentence(visiblePos);
1118 VisiblePosition endPosition = endOfSentence(startPosition);
1119 return VisiblePositionRange(startPosition, endPosition);
1122 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
1124 VisiblePosition startPosition = startOfParagraph(visiblePos);
1125 VisiblePosition endPosition = endOfParagraph(startPosition);
1126 return VisiblePositionRange(startPosition, endPosition);
1129 static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos)
1131 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1132 RenderObject* startRenderer = renderer;
1133 RenderStyle* style = &renderer->style();
1135 // traverse backward by renderer to look for style change
1136 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
1137 // skip non-leaf nodes
1138 if (r->firstChildSlow())
1141 // stop at style change
1142 if (&r->style() != style)
1149 return firstPositionInOrBeforeNode(startRenderer->node());
1152 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
1154 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1155 RenderObject* endRenderer = renderer;
1156 const RenderStyle& style = renderer->style();
1158 // traverse forward by renderer to look for style change
1159 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
1160 // skip non-leaf nodes
1161 if (r->firstChildSlow())
1164 // stop at style change
1165 if (&r->style() != &style)
1172 return lastPositionInOrAfterNode(endRenderer->node());
1175 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
1177 if (visiblePos.isNull())
1178 return VisiblePositionRange();
1180 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
1183 // NOTE: Consider providing this utility method as AX API
1184 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
1186 unsigned textLength = getLengthForTextRange();
1187 if (range.start + range.length > textLength)
1188 return VisiblePositionRange();
1190 VisiblePosition startPosition = visiblePositionForIndex(range.start);
1191 startPosition.setAffinity(DOWNSTREAM);
1192 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
1193 return VisiblePositionRange(startPosition, endPosition);
1196 VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const
1198 VisiblePosition startPosition = startOfLine(visiblePosition);
1199 VisiblePosition endPosition = endOfLine(visiblePosition);
1200 return VisiblePositionRange(startPosition, endPosition);
1203 static bool replacedNodeNeedsCharacter(Node* replacedNode)
1205 // we should always be given a rendered node and a replaced node, but be safe
1206 // replaced nodes are either attachments (widgets) or images
1207 if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode())
1210 // create an AX object, but skip it if it is not supposed to be seen
1211 AccessibilityObject* object = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode);
1212 if (object->accessibilityIsIgnored())
1218 // Finds a RenderListItem parent give a node.
1219 static RenderListItem* renderListItemContainerForNode(Node* node)
1221 for (; node; node = node->parentNode()) {
1222 RenderBoxModelObject* renderer = node->renderBoxModelObject();
1223 if (is<RenderListItem>(renderer))
1224 return downcast<RenderListItem>(renderer);
1229 // Returns the text associated with a list marker if this node is contained within a list item.
1230 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
1232 // If the range does not contain the start of the line, the list marker text should not be included.
1233 if (!isStartOfLine(visiblePositionStart))
1236 RenderListItem* listItem = renderListItemContainerForNode(node);
1240 // If this is in a list item, we need to manually add the text for the list marker
1241 // because a RenderListMarker does not have a Node equivalent and thus does not appear
1242 // when iterating text.
1243 return listItem->markerTextWithSuffix();
1246 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1248 if (visiblePositionRange.isNull())
1251 StringBuilder builder;
1252 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1253 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1254 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1255 if (it.text().length()) {
1256 // Add a textual representation for list marker text.
1257 builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start));
1258 it.appendTextToStringBuilder(builder);
1260 // locate the node and starting offset for this replaced range
1261 Node* node = it.range()->startContainer();
1262 ASSERT(node == it.range()->endContainer());
1263 int offset = it.range()->startOffset();
1264 if (replacedNodeNeedsCharacter(node->traverseToChildAt(offset)))
1265 builder.append(objectReplacementCharacter);
1269 return builder.toString();
1272 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
1274 // FIXME: Multi-byte support
1275 if (visiblePositionRange.isNull())
1279 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
1280 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
1281 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
1282 if (it.text().length())
1283 length += it.text().length();
1285 // locate the node and starting offset for this replaced range
1287 Node* node = it.range()->startContainer(exception);
1288 ASSERT(node == it.range()->endContainer(exception));
1289 int offset = it.range()->startOffset(exception);
1291 if (replacedNodeNeedsCharacter(node->traverseToChildAt(offset)))
1299 VisiblePosition AccessibilityObject::visiblePositionForBounds(const IntRect& rect, AccessibilityVisiblePositionForBounds visiblePositionForBounds) const
1302 return VisiblePosition();
1304 MainFrame* mainFrame = this->mainFrame();
1306 return VisiblePosition();
1308 // FIXME: Add support for right-to-left languages.
1309 IntPoint corner = (visiblePositionForBounds == FirstVisiblePositionForBounds) ? rect.minXMinYCorner() : rect.maxXMaxYCorner();
1310 VisiblePosition position = mainFrame->visiblePositionForPoint(corner);
1312 if (rect.contains(position.absoluteCaretBounds().center()))
1315 // If the initial position is located outside the bounds adjust it incrementally as needed.
1316 VisiblePosition nextPosition = position.next();
1317 VisiblePosition previousPosition = position.previous();
1318 while (nextPosition.isNotNull() || previousPosition.isNotNull()) {
1319 if (rect.contains(nextPosition.absoluteCaretBounds().center()))
1320 return nextPosition;
1321 if (rect.contains(previousPosition.absoluteCaretBounds().center()))
1322 return previousPosition;
1324 nextPosition = nextPosition.next();
1325 previousPosition = previousPosition.previous();
1328 return VisiblePosition();
1331 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
1333 if (visiblePos.isNull())
1334 return VisiblePosition();
1336 // make sure we move off of a word end
1337 VisiblePosition nextVisiblePos = visiblePos.next();
1338 if (nextVisiblePos.isNull())
1339 return VisiblePosition();
1341 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
1344 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
1346 if (visiblePos.isNull())
1347 return VisiblePosition();
1349 // make sure we move off of a word start
1350 VisiblePosition prevVisiblePos = visiblePos.previous();
1351 if (prevVisiblePos.isNull())
1352 return VisiblePosition();
1354 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
1357 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
1359 if (visiblePos.isNull())
1360 return VisiblePosition();
1362 // to make sure we move off of a line end
1363 VisiblePosition nextVisiblePos = visiblePos.next();
1364 if (nextVisiblePos.isNull())
1365 return VisiblePosition();
1367 VisiblePosition endPosition = endOfLine(nextVisiblePos);
1369 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
1370 // 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.
1371 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
1372 nextVisiblePos = nextVisiblePos.next();
1373 endPosition = endOfLine(nextVisiblePos);
1379 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
1381 if (visiblePos.isNull())
1382 return VisiblePosition();
1384 // make sure we move off of a line start
1385 VisiblePosition prevVisiblePos = visiblePos.previous();
1386 if (prevVisiblePos.isNull())
1387 return VisiblePosition();
1389 VisiblePosition startPosition = startOfLine(prevVisiblePos);
1391 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
1392 // 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.
1393 if (startPosition.isNull()) {
1394 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
1395 prevVisiblePos = prevVisiblePos.previous();
1396 startPosition = startOfLine(prevVisiblePos);
1399 startPosition = updateAXLineStartForVisiblePosition(startPosition);
1401 return startPosition;
1404 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
1406 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1407 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1408 if (visiblePos.isNull())
1409 return VisiblePosition();
1411 // make sure we move off of a sentence end
1412 VisiblePosition nextVisiblePos = visiblePos.next();
1413 if (nextVisiblePos.isNull())
1414 return VisiblePosition();
1416 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
1417 // see this empty line. Instead, return the end position of the empty line.
1418 VisiblePosition endPosition;
1420 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
1421 if (lineString.isEmpty())
1422 endPosition = nextVisiblePos;
1424 endPosition = endOfSentence(nextVisiblePos);
1429 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
1431 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
1432 // Related? <rdar://problem/3927736> Text selection broken in 8A336
1433 if (visiblePos.isNull())
1434 return VisiblePosition();
1436 // make sure we move off of a sentence start
1437 VisiblePosition previousVisiblePos = visiblePos.previous();
1438 if (previousVisiblePos.isNull())
1439 return VisiblePosition();
1441 // treat empty line as a separate sentence.
1442 VisiblePosition startPosition;
1444 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
1445 if (lineString.isEmpty())
1446 startPosition = previousVisiblePos;
1448 startPosition = startOfSentence(previousVisiblePos);
1450 return startPosition;
1453 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
1455 if (visiblePos.isNull())
1456 return VisiblePosition();
1458 // make sure we move off of a paragraph end
1459 VisiblePosition nextPos = visiblePos.next();
1460 if (nextPos.isNull())
1461 return VisiblePosition();
1463 return endOfParagraph(nextPos);
1466 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
1468 if (visiblePos.isNull())
1469 return VisiblePosition();
1471 // make sure we move off of a paragraph start
1472 VisiblePosition previousPos = visiblePos.previous();
1473 if (previousPos.isNull())
1474 return VisiblePosition();
1476 return startOfParagraph(previousPos);
1479 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
1481 if (visiblePos.isNull())
1484 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1488 return obj->document().axObjectCache()->getOrCreate(obj);
1491 // If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
1492 // This only returns true if this is the element that actually has the contentEditable attribute set.
1493 bool AccessibilityObject::hasContentEditableAttributeSet() const
1495 return contentEditableAttributeIsEnabled(element());
1498 bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element)
1503 const AtomicString& contentEditableValue = element->fastGetAttribute(contenteditableAttr);
1504 if (contentEditableValue.isNull())
1507 // Both "true" (case-insensitive) and the empty string count as true.
1508 return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
1511 #if HAVE(ACCESSIBILITY)
1512 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
1514 if (visiblePos.isNull() || !node())
1517 // If the position is not in the same editable region as this AX object, return -1.
1518 Node* containerNode = visiblePos.deepEquivalent().containerNode();
1519 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode))
1523 VisiblePosition currentVisiblePos = visiblePos;
1524 VisiblePosition savedVisiblePos;
1526 // move up until we get to the top
1527 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
1530 savedVisiblePos = currentVisiblePos;
1531 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole);
1532 currentVisiblePos = prevVisiblePos;
1534 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos)));
1540 // NOTE: Consider providing this utility method as AX API
1541 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
1543 int index1 = index(positionRange.start);
1544 int index2 = index(positionRange.end);
1545 if (index1 < 0 || index2 < 0 || index1 > index2)
1546 return PlainTextRange();
1548 return PlainTextRange(index1, index2 - index1);
1551 // The composed character range in the text associated with this accessibility object that
1552 // is specified by the given screen coordinates. This parameterized attribute returns the
1553 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
1554 // screen coordinates.
1555 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
1556 // an error in that case. We return textControl->text().length(), 1. Does this matter?
1557 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
1559 int i = index(visiblePositionForPoint(point));
1561 return PlainTextRange();
1563 return PlainTextRange(i, 1);
1566 // Given a character index, the range of text associated with this accessibility object
1567 // over which the style in effect at that character index applies.
1568 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
1570 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
1571 return plainTextRangeForVisiblePositionRange(range);
1574 // Given an indexed character, the line number of the text associated with this accessibility
1575 // object that contains the character.
1576 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
1578 return lineForPosition(visiblePositionForIndex(index, false));
1581 #if HAVE(ACCESSIBILITY)
1582 void AccessibilityObject::updateBackingStore()
1584 // Updating the layout may delete this object.
1585 RefPtr<AccessibilityObject> protector(this);
1587 if (Document* document = this->document()) {
1588 if (!document->view()->isInLayout())
1589 document->updateLayoutIgnorePendingStylesheets();
1592 updateChildrenIfNecessary();
1596 ScrollView* AccessibilityObject::scrollViewAncestor() const
1598 for (const AccessibilityObject* scrollParent = this; scrollParent; scrollParent = scrollParent->parentObject()) {
1599 if (is<AccessibilityScrollView>(*scrollParent))
1600 return downcast<AccessibilityScrollView>(*scrollParent).scrollView();
1606 Document* AccessibilityObject::document() const
1608 FrameView* frameView = documentFrameView();
1612 return frameView->frame().document();
1615 Page* AccessibilityObject::page() const
1617 Document* document = this->document();
1620 return document->page();
1623 FrameView* AccessibilityObject::documentFrameView() const
1625 const AccessibilityObject* object = this;
1626 while (object && !object->isAccessibilityRenderObject())
1627 object = object->parentObject();
1632 return object->documentFrameView();
1635 #if HAVE(ACCESSIBILITY)
1636 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool updateChildrenIfNeeded)
1638 if (updateChildrenIfNeeded)
1639 updateChildrenIfNecessary();
1645 void AccessibilityObject::updateChildrenIfNecessary()
1647 if (!hasChildren()) {
1648 // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time.
1649 AXAttributeCacheEnabler enableCache(axObjectCache());
1654 void AccessibilityObject::clearChildren()
1656 // Some objects have weak pointers to their parents and those associations need to be detached.
1657 for (const auto& child : m_children)
1658 child->detachFromParent();
1661 m_haveChildren = false;
1664 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
1666 RenderObject* obj = node->renderer();
1670 RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj);
1671 Element* anchor = axObj->anchorElement();
1675 RenderObject* anchorRenderer = anchor->renderer();
1676 if (!anchorRenderer)
1679 return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer);
1682 AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node)
1687 RenderObject* renderObject = node->renderer();
1691 AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject);
1692 for (; axObject && axObject->roleValue() != HeadingRole; axObject = axObject->parentObject()) { }
1697 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
1699 for (const auto& child : children()) {
1700 // Add tree items as the rows.
1701 if (child->roleValue() == TreeItemRole)
1702 result.append(child);
1704 // Now see if this item also has rows hiding inside of it.
1705 child->ariaTreeRows(result);
1709 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
1711 // The ARIA tree item content are the item that are not other tree items or their containing groups.
1712 for (const auto& child : children()) {
1713 AccessibilityRole role = child->roleValue();
1714 if (role == TreeItemRole || role == GroupRole)
1717 result.append(child);
1721 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
1723 for (const auto& obj : children()) {
1724 // Add tree items as the rows.
1725 if (obj->roleValue() == TreeItemRole)
1727 // If it's not a tree item, then descend into the group to find more tree items.
1729 obj->ariaTreeRows(result);
1733 const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role)
1736 case ApplicationAlertDialogRole:
1737 case ApplicationAlertRole:
1738 return ASCIILiteral("assertive");
1739 case ApplicationLogRole:
1740 case ApplicationStatusRole:
1741 return ASCIILiteral("polite");
1742 case ApplicationTimerRole:
1743 case ApplicationMarqueeRole:
1744 return ASCIILiteral("off");
1750 #if HAVE(ACCESSIBILITY)
1751 const String& AccessibilityObject::actionVerb() const
1754 // FIXME: Need to add verbs for select elements.
1755 static NeverDestroyed<const String> buttonAction(AXButtonActionVerb());
1756 static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb());
1757 static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb());
1758 static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb());
1759 static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb());
1760 static NeverDestroyed<const String> linkAction(AXLinkActionVerb());
1761 static NeverDestroyed<const String> menuListAction(AXMenuListActionVerb());
1762 static NeverDestroyed<const String> menuListPopupAction(AXMenuListPopupActionVerb());
1763 static NeverDestroyed<const String> listItemAction(AXListItemActionVerb());
1765 switch (roleValue()) {
1767 case ToggleButtonRole:
1768 return buttonAction;
1771 return textFieldAction;
1772 case RadioButtonRole:
1773 return radioButtonAction;
1776 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
1778 case WebCoreLinkRole:
1780 case PopUpButtonRole:
1781 return menuListAction;
1782 case MenuListPopupRole:
1783 return menuListPopupAction;
1785 return listItemAction;
1795 bool AccessibilityObject::ariaIsMultiline() const
1797 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
1800 String AccessibilityObject::invalidStatus() const
1802 String grammarValue = ASCIILiteral("grammar");
1803 String falseValue = ASCIILiteral("false");
1804 String spellingValue = ASCIILiteral("spelling");
1805 String trueValue = ASCIILiteral("true");
1806 String undefinedValue = ASCIILiteral("undefined");
1808 // aria-invalid can return false (default), grammar, spelling, or true.
1809 String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr));
1811 // If "false", "undefined" [sic, string value], empty, or missing, return "false".
1812 if (ariaInvalid.isEmpty() || ariaInvalid == falseValue || ariaInvalid == undefinedValue)
1814 // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0...
1815 // ...for @aria-invalid are "grammar" and "spelling".
1816 if (ariaInvalid == grammarValue)
1817 return grammarValue;
1818 if (ariaInvalid == spellingValue)
1819 return spellingValue;
1820 // Any other non empty string should be treated as "true".
1824 bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const
1826 Node* node = this->node();
1827 return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName);
1830 bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const
1832 Node* node = this->node();
1833 if (!is<Element>(node))
1836 return downcast<Element>(*node).fastHasAttribute(attribute);
1839 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
1841 if (Element* element = this->element())
1842 return element->fastGetAttribute(attribute);
1846 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
1847 AccessibilityOrientation AccessibilityObject::orientation() const
1849 LayoutRect bounds = elementRect();
1850 if (bounds.size().width() > bounds.size().height())
1851 return AccessibilityOrientationHorizontal;
1852 if (bounds.size().height() > bounds.size().width())
1853 return AccessibilityOrientationVertical;
1855 // A tie goes to horizontal.
1856 return AccessibilityOrientationHorizontal;
1859 bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const
1861 if (!axObject || !axObject->hasChildren())
1864 for (const AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1865 if (parent == axObject)
1871 bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const
1876 return this == axObject || axObject->isDescendantOfObject(this);
1879 AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const
1881 for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
1882 if (child->renderer() && child->renderer()->isAnonymousBlock())
1888 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
1889 typedef HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>> ARIAReverseRoleMap;
1891 static ARIARoleMap* gAriaRoleMap = nullptr;
1892 static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr;
1896 AccessibilityRole webcoreRole;
1899 static void initializeRoleMap()
1903 ASSERT(!gAriaReverseRoleMap);
1905 const RoleEntry roles[] = {
1906 { "alert", ApplicationAlertRole },
1907 { "alertdialog", ApplicationAlertDialogRole },
1908 { "application", LandmarkApplicationRole },
1909 { "article", DocumentArticleRole },
1910 { "banner", LandmarkBannerRole },
1911 { "button", ButtonRole },
1912 { "checkbox", CheckBoxRole },
1913 { "complementary", LandmarkComplementaryRole },
1914 { "contentinfo", LandmarkContentInfoRole },
1915 { "dialog", ApplicationDialogRole },
1916 { "directory", DirectoryRole },
1917 { "grid", TableRole },
1918 { "gridcell", CellRole },
1919 { "columnheader", ColumnHeaderRole },
1920 { "combobox", ComboBoxRole },
1921 { "definition", DefinitionRole },
1922 { "document", DocumentRole },
1923 { "form", FormRole },
1924 { "rowheader", RowHeaderRole },
1925 { "group", GroupRole },
1926 { "heading", HeadingRole },
1927 { "img", ImageRole },
1928 { "link", WebCoreLinkRole },
1929 { "list", ListRole },
1930 { "listitem", ListItemRole },
1931 { "listbox", ListBoxRole },
1932 { "log", ApplicationLogRole },
1933 // "option" isn't here because it may map to different roles depending on the parent element's role
1934 { "main", LandmarkMainRole },
1935 { "marquee", ApplicationMarqueeRole },
1936 { "math", DocumentMathRole },
1937 { "menu", MenuRole },
1938 { "menubar", MenuBarRole },
1939 { "menuitem", MenuItemRole },
1940 { "menuitemcheckbox", MenuItemCheckboxRole },
1941 { "menuitemradio", MenuItemRadioRole },
1942 { "none", PresentationalRole },
1943 { "note", DocumentNoteRole },
1944 { "navigation", LandmarkNavigationRole },
1945 { "option", ListBoxOptionRole },
1946 { "presentation", PresentationalRole },
1947 { "progressbar", ProgressIndicatorRole },
1948 { "radio", RadioButtonRole },
1949 { "radiogroup", RadioGroupRole },
1950 { "region", DocumentRegionRole },
1952 { "scrollbar", ScrollBarRole },
1953 { "search", LandmarkSearchRole },
1954 { "searchbox", SearchFieldRole },
1955 { "separator", SplitterRole },
1956 { "slider", SliderRole },
1957 { "spinbutton", SpinButtonRole },
1958 { "status", ApplicationStatusRole },
1959 { "switch", SwitchRole },
1961 { "tablist", TabListRole },
1962 { "tabpanel", TabPanelRole },
1963 { "text", StaticTextRole },
1964 { "textbox", TextAreaRole },
1965 { "timer", ApplicationTimerRole },
1966 { "toolbar", ToolbarRole },
1967 { "tooltip", UserInterfaceTooltipRole },
1968 { "tree", TreeRole },
1969 { "treegrid", TreeGridRole },
1970 { "treeitem", TreeItemRole }
1973 gAriaRoleMap = new ARIARoleMap;
1974 gAriaReverseRoleMap = new ARIAReverseRoleMap;
1975 size_t roleLength = WTF_ARRAY_LENGTH(roles);
1976 for (size_t i = 0; i < roleLength; ++i) {
1977 gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
1978 gAriaReverseRoleMap->set(roles[i].webcoreRole, roles[i].ariaRole);
1982 static ARIARoleMap& ariaRoleMap()
1984 initializeRoleMap();
1985 return *gAriaRoleMap;
1988 static ARIAReverseRoleMap& reverseAriaRoleMap()
1990 initializeRoleMap();
1991 return *gAriaReverseRoleMap;
1994 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
1996 ASSERT(!value.isEmpty());
1998 Vector<String> roleVector;
1999 value.split(' ', roleVector);
2000 AccessibilityRole role = UnknownRole;
2001 for (const auto& roleName : roleVector) {
2002 role = ariaRoleMap().get(roleName);
2010 String AccessibilityObject::computedRoleString() const
2012 // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296
2013 AccessibilityRole role = roleValue();
2014 if (role == HorizontalRuleRole)
2015 role = SplitterRole;
2017 return reverseAriaRoleMap().get(role);
2020 bool AccessibilityObject::hasHighlighting() const
2022 for (Node* node = this->node(); node; node = node->parentNode()) {
2023 if (node->hasTagName(markTag))
2030 const AtomicString& AccessibilityObject::roleDescription() const
2032 return getAttribute(aria_roledescriptionAttr);
2035 static bool nodeHasPresentationRole(Node* node)
2037 return nodeHasRole(node, "presentation") || nodeHasRole(node, "none");
2040 bool AccessibilityObject::supportsPressAction() const
2044 if (roleValue() == DetailsRole)
2047 Element* actionElement = this->actionElement();
2051 // [Bug: 136247] Heuristic: element handlers that have more than one accessible descendant should not be exposed as supporting press.
2052 if (actionElement != element()) {
2053 if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(actionElement)) {
2054 AccessibilityChildrenVector results;
2055 // Search within for immediate descendants that are static text. If we find more than one
2056 // then this is an event delegator actionElement and we should expose the press action.
2057 Vector<AccessibilitySearchKey> keys({ StaticTextSearchKey, ControlSearchKey, GraphicSearchKey, HeadingSearchKey, LinkSearchKey });
2058 AccessibilitySearchCriteria criteria(axObj, SearchDirectionNext, "", 2, false, false);
2059 criteria.searchKeys = keys;
2060 axObj->findMatchingObjects(&criteria, results);
2061 if (results.size() > 1)
2066 // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action.
2067 return !nodeHasPresentationRole(actionElement);
2070 bool AccessibilityObject::supportsDatetimeAttribute() const
2072 return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag);
2075 Element* AccessibilityObject::element() const
2077 Node* node = this->node();
2078 if (is<Element>(node))
2079 return downcast<Element>(node);
2083 bool AccessibilityObject::isValueAutofilled() const
2085 if (!isNativeTextControl())
2088 Node* node = this->node();
2089 if (!node || !is<HTMLInputElement>(*node))
2092 return downcast<HTMLInputElement>(*node).isAutoFilled();
2095 const AtomicString& AccessibilityObject::placeholderValue() const
2097 const AtomicString& placeholder = getAttribute(placeholderAttr);
2098 if (!placeholder.isEmpty())
2104 bool AccessibilityObject::isInsideARIALiveRegion() const
2106 if (supportsARIALiveRegion())
2109 for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
2110 if (axParent->supportsARIALiveRegion())
2117 bool AccessibilityObject::supportsARIAAttributes() const
2119 // This returns whether the element supports any global ARIA attributes.
2120 return supportsARIALiveRegion()
2121 || supportsARIADragging()
2122 || supportsARIADropping()
2123 || supportsARIAFlowTo()
2124 || supportsARIAOwns()
2125 || hasAttribute(aria_atomicAttr)
2126 || hasAttribute(aria_busyAttr)
2127 || hasAttribute(aria_controlsAttr)
2128 || hasAttribute(aria_describedbyAttr)
2129 || hasAttribute(aria_disabledAttr)
2130 || hasAttribute(aria_haspopupAttr)
2131 || hasAttribute(aria_invalidAttr)
2132 || hasAttribute(aria_labelAttr)
2133 || hasAttribute(aria_labelledbyAttr)
2134 || hasAttribute(aria_relevantAttr);
2137 bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomicString& liveRegionStatus)
2139 return equalIgnoringCase(liveRegionStatus, "polite") || equalIgnoringCase(liveRegionStatus, "assertive");
2142 bool AccessibilityObject::supportsARIALiveRegion() const
2144 return liveRegionStatusIsEnabled(ariaLiveRegionStatus());
2147 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const
2149 // Send the hit test back into the sub-frame if necessary.
2150 if (isAttachment()) {
2151 Widget* widget = widgetForAttachmentView();
2152 // Normalize the point for the widget's bounds.
2153 if (widget && widget->isFrameView()) {
2154 if (AXObjectCache* cache = axObjectCache())
2155 return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location()));
2159 // Check if there are any mock elements that need to be handled.
2160 for (const auto& child : m_children) {
2161 if (child->isMockObject() && child->elementRect().contains(point))
2162 return child->elementAccessibilityHitTest(point);
2165 return const_cast<AccessibilityObject*>(this);
2168 AXObjectCache* AccessibilityObject::axObjectCache() const
2170 Document* doc = document();
2172 return doc->axObjectCache();
2176 AccessibilityObject* AccessibilityObject::focusedUIElement() const
2178 Document* doc = document();
2182 Page* page = doc->page();
2186 return AXObjectCache::focusedUIElementForPage(page);
2189 AccessibilitySortDirection AccessibilityObject::sortDirection() const
2191 const AtomicString& sortAttribute = getAttribute(aria_sortAttr);
2192 if (equalIgnoringCase(sortAttribute, "ascending"))
2193 return SortDirectionAscending;
2194 if (equalIgnoringCase(sortAttribute, "descending"))
2195 return SortDirectionDescending;
2196 if (equalIgnoringCase(sortAttribute, "other"))
2197 return SortDirectionOther;
2199 return SortDirectionNone;
2202 bool AccessibilityObject::supportsRangeValue() const
2204 return isProgressIndicator()
2210 bool AccessibilityObject::supportsARIASetSize() const
2212 return hasAttribute(aria_setsizeAttr);
2215 bool AccessibilityObject::supportsARIAPosInSet() const
2217 return hasAttribute(aria_posinsetAttr);
2220 int AccessibilityObject::ariaSetSize() const
2222 return getAttribute(aria_setsizeAttr).toInt();
2225 int AccessibilityObject::ariaPosInSet() const
2227 return getAttribute(aria_posinsetAttr).toInt();
2230 String AccessibilityObject::identifierAttribute() const
2232 return getAttribute(idAttr);
2235 void AccessibilityObject::classList(Vector<String>& classList) const
2237 Node* node = this->node();
2238 if (!is<Element>(node))
2241 Element* element = downcast<Element>(node);
2242 DOMTokenList& list = element->classList();
2243 unsigned length = list.length();
2244 for (unsigned k = 0; k < length; k++)
2245 classList.append(list.item(k).string());
2248 bool AccessibilityObject::supportsARIAPressed() const
2250 const AtomicString& expanded = getAttribute(aria_pressedAttr);
2251 return equalIgnoringCase(expanded, "true") || equalIgnoringCase(expanded, "false");
2254 bool AccessibilityObject::supportsExpanded() const
2256 // Undefined values should not result in this attribute being exposed to ATs according to ARIA.
2257 const AtomicString& expanded = getAttribute(aria_expandedAttr);
2258 if (equalIgnoringCase(expanded, "true") || equalIgnoringCase(expanded, "false"))
2260 switch (roleValue()) {
2262 case DisclosureTriangleRole:
2270 bool AccessibilityObject::isExpanded() const
2272 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
2275 if (is<HTMLDetailsElement>(node()))
2276 return downcast<HTMLDetailsElement>(node())->isOpen();
2281 bool AccessibilityObject::supportsChecked() const
2283 switch (roleValue()) {
2285 case MenuItemCheckboxRole:
2286 case MenuItemRadioRole:
2287 case RadioButtonRole:
2295 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
2297 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
2298 // If it's an ARIA checkbox, radio, or switch the aria-checked attribute should be used.
2299 // If it's a toggle button, the aria-pressed attribute is consulted.
2301 if (isToggleButton()) {
2302 const AtomicString& ariaPressed = getAttribute(aria_pressedAttr);
2303 if (equalIgnoringCase(ariaPressed, "true"))
2304 return ButtonStateOn;
2305 if (equalIgnoringCase(ariaPressed, "mixed"))
2306 return ButtonStateMixed;
2307 return ButtonStateOff;
2310 const AtomicString& result = getAttribute(aria_checkedAttr);
2311 if (equalIgnoringCase(result, "true"))
2312 return ButtonStateOn;
2313 if (equalIgnoringCase(result, "mixed")) {
2314 // ARIA says that radio, menuitemradio, and switch elements must NOT expose button state mixed.
2315 AccessibilityRole ariaRole = ariaRoleAttribute();
2316 if (ariaRole == RadioButtonRole || ariaRole == MenuItemRadioRole || ariaRole == SwitchRole)
2317 return ButtonStateOff;
2318 return ButtonStateMixed;
2321 if (equalIgnoringCase(getAttribute(indeterminateAttr), "true"))
2322 return ButtonStateMixed;
2324 return ButtonStateOff;
2327 // This is a 1-dimensional scroll offset helper function that's applied
2328 // separately in the horizontal and vertical directions, because the
2329 // logic is the same. The goal is to compute the best scroll offset
2330 // in order to make an object visible within a viewport.
2332 // In case the whole object cannot fit, you can specify a
2333 // subfocus - a smaller region within the object that should
2334 // be prioritized. If the whole object can fit, the subfocus is
2337 // Example: the viewport is scrolled to the right just enough
2338 // that the object is in view.
2340 // +----------Viewport---------+
2345 // +----------Viewport---------+
2349 // When constraints cannot be fully satisfied, the min
2350 // (left/top) position takes precedence over the max (right/bottom).
2352 // Note that the return value represents the ideal new scroll offset.
2353 // This may be out of range - the calling function should clip this
2354 // to the available range.
2355 static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int objectMin, int objectMax, int viewportMin, int viewportMax)
2357 int viewportSize = viewportMax - viewportMin;
2359 // If the focus size is larger than the viewport size, shrink it in the
2360 // direction of subfocus.
2361 if (objectMax - objectMin > viewportSize) {
2362 // Subfocus must be within focus:
2363 subfocusMin = std::max(subfocusMin, objectMin);
2365 // Subfocus must be no larger than the viewport size; favor top/left.
2366 if (subfocusMin + viewportSize > objectMax)
2367 objectMin = objectMax - viewportSize;
2369 objectMin = subfocusMin;
2370 objectMax = subfocusMin + viewportSize;
2374 // Exit now if the focus is already within the viewport.
2375 if (objectMin - currentScrollOffset >= viewportMin
2376 && objectMax - currentScrollOffset <= viewportMax)
2377 return currentScrollOffset;
2379 // Scroll left if we're too far to the right.
2380 if (objectMax - currentScrollOffset > viewportMax)
2381 return objectMax - viewportMax;
2383 // Scroll right if we're too far to the left.
2384 if (objectMin - currentScrollOffset < viewportMin)
2385 return objectMin - viewportMin;
2387 ASSERT_NOT_REACHED();
2389 // This shouldn't happen.
2390 return currentScrollOffset;
2393 bool AccessibilityObject::isOnscreen() const
2395 bool isOnscreen = true;
2397 // To figure out if the element is onscreen, we start by building of a stack starting with the
2398 // element, and then include every scrollable parent in the hierarchy.
2399 Vector<const AccessibilityObject*> objects;
2401 objects.append(this);
2402 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
2403 if (parentObject->getScrollableAreaIfScrollable())
2404 objects.append(parentObject);
2407 // Now, go back through that chain and make sure each inner object is within the
2408 // visible bounds of the outer object.
2409 size_t levels = objects.size() - 1;
2411 for (size_t i = levels; i >= 1; i--) {
2412 const AccessibilityObject* outer = objects[i];
2413 const AccessibilityObject* inner = objects[i - 1];
2414 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
2415 const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
2416 const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect());
2418 if (!outerRect.intersects(innerRect)) {
2427 void AccessibilityObject::scrollToMakeVisible() const
2429 IntRect objectRect = snappedIntRect(boundingBoxRect());
2430 objectRect.setLocation(IntPoint());
2431 scrollToMakeVisibleWithSubFocus(objectRect);
2434 void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const
2436 // Search up the parent chain until we find the first one that's scrollable.
2437 AccessibilityObject* scrollParent = parentObject();
2438 ScrollableArea* scrollableArea;
2439 for (scrollableArea = nullptr;
2440 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable());
2441 scrollParent = scrollParent->parentObject()) { }
2442 if (!scrollableArea)
2445 LayoutRect objectRect = boundingBoxRect();
2446 IntPoint scrollPosition = scrollableArea->scrollPosition();
2447 // FIXME: unclear if we need LegacyIOSDocumentVisibleRect.
2448 IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
2450 int desiredX = computeBestScrollOffset(
2452 objectRect.x() + subfocus.x(),
2453 objectRect.x(), objectRect.maxX(),
2454 0, scrollVisibleRect.width());
2455 int desiredY = computeBestScrollOffset(
2457 objectRect.y() + subfocus.y(),
2458 objectRect.y(), objectRect.maxY(),
2459 0, scrollVisibleRect.height());
2461 scrollParent->scrollTo(IntPoint(desiredX, desiredY));
2463 // Recursively make sure the scroll parent itself is visible.
2464 if (scrollParent->parentObject())
2465 scrollParent->scrollToMakeVisible();
2468 void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
2470 // Search up the parent chain and create a vector of all scrollable parent objects
2471 // and ending with this object itself.
2472 Vector<const AccessibilityObject*> objects;
2474 objects.append(this);
2475 for (AccessibilityObject* parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
2476 if (parentObject->getScrollableAreaIfScrollable())
2477 objects.append(parentObject);
2482 // Start with the outermost scrollable (the main window) and try to scroll the
2483 // next innermost object to the given point.
2484 int offsetX = 0, offsetY = 0;
2485 IntPoint point = globalPoint;
2486 size_t levels = objects.size() - 1;
2487 for (size_t i = 0; i < levels; i++) {
2488 const AccessibilityObject* outer = objects[i];
2489 const AccessibilityObject* inner = objects[i + 1];
2491 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable();
2493 LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect();
2494 LayoutRect objectRect = innerRect;
2495 IntPoint scrollPosition = scrollableArea->scrollPosition();
2497 // Convert the object rect into local coordinates.
2498 objectRect.move(offsetX, offsetY);
2499 if (!outer->isAccessibilityScrollView())
2500 objectRect.move(scrollPosition.x(), scrollPosition.y());
2502 int desiredX = computeBestScrollOffset(
2505 objectRect.x(), objectRect.maxX(),
2506 point.x(), point.x());
2507 int desiredY = computeBestScrollOffset(
2510 objectRect.y(), objectRect.maxY(),
2511 point.y(), point.y());
2512 outer->scrollTo(IntPoint(desiredX, desiredY));
2514 if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) {
2515 // If outer object we just scrolled is a scroll view (main window or iframe) but the
2516 // inner object is not, keep track of the coordinate transformation to apply to
2517 // future nested calculations.
2518 scrollPosition = scrollableArea->scrollPosition();
2519 offsetX -= (scrollPosition.x() + point.x());
2520 offsetY -= (scrollPosition.y() + point.y());
2521 point.move(scrollPosition.x() - innerRect.x(),
2522 scrollPosition.y() - innerRect.y());
2523 } else if (inner->isAccessibilityScrollView()) {
2524 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation.
2531 bool AccessibilityObject::lastKnownIsIgnoredValue()
2533 if (m_lastKnownIsIgnoredValue == DefaultBehavior)
2534 m_lastKnownIsIgnoredValue = accessibilityIsIgnored() ? IgnoreObject : IncludeObject;
2536 return m_lastKnownIsIgnoredValue == IgnoreObject;
2539 void AccessibilityObject::setLastKnownIsIgnoredValue(bool isIgnored)
2541 m_lastKnownIsIgnoredValue = isIgnored ? IgnoreObject : IncludeObject;
2544 void AccessibilityObject::notifyIfIgnoredValueChanged()
2546 bool isIgnored = accessibilityIsIgnored();
2547 if (lastKnownIsIgnoredValue() != isIgnored) {
2548 if (AXObjectCache* cache = axObjectCache())
2549 cache->childrenChanged(parentObject());
2550 setLastKnownIsIgnoredValue(isIgnored);
2554 bool AccessibilityObject::ariaPressedIsPresent() const
2556 return !getAttribute(aria_pressedAttr).isEmpty();
2559 TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() const
2561 TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility;
2563 #if PLATFORM(GTK) || PLATFORM(EFL)
2564 // We need to emit replaced elements for GTK, and present
2565 // them with the 'object replacement character' (0xFFFC).
2566 behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters);
2572 AccessibilityRole AccessibilityObject::buttonRoleType() const
2574 // If aria-pressed is present, then it should be exposed as a toggle button.
2575 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
2576 if (ariaPressedIsPresent())
2577 return ToggleButtonRole;
2579 return PopUpButtonRole;
2580 // We don't contemplate RadioButtonRole, as it depends on the input
2586 bool AccessibilityObject::isButton() const
2588 AccessibilityRole role = roleValue();
2590 return role == ButtonRole || role == PopUpButtonRole || role == ToggleButtonRole;
2593 bool AccessibilityObject::accessibilityIsIgnoredByDefault() const
2595 return defaultObjectInclusion() == IgnoreObject;
2598 // ARIA component of hidden definition.
2599 // http://www.w3.org/TR/wai-aria/terms#def_hidden
2600 bool AccessibilityObject::isARIAHidden() const
2602 for (const AccessibilityObject* object = this; object; object = object->parentObject()) {
2603 if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true"))
2609 // DOM component of hidden definition.
2610 // http://www.w3.org/TR/wai-aria/terms#def_hidden
2611 bool AccessibilityObject::isDOMHidden() const
2613 RenderObject* renderer = this->renderer();
2617 const RenderStyle& style = renderer->style();
2618 return style.display() == NONE || style.visibility() != VISIBLE;
2621 AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const
2624 return IgnoreObject;
2626 if (isPresentationalChildOfAriaRole())
2627 return IgnoreObject;
2629 return accessibilityPlatformIncludesObject();
2632 bool AccessibilityObject::accessibilityIsIgnored() const
2634 AXComputedObjectAttributeCache* attributeCache = nullptr;
2635 AXObjectCache* cache = axObjectCache();
2637 attributeCache = cache->computedObjectAttributeCache();
2639 if (attributeCache) {
2640 AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID());
2646 case DefaultBehavior:
2651 bool result = computeAccessibilityIsIgnored();
2653 // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists.
2654 if (cache && (attributeCache = cache->computedObjectAttributeCache()))
2655 attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject);
2660 void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
2662 Node* node = this->node();
2663 if (!node || !node->isElementNode())
2666 TreeScope& treeScope = node->treeScope();
2668 String idList = getAttribute(attribute).string();
2669 if (idList.isEmpty())
2672 idList.replace('\n', ' ');
2673 Vector<String> idVector;
2674 idList.split(' ', idVector);
2676 for (const auto& idName : idVector) {
2677 if (Element* idElement = treeScope.getElementById(idName))
2678 elements.append(idElement);
2683 bool AccessibilityObject::preventKeyboardDOMEventDispatch() const
2685 Frame* frame = this->frame();
2686 return frame && frame->settings().preventKeyboardDOMEventDispatch();
2689 void AccessibilityObject::setPreventKeyboardDOMEventDispatch(bool on)
2691 Frame* frame = this->frame();
2694 frame->settings().setPreventKeyboardDOMEventDispatch(on);
2698 bool AccessibilityObject::isContainedByPasswordField() const
2700 Node* node = this->node();
2704 if (ariaRoleAttribute() != UnknownRole)
2707 Element* element = node->shadowHost();
2708 return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(*element).isPasswordField();
2711 } // namespace WebCore