Rubber-stamped by Brady Eidson.
[webbrowser.git] / WebCore / accessibility / AccessibilityObject.cpp
blob585e4cbbbd7265dd346503163656fbcdd264de7d
1 /*
2 * Copyright (C) 2008, 2009 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
6 * are met:
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.
29 #include "config.h"
30 #include "AccessibilityObject.h"
32 #include "AXObjectCache.h"
33 #include "AccessibilityRenderObject.h"
34 #include "CharacterNames.h"
35 #include "FloatRect.h"
36 #include "FocusController.h"
37 #include "Frame.h"
38 #include "FrameLoader.h"
39 #include "LocalizedStrings.h"
40 #include "NodeList.h"
41 #include "NotImplemented.h"
42 #include "Page.h"
43 #include "RenderImage.h"
44 #include "RenderListItem.h"
45 #include "RenderListMarker.h"
46 #include "RenderMenuList.h"
47 #include "RenderTextControl.h"
48 #include "RenderTheme.h"
49 #include "RenderView.h"
50 #include "RenderWidget.h"
51 #include "SelectionController.h"
52 #include "TextIterator.h"
53 #include "htmlediting.h"
54 #include "visible_units.h"
55 #include <wtf/StdLibExtras.h>
57 using namespace std;
59 namespace WebCore {
61 using namespace HTMLNames;
63 AccessibilityObject::AccessibilityObject()
64 : m_id(0)
65 , m_haveChildren(false)
66 , m_role(UnknownRole)
67 #if PLATFORM(GTK)
68 , m_wrapper(0)
69 #endif
73 AccessibilityObject::~AccessibilityObject()
75 ASSERT(isDetached());
78 void AccessibilityObject::detach()
80 #if HAVE(ACCESSIBILITY)
81 setWrapper(0);
82 #endif
85 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
87 AccessibilityObject* parent;
88 for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
91 return parent;
94 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
96 ASSERT(AXObjectCache::accessibilityEnabled());
98 if (!node)
99 return 0;
101 Document* document = node->document();
102 if (!document)
103 return 0;
105 AXObjectCache* cache = document->axObjectCache();
107 AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
108 while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
109 node = node->traverseNextNode();
111 while (node && !node->renderer())
112 node = node->traverseNextSibling();
114 if (!node)
115 return 0;
117 accessibleObject = cache->getOrCreate(node->renderer());
120 return accessibleObject;
123 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
125 return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
128 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
130 return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
131 || ariaRole == ComboBoxRole || ariaRole == SliderRole;
134 IntPoint AccessibilityObject::clickPoint() const
136 IntRect rect = elementRect();
137 return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
140 bool AccessibilityObject::press() const
142 Element* actionElem = actionElement();
143 if (!actionElem)
144 return false;
145 if (Frame* f = actionElem->document()->frame())
146 f->loader()->resetMultipleFormSubmissionProtection();
147 actionElem->accessKeyAction(true);
148 return true;
151 String AccessibilityObject::language() const
153 AccessibilityObject* parent = parentObject();
155 // as a last resort, fall back to the content language specified in the meta tag
156 if (!parent) {
157 Document* doc = document();
158 if (doc)
159 return doc->contentLanguage();
160 return String();
163 return parent->language();
166 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
168 if (visiblePos1.isNull() || visiblePos2.isNull())
169 return VisiblePositionRange();
171 VisiblePosition startPos;
172 VisiblePosition endPos;
173 bool alreadyInOrder;
175 // upstream is ordered before downstream for the same position
176 if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
177 alreadyInOrder = false;
179 // use selection order to see if the positions are in order
180 else
181 alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
183 if (alreadyInOrder) {
184 startPos = visiblePos1;
185 endPos = visiblePos2;
186 } else {
187 startPos = visiblePos2;
188 endPos = visiblePos1;
191 return VisiblePositionRange(startPos, endPos);
194 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
196 VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
197 VisiblePosition endPosition = endOfWord(startPosition);
198 return VisiblePositionRange(startPosition, endPosition);
201 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
203 VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
204 VisiblePosition endPosition = endOfWord(startPosition);
205 return VisiblePositionRange(startPosition, endPosition);
208 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
210 // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
211 // So let's update the position to include that.
212 VisiblePosition tempPosition;
213 VisiblePosition startPosition = visiblePosition;
214 Position p;
215 RenderObject* renderer;
216 while (true) {
217 tempPosition = startPosition.previous();
218 if (tempPosition.isNull())
219 break;
220 p = tempPosition.deepEquivalent();
221 if (!p.node())
222 break;
223 renderer = p.node()->renderer();
224 if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
225 break;
226 InlineBox* box;
227 int ignoredCaretOffset;
228 p.getInlineBoxAndOffset(tempPosition.affinity(), box, ignoredCaretOffset);
229 if (box)
230 break;
231 startPosition = tempPosition;
234 return startPosition;
237 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
239 if (visiblePos.isNull())
240 return VisiblePositionRange();
242 // make a caret selection for the position before marker position (to make sure
243 // we move off of a line start)
244 VisiblePosition prevVisiblePos = visiblePos.previous();
245 if (prevVisiblePos.isNull())
246 return VisiblePositionRange();
248 VisiblePosition startPosition = startOfLine(prevVisiblePos);
250 // keep searching for a valid line start position. Unless the VisiblePosition is at the very beginning, there should
251 // always be a valid line range. However, startOfLine will return null for position next to a floating object,
252 // since floating object doesn't really belong to any line.
253 // This check will reposition the marker before the floating object, to ensure we get a line start.
254 if (startPosition.isNull()) {
255 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
256 prevVisiblePos = prevVisiblePos.previous();
257 startPosition = startOfLine(prevVisiblePos);
259 } else
260 startPosition = updateAXLineStartForVisiblePosition(startPosition);
262 VisiblePosition endPosition = endOfLine(prevVisiblePos);
263 return VisiblePositionRange(startPosition, endPosition);
266 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
268 if (visiblePos.isNull())
269 return VisiblePositionRange();
271 // make sure we move off of a line end
272 VisiblePosition nextVisiblePos = visiblePos.next();
273 if (nextVisiblePos.isNull())
274 return VisiblePositionRange();
276 VisiblePosition startPosition = startOfLine(nextVisiblePos);
278 // fetch for a valid line start position
279 if (startPosition.isNull()) {
280 startPosition = visiblePos;
281 nextVisiblePos = nextVisiblePos.next();
282 } else
283 startPosition = updateAXLineStartForVisiblePosition(startPosition);
285 VisiblePosition endPosition = endOfLine(nextVisiblePos);
287 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
288 // Unless the VisiblePosition is at the very end, there should always be a valid line range. However, endOfLine will
289 // return null for position by a floating object, since floating object doesn't really belong to any line.
290 // This check will reposition the marker after the floating object, to ensure we get a line end.
291 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
292 nextVisiblePos = nextVisiblePos.next();
293 endPosition = endOfLine(nextVisiblePos);
296 return VisiblePositionRange(startPosition, endPosition);
299 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
301 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
302 // Related? <rdar://problem/3927736> Text selection broken in 8A336
303 VisiblePosition startPosition = startOfSentence(visiblePos);
304 VisiblePosition endPosition = endOfSentence(startPosition);
305 return VisiblePositionRange(startPosition, endPosition);
308 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
310 VisiblePosition startPosition = startOfParagraph(visiblePos);
311 VisiblePosition endPosition = endOfParagraph(startPosition);
312 return VisiblePositionRange(startPosition, endPosition);
315 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
317 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
318 RenderObject* startRenderer = renderer;
319 RenderStyle* style = renderer->style();
321 // traverse backward by renderer to look for style change
322 for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
323 // skip non-leaf nodes
324 if (r->firstChild())
325 continue;
327 // stop at style change
328 if (r->style() != style)
329 break;
331 // remember match
332 startRenderer = r;
335 return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY);
338 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
340 RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
341 RenderObject* endRenderer = renderer;
342 RenderStyle* style = renderer->style();
344 // traverse forward by renderer to look for style change
345 for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
346 // skip non-leaf nodes
347 if (r->firstChild())
348 continue;
350 // stop at style change
351 if (r->style() != style)
352 break;
354 // remember match
355 endRenderer = r;
358 return lastDeepEditingPositionForNode(endRenderer->node());
361 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
363 if (visiblePos.isNull())
364 return VisiblePositionRange();
366 return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
369 // NOTE: Consider providing this utility method as AX API
370 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
372 if (range.start + range.length > text().length())
373 return VisiblePositionRange();
375 VisiblePosition startPosition = visiblePositionForIndex(range.start);
376 startPosition.setAffinity(DOWNSTREAM);
377 VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
378 return VisiblePositionRange(startPosition, endPosition);
381 static bool replacedNodeNeedsCharacter(Node* replacedNode)
383 // we should always be given a rendered node and a replaced node, but be safe
384 // replaced nodes are either attachments (widgets) or images
385 if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode())
386 return false;
388 // create an AX object, but skip it if it is not supposed to be seen
389 AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
390 if (object->accessibilityIsIgnored())
391 return false;
393 return true;
396 // Finds a RenderListItem parent give a node.
397 RenderListItem* AccessibilityObject::renderListItemContainerForNode(Node* node) const
399 for (Node* stringNode = node; stringNode; stringNode = stringNode->parent()) {
400 RenderObject* renderObject = stringNode->renderer();
401 if (!renderObject || !renderObject->isListItem())
402 continue;
404 return toRenderListItem(renderObject);
407 return 0;
410 // Returns the text associated with a list marker if this node is contained within a list item.
411 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
413 // If the range does not contain the start of the line, the list marker text should not be included.
414 if (!isStartOfLine(visiblePositionStart))
415 return String();
417 RenderListItem* listItem = renderListItemContainerForNode(node);
418 if (!listItem)
419 return String();
421 // If this is in a list item, we need to manually add the text for the list marker
422 // because a RenderListMarker does not have a Node equivalent and thus does not appear
423 // when iterating text.
424 const String& markerText = listItem->markerText();
425 if (markerText.isEmpty())
426 return String();
428 // Append text, plus the period that follows the text.
429 // FIXME: Not all list marker styles are followed by a period, but this
430 // sounds much better when there is a synthesized pause because of a period.
431 Vector<UChar> resultVector;
432 resultVector.append(markerText.characters(), markerText.length());
433 resultVector.append('.');
434 resultVector.append(' ');
436 return String::adopt(resultVector);
439 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
441 if (visiblePositionRange.isNull())
442 return String();
444 Vector<UChar> resultVector;
445 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
446 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
447 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
448 if (it.length()) {
449 // Add a textual representation for list marker text
450 String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start);
451 if (!listMarkerText.isEmpty())
452 resultVector.append(listMarkerText.characters(), listMarkerText.length());
454 resultVector.append(it.characters(), it.length());
455 } else {
456 // locate the node and starting offset for this replaced range
457 int exception = 0;
458 Node* node = it.range()->startContainer(exception);
459 ASSERT(node == it.range()->endContainer(exception));
460 int offset = it.range()->startOffset(exception);
462 if (replacedNodeNeedsCharacter(node->childNode(offset)))
463 resultVector.append(objectReplacementCharacter);
467 return String::adopt(resultVector);
470 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
472 // FIXME: Multi-byte support
473 if (visiblePositionRange.isNull())
474 return -1;
476 int length = 0;
477 RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
478 for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
479 // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
480 if (it.length())
481 length += it.length();
482 else {
483 // locate the node and starting offset for this replaced range
484 int exception = 0;
485 Node* node = it.range()->startContainer(exception);
486 ASSERT(node == it.range()->endContainer(exception));
487 int offset = it.range()->startOffset(exception);
489 if (replacedNodeNeedsCharacter(node->childNode(offset)))
490 length++;
494 return length;
497 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
499 if (visiblePos.isNull())
500 return VisiblePosition();
502 // make sure we move off of a word end
503 VisiblePosition nextVisiblePos = visiblePos.next();
504 if (nextVisiblePos.isNull())
505 return VisiblePosition();
507 return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
510 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
512 if (visiblePos.isNull())
513 return VisiblePosition();
515 // make sure we move off of a word start
516 VisiblePosition prevVisiblePos = visiblePos.previous();
517 if (prevVisiblePos.isNull())
518 return VisiblePosition();
520 return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
523 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
525 if (visiblePos.isNull())
526 return VisiblePosition();
528 // to make sure we move off of a line end
529 VisiblePosition nextVisiblePos = visiblePos.next();
530 if (nextVisiblePos.isNull())
531 return VisiblePosition();
533 VisiblePosition endPosition = endOfLine(nextVisiblePos);
535 // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position
536 // 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.
537 while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
538 nextVisiblePos = nextVisiblePos.next();
539 endPosition = endOfLine(nextVisiblePos);
542 return endPosition;
545 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
547 if (visiblePos.isNull())
548 return VisiblePosition();
550 // make sure we move off of a line start
551 VisiblePosition prevVisiblePos = visiblePos.previous();
552 if (prevVisiblePos.isNull())
553 return VisiblePosition();
555 VisiblePosition startPosition = startOfLine(prevVisiblePos);
557 // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position
558 // 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.
559 if (startPosition.isNull()) {
560 while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
561 prevVisiblePos = prevVisiblePos.previous();
562 startPosition = startOfLine(prevVisiblePos);
564 } else
565 startPosition = updateAXLineStartForVisiblePosition(startPosition);
567 return startPosition;
570 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
572 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
573 // Related? <rdar://problem/3927736> Text selection broken in 8A336
574 if (visiblePos.isNull())
575 return VisiblePosition();
577 // make sure we move off of a sentence end
578 VisiblePosition nextVisiblePos = visiblePos.next();
579 if (nextVisiblePos.isNull())
580 return VisiblePosition();
582 // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
583 // see this empty line. Instead, return the end position of the empty line.
584 VisiblePosition endPosition;
586 String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
587 if (lineString.isEmpty())
588 endPosition = nextVisiblePos;
589 else
590 endPosition = endOfSentence(nextVisiblePos);
592 return endPosition;
595 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
597 // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
598 // Related? <rdar://problem/3927736> Text selection broken in 8A336
599 if (visiblePos.isNull())
600 return VisiblePosition();
602 // make sure we move off of a sentence start
603 VisiblePosition previousVisiblePos = visiblePos.previous();
604 if (previousVisiblePos.isNull())
605 return VisiblePosition();
607 // treat empty line as a separate sentence.
608 VisiblePosition startPosition;
610 String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
611 if (lineString.isEmpty())
612 startPosition = previousVisiblePos;
613 else
614 startPosition = startOfSentence(previousVisiblePos);
616 return startPosition;
619 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
621 if (visiblePos.isNull())
622 return VisiblePosition();
624 // make sure we move off of a paragraph end
625 VisiblePosition nextPos = visiblePos.next();
626 if (nextPos.isNull())
627 return VisiblePosition();
629 return endOfParagraph(nextPos);
632 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
634 if (visiblePos.isNull())
635 return VisiblePosition();
637 // make sure we move off of a paragraph start
638 VisiblePosition previousPos = visiblePos.previous();
639 if (previousPos.isNull())
640 return VisiblePosition();
642 return startOfParagraph(previousPos);
645 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
647 if (visiblePos.isNull())
648 return 0;
650 RenderObject* obj = visiblePos.deepEquivalent().node()->renderer();
651 if (!obj)
652 return 0;
654 return obj->document()->axObjectCache()->getOrCreate(obj);
657 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
659 if (visiblePos.isNull())
660 return 0;
662 unsigned lineCount = 0;
663 VisiblePosition currentVisiblePos = visiblePos;
664 VisiblePosition savedVisiblePos;
666 // move up until we get to the top
667 // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
668 // top document.
669 while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) {
670 ++lineCount;
671 savedVisiblePos = currentVisiblePos;
672 VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0);
673 currentVisiblePos = prevVisiblePos;
676 return lineCount - 1;
679 // NOTE: Consider providing this utility method as AX API
680 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
682 int index1 = index(positionRange.start);
683 int index2 = index(positionRange.end);
684 if (index1 < 0 || index2 < 0 || index1 > index2)
685 return PlainTextRange();
687 return PlainTextRange(index1, index2 - index1);
690 // The composed character range in the text associated with this accessibility object that
691 // is specified by the given screen coordinates. This parameterized attribute returns the
692 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
693 // screen coordinates.
694 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
695 // an error in that case. We return textControl->text().length(), 1. Does this matter?
696 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
698 int i = index(visiblePositionForPoint(point));
699 if (i < 0)
700 return PlainTextRange();
702 return PlainTextRange(i, 1);
705 // Given a character index, the range of text associated with this accessibility object
706 // over which the style in effect at that character index applies.
707 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
709 VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
710 return plainTextRangeForVisiblePositionRange(range);
713 // Given an indexed character, the line number of the text associated with this accessibility
714 // object that contains the character.
715 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
717 return lineForPosition(visiblePositionForIndex(index, false));
720 FrameView* AccessibilityObject::documentFrameView() const
722 const AccessibilityObject* object = this;
723 while (object && !object->isAccessibilityRenderObject())
724 object = object->parentObject();
726 if (!object)
727 return 0;
729 return object->documentFrameView();
732 void AccessibilityObject::clearChildren()
734 m_haveChildren = false;
735 m_children.clear();
738 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
740 RenderObject* obj = node->renderer();
741 if (!obj)
742 return 0;
744 RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
745 Element* anchor = axObj->anchorElement();
746 if (!anchor)
747 return 0;
749 RenderObject* anchorRenderer = anchor->renderer();
750 if (!anchorRenderer)
751 return 0;
753 return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
756 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
758 AccessibilityChildrenVector axChildren = children();
759 unsigned count = axChildren.size();
760 for (unsigned k = 0; k < count; ++k) {
761 AccessibilityObject* obj = axChildren[k].get();
763 // Add tree items as the rows.
764 if (obj->roleValue() == TreeItemRole)
765 result.append(obj);
767 // Now see if this item also has rows hiding inside of it.
768 obj->ariaTreeRows(result);
772 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
774 // The ARIA tree item content are the item that are not other tree items or their containing groups.
775 AccessibilityChildrenVector axChildren = children();
776 unsigned count = axChildren.size();
777 for (unsigned k = 0; k < count; ++k) {
778 AccessibilityObject* obj = axChildren[k].get();
779 AccessibilityRole role = obj->roleValue();
780 if (role == TreeItemRole || role == GroupRole)
781 continue;
783 result.append(obj);
787 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
789 AccessibilityChildrenVector axChildren = children();
790 unsigned count = axChildren.size();
791 for (unsigned k = 0; k < count; ++k) {
792 AccessibilityObject* obj = axChildren[k].get();
794 // Add tree items as the rows.
795 if (obj->roleValue() == TreeItemRole)
796 result.append(obj);
797 // If it's not a tree item, then descend into the group to find more tree items.
798 else
799 obj->ariaTreeRows(result);
803 const String& AccessibilityObject::actionVerb() const
805 // FIXME: Need to add verbs for select elements.
806 DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
807 DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
808 DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
809 DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
810 DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
811 DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
812 DEFINE_STATIC_LOCAL(const String, noAction, ());
814 switch (roleValue()) {
815 case ButtonRole:
816 return buttonAction;
817 case TextFieldRole:
818 case TextAreaRole:
819 return textFieldAction;
820 case RadioButtonRole:
821 return radioButtonAction;
822 case CheckBoxRole:
823 return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
824 case LinkRole:
825 case WebCoreLinkRole:
826 return linkAction;
827 default:
828 return noAction;
832 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
833 AccessibilityOrientation AccessibilityObject::orientation() const
835 IntRect bounds = elementRect();
836 if (bounds.size().width() > bounds.size().height())
837 return AccessibilityOrientationHorizontal;
838 if (bounds.size().height() > bounds.size().width())
839 return AccessibilityOrientationVertical;
841 // A tie goes to horizontal.
842 return AccessibilityOrientationHorizontal;
845 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
847 struct RoleEntry {
848 String ariaRole;
849 AccessibilityRole webcoreRole;
852 static ARIARoleMap* createARIARoleMap()
854 const RoleEntry roles[] = {
855 { "alert", ApplicationAlertRole },
856 { "alertdialog", ApplicationAlertDialogRole },
857 { "application", LandmarkApplicationRole },
858 { "article", DocumentArticleRole },
859 { "banner", LandmarkBannerRole },
860 { "button", ButtonRole },
861 { "checkbox", CheckBoxRole },
862 { "complementary", LandmarkComplementaryRole },
863 { "contentinfo", LandmarkContentInfoRole },
864 { "dialog", ApplicationDialogRole },
865 { "directory", DirectoryRole },
866 { "grid", TableRole },
867 { "gridcell", CellRole },
868 { "columnheader", ColumnHeaderRole },
869 { "combobox", ComboBoxRole },
870 { "definition", DefinitionListDefinitionRole },
871 { "document", DocumentRole },
872 { "rowheader", RowHeaderRole },
873 { "group", GroupRole },
874 { "heading", HeadingRole },
875 { "img", ImageRole },
876 { "link", WebCoreLinkRole },
877 { "list", ListRole },
878 { "listitem", GroupRole },
879 { "listbox", ListBoxRole },
880 { "log", ApplicationLogRole },
881 // "option" isn't here because it may map to different roles depending on the parent element's role
882 { "main", LandmarkMainRole },
883 { "marquee", ApplicationMarqueeRole },
884 { "math", DocumentMathRole },
885 { "menu", MenuRole },
886 { "menubar", GroupRole },
887 // "menuitem" isn't here because it may map to different roles depending on the parent element's role
888 { "menuitemcheckbox", MenuItemRole },
889 { "menuitemradio", MenuItemRole },
890 { "note", DocumentNoteRole },
891 { "navigation", LandmarkNavigationRole },
892 { "option", ListBoxOptionRole },
893 { "presentation", IgnoredRole },
894 { "progressbar", ProgressIndicatorRole },
895 { "radio", RadioButtonRole },
896 { "radiogroup", RadioGroupRole },
897 { "region", DocumentRegionRole },
898 { "row", RowRole },
899 { "range", SliderRole },
900 { "scrollbar", ScrollBarRole },
901 { "search", LandmarkSearchRole },
902 { "separator", SplitterRole },
903 { "slider", SliderRole },
904 { "spinbutton", ProgressIndicatorRole },
905 { "status", ApplicationStatusRole },
906 { "tab", TabRole },
907 { "tablist", TabListRole },
908 { "tabpanel", TabPanelRole },
909 { "text", StaticTextRole },
910 { "textbox", TextAreaRole },
911 { "timer", ApplicationTimerRole },
912 { "toolbar", ToolbarRole },
913 { "tooltip", UserInterfaceTooltipRole },
914 { "tree", TreeRole },
915 { "treeitem", TreeItemRole }
917 ARIARoleMap* roleMap = new ARIARoleMap;
919 const unsigned numRoles = sizeof(roles) / sizeof(roles[0]);
920 for (unsigned i = 0; i < numRoles; ++i)
921 roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
922 return roleMap;
925 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
927 ASSERT(!value.isEmpty());
928 static const ARIARoleMap* roleMap = createARIARoleMap();
929 return roleMap->get(value);
932 } // namespace WebCore