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