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