1/*
2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L.
5 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
6 *
7 * Portions from Mozilla a11y, copyright as follows:
8 *
9 * The Original Code is mozilla.org code.
10 *
11 * The Initial Developer of the Original Code is
12 * Sun Microsystems, Inc.
13 * Portions created by the Initial Developer are Copyright (C) 2002
14 * the Initial Developer. All Rights Reserved.
15 *
16 * This library is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU Library General Public
18 * License as published by the Free Software Foundation; either
19 * version 2 of the License, or (at your option) any later version.
20 *
21 * This library is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24 * Library General Public License for more details.
25 *
26 * You should have received a copy of the GNU Library General Public License
27 * along with this library; see the file COPYING.LIB.  If not, write to
28 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
29 * Boston, MA 02110-1301, USA.
30 */
31
32#include "config.h"
33#include "WebKitAccessibleInterfaceText.h"
34
35#if HAVE(ACCESSIBILITY)
36
37#include "AccessibilityObject.h"
38#include "Document.h"
39#include "Font.h"
40#include "FrameView.h"
41#include "HTMLParserIdioms.h"
42#include "HostWindow.h"
43#include "InlineTextBox.h"
44#include "NotImplemented.h"
45#include "RenderListItem.h"
46#include "RenderListMarker.h"
47#include "RenderText.h"
48#include "TextEncoding.h"
49#include "TextIterator.h"
50#include "VisibleUnits.h"
51#include "WebKitAccessibleUtil.h"
52#include "WebKitAccessibleWrapperAtk.h"
53#include "htmlediting.h"
54#include <wtf/gobject/GUniquePtr.h>
55#include <wtf/text/CString.h>
56
57using namespace WebCore;
58
59// Text attribute to expose the ARIA 'aria-invalid' attribute. Initially initialized
60// to ATK_TEXT_ATTR_INVALID (which means 'invalid' text attribute'), will later on
61// hold a reference to the custom registered AtkTextAttribute that we will use.
62static AtkTextAttribute atkTextAttributeInvalid = ATK_TEXT_ATTR_INVALID;
63
64static AccessibilityObject* core(AtkText* text)
65{
66    if (!WEBKIT_IS_ACCESSIBLE(text))
67        return 0;
68
69    return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text));
70}
71
72static int baselinePositionForRenderObject(RenderObject* renderObject)
73{
74    // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was
75    // removed in r70072. The implementation looks incorrect though, because this is not the
76    // baseline of the underlying RenderObject, but of the AccessibilityRenderObject.
77    const FontMetrics& fontMetrics = renderObject->firstLineStyle().fontMetrics();
78    return fontMetrics.ascent() + (renderObject->firstLineStyle().computedLineHeight() - fontMetrics.height()) / 2;
79}
80
81static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
82{
83    if (!object->isAccessibilityRenderObject())
84        return 0;
85
86    RenderObject* renderer = object->renderer();
87    RenderStyle* style = &renderer->style();
88
89    AtkAttributeSet* result = 0;
90    GUniquePtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
91    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
92
93    Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
94    if (bgColor.isValid()) {
95        buffer.reset(g_strdup_printf("%i,%i,%i", bgColor.red(), bgColor.green(), bgColor.blue()));
96        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
97    }
98
99    Color fgColor = style->visitedDependentColor(CSSPropertyColor);
100    if (fgColor.isValid()) {
101        buffer.reset(g_strdup_printf("%i,%i,%i", fgColor.red(), fgColor.green(), fgColor.blue()));
102        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
103    }
104
105    int baselinePosition;
106    bool includeRise = true;
107    switch (style->verticalAlign()) {
108    case SUB:
109        baselinePosition = -1 * baselinePositionForRenderObject(renderer);
110        break;
111    case SUPER:
112        baselinePosition = baselinePositionForRenderObject(renderer);
113        break;
114    case BASELINE:
115        baselinePosition = 0;
116        break;
117    default:
118        includeRise = false;
119        break;
120    }
121
122    if (includeRise) {
123        buffer.reset(g_strdup_printf("%i", baselinePosition));
124        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
125    }
126
127    if (!style->textIndent().isUndefined()) {
128        int indentation = valueForLength(style->textIndent(), object->size().width());
129        buffer.reset(g_strdup_printf("%i", indentation));
130        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
131    }
132
133    String fontFamilyName = style->font().firstFamily();
134    if (fontFamilyName.left(8) == "-webkit-")
135        fontFamilyName = fontFamilyName.substring(8);
136
137    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
138
139    int fontWeight = -1;
140    switch (style->font().weight()) {
141    case FontWeight100:
142        fontWeight = 100;
143        break;
144    case FontWeight200:
145        fontWeight = 200;
146        break;
147    case FontWeight300:
148        fontWeight = 300;
149        break;
150    case FontWeight400:
151        fontWeight = 400;
152        break;
153    case FontWeight500:
154        fontWeight = 500;
155        break;
156    case FontWeight600:
157        fontWeight = 600;
158        break;
159    case FontWeight700:
160        fontWeight = 700;
161        break;
162    case FontWeight800:
163        fontWeight = 800;
164        break;
165    case FontWeight900:
166        fontWeight = 900;
167    }
168    if (fontWeight > 0) {
169        buffer.reset(g_strdup_printf("%i", fontWeight));
170        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
171    }
172
173    switch (style->textAlign()) {
174    case TASTART:
175    case TAEND:
176        break;
177    case LEFT:
178    case WEBKIT_LEFT:
179        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
180        break;
181    case RIGHT:
182    case WEBKIT_RIGHT:
183        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
184        break;
185    case CENTER:
186    case WEBKIT_CENTER:
187        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
188        break;
189    case JUSTIFY:
190        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
191    }
192
193    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & TextDecorationUnderline) ? "single" : "none");
194
195    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
196
197    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & TextDecorationLineThrough) ? "true" : "false");
198
199    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
200
201    result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
202
203    String language = object->language();
204    if (!language.isEmpty())
205        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE), language.utf8().data());
206
207    String invalidStatus = object->invalidStatus();
208    if (invalidStatus != "false") {
209        // Register the custom attribute for 'aria-invalid' if not done yet.
210        if (atkTextAttributeInvalid == ATK_TEXT_ATTR_INVALID)
211            atkTextAttributeInvalid = atk_text_attribute_register("invalid");
212
213        result = addToAtkAttributeSet(result, atk_text_attribute_get_name(atkTextAttributeInvalid), invalidStatus.utf8().data());
214    }
215
216    return result;
217}
218
219static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
220{
221    return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
222}
223
224// Returns an AtkAttributeSet with the elements of attributeSet1 which
225// are either not present or different in attributeSet2. Neither
226// attributeSet1 nor attributeSet2 should be used after calling this.
227static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, AtkAttributeSet* attributeSet2)
228{
229    if (!attributeSet2)
230        return attributeSet1;
231
232    AtkAttributeSet* currentSet = attributeSet1;
233    AtkAttributeSet* found;
234    AtkAttributeSet* toDelete = 0;
235
236    while (currentSet) {
237        found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute);
238        if (found) {
239            AtkAttributeSet* nextSet = currentSet->next;
240            toDelete = g_slist_prepend(toDelete, currentSet->data);
241            attributeSet1 = g_slist_delete_link(attributeSet1, currentSet);
242            currentSet = nextSet;
243        } else
244            currentSet = currentSet->next;
245    }
246
247    atk_attribute_set_free(attributeSet2);
248    atk_attribute_set_free(toDelete);
249    return attributeSet1;
250}
251
252static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset);
253
254static guint accessibilityObjectLength(const AccessibilityObject* object)
255{
256    // Non render objects are not taken into account
257    if (!object->isAccessibilityRenderObject())
258        return 0;
259
260    // For those objects implementing the AtkText interface we use the
261    // well known API to always get the text in a consistent way
262    AtkObject* atkObj = ATK_OBJECT(object->wrapper());
263    if (ATK_IS_TEXT(atkObj)) {
264        GUniquePtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1));
265        return g_utf8_strlen(text.get(), -1);
266    }
267
268    // Even if we don't expose list markers to Assistive
269    // Technologies, we need to have a way to measure their length
270    // for those cases when it's needed to take it into account
271    // separately (as in getAccessibilityObjectForOffset)
272    RenderObject* renderer = object->renderer();
273    if (renderer && renderer->isListMarker()) {
274        RenderListMarker& marker = toRenderListMarker(*renderer);
275        return marker.text().length() + marker.suffix().length();
276    }
277
278    return 0;
279}
280
281static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
282{
283    const AccessibilityObject* result;
284    guint length = accessibilityObjectLength(object);
285    if (length > offset) {
286        *startOffset = 0;
287        *endOffset = length;
288        result = object;
289    } else {
290        *startOffset = -1;
291        *endOffset = -1;
292        result = 0;
293    }
294
295    if (!object->firstChild())
296        return result;
297
298    AccessibilityObject* child = object->firstChild();
299    guint currentOffset = 0;
300    guint childPosition = 0;
301    while (child && currentOffset <= offset) {
302        guint childLength = accessibilityObjectLength(child);
303        currentOffset = childLength + childPosition;
304        if (currentOffset > offset) {
305            gint childStartOffset;
306            gint childEndOffset;
307            const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition,  &childStartOffset, &childEndOffset);
308            if (childStartOffset >= 0) {
309                *startOffset = childStartOffset + childPosition;
310                *endOffset = childEndOffset + childPosition;
311                result = grandChild;
312            }
313        } else {
314            childPosition += childLength;
315            child = child->nextSibling();
316        }
317    }
318    return result;
319}
320
321static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
322{
323    const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
324    if (!child) {
325        *startOffset = -1;
326        *endOffset = -1;
327        return 0;
328    }
329
330    AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
331    AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
332
333    return attributeSetDifference(childAttributes, defaultAttributes);
334}
335
336static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
337{
338    GUniquePtr<char> textContent(webkitAccessibleTextGetText(text, startOffset, -1));
339    gint textLength = g_utf8_strlen(textContent.get(), -1);
340
341    // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps.
342    gint rangeLength = length;
343    if (rangeLength < 0 || rangeLength > textLength)
344        rangeLength = textLength;
345    AccessibilityObject* coreObject = core(text);
346
347    IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
348    switch (coords) {
349    case ATK_XY_SCREEN:
350        if (Document* document = coreObject->document())
351            extents = document->view()->contentsToScreen(extents);
352        break;
353    case ATK_XY_WINDOW:
354        // No-op
355        break;
356    }
357
358    return extents;
359}
360
361static int offsetAdjustmentForListItem(const AccessibilityObject* object)
362{
363    // We need to adjust the offsets for the list item marker in
364    // Left-To-Right text, since we expose it together with the text.
365    RenderObject* renderer = object->renderer();
366    if (renderer && renderer->isListItem() && renderer->style().direction() == LTR)
367        return toRenderListItem(renderer)->markerTextWithSuffix().length();
368
369    return 0;
370}
371
372static int webCoreOffsetToAtkOffset(const AccessibilityObject* object, int offset)
373{
374    if (!object->isListItem())
375        return offset;
376
377    return offset + offsetAdjustmentForListItem(object);
378}
379
380static int atkOffsetToWebCoreOffset(AtkText* text, int offset)
381{
382    AccessibilityObject* coreObject = core(text);
383    if (!coreObject || !coreObject->isListItem())
384        return offset;
385
386    return offset - offsetAdjustmentForListItem(coreObject);
387}
388
389static Node* getNodeForAccessibilityObject(AccessibilityObject* coreObject)
390{
391    if (!coreObject->isNativeTextControl())
392        return coreObject->node();
393
394    // For text controls, we get the first visible position on it (which will
395    // belong to its inner element, unreachable from the DOM) and return its
396    // parent node, so we have a "bounding node" for the accessibility object.
397    VisiblePosition positionInTextControlInnerElement = coreObject->visiblePositionForIndex(0, true);
398    Node* innerMostNode = positionInTextControlInnerElement.deepEquivalent().anchorNode();
399    if (!innerMostNode)
400        return 0;
401
402    return innerMostNode->parentNode();
403}
404
405static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
406{
407    // Default values, unless the contrary is proved.
408    startOffset = 0;
409    endOffset = 0;
410
411    Node* node = getNodeForAccessibilityObject(coreObject);
412    if (!node)
413        return;
414
415    if (selection.isNone())
416        return;
417
418    // We need to limit our search to positions that fall inside the domain of the current object.
419    Position firstValidPosition = firstPositionInOrBeforeNode(node->firstDescendant());
420    Position lastValidPosition = lastPositionInOrAfterNode(node->lastDescendant());
421
422    // Early return with proper values if the selection falls entirely out of the object.
423    if (!selectionBelongsToObject(coreObject, selection)) {
424        startOffset = comparePositions(selection.start(), firstValidPosition) <= 0 ? 0 : accessibilityObjectLength(coreObject);
425        endOffset = startOffset;
426        return;
427    }
428
429    // Find the proper range for the selection that falls inside the object.
430    Position nodeRangeStart = selection.start();
431    if (comparePositions(nodeRangeStart, firstValidPosition) < 0)
432        nodeRangeStart = firstValidPosition;
433
434    Position nodeRangeEnd = selection.end();
435    if (comparePositions(nodeRangeEnd, lastValidPosition) > 0)
436        nodeRangeEnd = lastValidPosition;
437
438    // Calculate position of the selected range inside the object.
439    Position parentFirstPosition = firstPositionInOrBeforeNode(node);
440    RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
441
442    // Set values for start offsets and calculate initial range length.
443    // These values might be adjusted later to cover special cases.
444    startOffset = webCoreOffsetToAtkOffset(coreObject, TextIterator::rangeLength(rangeInParent.get(), true));
445    RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
446    int rangeLength = TextIterator::rangeLength(nodeRange.get(), true);
447
448    // Special cases that are only relevant when working with *_END boundaries.
449    if (selection.affinity() == UPSTREAM) {
450        VisiblePosition visibleStart(nodeRangeStart, UPSTREAM);
451        VisiblePosition visibleEnd(nodeRangeEnd, UPSTREAM);
452
453        // We need to adjust offsets when finding wrapped lines so the position at the end
454        // of the line is properly taking into account when calculating the offsets.
455        if (isEndOfLine(visibleStart) && !lineBreakExistsAtVisiblePosition(visibleStart)) {
456            if (isStartOfLine(visibleStart.next()))
457                rangeLength++;
458
459            if (!isEndOfBlock(visibleStart))
460                startOffset = std::max(startOffset - 1, 0);
461        }
462
463        if (isEndOfLine(visibleEnd) && !lineBreakExistsAtVisiblePosition(visibleEnd) && !isEndOfBlock(visibleEnd))
464            rangeLength--;
465    }
466
467    endOffset = std::min(startOffset + rangeLength, static_cast<int>(accessibilityObjectLength(coreObject)));
468}
469
470static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint endOffset)
471{
472    g_return_val_if_fail(ATK_TEXT(text), 0);
473    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
474
475    AccessibilityObject* coreObject = core(text);
476
477#if ENABLE(INPUT_TYPE_COLOR)
478    if (coreObject->roleValue() == ColorWellRole) {
479        int r, g, b;
480        coreObject->colorValue(r, g, b);
481        return g_strdup_printf("rgb %7.5f %7.5f %7.5f 1", r / 255., g / 255., b / 255.);
482    }
483#endif
484
485    String ret;
486    if (coreObject->isTextControl())
487        ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
488    else {
489        ret = coreObject->stringValue();
490        if (!ret)
491            ret = coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren));
492    }
493
494    // Prefix a item number/bullet if needed
495    int actualEndOffset = endOffset == -1 ? ret.length() : endOffset;
496    if (coreObject->roleValue() == ListItemRole) {
497        RenderObject* objRenderer = coreObject->renderer();
498        if (objRenderer && objRenderer->isListItem()) {
499            String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
500            ret = objRenderer->style().direction() == LTR ? markerText + ret : ret + markerText;
501            if (endOffset == -1)
502                actualEndOffset = ret.length() + markerText.length();
503        }
504    }
505
506    ret = ret.substring(startOffset, actualEndOffset - startOffset);
507    return g_strdup(ret.utf8().data());
508}
509
510enum GetTextRelativePosition {
511    GetTextPositionAt,
512    GetTextPositionBefore,
513    GetTextPositionAfter
514};
515
516// Convenience function to be used in early returns.
517static char* emptyTextSelectionAtOffset(int offset, int* startOffset, int* endOffset)
518{
519    *startOffset = offset;
520    *endOffset = offset;
521    return g_strdup("");
522}
523
524static char* webkitAccessibleTextGetChar(AtkText* text, int offset, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
525{
526    int actualOffset = offset;
527    if (textPosition == GetTextPositionBefore)
528        actualOffset--;
529    else if (textPosition == GetTextPositionAfter)
530        actualOffset++;
531
532    GUniquePtr<char> textData(webkitAccessibleTextGetText(text, 0, -1));
533    int textLength = g_utf8_strlen(textData.get(), -1);
534
535    *startOffset = std::max(0, actualOffset);
536    *startOffset = std::min(*startOffset, textLength);
537
538    *endOffset = std::max(0, actualOffset + 1);
539    *endOffset = std::min(*endOffset, textLength);
540
541    if (*startOffset == *endOffset)
542        return g_strdup("");
543
544    return g_utf8_substring(textData.get(), *startOffset, *endOffset);
545}
546
547static VisiblePosition nextWordStartPosition(const VisiblePosition &position)
548{
549    VisiblePosition positionAfterCurrentWord = nextWordPosition(position);
550
551    // In order to skip spaces when moving right, we advance one word further
552    // and then move one word back. This will put us at the beginning of the
553    // word following.
554    VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
555
556    if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord)
557        positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
558
559    bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(position));
560    if (movingBackwardsMovedPositionToStartOfCurrentWord)
561        positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
562
563    return positionAfterCurrentWord;
564}
565
566static VisiblePosition previousWordEndPosition(const VisiblePosition &position)
567{
568    // We move forward and then backward to position ourselves at the beginning
569    // of the current word for this boundary, making the most of the semantics
570    // of previousWordPosition() and nextWordPosition().
571    VisiblePosition positionAtStartOfCurrentWord = previousWordPosition(nextWordPosition(position));
572    VisiblePosition positionAtPreviousWord = previousWordPosition(position);
573
574    // Need to consider special cases (punctuation) when we are in the last word of a sentence.
575    if (isStartOfWord(position) && positionAtPreviousWord != position && positionAtPreviousWord == positionAtStartOfCurrentWord)
576        return nextWordPosition(positionAtStartOfCurrentWord);
577
578    // In order to skip spaces when moving left, we advance one word backwards
579    // and then move one word forward. This will put us at the beginning of
580    // the word following.
581    VisiblePosition positionBeforeSpacingAndPreceedingWord = previousWordPosition(positionAtStartOfCurrentWord);
582
583    if (positionBeforeSpacingAndPreceedingWord != positionAtStartOfCurrentWord)
584        positionAtStartOfCurrentWord = nextWordPosition(positionBeforeSpacingAndPreceedingWord);
585
586    bool movingForwardMovedPositionToEndOfCurrentWord = nextWordPosition(positionAtStartOfCurrentWord) == previousWordPosition(nextWordPosition(position));
587    if (movingForwardMovedPositionToEndOfCurrentWord)
588        positionAtStartOfCurrentWord = positionBeforeSpacingAndPreceedingWord;
589
590    return positionAtStartOfCurrentWord;
591}
592
593static VisibleSelection wordAtPositionForAtkBoundary(const AccessibilityObject* /*coreObject*/, const VisiblePosition& position, AtkTextBoundary boundaryType)
594{
595    VisiblePosition startPosition;
596    VisiblePosition endPosition;
597
598    switch (boundaryType) {
599    case ATK_TEXT_BOUNDARY_WORD_START:
600        // isStartOfWord() returns true both when at the beginning of a "real" word
601        // as when at the beginning of a whitespace range between two "real" words,
602        // since that whitespace is considered a "word" as well. And in case we are
603        // already at the beginning of a "real" word we do not need to look backwards.
604        if (isStartOfWord(position) && isWhitespace(position.characterBefore()))
605            startPosition = position;
606        else
607            startPosition = previousWordPosition(position);
608        endPosition = nextWordStartPosition(startPosition);
609
610        // We need to make sure that we look for the word in the current line when
611        // we are at the beginning of a new line, and not look into the previous one
612        // at all, which might happen when lines belong to different nodes.
613        if (isStartOfLine(position) && isStartOfLine(endPosition)) {
614            startPosition = endPosition;
615            endPosition = nextWordStartPosition(startPosition);
616        }
617        break;
618
619    case ATK_TEXT_BOUNDARY_WORD_END:
620        startPosition = previousWordEndPosition(position);
621        endPosition = nextWordPosition(startPosition);
622        break;
623
624    default:
625        ASSERT_NOT_REACHED();
626    }
627
628    VisibleSelection selectedWord(startPosition, endPosition);
629
630    // We mark the selection as 'upstream' so we can use that information later,
631    // when finding the actual offsets in getSelectionOffsetsForObject().
632    if (boundaryType == ATK_TEXT_BOUNDARY_WORD_END)
633        selectedWord.setAffinity(UPSTREAM);
634
635    return selectedWord;
636}
637
638static int numberOfReplacedElementsBeforeOffset(AtkText* text, unsigned offset)
639{
640    GUniquePtr<char> textForObject(webkitAccessibleTextGetText(text, 0, offset));
641    String textBeforeOffset = String::fromUTF8(textForObject.get());
642
643    int count = 0;
644    size_t index = textBeforeOffset.find(objectReplacementCharacter, 0);
645    while (index < offset && index != WTF::notFound) {
646        index = textBeforeOffset.find(objectReplacementCharacter, index + 1);
647        count++;
648    }
649    return count;
650}
651
652static char* webkitAccessibleTextWordForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
653{
654    AccessibilityObject* coreObject = core(text);
655    Document* document = coreObject->document();
656    if (!document)
657        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
658
659    Node* node = getNodeForAccessibilityObject(coreObject);
660    if (!node)
661        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
662
663    int actualOffset = atkOffsetToWebCoreOffset(text, offset);
664
665    // Besides of the usual conversion from ATK offsets to WebCore offsets,
666    // we need to consider the potential embedded objects that might have been
667    // inserted in the text exposed through AtkText when calculating the offset.
668    actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset);
669
670    VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset);
671    VisibleSelection currentWord = wordAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType);
672
673    // Take into account other relative positions, if needed, by
674    // calculating the new position that we would need to consider.
675    VisiblePosition newPosition = caretPosition;
676    switch (textPosition) {
677    case GetTextPositionAt:
678        break;
679
680    case GetTextPositionBefore:
681        // Early return if asking for the previous word while already at the beginning.
682        if (isFirstVisiblePositionInNode(currentWord.visibleStart(), node))
683            return emptyTextSelectionAtOffset(0, startOffset, endOffset);
684
685        if (isStartOfLine(currentWord.end()))
686            newPosition = currentWord.visibleStart().previous();
687        else
688            newPosition = startOfWord(currentWord.start(), LeftWordIfOnBoundary);
689        break;
690
691    case GetTextPositionAfter:
692        // Early return if asking for the following word while already at the end.
693        if (isLastVisiblePositionInNode(currentWord.visibleEnd(), node))
694            return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset);
695
696        if (isEndOfLine(currentWord.end()))
697            newPosition = currentWord.visibleEnd().next();
698        else
699            newPosition = endOfWord(currentWord.end(), RightWordIfOnBoundary);
700        break;
701
702    default:
703        ASSERT_NOT_REACHED();
704    }
705
706    // Determine the relevant word we are actually interested in
707    // and calculate the ATK offsets for it, then return everything.
708    VisibleSelection selectedWord = newPosition != caretPosition ? wordAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentWord;
709    getSelectionOffsetsForObject(coreObject, selectedWord, *startOffset, *endOffset);
710    return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
711}
712
713static bool isSentenceBoundary(const VisiblePosition &pos)
714{
715    if (pos.isNull())
716        return false;
717
718    // It's definitely a sentence boundary if there's nothing before.
719    if (pos.previous().isNull())
720        return true;
721
722    // We go backwards and forward to make sure about this.
723    VisiblePosition startOfPreviousSentence = startOfSentence(pos);
724    return startOfPreviousSentence.isNotNull() && pos == endOfSentence(startOfPreviousSentence);
725}
726
727static bool isWhiteSpaceBetweenSentences(const VisiblePosition& position)
728{
729    if (position.isNull())
730        return false;
731
732    if (!isWhitespace(position.characterAfter()))
733        return false;
734
735    VisiblePosition startOfWhiteSpace = startOfWord(position, RightWordIfOnBoundary);
736    VisiblePosition endOfWhiteSpace = endOfWord(startOfWhiteSpace, RightWordIfOnBoundary);
737    if (!isSentenceBoundary(startOfWhiteSpace) && !isSentenceBoundary(endOfWhiteSpace))
738        return false;
739
740    return comparePositions(startOfWhiteSpace, position) <= 0 && comparePositions(endOfWhiteSpace, position) >= 0;
741}
742
743static VisibleSelection sentenceAtPositionForAtkBoundary(const AccessibilityObject*, const VisiblePosition& position, AtkTextBoundary boundaryType)
744{
745    VisiblePosition startPosition;
746    VisiblePosition endPosition;
747
748    bool isAtStartOfSentenceForEndBoundary = isWhiteSpaceBetweenSentences(position) || isSentenceBoundary(position);
749    if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_START || !isAtStartOfSentenceForEndBoundary) {
750        startPosition = isSentenceBoundary(position) ? position : startOfSentence(position);
751        // startOfSentence might stop at a linebreak in the HTML source code,
752        // but we don't want to stop there yet, so keep going.
753        while (!isSentenceBoundary(startPosition) && isHTMLLineBreak(startPosition.characterBefore()))
754            startPosition = startOfSentence(startPosition);
755
756        endPosition = endOfSentence(startPosition);
757    }
758
759    if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_END) {
760        if (isAtStartOfSentenceForEndBoundary) {
761            startPosition = position;
762            endPosition = endOfSentence(endOfWord(position, RightWordIfOnBoundary));
763        }
764
765        // startOfSentence returns a position after any white space previous to
766        // the sentence, so we might need to adjust that offset for this boundary.
767        if (isWhitespace(startPosition.characterBefore()))
768            startPosition = startOfWord(startPosition, LeftWordIfOnBoundary);
769
770        // endOfSentence returns a position after any white space after the
771        // sentence, so we might need to adjust that offset for this boundary.
772        if (isWhitespace(endPosition.characterBefore()))
773            endPosition = startOfWord(endPosition, LeftWordIfOnBoundary);
774
775        // Finally, do some additional adjustments that might be needed if
776        // positions are at the start or the end of a line.
777        if (isStartOfLine(startPosition) && !isStartOfBlock(startPosition))
778            startPosition = startPosition.previous();
779        if (isStartOfLine(endPosition) && !isStartOfBlock(endPosition))
780            endPosition = endPosition.previous();
781    }
782
783    VisibleSelection selectedSentence(startPosition, endPosition);
784
785    // We mark the selection as 'upstream' so we can use that information later,
786    // when finding the actual offsets in getSelectionOffsetsForObject().
787    if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_END)
788        selectedSentence.setAffinity(UPSTREAM);
789
790    return selectedSentence;
791}
792
793static char* webkitAccessibleTextSentenceForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
794{
795    AccessibilityObject* coreObject = core(text);
796    Document* document = coreObject->document();
797    if (!document)
798        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
799
800    Node* node = getNodeForAccessibilityObject(coreObject);
801    if (!node)
802        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
803
804    int actualOffset = atkOffsetToWebCoreOffset(text, offset);
805
806    // Besides of the usual conversion from ATK offsets to WebCore offsets,
807    // we need to consider the potential embedded objects that might have been
808    // inserted in the text exposed through AtkText when calculating the offset.
809    actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset);
810
811    VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset);
812    VisibleSelection currentSentence = sentenceAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType);
813
814    // Take into account other relative positions, if needed, by
815    // calculating the new position that we would need to consider.
816    VisiblePosition newPosition = caretPosition;
817    switch (textPosition) {
818    case GetTextPositionAt:
819        break;
820
821    case GetTextPositionBefore:
822        // Early return if asking for the previous sentence while already at the beginning.
823        if (isFirstVisiblePositionInNode(currentSentence.visibleStart(), node))
824            return emptyTextSelectionAtOffset(0, startOffset, endOffset);
825        newPosition = currentSentence.visibleStart().previous();
826        break;
827
828    case GetTextPositionAfter:
829        // Early return if asking for the following word while already at the end.
830        if (isLastVisiblePositionInNode(currentSentence.visibleEnd(), node))
831            return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset);
832        newPosition = currentSentence.visibleEnd().next();
833        break;
834
835    default:
836        ASSERT_NOT_REACHED();
837    }
838
839    // Determine the relevant sentence we are actually interested in
840    // and calculate the ATK offsets for it, then return everything.
841    VisibleSelection selectedSentence = newPosition != caretPosition ? sentenceAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentSentence;
842    getSelectionOffsetsForObject(coreObject, selectedSentence, *startOffset, *endOffset);
843    return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
844}
845
846static VisibleSelection lineAtPositionForAtkBoundary(const AccessibilityObject* coreObject, const VisiblePosition& position, AtkTextBoundary boundaryType)
847{
848    UNUSED_PARAM(coreObject);
849    VisiblePosition startPosition;
850    VisiblePosition endPosition;
851
852    switch (boundaryType) {
853    case ATK_TEXT_BOUNDARY_LINE_START:
854        startPosition = isStartOfLine(position) ? position : logicalStartOfLine(position);
855        endPosition = logicalEndOfLine(position);
856
857        // In addition to checking that we are not at the end of a block, we need
858        // to check that endPosition has not UPSTREAM affinity, since that would
859        // cause trouble inside of text controls (we would be advancing too much).
860        if (!isEndOfBlock(endPosition) && endPosition.affinity() != UPSTREAM)
861            endPosition = endPosition.next();
862        break;
863
864    case ATK_TEXT_BOUNDARY_LINE_END:
865        startPosition = isEndOfLine(position) ? position : logicalStartOfLine(position);
866        if (!isStartOfBlock(startPosition))
867            startPosition = startPosition.previous();
868        endPosition = logicalEndOfLine(position);
869        break;
870
871    default:
872        ASSERT_NOT_REACHED();
873    }
874
875    VisibleSelection selectedLine(startPosition, endPosition);
876
877    // We mark the selection as 'upstream' so we can use that information later,
878    // when finding the actual offsets in getSelectionOffsetsForObject().
879    if (boundaryType == ATK_TEXT_BOUNDARY_LINE_END)
880        selectedLine.setAffinity(UPSTREAM);
881
882    return selectedLine;
883}
884
885static char* webkitAccessibleTextLineForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
886{
887    AccessibilityObject* coreObject = core(text);
888    Document* document = coreObject->document();
889    if (!document)
890        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
891
892    Node* node = getNodeForAccessibilityObject(coreObject);
893    if (!node)
894        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
895
896    int actualOffset = atkOffsetToWebCoreOffset(text, offset);
897
898    // Besides the usual conversion from ATK offsets to WebCore offsets,
899    // we need to consider the potential embedded objects that might have been
900    // inserted in the text exposed through AtkText when calculating the offset.
901    actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset);
902
903    VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset);
904    VisibleSelection currentLine = lineAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType);
905
906    // Take into account other relative positions, if needed, by
907    // calculating the new position that we would need to consider.
908    VisiblePosition newPosition = caretPosition;
909    switch (textPosition) {
910    case GetTextPositionAt:
911        // No need to do additional work if we are using the "at" position, we just
912        // explicitly list this case option to catch invalid values in the default case.
913        break;
914
915    case GetTextPositionBefore:
916        // Early return if asking for the previous line while already at the beginning.
917        if (isFirstVisiblePositionInNode(currentLine.visibleStart(), node))
918            return emptyTextSelectionAtOffset(0, startOffset, endOffset);
919        newPosition = currentLine.visibleStart().previous();
920        break;
921
922    case GetTextPositionAfter:
923        // Early return if asking for the following word while already at the end.
924        if (isLastVisiblePositionInNode(currentLine.visibleEnd(), node))
925            return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset);
926        newPosition = currentLine.visibleEnd().next();
927        break;
928
929    default:
930        ASSERT_NOT_REACHED();
931    }
932
933    // Determine the relevant line we are actually interested in
934    // and calculate the ATK offsets for it, then return everything.
935    VisibleSelection selectedLine = newPosition != caretPosition ? lineAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentLine;
936    getSelectionOffsetsForObject(coreObject, selectedLine, *startOffset, *endOffset);
937
938    // We might need to adjust the start or end offset to include the list item marker,
939    // if present, when printing the first or the last full line for a list item.
940    RenderObject* renderer = coreObject->renderer();
941    if (renderer->isListItem()) {
942        // For Left-to-Right, the list item marker is at the beginning of the exposed text.
943        if (renderer->style().direction() == LTR && isFirstVisiblePositionInNode(selectedLine.visibleStart(), node))
944            *startOffset = 0;
945
946        // For Right-to-Left, the list item marker is at the end of the exposed text.
947        if (renderer->style().direction() == RTL && isLastVisiblePositionInNode(selectedLine.visibleEnd(), node))
948            *endOffset = accessibilityObjectLength(coreObject);
949    }
950
951    return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
952}
953
954static gchar* webkitAccessibleTextGetTextForOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, gint* startOffset, gint* endOffset)
955{
956    AccessibilityObject* coreObject = core(text);
957    if (!coreObject || !coreObject->isAccessibilityRenderObject())
958        return emptyTextSelectionAtOffset(0, startOffset, endOffset);
959
960    switch (boundaryType) {
961    case ATK_TEXT_BOUNDARY_CHAR:
962        return webkitAccessibleTextGetChar(text, offset, textPosition, startOffset, endOffset);
963
964    case ATK_TEXT_BOUNDARY_WORD_START:
965    case ATK_TEXT_BOUNDARY_WORD_END:
966        return webkitAccessibleTextWordForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset);
967
968    case ATK_TEXT_BOUNDARY_LINE_START:
969    case ATK_TEXT_BOUNDARY_LINE_END:
970        return webkitAccessibleTextLineForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset);
971
972    case ATK_TEXT_BOUNDARY_SENTENCE_START:
973    case ATK_TEXT_BOUNDARY_SENTENCE_END:
974        return webkitAccessibleTextSentenceForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset);
975
976    default:
977        ASSERT_NOT_REACHED();
978    }
979
980    // This should never be reached.
981    return 0;
982}
983
984static gchar* webkitAccessibleTextGetTextAfterOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
985{
986    g_return_val_if_fail(ATK_TEXT(text), 0);
987    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
988
989    return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAfter, startOffset, endOffset);
990}
991
992static gchar* webkitAccessibleTextGetTextAtOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
993{
994    g_return_val_if_fail(ATK_TEXT(text), 0);
995    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
996
997    return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAt, startOffset, endOffset);
998}
999
1000static gchar* webkitAccessibleTextGetTextBeforeOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1001{
1002    g_return_val_if_fail(ATK_TEXT(text), 0);
1003    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1004
1005    return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionBefore, startOffset, endOffset);
1006}
1007
1008static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText* text, gint)
1009{
1010    g_return_val_if_fail(ATK_TEXT(text), 0);
1011    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1012
1013    notImplemented();
1014    return 0;
1015}
1016
1017static gint webkitAccessibleTextGetCaretOffset(AtkText* text)
1018{
1019    g_return_val_if_fail(ATK_TEXT(text), 0);
1020    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1021
1022    // coreObject is the unignored object whose offset the caller is requesting.
1023    // focusedObject is the object with the caret. It is likely ignored -- unless it's a link.
1024    AccessibilityObject* coreObject = core(text);
1025    if (!coreObject->isAccessibilityRenderObject())
1026        return 0;
1027
1028    // We need to make sure we pass a valid object as reference.
1029    if (coreObject->accessibilityIsIgnored())
1030        coreObject = coreObject->parentObjectUnignored();
1031    if (!coreObject)
1032        return 0;
1033
1034    int offset;
1035    if (!objectFocusedAndCaretOffsetUnignored(coreObject, offset))
1036        return 0;
1037
1038    return webCoreOffsetToAtkOffset(coreObject, offset);
1039}
1040
1041static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
1042{
1043    g_return_val_if_fail(ATK_TEXT(text), 0);
1044    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1045
1046    AccessibilityObject* coreObject = core(text);
1047    AtkAttributeSet* result;
1048
1049    if (!coreObject) {
1050        *startOffset = 0;
1051        *endOffset = atk_text_get_character_count(text);
1052        return 0;
1053    }
1054
1055    if (offset == -1)
1056        offset = atk_text_get_caret_offset(text);
1057
1058    result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
1059
1060    if (*startOffset < 0) {
1061        *startOffset = offset;
1062        *endOffset = offset;
1063    }
1064
1065    return result;
1066}
1067
1068static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text)
1069{
1070    g_return_val_if_fail(ATK_TEXT(text), 0);
1071    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1072
1073    AccessibilityObject* coreObject = core(text);
1074    if (!coreObject || !coreObject->isAccessibilityRenderObject())
1075        return 0;
1076
1077    return getAttributeSetForAccessibilityObject(coreObject);
1078}
1079
1080static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
1081{
1082    g_return_if_fail(ATK_TEXT(text));
1083    returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text));
1084
1085    IntRect extents = textExtents(text, offset, 1, coords);
1086    *x = extents.x();
1087    *y = extents.y();
1088    *width = extents.width();
1089    *height = extents.height();
1090}
1091
1092static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
1093{
1094    g_return_if_fail(ATK_TEXT(text));
1095    returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text));
1096
1097    IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
1098    rect->x = extents.x();
1099    rect->y = extents.y();
1100    rect->width = extents.width();
1101    rect->height = extents.height();
1102}
1103
1104static gint webkitAccessibleTextGetCharacterCount(AtkText* text)
1105{
1106    g_return_val_if_fail(ATK_TEXT(text), 0);
1107    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1108
1109    return accessibilityObjectLength(core(text));
1110}
1111
1112static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, AtkCoordType)
1113{
1114    g_return_val_if_fail(ATK_TEXT(text), 0);
1115    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1116
1117    // FIXME: Use the AtkCoordType
1118    // TODO: Is it correct to ignore range.length?
1119    IntPoint pos(x, y);
1120    PlainTextRange range = core(text)->doAXRangeForPosition(pos);
1121    return range.start;
1122}
1123
1124static gint webkitAccessibleTextGetNSelections(AtkText* text)
1125{
1126    g_return_val_if_fail(ATK_TEXT(text), 0);
1127    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1128
1129    AccessibilityObject* coreObject = core(text);
1130    VisibleSelection selection = coreObject->selection();
1131
1132    // Only range selections are needed for the purpose of this method
1133    if (!selection.isRange())
1134        return 0;
1135
1136    // We don't support multiple selections for now, so there's only
1137    // two possibilities
1138    // Also, we don't want to do anything if the selection does not
1139    // belong to the currently selected object. We have to check since
1140    // there's no way to get the selection for a given object, only
1141    // the global one (the API is a bit confusing)
1142    return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
1143}
1144
1145static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
1146{
1147    g_return_val_if_fail(ATK_TEXT(text), 0);
1148    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0);
1149
1150    // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1151    if (selectionNum)
1152        return 0;
1153
1154    // Get the offsets of the selection for the selected object
1155    AccessibilityObject* coreObject = core(text);
1156    VisibleSelection selection = coreObject->selection();
1157    getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
1158
1159    // Return 0 instead of "", as that's the expected result for
1160    // this AtkText method when there's no selection
1161    if (*startOffset == *endOffset)
1162        return 0;
1163
1164    return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
1165}
1166
1167static gboolean webkitAccessibleTextAddSelection(AtkText* text, gint, gint)
1168{
1169    g_return_val_if_fail(ATK_TEXT(text), FALSE);
1170    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE);
1171
1172    notImplemented();
1173    return FALSE;
1174}
1175
1176static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
1177{
1178    g_return_val_if_fail(ATK_TEXT(text), FALSE);
1179    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE);
1180
1181    // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1182    if (selectionNum)
1183        return FALSE;
1184
1185    AccessibilityObject* coreObject = core(text);
1186    if (!coreObject->isAccessibilityRenderObject())
1187        return FALSE;
1188
1189    // Consider -1 and out-of-bound values and correct them to length
1190    gint textCount = webkitAccessibleTextGetCharacterCount(text);
1191    if (startOffset < 0 || startOffset > textCount)
1192        startOffset = textCount;
1193    if (endOffset < 0 || endOffset > textCount)
1194        endOffset = textCount;
1195
1196    // We need to adjust the offsets for the list item marker.
1197    int offsetAdjustment = offsetAdjustmentForListItem(coreObject);
1198    if (offsetAdjustment) {
1199        if (startOffset < offsetAdjustment || endOffset < offsetAdjustment)
1200            return FALSE;
1201
1202        startOffset = atkOffsetToWebCoreOffset(text, startOffset);
1203        endOffset = atkOffsetToWebCoreOffset(text, endOffset);
1204    }
1205
1206    PlainTextRange textRange(startOffset, endOffset - startOffset);
1207    VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1208    if (range.isNull())
1209        return FALSE;
1210
1211    coreObject->setSelectedVisiblePositionRange(range);
1212    return TRUE;
1213}
1214
1215static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectionNum)
1216{
1217    g_return_val_if_fail(ATK_TEXT(text), FALSE);
1218    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE);
1219
1220    // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1221    if (selectionNum)
1222        return FALSE;
1223
1224    // Do nothing if current selection doesn't belong to the object
1225    if (!webkitAccessibleTextGetNSelections(text))
1226        return FALSE;
1227
1228    // Set a new 0-sized selection to the caret position, in order
1229    // to simulate selection removal (GAIL style)
1230    gint caretOffset = webkitAccessibleTextGetCaretOffset(text);
1231    return webkitAccessibleTextSetSelection(text, selectionNum, caretOffset, caretOffset);
1232}
1233
1234static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset)
1235{
1236    // Internally, setting the caret offset is equivalent to set a zero-length
1237    // selection, so delegate in that implementation and void duplicated code.
1238    return webkitAccessibleTextSetSelection(text, 0, offset, offset);
1239}
1240
1241#if ATK_CHECK_VERSION(2, 10, 0)
1242static gchar* webkitAccessibleTextGetStringAtOffset(AtkText* text, gint offset, AtkTextGranularity granularity, gint* startOffset, gint* endOffset)
1243{
1244    // This new API has been designed to simplify the AtkText interface and it has been
1245    // designed to keep exactly the same behaviour the atk_text_get_text_at_text() for
1246    // ATK_TEXT_BOUNDARY_*_START boundaries, so for now we just need to translate the
1247    // granularity to the right old boundary and reuse the code for the old API.
1248    // However, this should be simplified later on (and a lot of code removed) once
1249    // WebKitGTK+ depends on ATK >= 2.9.4 *and* can safely assume that a version of
1250    // AT-SPI2 new enough not to include the old APIs is being used. But until then,
1251    // we will have to live with both the old and new APIs implemented here.
1252    AtkTextBoundary boundaryType = ATK_TEXT_BOUNDARY_CHAR;
1253    switch (granularity) {
1254    case ATK_TEXT_GRANULARITY_CHAR:
1255        break;
1256
1257    case ATK_TEXT_GRANULARITY_WORD:
1258        boundaryType = ATK_TEXT_BOUNDARY_WORD_START;
1259        break;
1260
1261    case ATK_TEXT_GRANULARITY_SENTENCE:
1262        boundaryType = ATK_TEXT_BOUNDARY_SENTENCE_START;
1263        break;
1264
1265    case ATK_TEXT_GRANULARITY_LINE:
1266        boundaryType = ATK_TEXT_BOUNDARY_LINE_START;
1267        break;
1268
1269    case ATK_TEXT_GRANULARITY_PARAGRAPH:
1270        // FIXME: This has not been a need with the old AtkText API, which means ATs won't
1271        // need it yet for some time, so we can skip it for now.
1272        notImplemented();
1273        return g_strdup("");
1274
1275    default:
1276        ASSERT_NOT_REACHED();
1277    }
1278
1279    return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAt, startOffset, endOffset);
1280}
1281#endif
1282
1283void webkitAccessibleTextInterfaceInit(AtkTextIface* iface)
1284{
1285    iface->get_text = webkitAccessibleTextGetText;
1286    iface->get_text_after_offset = webkitAccessibleTextGetTextAfterOffset;
1287    iface->get_text_at_offset = webkitAccessibleTextGetTextAtOffset;
1288    iface->get_text_before_offset = webkitAccessibleTextGetTextBeforeOffset;
1289    iface->get_character_at_offset = webkitAccessibleTextGetCharacterAtOffset;
1290    iface->get_caret_offset = webkitAccessibleTextGetCaretOffset;
1291    iface->get_run_attributes = webkitAccessibleTextGetRunAttributes;
1292    iface->get_default_attributes = webkitAccessibleTextGetDefaultAttributes;
1293    iface->get_character_extents = webkitAccessibleTextGetCharacterExtents;
1294    iface->get_range_extents = webkitAccessibleTextGetRangeExtents;
1295    iface->get_character_count = webkitAccessibleTextGetCharacterCount;
1296    iface->get_offset_at_point = webkitAccessibleTextGetOffsetAtPoint;
1297    iface->get_n_selections = webkitAccessibleTextGetNSelections;
1298    iface->get_selection = webkitAccessibleTextGetSelection;
1299    iface->add_selection = webkitAccessibleTextAddSelection;
1300    iface->remove_selection = webkitAccessibleTextRemoveSelection;
1301    iface->set_selection = webkitAccessibleTextSetSelection;
1302    iface->set_caret_offset = webkitAccessibleTextSetCaretOffset;
1303
1304#if ATK_CHECK_VERSION(2, 10, 0)
1305    iface->get_string_at_offset = webkitAccessibleTextGetStringAtOffset;
1306#endif
1307}
1308
1309#endif
1310