diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/accessibility/gtk | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/accessibility/gtk')
6 files changed, 3313 insertions, 0 deletions
diff --git a/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp b/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp new file mode 100644 index 0000000..c341a2d --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AXObjectCache.h" + +#include "AccessibilityObject.h" +#include "AccessibilityObjectWrapperAtk.h" +#include "AccessibilityRenderObject.h" +#include "GOwnPtr.h" +#include "Range.h" +#include "SelectElement.h" +#include "TextIterator.h" + +namespace WebCore { + +void AXObjectCache::detachWrapper(AccessibilityObject* obj) +{ + webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper())); +} + +void AXObjectCache::attachWrapper(AccessibilityObject* obj) +{ + AtkObject* atkObj = ATK_OBJECT(webkit_accessible_new(obj)); + obj->setWrapper(atkObj); + g_object_unref(atkObj); +} + +static void notifyChildrenSelectionChange(AccessibilityObject* object) +{ + // This static variable is needed to keep track of the old focused + // object as per previous calls to this function, in order to + // properly decide whether to emit some signals or not. + static RefPtr<AccessibilityObject> oldFocusedObject = 0; + + // Only list boxes supported so far. + if (!object || !object->isListBox()) + return; + + // Emit signal from the listbox's point of view first. + g_signal_emit_by_name(object->wrapper(), "selection-changed"); + + // Find the item where the selection change was triggered from. + AccessibilityObject::AccessibilityChildrenVector items = object->children(); + SelectElement* select = toSelectElement(static_cast<Element*>(object->node())); + if (!select) + return; + int changedItemIndex = select->activeSelectionStartListIndex(); + if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size())) + return; + AccessibilityObject* item = items.at(changedItemIndex).get(); + + // Ensure the oldFocusedObject belongs to the same document that + // the current item so further comparisons make sense. Otherwise, + // just reset oldFocusedObject so it won't be taken into account. + if (item && oldFocusedObject && item->document() != oldFocusedObject->document()) + oldFocusedObject = 0; + + AtkObject* axItem = item ? item->wrapper() : 0; + AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0; + + // Old focused object just lost focus, so emit the events. + if (axOldFocusedObject && axItem != axOldFocusedObject) { + g_signal_emit_by_name(axOldFocusedObject, "focus-event", false); + g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", false); + } + + // Emit needed events for the currently (un)selected item. + if (axItem) { + bool isSelected = item->isSelected(); + g_signal_emit_by_name(axItem, "state-change", "selected", isSelected); + g_signal_emit_by_name(axItem, "focus-event", isSelected); + g_signal_emit_by_name(axItem, "state-change", "focused", isSelected); + } + + // Update pointer to the previously focused object. + oldFocusedObject = item; +} + +void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification) +{ + AtkObject* axObject = coreObject->wrapper(); + if (!axObject) + return; + + if (notification == AXCheckedStateChanged) { + if (!coreObject->isCheckboxOrRadio()) + return; + g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked()); + } else if (notification == AXMenuListValueChanged) { + if (!coreObject->isMenuList()) + return; + g_signal_emit_by_name(axObject, "focus-event", true); + g_signal_emit_by_name(axObject, "state-change", "focused", true); + } else if (notification == AXSelectedChildrenChanged) + notifyChildrenSelectionChange(coreObject); +} + +static void emitTextChanged(AccessibilityRenderObject* object, AXObjectCache::AXTextChange textChange, unsigned offset, unsigned count) +{ + // Get the axObject for the parent object + AtkObject* wrapper = object->parentObjectUnignored()->wrapper(); + if (!wrapper || !ATK_IS_TEXT(wrapper)) + return; + + // Select the right signal to be emitted + CString detail; + switch (textChange) { + case AXObjectCache::AXTextInserted: + detail = "text-changed::insert"; + break; + case AXObjectCache::AXTextDeleted: + detail = "text-changed::delete"; + break; + } + + if (!detail.isNull()) + g_signal_emit_by_name(wrapper, detail.data(), offset, count); +} + +void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, unsigned count) +{ + // Sanity check + if (count < 1 || !object || !object->isAccessibilityRenderObject()) + return; + + Node* node = object->node(); + RefPtr<Range> range = Range::create(node->document(), Position(node->parentNode(), 0), Position(node, 0)); + emitTextChanged(toAccessibilityRenderObject(object), textChange, offset + TextIterator::rangeLength(range.get()), count); +} + +void AXObjectCache::handleFocusedUIElementChanged(RenderObject* oldFocusedRender, RenderObject* newFocusedRender) +{ + RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedRender); + if (oldObject) { + g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false); + g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", false); + } + RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedRender); + if (newObject) { + g_signal_emit_by_name(newObject->wrapper(), "focus-event", true); + g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", true); + } +} + +void AXObjectCache::handleScrolledToAnchor(const Node*) +{ +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp b/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp new file mode 100644 index 0000000..9772b43 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2008 Apple Ltd. + * Copyright (C) 2008 Alp Toker <alp@atoker.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObject.h" +#include "RenderObject.h" +#include "RenderText.h" + +#include <glib-object.h> + +#if HAVE(ACCESSIBILITY) + +namespace WebCore { + +bool AccessibilityObject::accessibilityIgnoreAttachment() const +{ + return false; +} + +AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const +{ + AccessibilityObject* parent = parentObject(); + if (!parent) + return DefaultBehavior; + + if (roleValue() == SplitterRole) + return IncludeObject; + + if (isGroup()) { + // When a list item is made up entirely of children (e.g. paragraphs) + // the list item gets ignored. We need it. + if (parent->isList()) + return IncludeObject; + + // We expect the parent of a table cell to be a table. + AccessibilityObject* child = firstChild(); + if (child && child->roleValue() == CellRole) + return IgnoreObject; + } + + // Entries and password fields have extraneous children which we want to ignore. + if (parent->isPasswordField() || parent->isTextControl()) + return IgnoreObject; + + AccessibilityRole role = roleValue(); + + // Include all tables, even layout tables. The AT can decide what to do with each. + if (role == CellRole || role == TableRole) + return IncludeObject; + + // We at some point might have a need to expose a table row; but it's not standard Gtk+. + if (role == RowRole) + return IgnoreObject; + + // The object containing the text should implement AtkText itself. + if (role == StaticTextRole) + return IgnoreObject; + + // Include all list items, regardless they have or not inline children + if (role == ListItemRole) + return IncludeObject; + + // Bullets/numbers for list items shouldn't be exposed as AtkObjects. + if (role == ListMarkerRole) + return IgnoreObject; + + return DefaultBehavior; +} + +AccessibilityObjectWrapper* AccessibilityObject::wrapper() const +{ + return m_wrapper; +} + +void AccessibilityObject::setWrapper(AccessibilityObjectWrapper* wrapper) +{ + if (wrapper == m_wrapper) + return; + + if (m_wrapper) + g_object_unref(m_wrapper); + + m_wrapper = wrapper; + + if (m_wrapper) + g_object_ref(m_wrapper); +} + +bool AccessibilityObject::allowsTextRanges() const +{ + return isTextControl() || isWebArea() || isGroup() || isLink() || isHeading(); +} + +unsigned AccessibilityObject::getLengthForTextRange() const +{ + unsigned textLength = text().length(); + + if (textLength) + return textLength; + + // Gtk ATs need this for all text objects; not just text controls. + Node* node = this->node(); + RenderObject* renderer = node ? node->renderer() : 0; + if (renderer && renderer->isText()) { + RenderText* renderText = toRenderText(renderer); + textLength = renderText ? renderText->textLength() : 0; + } + + // Get the text length from the elements under the + // accessibility object if the value is still zero. + if (!textLength && allowsTextRanges()) + textLength = textUnderElement().length(); + + return textLength; +} + +} // namespace WebCore + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp new file mode 100644 index 0000000..7489034 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp @@ -0,0 +1,2489 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * Copyright (C) 2009 Igalia S.L. + * Copyright (C) 2009 Jan Alonzo + * + * Portions from Mozilla a11y, copyright as follows: + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Sun Microsystems, Inc. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityObjectWrapperAtk.h" + +#if HAVE(ACCESSIBILITY) + +#include "AXObjectCache.h" +#include "AccessibilityList.h" +#include "AccessibilityListBox.h" +#include "AccessibilityListBoxOption.h" +#include "AccessibilityRenderObject.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "AccessibilityTableColumn.h" +#include "AccessibilityTableRow.h" +#include "Document.h" +#include "DocumentType.h" +#include "Editor.h" +#include "Frame.h" +#include "FrameView.h" +#include "GOwnPtr.h" +#include "HostWindow.h" +#include "HTMLNames.h" +#include "HTMLTableCaptionElement.h" +#include "HTMLTableElement.h" +#include "InlineTextBox.h" +#include "IntRect.h" +#include "NotImplemented.h" +#include "RenderListItem.h" +#include "RenderListMarker.h" +#include "RenderText.h" +#include "SelectElement.h" +#include "Settings.h" +#include "TextEncoding.h" +#include "TextIterator.h" +#include "WebKitAccessibleHyperlink.h" + +#include <atk/atk.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <libgail-util/gail-util.h> +#include <pango/pango.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> + +using namespace WebCore; + +static AccessibilityObject* fallbackObject() +{ + // FIXME: An AXObjectCache with a Document is meaningless. + static AXObjectCache* fallbackCache = new AXObjectCache(0); + static AccessibilityObject* object = 0; + if (!object) { + // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack + object = fallbackCache->getOrCreate(ListBoxOptionRole); + object->ref(); + } + + return object; +} + +// Used to provide const char* returns. +static const char* returnString(const String& str) +{ + static CString returnedString; + returnedString = str.utf8(); + return returnedString.data(); +} + +static AccessibilityObject* core(WebKitAccessible* accessible) +{ + if (!accessible) + return 0; + + return accessible->m_object; +} + +static AccessibilityObject* core(AtkObject* object) +{ + if (!WEBKIT_IS_ACCESSIBLE(object)) + return 0; + + return core(WEBKIT_ACCESSIBLE(object)); +} + +static AccessibilityObject* core(AtkAction* action) +{ + return core(ATK_OBJECT(action)); +} + +static AccessibilityObject* core(AtkSelection* selection) +{ + return core(ATK_OBJECT(selection)); +} + +static AccessibilityObject* core(AtkText* text) +{ + return core(ATK_OBJECT(text)); +} + +static AccessibilityObject* core(AtkEditableText* text) +{ + return core(ATK_OBJECT(text)); +} + +static AccessibilityObject* core(AtkComponent* component) +{ + return core(ATK_OBJECT(component)); +} + +static AccessibilityObject* core(AtkImage* image) +{ + return core(ATK_OBJECT(image)); +} + +static AccessibilityObject* core(AtkTable* table) +{ + return core(ATK_OBJECT(table)); +} + +static AccessibilityObject* core(AtkHypertext* hypertext) +{ + return core(ATK_OBJECT(hypertext)); +} + +static AccessibilityObject* core(AtkDocument* document) +{ + return core(ATK_OBJECT(document)); +} + +static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset); + +static const gchar* webkit_accessible_get_name(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + if (!coreObject->isAccessibilityRenderObject()) + return returnString(coreObject->stringValue()); + + AccessibilityRenderObject* renderObject = static_cast<AccessibilityRenderObject*>(coreObject); + if (coreObject->isControl()) { + AccessibilityObject* label = renderObject->correspondingLabelForControlElement(); + if (label) { + AtkObject* atkObject = label->wrapper(); + if (ATK_IS_TEXT(atkObject)) + return webkit_accessible_text_get_text(ATK_TEXT(atkObject), 0, -1); + } + + // Try text under the node + String textUnder = renderObject->textUnderElement(); + if (textUnder.length()) + return returnString(textUnder); + } + + if (renderObject->isImage() || renderObject->isInputImage()) { + Node* node = renderObject->renderer()->node(); + if (node && node->isHTMLElement()) { + // Get the attribute rather than altText String so as not to fall back on title. + String alt = static_cast<HTMLElement*>(node)->getAttribute(HTMLNames::altAttr); + if (!alt.isEmpty()) + return returnString(alt); + } + } + + return returnString(coreObject->stringValue()); +} + +static const gchar* webkit_accessible_get_description(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + Node* node = 0; + if (coreObject->isAccessibilityRenderObject()) + node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); + if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole) + return returnString(coreObject->accessibilityDescription()); + + // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. + if (coreObject->roleValue() == TableRole) { + String summary = static_cast<HTMLTableElement*>(node)->summary(); + if (!summary.isEmpty()) + return returnString(summary); + } + + // The title attribute should be reliably available as the object's descripton. + // We do not want to fall back on other attributes in its absence. See bug 25524. + String title = static_cast<HTMLElement*>(node)->title(); + if (!title.isEmpty()) + return returnString(title); + + return returnString(coreObject->accessibilityDescription()); +} + +static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) +{ + AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); + if (accObject->isControl()) { + AccessibilityObject* label = accObject->correspondingLabelForControlElement(); + if (label) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } else { + AccessibilityObject* control = accObject->correspondingControlForLabelElement(); + if (control) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); + } +} + +static gpointer webkit_accessible_parent_class = 0; + +static bool isRootObject(AccessibilityObject* coreObject) +{ + // The root accessible object in WebCore is always an object with + // the ScrolledArea role with one child with the WebArea role. + if (!coreObject || !coreObject->isScrollView()) + return false; + + AccessibilityObject* firstChild = coreObject->firstChild(); + if (!firstChild || !firstChild->isWebArea()) + return false; + + return true; +} + +static AtkObject* atkParentOfRootObject(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); + + // The top level object claims to not have a parent. This makes it + // impossible for assistive technologies to ascend the accessible + // hierarchy all the way to the application. (Bug 30489) + if (!coreParent && isRootObject(coreObject)) { + HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); + if (hostWindow) { + PlatformPageClient scrollView = hostWindow->platformPageClient(); + if (scrollView) { + GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView); + if (scrollViewParent) + return gtk_widget_get_accessible(scrollViewParent); + } + } + } + + if (!coreParent) + return 0; + + return coreParent->wrapper(); +} + +static AtkObject* webkit_accessible_get_parent(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); + if (!coreParent && isRootObject(coreObject)) + return atkParentOfRootObject(object); + + if (!coreParent) + return 0; + + return coreParent->wrapper(); +} + +static gint webkit_accessible_get_n_children(AtkObject* object) +{ + return core(object)->children().size(); +} + +static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); + if (index < 0 || static_cast<unsigned>(index) >= children.size()) + return 0; + + AccessibilityObject* coreChild = children.at(index).get(); + + if (!coreChild) + return 0; + + AtkObject* child = coreChild->wrapper(); + atk_object_set_parent(child, object); + g_object_ref(child); + + return child; +} + +static gint webkit_accessible_get_index_in_parent(AtkObject* object) +{ + AccessibilityObject* coreObject = core(object); + AccessibilityObject* parent = coreObject->parentObjectUnignored(); + + if (!parent && isRootObject(coreObject)) { + AtkObject* atkParent = atkParentOfRootObject(object); + if (!atkParent) + return -1; + + unsigned count = atk_object_get_n_accessible_children(atkParent); + for (unsigned i = 0; i < count; ++i) { + AtkObject* child = atk_object_ref_accessible_child(atkParent, i); + bool childIsObject = child == object; + g_object_unref(child); + if (childIsObject) + return i; + } + } + + AccessibilityObject::AccessibilityChildrenVector children = parent->children(); + unsigned count = children.size(); + for (unsigned i = 0; i < count; ++i) { + if (children[i] == coreObject) + return i; + } + + return -1; +} + +static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value) +{ + AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute))); + attribute->name = g_strdup(name); + attribute->value = g_strdup(value); + attributeSet = g_slist_prepend(attributeSet, attribute); + + return attributeSet; +} + +static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object) +{ + AtkAttributeSet* attributeSet = 0; + attributeSet = addAttributeToSet(attributeSet, "toolkit", "WebKitGtk"); + + AccessibilityObject* coreObject = core(object); + if (!coreObject) + return attributeSet; + + int headingLevel = coreObject->headingLevel(); + if (headingLevel) { + String value = String::number(headingLevel); + attributeSet = addAttributeToSet(attributeSet, "level", value.utf8().data()); + } + + // Set the 'layout-guess' attribute to help Assistive + // Technologies know when an exposed table is not data table. + if (coreObject->isAccessibilityTable() && !coreObject->isDataTable()) + attributeSet = addAttributeToSet(attributeSet, "layout-guess", "true"); + + return attributeSet; +} + +static AtkRole atkRole(AccessibilityRole role) +{ + switch (role) { + case UnknownRole: + return ATK_ROLE_UNKNOWN; + case ButtonRole: + return ATK_ROLE_PUSH_BUTTON; + case RadioButtonRole: + return ATK_ROLE_RADIO_BUTTON; + case CheckBoxRole: + return ATK_ROLE_CHECK_BOX; + case SliderRole: + return ATK_ROLE_SLIDER; + case TabGroupRole: + return ATK_ROLE_PAGE_TAB_LIST; + case TextFieldRole: + case TextAreaRole: + return ATK_ROLE_ENTRY; + case StaticTextRole: + return ATK_ROLE_TEXT; + case OutlineRole: + return ATK_ROLE_TREE; + case MenuBarRole: + return ATK_ROLE_MENU_BAR; + case MenuListPopupRole: + case MenuRole: + return ATK_ROLE_MENU; + case MenuListOptionRole: + case MenuItemRole: + return ATK_ROLE_MENU_ITEM; + case ColumnRole: + //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? + return ATK_ROLE_UNKNOWN; // Matches Mozilla + case RowRole: + //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? + return ATK_ROLE_LIST_ITEM; // Matches Mozilla + case ToolbarRole: + return ATK_ROLE_TOOL_BAR; + case BusyIndicatorRole: + return ATK_ROLE_PROGRESS_BAR; // Is this right? + case ProgressIndicatorRole: + //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp + return ATK_ROLE_PROGRESS_BAR; + case WindowRole: + return ATK_ROLE_WINDOW; + case PopUpButtonRole: + case ComboBoxRole: + return ATK_ROLE_COMBO_BOX; + case SplitGroupRole: + return ATK_ROLE_SPLIT_PANE; + case SplitterRole: + return ATK_ROLE_SEPARATOR; + case ColorWellRole: + return ATK_ROLE_COLOR_CHOOSER; + case ListRole: + return ATK_ROLE_LIST; + case ScrollBarRole: + return ATK_ROLE_SCROLL_BAR; + case ScrollAreaRole: + return ATK_ROLE_SCROLL_PANE; + case GridRole: // Is this right? + case TableRole: + return ATK_ROLE_TABLE; + case ApplicationRole: + return ATK_ROLE_APPLICATION; + case GroupRole: + case RadioGroupRole: + return ATK_ROLE_PANEL; + case CellRole: + return ATK_ROLE_TABLE_CELL; + case LinkRole: + case WebCoreLinkRole: + case ImageMapLinkRole: + return ATK_ROLE_LINK; + case ImageMapRole: + case ImageRole: + return ATK_ROLE_IMAGE; + case ListMarkerRole: + return ATK_ROLE_TEXT; + case WebAreaRole: + //return ATK_ROLE_HTML_CONTAINER; // Is this right? + return ATK_ROLE_DOCUMENT_FRAME; + case HeadingRole: + return ATK_ROLE_HEADING; + case ListBoxRole: + return ATK_ROLE_LIST; + case ListItemRole: + case ListBoxOptionRole: + return ATK_ROLE_LIST_ITEM; + default: + return ATK_ROLE_UNKNOWN; + } +} + +static AtkRole webkit_accessible_get_role(AtkObject* object) +{ + AccessibilityObject* axObject = core(object); + + if (!axObject) + return ATK_ROLE_UNKNOWN; + + // WebCore does not know about paragraph role, label role, or section role + if (axObject->isAccessibilityRenderObject()) { + Node* node = static_cast<AccessibilityRenderObject*>(axObject)->renderer()->node(); + if (node) { + if (node->hasTagName(HTMLNames::pTag)) + return ATK_ROLE_PARAGRAPH; + if (node->hasTagName(HTMLNames::labelTag)) + return ATK_ROLE_LABEL; + if (node->hasTagName(HTMLNames::divTag)) + return ATK_ROLE_SECTION; + if (node->hasTagName(HTMLNames::formTag)) + return ATK_ROLE_FORM; + } + } + + // Note: Why doesn't WebCore have a password field for this + if (axObject->isPasswordField()) + return ATK_ROLE_PASSWORD_TEXT; + + return atkRole(axObject->roleValue()); +} + +static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection) +{ + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return false; + + if (selection.isNone()) + return false; + + RefPtr<Range> range = selection.toNormalizedRange(); + if (!range) + return false; + + // We want to check that both the selection intersects the node + // AND that the selection is not just "touching" one of the + // boundaries for the selected node. We want to check whether the + // node is actually inside the region, at least partially. + Node* node = coreObject->node(); + Node* lastDescendant = node->lastDescendant(); + ExceptionCode ec = 0; + return (range->intersectsNode(node, ec) + && (range->endContainer() != node || range->endOffset()) + && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant))); +} + +static bool isTextWithCaret(AccessibilityObject* coreObject) +{ + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return false; + + Document* document = coreObject->document(); + if (!document) + return false; + + Frame* frame = document->frame(); + if (!frame) + return false; + + Settings* settings = frame->settings(); + if (!settings || !settings->caretBrowsingEnabled()) + return false; + + // Check text objects and paragraphs only. + AtkObject* axObject = coreObject->wrapper(); + AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID; + if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH) + return false; + + // Finally, check whether the caret is set in the current object. + VisibleSelection selection = coreObject->selection(); + if (!selection.isCaret()) + return false; + + return selectionBelongsToObject(coreObject, selection); +} + +static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) +{ + AccessibilityObject* parent = coreObject->parentObject(); + bool isListBoxOption = parent && parent->isListBox(); + + // Please keep the state list in alphabetical order + if (coreObject->isChecked()) + atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); + + // FIXME: isReadOnly does not seem to do the right thing for + // controls, so check explicitly for them. In addition, because + // isReadOnly is false for listBoxOptions, we need to add one + // more check so that we do not present them as being "editable". + if ((!coreObject->isReadOnly() || + (coreObject->isControl() && coreObject->canSetValueAttribute())) && + !isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); + + // FIXME: Put both ENABLED and SENSITIVE together here for now + if (coreObject->isEnabled()) { + atk_state_set_add_state(stateSet, ATK_STATE_ENABLED); + atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE); + } + + if (coreObject->canSetExpandedAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE); + + if (coreObject->isExpanded()) + atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED); + + if (coreObject->canSetFocusAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + + if (coreObject->isFocused() || isTextWithCaret(coreObject)) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); + + // TODO: ATK_STATE_HORIZONTAL + + if (coreObject->isIndeterminate()) + atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); + + if (coreObject->isMultiSelectable()) + atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); + + // TODO: ATK_STATE_OPAQUE + + if (coreObject->isPressed()) + atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); + + // TODO: ATK_STATE_SELECTABLE_TEXT + + if (coreObject->canSetSelectedAttribute()) { + atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE); + // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} + // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the + // former. + if (isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + } + + if (coreObject->isSelected()) { + atk_state_set_add_state(stateSet, ATK_STATE_SELECTED); + // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} + // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the + // former. + if (isListBoxOption) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); + } + + // FIXME: Group both SHOWING and VISIBLE here for now + // Not sure how to handle this in WebKit, see bug + // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other + // issues with SHOWING vs VISIBLE within GTK+ + if (!coreObject->isOffScreen()) { + atk_state_set_add_state(stateSet, ATK_STATE_SHOWING); + atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE); + } + + // Mutually exclusive, so we group these two + if (coreObject->roleValue() == TextFieldRole) + atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); + else if (coreObject->roleValue() == TextAreaRole) + atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); + + // TODO: ATK_STATE_SENSITIVE + + // TODO: ATK_STATE_VERTICAL + + if (coreObject->isVisited()) + atk_state_set_add_state(stateSet, ATK_STATE_VISITED); +} + +static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object) +{ + AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object); + AccessibilityObject* coreObject = core(object); + + if (coreObject == fallbackObject()) { + atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); + return stateSet; + } + + // Text objects must be focusable. + AtkRole role = atk_object_get_role(object); + if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH) + atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); + + setAtkStateSetFromCoreObject(coreObject, stateSet); + return stateSet; +} + +static AtkRelationSet* webkit_accessible_ref_relation_set(AtkObject* object) +{ + AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object); + AccessibilityObject* coreObject = core(object); + + setAtkRelationSetFromCoreObject(coreObject, relationSet); + + return relationSet; +} + +static void webkit_accessible_init(AtkObject* object, gpointer data) +{ + if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize) + ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data); + + WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data); +} + +static void webkit_accessible_finalize(GObject* object) +{ + // This is a good time to clear the return buffer. + returnString(String()); + + G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object); +} + +static void webkit_accessible_class_init(AtkObjectClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + + webkit_accessible_parent_class = g_type_class_peek_parent(klass); + + gobjectClass->finalize = webkit_accessible_finalize; + + klass->initialize = webkit_accessible_init; + klass->get_name = webkit_accessible_get_name; + klass->get_description = webkit_accessible_get_description; + klass->get_parent = webkit_accessible_get_parent; + klass->get_n_children = webkit_accessible_get_n_children; + klass->ref_child = webkit_accessible_ref_child; + klass->get_role = webkit_accessible_get_role; + klass->ref_state_set = webkit_accessible_ref_state_set; + klass->get_index_in_parent = webkit_accessible_get_index_in_parent; + klass->get_attributes = webkit_accessible_get_attributes; + klass->ref_relation_set = webkit_accessible_ref_relation_set; +} + +GType +webkit_accessible_get_type(void) +{ + static volatile gsize type_volatile = 0; + + if (g_once_init_enter(&type_volatile)) { + static const GTypeInfo tinfo = { + sizeof(WebKitAccessibleClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) webkit_accessible_class_init, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessible), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + GType type = g_type_register_static(ATK_TYPE_OBJECT, + "WebKitAccessible", &tinfo, GTypeFlags(0)); + g_once_init_leave(&type_volatile, type); + } + + return type_volatile; +} + +static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, FALSE); + return core(action)->performDefaultAction(); +} + +static gint webkit_accessible_action_get_n_actions(AtkAction* action) +{ + return 1; +} + +static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, 0); + // TODO: Need a way to provide/localize action descriptions. + notImplemented(); + return ""; +} + +static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, 0); + // FIXME: Construct a proper keybinding string. + return returnString(core(action)->accessKey().string()); +} + +static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i) +{ + g_return_val_if_fail(i == 0, 0); + return returnString(core(action)->actionVerb()); +} + +static void atk_action_interface_init(AtkActionIface* iface) +{ + iface->do_action = webkit_accessible_action_do_action; + iface->get_n_actions = webkit_accessible_action_get_n_actions; + iface->get_description = webkit_accessible_action_get_description; + iface->get_keybinding = webkit_accessible_action_get_keybinding; + iface->get_name = webkit_accessible_action_get_name; +} + +// Selection (for controls) + +static AccessibilityObject* listObjectForSelection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + + // Only list boxes and menu lists supported so far. + if (!coreSelection->isListBox() && !coreSelection->isMenuList()) + return 0; + + // For list boxes the list object is just itself. + if (coreSelection->isListBox()) + return coreSelection; + + // For menu lists we need to return the first accessible child, + // with role MenuListPopupRole, since that's the one holding the list + // of items with role MenuListOptionRole. + AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children(); + if (!children.size()) + return 0; + + AccessibilityObject* listObject = children.at(0).get(); + if (!listObject->isMenuListPopup()) + return 0; + + return listObject; +} + +static AccessibilityObject* optionFromList(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || i < 0) + return 0; + + // Need to select the proper list object depending on the type. + AccessibilityObject* listObject = listObjectForSelection(selection); + if (!listObject) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector options = listObject->children(); + if (i < static_cast<gint>(options.size())) + return options.at(i).get(); + + return 0; +} + +static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i) +{ + // i is the ith selection as opposed to the ith child. + + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || i < 0) + return 0; + + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + if (coreSelection->isListBox()) + coreSelection->selectedChildren(selectedItems); + else if (coreSelection->isMenuList()) { + RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer(); + if (!renderer) + return 0; + + SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node())); + int selectedIndex = selectNode->selectedIndex(); + const Vector<Element*> listItems = selectNode->listItems(); + + if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size())) + return 0; + + return optionFromList(selection, selectedIndex); + } + + if (i < static_cast<gint>(selectedItems.size())) + return selectedItems.at(i).get(); + + return 0; +} + +static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return false; + + AccessibilityObject* option = optionFromList(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) { + option->setSelected(true); + return option->isSelected(); + } + + return false; +} + +static gboolean webkit_accessible_selection_clear_selection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return false; + + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + if (coreSelection->isListBox() || coreSelection->isMenuList()) { + // Set the list of selected items to an empty list; then verify that it worked. + AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); + listBox->setSelectedChildren(selectedItems); + listBox->selectedChildren(selectedItems); + return selectedItems.size() == 0; + } + return false; +} + +static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* option = optionFromSelection(selection, i); + if (option) { + AtkObject* child = option->wrapper(); + g_object_ref(child); + return child; + } + + return 0; +} + +static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || !coreSelection->isAccessibilityRenderObject()) + return 0; + + if (coreSelection->isListBox()) { + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + coreSelection->selectedChildren(selectedItems); + return static_cast<gint>(selectedItems.size()); + } + + if (coreSelection->isMenuList()) { + RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer(); + if (!renderer) + return 0; + + SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node())); + int selectedIndex = selectNode->selectedIndex(); + const Vector<Element*> listItems = selectNode->listItems(); + + return selectedIndex >= 0 && selectedIndex < static_cast<int>(listItems.size()); + } + + return 0; +} + +static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return 0; + + AccessibilityObject* option = optionFromList(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) + return option->isSelected(); + + return false; +} + +static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection) + return 0; + + // TODO: This is only getting called if i == 0. What is preventing the rest? + AccessibilityObject* option = optionFromSelection(selection, i); + if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) { + option->setSelected(false); + return !option->isSelected(); + } + + return false; +} + +static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection) +{ + AccessibilityObject* coreSelection = core(selection); + if (!coreSelection || !coreSelection->isMultiSelectable()) + return false; + + AccessibilityRenderObject::AccessibilityChildrenVector children = coreSelection->children(); + if (coreSelection->isListBox()) { + AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); + listBox->setSelectedChildren(children); + AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; + listBox->selectedChildren(selectedItems); + return selectedItems.size() == children.size(); + } + + return false; +} + +static void atk_selection_interface_init(AtkSelectionIface* iface) +{ + iface->add_selection = webkit_accessible_selection_add_selection; + iface->clear_selection = webkit_accessible_selection_clear_selection; + iface->ref_selection = webkit_accessible_selection_ref_selection; + iface->get_selection_count = webkit_accessible_selection_get_selection_count; + iface->is_child_selected = webkit_accessible_selection_is_child_selected; + iface->remove_selection = webkit_accessible_selection_remove_selection; + iface->select_all_selection = webkit_accessible_selection_select_all_selection; +} + +// Text + +static gchar* utf8Substr(const gchar* string, gint start, gint end) +{ + ASSERT(string); + glong strLen = g_utf8_strlen(string, -1); + if (start > strLen || end > strLen) + return 0; + gchar* startPtr = g_utf8_offset_to_pointer(string, start); + gsize lenInBytes = g_utf8_offset_to_pointer(string, end) - startPtr + 1; + gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1)); + return g_utf8_strncpy(output, startPtr, end - start + 1); +} + +// This function is not completely general, is it's tied to the +// internals of WebCore's text presentation. +static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to) +{ + CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables); + gchar* utf8String = utf8Substr(stringUTF8.data(), from, to); + if (!g_utf8_validate(utf8String, -1, 0)) { + g_free(utf8String); + return 0; + } + gsize len = strlen(utf8String); + GString* ret = g_string_new_len(0, len); + gchar* ptr = utf8String; + + // WebCore introduces line breaks in the text that do not reflect + // the layout you see on the screen, replace them with spaces + while (len > 0) { + gint index, start; + pango_find_paragraph_boundary(ptr, len, &index, &start); + g_string_append_len(ret, ptr, index); + if (index == start) + break; + g_string_append_c(ret, ' '); + ptr += start; + len -= start; + } + + g_free(utf8String); + return g_string_free(ret, FALSE); +} + +gchar* textForRenderer(RenderObject* renderer) +{ + GString* resultText = g_string_new(0); + + if (!renderer) + return g_string_free(resultText, FALSE); + + // For RenderBlocks, piece together the text from the RenderText objects they contain. + 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 { + // We need to check children, if any, to consider when + // current object is not a text object but some of its + // children are, in order not to miss those portions of + // text by not properly handling those situations + if (object->firstChild()) + g_string_append(resultText, textForRenderer(object)); + + continue; + } + + InlineTextBox* box = renderText->firstTextBox(); + while (box) { + gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); + g_string_append(resultText, text); + // Newline chars in the source result in separate text boxes, so check + // before adding a newline in the layout. See bug 25415 comment #78. + // If the next sibling is a BR, we'll add the newline when we examine that child. + if (!box->nextOnLineExists() && (!object->nextSibling() || !object->nextSibling()->isBR())) + g_string_append(resultText, "\n"); + box = box->nextTextBox(); + } + } + + // Insert the text of the marker for list item in the right place, if present + 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); +} + +gchar* textForObject(AccessibilityRenderObject* accObject) +{ + GString* str = g_string_new(0); + + // For text controls, we can get the text line by line. + if (accObject->isTextControl()) { + unsigned textLength = accObject->textLength(); + int lineNumber = 0; + PlainTextRange range = accObject->doAXRangeForLine(lineNumber); + while (range.length) { + // When a line of text wraps in a text area, the final space is removed. + if (range.start + range.length < textLength) + range.length -= 1; + String lineText = accObject->doAXStringForRange(range); + g_string_append(str, lineText.utf8().data()); + g_string_append(str, "\n"); + range = accObject->doAXRangeForLine(++lineNumber); + } + } else if (accObject->isAccessibilityRenderObject()) { + GOwnPtr<gchar> rendererText(textForRenderer(accObject->renderer())); + g_string_append(str, rendererText.get()); + } + + return g_string_free(str, FALSE); +} + +static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset) +{ + AccessibilityObject* coreObject = core(text); + String ret; + unsigned start = startOffset; + if (endOffset == -1) { + endOffset = coreObject->stringValue().length(); + if (!endOffset) + endOffset = coreObject->textUnderElement().length(); + } + int length = endOffset - startOffset; + + if (coreObject->isTextControl()) + ret = coreObject->doAXStringForRange(PlainTextRange(start, length)); + else { + ret = coreObject->stringValue().substring(start, length); + if (!ret) + ret = coreObject->textUnderElement().substring(start, length); + } + + if (!ret.length()) { + // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs) + ret = String(textForObject(static_cast<AccessibilityRenderObject*>(coreObject))); + if (!endOffset) + endOffset = ret.length(); + ret = ret.substring(start, endOffset - startOffset); + } + + // Prefix a item number/bullet if needed + if (coreObject->roleValue() == ListItemRole) { + RenderObject* objRenderer = static_cast<AccessibilityRenderObject*>(coreObject)->renderer(); + if (objRenderer && objRenderer->isListItem()) { + String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix(); + ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText; + } + } + + return g_strdup(ret.utf8().data()); +} + +static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject) +{ + gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util"); + if (data) + return static_cast<GailTextUtil*>(data); + + GailTextUtil* gailTextUtil = gail_text_util_new(); + gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1)); + g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref); + return gailTextUtil; +} + +static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) +{ + AccessibilityObject* coreObject = core(textObject); + + HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); + if (!hostWindow) + return 0; + PlatformPageClient webView = hostWindow->platformPageClient(); + if (!webView) + return 0; + + AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); + if (!accObject) + return 0; + + // Create a string with the layout as it appears on the screen + PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(accObject)); + g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref); + return layout; +} + +static gchar* webkit_accessible_text_get_text_after_offset(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* webkit_accessible_text_get_text_at_offset(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* webkit_accessible_text_get_text_before_offset(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 webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset) +{ + notImplemented(); + return 0; +} + +static gint webkit_accessible_text_get_caret_offset(AtkText* text) +{ + // coreObject is the unignored object whose offset the caller is requesting. + // focusedObject is the object with the caret. It is likely ignored -- unless it's a link. + AccessibilityObject* coreObject = core(text); + Node* focusedNode = coreObject->selection().end().node(); + + if (!focusedNode) + return 0; + + RenderObject* focusedRenderer = focusedNode->renderer(); + AccessibilityObject* focusedObject = coreObject->document()->axObjectCache()->getOrCreate(focusedRenderer); + + int offset; + // Don't ignore links if the offset is being requested for a link. + if (!objectAndOffsetUnignored(focusedObject, offset, !coreObject->isLink())) + return 0; + + // TODO: Verify this for RTL text. + return offset; +} + +static int baselinePositionForAccessibilityRenderObject(RenderObject* renderObject) +{ + // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was + // removed in r70072. The implementation looks incorrect though, because this is not the + // baseline of the underlying RenderObject, but of the AccessibilityRenderObject. + const Font& f = renderObject->firstLineStyle()->font(); + return f.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - f.height()) / 2; +} + +static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object) +{ + if (!object->isAccessibilityRenderObject()) + return 0; + + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(object)->renderer(); + RenderStyle* style = renderer->style(); + + AtkAttributeSet* result = 0; + GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); + result = addAttributeToSet(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 = addAttributeToSet(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 = addAttributeToSet(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 * baselinePositionForAccessibilityRenderObject(renderer); + break; + case SUPER: + baselinePosition = baselinePositionForAccessibilityRenderObject(renderer); + break; + case BASELINE: + baselinePosition = 0; + break; + default: + includeRise = false; + break; + } + + if (includeRise) { + buffer.set(g_strdup_printf("%i", baselinePosition)); + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get()); + } + + int indentation = style->textIndent().calcValue(object->size().width()); + if (indentation != undefinedLength) { + buffer.set(g_strdup_printf("%i", indentation)); + result = addAttributeToSet(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 = addAttributeToSet(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 = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get()); + } + + switch (style->textAlign()) { + case TAAUTO: + break; + case LEFT: + case WEBKIT_LEFT: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left"); + break; + case RIGHT: + case WEBKIT_RIGHT: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right"); + break; + case CENTER: + case WEBKIT_CENTER: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center"); + break; + case JUSTIFY: + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill"); + } + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false"); + + result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false"); + + result = addAttributeToSet(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); +} + +// Returns an AtkAttributeSet with the elements of a1 which are either +// not present or different in a2. Neither a1 nor a2 should be used +// after calling this function. +static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* a1, AtkAttributeSet* a2) +{ + if (!a2) + return a1; + + AtkAttributeSet* i = a1; + AtkAttributeSet* found; + AtkAttributeSet* toDelete = 0; + + while (i) { + found = g_slist_find_custom(a2, i->data, (GCompareFunc)compareAttribute); + if (found) { + AtkAttributeSet* t = i->next; + toDelete = g_slist_prepend(toDelete, i->data); + a1 = g_slist_delete_link(a1, i); + i = t; + } else + i = i->next; + } + + atk_attribute_set_free(a2); + atk_attribute_set_free(toDelete); + return a1; +} + +static guint accessibilityObjectLength(const AccessibilityObject* object) +{ + // Non render objects are not taken into account + if (!object->isAccessibilityRenderObject()) + return 0; + + // For those objects implementing the AtkText interface we use the + // well known API to always get the text in a consistent way + AtkObject* atkObj = ATK_OBJECT(object->wrapper()); + if (ATK_IS_TEXT(atkObj)) { + GOwnPtr<gchar> text(webkit_accessible_text_get_text(ATK_TEXT(atkObj), 0, -1)); + return g_utf8_strlen(text.get(), -1); + } + + // Even if we don't expose list markers to Assistive + // Technologies, we need to have a way to measure their length + // for those cases when it's needed to take it into account + // separately (as in getAccessibilityObjectForOffset) + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(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 AtkAttributeSet* webkit_accessible_text_get_run_attributes(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* webkit_accessible_text_get_default_attributes(AtkText* text) +{ + AccessibilityObject* coreObject = core(text); + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return 0; + + return getAttributeSetForAccessibilityObject(coreObject); +} + +static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords) +{ + gchar* textContent = webkit_accessible_text_get_text(text, startOffset, -1); + gint textLength = g_utf8_strlen(textContent, -1); + + // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps. + 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: + extents = coreObject->document()->view()->contentsToScreen(extents); + break; + case ATK_XY_WINDOW: + // No-op + break; + } + + return extents; +} + +static void webkit_accessible_text_get_character_extents(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 webkit_accessible_text_get_range_extents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect) +{ + IntRect extents = textExtents(text, startOffset, endOffset - startOffset + 1, coords); + rect->x = extents.x(); + rect->y = extents.y(); + rect->width = extents.width(); + rect->height = extents.height(); +} + +static gint webkit_accessible_text_get_character_count(AtkText* text) +{ + return accessibilityObjectLength(core(text)); +} + +static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords) +{ + // FIXME: Use the AtkCoordType + // TODO: Is it correct to ignore range.length? + IntPoint pos(x, y); + PlainTextRange range = core(text)->doAXRangeForPosition(pos); + return range.start; +} + +static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset) +{ + if (!coreObject->isAccessibilityRenderObject()) + return; + + // Early return if the selection doesn't affect the selected node. + if (!selectionBelongsToObject(coreObject, selection)) + return; + + // We need to find the exact start and end positions in the + // selected node that intersects the selection, to later on get + // the right values for the effective start and end offsets. + ExceptionCode ec = 0; + Position nodeRangeStart; + Position nodeRangeEnd; + Node* node = coreObject->node(); + RefPtr<Range> selRange = selection.toNormalizedRange(); + + // If the selection affects the selected node and its first + // possible position is also in the selection, we must set + // nodeRangeStart to that position, otherwise to the selection's + // start position (it would belong to the node anyway). + Node* firstLeafNode = node->firstDescendant(); + if (selRange->isPointInRange(firstLeafNode, 0, ec)) + nodeRangeStart = firstPositionInNode(firstLeafNode); + else + nodeRangeStart = selRange->startPosition(); + + // If the selection affects the selected node and its last + // possible position is also in the selection, we must set + // nodeRangeEnd to that position, otherwise to the selection's + // end position (it would belong to the node anyway). + Node* lastLeafNode = node->lastDescendant(); + if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec)) + nodeRangeEnd = lastPositionInNode(lastLeafNode); + else + nodeRangeEnd = selRange->endPosition(); + + // Calculate position of the selected range inside the object. + Position parentFirstPosition = firstPositionInNode(node); + RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart); + + // Set values for start and end offsets. + startOffset = TextIterator::rangeLength(rangeInParent.get()); + RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd); + endOffset = startOffset + TextIterator::rangeLength(nodeRange.get()); +} + +static gint webkit_accessible_text_get_n_selections(AtkText* text) +{ + AccessibilityObject* coreObject = core(text); + VisibleSelection selection = coreObject->selection(); + + // Only range selections are needed for the purpose of this method + if (!selection.isRange()) + return 0; + + // We don't support multiple selections for now, so there's only + // two possibilities + // Also, we don't want to do anything if the selection does not + // belong to the currently selected object. We have to check since + // there's no way to get the selection for a given object, only + // the global one (the API is a bit confusing) + return selectionBelongsToObject(coreObject, selection) ? 1 : 0; +} + +static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset) +{ + // Default values, unless the contrary is proved + *startOffset = *endOffset = 0; + + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return 0; + + // Get the offsets of the selection for the selected object + AccessibilityObject* coreObject = core(text); + VisibleSelection selection = coreObject->selection(); + getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset); + + // Return 0 instead of "", as that's the expected result for + // this AtkText method when there's no selection + if (*startOffset == *endOffset) + return 0; + + return webkit_accessible_text_get_text(text, *startOffset, *endOffset); +} + +static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset) +{ + notImplemented(); + return FALSE; +} + +static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset) +{ + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return FALSE; + + // Consider -1 and out-of-bound values and correct them to length + gint textCount = webkit_accessible_text_get_character_count(text); + if (startOffset < 0 || startOffset > textCount) + startOffset = textCount; + if (endOffset < 0 || endOffset > textCount) + endOffset = textCount; + + AccessibilityObject* coreObject = core(text); + PlainTextRange textRange(startOffset, endOffset - startOffset); + VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); + coreObject->setSelectedVisiblePositionRange(range); + + return TRUE; +} + +static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selectionNum) +{ + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. + if (selectionNum) + return FALSE; + + // Do nothing if current selection doesn't belong to the object + if (!webkit_accessible_text_get_n_selections(text)) + return FALSE; + + // Set a new 0-sized selection to the caret position, in order + // to simulate selection removal (GAIL style) + gint caretOffset = webkit_accessible_text_get_caret_offset(text); + return webkit_accessible_text_set_selection(text, selectionNum, caretOffset, caretOffset); +} + +static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset) +{ + AccessibilityObject* coreObject = core(text); + + PlainTextRange textRange(offset, 0); + VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); + coreObject->setSelectedVisiblePositionRange(range); + + return TRUE; +} + +static void atk_text_interface_init(AtkTextIface* iface) +{ + iface->get_text = webkit_accessible_text_get_text; + iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset; + iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset; + iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset; + iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset; + iface->get_caret_offset = webkit_accessible_text_get_caret_offset; + iface->get_run_attributes = webkit_accessible_text_get_run_attributes; + iface->get_default_attributes = webkit_accessible_text_get_default_attributes; + iface->get_character_extents = webkit_accessible_text_get_character_extents; + iface->get_range_extents = webkit_accessible_text_get_range_extents; + iface->get_character_count = webkit_accessible_text_get_character_count; + iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point; + iface->get_n_selections = webkit_accessible_text_get_n_selections; + iface->get_selection = webkit_accessible_text_get_selection; + + // set methods + iface->add_selection = webkit_accessible_text_add_selection; + iface->remove_selection = webkit_accessible_text_remove_selection; + iface->set_selection = webkit_accessible_text_set_selection; + iface->set_caret_offset = webkit_accessible_text_set_caret_offset; +} + +// EditableText + +static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset) +{ + notImplemented(); + return FALSE; +} + +static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string) +{ + // FIXME: string nullcheck? + core(text)->setValue(String::fromUTF8(string)); +} + +static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position) +{ + // FIXME: string nullcheck? + + AccessibilityObject* coreObject = core(text); + // FIXME: Not implemented in WebCore + //coreObject->setSelectedTextRange(PlainTextRange(*position, 0)); + //coreObject->setSelectedText(String::fromUTF8(string)); + + if (!coreObject->document() || !coreObject->document()->frame()) + return; + coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0))); + coreObject->setFocused(true); + // FIXME: We should set position to the actual inserted text length, which may be less than that requested. + if (coreObject->document()->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0)) + *position += length; +} + +static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + notImplemented(); +} + +static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + notImplemented(); +} + +static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos) +{ + AccessibilityObject* coreObject = core(text); + // FIXME: Not implemented in WebCore + //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos)); + //coreObject->setSelectedText(String()); + + if (!coreObject->document() || !coreObject->document()->frame()) + return; + coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos))); + coreObject->setFocused(true); + coreObject->document()->frame()->editor()->performDelete(); +} + +static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position) +{ + notImplemented(); +} + +static void atk_editable_text_interface_init(AtkEditableTextIface* iface) +{ + iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes; + iface->set_text_contents = webkit_accessible_editable_text_set_text_contents; + iface->insert_text = webkit_accessible_editable_text_insert_text; + iface->copy_text = webkit_accessible_editable_text_copy_text; + iface->cut_text = webkit_accessible_editable_text_cut_text; + iface->delete_text = webkit_accessible_editable_text_delete_text; + iface->paste_text = webkit_accessible_editable_text_paste_text; +} + +static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0) +{ + FrameView* frameView = coreObject->documentFrameView(); + + if (frameView) { + switch (coordType) { + case ATK_XY_WINDOW: + rect = frameView->contentsToWindow(rect); + break; + case ATK_XY_SCREEN: + rect = frameView->contentsToScreen(rect); + break; + } + } + + if (x) + *x = rect.x(); + if (y) + *y = rect.y(); + if (width) + *width = rect.width(); + if (height) + *height = rect.height(); +} + +static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y) +{ + IntPoint pos(x, y); + + FrameView* frameView = coreObject->documentFrameView(); + if (frameView) { + switch (coordType) { + case ATK_XY_SCREEN: + return frameView->screenToContents(pos); + case ATK_XY_WINDOW: + return frameView->windowToContents(pos); + } + } + + return pos; +} + +static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType) +{ + IntPoint pos = atkToContents(core(component), coordType, x, y); + + AccessibilityObject* target = core(component)->accessibilityHitTest(pos); + if (!target) + return 0; + g_object_ref(target->wrapper()); + return target->wrapper(); +} + +static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType) +{ + IntRect rect = core(component)->elementRect(); + contentsToAtk(core(component), coordType, rect, x, y, width, height); +} + +static gboolean webkit_accessible_component_grab_focus(AtkComponent* component) +{ + core(component)->setFocused(true); + return core(component)->isFocused(); +} + +static void atk_component_interface_init(AtkComponentIface* iface) +{ + iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point; + iface->get_extents = webkit_accessible_component_get_extents; + iface->grab_focus = webkit_accessible_component_grab_focus; +} + +// Image + +static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType) +{ + IntRect rect = core(image)->elementRect(); + contentsToAtk(core(image), coordType, rect, x, y); +} + +static const gchar* webkit_accessible_image_get_image_description(AtkImage* image) +{ + return returnString(core(image)->accessibilityDescription()); +} + +static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height) +{ + IntSize size = core(image)->size(); + + if (width) + *width = size.width(); + if (height) + *height = size.height(); +} + +static void atk_image_interface_init(AtkImageIface* iface) +{ + iface->get_image_position = webkit_accessible_image_get_image_position; + iface->get_image_description = webkit_accessible_image_get_image_description; + iface->get_image_size = webkit_accessible_image_get_image_size; +} + +// Table + +static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row); + return 0; +} + +static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable) +{ + // Calculate the cell's index as if we had a traditional Gtk+ table in + // which cells are all direct children of the table, arranged row-first. + AccessibilityObject::AccessibilityChildrenVector allCells; + axTable->cells(allCells); + AccessibilityObject::AccessibilityChildrenVector::iterator position; + position = std::find(allCells.begin(), allCells.end(), axCell); + if (position == allCells.end()) + return -1; + return position - allCells.begin(); +} + +static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allCells; + static_cast<AccessibilityTable*>(accTable)->cells(allCells); + if (0 <= index && static_cast<unsigned>(index) < allCells.size()) { + AccessibilityObject* accCell = allCells.at(index).get(); + return static_cast<AccessibilityTableCell*>(accCell); + } + } + return 0; +} + +static AtkObject* webkit_accessible_table_ref_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + if (!axCell) + return 0; + return axCell->wrapper(); +} + +static gint webkit_accessible_table_get_index_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table)); + return cellIndex(axCell, axTable); +} + +static gint webkit_accessible_table_get_column_at_index(AtkTable* table, gint index) +{ + AccessibilityTableCell* axCell = cellAtIndex(table, index); + if (axCell){ + pair<int, int> columnRange; + axCell->columnIndexRange(columnRange); + return columnRange.first; + } + return -1; +} + +static gint webkit_accessible_table_get_row_at_index(AtkTable* table, gint index) +{ + AccessibilityTableCell* axCell = cellAtIndex(table, index); + if (axCell){ + pair<int, int> rowRange; + axCell->rowIndexRange(rowRange); + return rowRange.first; + } + return -1; +} + +static gint webkit_accessible_table_get_n_columns(AtkTable* table) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->columnCount(); + return 0; +} + +static gint webkit_accessible_table_get_n_rows(AtkTable* table) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) + return static_cast<AccessibilityTable*>(accTable)->rowCount(); + return 0; +} + +static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { + pair<int, int> columnRange; + axCell->columnIndexRange(columnRange); + return columnRange.second; + } + return 0; +} + +static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column) +{ + AccessibilityTableCell* axCell = cell(table, row, column); + if (axCell) { + pair<int, int> rowRange; + axCell->rowIndexRange(rowRange); + return rowRange.second; + } + return 0; +} + +static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allColumnHeaders; + static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders); + unsigned columnCount = allColumnHeaders.size(); + for (unsigned k = 0; k < columnCount; ++k) { + pair<int, int> columnRange; + AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get()); + cell->columnIndexRange(columnRange); + if (columnRange.first <= column && column < columnRange.first + columnRange.second) + return allColumnHeaders[k]->wrapper(); + } + } + return 0; +} + +static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + AccessibilityObject::AccessibilityChildrenVector allRowHeaders; + static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders); + unsigned rowCount = allRowHeaders.size(); + for (unsigned k = 0; k < rowCount; ++k) { + pair<int, int> rowRange; + AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get()); + cell->rowIndexRange(rowRange); + if (rowRange.first <= row && row < rowRange.first + rowRange.second) + return allRowHeaders[k]->wrapper(); + } + } + return 0; +} + +static AtkObject* webkit_accessible_table_get_caption(AtkTable* table) +{ + AccessibilityObject* accTable = core(table); + if (accTable->isAccessibilityRenderObject()) { + Node* node = static_cast<AccessibilityRenderObject*>(accTable)->renderer()->node(); + if (node && node->hasTagName(HTMLNames::tableTag)) { + HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(node)->caption(); + if (caption) + return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper(); + } + } + return 0; +} + +static const gchar* webkit_accessible_table_get_column_description(AtkTable* table, gint column) +{ + AtkObject* columnHeader = atk_table_get_column_header(table, column); + if (columnHeader && ATK_IS_TEXT(columnHeader)) + return webkit_accessible_text_get_text(ATK_TEXT(columnHeader), 0, -1); + + return 0; +} + +static const gchar* webkit_accessible_table_get_row_description(AtkTable* table, gint row) +{ + AtkObject* rowHeader = atk_table_get_row_header(table, row); + if (rowHeader && ATK_IS_TEXT(rowHeader)) + return webkit_accessible_text_get_text(ATK_TEXT(rowHeader), 0, -1); + + return 0; +} + +static void atk_table_interface_init(AtkTableIface* iface) +{ + iface->ref_at = webkit_accessible_table_ref_at; + iface->get_index_at = webkit_accessible_table_get_index_at; + iface->get_column_at_index = webkit_accessible_table_get_column_at_index; + iface->get_row_at_index = webkit_accessible_table_get_row_at_index; + iface->get_n_columns = webkit_accessible_table_get_n_columns; + iface->get_n_rows = webkit_accessible_table_get_n_rows; + iface->get_column_extent_at = webkit_accessible_table_get_column_extent_at; + iface->get_row_extent_at = webkit_accessible_table_get_row_extent_at; + iface->get_column_header = webkit_accessible_table_get_column_header; + iface->get_row_header = webkit_accessible_table_get_row_header; + iface->get_caption = webkit_accessible_table_get_caption; + iface->get_column_description = webkit_accessible_table_get_column_description; + iface->get_row_description = webkit_accessible_table_get_row_description; +} + +static AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, gint index) +{ + AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); + if (index < 0 || static_cast<unsigned>(index) >= children.size()) + return 0; + + gint currentLink = -1; + for (unsigned i = 0; i < children.size(); i++) { + AccessibilityObject* coreChild = children.at(i).get(); + if (!coreChild->accessibilityIsIgnored() && coreChild->isLink()) { + currentLink++; + if (index != currentLink) + continue; + + AtkObject* axObject = coreChild->wrapper(); + if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject)) + return 0; + + return atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(axObject)); + } + } + + return 0; +} + +static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext) +{ + AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); + if (!children.size()) + return 0; + + gint linksFound = 0; + for (size_t i = 0; i < children.size(); i++) { + AccessibilityObject* coreChild = children.at(i).get(); + if (!coreChild->accessibilityIsIgnored() && coreChild->isLink()) + linksFound++; + } + + return linksFound; +} + +static gint webkitAccessibleHypertextGetLinkIndex(AtkHypertext* hypertext, gint charIndex) +{ + size_t linksCount = webkitAccessibleHypertextGetNLinks(hypertext); + if (!linksCount) + return -1; + + for (size_t i = 0; i < linksCount; i++) { + AtkHyperlink* hyperlink = ATK_HYPERLINK(webkitAccessibleHypertextGetLink(hypertext, i)); + gint startIndex = atk_hyperlink_get_start_index(hyperlink); + gint endIndex = atk_hyperlink_get_end_index(hyperlink); + + // Check if the char index in the link's offset range + if (startIndex <= charIndex && charIndex < endIndex) + return i; + } + + // Not found if reached + return -1; +} + +static void atkHypertextInterfaceInit(AtkHypertextIface* iface) +{ + iface->get_link = webkitAccessibleHypertextGetLink; + iface->get_n_links = webkitAccessibleHypertextGetNLinks; + iface->get_link_index = webkitAccessibleHypertextGetLinkIndex; +} + +static AtkHyperlink* webkitAccessibleHyperlinkImplGetHyperlink(AtkHyperlinkImpl* hyperlink) +{ + AtkHyperlink* hyperlinkObject = ATK_HYPERLINK(g_object_get_data(G_OBJECT(hyperlink), "hyperlink-object")); + if (!hyperlinkObject) { + hyperlinkObject = ATK_HYPERLINK(webkitAccessibleHyperlinkNew(hyperlink)); + g_object_set_data(G_OBJECT(hyperlink), "hyperlink-object", hyperlinkObject); + } + return hyperlinkObject; +} + +static void atkHyperlinkImplInterfaceInit(AtkHyperlinkImplIface* iface) +{ + iface->get_hyperlink = webkitAccessibleHyperlinkImplGetHyperlink; +} + +static const gchar* documentAttributeValue(AtkDocument* document, const gchar* attribute) +{ + Document* coreDocument = core(document)->document(); + if (!coreDocument) + return 0; + + String value = String(); + if (!g_ascii_strcasecmp(attribute, "DocType") && coreDocument->doctype()) + value = coreDocument->doctype()->name(); + else if (!g_ascii_strcasecmp(attribute, "Encoding")) + value = coreDocument->charset(); + else if (!g_ascii_strcasecmp(attribute, "URI")) + value = coreDocument->documentURI(); + if (!value.isEmpty()) + return returnString(value); + + return 0; +} + +static const gchar* webkit_accessible_document_get_attribute_value(AtkDocument* document, const gchar* attribute) +{ + return documentAttributeValue(document, attribute); +} + +static AtkAttributeSet* webkit_accessible_document_get_attributes(AtkDocument* document) +{ + AtkAttributeSet* attributeSet = 0; + const gchar* attributes [] = {"DocType", "Encoding", "URI"}; + + for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) { + const gchar* value = documentAttributeValue(document, attributes[i]); + if (value) + attributeSet = addAttributeToSet(attributeSet, attributes[i], value); + } + + return attributeSet; +} + +static const gchar* webkit_accessible_document_get_locale(AtkDocument* document) +{ + + // TODO: Should we fall back on lang xml:lang when the following comes up empty? + String language = core(document)->language(); + if (!language.isEmpty()) + return returnString(language); + + return 0; +} + +static void atk_document_interface_init(AtkDocumentIface* iface) +{ + iface->get_document_attribute_value = webkit_accessible_document_get_attribute_value; + iface->get_document_attributes = webkit_accessible_document_get_attributes; + iface->get_document_locale = webkit_accessible_document_get_locale; +} + +static const GInterfaceInfo AtkInterfacesInitFunctions[] = { + {(GInterfaceInitFunc)atk_action_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_selection_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_editable_text_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_text_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_component_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_image_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_table_interface_init, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atkHypertextInterfaceInit, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atkHyperlinkImplInterfaceInit, + (GInterfaceFinalizeFunc) 0, 0}, + {(GInterfaceInitFunc)atk_document_interface_init, + (GInterfaceFinalizeFunc) 0, 0} +}; + +enum WAIType { + WAI_ACTION, + WAI_SELECTION, + WAI_EDITABLE_TEXT, + WAI_TEXT, + WAI_COMPONENT, + WAI_IMAGE, + WAI_TABLE, + WAI_HYPERTEXT, + WAI_HYPERLINK, + WAI_DOCUMENT +}; + +static GType GetAtkInterfaceTypeFromWAIType(WAIType type) +{ + switch (type) { + case WAI_ACTION: + return ATK_TYPE_ACTION; + case WAI_SELECTION: + return ATK_TYPE_SELECTION; + case WAI_EDITABLE_TEXT: + return ATK_TYPE_EDITABLE_TEXT; + case WAI_TEXT: + return ATK_TYPE_TEXT; + case WAI_COMPONENT: + return ATK_TYPE_COMPONENT; + case WAI_IMAGE: + return ATK_TYPE_IMAGE; + case WAI_TABLE: + return ATK_TYPE_TABLE; + case WAI_HYPERTEXT: + return ATK_TYPE_HYPERTEXT; + case WAI_HYPERLINK: + return ATK_TYPE_HYPERLINK_IMPL; + case WAI_DOCUMENT: + return ATK_TYPE_DOCUMENT; + } + + return G_TYPE_INVALID; +} + +static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) +{ + guint16 interfaceMask = 0; + + // Component interface is always supported + interfaceMask |= 1 << WAI_COMPONENT; + + AccessibilityRole role = coreObject->roleValue(); + + // Action + // As the implementation of the AtkAction interface is a very + // basic one (just relays in executing the default action for each + // object, and only supports having one action per object), it is + // better just to implement this interface for every instance of + // the WebKitAccessible class and let WebCore decide what to do. + interfaceMask |= 1 << WAI_ACTION; + + // Hyperlink + if (coreObject->isLink()) + interfaceMask |= 1 << WAI_HYPERLINK; + + // Selection + if (coreObject->isListBox() || coreObject->isMenuList()) + interfaceMask |= 1 << WAI_SELECTION; + + // Text & Editable Text + if (role == StaticTextRole || coreObject->isMenuListOption()) + interfaceMask |= 1 << WAI_TEXT; + else if (coreObject->isAccessibilityRenderObject()) { + if (coreObject->isTextControl()) { + interfaceMask |= 1 << WAI_TEXT; + if (!coreObject->isReadOnly()) + interfaceMask |= 1 << WAI_EDITABLE_TEXT; + } else { + AccessibilityRenderObject* axRenderObject = static_cast<AccessibilityRenderObject*>(coreObject); + RenderObject* renderer = axRenderObject->renderer(); + if (role != TableRole) { + interfaceMask |= 1 << WAI_HYPERTEXT; + if (renderer && renderer->childrenInline()) + interfaceMask |= 1 << WAI_TEXT; + } + + // Add the TEXT interface for list items whose + // first accessible child has a text renderer + if (role == ListItemRole) { + AccessibilityObject::AccessibilityChildrenVector children = axRenderObject->children(); + if (children.size()) { + AccessibilityObject* axRenderChild = children.at(0).get(); + interfaceMask |= getInterfaceMaskFromObject(axRenderChild); + } + } + } + } + + // Image + if (coreObject->isImage()) + interfaceMask |= 1 << WAI_IMAGE; + + // Table + if (role == TableRole) + interfaceMask |= 1 << WAI_TABLE; + + // Document + if (role == WebAreaRole) + interfaceMask |= 1 << WAI_DOCUMENT; + + return interfaceMask; +} + +static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask) +{ +#define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */ + static char name[WAI_TYPE_NAME_LEN + 1]; + + g_sprintf(name, "WAIType%x", interfaceMask); + name[WAI_TYPE_NAME_LEN] = '\0'; + + return name; +} + +static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) +{ + static const GTypeInfo typeInfo = { + sizeof(WebKitAccessibleClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) 0, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessible), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) 0, + 0 /* value table */ + }; + + guint16 interfaceMask = getInterfaceMaskFromObject(coreObject); + const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask); + GType type = g_type_from_name(atkTypeName); + if (type) + return type; + + type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, + atkTypeName, + &typeInfo, GTypeFlags(0)); + for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) { + if (interfaceMask & (1 << i)) + g_type_add_interface_static(type, + GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)), + &AtkInterfacesInitFunctions[i]); + } + + return type; +} + +WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject) +{ + GType type = getAccessibilityTypeFromObject(coreObject); + AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0)); + + atk_object_initialize(object, coreObject); + + return WEBKIT_ACCESSIBLE(object); +} + +AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible) +{ + return accessible->m_object; +} + +void webkit_accessible_detach(WebKitAccessible* accessible) +{ + ASSERT(accessible->m_object); + + // We replace the WebCore AccessibilityObject with a fallback object that + // provides default implementations to avoid repetitive null-checking after + // detachment. + accessible->m_object = fallbackObject(); +} + +AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible) +{ + if (!accessible->m_object) + return 0; + + RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); + if (!focusedObj) + return 0; + + return focusedObj->wrapper(); +} + +AccessibilityObject* objectAndOffsetUnignored(AccessibilityObject* coreObject, int& offset, bool ignoreLinks) +{ + Node* endNode = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); + int endOffset = coreObject->selection().end().computeOffsetInContainerNode(); + // Indication that something bogus has transpired. + offset = -1; + + AccessibilityObject* realObject = coreObject; + if (realObject->accessibilityIsIgnored()) + realObject = realObject->parentObjectUnignored(); + if (!realObject) + return 0; + + if (ignoreLinks && realObject->isLink()) + realObject = realObject->parentObjectUnignored(); + if (!realObject) + return 0; + + Node* node = static_cast<AccessibilityRenderObject*>(realObject)->renderer()->node(); + if (node) { + RefPtr<Range> range = rangeOfContents(node); + if (range->ownerDocument() == node->document()) { + ExceptionCode ec = 0; + range->setEndBefore(endNode, ec); + if (range->boundaryPointsValid()) + offset = range->text().length() + endOffset; + } + } + return realObject; +} + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h new file mode 100644 index 0000000..e8cec08 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * Copyright (C) 2009 Jan Alonzo + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef AccessibilityObjectWrapperAtk_h +#define AccessibilityObjectWrapperAtk_h + +#include <atk/atk.h> + +namespace WebCore { + class AccessibilityObject; +} + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_ACCESSIBLE (webkit_accessible_get_type ()) +#define WEBKIT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessible)) +#define WEBKIT_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessibleClass)) +#define WEBKIT_IS_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_ACCESSIBLE)) +#define WEBKIT_IS_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_ACCESSIBLE)) +#define WEBKIT_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessibleClass)) + +typedef struct _WebKitAccessible WebKitAccessible; +typedef struct _WebKitAccessibleClass WebKitAccessibleClass; + +struct _WebKitAccessible +{ + AtkObject parent; + WebCore::AccessibilityObject* m_object; +}; + +struct _WebKitAccessibleClass +{ + AtkObjectClass parent_class; +}; + +GType webkit_accessible_get_type (void) G_GNUC_CONST; + +WebKitAccessible* webkit_accessible_new (WebCore::AccessibilityObject* core_object); + +WebCore::AccessibilityObject* webkit_accessible_get_accessibility_object (WebKitAccessible* accessible); + +void webkit_accessible_detach (WebKitAccessible* accessible); + +AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible); + +WebCore::AccessibilityObject* objectAndOffsetUnignored(WebCore::AccessibilityObject* coreObject, int& offset, bool ignoreLinks); + +G_END_DECLS + +#endif // AccessibilityObjectWrapperAtk_h diff --git a/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp new file mode 100644 index 0000000..5927430 --- /dev/null +++ b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2010 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitAccessibleHyperlink.h" + +#if HAVE(ACCESSIBILITY) + +#include "AXObjectCache.h" +#include "AccessibilityObject.h" +#include "AccessibilityObjectWrapperAtk.h" +#include "AccessibilityRenderObject.h" +#include "NotImplemented.h" +#include "Position.h" +#include "Range.h" +#include "RenderListMarker.h" +#include "RenderObject.h" +#include "TextIterator.h" + +#include <atk/atk.h> +#include <glib.h> + +using namespace WebCore; + +struct _WebKitAccessibleHyperlinkPrivate { + WebKitAccessible* hyperlinkImpl; +}; + +#define WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkPrivate)) + +enum { + PROP_0, + + PROP_HYPERLINK_IMPL +}; + +static gpointer webkitAccessibleHyperlinkParentClass = 0; + +// Used to provide const char* returns. +static const char* returnString(const String& str) +{ + static CString returnedString; + returnedString = str.utf8(); + return returnedString.data(); +} + +static AccessibilityObject* core(WebKitAccessible* accessible) +{ + if (!accessible || !WEBKIT_IS_ACCESSIBLE(accessible)) + return 0; + + return webkit_accessible_get_accessibility_object(accessible); +} + +static AccessibilityObject* core(WebKitAccessibleHyperlink* link) +{ + if (!link) + return 0; + + return core(link->priv->hyperlinkImpl); +} + +static AccessibilityObject* core(AtkHyperlink* link) +{ + if (!WEBKIT_IS_ACCESSIBLE_HYPERLINK(link)) + return 0; + + return core(WEBKIT_ACCESSIBLE_HYPERLINK(link)); +} + +static AccessibilityObject* core(AtkAction* action) +{ + return core(WEBKIT_ACCESSIBLE_HYPERLINK(action)); +} + + +static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), FALSE); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE); + g_return_val_if_fail(!index, FALSE); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return FALSE; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return FALSE; + + return coreObject->performDefaultAction(); +} + +static gint webkitAccessibleHyperlinkActionGetNActions(AtkAction* action) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + return 1; +} + +static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + // TODO: Need a way to provide/localize action descriptions. + notImplemented(); + return ""; +} + +static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return 0; + + return returnString(coreObject->accessKey().string()); +} + +static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + g_return_val_if_fail(!index, 0); + + if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) + return 0; + + AccessibilityObject* coreObject = core(action); + if (!coreObject) + return 0; + + return returnString(coreObject->actionVerb()); +} + +static void atkActionInterfaceInit(AtkActionIface* iface) +{ + iface->do_action = webkitAccessibleHyperlinkActionDoAction; + iface->get_n_actions = webkitAccessibleHyperlinkActionGetNActions; + iface->get_description = webkitAccessibleHyperlinkActionGetDescription; + iface->get_keybinding = webkitAccessibleHyperlinkActionGetKeybinding; + iface->get_name = webkitAccessibleHyperlinkActionGetName; +} + +static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + // FIXME: Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(!index, 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject || coreObject->url().isNull()) + return 0; + + return g_strdup(returnString(coreObject->url().string())); +} + +static AtkObject* webkitAccessibleHyperlinkGetObject(AtkHyperlink* link, gint index) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + + // FIXME: Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(!index, 0); + + return ATK_OBJECT(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl); +} + +static gint getRangeLengthForObject(AccessibilityObject* obj, Range* range) +{ + // This is going to be the actual length in most of the cases + int baseLength = TextIterator::rangeLength(range); + + // Check whether the current hyperlink belongs to a list item. + // If so, we need to consider the length of the item's marker + AccessibilityObject* parent = obj->parentObjectUnignored(); + if (!parent || !parent->isAccessibilityRenderObject() || !parent->isListItem()) + return baseLength; + + // Even if we don't expose list markers to Assistive + // Technologies, we need to have a way to measure their length + // for those cases when it's needed to take it into account + // separately (as in getAccessibilityObjectForOffset) + AccessibilityObject* markerObj = parent->firstChild(); + if (!markerObj) + return baseLength; + + RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(markerObj)->renderer(); + if (!renderer || !renderer->isListMarker()) + return baseLength; + + RenderListMarker* marker = toRenderListMarker(renderer); + return baseLength + marker->text().length() + marker->suffix().length(); +} + +static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject) + return 0; + + Node* node = coreObject->node(); + if (!node) + return 0; + + RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node->parentNode()), firstPositionInNode(node)); + return getRangeLengthForObject(coreObject, range.get()); +} + +static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + + AccessibilityObject* coreObject = core(link); + if (!coreObject) + return 0; + + Node* node = coreObject->node(); + if (!node) + return 0; + + RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node->parentNode()), lastPositionInNode(node)); + return getRangeLengthForObject(coreObject, range.get()); +} + +static gboolean webkitAccessibleHyperlinkIsValid(AtkHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); + + // Link is valid for the whole object's lifetime + return TRUE; +} + +static gint webkitAccessibleHyperlinkGetNAnchors(AtkHyperlink* link) +{ + // FIXME Do NOT support more than one instance of an AtkObject + // implementing AtkHyperlinkImpl in every instance of AtkHyperLink + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + return 1; +} + +static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink* link) +{ + // Not implemented: this function is deprecated in ATK now + notImplemented(); + return FALSE; +} + +static void webkitAccessibleHyperlinkGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* pspec) +{ + switch (propId) { + case PROP_HYPERLINK_IMPL: + g_value_set_object(value, WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv->hyperlinkImpl); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + } +} + +static void webkitAccessibleHyperlinkSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* pspec) +{ + WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(object)->priv; + + switch (propId) { + case PROP_HYPERLINK_IMPL: + // No need to check and unref previous values of + // priv->hyperlinkImpl as this is a CONSTRUCT ONLY property + priv->hyperlinkImpl = WEBKIT_ACCESSIBLE(g_value_get_object(value)); + g_object_weak_ref(G_OBJECT(priv->hyperlinkImpl), (GWeakNotify)g_object_unref, object); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + } +} + +static void webkitAccessibleHyperlinkFinalize(GObject* object) +{ + G_OBJECT_CLASS(webkitAccessibleHyperlinkParentClass)->finalize(object); +} + +static void webkitAccessibleHyperlinkClassInit(AtkHyperlinkClass* klass) +{ + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + + webkitAccessibleHyperlinkParentClass = g_type_class_peek_parent(klass); + + gobjectClass->finalize = webkitAccessibleHyperlinkFinalize; + gobjectClass->set_property = webkitAccessibleHyperlinkSetProperty; + gobjectClass->get_property = webkitAccessibleHyperlinkGetProperty; + + klass->get_uri = webkitAccessibleHyperlinkGetURI; + klass->get_object = webkitAccessibleHyperlinkGetObject; + klass->get_start_index = webkitAccessibleHyperlinkGetStartIndex; + klass->get_end_index = webkitAccessibleHyperlinkGetEndIndex; + klass->is_valid = webkitAccessibleHyperlinkIsValid; + klass->get_n_anchors = webkitAccessibleHyperlinkGetNAnchors; + klass->is_selected_link = webkitAccessibleHyperlinkIsSelectedLink; + + g_object_class_install_property(gobjectClass, PROP_HYPERLINK_IMPL, + g_param_spec_object("hyperlink-impl", + "Hyperlink implementation", + "The associated WebKitAccessible instance.", + WEBKIT_TYPE_ACCESSIBLE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); + + g_type_class_add_private(gobjectClass, sizeof(WebKitAccessibleHyperlinkPrivate)); +} + +static void webkitAccessibleHyperlinkInit(AtkHyperlink* link) +{ + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv = WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(link); + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = 0; +} + +GType webkitAccessibleHyperlinkGetType() +{ + static volatile gsize typeVolatile = 0; + + if (g_once_init_enter(&typeVolatile)) { + static const GTypeInfo tinfo = { + sizeof(WebKitAccessibleHyperlinkClass), + (GBaseInitFunc) 0, + (GBaseFinalizeFunc) 0, + (GClassInitFunc) webkitAccessibleHyperlinkClassInit, + (GClassFinalizeFunc) 0, + 0, /* class data */ + sizeof(WebKitAccessibleHyperlink), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) webkitAccessibleHyperlinkInit, + 0 /* value table */ + }; + + static const GInterfaceInfo actionInfo = { + (GInterfaceInitFunc)(GInterfaceInitFunc)atkActionInterfaceInit, + (GInterfaceFinalizeFunc) 0, 0 + }; + + GType type = g_type_register_static(ATK_TYPE_HYPERLINK, "WebKitAccessibleHyperlink", &tinfo, GTypeFlags(0)); + g_type_add_interface_static(type, ATK_TYPE_ACTION, &actionInfo); + + g_once_init_leave(&typeVolatile, type); + } + + return typeVolatile; +} + +WebKitAccessibleHyperlink* webkitAccessibleHyperlinkNew(AtkHyperlinkImpl* hyperlinkImpl) +{ + g_return_val_if_fail(ATK_IS_HYPERLINK_IMPL(hyperlinkImpl), 0); + return WEBKIT_ACCESSIBLE_HYPERLINK(g_object_new(WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, "hyperlink-impl", hyperlinkImpl, 0)); +} + +WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(WebKitAccessibleHyperlink* link) +{ + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + return core(link); +} + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h new file mode 100644 index 0000000..9df819d --- /dev/null +++ b/Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef WebKitAccessibleHyperlink_h +#define WebKitAccessibleHyperlink_h + +#include "AccessibilityObjectWrapperAtk.h" + +#include <atk/atk.h> + +namespace WebCore { +class AccessibilityObject; +} + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_ACCESSIBLE_HYPERLINK (webkitAccessibleHyperlinkGetType ()) +#define WEBKIT_ACCESSIBLE_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlink)) +#define WEBKIT_ACCESSIBLE_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkClass)) +#define WEBKIT_IS_ACCESSIBLE_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK)) +#define WEBKIT_IS_ACCESSIBLE_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK)) +#define WEBKIT_ACCESSIBLE_HYPERLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WEBKIT_TYPE_ACCESSIBLE_HYPERLINK, WebKitAccessibleHyperlinkClass)) + +typedef struct _WebKitAccessibleHyperlink WebKitAccessibleHyperlink; +typedef struct _WebKitAccessibleHyperlinkClass WebKitAccessibleHyperlinkClass; +typedef struct _WebKitAccessibleHyperlinkPrivate WebKitAccessibleHyperlinkPrivate; + +struct _WebKitAccessibleHyperlink { + AtkHyperlink parent; + + // private + WebKitAccessibleHyperlinkPrivate *priv; +}; + +struct _WebKitAccessibleHyperlinkClass { + AtkObjectClass parentClass; +}; + +GType webkitAccessibleHyperlinkGetType(void) G_GNUC_CONST; + +WebKitAccessibleHyperlink* webkitAccessibleHyperlinkNew(AtkHyperlinkImpl* hyperlinkImpl); + +WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(WebKitAccessibleHyperlink* link); + +G_END_DECLS + +#endif // WebKitAccessibleHyperlink_h |