/*
* 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 "AccessibilityTable.h"
#include "AccessibilityTableCell.h"
#include "AccessibilityTableColumn.h"
#include "AccessibilityTableRow.h"
#include "CharacterNames.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 "htmlediting.h"
#include "visible_units.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 AccessibilityObject* core(AtkValue* value)
{
return core(ATK_OBJECT(value));
}
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());
if (coreObject->isControl()) {
AccessibilityObject* label = coreObject->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 = coreObject->textUnderElement();
if (textUnder.length())
return returnString(textUnder);
}
if (coreObject->isImage() || coreObject->isInputImage()) {
Node* node = coreObject->node();
if (node && node->isHTMLElement()) {
// Get the attribute rather than altText String so as not to fall back on title.
String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr);
if (!alt.isEmpty())
return returnString(alt);
}
}
// Fallback for the webArea object: just return the document's title.
if (coreObject->isWebArea()) {
Document* document = coreObject->document();
if (document)
return returnString(document->title());
}
return returnString(coreObject->stringValue());
}
static const gchar* webkit_accessible_get_description(AtkObject* object)
{
AccessibilityObject* coreObject = core(object);
Node* node = 0;
if (coreObject->isAccessibilityRenderObject())
node = coreObject->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 = toHTMLElement(node)->title();
if (!title.isEmpty())
return returnString(title);
return returnString(coreObject->accessibilityDescription());
}
static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
{
if (coreObject->isControl()) {
AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
if (label)
atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
} else {
AccessibilityObject* control = coreObject->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)) {
Document* document = coreObject->document();
if (!document)
return 0;
HostWindow* hostWindow = 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 RowHeaderRole: // Row headers are cells after all.
case ColumnHeaderRole: // Column headers are cells after all.
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;
case ParagraphRole:
return ATK_ROLE_PARAGRAPH;
case LabelRole:
return ATK_ROLE_LABEL;
case DivRole:
return ATK_ROLE_SECTION;
case FormRole:
return ATK_ROLE_FORM;
default:
return ATK_ROLE_UNKNOWN;
}
}
static AtkRole webkit_accessible_get_role(AtkObject* object)
{
AccessibilityObject* coreObject = core(object);
if (!coreObject)
return ATK_ROLE_UNKNOWN;
// Note: Why doesn't WebCore have a password field for this
if (coreObject->isPasswordField())
return ATK_ROLE_PASSWORD_TEXT;
return atkRole(coreObject->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;
AccessibilityObject::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;
AccessibilityObject::AccessibilityChildrenVector selectedItems;
if (coreSelection->isListBox())
coreSelection->selectedChildren(selectedItems);
else if (coreSelection->isMenuList()) {
RenderObject* renderer = 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;
AccessibilityObject::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()) {
AccessibilityObject::AccessibilityChildrenVector selectedItems;
coreSelection->selectedChildren(selectedItems);
return static_cast<gint>(selectedItems.size());
}
if (coreSelection->isMenuList()) {
RenderObject* renderer = 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;
AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
if (coreSelection->isListBox()) {
AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
listBox->setSelectedChildren(children);
AccessibilityObject::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 + 1) - startPtr;
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 {
if (object->isReplaced())
g_string_append_unichar(resultText, objectReplacementCharacter);
// 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 ? renderText->firstTextBox() : 0;
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(AccessibilityObject* coreObject)
{
GString* str = g_string_new(0);
// For text controls, we can get the text line by line.
if (coreObject->isTextControl()) {
unsigned textLength = coreObject->textLength();
int lineNumber = 0;
PlainTextRange range = coreObject->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 = coreObject->doAXStringForRange(range);
g_string_append(str, lineText.utf8().data());
g_string_append(str, "\n");
range = coreObject->doAXRangeForLine(++lineNumber);
}
} else if (coreObject->isAccessibilityRenderObject()) {
GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
g_string_append(str, rendererText.get());
}
return g_string_free(str, FALSE);
}
static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset)
{
AccessibilityObject* coreObject = core(text);
int end = endOffset;
if (endOffset == -1) {
end = coreObject->stringValue().length();
if (!end)
end = coreObject->textUnderElement().length();
}
String ret;
if (coreObject->isTextControl())
ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
else {
ret = coreObject->stringValue();
if (!ret)
ret = coreObject->textUnderElement();
}
if (!ret.length()) {
// This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
ret = String(textForObject(coreObject));
if (!end)
end = ret.length();
}
// Prefix a item number/bullet if needed
if (coreObject->roleValue() == ListItemRole) {
RenderObject* objRenderer = coreObject->renderer();
if (objRenderer && objRenderer->isListItem()) {
String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
if (endOffset == -1)
end += markerText.length();
}
}
ret = ret.substring(startOffset, end - startOffset);
return g_strdup(ret.utf8().data());
}
static 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);
Document* document = coreObject->document();
if (!document)
return 0;
HostWindow* hostWindow = document->view()->hostWindow();
if (!hostWindow)
return 0;
PlatformPageClient webView = hostWindow->platformPageClient();
if (!webView)
return 0;
// Create a string with the layout as it appears on the screen
PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(coreObject));
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);
if (!coreObject->isAccessibilityRenderObject())
return 0;
Document* document = coreObject->document();
if (!document)
return 0;
Node* focusedNode = coreObject->selection().end().deprecatedNode();
if (!focusedNode)
return 0;
RenderObject* focusedRenderer = focusedNode->renderer();
AccessibilityObject* focusedObject = 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;
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
// We need to adjust the offset for the list item marker.
offset += markerText.length();
}
// TODO: Verify this for RTL text.
return offset;
}
static int baselinePositionForRenderObject(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 FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
}
static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
{
if (!object->isAccessibilityRenderObject())
return 0;
RenderObject* renderer = object->renderer();
RenderStyle* style = renderer->style();
AtkAttributeSet* result = 0;
GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
result = 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 * baselinePositionForRenderObject(renderer);
break;
case SUPER:
baselinePosition = baselinePositionForRenderObject(renderer);
break;
case BASELINE:
baselinePosition = 0;
break;
default:
includeRise = false;
break;
}
if (includeRise) {
buffer.set(g_strdup_printf("%i", baselinePosition));
result = 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:
case TASTART:
case TAEND:
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 = 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:
if (Document* document = coreObject->document())
extents = 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, 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 = firstPositionInOrBeforeNode(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 = lastPositionInOrAfterNode(lastLeafNode);
else
nodeRangeEnd = selRange->endPosition();
// Calculate position of the selected range inside the object.
Position parentFirstPosition = firstPositionInOrBeforeNode(node);
RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
// Set values for start and end offsets.
startOffset = TextIterator::rangeLength(rangeInParent.get(), true);
// We need to adjust the offsets for the list item marker.
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
startOffset += markerText.length();
}
RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true);
}
static 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;
AccessibilityObject* coreObject = core(text);
if (!coreObject->isAccessibilityRenderObject())
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;
// We need to adjust the offsets for the list item marker.
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
int markerLength = markerText.length();
if (startOffset < markerLength || endOffset < markerLength)
return FALSE;
startOffset -= markerLength;
endOffset -= markerLength;
}
PlainTextRange textRange(startOffset, endOffset - startOffset);
VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
if (range.isNull())
return FALSE;
coreObject->setSelectedVisiblePositionRange(range);
return TRUE;
}
static gboolean 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);
if (!coreObject->isAccessibilityRenderObject())
return FALSE;
RenderObject* renderer = coreObject->renderer();
if (renderer && renderer->isListItem()) {
String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
int markerLength = markerText.length();
if (offset < markerLength)
return FALSE;
// We need to adjust the offset for list items.
offset -= markerLength;
}
PlainTextRange textRange(offset, 0);
VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
if (range.isNull())
return FALSE;
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));
Document* document = coreObject->document();
if (!document || !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 (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());
Document* document = coreObject->document();
if (!document || !document->frame())
return;
coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos)));
coreObject->setFocused(true);
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 = accTable->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()) {
AtkObject* axObject = coreChild->wrapper();
if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject))
continue;
currentLink++;
if (index != currentLink)
continue;
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()) {
AtkObject* axObject = coreChild->wrapper();
if (axObject && ATK_IS_HYPERLINK_IMPL(axObject))
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 void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue)
{
memset(gValue, 0, sizeof(GValue));
g_value_init(gValue, G_TYPE_DOUBLE);
g_value_set_double(gValue, core(value)->valueForRange());
}
static void webkitAccessibleValueGetMaximumValue(AtkValue* value, GValue* gValue)
{
memset(gValue, 0, sizeof(GValue));
g_value_init(gValue, G_TYPE_DOUBLE);
g_value_set_double(gValue, core(value)->maxValueForRange());
}
static void webkitAccessibleValueGetMinimumValue(AtkValue* value, GValue* gValue)
{
memset(gValue, 0, sizeof(GValue));
g_value_init(gValue, G_TYPE_DOUBLE);
g_value_set_double(gValue, core(value)->minValueForRange());
}
static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GValue* gValue)
{
if (!G_VALUE_HOLDS_DOUBLE(gValue) && !G_VALUE_HOLDS_INT(gValue))
return FALSE;
AccessibilityObject* coreObject = core(value);
if (!coreObject->canSetValueAttribute())
return FALSE;
if (G_VALUE_HOLDS_DOUBLE(gValue))
coreObject->setValue(String::number(g_value_get_double(gValue)));
else
coreObject->setValue(String::number(g_value_get_int(gValue)));
return TRUE;
}
static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gValue)
{
memset(gValue, 0, sizeof(GValue));
g_value_init(gValue, G_TYPE_DOUBLE);
// There's not such a thing in the WAI-ARIA specification, thus return zero.
g_value_set_double(gValue, 0.0);
}
static void atkValueInterfaceInit(AtkValueIface* iface)
{
iface->get_current_value = webkitAccessibleValueGetCurrentValue;
iface->get_maximum_value = webkitAccessibleValueGetMaximumValue;
iface->get_minimum_value = webkitAccessibleValueGetMinimumValue;
iface->set_current_value = webkitAccessibleValueSetCurrentValue;
iface->get_minimum_increment = webkitAccessibleValueGetMinimumIncrement;
}
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},
{(GInterfaceInitFunc)atkValueInterfaceInit,
(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,
WAI_VALUE,
};
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;
case WAI_VALUE:
return ATK_TYPE_VALUE;
}
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;
// Selection
if (coreObject->isListBox() || coreObject->isMenuList())
interfaceMask |= 1 << WAI_SELECTION;
// Get renderer if available.
RenderObject* renderer = 0;
if (coreObject->isAccessibilityRenderObject())
renderer = coreObject->renderer();
// Hyperlink (links and embedded objects).
if (coreObject->isLink() || (renderer && renderer->isReplaced()))
interfaceMask |= 1 << WAI_HYPERLINK;
// Text & Editable Text
if (role == StaticTextRole || coreObject->isMenuListOption())
interfaceMask |= 1 << WAI_TEXT;
else {
if (coreObject->isTextControl()) {
interfaceMask |= 1 << WAI_TEXT;
if (!coreObject->isReadOnly())
interfaceMask |= 1 << WAI_EDITABLE_TEXT;
} else {
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 = coreObject->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;
// Value
if (role == SliderRole)
interfaceMask |= 1 << WAI_VALUE;
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);
if (core(accessible)->roleValue() == WebAreaRole)
g_signal_emit_by_name(accessible, "state-change", "defunct", true);
// 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)
{
// 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 = realObject->node();
if (node) {
VisiblePosition startPosition = VisiblePosition(positionBeforeNode(node), DOWNSTREAM);
VisiblePosition endPosition = realObject->selection().visibleEnd();
if (startPosition == endPosition)
offset = 0;
else if (!isStartOfLine(endPosition)) {
RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
offset = TextIterator::rangeLength(range.get(), true) + 1;
} else {
RefPtr<Range> range = makeRange(startPosition, endPosition);
offset = TextIterator::rangeLength(range.get(), true);
}
}
return realObject;
}
#endif // HAVE(ACCESSIBILITY)