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