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