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