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.
7 * Portions from Mozilla a11y, copyright as follows:
9 * The Original Code is mozilla.org code.
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.
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.
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.
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.
33 #include "WebKitAccessibleInterfaceText.h"
35 #if HAVE(ACCESSIBILITY)
37 #include "AccessibilityObject.h"
40 #include "FrameView.h"
41 #include "HostWindow.h"
42 #include "InlineTextBox.h"
43 #include "NotImplemented.h"
44 #include "RenderListItem.h"
45 #include "RenderListMarker.h"
46 #include "RenderText.h"
47 #include "TextEncoding.h"
48 #include "TextIterator.h"
49 #include "VisibleUnits.h"
50 #include "WebKitAccessibleUtil.h"
51 #include "WebKitAccessibleWrapperAtk.h"
52 #include "htmlediting.h"
53 #include <wtf/NotFound.h>
54 #include <wtf/gobject/GOwnPtr.h>
55 #include <wtf/text/CString.h>
58 #include <libgail-util/gail-util.h>
59 #include <pango/pango.h>
62 using namespace WebCore;
64 static AccessibilityObject* core(AtkText* text)
66 if (!WEBKIT_IS_ACCESSIBLE(text))
69 return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text));
72 static gchar* textForRenderer(RenderObject* renderer)
74 GString* resultText = g_string_new(0);
77 return g_string_free(resultText, FALSE);
79 // For RenderBlocks, piece together the text from the RenderText objects they contain.
80 for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) {
82 g_string_append(resultText, "\n");
86 RenderText* renderText;
88 renderText = toRenderText(object);
90 // List item's markers will be treated in an special way
91 // later on this function, so ignore them here.
92 if (object->isReplaced() && !object->isListMarker())
93 g_string_append_unichar(resultText, objectReplacementCharacter);
95 // We need to check children, if any, to consider when
96 // current object is not a text object but some of its
97 // children are, in order not to miss those portions of
98 // text by not properly handling those situations
99 if (object->firstChild()) {
100 GOwnPtr<char> objectText(textForRenderer(object));
101 g_string_append(resultText, objectText.get());
106 InlineTextBox* box = renderText ? renderText->firstTextBox() : 0;
108 // WebCore introduces line breaks in the text that do not reflect
109 // the layout you see on the screen, replace them with spaces.
110 String text = String(renderText->characters(), renderText->textLength()).replace("\n", " ");
111 g_string_append(resultText, text.substring(box->start(), box->end() - box->start() + 1).utf8().data());
113 // Newline chars in the source result in separate text boxes, so check
114 // before adding a newline in the layout. See bug 25415 comment #78.
115 // If the next sibling is a BR, we'll add the newline when we examine that child.
116 if (!box->nextOnLineExists() && !(object->nextSibling() && object->nextSibling()->isBR())) {
117 // If there was a '\n' in the last position of the
118 // current text box, it would have been converted to a
119 // space in String::replace(), so remove it first.
120 if (renderText->characters()[box->end()] == '\n')
121 g_string_erase(resultText, resultText->len - 1, -1);
123 g_string_append(resultText, "\n");
125 box = box->nextTextBox();
129 // Insert the text of the marker for list item in the right place, if present
130 if (renderer->isListItem()) {
131 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
132 if (renderer->style()->direction() == LTR)
133 g_string_prepend(resultText, markerText.utf8().data());
135 g_string_append(resultText, markerText.utf8().data());
138 return g_string_free(resultText, FALSE);
141 static gchar* textForObject(const AccessibilityObject* coreObject)
143 GString* str = g_string_new(0);
145 // For text controls, we can get the text line by line.
146 if (coreObject->isTextControl()) {
147 unsigned textLength = coreObject->textLength();
149 PlainTextRange range = coreObject->doAXRangeForLine(lineNumber);
150 while (range.length) {
151 // When a line of text wraps in a text area, the final space is removed.
152 if (range.start + range.length < textLength)
154 String lineText = coreObject->doAXStringForRange(range);
155 g_string_append(str, lineText.utf8().data());
156 g_string_append(str, "\n");
157 range = coreObject->doAXRangeForLine(++lineNumber);
159 } else if (coreObject->isAccessibilityRenderObject()) {
160 GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
161 g_string_append(str, rendererText.get());
164 return g_string_free(str, FALSE);
167 static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset);
170 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
172 GailTextUtil* gailTextUtil = gail_text_util_new();
173 GOwnPtr<char> text(webkitAccessibleTextGetText(textObject, 0, -1));
174 gail_text_util_text_setup(gailTextUtil, text.get());
178 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
180 AccessibilityObject* coreObject = core(textObject);
182 Document* document = coreObject->document();
186 HostWindow* hostWindow = document->view()->hostWindow();
189 PlatformPageClient webView = hostWindow->platformPageClient();
193 // Create a string with the layout as it appears on the screen
194 GOwnPtr<char> objectText(textForObject(coreObject));
195 PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), objectText.get());
200 static int baselinePositionForRenderObject(RenderObject* renderObject)
202 // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was
203 // removed in r70072. The implementation looks incorrect though, because this is not the
204 // baseline of the underlying RenderObject, but of the AccessibilityRenderObject.
205 const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
206 return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
209 static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
211 if (!object->isAccessibilityRenderObject())
214 RenderObject* renderer = object->renderer();
215 RenderStyle* style = renderer->style();
217 AtkAttributeSet* result = 0;
218 GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
219 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
221 Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
222 if (bgColor.isValid()) {
223 buffer.set(g_strdup_printf("%i,%i,%i", bgColor.red(), bgColor.green(), bgColor.blue()));
224 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
227 Color fgColor = style->visitedDependentColor(CSSPropertyColor);
228 if (fgColor.isValid()) {
229 buffer.set(g_strdup_printf("%i,%i,%i", fgColor.red(), fgColor.green(), fgColor.blue()));
230 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
233 int baselinePosition;
234 bool includeRise = true;
235 switch (style->verticalAlign()) {
237 baselinePosition = -1 * baselinePositionForRenderObject(renderer);
240 baselinePosition = baselinePositionForRenderObject(renderer);
243 baselinePosition = 0;
251 buffer.set(g_strdup_printf("%i", baselinePosition));
252 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
255 if (!style->textIndent().isUndefined()) {
256 int indentation = valueForLength(style->textIndent(), object->size().width(), &renderer->view());
257 buffer.set(g_strdup_printf("%i", indentation));
258 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
261 String fontFamilyName = style->font().firstFamily();
262 if (fontFamilyName.left(8) == "-webkit-")
263 fontFamilyName = fontFamilyName.substring(8);
265 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
268 switch (style->font().weight()) {
296 if (fontWeight > 0) {
297 buffer.set(g_strdup_printf("%i", fontWeight));
298 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
301 switch (style->textAlign()) {
307 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
311 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
315 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
318 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
321 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & TextDecorationUnderline) ? "single" : "none");
323 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
325 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & TextDecorationLineThrough) ? "true" : "false");
327 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
329 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
331 String language = object->language();
332 if (!language.isEmpty())
333 result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE), language.utf8().data());
338 static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
340 return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
343 // Returns an AtkAttributeSet with the elements of attributeSet1 which
344 // are either not present or different in attributeSet2. Neither
345 // attributeSet1 nor attributeSet2 should be used after calling this.
346 static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, AtkAttributeSet* attributeSet2)
349 return attributeSet1;
351 AtkAttributeSet* currentSet = attributeSet1;
352 AtkAttributeSet* found;
353 AtkAttributeSet* toDelete = 0;
356 found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute);
358 AtkAttributeSet* nextSet = currentSet->next;
359 toDelete = g_slist_prepend(toDelete, currentSet->data);
360 attributeSet1 = g_slist_delete_link(attributeSet1, currentSet);
361 currentSet = nextSet;
363 currentSet = currentSet->next;
366 atk_attribute_set_free(attributeSet2);
367 atk_attribute_set_free(toDelete);
368 return attributeSet1;
371 static guint accessibilityObjectLength(const AccessibilityObject* object)
373 // Non render objects are not taken into account
374 if (!object->isAccessibilityRenderObject())
377 // For those objects implementing the AtkText interface we use the
378 // well known API to always get the text in a consistent way
379 AtkObject* atkObj = ATK_OBJECT(object->wrapper());
380 if (ATK_IS_TEXT(atkObj)) {
381 GOwnPtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1));
382 return g_utf8_strlen(text.get(), -1);
385 // Even if we don't expose list markers to Assistive
386 // Technologies, we need to have a way to measure their length
387 // for those cases when it's needed to take it into account
388 // separately (as in getAccessibilityObjectForOffset)
389 RenderObject* renderer = object->renderer();
390 if (renderer && renderer->isListMarker()) {
391 RenderListMarker* marker = toRenderListMarker(renderer);
392 return marker->text().length() + marker->suffix().length();
398 static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
400 const AccessibilityObject* result;
401 guint length = accessibilityObjectLength(object);
402 if (length > offset) {
412 if (!object->firstChild())
415 AccessibilityObject* child = object->firstChild();
416 guint currentOffset = 0;
417 guint childPosition = 0;
418 while (child && currentOffset <= offset) {
419 guint childLength = accessibilityObjectLength(child);
420 currentOffset = childLength + childPosition;
421 if (currentOffset > offset) {
422 gint childStartOffset;
424 const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset);
425 if (childStartOffset >= 0) {
426 *startOffset = childStartOffset + childPosition;
427 *endOffset = childEndOffset + childPosition;
431 childPosition += childLength;
432 child = child->nextSibling();
438 static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
440 const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
447 AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
448 AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
450 return attributeSetDifference(childAttributes, defaultAttributes);
453 static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
455 GOwnPtr<char> textContent(webkitAccessibleTextGetText(text, startOffset, -1));
456 gint textLength = g_utf8_strlen(textContent.get(), -1);
458 // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps.
459 gint rangeLength = length;
460 if (rangeLength < 0 || rangeLength > textLength)
461 rangeLength = textLength;
462 AccessibilityObject* coreObject = core(text);
464 IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
467 if (Document* document = coreObject->document())
468 extents = document->view()->contentsToScreen(extents);
478 static int offsetAdjustmentForListItem(const AccessibilityObject* object)
480 // We need to adjust the offsets for the list item marker in
481 // Left-To-Right text, since we expose it together with the text.
482 RenderObject* renderer = object->renderer();
483 if (renderer && renderer->isListItem() && renderer->style()->direction() == LTR)
484 return toRenderListItem(renderer)->markerTextWithSuffix().length();
489 static int webCoreOffsetToAtkOffset(const AccessibilityObject* object, int offset)
491 if (!object->isListItem())
494 return offset + offsetAdjustmentForListItem(object);
497 static int atkOffsetToWebCoreOffset(AtkText* text, int offset)
499 AccessibilityObject* coreObject = core(text);
500 if (!coreObject || !coreObject->isListItem())
503 return offset - offsetAdjustmentForListItem(coreObject);
506 static Node* getNodeForAccessibilityObject(AccessibilityObject* coreObject)
508 if (!coreObject->isNativeTextControl())
509 return coreObject->node();
511 // For text controls, we get the first visible position on it (which will
512 // belong to its inner element, unreachable from the DOM) and return its
513 // parent node, so we have a "bounding node" for the accessibility object.
514 VisiblePosition positionInTextControlInnerElement = coreObject->visiblePositionForIndex(0, true);
515 Node* innerMostNode = positionInTextControlInnerElement.deepEquivalent().anchorNode();
519 return innerMostNode->parentNode();
522 static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
524 // Default values, unless the contrary is proved.
528 Node* node = getNodeForAccessibilityObject(coreObject);
532 if (selection.isNone())
535 // We need to limit our search to positions that fall inside the domain of the current object.
536 Position firstValidPosition = firstPositionInOrBeforeNode(node->firstDescendant());
537 Position lastValidPosition = lastPositionInOrAfterNode(node->lastDescendant());
539 // Early return with proper values if the selection falls entirely out of the object.
540 if (!selectionBelongsToObject(coreObject, selection)) {
541 startOffset = comparePositions(selection.start(), firstValidPosition) < 0 ? 0 : accessibilityObjectLength(coreObject);
542 endOffset = startOffset;
546 // Find the proper range for the selection that falls inside the object.
547 Position nodeRangeStart = selection.start();
548 if (comparePositions(nodeRangeStart, firstValidPosition) < 0)
549 nodeRangeStart = firstValidPosition;
551 Position nodeRangeEnd = selection.end();
552 if (comparePositions(nodeRangeEnd, lastValidPosition) > 0)
553 nodeRangeEnd = lastValidPosition;
555 // Calculate position of the selected range inside the object.
556 Position parentFirstPosition = firstPositionInOrBeforeNode(node);
557 RefPtr<Range> rangeInParent = Range::create(&node->document(), parentFirstPosition, nodeRangeStart);
559 // Set values for start offsets and calculate initial range length.
560 // These values might be adjusted later to cover special cases.
561 startOffset = webCoreOffsetToAtkOffset(coreObject, TextIterator::rangeLength(rangeInParent.get(), true));
562 RefPtr<Range> nodeRange = Range::create(&node->document(), nodeRangeStart, nodeRangeEnd);
563 int rangeLength = TextIterator::rangeLength(nodeRange.get(), true);
565 // Special cases that are only relevant when working with *_END boundaries.
566 if (selection.affinity() == UPSTREAM) {
567 VisiblePosition visibleStart(nodeRangeStart, UPSTREAM);
568 VisiblePosition visibleEnd(nodeRangeEnd, UPSTREAM);
570 // We need to adjust offsets when finding wrapped lines so the position at the end
571 // of the line is properly taking into account when calculating the offsets.
572 if (isEndOfLine(visibleStart) && !lineBreakExistsAtVisiblePosition(visibleStart)) {
573 if (isStartOfLine(visibleStart.next()))
576 if (!isEndOfBlock(visibleStart))
577 startOffset = std::max(startOffset - 1, 0);
580 if (isEndOfLine(visibleEnd) && !lineBreakExistsAtVisiblePosition(visibleEnd) && !isEndOfBlock(visibleEnd))
584 endOffset = std::min(startOffset + rangeLength, static_cast<int>(accessibilityObjectLength(coreObject)));
587 static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint endOffset)
589 AccessibilityObject* coreObject = core(text);
592 if (endOffset == -1) {
593 end = coreObject->stringValue().length();
595 end = coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).length();
599 if (coreObject->isTextControl())
600 ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
602 ret = coreObject->stringValue();
604 ret = coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren));
608 // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
609 // In such instances, there may also be embedded objects. The object replacement character
610 // is something ATs want included and we have to account for the fact that it is multibyte.
611 GOwnPtr<char> objectText(textForObject(coreObject));
612 ret = String::fromUTF8(objectText.get());
617 // Prefix a item number/bullet if needed
618 if (coreObject->roleValue() == ListItemRole) {
619 RenderObject* objRenderer = coreObject->renderer();
620 if (objRenderer && objRenderer->isListItem()) {
621 String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
622 ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
624 end += markerText.length();
628 ret = ret.substring(startOffset, end - startOffset);
629 return g_strdup(ret.utf8().data());
632 enum GetTextRelativePosition {
634 GetTextPositionBefore,
638 // Convenience function to be used in early returns.
639 static char* emptyTextSelectionAtOffset(int offset, int* startOffset, int* endOffset)
641 *startOffset = offset;
646 static char* webkitAccessibleTextGetChar(AtkText* text, int offset, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
648 int actualOffset = offset;
649 if (textPosition == GetTextPositionBefore)
651 else if (textPosition == GetTextPositionAfter)
654 GOwnPtr<char> textData(webkitAccessibleTextGetText(text, 0, -1));
655 int textLength = g_utf8_strlen(textData.get(), -1);
657 *startOffset = std::max(0, actualOffset);
658 *startOffset = std::min(*startOffset, textLength);
660 *endOffset = std::max(0, actualOffset + 1);
661 *endOffset = std::min(*endOffset, textLength);
663 if (*startOffset == *endOffset)
666 return g_utf8_substring(textData.get(), *startOffset, *endOffset);
669 static VisiblePosition nextWordStartPosition(const VisiblePosition &position)
671 VisiblePosition positionAfterCurrentWord = nextWordPosition(position);
673 // In order to skip spaces when moving right, we advance one word further
674 // and then move one word back. This will put us at the beginning of the
676 VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
678 if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord)
679 positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
681 bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(position));
682 if (movingBackwardsMovedPositionToStartOfCurrentWord)
683 positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
685 return positionAfterCurrentWord;
688 static VisiblePosition previousWordEndPosition(const VisiblePosition &position)
690 // We move forward and then backward to position ourselves at the beginning
691 // of the current word for this boundary, making the most of the semantics
692 // of previousWordPosition() and nextWordPosition().
693 VisiblePosition positionAtStartOfCurrentWord = previousWordPosition(nextWordPosition(position));
694 VisiblePosition positionAtPreviousWord = previousWordPosition(position);
696 // Need to consider special cases (punctuation) when we are in the last word of a sentence.
697 if (isStartOfWord(position) && positionAtPreviousWord != position && positionAtPreviousWord == positionAtStartOfCurrentWord)
698 return nextWordPosition(positionAtStartOfCurrentWord);
700 // In order to skip spaces when moving left, we advance one word backwards
701 // and then move one word forward. This will put us at the beginning of
702 // the word following.
703 VisiblePosition positionBeforeSpacingAndPreceedingWord = previousWordPosition(positionAtStartOfCurrentWord);
705 if (positionBeforeSpacingAndPreceedingWord != positionAtStartOfCurrentWord)
706 positionAtStartOfCurrentWord = nextWordPosition(positionBeforeSpacingAndPreceedingWord);
708 bool movingForwardMovedPositionToEndOfCurrentWord = nextWordPosition(positionAtStartOfCurrentWord) == previousWordPosition(nextWordPosition(position));
709 if (movingForwardMovedPositionToEndOfCurrentWord)
710 positionAtStartOfCurrentWord = positionBeforeSpacingAndPreceedingWord;
712 return positionAtStartOfCurrentWord;
715 static VisibleSelection wordAtPositionForAtkBoundary(const AccessibilityObject* /*coreObject*/, const VisiblePosition& position, AtkTextBoundary boundaryType)
717 VisiblePosition startPosition;
718 VisiblePosition endPosition;
720 switch (boundaryType) {
721 case ATK_TEXT_BOUNDARY_WORD_START:
722 // isStartOfWord() returns true both when at the beginning of a "real" word
723 // as when at the beginning of a whitespace range between two "real" words,
724 // since that whitespace is considered a "word" as well. And in case we are
725 // already at the beginning of a "real" word we do not need to look backwards.
726 if (isStartOfWord(position) && isWhitespace(position.characterBefore()))
727 startPosition = position;
729 startPosition = previousWordPosition(position);
730 endPosition = nextWordStartPosition(startPosition);
732 // We need to make sure that we look for the word in the current line when
733 // we are at the beginning of a new line, and not look into the previous one
734 // at all, which might happen when lines belong to different nodes.
735 if (isStartOfLine(position) && isStartOfLine(endPosition)) {
736 startPosition = endPosition;
737 endPosition = nextWordStartPosition(startPosition);
741 case ATK_TEXT_BOUNDARY_WORD_END:
742 startPosition = previousWordEndPosition(position);
743 endPosition = nextWordPosition(startPosition);
747 ASSERT_NOT_REACHED();
750 VisibleSelection selectedWord(startPosition, endPosition);
752 // We mark the selection as 'upstream' so we can use that information later,
753 // when finding the actual offsets in getSelectionOffsetsForObject().
754 if (boundaryType == ATK_TEXT_BOUNDARY_WORD_END)
755 selectedWord.setAffinity(UPSTREAM);
760 static int numberOfReplacedElementsBeforeOffset(AtkText* text, unsigned offset)
762 GOwnPtr<char> textForObject(webkitAccessibleTextGetText(text, 0, offset));
763 String textBeforeOffset = String::fromUTF8(textForObject.get());
766 size_t index = textBeforeOffset.find(objectReplacementCharacter, 0);
767 while (index < offset && index != WTF::notFound) {
768 index = textBeforeOffset.find(objectReplacementCharacter, index + 1);
774 static char* webkitAccessibleTextGetWordForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset)
776 AccessibilityObject* coreObject = core(text);
777 Document* document = coreObject->document();
779 return emptyTextSelectionAtOffset(0, startOffset, endOffset);
781 Node* node = getNodeForAccessibilityObject(coreObject);
783 return emptyTextSelectionAtOffset(0, startOffset, endOffset);
785 int actualOffset = atkOffsetToWebCoreOffset(text, offset);
787 // Besides of the usual conversion from ATK offsets to WebCore offsets,
788 // we need to consider the potential embedded objects that might have been
789 // inserted in the text exposed through AtkText when calculating the offset.
790 actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset);
792 VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset);
793 VisibleSelection currentWord = wordAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType);
795 // Take into account other relative positions, if needed, by
796 // calculating the new position that we would need to consider.
797 VisiblePosition newPosition = caretPosition;
798 switch (textPosition) {
799 case GetTextPositionAt:
802 case GetTextPositionBefore:
803 // Early return if asking for the previous word while already at the beginning.
804 if (isFirstVisiblePositionInNode(currentWord.visibleStart(), node))
805 return emptyTextSelectionAtOffset(0, startOffset, endOffset);
807 if (isStartOfLine(currentWord.end()))
808 newPosition = currentWord.visibleStart().previous();
810 newPosition = startOfWord(currentWord.start(), LeftWordIfOnBoundary);
813 case GetTextPositionAfter:
814 // Early return if asking for the following word while already at the end.
815 if (isLastVisiblePositionInNode(currentWord.visibleEnd(), node))
816 return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset);
818 if (isEndOfLine(currentWord.end()))
819 newPosition = currentWord.visibleEnd().next();
821 newPosition = endOfWord(currentWord.end(), RightWordIfOnBoundary);
825 ASSERT_NOT_REACHED();
828 // Determine the relevant word we are actually interested in
829 // and calculate the ATK offsets for it, then return everything.
830 VisibleSelection selectedWord = newPosition != caretPosition ? wordAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentWord;
831 getSelectionOffsetsForObject(coreObject, selectedWord, *startOffset, *endOffset);
832 return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
835 static gchar* webkitAccessibleTextGetTextForOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, gint* startOffset, gint* endOffset)
837 AccessibilityObject* coreObject = core(text);
838 if (!coreObject || !coreObject->isAccessibilityRenderObject())
839 return emptyTextSelectionAtOffset(0, startOffset, endOffset);
841 if (boundaryType == ATK_TEXT_BOUNDARY_CHAR)
842 return webkitAccessibleTextGetChar(text, offset, textPosition, startOffset, endOffset);
844 if (boundaryType == ATK_TEXT_BOUNDARY_WORD_START || boundaryType == ATK_TEXT_BOUNDARY_WORD_END)
845 return webkitAccessibleTextGetWordForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset);
848 // FIXME: Get rid of the code below once every single part above
849 // has been properly implemented without using Pango/Cairo.
850 GailOffsetType offsetType = GAIL_AT_OFFSET;
851 switch (textPosition) {
852 case GetTextPositionBefore:
853 offsetType = GAIL_BEFORE_OFFSET;
856 case GetTextPositionAt:
859 case GetTextPositionAfter:
860 offsetType = GAIL_AFTER_OFFSET;
864 ASSERT_NOT_REACHED();
867 // Make sure we always return valid valid values for offsets.
871 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), offsetType, boundaryType, offset, startOffset, endOffset);
878 static gchar* webkitAccessibleTextGetTextAfterOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
880 return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAfter, startOffset, endOffset);
883 static gchar* webkitAccessibleTextGetTextAtOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
885 return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAt, startOffset, endOffset);
888 static gchar* webkitAccessibleTextGetTextBeforeOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
890 return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionBefore, startOffset, endOffset);
893 static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText*, gint)
899 static gint webkitAccessibleTextGetCaretOffset(AtkText* text)
901 // coreObject is the unignored object whose offset the caller is requesting.
902 // focusedObject is the object with the caret. It is likely ignored -- unless it's a link.
903 AccessibilityObject* coreObject = core(text);
904 if (!coreObject->isAccessibilityRenderObject())
907 // We need to make sure we pass a valid object as reference.
908 if (coreObject->accessibilityIsIgnored())
909 coreObject = coreObject->parentObjectUnignored();
914 if (!objectFocusedAndCaretOffsetUnignored(coreObject, offset))
917 return webCoreOffsetToAtkOffset(coreObject, offset);
920 static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
922 AccessibilityObject* coreObject = core(text);
923 AtkAttributeSet* result;
927 *endOffset = atk_text_get_character_count(text);
932 offset = atk_text_get_caret_offset(text);
934 result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
936 if (*startOffset < 0) {
937 *startOffset = offset;
944 static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text)
946 AccessibilityObject* coreObject = core(text);
947 if (!coreObject || !coreObject->isAccessibilityRenderObject())
950 return getAttributeSetForAccessibilityObject(coreObject);
953 static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
955 IntRect extents = textExtents(text, offset, 1, coords);
958 *width = extents.width();
959 *height = extents.height();
962 static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
964 IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
965 rect->x = extents.x();
966 rect->y = extents.y();
967 rect->width = extents.width();
968 rect->height = extents.height();
971 static gint webkitAccessibleTextGetCharacterCount(AtkText* text)
973 return accessibilityObjectLength(core(text));
976 static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, AtkCoordType)
978 // FIXME: Use the AtkCoordType
979 // TODO: Is it correct to ignore range.length?
981 PlainTextRange range = core(text)->doAXRangeForPosition(pos);
985 static gint webkitAccessibleTextGetNSelections(AtkText* text)
987 AccessibilityObject* coreObject = core(text);
988 VisibleSelection selection = coreObject->selection();
990 // Only range selections are needed for the purpose of this method
991 if (!selection.isRange())
994 // We don't support multiple selections for now, so there's only
996 // Also, we don't want to do anything if the selection does not
997 // belong to the currently selected object. We have to check since
998 // there's no way to get the selection for a given object, only
999 // the global one (the API is a bit confusing)
1000 return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
1003 static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
1005 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1009 // Get the offsets of the selection for the selected object
1010 AccessibilityObject* coreObject = core(text);
1011 VisibleSelection selection = coreObject->selection();
1012 getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
1014 // Return 0 instead of "", as that's the expected result for
1015 // this AtkText method when there's no selection
1016 if (*startOffset == *endOffset)
1019 return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
1022 static gboolean webkitAccessibleTextAddSelection(AtkText*, gint, gint)
1028 static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
1030 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1034 AccessibilityObject* coreObject = core(text);
1035 if (!coreObject->isAccessibilityRenderObject())
1038 // Consider -1 and out-of-bound values and correct them to length
1039 gint textCount = webkitAccessibleTextGetCharacterCount(text);
1040 if (startOffset < 0 || startOffset > textCount)
1041 startOffset = textCount;
1042 if (endOffset < 0 || endOffset > textCount)
1043 endOffset = textCount;
1045 // We need to adjust the offsets for the list item marker.
1046 int offsetAdjustment = offsetAdjustmentForListItem(coreObject);
1047 if (offsetAdjustment) {
1048 if (startOffset < offsetAdjustment || endOffset < offsetAdjustment)
1051 startOffset = atkOffsetToWebCoreOffset(text, startOffset);
1052 endOffset = atkOffsetToWebCoreOffset(text, endOffset);
1055 PlainTextRange textRange(startOffset, endOffset - startOffset);
1056 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1060 coreObject->setSelectedVisiblePositionRange(range);
1064 static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectionNum)
1066 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1070 // Do nothing if current selection doesn't belong to the object
1071 if (!webkitAccessibleTextGetNSelections(text))
1074 // Set a new 0-sized selection to the caret position, in order
1075 // to simulate selection removal (GAIL style)
1076 gint caretOffset = webkitAccessibleTextGetCaretOffset(text);
1077 return webkitAccessibleTextSetSelection(text, selectionNum, caretOffset, caretOffset);
1080 static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset)
1082 AccessibilityObject* coreObject = core(text);
1084 if (!coreObject->isAccessibilityRenderObject())
1087 // We need to adjust the offsets for the list item marker.
1088 int offsetAdjustment = offsetAdjustmentForListItem(coreObject);
1089 if (offsetAdjustment) {
1090 if (offset < offsetAdjustment)
1093 offset = atkOffsetToWebCoreOffset(text, offset);
1096 PlainTextRange textRange(offset, 0);
1097 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1101 coreObject->setSelectedVisiblePositionRange(range);
1105 void webkitAccessibleTextInterfaceInit(AtkTextIface* iface)
1107 iface->get_text = webkitAccessibleTextGetText;
1108 iface->get_text_after_offset = webkitAccessibleTextGetTextAfterOffset;
1109 iface->get_text_at_offset = webkitAccessibleTextGetTextAtOffset;
1110 iface->get_text_before_offset = webkitAccessibleTextGetTextBeforeOffset;
1111 iface->get_character_at_offset = webkitAccessibleTextGetCharacterAtOffset;
1112 iface->get_caret_offset = webkitAccessibleTextGetCaretOffset;
1113 iface->get_run_attributes = webkitAccessibleTextGetRunAttributes;
1114 iface->get_default_attributes = webkitAccessibleTextGetDefaultAttributes;
1115 iface->get_character_extents = webkitAccessibleTextGetCharacterExtents;
1116 iface->get_range_extents = webkitAccessibleTextGetRangeExtents;
1117 iface->get_character_count = webkitAccessibleTextGetCharacterCount;
1118 iface->get_offset_at_point = webkitAccessibleTextGetOffsetAtPoint;
1119 iface->get_n_selections = webkitAccessibleTextGetNSelections;
1120 iface->get_selection = webkitAccessibleTextGetSelection;
1121 iface->add_selection = webkitAccessibleTextAddSelection;
1122 iface->remove_selection = webkitAccessibleTextRemoveSelection;
1123 iface->set_selection = webkitAccessibleTextSetSelection;
1124 iface->set_caret_offset = webkitAccessibleTextSetCaretOffset;