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