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