e6c991fd1555a0c48a32d385da38f4ce6d8100b0
[WebKit-https.git] / WebCore / accessibility / AccessibilityObject.cpp
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
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
73 AccessibilityObject::~AccessibilityObject()
74 {
75     ASSERT(isDetached());
76 }
77
78 void AccessibilityObject::detach()
79 {
80 #if HAVE(ACCESSIBILITY)
81     setWrapper(0);
82 #endif    
83 }
84
85 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
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
123 bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole)
124 {
125     return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole;
126 }    
127     
128 bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole)
129 {
130     return isARIAInput(ariaRole) || ariaRole == TextAreaRole || ariaRole == ButtonRole 
131     || ariaRole == ComboBoxRole || ariaRole == SliderRole; 
132 }
133
134 IntPoint AccessibilityObject::clickPoint() const
135 {
136     IntRect rect = elementRect();
137     return IntPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
138 }
139
140 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     
151 String AccessibilityObject::language() const
152 {
153     const AtomicString& lang = getAttribute(langAttr);
154     if (!lang.isEmpty())
155         return lang;
156
157     AccessibilityObject* parent = parentObject();
158     
159     // as a last resort, fall back to the content language specified in the meta tag
160     if (!parent) {
161         Document* doc = document();
162         if (doc)
163             return doc->contentLanguage();
164         return nullAtom;
165     }
166     
167     return parent->language();
168 }
169     
170 VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& visiblePos1, const VisiblePosition& visiblePos2) const
171 {
172     if (visiblePos1.isNull() || visiblePos2.isNull())
173         return VisiblePositionRange();
174
175     VisiblePosition startPos;
176     VisiblePosition endPos;
177     bool alreadyInOrder;
178
179     // upstream is ordered before downstream for the same position
180     if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM)
181         alreadyInOrder = false;
182
183     // use selection order to see if the positions are in order
184     else
185         alreadyInOrder = VisibleSelection(visiblePos1, visiblePos2).isBaseFirst();
186
187     if (alreadyInOrder) {
188         startPos = visiblePos1;
189         endPos = visiblePos2;
190     } else {
191         startPos = visiblePos2;
192         endPos = visiblePos1;
193     }
194
195     return VisiblePositionRange(startPos, endPos);
196 }
197
198 VisiblePositionRange AccessibilityObject::positionOfLeftWord(const VisiblePosition& visiblePos) const
199 {
200     VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary);
201     VisiblePosition endPosition = endOfWord(startPosition);
202     return VisiblePositionRange(startPosition, endPosition);
203 }
204
205 VisiblePositionRange AccessibilityObject::positionOfRightWord(const VisiblePosition& visiblePos) const
206 {
207     VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary);
208     VisiblePosition endPosition = endOfWord(startPosition);
209     return VisiblePositionRange(startPosition, endPosition);
210 }
211
212 static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition)
213 {
214     // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line.
215     // So let's update the position to include that.
216     VisiblePosition tempPosition;
217     VisiblePosition startPosition = visiblePosition;
218     Position p;
219     RenderObject* renderer;
220     while (true) {
221         tempPosition = startPosition.previous();
222         if (tempPosition.isNull())
223             break;
224         p = tempPosition.deepEquivalent();
225         if (!p.node())
226             break;
227         renderer = p.node()->renderer();
228         if (!renderer || (renderer->isRenderBlock() && !p.deprecatedEditingOffset()))
229             break;
230         InlineBox* box;
231         int ignoredCaretOffset;
232         p.getInlineBoxAndOffset(tempPosition.affinity(), box, ignoredCaretOffset);
233         if (box)
234             break;
235         startPosition = tempPosition;
236     }
237
238     return startPosition;
239 }
240
241 VisiblePositionRange AccessibilityObject::leftLineVisiblePositionRange(const VisiblePosition& visiblePos) const
242 {
243     if (visiblePos.isNull())
244         return VisiblePositionRange();
245
246     // make a caret selection for the position before marker position (to make sure
247     // we move off of a line start)
248     VisiblePosition prevVisiblePos = visiblePos.previous();
249     if (prevVisiblePos.isNull())
250         return VisiblePositionRange();
251
252     VisiblePosition startPosition = startOfLine(prevVisiblePos);
253
254     // keep searching for a valid line start position.  Unless the VisiblePosition is at the very beginning, there should
255     // always be a valid line range.  However, startOfLine will return null for position next to a floating object,
256     // since floating object doesn't really belong to any line.
257     // This check will reposition the marker before the floating object, to ensure we get a line start.
258     if (startPosition.isNull()) {
259         while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
260             prevVisiblePos = prevVisiblePos.previous();
261             startPosition = startOfLine(prevVisiblePos);
262         }
263     } else
264         startPosition = updateAXLineStartForVisiblePosition(startPosition);
265
266     VisiblePosition endPosition = endOfLine(prevVisiblePos);
267     return VisiblePositionRange(startPosition, endPosition);
268 }
269
270 VisiblePositionRange AccessibilityObject::rightLineVisiblePositionRange(const VisiblePosition& visiblePos) const
271 {
272     if (visiblePos.isNull())
273         return VisiblePositionRange();
274
275     // make sure we move off of a line end
276     VisiblePosition nextVisiblePos = visiblePos.next();
277     if (nextVisiblePos.isNull())
278         return VisiblePositionRange();
279
280     VisiblePosition startPosition = startOfLine(nextVisiblePos);
281
282     // fetch for a valid line start position
283     if (startPosition.isNull()) {
284         startPosition = visiblePos;
285         nextVisiblePos = nextVisiblePos.next();
286     } else
287         startPosition = updateAXLineStartForVisiblePosition(startPosition);
288
289     VisiblePosition endPosition = endOfLine(nextVisiblePos);
290
291     // as long as the position hasn't reached the end of the doc,  keep searching for a valid line end position
292     // Unless the VisiblePosition is at the very end, there should always be a valid line range.  However, endOfLine will
293     // return null for position by a floating object, since floating object doesn't really belong to any line.
294     // This check will reposition the marker after the floating object, to ensure we get a line end.
295     while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
296         nextVisiblePos = nextVisiblePos.next();
297         endPosition = endOfLine(nextVisiblePos);
298     }
299
300     return VisiblePositionRange(startPosition, endPosition);
301 }
302
303 VisiblePositionRange AccessibilityObject::sentenceForPosition(const VisiblePosition& visiblePos) const
304 {
305     // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
306     // Related? <rdar://problem/3927736> Text selection broken in 8A336
307     VisiblePosition startPosition = startOfSentence(visiblePos);
308     VisiblePosition endPosition = endOfSentence(startPosition);
309     return VisiblePositionRange(startPosition, endPosition);
310 }
311
312 VisiblePositionRange AccessibilityObject::paragraphForPosition(const VisiblePosition& visiblePos) const
313 {
314     VisiblePosition startPosition = startOfParagraph(visiblePos);
315     VisiblePosition endPosition = endOfParagraph(startPosition);
316     return VisiblePositionRange(startPosition, endPosition);
317 }
318
319 static VisiblePosition startOfStyleRange(const VisiblePosition visiblePos)
320 {
321     RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
322     RenderObject* startRenderer = renderer;
323     RenderStyle* style = renderer->style();
324
325     // traverse backward by renderer to look for style change
326     for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) {
327         // skip non-leaf nodes
328         if (r->firstChild())
329             continue;
330
331         // stop at style change
332         if (r->style() != style)
333             break;
334
335         // remember match
336         startRenderer = r;
337     }
338
339     return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY);
340 }
341
342 static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos)
343 {
344     RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer();
345     RenderObject* endRenderer = renderer;
346     RenderStyle* style = renderer->style();
347
348     // traverse forward by renderer to look for style change
349     for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) {
350         // skip non-leaf nodes
351         if (r->firstChild())
352             continue;
353
354         // stop at style change
355         if (r->style() != style)
356             break;
357
358         // remember match
359         endRenderer = r;
360     }
361
362     return lastDeepEditingPositionForNode(endRenderer->node());
363 }
364
365 VisiblePositionRange AccessibilityObject::styleRangeForPosition(const VisiblePosition& visiblePos) const
366 {
367     if (visiblePos.isNull())
368         return VisiblePositionRange();
369
370     return VisiblePositionRange(startOfStyleRange(visiblePos), endOfStyleRange(visiblePos));
371 }
372
373 // NOTE: Consider providing this utility method as AX API
374 VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const PlainTextRange& range) const
375 {
376     unsigned textLength = getLengthForTextRange();
377     if (range.start + range.length > textLength)
378         return VisiblePositionRange();
379
380     VisiblePosition startPosition = visiblePositionForIndex(range.start);
381     startPosition.setAffinity(DOWNSTREAM);
382     VisiblePosition endPosition = visiblePositionForIndex(range.start + range.length);
383     return VisiblePositionRange(startPosition, endPosition);
384 }
385
386 static bool replacedNodeNeedsCharacter(Node* replacedNode)
387 {
388     // we should always be given a rendered node and a replaced node, but be safe
389     // replaced nodes are either attachments (widgets) or images
390     if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode())
391         return false;
392
393     // create an AX object, but skip it if it is not supposed to be seen
394     AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
395     if (object->accessibilityIsIgnored())
396         return false;
397
398     return true;
399 }
400
401 // Finds a RenderListItem parent give a node.
402 static RenderListItem* renderListItemContainerForNode(Node* node)
403 {
404     for (; node; node = node->parentNode()) {
405         RenderBoxModelObject* renderer = node->renderBoxModelObject();
406         if (renderer && renderer->isListItem())
407             return toRenderListItem(renderer);
408     }
409     return 0;
410 }
411     
412 // Returns the text associated with a list marker if this node is contained within a list item.
413 String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
414 {
415     // If the range does not contain the start of the line, the list marker text should not be included.
416     if (!isStartOfLine(visiblePositionStart))
417         return String();
418
419     RenderListItem* listItem = renderListItemContainerForNode(node);
420     if (!listItem)
421         return String();
422         
423     // If this is in a list item, we need to manually add the text for the list marker 
424     // because a RenderListMarker does not have a Node equivalent and thus does not appear
425     // when iterating text.
426     const String& markerText = listItem->markerText();
427     if (markerText.isEmpty())
428         return String();
429                 
430     // Append text, plus the period that follows the text.
431     // FIXME: Not all list marker styles are followed by a period, but this
432     // sounds much better when there is a synthesized pause because of a period.
433     Vector<UChar> resultVector;
434     resultVector.append(markerText.characters(), markerText.length());
435     resultVector.append('.');
436     resultVector.append(' ');
437     
438     return String::adopt(resultVector);
439 }
440     
441 String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
442 {
443     if (visiblePositionRange.isNull())
444         return String();
445
446     Vector<UChar> resultVector;
447     RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
448     for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
449         // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
450         if (it.length()) {
451             // Add a textual representation for list marker text
452             String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start);
453             if (!listMarkerText.isEmpty())
454                 resultVector.append(listMarkerText.characters(), listMarkerText.length());
455                 
456             resultVector.append(it.characters(), it.length());
457         } else {
458             // locate the node and starting offset for this replaced range
459             int exception = 0;
460             Node* node = it.range()->startContainer(exception);
461             ASSERT(node == it.range()->endContainer(exception));
462             int offset = it.range()->startOffset(exception);
463
464             if (replacedNodeNeedsCharacter(node->childNode(offset)))
465                 resultVector.append(objectReplacementCharacter);
466         }
467     }
468
469     return String::adopt(resultVector);
470 }
471
472 int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
473 {
474     // FIXME: Multi-byte support
475     if (visiblePositionRange.isNull())
476         return -1;
477     
478     int length = 0;
479     RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end);
480     for (TextIterator it(range.get()); !it.atEnd(); it.advance()) {
481         // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
482         if (it.length())
483             length += it.length();
484         else {
485             // locate the node and starting offset for this replaced range
486             int exception = 0;
487             Node* node = it.range()->startContainer(exception);
488             ASSERT(node == it.range()->endContainer(exception));
489             int offset = it.range()->startOffset(exception);
490
491             if (replacedNodeNeedsCharacter(node->childNode(offset)))
492                 length++;
493         }
494     }
495     
496     return length;
497 }
498
499 VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const
500 {
501     if (visiblePos.isNull())
502         return VisiblePosition();
503
504     // make sure we move off of a word end
505     VisiblePosition nextVisiblePos = visiblePos.next();
506     if (nextVisiblePos.isNull())
507         return VisiblePosition();
508
509     return endOfWord(nextVisiblePos, LeftWordIfOnBoundary);
510 }
511
512 VisiblePosition AccessibilityObject::previousWordStart(const VisiblePosition& visiblePos) const
513 {
514     if (visiblePos.isNull())
515         return VisiblePosition();
516
517     // make sure we move off of a word start
518     VisiblePosition prevVisiblePos = visiblePos.previous();
519     if (prevVisiblePos.isNull())
520         return VisiblePosition();
521
522     return startOfWord(prevVisiblePos, RightWordIfOnBoundary);
523 }
524
525 VisiblePosition AccessibilityObject::nextLineEndPosition(const VisiblePosition& visiblePos) const
526 {
527     if (visiblePos.isNull())
528         return VisiblePosition();
529
530     // to make sure we move off of a line end
531     VisiblePosition nextVisiblePos = visiblePos.next();
532     if (nextVisiblePos.isNull())
533         return VisiblePosition();
534
535     VisiblePosition endPosition = endOfLine(nextVisiblePos);
536
537     // as long as the position hasn't reached the end of the doc,  keep searching for a valid line end position
538     // 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.
539     while (endPosition.isNull() && nextVisiblePos.isNotNull()) {
540         nextVisiblePos = nextVisiblePos.next();
541         endPosition = endOfLine(nextVisiblePos);
542     }
543
544     return endPosition;
545 }
546
547 VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
548 {
549     if (visiblePos.isNull())
550         return VisiblePosition();
551
552     // make sure we move off of a line start
553     VisiblePosition prevVisiblePos = visiblePos.previous();
554     if (prevVisiblePos.isNull())
555         return VisiblePosition();
556
557     VisiblePosition startPosition = startOfLine(prevVisiblePos);
558
559     // as long as the position hasn't reached the beginning of the doc,  keep searching for a valid line start position
560     // 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.
561     if (startPosition.isNull()) {
562         while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
563             prevVisiblePos = prevVisiblePos.previous();
564             startPosition = startOfLine(prevVisiblePos);
565         }
566     } else
567         startPosition = updateAXLineStartForVisiblePosition(startPosition);
568
569     return startPosition;
570 }
571
572 VisiblePosition AccessibilityObject::nextSentenceEndPosition(const VisiblePosition& visiblePos) const
573 {
574     // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
575     // Related? <rdar://problem/3927736> Text selection broken in 8A336
576     if (visiblePos.isNull())
577         return VisiblePosition();
578
579     // make sure we move off of a sentence end
580     VisiblePosition nextVisiblePos = visiblePos.next();
581     if (nextVisiblePos.isNull())
582         return VisiblePosition();
583
584     // an empty line is considered a sentence. If it's skipped, then the sentence parser will not
585     // see this empty line.  Instead, return the end position of the empty line.
586     VisiblePosition endPosition;
587     
588     String lineString = plainText(makeRange(startOfLine(nextVisiblePos), endOfLine(nextVisiblePos)).get());
589     if (lineString.isEmpty())
590         endPosition = nextVisiblePos;
591     else
592         endPosition = endOfSentence(nextVisiblePos);
593
594     return endPosition;
595 }
596
597 VisiblePosition AccessibilityObject::previousSentenceStartPosition(const VisiblePosition& visiblePos) const
598 {
599     // FIXME: FO 2 IMPLEMENT (currently returns incorrect answer)
600     // Related? <rdar://problem/3927736> Text selection broken in 8A336
601     if (visiblePos.isNull())
602         return VisiblePosition();
603
604     // make sure we move off of a sentence start
605     VisiblePosition previousVisiblePos = visiblePos.previous();
606     if (previousVisiblePos.isNull())
607         return VisiblePosition();
608
609     // treat empty line as a separate sentence.
610     VisiblePosition startPosition;
611     
612     String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get());
613     if (lineString.isEmpty())
614         startPosition = previousVisiblePos;
615     else
616         startPosition = startOfSentence(previousVisiblePos);
617
618     return startPosition;
619 }
620
621 VisiblePosition AccessibilityObject::nextParagraphEndPosition(const VisiblePosition& visiblePos) const
622 {
623     if (visiblePos.isNull())
624         return VisiblePosition();
625
626     // make sure we move off of a paragraph end
627     VisiblePosition nextPos = visiblePos.next();
628     if (nextPos.isNull())
629         return VisiblePosition();
630
631     return endOfParagraph(nextPos);
632 }
633
634 VisiblePosition AccessibilityObject::previousParagraphStartPosition(const VisiblePosition& visiblePos) const
635 {
636     if (visiblePos.isNull())
637         return VisiblePosition();
638
639     // make sure we move off of a paragraph start
640     VisiblePosition previousPos = visiblePos.previous();
641     if (previousPos.isNull())
642         return VisiblePosition();
643
644     return startOfParagraph(previousPos);
645 }
646
647 AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const
648 {
649     if (visiblePos.isNull())
650         return 0;
651
652     RenderObject* obj = visiblePos.deepEquivalent().node()->renderer();
653     if (!obj)
654         return 0;
655
656     return obj->document()->axObjectCache()->getOrCreate(obj);
657 }
658
659 int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const
660 {
661     if (visiblePos.isNull())
662         return 0;
663
664     unsigned lineCount = 0;
665     VisiblePosition currentVisiblePos = visiblePos;
666     VisiblePosition savedVisiblePos;
667
668     // move up until we get to the top
669     // FIXME: This only takes us to the top of the rootEditableElement, not the top of the
670     // top document.
671     while (currentVisiblePos.isNotNull() && !(inSameLine(currentVisiblePos, savedVisiblePos))) {
672         ++lineCount;
673         savedVisiblePos = currentVisiblePos;
674         VisiblePosition prevVisiblePos = previousLinePosition(currentVisiblePos, 0);
675         currentVisiblePos = prevVisiblePos;
676     }
677
678     return lineCount - 1;
679 }
680
681 // NOTE: Consider providing this utility method as AX API
682 PlainTextRange AccessibilityObject::plainTextRangeForVisiblePositionRange(const VisiblePositionRange& positionRange) const
683 {
684     int index1 = index(positionRange.start);
685     int index2 = index(positionRange.end);
686     if (index1 < 0 || index2 < 0 || index1 > index2)
687         return PlainTextRange();
688
689     return PlainTextRange(index1, index2 - index1);
690 }
691
692 // The composed character range in the text associated with this accessibility object that
693 // is specified by the given screen coordinates. This parameterized attribute returns the
694 // complete range of characters (including surrogate pairs of multi-byte glyphs) at the given
695 // screen coordinates.
696 // NOTE: This varies from AppKit when the point is below the last line. AppKit returns an
697 // an error in that case. We return textControl->text().length(), 1. Does this matter?
698 PlainTextRange AccessibilityObject::doAXRangeForPosition(const IntPoint& point) const
699 {
700     int i = index(visiblePositionForPoint(point));
701     if (i < 0)
702         return PlainTextRange();
703
704     return PlainTextRange(i, 1);
705 }
706
707 // Given a character index, the range of text associated with this accessibility object
708 // over which the style in effect at that character index applies.
709 PlainTextRange AccessibilityObject::doAXStyleRangeForIndex(unsigned index) const
710 {
711     VisiblePositionRange range = styleRangeForPosition(visiblePositionForIndex(index, false));
712     return plainTextRangeForVisiblePositionRange(range);
713 }
714
715 // Given an indexed character, the line number of the text associated with this accessibility
716 // object that contains the character.
717 unsigned AccessibilityObject::doAXLineForIndex(unsigned index)
718 {
719     return lineForPosition(visiblePositionForIndex(index, false));
720 }
721
722 FrameView* AccessibilityObject::documentFrameView() const 
723
724     const AccessibilityObject* object = this;
725     while (object && !object->isAccessibilityRenderObject()) 
726         object = object->parentObject();
727         
728     if (!object)
729         return 0;
730
731     return object->documentFrameView();
732 }
733
734 void AccessibilityObject::clearChildren()
735 {
736     m_children.clear();
737     m_haveChildren = false;
738 }
739
740 AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node)
741 {
742     RenderObject* obj = node->renderer();
743     if (!obj)
744         return 0;
745     
746     RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj);
747     Element* anchor = axObj->anchorElement();
748     if (!anchor)
749         return 0;
750     
751     RenderObject* anchorRenderer = anchor->renderer();
752     if (!anchorRenderer)
753         return 0;
754     
755     return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer);
756 }
757     
758 void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
759 {
760     AccessibilityChildrenVector axChildren = children();
761     unsigned count = axChildren.size();
762     for (unsigned k = 0; k < count; ++k) {
763         AccessibilityObject* obj = axChildren[k].get();
764         
765         // Add tree items as the rows.
766         if (obj->roleValue() == TreeItemRole) 
767             result.append(obj);
768
769         // Now see if this item also has rows hiding inside of it.
770         obj->ariaTreeRows(result);
771     }
772 }
773     
774 void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
775 {
776     // The ARIA tree item content are the item that are not other tree items or their containing groups.
777     AccessibilityChildrenVector axChildren = children();
778     unsigned count = axChildren.size();
779     for (unsigned k = 0; k < count; ++k) {
780         AccessibilityObject* obj = axChildren[k].get();
781         AccessibilityRole role = obj->roleValue();
782         if (role == TreeItemRole || role == GroupRole)
783             continue;
784         
785         result.append(obj);
786     }
787 }
788
789 void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
790 {
791     AccessibilityChildrenVector axChildren = children();
792     unsigned count = axChildren.size();
793     for (unsigned k = 0; k < count; ++k) {
794         AccessibilityObject* obj = axChildren[k].get();
795         
796         // Add tree items as the rows.
797         if (obj->roleValue() == TreeItemRole)
798             result.append(obj);
799         // If it's not a tree item, then descend into the group to find more tree items.
800         else 
801             obj->ariaTreeRows(result);
802     }    
803 }
804     
805 const String& AccessibilityObject::actionVerb() const
806 {
807     // FIXME: Need to add verbs for select elements.
808     DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb()));
809     DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb()));
810     DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb()));
811     DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb()));
812     DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb()));
813     DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb()));
814     DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb()));
815     DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb()));
816     DEFINE_STATIC_LOCAL(const String, noAction, ());
817
818     switch (roleValue()) {
819     case ButtonRole:
820         return buttonAction;
821     case TextFieldRole:
822     case TextAreaRole:
823         return textFieldAction;
824     case RadioButtonRole:
825         return radioButtonAction;
826     case CheckBoxRole:
827         return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction;
828     case LinkRole:
829     case WebCoreLinkRole:
830         return linkAction;
831     case PopUpButtonRole:
832         return menuListAction;
833     case MenuListPopupRole:
834         return menuListPopupAction;
835     default:
836         return noAction;
837     }
838 }
839
840 bool AccessibilityObject::ariaIsMultiline() const
841 {
842     return equalIgnoringCase(getAttribute(aria_multilineAttr), "true");
843 }
844
845 const AtomicString& AccessibilityObject::invalidStatus() const
846 {
847     DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false"));
848     
849     // aria-invalid can return false (default), grammer, spelling, or true.
850     const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr);
851     
852     // If empty or not present, it should return false.
853     if (ariaInvalid.isEmpty())
854         return invalidStatusFalse;
855     
856     return ariaInvalid;
857 }
858  
859 const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const
860 {
861     Node* elementNode = node();
862     if (!elementNode)
863         return nullAtom;
864     
865     if (!elementNode->isElementNode())
866         return nullAtom;
867     
868     Element* element = static_cast<Element*>(elementNode);
869     return element->fastGetAttribute(attribute);
870 }
871     
872 // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width;
873 AccessibilityOrientation AccessibilityObject::orientation() const
874 {
875     IntRect bounds = elementRect();
876     if (bounds.size().width() > bounds.size().height())
877         return AccessibilityOrientationHorizontal;
878     if (bounds.size().height() > bounds.size().width())
879         return AccessibilityOrientationVertical;
880
881     // A tie goes to horizontal.
882     return AccessibilityOrientationHorizontal;
883 }    
884
885 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
886
887 struct RoleEntry {
888     String ariaRole;
889     AccessibilityRole webcoreRole;
890 };
891
892 static ARIARoleMap* createARIARoleMap()
893 {
894     const RoleEntry roles[] = {
895         { "alert", ApplicationAlertRole },
896         { "alertdialog", ApplicationAlertDialogRole },
897         { "application", LandmarkApplicationRole },
898         { "article", DocumentArticleRole },
899         { "banner", LandmarkBannerRole },
900         { "button", ButtonRole },
901         { "checkbox", CheckBoxRole },
902         { "complementary", LandmarkComplementaryRole },
903         { "contentinfo", LandmarkContentInfoRole },
904         { "dialog", ApplicationDialogRole },
905         { "directory", DirectoryRole },
906         { "grid", TableRole },
907         { "gridcell", CellRole },
908         { "columnheader", ColumnHeaderRole },
909         { "combobox", ComboBoxRole },
910         { "definition", DefinitionListDefinitionRole },
911         { "document", DocumentRole },
912         { "rowheader", RowHeaderRole },
913         { "group", GroupRole },
914         { "heading", HeadingRole },
915         { "img", ImageRole },
916         { "link", WebCoreLinkRole },
917         { "list", ListRole },        
918         { "listitem", ListItemRole },        
919         { "listbox", ListBoxRole },
920         { "log", ApplicationLogRole },
921         // "option" isn't here because it may map to different roles depending on the parent element's role
922         { "main", LandmarkMainRole },
923         { "marquee", ApplicationMarqueeRole },
924         { "math", DocumentMathRole },
925         { "menu", MenuRole },
926         { "menubar", MenuBarRole },
927         // "menuitem" isn't here because it may map to different roles depending on the parent element's role
928         { "menuitemcheckbox", MenuItemRole },
929         { "menuitemradio", MenuItemRole },
930         { "note", DocumentNoteRole },
931         { "navigation", LandmarkNavigationRole },
932         { "option", ListBoxOptionRole },
933         { "presentation", PresentationalRole },
934         { "progressbar", ProgressIndicatorRole },
935         { "radio", RadioButtonRole },
936         { "radiogroup", RadioGroupRole },
937         { "region", DocumentRegionRole },
938         { "row", RowRole },
939         { "range", SliderRole },
940         { "scrollbar", ScrollBarRole },
941         { "search", LandmarkSearchRole },
942         { "separator", SplitterRole },
943         { "slider", SliderRole },
944         { "spinbutton", ProgressIndicatorRole },
945         { "status", ApplicationStatusRole },
946         { "tab", TabRole },
947         { "tablist", TabListRole },
948         { "tabpanel", TabPanelRole },
949         { "text", StaticTextRole },
950         { "textbox", TextAreaRole },
951         { "timer", ApplicationTimerRole },
952         { "toolbar", ToolbarRole },
953         { "tooltip", UserInterfaceTooltipRole },
954         { "tree", TreeRole },
955         { "treegrid", TreeGridRole },
956         { "treeitem", TreeItemRole }
957     };
958     ARIARoleMap* roleMap = new ARIARoleMap;
959
960     for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i)
961         roleMap->set(roles[i].ariaRole, roles[i].webcoreRole);
962     return roleMap;
963 }
964
965 AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value)
966 {
967     ASSERT(!value.isEmpty());
968     static const ARIARoleMap* roleMap = createARIARoleMap();
969     return roleMap->get(value);
970 }
971
972 const AtomicString& AccessibilityObject::placeholderValue() const
973 {
974     const AtomicString& placeholder = getAttribute(placeholderAttr);
975     if (!placeholder.isEmpty())
976         return placeholder;
977     
978     return nullAtom;
979 }
980     
981 bool AccessibilityObject::isInsideARIALiveRegion() const
982 {
983     if (supportsARIALiveRegion())
984         return true;
985     
986     for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) {
987         if (axParent->supportsARIALiveRegion())
988             return true;
989     }
990     
991     return false;
992 }
993
994 bool AccessibilityObject::supportsARIAAttributes() const
995 {
996     return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns();
997 }
998     
999 bool AccessibilityObject::supportsARIALiveRegion() const
1000 {
1001     const AtomicString& liveRegion = ariaLiveRegionStatus();
1002     return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive");
1003 }
1004     
1005 AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const
1006 {
1007     // If this is a real checkbox or radio button, AccessibilityRenderObject will handle.
1008     // If it's an ARIA checkbox or radio, the aria-checked attribute should be used.
1009
1010     const AtomicString& result = getAttribute(aria_checkedAttr);
1011     if (equalIgnoringCase(result, "true"))
1012         return ButtonStateOn;
1013     if (equalIgnoringCase(result, "mixed"))
1014         return ButtonStateMixed;
1015     
1016     return ButtonStateOff;
1017 }
1018     
1019 } // namespace WebCore