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 Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
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 "AccessibilityTable.h"
35 #include "FloatRect.h"
36 #include "FocusController.h"
38 #include "FrameLoader.h"
39 #include "FrameSelection.h"
40 #include "HTMLNames.h"
41 #include "LocalizedStrings.h"
43 #include "NotImplemented.h"
45 #include "RenderImage.h"
46 #include "RenderListItem.h"
47 #include "RenderListMarker.h"
48 #include "RenderMenuList.h"
49 #include "RenderTextControl.h"
50 #include "RenderTheme.h"
51 #include "RenderView.h"
52 #include "RenderWidget.h"
53 #include "RenderedPosition.h"
55 #include "TextCheckerClient.h"
56 #include "TextCheckingHelper.h"
57 #include "TextIterator.h"
58 #include "htmlediting.h"
59 #include "visible_units.h"
60 #include <wtf/StdLibExtras.h>
61 #include <wtf/text/StringBuilder.h>
62 #include <wtf/text/WTFString.h>
63 #include <wtf/unicode/CharacterNames.h>
69 using namespace HTMLNames;
71 AccessibilityObject::AccessibilityObject()
73 , m_haveChildren(false)
77 #elif PLATFORM(CHROMIUM)
83 AccessibilityObject::~AccessibilityObject()
88 void AccessibilityObject::detach()
90 #if HAVE(ACCESSIBILITY) && PLATFORM(CHROMIUM)
92 #elif HAVE(ACCESSIBILITY)
97 bool AccessibilityObject::isDetached() const
99 #if HAVE(ACCESSIBILITY) && PLATFORM(CHROMIUM)
101 #elif HAVE(ACCESSIBILITY)
108 bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
110 if (!axObject || !criteria)
113 switch (criteria->searchKey) {
114 // The AnyTypeSearchKey matches any non-null AccessibilityObject.
115 case AnyTypeSearchKey:
118 case BlockquoteSameLevelSearchKey:
119 return criteria->startObject
120 && axObject->isBlockquote()
121 && axObject->blockquoteLevel() == criteria->startObject->blockquoteLevel();
123 case BlockquoteSearchKey:
124 return axObject->isBlockquote();
126 case BoldFontSearchKey:
127 return axObject->hasBoldFont();
129 case ButtonSearchKey:
130 return axObject->isButton();
132 case CheckBoxSearchKey:
133 return axObject->isCheckbox();
135 case ControlSearchKey:
136 return axObject->isControl();
138 case DifferentTypeSearchKey:
139 return criteria->startObject
140 && axObject->roleValue() != criteria->startObject->roleValue();
142 case FontChangeSearchKey:
143 return criteria->startObject
144 && !axObject->hasSameFont(criteria->startObject->renderer());
146 case FontColorChangeSearchKey:
147 return criteria->startObject
148 && !axObject->hasSameFontColor(criteria->startObject->renderer());
151 return axObject->isWebArea();
153 case GraphicSearchKey:
154 return axObject->isImage();
156 case HeadingLevel1SearchKey:
157 return axObject->headingLevel() == 1;
159 case HeadingLevel2SearchKey:
160 return axObject->headingLevel() == 2;
162 case HeadingLevel3SearchKey:
163 return axObject->headingLevel() == 3;
165 case HeadingLevel4SearchKey:
166 return axObject->headingLevel() == 4;
168 case HeadingLevel5SearchKey:
169 return axObject->headingLevel() == 5;
171 case HeadingLevel6SearchKey:
172 return axObject->headingLevel() == 6;
174 case HeadingSameLevelSearchKey:
175 return criteria->startObject
176 && axObject->isHeading()
177 && axObject->headingLevel() == criteria->startObject->headingLevel();
179 case HeadingSearchKey:
180 return axObject->isHeading();
182 case HighlightedSearchKey:
183 return axObject->hasHighlighting();
185 case ItalicFontSearchKey:
186 return axObject->hasItalicFont();
188 case LandmarkSearchKey:
189 return axObject->isLandmark();
192 return axObject->isLink();
195 return axObject->isList();
197 case LiveRegionSearchKey:
198 return axObject->supportsARIALiveRegion();
200 case MisspelledWordSearchKey:
201 return axObject->hasMisspelling();
203 case PlainTextSearchKey:
204 return axObject->hasPlainText();
206 case RadioGroupSearchKey:
207 return axObject->isRadioGroup();
209 case SameTypeSearchKey:
210 return criteria->startObject
211 && axObject->roleValue() == criteria->startObject->roleValue();
213 case StaticTextSearchKey:
214 return axObject->hasStaticText();
216 case StyleChangeSearchKey:
217 return criteria->startObject
218 && !axObject->hasSameStyle(criteria->startObject->renderer());
220 case TableSameLevelSearchKey:
221 return criteria->startObject
222 && axObject->isAccessibilityTable()
223 && axObject->tableLevel() == criteria->startObject->tableLevel();
226 return axObject->isAccessibilityTable();
228 case TextFieldSearchKey:
229 return axObject->isTextControl();
231 case UnderlineSearchKey:
232 return axObject->hasUnderline();
234 case UnvisitedLinkSearchKey:
235 return axObject->isUnvisited();
237 case VisitedLinkSearchKey:
238 return axObject->isVisited();
245 bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchCriteria* criteria)
247 if (!axObject || !criteria)
250 return axObject->accessibilityObjectContainsText(criteria->searchText);
253 bool AccessibilityObject::accessibilityObjectContainsText(String* text) const
255 // If text is null or empty we return true.
258 || title().contains(*text, false)
259 || accessibilityDescription().contains(*text, false)
260 || stringValue().contains(*text, false);
263 bool AccessibilityObject::isBlockquote() const
265 return node() && node()->hasTagName(blockquoteTag);
268 bool AccessibilityObject::isARIATextControl() const
270 return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole;
273 bool AccessibilityObject::isLandmark() const
275 AccessibilityRole role = roleValue();
277 return role == LandmarkApplicationRole
278 || role == LandmarkBannerRole
279 || role == LandmarkComplementaryRole
280 || role == LandmarkContentInfoRole
281 || role == LandmarkMainRole
282 || role == LandmarkNavigationRole
283 || role == LandmarkSearchRole;
286 bool AccessibilityObject::hasMisspelling() const
291 Document* document = node()->document();
295 Frame* frame = document->frame();
299 Editor* editor = frame->editor();
303 TextCheckerClient* textChecker = editor->textChecker();
307 const UChar* chars = stringValue().characters();
308 int charsLength = stringValue().length();
309 bool isMisspelled = false;
311 if (unifiedTextCheckerEnabled(frame)) {
312 Vector<TextCheckingResult> results;
313 checkTextOfParagraph(textChecker, chars, charsLength, TextCheckingTypeSpelling, results);
314 if (!results.isEmpty())
319 int misspellingLength = 0;
320 int misspellingLocation = -1;
321 textChecker->checkSpellingOfString(chars, charsLength, &misspellingLocation, &misspellingLength);
322 if (misspellingLength || misspellingLocation != -1)
328 int AccessibilityObject::blockquoteLevel() const
331 for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) {
332 if (elementNode->hasTagName(blockquoteTag))
339 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
341 AccessibilityObject* parent;
342 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
348 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
353 Document* document = node->document();
357 AXObjectCache* cache = document->axObjectCache();
359 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
360 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
361 node = node->traverseNextNode();
363 while (node && !node->renderer())
364 node = node->traverseNextSibling();
369 accessibleObject = cache->getOrCreate(node->renderer());
372 return accessibleObject;
375 static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results)
377 // Find the next descendant of this attachment object so search can continue through frames.
378 if (object->isAttachment()) {
379 Widget* widget = object->widgetForAttachmentView();
380 if (!widget || !widget->isFrameView())
383 Document* doc = static_cast<FrameView*>(widget)->frame()->document();
384 if (!doc || !doc->renderer())
387 object = object->axObjectCache()->getOrCreate(doc->renderer());
391 results.append(object);
394 static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results)
396 AccessibilityObject::AccessibilityChildrenVector searchChildren;
397 // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables).
398 // The rows from the table should be queried, since those are direct descendants of the table, and they contain content.
399 if (object->isAccessibilityTable())
400 searchChildren = toAccessibilityTable(object)->rows();
402 searchChildren = object->children();
404 size_t childrenSize = searchChildren.size();
406 size_t startIndex = isForward ? childrenSize : 0;
407 size_t endIndex = isForward ? 0 : childrenSize;
409 size_t searchPosition = startObject ? searchChildren.find(startObject) : WTF::notFound;
410 if (searchPosition != WTF::notFound) {
412 endIndex = searchPosition + 1;
414 endIndex = searchPosition;
417 // This is broken into two statements so that it's easier read.
419 for (size_t i = startIndex; i > endIndex; i--)
420 appendAccessibilityObject(searchChildren.at(i - 1).get(), results);
422 for (size_t i = startIndex; i < endIndex; i++)
423 appendAccessibilityObject(searchChildren.at(i).get(), results);
427 // Returns true if the number of results is now >= the number of results desired.
428 bool AccessibilityObject::objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject* object, AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
430 if (isAccessibilityObjectSearchMatch(object, criteria) && isAccessibilityTextSearchMatch(object, criteria)) {
431 results.append(object);
433 // Enough results were found to stop searching.
434 if (results.size() >= criteria->resultsLimit)
441 void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* criteria, AccessibilityChildrenVector& results)
448 // This search mechanism only searches the elements before/after the starting object.
449 // It does this by stepping up the parent chain and at each level doing a DFS.
451 // If there's no start object, it means we want to search everything.
452 AccessibilityObject* startObject = criteria->startObject;
456 bool isForward = criteria->searchDirection == SearchDirectionNext;
458 // In the first iteration of the loop, it will examine the children of the start object for matches.
459 // However, when going backwards, those children should not be considered, so the loop is skipped ahead.
460 AccessibilityObject* previousObject = 0;
462 previousObject = startObject;
463 startObject = startObject->parentObjectUnignored();
466 // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice)
467 for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) {
469 // Only append the children after/before the previous element, so that the search does not check elements that are
470 // already behind/ahead of start element.
471 AccessibilityChildrenVector searchStack;
472 appendChildrenToArray(startObject, isForward, previousObject, searchStack);
474 // This now does a DFS at the current level of the parent.
475 while (!searchStack.isEmpty()) {
476 AccessibilityObject* searchObject = searchStack.last().get();
477 searchStack.removeLast();
479 if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results))
482 appendChildrenToArray(searchObject, isForward, 0, searchStack);
485 if (results.size() >= criteria->resultsLimit)
488 // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element.
489 if (!isForward && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results))
492 previousObject = startObject;
496 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
498 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
501 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
503 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
504 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
507 IntPoint AccessibilityObject::clickPoint()
509 LayoutRect rect = elementRect();
510 return roundedIntPoint(LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2));
513 IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector<FloatQuad>& quads)
519 size_t count = quads.size();
524 for (size_t i = 0; i < count; ++i) {
525 IntRect r = quads[i].enclosingBoundingBox();
527 if (obj->style()->hasAppearance())
528 obj->theme()->adjustRepaintRect(obj, r);
535 bool AccessibilityObject::press() const
537 Element* actionElem = actionElement();
540 if (Frame* f = actionElem->document()->frame())
541 f->loader()->resetMultipleFormSubmissionProtection();
542 actionElem->accessKeyAction(true);
546 String AccessibilityObject::language() const
548 const AtomicString& lang = getAttribute(langAttr);
552 AccessibilityObject* parent = parentObject();
554 // as a last resort, fall back to the content language specified in the meta tag
556 Document* doc = document();
558 return doc->contentLanguage();
562 return parent->language();
565 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
567 if (visiblePos1.isNull() || visiblePos2.isNull())
568 return VisiblePositionRange();
570 VisiblePosition startPos;
571 VisiblePosition endPos;
574 // upstream is ordered before downstream for the same position
575 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
576 alreadyInOrder = false;
578 // use selection order to see if the positions are in order
580 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
582 if (alreadyInOrder) {
583 startPos = visiblePos1;
584 endPos = visiblePos2;
586 startPos = visiblePos2;
587 endPos = visiblePos1;
590 return VisiblePositionRange(startPos, endPos);
593 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
595 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
596 VisiblePosition endPosition = endOfWord(startPosition);
597 return VisiblePositionRange(startPosition, endPosition);
600 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
602 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
603 VisiblePosition endPosition = endOfWord(startPosition);
604 return VisiblePositionRange(startPosition, endPosition);
607 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
609 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
610 // So let's update the position to include that.
611 VisiblePosition tempPosition;
612 VisiblePosition startPosition = visiblePosition;
614 tempPosition = startPosition.previous();
615 if (tempPosition.isNull())
617 Position p = tempPosition.deepEquivalent();
618 RenderObject* renderer = p.deprecatedNode()->renderer();
619 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
621 if (!RenderedPosition(tempPosition).isNull())
623 startPosition = tempPosition;
626 return startPosition;
629 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
631 if (visiblePos.isNull())
632 return VisiblePositionRange();
634 // make a caret selection for the position before marker position (to make sure
635 // we move off of a line start)
636 VisiblePosition prevVisiblePos = visiblePos.previous();
637 if (prevVisiblePos.isNull())
638 return VisiblePositionRange();
640 VisiblePosition startPosition = startOfLine(prevVisiblePos);
642 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
643 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
644 // since floating object doesn't really belong to any line.
645 // This check will reposition the marker before the floating object, to ensure we get a line start.
646 if (startPosition.isNull()) {
647 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
648 prevVisiblePos = prevVisiblePos.previous();
649 startPosition = startOfLine(prevVisiblePos);
652 startPosition = updateAXLineStartForVisiblePosition(startPosition);
654 VisiblePosition endPosition = endOfLine(prevVisiblePos);
655 return VisiblePositionRange(startPosition, endPosition);
658 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
660 if (visiblePos.isNull())
661 return VisiblePositionRange();
663 // make sure we move off of a line end
664 VisiblePosition nextVisiblePos = visiblePos.next();
665 if (nextVisiblePos.isNull())
666 return VisiblePositionRange();
668 VisiblePosition startPosition = startOfLine(nextVisiblePos);
670 // fetch for a valid line start position
671 if (startPosition.isNull()) {
672 startPosition = visiblePos;
673 nextVisiblePos = nextVisiblePos.next();
675 startPosition = updateAXLineStartForVisiblePosition(startPosition);
677 VisiblePosition endPosition = endOfLine(nextVisiblePos);
679 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
680 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
681 // return null for position by a floating object, since floating object doesn't really belong to any line.
682 // This check will reposition the marker after the floating object, to ensure we get a line end.
683 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
684 nextVisiblePos = nextVisiblePos.next();
685 endPosition = endOfLine(nextVisiblePos);
688 return VisiblePositionRange(startPosition, endPosition);
691 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
693 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
694 // Related? <rdar://problem/3927736> Text selection broken in 8A336
695 VisiblePosition startPosition = startOfSentence(visiblePos);
696 VisiblePosition endPosition = endOfSentence(startPosition);
697 return VisiblePositionRange(startPosition, endPosition);
700 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
702 VisiblePosition startPosition = startOfParagraph(visiblePos);
703 VisiblePosition endPosition = endOfParagraph(startPosition);
704 return VisiblePositionRange(startPosition, endPosition);
707 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
709 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
710 RenderObject* startRenderer = renderer;
711 RenderStyle* style = renderer->style();
713 // traverse backward by renderer to look for style change
714 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
715 // skip non-leaf nodes
719 // stop at style change
720 if (r->style() != style)
727 return firstPositionInOrBeforeNode(startRenderer->node());
730 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
732 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
733 RenderObject* endRenderer = renderer;
734 RenderStyle* style = renderer->style();
736 // traverse forward by renderer to look for style change
737 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
738 // skip non-leaf nodes
742 // stop at style change
743 if (r->style() != style)
750 return lastPositionInOrAfterNode(endRenderer->node());
753 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
755 if (visiblePos.isNull())
756 return VisiblePositionRange();
758 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
761 // NOTE: Consider providing this utility method as AX API
762 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
764 unsigned textLength = getLengthForTextRange();
765 if (range.start + range.length > textLength)
766 return VisiblePositionRange();
768 VisiblePosition startPosition = visiblePositionForIndex(range.start);
769 startPosition.setAffinity(DOWNSTREAM);
770 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
771 return VisiblePositionRange(startPosition, endPosition);
774 static bool replacedNodeNeedsCharacter(Node* replacedNode)
776 // we should always be given a rendered node and a replaced node, but be safe
777 // replaced nodes are either attachments (widgets) or images
778 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode())
781 // create an AX object, but skip it if it is not supposed to be seen
782 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
783 if (object->accessibilityIsIgnored())
789 // Finds a RenderListItem parent give a node.
790 static RenderListItem* renderListItemContainerForNode(Node* node)
792 for (; node; node = node->parentNode()) {
793 RenderBoxModelObject* renderer = node->renderBoxModelObject();
794 if (renderer && renderer->isListItem())
795 return toRenderListItem(renderer);
800 // Returns the text associated with a list marker if this node is contained within a list item.
801 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
803 // If the range does not contain the start of the line, the list marker text should not be included.
804 if (!isStartOfLine(visiblePositionStart))
807 RenderListItem* listItem = renderListItemContainerForNode(node);
811 // If this is in a list item, we need to manually add the text for the list marker
812 // because a RenderListMarker does not have a Node equivalent and thus does not appear
813 // when iterating text.
814 const String& markerText = listItem->markerText();
815 if (markerText.isEmpty())
818 // Append text, plus the period that follows the text.
819 // FIXME: Not all list marker styles are followed by a period, but this
820 // sounds much better when there is a synthesized pause because of a period.
821 return markerText + ". ";
824 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
826 if (visiblePositionRange.isNull())
829 StringBuilder builder;
830 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
831 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
832 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
834 // Add a textual representation for list marker text
835 String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start);
836 if (!listMarkerText.isEmpty())
837 builder.append(listMarkerText);
839 builder.append(it.characters(), it.length());
841 // locate the node and starting offset for this replaced range
843 Node* node = it.range()->startContainer(exception);
844 ASSERT(node == it.range()->endContainer(exception));
845 int offset = it.range()->startOffset(exception);
847 if (replacedNodeNeedsCharacter(node->childNode(offset)))
848 builder.append(objectReplacementCharacter);
852 return builder.toString();
855 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
857 // FIXME: Multi-byte support
858 if (visiblePositionRange.isNull())
862 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
863 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
864 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
866 length += it.length();
868 // locate the node and starting offset for this replaced range
870 Node* node = it.range()->startContainer(exception);
871 ASSERT(node == it.range()->endContainer(exception));
872 int offset = it.range()->startOffset(exception);
874 if (replacedNodeNeedsCharacter(node->childNode(offset)))
882 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
884 if (visiblePos.isNull())
885 return VisiblePosition();
887 // make sure we move off of a word end
888 VisiblePosition nextVisiblePos = visiblePos.next();
889 if (nextVisiblePos.isNull())
890 return VisiblePosition();
892 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
895 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
897 if (visiblePos.isNull())
898 return VisiblePosition();
900 // make sure we move off of a word start
901 VisiblePosition prevVisiblePos = visiblePos.previous();
902 if (prevVisiblePos.isNull())
903 return VisiblePosition();
905 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
908 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
910 if (visiblePos.isNull())
911 return VisiblePosition();
913 // to make sure we move off of a line end
914 VisiblePosition nextVisiblePos = visiblePos.next();
915 if (nextVisiblePos.isNull())
916 return VisiblePosition();
918 VisiblePosition endPosition = endOfLine(nextVisiblePos);
920 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
921 // 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.
922 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
923 nextVisiblePos = nextVisiblePos.next();
924 endPosition = endOfLine(nextVisiblePos);
930 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
932 if (visiblePos.isNull())
933 return VisiblePosition();
935 // make sure we move off of a line start
936 VisiblePosition prevVisiblePos = visiblePos.previous();
937 if (prevVisiblePos.isNull())
938 return VisiblePosition();
940 VisiblePosition startPosition = startOfLine(prevVisiblePos);
942 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
943 // 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.
944 if (startPosition.isNull()) {
945 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
946 prevVisiblePos = prevVisiblePos.previous();
947 startPosition = startOfLine(prevVisiblePos);
950 startPosition = updateAXLineStartForVisiblePosition(startPosition);
952 return startPosition;
955 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
957 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
958 // Related? <rdar://problem/3927736> Text selection broken in 8A336
959 if (visiblePos.isNull())
960 return VisiblePosition();
962 // make sure we move off of a sentence end
963 VisiblePosition nextVisiblePos = visiblePos.next();
964 if (nextVisiblePos.isNull())
965 return VisiblePosition();
967 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
968 // see this empty line. Instead, return the end position of the empty line.
969 VisiblePosition endPosition;
971 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
972 if (lineString.isEmpty())
973 endPosition = nextVisiblePos;
975 endPosition = endOfSentence(nextVisiblePos);
980 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
982 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
983 // Related? <rdar://problem/3927736> Text selection broken in 8A336
984 if (visiblePos.isNull())
985 return VisiblePosition();
987 // make sure we move off of a sentence start
988 VisiblePosition previousVisiblePos = visiblePos.previous();
989 if (previousVisiblePos.isNull())
990 return VisiblePosition();
992 // treat empty line as a separate sentence.
993 VisiblePosition startPosition;
995 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
996 if (lineString.isEmpty())
997 startPosition = previousVisiblePos;
999 startPosition = startOfSentence(previousVisiblePos);
1001 return startPosition;
1004 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
1006 if (visiblePos.isNull())
1007 return VisiblePosition();
1009 // make sure we move off of a paragraph end
1010 VisiblePosition nextPos = visiblePos.next();
1011 if (nextPos.isNull())
1012 return VisiblePosition();
1014 return endOfParagraph(nextPos);
1017 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
1019 if (visiblePos.isNull())
1020 return VisiblePosition();
1022 // make sure we move off of a paragraph start
1023 VisiblePosition previousPos = visiblePos.previous();
1024 if (previousPos.isNull())
1025 return VisiblePosition();
1027 return startOfParagraph(previousPos);
1030 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
1032 if (visiblePos.isNull())
1035 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer();
1039 return obj->document()->axObjectCache()->getOrCreate(obj);
1042 #if HAVE(ACCESSIBILITY)
1043 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
1045 if (visiblePos.isNull() || !node())
1048 // If the position is not in the same editable region as this AX object, return -1.
1049 Node* containerNode = visiblePos.deepEquivalent().containerNode();
1050 if (!containerNode->containsIncludingShadowDOM(node()) && !node()->containsIncludingShadowDOM(containerNode))
1054 VisiblePosition currentVisiblePos = visiblePos;
1055 VisiblePosition savedVisiblePos;
1057 // move up until we get to the top
1058 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
1061 savedVisiblePos = currentVisiblePos;
1062 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0, HasEditableAXRole);
1063 currentVisiblePos = prevVisiblePos;
1065 } while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos)));
1071 // NOTE: Consider providing this utility method as AX API
1072 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
1074 int index1 = index(positionRange.start);
1075 int index2 = index(positionRange.end);
1076 if (index1 < 0 || index2 < 0 || index1 > index2)
1077 return PlainTextRange();
1079 return PlainTextRange(index1, index2 - index1);
1082 // The composed character range in the text associated with this accessibility object that
1083 // is specified by the given screen coordinates. This parameterized attribute returns the
1084 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
1085 // screen coordinates.
1086 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
1087 // an error in that case. We return textControl->text().length(), 1. Does this matter?
1088 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
1090 int i = index(visiblePositionForPoint(point));
1092 return PlainTextRange();
1094 return PlainTextRange(i, 1);
1097 // Given a character index, the range of text associated with this accessibility object
1098 // over which the style in effect at that character index applies.
1099 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
1101 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
1102 return plainTextRangeForVisiblePositionRange(range);
1105 // Given an indexed character, the line number of the text associated with this accessibility
1106 // object that contains the character.
1107 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
1109 return lineForPosition(visiblePositionForIndex(index, false));
1112 #if HAVE(ACCESSIBILITY)
1113 void AccessibilityObject::updateBackingStore()
1115 // Updating the layout may delete this object.
1116 if (Document* document = this->document())
1117 document->updateLayoutIgnorePendingStylesheets();
1121 Document* AccessibilityObject::document() const
1123 FrameView* frameView = documentFrameView();
1127 return frameView->frame()->document();
1130 Page* AccessibilityObject::page() const
1132 Document* document = this->document();
1135 return document->page();
1138 FrameView* AccessibilityObject::documentFrameView() const
1140 const AccessibilityObject* object = this;
1141 while (object && !object->isAccessibilityRenderObject())
1142 object = object->parentObject();
1147 return object->documentFrameView();
1150 #if HAVE(ACCESSIBILITY)
1151 const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children()
1153 updateChildrenIfNecessary();
1159 void AccessibilityObject::updateChildrenIfNecessary()
1165 void AccessibilityObject::clearChildren()
1167 // Some objects have weak pointers to their parents and those associations need to be detached.
1168 size_t length = m_children.size();
1169 for (size_t i = 0; i < length; i++)
1170 m_children[i]->detachFromParent();
1173 m_haveChildren = false;
1176 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
1178 RenderObject* obj = node->renderer();
1182 RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
1183 Element* anchor = axObj->anchorElement();
1187 RenderObject* anchorRenderer = anchor->renderer();
1188 if (!anchorRenderer)
1191 return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
1194 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
1196 AccessibilityChildrenVector axChildren = children();
1197 unsigned count = axChildren.size();
1198 for (unsigned k = 0; k < count; ++k) {
1199 AccessibilityObject* obj = axChildren[k].get();
1201 // Add tree items as the rows.
1202 if (obj->roleValue() == TreeItemRole)
1205 // Now see if this item also has rows hiding inside of it.
1206 obj->ariaTreeRows(result);
1210 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
1212 // The ARIA tree item content are the item that are not other tree items or their containing groups.
1213 AccessibilityChildrenVector axChildren = children();
1214 unsigned count = axChildren.size();
1215 for (unsigned k = 0; k < count; ++k) {
1216 AccessibilityObject* obj = axChildren[k].get();
1217 AccessibilityRole role = obj->roleValue();
1218 if (role == TreeItemRole || role == GroupRole)
1225 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
1227 AccessibilityChildrenVector axChildren = children();
1228 unsigned count = axChildren.size();
1229 for (unsigned k = 0; k < count; ++k) {
1230 AccessibilityObject* obj = axChildren[k].get();
1232 // Add tree items as the rows.
1233 if (obj->roleValue() == TreeItemRole)
1235 // If it's not a tree item, then descend into the group to find more tree items.
1237 obj->ariaTreeRows(result);
1241 #if HAVE(ACCESSIBILITY)
1242 const String& AccessibilityObject::actionVerb() const
1244 // FIXME: Need to add verbs for select elements.
1245 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
1246 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
1247 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
1248 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
1249 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
1250 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
1251 DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb()));
1252 DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb()));
1253 DEFINE_STATIC_LOCAL(const String, noAction, ());
1255 switch (roleValue()) {
1257 return buttonAction;
1260 return textFieldAction;
1261 case RadioButtonRole:
1262 return radioButtonAction;
1264 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
1266 case WebCoreLinkRole:
1268 case PopUpButtonRole:
1269 return menuListAction;
1270 case MenuListPopupRole:
1271 return menuListPopupAction;
1278 bool AccessibilityObject::ariaIsMultiline() const
1280 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
1283 const AtomicString& AccessibilityObject::invalidStatus() const
1285 DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false"));
1287 // aria-invalid can return false (default), grammer, spelling, or true.
1288 const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr);
1290 // If empty or not present, it should return false.
1291 if (ariaInvalid.isEmpty())
1292 return invalidStatusFalse;
1297 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
1299 Node* elementNode = node();
1303 if (!elementNode->isElementNode())
1306 Element* element = static_cast<Element*>(elementNode);
1307 return element->fastGetAttribute(attribute);
1310 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
1311 AccessibilityOrientation AccessibilityObject::orientation() const
1313 LayoutRect bounds = elementRect();
1314 if (bounds.size().width() > bounds.size().height())
1315 return AccessibilityOrientationHorizontal;
1316 if (bounds.size().height() > bounds.size().width())
1317 return AccessibilityOrientationVertical;
1319 // A tie goes to horizontal.
1320 return AccessibilityOrientationHorizontal;
1323 bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const
1325 if (!axObject || !axObject->hasChildren())
1328 for (const AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
1329 if (parent == axObject)
1335 bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const
1340 return this == axObject || axObject->isDescendantOfObject(this);
1343 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
1347 AccessibilityRole webcoreRole;
1350 static ARIARoleMap* createARIARoleMap()
1352 const RoleEntry roles[] = {
1353 { "alert", ApplicationAlertRole },
1354 { "alertdialog", ApplicationAlertDialogRole },
1355 { "application", LandmarkApplicationRole },
1356 { "article", DocumentArticleRole },
1357 { "banner", LandmarkBannerRole },
1358 { "button", ButtonRole },
1359 { "checkbox", CheckBoxRole },
1360 { "complementary", LandmarkComplementaryRole },
1361 { "contentinfo", LandmarkContentInfoRole },
1362 { "dialog", ApplicationDialogRole },
1363 { "directory", DirectoryRole },
1364 { "grid", TableRole },
1365 { "gridcell", CellRole },
1366 { "columnheader", ColumnHeaderRole },
1367 { "combobox", ComboBoxRole },
1368 { "definition", DefinitionListDefinitionRole },
1369 { "document", DocumentRole },
1370 { "rowheader", RowHeaderRole },
1371 { "group", GroupRole },
1372 { "heading", HeadingRole },
1373 { "img", ImageRole },
1374 { "link", WebCoreLinkRole },
1375 { "list", ListRole },
1376 { "listitem", ListItemRole },
1377 { "listbox", ListBoxRole },
1378 { "log", ApplicationLogRole },
1379 // "option" isn't here because it may map to different roles depending on the parent element's role
1380 { "main", LandmarkMainRole },
1381 { "marquee", ApplicationMarqueeRole },
1382 { "math", DocumentMathRole },
1383 { "menu", MenuRole },
1384 { "menubar", MenuBarRole },
1385 { "menuitem", MenuItemRole },
1386 { "menuitemcheckbox", MenuItemRole },
1387 { "menuitemradio", MenuItemRole },
1388 { "note", DocumentNoteRole },
1389 { "navigation", LandmarkNavigationRole },
1390 { "option", ListBoxOptionRole },
1391 { "presentation", PresentationalRole },
1392 { "progressbar", ProgressIndicatorRole },
1393 { "radio", RadioButtonRole },
1394 { "radiogroup", RadioGroupRole },
1395 { "region", DocumentRegionRole },
1397 { "range", SliderRole },
1398 { "scrollbar", ScrollBarRole },
1399 { "search", LandmarkSearchRole },
1400 { "separator", SplitterRole },
1401 { "slider", SliderRole },
1402 { "spinbutton", SpinButtonRole },
1403 { "status", ApplicationStatusRole },
1405 { "tablist", TabListRole },
1406 { "tabpanel", TabPanelRole },
1407 { "text", StaticTextRole },
1408 { "textbox", TextAreaRole },
1409 { "timer", ApplicationTimerRole },
1410 { "toolbar", ToolbarRole },
1411 { "tooltip", UserInterfaceTooltipRole },
1412 { "tree", TreeRole },
1413 { "treegrid", TreeGridRole },
1414 { "treeitem", TreeItemRole }
1416 ARIARoleMap* roleMap = new ARIARoleMap;
1418 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i)
1419 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
1423 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
1425 ASSERT(!value.isEmpty());
1427 static const ARIARoleMap* roleMap = createARIARoleMap();
1429 Vector<String> roleVector;
1430 value.split(' ', roleVector);
1431 AccessibilityRole role = UnknownRole;
1432 unsigned size = roleVector.size();
1433 for (unsigned i = 0; i < size; ++i) {
1434 String roleName = roleVector[i];
1435 role = roleMap->get(roleName);
1443 bool AccessibilityObject::hasHighlighting() const
1445 for (Node* node = this->node(); node; node = node->parentNode()) {
1446 if (node->hasTagName(markTag))
1453 const AtomicString& AccessibilityObject::placeholderValue() const
1455 const AtomicString& placeholder = getAttribute(placeholderAttr);
1456 if (!placeholder.isEmpty())
1462 bool AccessibilityObject::isInsideARIALiveRegion() const
1464 if (supportsARIALiveRegion())
1467 for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
1468 if (axParent->supportsARIALiveRegion())
1475 bool AccessibilityObject::supportsARIAAttributes() const
1477 return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns();
1480 bool AccessibilityObject::supportsARIALiveRegion() const
1482 const AtomicString& liveRegion = ariaLiveRegionStatus();
1483 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
1486 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const
1488 // Send the hit test back into the sub-frame if necessary.
1489 if (isAttachment()) {
1490 Widget* widget = widgetForAttachmentView();
1491 // Normalize the point for the widget's bounds.
1492 if (widget && widget->isFrameView())
1493 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(toPoint(point - widget->frameRect().location()));
1496 // Check if there are any mock elements that need to be handled.
1497 size_t count = m_children.size();
1498 for (size_t k = 0; k < count; k++) {
1499 if (m_children[k]->isMockObject() && m_children[k]->elementRect().contains(point))
1500 return m_children[k]->elementAccessibilityHitTest(point);
1503 return const_cast<AccessibilityObject*>(this);
1506 AXObjectCache* AccessibilityObject::axObjectCache() const
1508 Document* doc = document();
1510 return doc->axObjectCache();
1514 AccessibilityObject* AccessibilityObject::focusedUIElement() const
1516 Document* doc = document();
1520 Page* page = doc->page();
1524 return AXObjectCache::focusedUIElementForPage(page);
1527 AccessibilitySortDirection AccessibilityObject::sortDirection() const
1529 const AtomicString& sortAttribute = getAttribute(aria_sortAttr);
1530 if (equalIgnoringCase(sortAttribute, "ascending"))
1531 return SortDirectionAscending;
1532 if (equalIgnoringCase(sortAttribute, "descending"))
1533 return SortDirectionDescending;
1535 return SortDirectionNone;
1538 bool AccessibilityObject::supportsARIAExpanded() const
1540 return !getAttribute(aria_expandedAttr).isEmpty();
1543 bool AccessibilityObject::isExpanded() const
1545 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
1551 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
1553 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
1554 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used.
1556 const AtomicString& result = getAttribute(aria_checkedAttr);
1557 if (equalIgnoringCase(result, "true"))
1558 return ButtonStateOn;
1559 if (equalIgnoringCase(result, "mixed"))
1560 return ButtonStateMixed;
1562 return ButtonStateOff;
1565 // This is a 1-dimensional scroll offset helper function that's applied
1566 // separately in the horizontal and vertical directions, because the
1567 // logic is the same. The goal is to compute the best scroll offset
1568 // in order to make an object visible within a viewport.
1570 // In case the whole object cannot fit, you can specify a
1571 // subfocus - a smaller region within the object that should
1572 // be prioritized. If the whole object can fit, the subfocus is
1575 // Example: the viewport is scrolled to the right just enough
1576 // that the object is in view.
1578 // +----------Viewport---------+
1583 // +----------Viewport---------+
1587 // When constraints cannot be fully satisfied, the min
1588 // (left/top) position takes precedence over the max (right/bottom).
1590 // Note that the return value represents the ideal new scroll offset.
1591 // This may be out of range - the calling function should clip this
1592 // to the available range.
1593 static int computeBestScrollOffset(int currentScrollOffset,
1594 int subfocusMin, int subfocusMax,
1595 int objectMin, int objectMax,
1596 int viewportMin, int viewportMax) {
1597 int viewportSize = viewportMax - viewportMin;
1599 // If the focus size is larger than the viewport size, shrink it in the
1600 // direction of subfocus.
1601 if (objectMax - objectMin > viewportSize) {
1602 // Subfocus must be within focus:
1603 subfocusMin = std::max(subfocusMin, objectMin);
1604 subfocusMax = std::min(subfocusMax, objectMax);
1606 // Subfocus must be no larger than the viewport size; favor top/left.
1607 if (subfocusMax - subfocusMin > viewportSize)
1608 subfocusMax = subfocusMin + viewportSize;
1610 if (subfocusMin + viewportSize > objectMax)
1611 objectMin = objectMax - viewportSize;
1613 objectMin = subfocusMin;
1614 objectMax = subfocusMin + viewportSize;
1618 // Exit now if the focus is already within the viewport.
1619 if (objectMin - currentScrollOffset >= viewportMin
1620 && objectMax - currentScrollOffset <= viewportMax)
1621 return currentScrollOffset;
1623 // Scroll left if we're too far to the right.
1624 if (objectMax - currentScrollOffset > viewportMax)
1625 return objectMax - viewportMax;
1627 // Scroll right if we're too far to the left.
1628 if (objectMin - currentScrollOffset < viewportMin)
1629 return objectMin - viewportMin;
1631 ASSERT_NOT_REACHED();
1633 // This shouldn't happen.
1634 return currentScrollOffset;
1637 void AccessibilityObject::scrollToMakeVisible() const
1639 IntRect objectRect = pixelSnappedIntRect(boundingBoxRect());
1640 objectRect.setLocation(IntPoint());
1641 scrollToMakeVisibleWithSubFocus(objectRect);
1644 void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const
1646 // Search up the parent chain until we find the first one that's scrollable.
1647 AccessibilityObject* scrollParent = parentObject();
1648 ScrollableArea* scrollableArea;
1649 for (scrollableArea = 0;
1650 scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable());
1651 scrollParent = scrollParent->parentObject()) { }
1652 if (!scrollableArea)
1655 LayoutRect objectRect = boundingBoxRect();
1656 IntPoint scrollPosition = scrollableArea->scrollPosition();
1657 IntRect scrollVisibleRect = scrollableArea->visibleContentRect();
1659 int desiredX = computeBestScrollOffset(
1661 objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(),
1662 objectRect.x(), objectRect.maxX(),
1663 0, scrollVisibleRect.width());
1664 int desiredY = computeBestScrollOffset(
1666 objectRect.y() + subfocus.y(), objectRect.y() + subfocus.maxY(),
1667 objectRect.y(), objectRect.maxY(),
1668 0, scrollVisibleRect.height());
1670 scrollParent->scrollTo(IntPoint(desiredX, desiredY));
1672 // Recursively make sure the scroll parent itself is visible.
1673 if (scrollParent->parentObject())
1674 scrollParent->scrollToMakeVisible();
1677 void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const
1679 // Search up the parent chain and create a vector of all scrollable parent objects
1680 // and ending with this object itself.
1681 Vector<const AccessibilityObject*> objects;
1682 AccessibilityObject* parentObject;
1683 for (parentObject = this->parentObject(); parentObject; parentObject = parentObject->parentObject()) {
1684 if (parentObject->getScrollableAreaIfScrollable())
1685 objects.prepend(parentObject);
1687 objects.append(this);
1689 // Start with the outermost scrollable (the main window) and try to scroll the
1690 // next innermost object to the given point.
1691 int offsetX = 0, offsetY = 0;
1692 IntPoint point = globalPoint;
1693 size_t levels = objects.size() - 1;
1694 for (size_t i = 0; i < levels; i++) {
1695 const AccessibilityObject* outer = objects[i];
1696 const AccessibilityObject* inner = objects[i + 1];
1698 ScrollableArea* scrollableArea = outer->getScrollableAreaIfScrollable();
1700 LayoutRect innerRect = inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect();
1701 LayoutRect objectRect = innerRect;
1702 IntPoint scrollPosition = scrollableArea->scrollPosition();
1704 // Convert the object rect into local coordinates.
1705 objectRect.move(offsetX, offsetY);
1706 if (!outer->isAccessibilityScrollView())
1707 objectRect.move(scrollPosition.x(), scrollPosition.y());
1709 int desiredX = computeBestScrollOffset(
1711 objectRect.x(), objectRect.maxX(),
1712 objectRect.x(), objectRect.maxX(),
1713 point.x(), point.x());
1714 int desiredY = computeBestScrollOffset(
1716 objectRect.y(), objectRect.maxY(),
1717 objectRect.y(), objectRect.maxY(),
1718 point.y(), point.y());
1719 outer->scrollTo(IntPoint(desiredX, desiredY));
1721 if (outer->isAccessibilityScrollView() && !inner->isAccessibilityScrollView()) {
1722 // If outer object we just scrolled is a scroll view (main window or iframe) but the
1723 // inner object is not, keep track of the coordinate transformation to apply to
1724 // future nested calculations.
1725 scrollPosition = scrollableArea->scrollPosition();
1726 offsetX -= (scrollPosition.x() + point.x());
1727 offsetY -= (scrollPosition.y() + point.y());
1728 point.move(scrollPosition.x() - innerRect.x(),
1729 scrollPosition.y() - innerRect.y());
1730 } else if (inner->isAccessibilityScrollView()) {
1731 // Otherwise, if the inner object is a scroll view, reset the coordinate transformation.
1738 } // namespace WebCore