• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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.
16  *
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.
27  */
28 
29 #include "config.h"
30 #include "AccessibilityObject.h"
31 
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>
56 
57 using namespace std;
58 
59 namespace WebCore {
60 
61 using namespace HTMLNames;
62 
AccessibilityObject()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
70 {
71 }
72 
~AccessibilityObject()73 AccessibilityObject::~AccessibilityObject()
74 {
75     ASSERT(isDetached());
76 }
77 
detach()78 void AccessibilityObject::detach()
79 {
80 #if HAVE(ACCESSIBILITY)
81     setWrapper(0);
82 #endif
83 }
84 
parentObjectUnignored() const85 AccessibilityObject* AccessibilityObject::parentObjectUnignored() const
86 {
87     AccessibilityObject* parent;
88     for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
89     }
90 
91     return parent;
92 }
93 
firstAccessibleObjectFromNode(const Node * node)94 AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node)
95 {
96     ASSERT(AXObjectCache::accessibilityEnabled());
97 
98     if (!node)
99         return 0;
100 
101     Document* document = node->document();
102     if (!document)
103         return 0;
104 
105     AXObjectCache* cache = document->axObjectCache();
106 
107     AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer());
108     while (accessibleObject && accessibleObject->accessibilityIsIgnored()) {
109         node = node->traverseNextNode();
110 
111         while (node && !node->renderer())
112             node = node->traverseNextSibling();
113 
114         if (!node)
115             return 0;
116 
117         accessibleObject = cache->getOrCreate(node->renderer());
118     }
119 
120     return accessibleObject;
121 }
122 
isARIAInput(AccessibilityRole ariaRole)123 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
124 {
125     return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
126 }
127 
isARIAControl(AccessibilityRole ariaRole)128 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
129 {
130     return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole
131     || ariaRole == ComboBoxRole || ariaRole == SliderRole;
132 }
133 
clickPoint() const134 IntPoint AccessibilityObject::clickPoint() const
135 {
136     IntRect rect = elementRect();
137     return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
138 }
139 
press() const140 bool AccessibilityObject::press() const
141 {
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;
149 }
150 
language() const151 String AccessibilityObject::language() const
152 {
153     AccessibilityObject* parent = parentObject();
154 
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();
161     }
162 
163     return parent->language();
164 }
165 
visiblePositionRangeForUnorderedPositions(const VisiblePosition & visiblePos1,const VisiblePosition & visiblePos2) const166 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
167 {
168     if (visiblePos1.isNull() || visiblePos2.isNull())
169         return VisiblePositionRange();
170 
171     VisiblePosition startPos;
172     VisiblePosition endPos;
173     bool alreadyInOrder;
174 
175     // upstream is ordered before downstream for the same position
176     if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
177         alreadyInOrder = false;
178 
179     // use selection order to see if the positions are in order
180     else
181         alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
182 
183     if (alreadyInOrder) {
184         startPos = visiblePos1;
185         endPos = visiblePos2;
186     } else {
187         startPos = visiblePos2;
188         endPos = visiblePos1;
189     }
190 
191     return VisiblePositionRange(startPos, endPos);
192 }
193 
positionOfLeftWord(const VisiblePosition & visiblePos) const194 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
195 {
196     VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
197     VisiblePosition endPosition = endOfWord(startPosition);
198     return VisiblePositionRange(startPosition, endPosition);
199 }
200 
positionOfRightWord(const VisiblePosition & visiblePos) const201 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
202 {
203     VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
204     VisiblePosition endPosition = endOfWord(startPosition);
205     return VisiblePositionRange(startPosition, endPosition);
206 }
207 
updateAXLineStartForVisiblePosition(const VisiblePosition & visiblePosition)208 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
209 {
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;
232     }
233 
234     return startPosition;
235 }
236 
leftLineVisiblePositionRange(const VisiblePosition & visiblePos) const237 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
238 {
239     if (visiblePos.isNull())
240         return VisiblePositionRange();
241 
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();
247 
248     VisiblePosition startPosition = startOfLine(prevVisiblePos);
249 
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);
258         }
259     } else
260         startPosition = updateAXLineStartForVisiblePosition(startPosition);
261 
262     VisiblePosition endPosition = endOfLine(prevVisiblePos);
263     return VisiblePositionRange(startPosition, endPosition);
264 }
265 
rightLineVisiblePositionRange(const VisiblePosition & visiblePos) const266 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
267 {
268     if (visiblePos.isNull())
269         return VisiblePositionRange();
270 
271     // make sure we move off of a line end
272     VisiblePosition nextVisiblePos = visiblePos.next();
273     if (nextVisiblePos.isNull())
274         return VisiblePositionRange();
275 
276     VisiblePosition startPosition = startOfLine(nextVisiblePos);
277 
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);
284 
285     VisiblePosition endPosition = endOfLine(nextVisiblePos);
286 
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);
294     }
295 
296     return VisiblePositionRange(startPosition, endPosition);
297 }
298 
sentenceForPosition(const VisiblePosition & visiblePos) const299 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
300 {
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);
306 }
307 
paragraphForPosition(const VisiblePosition & visiblePos) const308 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
309 {
310     VisiblePosition startPosition = startOfParagraph(visiblePos);
311     VisiblePosition endPosition = endOfParagraph(startPosition);
312     return VisiblePositionRange(startPosition, endPosition);
313 }
314 
startOfStyleRange(const VisiblePosition visiblePos)315 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
316 {
317     RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
318     RenderObject* startRenderer = renderer;
319     RenderStyle* style = renderer->style();
320 
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;
326 
327         // stop at style change
328         if (r->style() != style)
329             break;
330 
331         // remember match
332         startRenderer = r;
333     }
334 
335     return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY);
336 }
337 
endOfStyleRange(const VisiblePosition & visiblePos)338 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
339 {
340     RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
341     RenderObject* endRenderer = renderer;
342     RenderStyle* style = renderer->style();
343 
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;
349 
350         // stop at style change
351         if (r->style() != style)
352             break;
353 
354         // remember match
355         endRenderer = r;
356     }
357 
358     return lastDeepEditingPositionForNode(endRenderer->node());
359 }
360 
styleRangeForPosition(const VisiblePosition & visiblePos) const361 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
362 {
363     if (visiblePos.isNull())
364         return VisiblePositionRange();
365 
366     return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
367 }
368 
369 // NOTE: Consider providing this utility method as AX API
visiblePositionRangeForRange(const PlainTextRange & range) const370 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
371 {
372     if (range.start + range.length > text().length())
373         return VisiblePositionRange();
374 
375     VisiblePosition startPosition = visiblePositionForIndex(range.start);
376     startPosition.setAffinity(DOWNSTREAM);
377     VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
378     return VisiblePositionRange(startPosition, endPosition);
379 }
380 
replacedNodeNeedsCharacter(Node * replacedNode)381 static bool replacedNodeNeedsCharacter(Node* replacedNode)
382 {
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;
387 
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;
392 
393     return true;
394 }
395 
396 // Finds a RenderListItem parent give a node.
renderListItemContainerForNode(Node * node) const397 RenderListItem* AccessibilityObject::renderListItemContainerForNode(Node* node) const
398 {
399     for (Node* stringNode = node; stringNode; stringNode = stringNode->parent()) {
400         RenderObject* renderObject = stringNode->renderer();
401         if (!renderObject || !renderObject->isListItem())
402             continue;
403 
404         return toRenderListItem(renderObject);
405     }
406 
407     return 0;
408 }
409 
410 // Returns the text associated with a list marker if this node is contained within a list item.
listMarkerTextForNodeAndPosition(Node * node,const VisiblePosition & visiblePositionStart) const411 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
412 {
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();
416 
417     RenderListItem* listItem = renderListItemContainerForNode(node);
418     if (!listItem)
419         return String();
420 
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();
427 
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(' ');
435 
436     return String::adopt(resultVector);
437 }
438 
stringForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const439 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
440 {
441     if (visiblePositionRange.isNull())
442         return String();
443 
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());
453 
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);
461 
462             if (replacedNodeNeedsCharacter(node->childNode(offset)))
463                 resultVector.append(objectReplacementCharacter);
464         }
465     }
466 
467     return String::adopt(resultVector);
468 }
469 
lengthForVisiblePositionRange(const VisiblePositionRange & visiblePositionRange) const470 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
471 {
472     // FIXME: Multi-byte support
473     if (visiblePositionRange.isNull())
474         return -1;
475 
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);
488 
489             if (replacedNodeNeedsCharacter(node->childNode(offset)))
490                 length++;
491         }
492     }
493 
494     return length;
495 }
496 
nextWordEnd(const VisiblePosition & visiblePos) const497 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
498 {
499     if (visiblePos.isNull())
500         return VisiblePosition();
501 
502     // make sure we move off of a word end
503     VisiblePosition nextVisiblePos = visiblePos.next();
504     if (nextVisiblePos.isNull())
505         return VisiblePosition();
506 
507     return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
508 }
509 
previousWordStart(const VisiblePosition & visiblePos) const510 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
511 {
512     if (visiblePos.isNull())
513         return VisiblePosition();
514 
515     // make sure we move off of a word start
516     VisiblePosition prevVisiblePos = visiblePos.previous();
517     if (prevVisiblePos.isNull())
518         return VisiblePosition();
519 
520     return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
521 }
522 
nextLineEndPosition(const VisiblePosition & visiblePos) const523 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
524 {
525     if (visiblePos.isNull())
526         return VisiblePosition();
527 
528     // to make sure we move off of a line end
529     VisiblePosition nextVisiblePos = visiblePos.next();
530     if (nextVisiblePos.isNull())
531         return VisiblePosition();
532 
533     VisiblePosition endPosition = endOfLine(nextVisiblePos);
534 
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);
540     }
541 
542     return endPosition;
543 }
544 
previousLineStartPosition(const VisiblePosition & visiblePos) const545 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
546 {
547     if (visiblePos.isNull())
548         return VisiblePosition();
549 
550     // make sure we move off of a line start
551     VisiblePosition prevVisiblePos = visiblePos.previous();
552     if (prevVisiblePos.isNull())
553         return VisiblePosition();
554 
555     VisiblePosition startPosition = startOfLine(prevVisiblePos);
556 
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);
563         }
564     } else
565         startPosition = updateAXLineStartForVisiblePosition(startPosition);
566 
567     return startPosition;
568 }
569 
nextSentenceEndPosition(const VisiblePosition & visiblePos) const570 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
571 {
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();
576 
577     // make sure we move off of a sentence end
578     VisiblePosition nextVisiblePos = visiblePos.next();
579     if (nextVisiblePos.isNull())
580         return VisiblePosition();
581 
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;
585 
586     String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
587     if (lineString.isEmpty())
588         endPosition = nextVisiblePos;
589     else
590         endPosition = endOfSentence(nextVisiblePos);
591 
592     return endPosition;
593 }
594 
previousSentenceStartPosition(const VisiblePosition & visiblePos) const595 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
596 {
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();
601 
602     // make sure we move off of a sentence start
603     VisiblePosition previousVisiblePos = visiblePos.previous();
604     if (previousVisiblePos.isNull())
605         return VisiblePosition();
606 
607     // treat empty line as a separate sentence.
608     VisiblePosition startPosition;
609 
610     String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
611     if (lineString.isEmpty())
612         startPosition = previousVisiblePos;
613     else
614         startPosition = startOfSentence(previousVisiblePos);
615 
616     return startPosition;
617 }
618 
nextParagraphEndPosition(const VisiblePosition & visiblePos) const619 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
620 {
621     if (visiblePos.isNull())
622         return VisiblePosition();
623 
624     // make sure we move off of a paragraph end
625     VisiblePosition nextPos = visiblePos.next();
626     if (nextPos.isNull())
627         return VisiblePosition();
628 
629     return endOfParagraph(nextPos);
630 }
631 
previousParagraphStartPosition(const VisiblePosition & visiblePos) const632 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
633 {
634     if (visiblePos.isNull())
635         return VisiblePosition();
636 
637     // make sure we move off of a paragraph start
638     VisiblePosition previousPos = visiblePos.previous();
639     if (previousPos.isNull())
640         return VisiblePosition();
641 
642     return startOfParagraph(previousPos);
643 }
644 
accessibilityObjectForPosition(const VisiblePosition & visiblePos) const645 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
646 {
647     if (visiblePos.isNull())
648         return 0;
649 
650     RenderObject* obj = visiblePos.deepEquivalent().node()->renderer();
651     if (!obj)
652         return 0;
653 
654     return obj->document()->axObjectCache()->getOrCreate(obj);
655 }
656 
lineForPosition(const VisiblePosition & visiblePos) const657 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
658 {
659     if (visiblePos.isNull())
660         return 0;
661 
662     unsigned lineCount = 0;
663     VisiblePosition currentVisiblePos = visiblePos;
664     VisiblePosition savedVisiblePos;
665 
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;
674     }
675 
676     return lineCount - 1;
677 }
678 
679 // NOTE: Consider providing this utility method as AX API
plainTextRangeForVisiblePositionRange(const VisiblePositionRange & positionRange) const680 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
681 {
682     int index1 = index(positionRange.start);
683     int index2 = index(positionRange.end);
684     if (index1 < 0 || index2 < 0 || index1 > index2)
685         return PlainTextRange();
686 
687     return PlainTextRange(index1, index2 - index1);
688 }
689 
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?
doAXRangeForPosition(const IntPoint & point) const696 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
697 {
698     int i = index(visiblePositionForPoint(point));
699     if (i < 0)
700         return PlainTextRange();
701 
702     return PlainTextRange(i, 1);
703 }
704 
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.
doAXStyleRangeForIndex(unsigned index) const707 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
708 {
709     VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
710     return plainTextRangeForVisiblePositionRange(range);
711 }
712 
713 // Given an indexed character, the line number of the text associated with this accessibility
714 // object that contains the character.
doAXLineForIndex(unsigned index)715 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
716 {
717     return lineForPosition(visiblePositionForIndex(index, false));
718 }
719 
documentFrameView() const720 FrameView* AccessibilityObject::documentFrameView() const
721 {
722     const AccessibilityObject* object = this;
723     while (object && !object->isAccessibilityRenderObject())
724         object = object->parentObject();
725 
726     if (!object)
727         return 0;
728 
729     return object->documentFrameView();
730 }
731 
clearChildren()732 void AccessibilityObject::clearChildren()
733 {
734     m_haveChildren = false;
735     m_children.clear();
736 }
737 
anchorElementForNode(Node * node)738 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
739 {
740     RenderObject* obj = node->renderer();
741     if (!obj)
742         return 0;
743 
744     RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
745     Element* anchor = axObj->anchorElement();
746     if (!anchor)
747         return 0;
748 
749     RenderObject* anchorRenderer = anchor->renderer();
750     if (!anchorRenderer)
751         return 0;
752 
753     return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
754 }
755 
ariaTreeRows(AccessibilityChildrenVector & result)756 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
757 {
758     AccessibilityChildrenVector axChildren = children();
759     unsigned count = axChildren.size();
760     for (unsigned k = 0; k < count; ++k) {
761         AccessibilityObject* obj = axChildren[k].get();
762 
763         // Add tree items as the rows.
764         if (obj->roleValue() == TreeItemRole)
765             result.append(obj);
766 
767         // Now see if this item also has rows hiding inside of it.
768         obj->ariaTreeRows(result);
769     }
770 }
771 
ariaTreeItemContent(AccessibilityChildrenVector & result)772 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
773 {
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;
782 
783         result.append(obj);
784     }
785 }
786 
ariaTreeItemDisclosedRows(AccessibilityChildrenVector & result)787 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
788 {
789     AccessibilityChildrenVector axChildren = children();
790     unsigned count = axChildren.size();
791     for (unsigned k = 0; k < count; ++k) {
792         AccessibilityObject* obj = axChildren[k].get();
793 
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);
800     }
801 }
802 
actionVerb() const803 const String& AccessibilityObject::actionVerb() const
804 {
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, menuListAction, (AXMenuListActionVerb()));
813     DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb()));
814     DEFINE_STATIC_LOCAL(const String, noAction, ());
815 
816     switch (roleValue()) {
817     case ButtonRole:
818         return buttonAction;
819     case TextFieldRole:
820     case TextAreaRole:
821         return textFieldAction;
822     case RadioButtonRole:
823         return radioButtonAction;
824     case CheckBoxRole:
825         return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
826     case LinkRole:
827     case WebCoreLinkRole:
828         return linkAction;
829     case PopUpButtonRole:
830         return menuListAction;
831     case MenuListPopupRole:
832         return menuListPopupAction;
833     default:
834         return noAction;
835     }
836 }
837 
838 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
orientation() const839 AccessibilityOrientation AccessibilityObject::orientation() const
840 {
841     IntRect bounds = elementRect();
842     if (bounds.size().width() > bounds.size().height())
843         return AccessibilityOrientationHorizontal;
844     if (bounds.size().height() > bounds.size().width())
845         return AccessibilityOrientationVertical;
846 
847     // A tie goes to horizontal.
848     return AccessibilityOrientationHorizontal;
849 }
850 
851 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
852 
853 struct RoleEntry {
854     String ariaRole;
855     AccessibilityRole webcoreRole;
856 };
857 
createARIARoleMap()858 static ARIARoleMap* createARIARoleMap()
859 {
860     const RoleEntry roles[] = {
861         { "alert", ApplicationAlertRole },
862         { "alertdialog", ApplicationAlertDialogRole },
863         { "application", LandmarkApplicationRole },
864         { "article", DocumentArticleRole },
865         { "banner", LandmarkBannerRole },
866         { "button", ButtonRole },
867         { "checkbox", CheckBoxRole },
868         { "complementary", LandmarkComplementaryRole },
869         { "contentinfo", LandmarkContentInfoRole },
870         { "dialog", ApplicationDialogRole },
871         { "directory", DirectoryRole },
872         { "grid", TableRole },
873         { "gridcell", CellRole },
874         { "columnheader", ColumnHeaderRole },
875         { "combobox", ComboBoxRole },
876         { "definition", DefinitionListDefinitionRole },
877         { "document", DocumentRole },
878         { "rowheader", RowHeaderRole },
879         { "group", GroupRole },
880         { "heading", HeadingRole },
881         { "img", ImageRole },
882         { "link", WebCoreLinkRole },
883         { "list", ListRole },
884         { "listitem", GroupRole },
885         { "listbox", ListBoxRole },
886         { "log", ApplicationLogRole },
887         // "option" isn't here because it may map to different roles depending on the parent element's role
888         { "main", LandmarkMainRole },
889         { "marquee", ApplicationMarqueeRole },
890         { "math", DocumentMathRole },
891         { "menu", MenuRole },
892         { "menubar", GroupRole },
893         // "menuitem" isn't here because it may map to different roles depending on the parent element's role
894         { "menuitemcheckbox", MenuItemRole },
895         { "menuitemradio", MenuItemRole },
896         { "note", DocumentNoteRole },
897         { "navigation", LandmarkNavigationRole },
898         { "option", ListBoxOptionRole },
899         { "presentation", IgnoredRole },
900         { "progressbar", ProgressIndicatorRole },
901         { "radio", RadioButtonRole },
902         { "radiogroup", RadioGroupRole },
903         { "region", DocumentRegionRole },
904         { "row", RowRole },
905         { "range", SliderRole },
906         { "scrollbar", ScrollBarRole },
907         { "search", LandmarkSearchRole },
908         { "separator", SplitterRole },
909         { "slider", SliderRole },
910         { "spinbutton", ProgressIndicatorRole },
911         { "status", ApplicationStatusRole },
912         { "tab", TabRole },
913         { "tablist", TabListRole },
914         { "tabpanel", TabPanelRole },
915         { "text", StaticTextRole },
916         { "textbox", TextAreaRole },
917         { "timer", ApplicationTimerRole },
918         { "toolbar", ToolbarRole },
919         { "tooltip", UserInterfaceTooltipRole },
920         { "tree", TreeRole },
921         { "treegrid", TreeGridRole },
922         { "treeitem", TreeItemRole }
923     };
924     ARIARoleMap* roleMap = new ARIARoleMap;
925 
926     const unsigned numRoles = sizeof(roles) / sizeof(roles[0]);
927     for (unsigned i = 0; i < numRoles; ++i)
928         roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
929     return roleMap;
930 }
931 
ariaRoleToWebCoreRole(const String & value)932 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
933 {
934     ASSERT(!value.isEmpty());
935     static const ARIARoleMap* roleMap = createARIARoleMap();
936     return roleMap->get(value);
937 }
938 
isInsideARIALiveRegion() const939 bool AccessibilityObject::isInsideARIALiveRegion() const
940 {
941     if (supportsARIALiveRegion())
942         return true;
943 
944     for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
945         if (axParent->supportsARIALiveRegion())
946             return true;
947     }
948 
949     return false;
950 }
951 
supportsARIALiveRegion() const952 bool AccessibilityObject::supportsARIALiveRegion() const
953 {
954     const AtomicString& liveRegion = ariaLiveRegionStatus();
955     return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
956 }
957 
958 
959 } // namespace WebCore
960