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 "FloatRect.h"
35 #include "FocusController.h"
37 #include "FrameLoader.h"
38 #include "FrameSelection.h"
39 #include "HTMLNames.h"
40 #include "LocalizedStrings.h"
42 #include "NotImplemented.h"
44 #include "RenderImage.h"
45 #include "RenderListItem.h"
46 #include "RenderListMarker.h"
47 #include "RenderMenuList.h"
48 #include "RenderTextControl.h"
49 #include "RenderTheme.h"
50 #include "RenderView.h"
51 #include "RenderWidget.h"
52 #include "RenderedPosition.h"
53 #include "TextCheckerClient.h"
54 #include "TextIterator.h"
55 #include "htmlediting.h"
56 #include "visible_units.h"
57 #include <wtf/StdLibExtras.h>
58 #include <wtf/text/StringBuilder.h>
59 #include <wtf/text/WTFString.h>
60 #include <wtf/unicode/CharacterNames.h>
66 using namespace HTMLNames;
68 AccessibilityObject::AccessibilityObject()
70 , m_haveChildren(false)
78 AccessibilityObject::~AccessibilityObject()
83 void AccessibilityObject::detach()
85 #if HAVE(ACCESSIBILITY)
90 bool AccessibilityObject::isAccessibilityObjectSearchMatch(AccessibilityObject* axObject, AccessibilitySearchPredicate* axSearchPredicate)
92 if (!axObject || !axSearchPredicate)
95 switch (axSearchPredicate->axSearchKey) {
96 // The AnyTypeSearchKey matches any non-null AccessibilityObject.
97 case AnyTypeSearchKey:
100 case BlockquoteSameLevelSearchKey:
101 return axSearchPredicate->axStartObject
102 && axObject->isBlockquote()
103 && axObject->blockquoteLevel() == axSearchPredicate->axStartObject->blockquoteLevel();
105 case BlockquoteSearchKey:
106 return axObject->isBlockquote();
108 case BoldFontSearchKey:
109 return axObject->hasBoldFont();
111 case ButtonSearchKey:
112 return axObject->isButton();
114 case CheckBoxSearchKey:
115 return axObject->isCheckbox();
117 case ControlSearchKey:
118 return axObject->isControl();
120 case DifferentTypeSearchKey:
121 return axSearchPredicate->axStartObject
122 && axObject->roleValue() != axSearchPredicate->axStartObject->roleValue();
124 case FontChangeSearchKey:
125 return axSearchPredicate->axStartObject
126 && !axObject->hasSameFont(axSearchPredicate->axStartObject->renderer());
128 case FontColorChangeSearchKey:
129 return axSearchPredicate->axStartObject
130 && !axObject->hasSameFontColor(axSearchPredicate->axStartObject->renderer());
132 // FIXME: Handle this search key.
136 case GraphicSearchKey:
137 return axObject->isImage();
139 case HeadingLevel1SearchKey:
140 return axObject->headingLevel() == 1;
142 case HeadingLevel2SearchKey:
143 return axObject->headingLevel() == 2;
145 case HeadingLevel3SearchKey:
146 return axObject->headingLevel() == 3;
148 case HeadingLevel4SearchKey:
149 return axObject->headingLevel() == 4;
151 case HeadingLevel5SearchKey:
152 return axObject->headingLevel() == 5;
154 case HeadingLevel6SearchKey:
155 return axObject->headingLevel() == 6;
157 case HeadingSameLevelSearchKey:
158 return axSearchPredicate->axStartObject
159 && axObject->isHeading()
160 && axObject->headingLevel() == axSearchPredicate->axStartObject->headingLevel();
162 case HeadingSearchKey:
163 return axObject->isHeading();
165 case ItalicFontSearchKey:
166 return axObject->hasItalicFont();
168 case LandmarkSearchKey:
169 return axObject->isLandmark();
172 return axObject->isLink();
175 return axObject->isList();
177 case LiveRegionSearchKey:
178 return axObject->supportsARIALiveRegion();
180 case MisspelledWordSearchKey:
181 return axObject->hasMisspelling();
183 case PlainTextSearchKey:
184 return axObject->hasPlainText();
186 case RadioGroupSearchKey:
187 return axObject->isRadioGroup();
189 case SameTypeSearchKey:
190 return axSearchPredicate->axStartObject
191 && axObject->roleValue() == axSearchPredicate->axStartObject->roleValue();
193 case StaticTextSearchKey:
194 return axObject->hasStaticText();
196 case StyleChangeSearchKey:
197 return axSearchPredicate->axStartObject
198 && !axObject->hasSameStyle(axSearchPredicate->axStartObject->renderer());
200 case TableSameLevelSearchKey:
201 return axSearchPredicate->axStartObject
202 && axObject->isAccessibilityTable()
203 && axObject->tableLevel() == axSearchPredicate->axStartObject->tableLevel();
206 return axObject->isAccessibilityTable();
208 case TextFieldSearchKey:
209 return axObject->isTextControl();
211 case UnderlineSearchKey:
212 return axObject->hasUnderline();
214 case UnvisitedLinkSearchKey:
215 return axObject->isUnvisited();
217 case VisitedLinkSearchKey:
218 return axObject->isVisited();
225 bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* axObject, AccessibilitySearchPredicate* axSearchPredicate)
227 if (!axObject || !axSearchPredicate)
230 return axObject->accessibilityObjectContainsText(axSearchPredicate->searchText);
233 bool AccessibilityObject::accessibilityObjectContainsText(String* text) const
235 // If text is null or empty we return true.
238 || title().contains(*text, false)
239 || accessibilityDescription().contains(*text, false)
240 || stringValue().contains(*text, false);
243 bool AccessibilityObject::isBlockquote() const
245 return node() && node()->hasTagName(blockquoteTag);
248 bool AccessibilityObject::isLandmark() const
250 AccessibilityRole role = roleValue();
252 return role == LandmarkApplicationRole
253 || role == LandmarkBannerRole
254 || role == LandmarkComplementaryRole
255 || role == LandmarkContentInfoRole
256 || role == LandmarkMainRole
257 || role == LandmarkNavigationRole
258 || role == LandmarkSearchRole;
261 bool AccessibilityObject::hasMisspelling() const
266 Document* document = node()->document();
270 Frame* frame = document->frame();
274 Editor* editor = frame->editor();
278 TextCheckerClient* textChecker = editor->textChecker();
282 const UChar* chars = stringValue().characters();
283 int charsLength = stringValue().length();
284 bool isMisspelled = false;
286 #if USE(UNIFIED_TEXT_CHECKING)
287 Vector<TextCheckingResult> results;
288 textChecker->checkTextOfParagraph(chars, charsLength, TextCheckingTypeSpelling, results);
289 if (!results.isEmpty())
292 int misspellingLength = 0;
293 int misspellingLocation = -1;
294 textChecker->checkSpellingOfString(chars, charsLength, &misspellingLocation, &misspellingLength);
295 if (misspellingLength || misspellingLocation != -1)
302 int AccessibilityObject::blockquoteLevel() const
305 for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) {
306 if (elementNode->hasTagName(blockquoteTag))
313 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
315 AccessibilityObject* parent;
316 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
322 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
324 ASSERT(AXObjectCache::accessibilityEnabled());
329 Document* document = node->document();
333 AXObjectCache* cache = document->axObjectCache();
335 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
336 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
337 node = node->traverseNextNode();
339 while (node && !node->renderer())
340 node = node->traverseNextSibling();
345 accessibleObject = cache->getOrCreate(node->renderer());
348 return accessibleObject;
351 void AccessibilityObject::accessibleObjectsWithAccessibilitySearchPredicate(AccessibilitySearchPredicate* axSearchPredicate, AccessibilityChildrenVector& axResults)
353 ASSERT(AXObjectCache::accessibilityEnabled());
355 if (!axSearchPredicate)
358 AccessibilityChildrenVector axChildren;
359 if (axSearchPredicate->axContainerObject)
360 axChildren.append(axSearchPredicate->axContainerObject);
362 bool isSearchDirectionNext = (axSearchPredicate->axSearchDirection == SearchDirectionNext);
363 bool didFindAXStartObject = (!axSearchPredicate->axStartObject);
365 // FIXME: Iterate the AccessibilityObject cache creating and adding objects if nessesary.
366 while (!axChildren.isEmpty() && axResults.size() < axSearchPredicate->resultsLimit) {
367 AccessibilityObject* axChild = axChildren.last().get();
368 axChildren.removeLast();
370 if (didFindAXStartObject) {
371 if (isAccessibilityObjectSearchMatch(axChild, axSearchPredicate)
372 && isAccessibilityTextSearchMatch(axChild, axSearchPredicate))
373 axResults.append(axChild);
374 } else if (axChild == axSearchPredicate->axStartObject)
375 didFindAXStartObject = true;
377 AccessibilityChildrenVector axGrandchildren = axChild->children();
378 unsigned axGrandchildrenSize = axChild->children().size();
379 for (unsigned i = (isSearchDirectionNext) ? axGrandchildrenSize : 0; (isSearchDirectionNext) ? i > 0 : i < axGrandchildrenSize; (isSearchDirectionNext) ? i-- : i++)
380 // FIXME: Handle attachments.
381 axChildren.append(axGrandchildren.at((isSearchDirectionNext) ? i - 1 : i).get());
385 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
387 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
390 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
392 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
393 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
396 LayoutPoint AccessibilityObject::clickPoint() const
398 LayoutRect rect = elementRect();
399 return LayoutPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
402 bool AccessibilityObject::press() const
404 Element* actionElem = actionElement();
407 if (Frame* f = actionElem->document()->frame())
408 f->loader()->resetMultipleFormSubmissionProtection();
409 actionElem->accessKeyAction(true);
413 String AccessibilityObject::language() const
415 const AtomicString& lang = getAttribute(langAttr);
419 AccessibilityObject* parent = parentObject();
421 // as a last resort, fall back to the content language specified in the meta tag
423 Document* doc = document();
425 return doc->contentLanguage();
429 return parent->language();
432 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
434 if (visiblePos1.isNull() || visiblePos2.isNull())
435 return VisiblePositionRange();
437 VisiblePosition startPos;
438 VisiblePosition endPos;
441 // upstream is ordered before downstream for the same position
442 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
443 alreadyInOrder = false;
445 // use selection order to see if the positions are in order
447 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
449 if (alreadyInOrder) {
450 startPos = visiblePos1;
451 endPos = visiblePos2;
453 startPos = visiblePos2;
454 endPos = visiblePos1;
457 return VisiblePositionRange(startPos, endPos);
460 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
462 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
463 VisiblePosition endPosition = endOfWord(startPosition);
464 return VisiblePositionRange(startPosition, endPosition);
467 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
469 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
470 VisiblePosition endPosition = endOfWord(startPosition);
471 return VisiblePositionRange(startPosition, endPosition);
474 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
476 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
477 // So let's update the position to include that.
478 VisiblePosition tempPosition;
479 VisiblePosition startPosition = visiblePosition;
481 tempPosition = startPosition.previous();
482 if (tempPosition.isNull() || tempPosition.isNull())
484 Position p = tempPosition.deepEquivalent();
485 RenderObject* renderer = p.deprecatedNode()->renderer();
486 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
488 if (!RenderedPosition(tempPosition).isNull())
490 startPosition = tempPosition;
493 return startPosition;
496 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
498 if (visiblePos.isNull())
499 return VisiblePositionRange();
501 // make a caret selection for the position before marker position (to make sure
502 // we move off of a line start)
503 VisiblePosition prevVisiblePos = visiblePos.previous();
504 if (prevVisiblePos.isNull())
505 return VisiblePositionRange();
507 VisiblePosition startPosition = startOfLine(prevVisiblePos);
509 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
510 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
511 // since floating object doesn't really belong to any line.
512 // This check will reposition the marker before the floating object, to ensure we get a line start.
513 if (startPosition.isNull()) {
514 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
515 prevVisiblePos = prevVisiblePos.previous();
516 startPosition = startOfLine(prevVisiblePos);
519 startPosition = updateAXLineStartForVisiblePosition(startPosition);
521 VisiblePosition endPosition = endOfLine(prevVisiblePos);
522 return VisiblePositionRange(startPosition, endPosition);
525 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
527 if (visiblePos.isNull())
528 return VisiblePositionRange();
530 // make sure we move off of a line end
531 VisiblePosition nextVisiblePos = visiblePos.next();
532 if (nextVisiblePos.isNull())
533 return VisiblePositionRange();
535 VisiblePosition startPosition = startOfLine(nextVisiblePos);
537 // fetch for a valid line start position
538 if (startPosition.isNull()) {
539 startPosition = visiblePos;
540 nextVisiblePos = nextVisiblePos.next();
542 startPosition = updateAXLineStartForVisiblePosition(startPosition);
544 VisiblePosition endPosition = endOfLine(nextVisiblePos);
546 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
547 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
548 // return null for position by a floating object, since floating object doesn't really belong to any line.
549 // This check will reposition the marker after the floating object, to ensure we get a line end.
550 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
551 nextVisiblePos = nextVisiblePos.next();
552 endPosition = endOfLine(nextVisiblePos);
555 return VisiblePositionRange(startPosition, endPosition);
558 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
560 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
561 // Related? <rdar://problem/3927736> Text selection broken in 8A336
562 VisiblePosition startPosition = startOfSentence(visiblePos);
563 VisiblePosition endPosition = endOfSentence(startPosition);
564 return VisiblePositionRange(startPosition, endPosition);
567 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
569 VisiblePosition startPosition = startOfParagraph(visiblePos);
570 VisiblePosition endPosition = endOfParagraph(startPosition);
571 return VisiblePositionRange(startPosition, endPosition);
574 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
576 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
577 RenderObject* startRenderer = renderer;
578 RenderStyle* style = renderer->style();
580 // traverse backward by renderer to look for style change
581 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
582 // skip non-leaf nodes
586 // stop at style change
587 if (r->style() != style)
594 return firstPositionInOrBeforeNode(startRenderer->node());
597 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
599 RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer();
600 RenderObject* endRenderer = renderer;
601 RenderStyle* style = renderer->style();
603 // traverse forward by renderer to look for style change
604 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
605 // skip non-leaf nodes
609 // stop at style change
610 if (r->style() != style)
617 return lastPositionInOrAfterNode(endRenderer->node());
620 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
622 if (visiblePos.isNull())
623 return VisiblePositionRange();
625 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
628 // NOTE: Consider providing this utility method as AX API
629 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
631 unsigned textLength = getLengthForTextRange();
632 if (range.start + range.length > textLength)
633 return VisiblePositionRange();
635 VisiblePosition startPosition = visiblePositionForIndex(range.start);
636 startPosition.setAffinity(DOWNSTREAM);
637 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
638 return VisiblePositionRange(startPosition, endPosition);
641 static bool replacedNodeNeedsCharacter(Node* replacedNode)
643 // we should always be given a rendered node and a replaced node, but be safe
644 // replaced nodes are either attachments (widgets) or images
645 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode())
648 // create an AX object, but skip it if it is not supposed to be seen
649 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
650 if (object->accessibilityIsIgnored())
656 // Finds a RenderListItem parent give a node.
657 static RenderListItem* renderListItemContainerForNode(Node* node)
659 for (; node; node = node->parentNode()) {
660 RenderBoxModelObject* renderer = node->renderBoxModelObject();
661 if (renderer && renderer->isListItem())
662 return toRenderListItem(renderer);
667 // Returns the text associated with a list marker if this node is contained within a list item.
668 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
670 // If the range does not contain the start of the line, the list marker text should not be included.
671 if (!isStartOfLine(visiblePositionStart))
674 RenderListItem* listItem = renderListItemContainerForNode(node);
678 // If this is in a list item, we need to manually add the text for the list marker
679 // because a RenderListMarker does not have a Node equivalent and thus does not appear
680 // when iterating text.
681 const String& markerText = listItem->markerText();
682 if (markerText.isEmpty())
685 // Append text, plus the period that follows the text.
686 // FIXME: Not all list marker styles are followed by a period, but this
687 // sounds much better when there is a synthesized pause because of a period.
688 return markerText + ". ";
691 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
693 if (visiblePositionRange.isNull())
696 StringBuilder builder;
697 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
698 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
699 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
701 // Add a textual representation for list marker text
702 String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start);
703 if (!listMarkerText.isEmpty())
704 builder.append(listMarkerText);
706 builder.append(it.characters(), it.length());
708 // locate the node and starting offset for this replaced range
710 Node* node = it.range()->startContainer(exception);
711 ASSERT(node == it.range()->endContainer(exception));
712 int offset = it.range()->startOffset(exception);
714 if (replacedNodeNeedsCharacter(node->childNode(offset)))
715 builder.append(objectReplacementCharacter);
719 return builder.toString();
722 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
724 // FIXME: Multi-byte support
725 if (visiblePositionRange.isNull())
729 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
730 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
731 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
733 length += it.length();
735 // locate the node and starting offset for this replaced range
737 Node* node = it.range()->startContainer(exception);
738 ASSERT(node == it.range()->endContainer(exception));
739 int offset = it.range()->startOffset(exception);
741 if (replacedNodeNeedsCharacter(node->childNode(offset)))
749 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
751 if (visiblePos.isNull())
752 return VisiblePosition();
754 // make sure we move off of a word end
755 VisiblePosition nextVisiblePos = visiblePos.next();
756 if (nextVisiblePos.isNull())
757 return VisiblePosition();
759 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
762 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
764 if (visiblePos.isNull())
765 return VisiblePosition();
767 // make sure we move off of a word start
768 VisiblePosition prevVisiblePos = visiblePos.previous();
769 if (prevVisiblePos.isNull())
770 return VisiblePosition();
772 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
775 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
777 if (visiblePos.isNull())
778 return VisiblePosition();
780 // to make sure we move off of a line end
781 VisiblePosition nextVisiblePos = visiblePos.next();
782 if (nextVisiblePos.isNull())
783 return VisiblePosition();
785 VisiblePosition endPosition = endOfLine(nextVisiblePos);
787 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
788 // 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.
789 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
790 nextVisiblePos = nextVisiblePos.next();
791 endPosition = endOfLine(nextVisiblePos);
797 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
799 if (visiblePos.isNull())
800 return VisiblePosition();
802 // make sure we move off of a line start
803 VisiblePosition prevVisiblePos = visiblePos.previous();
804 if (prevVisiblePos.isNull())
805 return VisiblePosition();
807 VisiblePosition startPosition = startOfLine(prevVisiblePos);
809 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
810 // 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.
811 if (startPosition.isNull()) {
812 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
813 prevVisiblePos = prevVisiblePos.previous();
814 startPosition = startOfLine(prevVisiblePos);
817 startPosition = updateAXLineStartForVisiblePosition(startPosition);
819 return startPosition;
822 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
824 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
825 // Related? <rdar://problem/3927736> Text selection broken in 8A336
826 if (visiblePos.isNull())
827 return VisiblePosition();
829 // make sure we move off of a sentence end
830 VisiblePosition nextVisiblePos = visiblePos.next();
831 if (nextVisiblePos.isNull())
832 return VisiblePosition();
834 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
835 // see this empty line. Instead, return the end position of the empty line.
836 VisiblePosition endPosition;
838 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
839 if (lineString.isEmpty())
840 endPosition = nextVisiblePos;
842 endPosition = endOfSentence(nextVisiblePos);
847 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
849 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
850 // Related? <rdar://problem/3927736> Text selection broken in 8A336
851 if (visiblePos.isNull())
852 return VisiblePosition();
854 // make sure we move off of a sentence start
855 VisiblePosition previousVisiblePos = visiblePos.previous();
856 if (previousVisiblePos.isNull())
857 return VisiblePosition();
859 // treat empty line as a separate sentence.
860 VisiblePosition startPosition;
862 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
863 if (lineString.isEmpty())
864 startPosition = previousVisiblePos;
866 startPosition = startOfSentence(previousVisiblePos);
868 return startPosition;
871 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
873 if (visiblePos.isNull())
874 return VisiblePosition();
876 // make sure we move off of a paragraph end
877 VisiblePosition nextPos = visiblePos.next();
878 if (nextPos.isNull())
879 return VisiblePosition();
881 return endOfParagraph(nextPos);
884 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
886 if (visiblePos.isNull())
887 return VisiblePosition();
889 // make sure we move off of a paragraph start
890 VisiblePosition previousPos = visiblePos.previous();
891 if (previousPos.isNull())
892 return VisiblePosition();
894 return startOfParagraph(previousPos);
897 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
899 if (visiblePos.isNull())
902 RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer();
906 return obj->document()->axObjectCache()->getOrCreate(obj);
909 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
911 if (visiblePos.isNull())
914 unsigned lineCount = 0;
915 VisiblePosition currentVisiblePos = visiblePos;
916 VisiblePosition savedVisiblePos;
918 // move up until we get to the top
919 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
921 while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) {
923 savedVisiblePos = currentVisiblePos;
924 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0);
925 currentVisiblePos = prevVisiblePos;
928 return lineCount - 1;
931 // NOTE: Consider providing this utility method as AX API
932 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
934 int index1 = index(positionRange.start);
935 int index2 = index(positionRange.end);
936 if (index1 < 0 || index2 < 0 || index1 > index2)
937 return PlainTextRange();
939 return PlainTextRange(index1, index2 - index1);
942 // The composed character range in the text associated with this accessibility object that
943 // is specified by the given screen coordinates. This parameterized attribute returns the
944 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
945 // screen coordinates.
946 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
947 // an error in that case. We return textControl->text().length(), 1. Does this matter?
948 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
950 int i = index(visiblePositionForPoint(point));
952 return PlainTextRange();
954 return PlainTextRange(i, 1);
957 // Given a character index, the range of text associated with this accessibility object
958 // over which the style in effect at that character index applies.
959 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
961 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
962 return plainTextRangeForVisiblePositionRange(range);
965 // Given an indexed character, the line number of the text associated with this accessibility
966 // object that contains the character.
967 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
969 return lineForPosition(visiblePositionForIndex(index, false));
972 void AccessibilityObject::updateBackingStore()
974 // Updating the layout may delete this object.
975 if (Document* document = this->document())
976 document->updateLayoutIgnorePendingStylesheets();
979 Document* AccessibilityObject::document() const
981 FrameView* frameView = documentFrameView();
985 return frameView->frame()->document();
988 Page* AccessibilityObject::page() const
990 Document* document = this->document();
993 return document->page();
996 FrameView* AccessibilityObject::documentFrameView() const
998 const AccessibilityObject* object = this;
999 while (object && !object->isAccessibilityRenderObject())
1000 object = object->parentObject();
1005 return object->documentFrameView();
1008 void AccessibilityObject::updateChildrenIfNecessary()
1014 void AccessibilityObject::clearChildren()
1017 m_haveChildren = false;
1020 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
1022 RenderObject* obj = node->renderer();
1026 RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
1027 Element* anchor = axObj->anchorElement();
1031 RenderObject* anchorRenderer = anchor->renderer();
1032 if (!anchorRenderer)
1035 return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
1038 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
1040 AccessibilityChildrenVector axChildren = children();
1041 unsigned count = axChildren.size();
1042 for (unsigned k = 0; k < count; ++k) {
1043 AccessibilityObject* obj = axChildren[k].get();
1045 // Add tree items as the rows.
1046 if (obj->roleValue() == TreeItemRole)
1049 // Now see if this item also has rows hiding inside of it.
1050 obj->ariaTreeRows(result);
1054 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
1056 // The ARIA tree item content are the item that are not other tree items or their containing groups.
1057 AccessibilityChildrenVector axChildren = children();
1058 unsigned count = axChildren.size();
1059 for (unsigned k = 0; k < count; ++k) {
1060 AccessibilityObject* obj = axChildren[k].get();
1061 AccessibilityRole role = obj->roleValue();
1062 if (role == TreeItemRole || role == GroupRole)
1069 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
1071 AccessibilityChildrenVector axChildren = children();
1072 unsigned count = axChildren.size();
1073 for (unsigned k = 0; k < count; ++k) {
1074 AccessibilityObject* obj = axChildren[k].get();
1076 // Add tree items as the rows.
1077 if (obj->roleValue() == TreeItemRole)
1079 // If it's not a tree item, then descend into the group to find more tree items.
1081 obj->ariaTreeRows(result);
1085 const String& AccessibilityObject::actionVerb() const
1087 // FIXME: Need to add verbs for select elements.
1088 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
1089 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
1090 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
1091 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
1092 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
1093 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
1094 DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb()));
1095 DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb()));
1096 DEFINE_STATIC_LOCAL(const String, noAction, ());
1098 switch (roleValue()) {
1100 return buttonAction;
1103 return textFieldAction;
1104 case RadioButtonRole:
1105 return radioButtonAction;
1107 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
1109 case WebCoreLinkRole:
1111 case PopUpButtonRole:
1112 return menuListAction;
1113 case MenuListPopupRole:
1114 return menuListPopupAction;
1120 bool AccessibilityObject::ariaIsMultiline() const
1122 return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
1125 const AtomicString& AccessibilityObject::invalidStatus() const
1127 DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false"));
1129 // aria-invalid can return false (default), grammer, spelling, or true.
1130 const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr);
1132 // If empty or not present, it should return false.
1133 if (ariaInvalid.isEmpty())
1134 return invalidStatusFalse;
1139 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
1141 Node* elementNode = node();
1145 if (!elementNode->isElementNode())
1148 Element* element = static_cast<Element*>(elementNode);
1149 return element->fastGetAttribute(attribute);
1152 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
1153 AccessibilityOrientation AccessibilityObject::orientation() const
1155 LayoutRect bounds = elementRect();
1156 if (bounds.size().width() > bounds.size().height())
1157 return AccessibilityOrientationHorizontal;
1158 if (bounds.size().height() > bounds.size().width())
1159 return AccessibilityOrientationVertical;
1161 // A tie goes to horizontal.
1162 return AccessibilityOrientationHorizontal;
1165 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
1169 AccessibilityRole webcoreRole;
1172 static ARIARoleMap* createARIARoleMap()
1174 const RoleEntry roles[] = {
1175 { "alert", ApplicationAlertRole },
1176 { "alertdialog", ApplicationAlertDialogRole },
1177 { "application", LandmarkApplicationRole },
1178 { "article", DocumentArticleRole },
1179 { "banner", LandmarkBannerRole },
1180 { "button", ButtonRole },
1181 { "checkbox", CheckBoxRole },
1182 { "complementary", LandmarkComplementaryRole },
1183 { "contentinfo", LandmarkContentInfoRole },
1184 { "dialog", ApplicationDialogRole },
1185 { "directory", DirectoryRole },
1186 { "grid", TableRole },
1187 { "gridcell", CellRole },
1188 { "columnheader", ColumnHeaderRole },
1189 { "combobox", ComboBoxRole },
1190 { "definition", DefinitionListDefinitionRole },
1191 { "document", DocumentRole },
1192 { "rowheader", RowHeaderRole },
1193 { "group", GroupRole },
1194 { "heading", HeadingRole },
1195 { "img", ImageRole },
1196 { "link", WebCoreLinkRole },
1197 { "list", ListRole },
1198 { "listitem", ListItemRole },
1199 { "listbox", ListBoxRole },
1200 { "log", ApplicationLogRole },
1201 // "option" isn't here because it may map to different roles depending on the parent element's role
1202 { "main", LandmarkMainRole },
1203 { "marquee", ApplicationMarqueeRole },
1204 { "math", DocumentMathRole },
1205 { "menu", MenuRole },
1206 { "menubar", MenuBarRole },
1207 { "menuitem", MenuItemRole },
1208 { "menuitemcheckbox", MenuItemRole },
1209 { "menuitemradio", MenuItemRole },
1210 { "note", DocumentNoteRole },
1211 { "navigation", LandmarkNavigationRole },
1212 { "option", ListBoxOptionRole },
1213 { "presentation", PresentationalRole },
1214 { "progressbar", ProgressIndicatorRole },
1215 { "radio", RadioButtonRole },
1216 { "radiogroup", RadioGroupRole },
1217 { "region", DocumentRegionRole },
1219 { "range", SliderRole },
1220 { "scrollbar", ScrollBarRole },
1221 { "search", LandmarkSearchRole },
1222 { "separator", SplitterRole },
1223 { "slider", SliderRole },
1224 { "spinbutton", ProgressIndicatorRole },
1225 { "status", ApplicationStatusRole },
1227 { "tablist", TabListRole },
1228 { "tabpanel", TabPanelRole },
1229 { "text", StaticTextRole },
1230 { "textbox", TextAreaRole },
1231 { "timer", ApplicationTimerRole },
1232 { "toolbar", ToolbarRole },
1233 { "tooltip", UserInterfaceTooltipRole },
1234 { "tree", TreeRole },
1235 { "treegrid", TreeGridRole },
1236 { "treeitem", TreeItemRole }
1238 ARIARoleMap* roleMap = new ARIARoleMap;
1240 for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i)
1241 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
1245 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
1247 ASSERT(!value.isEmpty());
1249 static const ARIARoleMap* roleMap = createARIARoleMap();
1251 Vector<String> roleVector;
1252 value.split(' ', roleVector);
1253 AccessibilityRole role = UnknownRole;
1254 unsigned size = roleVector.size();
1255 for (unsigned i = 0; i < size; ++i) {
1256 String roleName = roleVector[i];
1257 role = roleMap->get(roleName);
1265 const AtomicString& AccessibilityObject::placeholderValue() const
1267 const AtomicString& placeholder = getAttribute(placeholderAttr);
1268 if (!placeholder.isEmpty())
1274 bool AccessibilityObject::isInsideARIALiveRegion() const
1276 if (supportsARIALiveRegion())
1279 for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
1280 if (axParent->supportsARIALiveRegion())
1287 bool AccessibilityObject::supportsARIAAttributes() const
1289 return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns();
1292 bool AccessibilityObject::supportsARIALiveRegion() const
1294 const AtomicString& liveRegion = ariaLiveRegionStatus();
1295 return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
1298 AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const LayoutPoint& point) const
1300 // Send the hit test back into the sub-frame if necessary.
1301 if (isAttachment()) {
1302 Widget* widget = widgetForAttachmentView();
1303 // Normalize the point for the widget's bounds.
1304 if (widget && widget->isFrameView())
1305 return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(toPoint(point - widget->frameRect().location()));
1308 return const_cast<AccessibilityObject*>(this);
1311 AXObjectCache* AccessibilityObject::axObjectCache() const
1313 Document* doc = document();
1315 return doc->axObjectCache();
1319 AccessibilityObject* AccessibilityObject::focusedUIElement() const
1321 Document* doc = document();
1325 Page* page = doc->page();
1329 return AXObjectCache::focusedUIElementForPage(page);
1332 AccessibilitySortDirection AccessibilityObject::sortDirection() const
1334 const AtomicString& sortAttribute = getAttribute(aria_sortAttr);
1335 if (equalIgnoringCase(sortAttribute, "ascending"))
1336 return SortDirectionAscending;
1337 if (equalIgnoringCase(sortAttribute, "descending"))
1338 return SortDirectionDescending;
1340 return SortDirectionNone;
1343 bool AccessibilityObject::supportsARIAExpanded() const
1345 return !getAttribute(aria_expandedAttr).isEmpty();
1348 bool AccessibilityObject::isExpanded() const
1350 if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true"))
1356 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
1358 // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
1359 // If it's an ARIA checkbox or radio, the aria-checked attribute should be used.
1361 const AtomicString& result = getAttribute(aria_checkedAttr);
1362 if (equalIgnoringCase(result, "true"))
1363 return ButtonStateOn;
1364 if (equalIgnoringCase(result, "mixed"))
1365 return ButtonStateMixed;
1367 return ButtonStateOff;
1370 } // namespace WebCore