WebKitAccessibleInterfaceText.cpp [plain text]
#include "config.h"
#include "WebKitAccessibleInterfaceText.h"
#include "AccessibilityObject.h"
#include "Document.h"
#include "FrameView.h"
#include <wtf/gobject/GOwnPtr.h>
#include "HostWindow.h"
#include "InlineTextBox.h"
#include "NotImplemented.h"
#include "RenderListItem.h"
#include "RenderListMarker.h"
#include "RenderText.h"
#include "TextEncoding.h"
#include "TextIterator.h"
#include "WebKitAccessibleUtil.h"
#include "WebKitAccessibleWrapperAtk.h"
#include "htmlediting.h"
#include <libgail-util/gail-util.h>
#include <pango/pango.h>
using namespace WebCore;
static AccessibilityObject* core(AtkText* text)
{
if (!WEBKIT_IS_ACCESSIBLE(text))
return 0;
return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text));
}
static gchar* textForRenderer(RenderObject* renderer)
{
GString* resultText = g_string_new(0);
if (!renderer)
return g_string_free(resultText, FALSE);
for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) {
if (object->isBR()) {
g_string_append(resultText, "\n");
continue;
}
RenderText* renderText;
if (object->isText())
renderText = toRenderText(object);
else {
if (object->isReplaced() && !renderer->isListItem())
g_string_append_unichar(resultText, objectReplacementCharacter);
if (object->firstChild())
g_string_append(resultText, textForRenderer(object));
continue;
}
InlineTextBox* box = renderText ? renderText->firstTextBox() : 0;
while (box) {
String text = String(renderText->characters(), renderText->textLength()).replace("\n", " ");
g_string_append(resultText, text.substring(box->start(), box->end() - box->start() + 1).utf8().data());
if (!box->nextOnLineExists() && !(object->nextSibling() && object->nextSibling()->isBR())) {
if (renderText->characters()[box->end()] == '\n')
g_string_erase(resultText, resultText->len - 1, -1);
g_string_append(resultText, "\n");
}
box = box->nextTextBox();
}
}
if (renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
if (renderer->style()->direction() == LTR)
g_string_prepend(resultText, markerText.utf8().data());
else
g_string_append(resultText, markerText.utf8().data());
}
return g_string_free(resultText, FALSE);
}
static gchar* textForObject(AccessibilityObject* coreObject)
{
GString* str = g_string_new(0);
if (coreObject->isTextControl()) {
unsigned textLength = coreObject->textLength();
int lineNumber = 0;
PlainTextRange range = coreObject->doAXRangeForLine(lineNumber);
while (range.length) {
if (range.start + range.length < textLength)
range.length -= 1;
String lineText = coreObject->doAXStringForRange(range);
g_string_append(str, lineText.utf8().data());
g_string_append(str, "\n");
range = coreObject->doAXRangeForLine(++lineNumber);
}
} else if (coreObject->isAccessibilityRenderObject()) {
GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
g_string_append(str, rendererText.get());
}
return g_string_free(str, FALSE);
}
static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset);
static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
{
GailTextUtil* gailTextUtil = gail_text_util_new();
gail_text_util_text_setup(gailTextUtil, webkitAccessibleTextGetText(textObject, 0, -1));
return gailTextUtil;
}
static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
{
AccessibilityObject* coreObject = core(textObject);
Document* document = coreObject->document();
if (!document)
return 0;
HostWindow* hostWindow = document->view()->hostWindow();
if (!hostWindow)
return 0;
PlatformPageClient webView = hostWindow->platformPageClient();
if (!webView)
return 0;
PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(coreObject));
return layout;
}
static int baselinePositionForRenderObject(RenderObject* renderObject)
{
const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
}
static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
{
if (!object->isAccessibilityRenderObject())
return 0;
RenderObject* renderer = object->renderer();
RenderStyle* style = renderer->style();
AtkAttributeSet* result = 0;
GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
if (bgColor.isValid()) {
buffer.set(g_strdup_printf("%i,%i,%i",
bgColor.red(), bgColor.green(), bgColor.blue()));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
}
Color fgColor = style->visitedDependentColor(CSSPropertyColor);
if (fgColor.isValid()) {
buffer.set(g_strdup_printf("%i,%i,%i",
fgColor.red(), fgColor.green(), fgColor.blue()));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
}
int baselinePosition;
bool includeRise = true;
switch (style->verticalAlign()) {
case SUB:
baselinePosition = -1 * baselinePositionForRenderObject(renderer);
break;
case SUPER:
baselinePosition = baselinePositionForRenderObject(renderer);
break;
case BASELINE:
baselinePosition = 0;
break;
default:
includeRise = false;
break;
}
if (includeRise) {
buffer.set(g_strdup_printf("%i", baselinePosition));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
}
if (!style->textIndent().isUndefined()) {
int indentation = valueForLength(style->textIndent(), object->size().width(), renderer->view());
buffer.set(g_strdup_printf("%i", indentation));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
}
String fontFamilyName = style->font().family().family().string();
if (fontFamilyName.left(8) == "-webkit-")
fontFamilyName = fontFamilyName.substring(8);
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
int fontWeight = -1;
switch (style->font().weight()) {
case FontWeight100:
fontWeight = 100;
break;
case FontWeight200:
fontWeight = 200;
break;
case FontWeight300:
fontWeight = 300;
break;
case FontWeight400:
fontWeight = 400;
break;
case FontWeight500:
fontWeight = 500;
break;
case FontWeight600:
fontWeight = 600;
break;
case FontWeight700:
fontWeight = 700;
break;
case FontWeight800:
fontWeight = 800;
break;
case FontWeight900:
fontWeight = 900;
}
if (fontWeight > 0) {
buffer.set(g_strdup_printf("%i", fontWeight));
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
}
switch (style->textAlign()) {
case TAAUTO:
case TASTART:
case TAEND:
break;
case LEFT:
case WEBKIT_LEFT:
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
break;
case RIGHT:
case WEBKIT_RIGHT:
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
break;
case CENTER:
case WEBKIT_CENTER:
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
break;
case JUSTIFY:
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
}
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none");
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false");
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
return result;
}
static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
{
return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
}
static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, AtkAttributeSet* attributeSet2)
{
if (!attributeSet2)
return attributeSet1;
AtkAttributeSet* currentSet = attributeSet1;
AtkAttributeSet* found;
AtkAttributeSet* toDelete = 0;
while (currentSet) {
found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute);
if (found) {
AtkAttributeSet* nextSet = currentSet->next;
toDelete = g_slist_prepend(toDelete, currentSet->data);
attributeSet1 = g_slist_delete_link(attributeSet1, currentSet);
currentSet = nextSet;
} else
currentSet = currentSet->next;
}
atk_attribute_set_free(attributeSet2);
atk_attribute_set_free(toDelete);
return attributeSet1;
}
static guint accessibilityObjectLength(const AccessibilityObject* object)
{
if (!object->isAccessibilityRenderObject())
return 0;
AtkObject* atkObj = ATK_OBJECT(object->wrapper());
if (ATK_IS_TEXT(atkObj)) {
GOwnPtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1));
return g_utf8_strlen(text.get(), -1);
}
RenderObject* renderer = object->renderer();
if (renderer && renderer->isListMarker()) {
RenderListMarker* marker = toRenderListMarker(renderer);
return marker->text().length() + marker->suffix().length();
}
return 0;
}
static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
{
const AccessibilityObject* result;
guint length = accessibilityObjectLength(object);
if (length > offset) {
*startOffset = 0;
*endOffset = length;
result = object;
} else {
*startOffset = -1;
*endOffset = -1;
result = 0;
}
if (!object->firstChild())
return result;
AccessibilityObject* child = object->firstChild();
guint currentOffset = 0;
guint childPosition = 0;
while (child && currentOffset <= offset) {
guint childLength = accessibilityObjectLength(child);
currentOffset = childLength + childPosition;
if (currentOffset > offset) {
gint childStartOffset;
gint childEndOffset;
const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset);
if (childStartOffset >= 0) {
*startOffset = childStartOffset + childPosition;
*endOffset = childEndOffset + childPosition;
result = grandChild;
}
} else {
childPosition += childLength;
child = child->nextSibling();
}
}
return result;
}
static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
{
const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
if (!child) {
*startOffset = -1;
*endOffset = -1;
return 0;
}
AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
return attributeSetDifference(childAttributes, defaultAttributes);
}
static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
{
gchar* textContent = webkitAccessibleTextGetText(text, startOffset, -1);
gint textLength = g_utf8_strlen(textContent, -1);
gint rangeLength = length;
if (rangeLength < 0 || rangeLength > textLength)
rangeLength = textLength;
AccessibilityObject* coreObject = core(text);
IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
switch (coords) {
case ATK_XY_SCREEN:
if (Document* document = coreObject->document())
extents = document->view()->contentsToScreen(extents);
break;
case ATK_XY_WINDOW:
break;
}
return extents;
}
static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
{
if (!coreObject->isAccessibilityRenderObject())
return;
if (!selectionBelongsToObject(coreObject, selection))
return;
ExceptionCode ec = 0;
Position nodeRangeStart;
Position nodeRangeEnd;
Node* node = coreObject->node();
RefPtr<Range> selRange = selection.toNormalizedRange();
Node* firstLeafNode = node->firstDescendant();
if (selRange->isPointInRange(firstLeafNode, 0, ec))
nodeRangeStart = firstPositionInOrBeforeNode(firstLeafNode);
else
nodeRangeStart = selRange->startPosition();
Node* lastLeafNode = node->lastDescendant();
if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec))
nodeRangeEnd = lastPositionInOrAfterNode(lastLeafNode);
else
nodeRangeEnd = selRange->endPosition();
Position parentFirstPosition = firstPositionInOrBeforeNode(node);
RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
startOffset = TextIterator::rangeLength(rangeInParent.get(), true);
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
startOffset += markerText.length();
}
RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true);
}
static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint endOffset)
{
AccessibilityObject* coreObject = core(text);
int end = endOffset;
if (endOffset == -1) {
end = coreObject->stringValue().length();
if (!end)
end = coreObject->textUnderElement().length();
}
String ret;
if (coreObject->isTextControl())
ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
else {
ret = coreObject->stringValue();
if (!ret)
ret = coreObject->textUnderElement();
}
if (!ret.length()) {
ret = String(textForObject(coreObject));
if (!end)
end = ret.length();
}
if (coreObject->roleValue() == ListItemRole) {
RenderObject* objRenderer = coreObject->renderer();
if (objRenderer && objRenderer->isListItem()) {
String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
if (endOffset == -1)
end += markerText.length();
}
}
ret = ret.substring(startOffset, end - startOffset);
return g_strdup(ret.utf8().data());
}
static gchar* webkitAccessibleTextGetTextAfterOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
{
return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset);
}
static gchar* webkitAccessibleTextGetTextAtOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
{
return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset);
}
static gchar* webkitAccessibleTextGetTextBeforeOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
{
return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset);
}
static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText*, gint)
{
notImplemented();
return 0;
}
static gint webkitAccessibleTextGetCaretOffset(AtkText* text)
{
AccessibilityObject* coreObject = core(text);
if (!coreObject->isAccessibilityRenderObject())
return 0;
if (coreObject->accessibilityIsIgnored())
coreObject = coreObject->parentObjectUnignored();
if (!coreObject)
return 0;
int offset;
if (!objectFocusedAndCaretOffsetUnignored(coreObject, offset))
return 0;
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
offset += markerText.length();
}
return offset;
}
static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
{
AccessibilityObject* coreObject = core(text);
AtkAttributeSet* result;
if (!coreObject) {
*startOffset = 0;
*endOffset = atk_text_get_character_count(text);
return 0;
}
if (offset == -1)
offset = atk_text_get_caret_offset(text);
result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
if (*startOffset < 0) {
*startOffset = offset;
*endOffset = offset;
}
return result;
}
static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text)
{
AccessibilityObject* coreObject = core(text);
if (!coreObject || !coreObject->isAccessibilityRenderObject())
return 0;
return getAttributeSetForAccessibilityObject(coreObject);
}
static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
{
IntRect extents = textExtents(text, offset, 1, coords);
*x = extents.x();
*y = extents.y();
*width = extents.width();
*height = extents.height();
}
static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
{
IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
rect->x = extents.x();
rect->y = extents.y();
rect->width = extents.width();
rect->height = extents.height();
}
static gint webkitAccessibleTextGetCharacterCount(AtkText* text)
{
return accessibilityObjectLength(core(text));
}
static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, AtkCoordType coords)
{
IntPoint pos(x, y);
PlainTextRange range = core(text)->doAXRangeForPosition(pos);
return range.start;
}
static gint webkitAccessibleTextGetNSelections(AtkText* text)
{
AccessibilityObject* coreObject = core(text);
VisibleSelection selection = coreObject->selection();
if (!selection.isRange())
return 0;
return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
}
static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
{
*startOffset = *endOffset = 0;
if (selectionNum)
return 0;
AccessibilityObject* coreObject = core(text);
VisibleSelection selection = coreObject->selection();
getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
if (*startOffset == *endOffset)
return 0;
return webkitAccessibleTextGetText(text, *startOffset, *endOffset);
}
static gboolean webkitAccessibleTextAddSelection(AtkText*, gint, gint)
{
notImplemented();
return FALSE;
}
static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
{
if (selectionNum)
return FALSE;
AccessibilityObject* coreObject = core(text);
if (!coreObject->isAccessibilityRenderObject())
return FALSE;
gint textCount = webkitAccessibleTextGetCharacterCount(text);
if (startOffset < 0 || startOffset > textCount)
startOffset = textCount;
if (endOffset < 0 || endOffset > textCount)
endOffset = textCount;
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
int markerLength = markerText.length();
if (startOffset < markerLength || endOffset < markerLength)
return FALSE;
startOffset -= markerLength;
endOffset -= markerLength;
}
PlainTextRange textRange(startOffset, endOffset - startOffset);
VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
if (range.isNull())
return FALSE;
coreObject->setSelectedVisiblePositionRange(range);
return TRUE;
}
static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectionNum)
{
if (selectionNum)
return FALSE;
if (!webkitAccessibleTextGetNSelections(text))
return FALSE;
gint caretOffset = webkitAccessibleTextGetCaretOffset(text);
return webkitAccessibleTextSetSelection(text, selectionNum, caretOffset, caretOffset);
}
static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset)
{
AccessibilityObject* coreObject = core(text);
if (!coreObject->isAccessibilityRenderObject())
return FALSE;
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
int markerLength = markerText.length();
if (offset < markerLength)
return FALSE;
offset -= markerLength;
}
PlainTextRange textRange(offset, 0);
VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
if (range.isNull())
return FALSE;
coreObject->setSelectedVisiblePositionRange(range);
return TRUE;
}
void webkitAccessibleTextInterfaceInit(AtkTextIface* iface)
{
iface->get_text = webkitAccessibleTextGetText;
iface->get_text_after_offset = webkitAccessibleTextGetTextAfterOffset;
iface->get_text_at_offset = webkitAccessibleTextGetTextAtOffset;
iface->get_text_before_offset = webkitAccessibleTextGetTextBeforeOffset;
iface->get_character_at_offset = webkitAccessibleTextGetCharacterAtOffset;
iface->get_caret_offset = webkitAccessibleTextGetCaretOffset;
iface->get_run_attributes = webkitAccessibleTextGetRunAttributes;
iface->get_default_attributes = webkitAccessibleTextGetDefaultAttributes;
iface->get_character_extents = webkitAccessibleTextGetCharacterExtents;
iface->get_range_extents = webkitAccessibleTextGetRangeExtents;
iface->get_character_count = webkitAccessibleTextGetCharacterCount;
iface->get_offset_at_point = webkitAccessibleTextGetOffsetAtPoint;
iface->get_n_selections = webkitAccessibleTextGetNSelections;
iface->get_selection = webkitAccessibleTextGetSelection;
iface->add_selection = webkitAccessibleTextAddSelection;
iface->remove_selection = webkitAccessibleTextRemoveSelection;
iface->set_selection = webkitAccessibleTextSetSelection;
iface->set_caret_offset = webkitAccessibleTextSetCaretOffset;
}