summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/accessibility/gtk
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/accessibility/gtk
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_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')
-rw-r--r--Source/WebCore/accessibility/gtk/AXObjectCacheAtk.cpp166
-rw-r--r--Source/WebCore/accessibility/gtk/AccessibilityObjectAtk.cpp136
-rw-r--r--Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp2489
-rw-r--r--Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.h67
-rw-r--r--Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.cpp392
-rw-r--r--Source/WebCore/accessibility/gtk/WebKitAccessibleHyperlink.h63
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