2b20f9facdf5f1de0c0301552904b200ba9135ca
[WebKit-https.git] / Source / WebCore / accessibility / gtk / 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  *
6  * Portions from Mozilla a11y, copyright as follows:
7  *
8  * The Original Code is mozilla.org code.
9  *
10  * The Initial Developer of the Original Code is
11  * Sun Microsystems, Inc.
12  * Portions created by the Initial Developer are Copyright (C) 2002
13  * the Initial Developer. All Rights Reserved.
14  *
15  * This library is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU Library General Public
17  * License as published by the Free Software Foundation; either
18  * version 2 of the License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23  * Library General Public License for more details.
24  *
25  * You should have received a copy of the GNU Library General Public License
26  * along with this library; see the file COPYING.LIB.  If not, write to
27  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28  * Boston, MA 02110-1301, USA.
29  */
30
31 #include "config.h"
32 #include "WebKitAccessibleInterfaceText.h"
33
34 #include "AccessibilityObject.h"
35 #include "Document.h"
36 #include "FrameView.h"
37 #include <wtf/gobject/GOwnPtr.h>
38 #include "HostWindow.h"
39 #include "InlineTextBox.h"
40 #include "NotImplemented.h"
41 #include "RenderListItem.h"
42 #include "RenderListMarker.h"
43 #include "RenderText.h"
44 #include "TextEncoding.h"
45 #include "TextIterator.h"
46 #include "WebKitAccessibleUtil.h"
47 #include "WebKitAccessibleWrapperAtk.h"
48 #include "htmlediting.h"
49 #include <libgail-util/gail-util.h>
50 #include <pango/pango.h>
51
52 using namespace WebCore;
53
54 static AccessibilityObject* core(AtkText* text)
55 {
56     if (!WEBKIT_IS_ACCESSIBLE(text))
57         return 0;
58
59     return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text));
60 }
61
62 static gchar* textForRenderer(RenderObject* renderer)
63 {
64     GString* resultText = g_string_new(0);
65
66     if (!renderer)
67         return g_string_free(resultText, FALSE);
68
69     // For RenderBlocks, piece together the text from the RenderText objects they contain.
70     for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) {
71         if (object->isBR()) {
72             g_string_append(resultText, "\n");
73             continue;
74         }
75
76         RenderText* renderText;
77         if (object->isText())
78             renderText = toRenderText(object);
79         else {
80             // List item's markers will be treated in an special way
81             // later on this function, so ignore them here.
82             if (object->isReplaced() && !renderer->isListItem())
83                 g_string_append_unichar(resultText, objectReplacementCharacter);
84
85             // We need to check children, if any, to consider when
86             // current object is not a text object but some of its
87             // children are, in order not to miss those portions of
88             // text by not properly handling those situations
89             if (object->firstChild())
90                 g_string_append(resultText, textForRenderer(object));
91
92             continue;
93         }
94
95         InlineTextBox* box = renderText ? renderText->firstTextBox() : 0;
96         while (box) {
97             // WebCore introduces line breaks in the text that do not reflect
98             // the layout you see on the screen, replace them with spaces.
99             String text = String(renderText->characters(), renderText->textLength()).replace("\n", " ");
100             g_string_append(resultText, text.substring(box->start(), box->end() - box->start() + 1).utf8().data());
101
102             // Newline chars in the source result in separate text boxes, so check
103             // before adding a newline in the layout. See bug 25415 comment #78.
104             // If the next sibling is a BR, we'll add the newline when we examine that child.
105             if (!box->nextOnLineExists() && !(object->nextSibling() && object->nextSibling()->isBR())) {
106                 // If there was a '\n' in the last position of the
107                 // current text box, it would have been converted to a
108                 // space in String::replace(), so remove it first.
109                 if (renderText->characters()[box->end()] == '\n')
110                     g_string_erase(resultText, resultText->len - 1, -1);
111
112                 g_string_append(resultText, "\n");
113             }
114             box = box->nextTextBox();
115         }
116     }
117
118     // Insert the text of the marker for list item in the right place, if present
119     if (renderer->isListItem()) {
120         String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
121         if (renderer->style()->direction() == LTR)
122             g_string_prepend(resultText, markerText.utf8().data());
123         else
124             g_string_append(resultText, markerText.utf8().data());
125     }
126
127     return g_string_free(resultText, FALSE);
128 }
129
130 static gchar* textForObject(AccessibilityObject* coreObject)
131 {
132     GString* str = g_string_new(0);
133
134     // For text controls, we can get the text line by line.
135     if (coreObject->isTextControl()) {
136         unsigned textLength = coreObject->textLength();
137         int lineNumber = 0;
138         PlainTextRange range = coreObject->doAXRangeForLine(lineNumber);
139         while (range.length) {
140             // When a line of text wraps in a text area, the final space is removed.
141             if (range.start + range.length < textLength)
142                 range.length -= 1;
143             String lineText = coreObject->doAXStringForRange(range);
144             g_string_append(str, lineText.utf8().data());
145             g_string_append(str, "\n");
146             range = coreObject->doAXRangeForLine(++lineNumber);
147         }
148     } else if (coreObject->isAccessibilityRenderObject()) {
149         GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
150         g_string_append(str, rendererText.get());
151     }
152
153     return g_string_free(str, FALSE);
154 }
155
156 static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset);
157
158 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
159 {
160     GailTextUtil* gailTextUtil = gail_text_util_new();
161     gail_text_util_text_setup(gailTextUtil, webkitAccessibleTextGetText(textObject, 0, -1));
162     return gailTextUtil;
163 }
164
165 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
166 {
167     AccessibilityObject* coreObject = core(textObject);
168
169     Document* document = coreObject->document();
170     if (!document)
171         return 0;
172
173     HostWindow* hostWindow = document->view()->hostWindow();
174     if (!hostWindow)
175         return 0;
176     PlatformPageClient webView = hostWindow->platformPageClient();
177     if (!webView)
178         return 0;
179
180     // Create a string with the layout as it appears on the screen
181     PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(coreObject));
182     return layout;
183 }
184
185 static int baselinePositionForRenderObject(RenderObject* renderObject)
186 {
187     // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was
188     // removed in r70072. The implementation looks incorrect though, because this is not the
189     // baseline of the underlying RenderObject, but of the AccessibilityRenderObject.
190     const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
191     return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
192 }
193
194 static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
195 {
196     if (!object->isAccessibilityRenderObject())
197         return 0;
198
199     RenderObject* renderer = object->renderer();
200     RenderStyle* style = renderer->style();
201
202     AtkAttributeSet* result = 0;
203     GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
204     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
205
206     Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
207     if (bgColor.isValid()) {
208         buffer.set(g_strdup_printf("%i,%i,%i",
209                                    bgColor.red(), bgColor.green(), bgColor.blue()));
210         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
211     }
212
213     Color fgColor = style->visitedDependentColor(CSSPropertyColor);
214     if (fgColor.isValid()) {
215         buffer.set(g_strdup_printf("%i,%i,%i",
216                                    fgColor.red(), fgColor.green(), fgColor.blue()));
217         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
218     }
219
220     int baselinePosition;
221     bool includeRise = true;
222     switch (style->verticalAlign()) {
223     case SUB:
224         baselinePosition = -1 * baselinePositionForRenderObject(renderer);
225         break;
226     case SUPER:
227         baselinePosition = baselinePositionForRenderObject(renderer);
228         break;
229     case BASELINE:
230         baselinePosition = 0;
231         break;
232     default:
233         includeRise = false;
234         break;
235     }
236
237     if (includeRise) {
238         buffer.set(g_strdup_printf("%i", baselinePosition));
239         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
240     }
241
242     if (!style->textIndent().isUndefined()) {
243         int indentation = valueForLength(style->textIndent(), object->size().width(), renderer->view());
244         buffer.set(g_strdup_printf("%i", indentation));
245         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
246     }
247
248     String fontFamilyName = style->font().family().family().string();
249     if (fontFamilyName.left(8) == "-webkit-")
250         fontFamilyName = fontFamilyName.substring(8);
251
252     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
253
254     int fontWeight = -1;
255     switch (style->font().weight()) {
256     case FontWeight100:
257         fontWeight = 100;
258         break;
259     case FontWeight200:
260         fontWeight = 200;
261         break;
262     case FontWeight300:
263         fontWeight = 300;
264         break;
265     case FontWeight400:
266         fontWeight = 400;
267         break;
268     case FontWeight500:
269         fontWeight = 500;
270         break;
271     case FontWeight600:
272         fontWeight = 600;
273         break;
274     case FontWeight700:
275         fontWeight = 700;
276         break;
277     case FontWeight800:
278         fontWeight = 800;
279         break;
280     case FontWeight900:
281         fontWeight = 900;
282     }
283     if (fontWeight > 0) {
284         buffer.set(g_strdup_printf("%i", fontWeight));
285         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
286     }
287
288     switch (style->textAlign()) {
289     case TAAUTO:
290     case TASTART:
291     case TAEND:
292         break;
293     case LEFT:
294     case WEBKIT_LEFT:
295         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
296         break;
297     case RIGHT:
298     case WEBKIT_RIGHT:
299         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
300         break;
301     case CENTER:
302     case WEBKIT_CENTER:
303         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
304         break;
305     case JUSTIFY:
306         result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
307     }
308
309     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none");
310
311     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
312
313     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false");
314
315     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
316
317     result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
318
319     return result;
320 }
321
322 static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
323 {
324     return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
325 }
326
327 // Returns an AtkAttributeSet with the elements of attributeSet1 which
328 // are either not present or different in attributeSet2. Neither
329 // attributeSet1 nor attributeSet2 should be used after calling this.
330 static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, AtkAttributeSet* attributeSet2)
331 {
332     if (!attributeSet2)
333         return attributeSet1;
334
335     AtkAttributeSet* currentSet = attributeSet1;
336     AtkAttributeSet* found;
337     AtkAttributeSet* toDelete = 0;
338
339     while (currentSet) {
340         found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute);
341         if (found) {
342             AtkAttributeSet* nextSet = currentSet->next;
343             toDelete = g_slist_prepend(toDelete, currentSet->data);
344             attributeSet1 = g_slist_delete_link(attributeSet1, currentSet);
345             currentSet = nextSet;
346         } else
347             currentSet = currentSet->next;
348     }
349
350     atk_attribute_set_free(attributeSet2);
351     atk_attribute_set_free(toDelete);
352     return attributeSet1;
353 }
354
355 static guint accessibilityObjectLength(const AccessibilityObject* object)
356 {
357     // Non render objects are not taken into account
358     if (!object->isAccessibilityRenderObject())
359         return 0;
360
361     // For those objects implementing the AtkText interface we use the
362     // well known API to always get the text in a consistent way
363     AtkObject* atkObj = ATK_OBJECT(object->wrapper());
364     if (ATK_IS_TEXT(atkObj)) {
365         GOwnPtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1));
366         return g_utf8_strlen(text.get(), -1);
367     }
368
369     // Even if we don't expose list markers to Assistive
370     // Technologies, we need to have a way to measure their length
371     // for those cases when it's needed to take it into account
372     // separately (as in getAccessibilityObjectForOffset)
373     RenderObject* renderer = object->renderer();
374     if (renderer && renderer->isListMarker()) {
375         RenderListMarker* marker = toRenderListMarker(renderer);
376         return marker->text().length() + marker->suffix().length();
377     }
378
379     return 0;
380 }
381
382 static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
383 {
384     const AccessibilityObject* result;
385     guint length = accessibilityObjectLength(object);
386     if (length > offset) {
387         *startOffset = 0;
388         *endOffset = length;
389         result = object;
390     } else {
391         *startOffset = -1;
392         *endOffset = -1;
393         result = 0;
394     }
395
396     if (!object->firstChild())
397         return result;
398
399     AccessibilityObject* child = object->firstChild();
400     guint currentOffset = 0;
401     guint childPosition = 0;
402     while (child && currentOffset <= offset) {
403         guint childLength = accessibilityObjectLength(child);
404         currentOffset = childLength + childPosition;
405         if (currentOffset > offset) {
406             gint childStartOffset;
407             gint childEndOffset;
408             const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition,  &childStartOffset, &childEndOffset);
409             if (childStartOffset >= 0) {
410                 *startOffset = childStartOffset + childPosition;
411                 *endOffset = childEndOffset + childPosition;
412                 result = grandChild;
413             }
414         } else {
415             childPosition += childLength;
416             child = child->nextSibling();
417         }
418     }
419     return result;
420 }
421
422 static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
423 {
424     const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
425     if (!child) {
426         *startOffset = -1;
427         *endOffset = -1;
428         return 0;
429     }
430
431     AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
432     AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
433
434     return attributeSetDifference(childAttributes, defaultAttributes);
435 }
436
437 static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
438 {
439     gchar* textContent = webkitAccessibleTextGetText(text, startOffset, -1);
440     gint textLength = g_utf8_strlen(textContent, -1);
441
442     // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps.
443     gint rangeLength = length;
444     if (rangeLength < 0 || rangeLength > textLength)
445         rangeLength = textLength;
446     AccessibilityObject* coreObject = core(text);
447
448     IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
449     switch (coords) {
450     case ATK_XY_SCREEN:
451         if (Document* document = coreObject->document())
452             extents = document->view()->contentsToScreen(extents);
453         break;
454     case ATK_XY_WINDOW:
455         // No-op
456         break;
457     }
458
459     return extents;
460 }
461
462 static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
463 {
464     if (!coreObject->isAccessibilityRenderObject())
465         return;
466
467     // Early return if the selection doesn't affect the selected node.
468     if (!selectionBelongsToObject(coreObject, selection))
469         return;
470
471     // We need to find the exact start and end positions in the
472     // selected node that intersects the selection, to later on get
473     // the right values for the effective start and end offsets.
474     ExceptionCode ec = 0;
475     Position nodeRangeStart;
476     Position nodeRangeEnd;
477     Node* node = coreObject->node();
478     RefPtr<Range> selRange = selection.toNormalizedRange();
479
480     // If the selection affects the selected node and its first
481     // possible position is also in the selection, we must set
482     // nodeRangeStart to that position, otherwise to the selection's
483     // start position (it would belong to the node anyway).
484     Node* firstLeafNode = node->firstDescendant();
485     if (selRange->isPointInRange(firstLeafNode, 0, ec))
486         nodeRangeStart = firstPositionInOrBeforeNode(firstLeafNode);
487     else
488         nodeRangeStart = selRange->startPosition();
489
490     // If the selection affects the selected node and its last
491     // possible position is also in the selection, we must set
492     // nodeRangeEnd to that position, otherwise to the selection's
493     // end position (it would belong to the node anyway).
494     Node* lastLeafNode = node->lastDescendant();
495     if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec))
496         nodeRangeEnd = lastPositionInOrAfterNode(lastLeafNode);
497     else
498         nodeRangeEnd = selRange->endPosition();
499
500     // Calculate position of the selected range inside the object.
501     Position parentFirstPosition = firstPositionInOrBeforeNode(node);
502     RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
503
504     // Set values for start and end offsets.
505     startOffset = TextIterator::rangeLength(rangeInParent.get(), true);
506
507     // We need to adjust the offsets for the list item marker.
508     RenderObject* renderer = coreObject->renderer();
509     if (renderer && renderer->isListItem()) {
510         String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
511         startOffset += markerText.length();
512     }
513
514     RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
515     endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true);
516 }
517
518 static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint endOffset)
519 {
520     AccessibilityObject* coreObject = core(text);
521
522     int end = endOffset;
523     if (endOffset == -1) {
524         end = coreObject->stringValue().length();
525         if (!end)
526             end = coreObject->textUnderElement().length();
527     }
528
529     String ret;
530     if (coreObject->isTextControl())
531         ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
532     else {
533         ret = coreObject->stringValue();
534         if (!ret)
535             ret = coreObject->textUnderElement();
536     }
537
538     if (!ret.length()) {
539         // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
540         ret = String(textForObject(coreObject));
541         if (!end)
542             end = ret.length();
543     }
544
545     // Prefix a item number/bullet if needed
546     if (coreObject->roleValue() == ListItemRole) {
547         RenderObject* objRenderer = coreObject->renderer();
548         if (objRenderer && objRenderer->isListItem()) {
549             String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
550             ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
551             if (endOffset == -1)
552                 end += markerText.length();
553         }
554     }
555
556     ret = ret.substring(startOffset, end - startOffset);
557     return g_strdup(ret.utf8().data());
558 }
559
560 static gchar* webkitAccessibleTextGetTextAfterOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
561 {
562     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset);
563 }
564
565 static gchar* webkitAccessibleTextGetTextAtOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
566 {
567     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset);
568 }
569
570 static gchar* webkitAccessibleTextGetTextBeforeOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
571 {
572     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset);
573 }
574
575 static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText*, gint)
576 {
577     notImplemented();
578     return 0;
579 }
580
581 static gint webkitAccessibleTextGetCaretOffset(AtkText* text)
582 {
583     // coreObject is the unignored object whose offset the caller is requesting.
584     // focusedObject is the object with the caret. It is likely ignored -- unless it's a link.
585     AccessibilityObject* coreObject = core(text);
586     if (!coreObject->isAccessibilityRenderObject())
587         return 0;
588
589     // We need to make sure we pass a valid object as reference.
590     if (coreObject->accessibilityIsIgnored())
591         coreObject = coreObject->parentObjectUnignored();
592     if (!coreObject)
593         return 0;
594
595     int offset;
596     if (!objectFocusedAndCaretOffsetUnignored(coreObject, offset))
597         return 0;
598
599     RenderObject* renderer = coreObject->renderer();
600     if (renderer && renderer->isListItem()) {
601         String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
602
603         // We need to adjust the offset for the list item marker.
604         offset += markerText.length();
605     }
606
607     // TODO: Verify this for RTL text.
608     return offset;
609 }
610
611 static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
612 {
613     AccessibilityObject* coreObject = core(text);
614     AtkAttributeSet* result;
615
616     if (!coreObject) {
617         *startOffset = 0;
618         *endOffset = atk_text_get_character_count(text);
619         return 0;
620     }
621
622     if (offset == -1)
623         offset = atk_text_get_caret_offset(text);
624
625     result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
626
627     if (*startOffset < 0) {
628         *startOffset = offset;
629         *endOffset = offset;
630     }
631
632     return result;
633 }
634
635 static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text)
636 {
637     AccessibilityObject* coreObject = core(text);
638     if (!coreObject || !coreObject->isAccessibilityRenderObject())
639         return 0;
640
641     return getAttributeSetForAccessibilityObject(coreObject);
642 }
643
644 static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
645 {
646     IntRect extents = textExtents(text, offset, 1, coords);
647     *x = extents.x();
648     *y = extents.y();
649     *width = extents.width();
650     *height = extents.height();
651 }
652
653 static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
654 {
655     IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
656     rect->x = extents.x();
657     rect->y = extents.y();
658     rect->width = extents.width();
659     rect->height = extents.height();
660 }
661
662 static gint webkitAccessibleTextGetCharacterCount(AtkText* text)
663 {
664     return accessibilityObjectLength(core(text));
665 }
666
667 static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, AtkCoordType coords)
668 {
669     // FIXME: Use the AtkCoordType
670     // TODO: Is it correct to ignore range.length?
671     IntPoint pos(x, y);
672     PlainTextRange range = core(text)->doAXRangeForPosition(pos);
673     return range.start;
674 }
675
676 static gint webkitAccessibleTextGetNSelections(AtkText* text)
677 {
678     AccessibilityObject* coreObject = core(text);
679     VisibleSelection selection = coreObject->selection();
680
681     // Only range selections are needed for the purpose of this method
682     if (!selection.isRange())
683         return 0;
684
685     // We don't support multiple selections for now, so there's only
686     // two possibilities
687     // Also, we don't want to do anything if the selection does not
688     // belong to the currently selected object. We have to check since
689     // there's no way to get the selection for a given object, only
690     // the global one (the API is a bit confusing)
691     return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
692 }
693
694 static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
695 {
696     // Default values, unless the contrary is proved
697     *startOffset = *endOffset = 0;
698
699     // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
700     if (selectionNum)
701         return 0;
702
703     // Get the offsets of the selection for the selected object
704     AccessibilityObject* coreObject = core(text);
705     VisibleSelection selection = coreObject->selection();
706     getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
707
708     // Return 0 instead of "", as that's the expected result for
709     // this AtkText method when there's no selection
710     if (*startOffset == *endOffset)
711         return 0;
712
713     return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
714 }
715
716 static gboolean webkitAccessibleTextAddSelection(AtkText*, gint, gint)
717 {
718     notImplemented();
719     return FALSE;
720 }
721
722 static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
723 {
724     // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
725     if (selectionNum)
726         return FALSE;
727
728     AccessibilityObject* coreObject = core(text);
729     if (!coreObject->isAccessibilityRenderObject())
730         return FALSE;
731
732     // Consider -1 and out-of-bound values and correct them to length
733     gint textCount = webkitAccessibleTextGetCharacterCount(text);
734     if (startOffset < 0 || startOffset > textCount)
735         startOffset = textCount;
736     if (endOffset < 0 || endOffset > textCount)
737         endOffset = textCount;
738
739     // We need to adjust the offsets for the list item marker.
740     RenderObject* renderer = coreObject->renderer();
741     if (renderer && renderer->isListItem()) {
742         String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
743         int markerLength = markerText.length();
744         if (startOffset < markerLength || endOffset < markerLength)
745             return FALSE;
746
747         startOffset -= markerLength;
748         endOffset -= markerLength;
749     }
750
751     PlainTextRange textRange(startOffset, endOffset - startOffset);
752     VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
753     if (range.isNull())
754         return FALSE;
755
756     coreObject->setSelectedVisiblePositionRange(range);
757     return TRUE;
758 }
759
760 static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectionNum)
761 {
762     // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
763     if (selectionNum)
764         return FALSE;
765
766     // Do nothing if current selection doesn't belong to the object
767     if (!webkitAccessibleTextGetNSelections(text))
768         return FALSE;
769
770     // Set a new 0-sized selection to the caret position, in order
771     // to simulate selection removal (GAIL style)
772     gint caretOffset = webkitAccessibleTextGetCaretOffset(text);
773     return webkitAccessibleTextSetSelection(text, selectionNum, caretOffset, caretOffset);
774 }
775
776 static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset)
777 {
778     AccessibilityObject* coreObject = core(text);
779
780     if (!coreObject->isAccessibilityRenderObject())
781         return FALSE;
782
783     RenderObject* renderer = coreObject->renderer();
784     if (renderer && renderer->isListItem()) {
785         String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
786         int markerLength = markerText.length();
787         if (offset < markerLength)
788             return FALSE;
789
790         // We need to adjust the offset for list items.
791         offset -= markerLength;
792     }
793
794     PlainTextRange textRange(offset, 0);
795     VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
796     if (range.isNull())
797         return FALSE;
798
799     coreObject->setSelectedVisiblePositionRange(range);
800     return TRUE;
801 }
802
803 void webkitAccessibleTextInterfaceInit(AtkTextIface* iface)
804 {
805     iface->get_text = webkitAccessibleTextGetText;
806     iface->get_text_after_offset = webkitAccessibleTextGetTextAfterOffset;
807     iface->get_text_at_offset = webkitAccessibleTextGetTextAtOffset;
808     iface->get_text_before_offset = webkitAccessibleTextGetTextBeforeOffset;
809     iface->get_character_at_offset = webkitAccessibleTextGetCharacterAtOffset;
810     iface->get_caret_offset = webkitAccessibleTextGetCaretOffset;
811     iface->get_run_attributes = webkitAccessibleTextGetRunAttributes;
812     iface->get_default_attributes = webkitAccessibleTextGetDefaultAttributes;
813     iface->get_character_extents = webkitAccessibleTextGetCharacterExtents;
814     iface->get_range_extents = webkitAccessibleTextGetRangeExtents;
815     iface->get_character_count = webkitAccessibleTextGetCharacterCount;
816     iface->get_offset_at_point = webkitAccessibleTextGetOffsetAtPoint;
817     iface->get_n_selections = webkitAccessibleTextGetNSelections;
818     iface->get_selection = webkitAccessibleTextGetSelection;
819     iface->add_selection = webkitAccessibleTextAddSelection;
820     iface->remove_selection = webkitAccessibleTextRemoveSelection;
821     iface->set_selection = webkitAccessibleTextSetSelection;
822     iface->set_caret_offset = webkitAccessibleTextSetCaretOffset;
823 }