2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Igalia S.L.
4 * Copyright (C) 2009 Jan Alonzo
6 * Portions from Mozilla a11y, copyright as follows:
8 * The Original Code is mozilla.org code.
10 * The Initial Developer of the Original Code is
11 * Sun Microsystems, Inc.
12 * Portions created by the Initial Developer are Copyright (C) 2002
13 * the Initial Developer. All Rights Reserved.
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Library General Public License for more details.
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB. If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
32 #include "AccessibilityObjectWrapperAtk.h"
34 #if HAVE(ACCESSIBILITY)
36 #include "AXObjectCache.h"
37 #include "AccessibilityList.h"
38 #include "AccessibilityListBox.h"
39 #include "AccessibilityListBoxOption.h"
40 #include "AccessibilityTable.h"
41 #include "AccessibilityTableCell.h"
42 #include "AccessibilityTableColumn.h"
43 #include "AccessibilityTableRow.h"
44 #include "CharacterNames.h"
46 #include "DocumentType.h"
49 #include "FrameView.h"
51 #include "HostWindow.h"
52 #include "HTMLNames.h"
53 #include "HTMLTableCaptionElement.h"
54 #include "HTMLTableElement.h"
55 #include "InlineTextBox.h"
57 #include "NotImplemented.h"
58 #include "RenderListItem.h"
59 #include "RenderListMarker.h"
60 #include "RenderText.h"
61 #include "SelectElement.h"
63 #include "TextEncoding.h"
64 #include "TextIterator.h"
65 #include "WebKitAccessibleHyperlink.h"
66 #include "htmlediting.h"
67 #include "visible_units.h"
71 #include <glib/gprintf.h>
72 #include <libgail-util/gail-util.h>
73 #include <pango/pango.h>
74 #include <wtf/text/AtomicString.h>
75 #include <wtf/text/CString.h>
77 using namespace WebCore;
79 static AccessibilityObject* fallbackObject()
81 // FIXME: An AXObjectCache with a Document is meaningless.
82 static AXObjectCache* fallbackCache = new AXObjectCache(0);
83 static AccessibilityObject* object = 0;
85 // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack
86 object = fallbackCache->getOrCreate(ListBoxOptionRole);
93 // Used to provide const char* returns.
94 static const char* returnString(const String& str)
96 static CString returnedString;
97 returnedString = str.utf8();
98 return returnedString.data();
101 static AccessibilityObject* core(WebKitAccessible* accessible)
106 return accessible->m_object;
109 static AccessibilityObject* core(AtkObject* object)
111 if (!WEBKIT_IS_ACCESSIBLE(object))
114 return core(WEBKIT_ACCESSIBLE(object));
117 static AccessibilityObject* core(AtkAction* action)
119 return core(ATK_OBJECT(action));
122 static AccessibilityObject* core(AtkSelection* selection)
124 return core(ATK_OBJECT(selection));
127 static AccessibilityObject* core(AtkText* text)
129 return core(ATK_OBJECT(text));
132 static AccessibilityObject* core(AtkEditableText* text)
134 return core(ATK_OBJECT(text));
137 static AccessibilityObject* core(AtkComponent* component)
139 return core(ATK_OBJECT(component));
142 static AccessibilityObject* core(AtkImage* image)
144 return core(ATK_OBJECT(image));
147 static AccessibilityObject* core(AtkTable* table)
149 return core(ATK_OBJECT(table));
152 static AccessibilityObject* core(AtkHypertext* hypertext)
154 return core(ATK_OBJECT(hypertext));
157 static AccessibilityObject* core(AtkDocument* document)
159 return core(ATK_OBJECT(document));
162 static AccessibilityObject* core(AtkValue* value)
164 return core(ATK_OBJECT(value));
167 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset);
169 static const gchar* webkit_accessible_get_name(AtkObject* object)
171 AccessibilityObject* coreObject = core(object);
172 if (!coreObject->isAccessibilityRenderObject())
173 return returnString(coreObject->stringValue());
175 if (coreObject->isControl()) {
176 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
178 AtkObject* atkObject = label->wrapper();
179 if (ATK_IS_TEXT(atkObject))
180 return webkit_accessible_text_get_text(ATK_TEXT(atkObject), 0, -1);
183 // Try text under the node.
184 String textUnder = coreObject->textUnderElement();
185 if (textUnder.length())
186 return returnString(textUnder);
189 if (coreObject->isImage() || coreObject->isInputImage()) {
190 Node* node = coreObject->node();
191 if (node && node->isHTMLElement()) {
192 // Get the attribute rather than altText String so as not to fall back on title.
193 String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr);
195 return returnString(alt);
199 // Fallback for the webArea object: just return the document's title.
200 if (coreObject->isWebArea()) {
201 Document* document = coreObject->document();
203 return returnString(document->title());
206 return returnString(coreObject->stringValue());
209 static const gchar* webkit_accessible_get_description(AtkObject* object)
211 AccessibilityObject* coreObject = core(object);
213 if (coreObject->isAccessibilityRenderObject())
214 node = coreObject->node();
215 if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole)
216 return returnString(coreObject->accessibilityDescription());
218 // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here.
219 if (coreObject->roleValue() == TableRole) {
220 String summary = static_cast<HTMLTableElement*>(node)->summary();
221 if (!summary.isEmpty())
222 return returnString(summary);
225 // The title attribute should be reliably available as the object's descripton.
226 // We do not want to fall back on other attributes in its absence. See bug 25524.
227 String title = toHTMLElement(node)->title();
228 if (!title.isEmpty())
229 return returnString(title);
231 return returnString(coreObject->accessibilityDescription());
234 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
236 if (coreObject->isControl()) {
237 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
239 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
241 AccessibilityObject* control = coreObject->correspondingControlForLabelElement();
243 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper());
247 static gpointer webkit_accessible_parent_class = 0;
249 static bool isRootObject(AccessibilityObject* coreObject)
251 // The root accessible object in WebCore is always an object with
252 // the ScrolledArea role with one child with the WebArea role.
253 if (!coreObject || !coreObject->isScrollView())
256 AccessibilityObject* firstChild = coreObject->firstChild();
257 if (!firstChild || !firstChild->isWebArea())
263 static AtkObject* atkParentOfRootObject(AtkObject* object)
265 AccessibilityObject* coreObject = core(object);
266 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
268 // The top level object claims to not have a parent. This makes it
269 // impossible for assistive technologies to ascend the accessible
270 // hierarchy all the way to the application. (Bug 30489)
271 if (!coreParent && isRootObject(coreObject)) {
272 Document* document = coreObject->document();
276 HostWindow* hostWindow = document->view()->hostWindow();
278 PlatformPageClient scrollView = hostWindow->platformPageClient();
280 GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView);
281 if (scrollViewParent)
282 return gtk_widget_get_accessible(scrollViewParent);
290 return coreParent->wrapper();
293 static AtkObject* webkit_accessible_get_parent(AtkObject* object)
295 AccessibilityObject* coreObject = core(object);
299 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
301 if (!coreParent && isRootObject(coreObject))
302 return atkParentOfRootObject(object);
307 // GTK doesn't expose table rows to Assistive technologies, but we
308 // need to have them anyway in the hierarchy from WebCore to
309 // properly perform coordinates calculations when requested.
310 if (coreParent->isTableRow() && coreObject->isTableCell())
311 coreParent = coreParent->parentObjectUnignored();
313 return coreParent->wrapper();
316 static gint getNChildrenForTable(AccessibilityObject* coreObject)
318 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
319 size_t tableChildrenCount = tableChildren.size();
320 size_t cellsCount = 0;
322 // Look for the actual index of the cell inside the table.
323 for (unsigned i = 0; i < tableChildrenCount; ++i) {
324 if (tableChildren[i]->isTableRow()) {
325 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children();
326 cellsCount += rowChildren.size();
334 static gint webkit_accessible_get_n_children(AtkObject* object)
336 AccessibilityObject* coreObject = core(object);
338 // Tables should be treated in a different way because rows should
339 // be bypassed for GTK when exposing the accessible hierarchy.
340 if (coreObject->isAccessibilityTable())
341 return getNChildrenForTable(coreObject);
343 return coreObject->children().size();
346 static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index)
348 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
349 size_t tableChildrenCount = tableChildren.size();
350 size_t cellsCount = 0;
352 // Look for the actual index of the cell inside the table.
353 size_t current = static_cast<size_t>(index);
354 for (unsigned i = 0; i < tableChildrenCount; ++i) {
355 if (tableChildren[i]->isTableRow()) {
356 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children();
357 size_t rowChildrenCount = rowChildren.size();
358 if (current < cellsCount + rowChildrenCount)
359 return rowChildren.at(current - cellsCount).get();
360 cellsCount += rowChildrenCount;
361 } else if (cellsCount == current)
362 return tableChildren[i].get();
367 // Shouldn't reach if the child was found.
371 static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index)
376 AccessibilityObject* coreObject = core(object);
377 AccessibilityObject* coreChild = 0;
379 // Tables are special cases in GTK because rows should be
380 // bypassed, but still taking their cells into account.
381 if (coreObject->isAccessibilityTable())
382 coreChild = getChildForTable(coreObject, index);
384 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
385 if (static_cast<unsigned>(index) >= children.size())
387 coreChild = children.at(index).get();
393 AtkObject* child = coreChild->wrapper();
394 atk_object_set_parent(child, object);
400 static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject)
402 AccessibilityObject* parent = coreObject->parentObjectUnignored();
406 AccessibilityObject* grandParent = parent->parentObjectUnignored();
410 AccessibilityObject::AccessibilityChildrenVector rows = grandParent->children();
411 size_t rowsCount = rows.size();
412 size_t previousCellsCount = 0;
414 // Look for the actual index of the cell inside the table.
415 for (unsigned i = 0; i < rowsCount; ++i) {
416 if (!rows[i]->isTableRow())
419 AccessibilityObject::AccessibilityChildrenVector cells = rows[i]->children();
420 size_t cellsCount = cells.size();
422 if (rows[i] == parent) {
423 for (unsigned j = 0; j < cellsCount; ++j) {
424 if (cells[j] == coreObject)
425 return previousCellsCount + j;
429 previousCellsCount += cellsCount;
436 static gint webkit_accessible_get_index_in_parent(AtkObject* object)
438 AccessibilityObject* coreObject = core(object);
439 AccessibilityObject* parent = coreObject->parentObjectUnignored();
441 if (!parent && isRootObject(coreObject)) {
442 AtkObject* atkParent = atkParentOfRootObject(object);
446 unsigned count = atk_object_get_n_accessible_children(atkParent);
447 for (unsigned i = 0; i < count; ++i) {
448 AtkObject* child = atk_object_ref_accessible_child(atkParent, i);
449 bool childIsObject = child == object;
450 g_object_unref(child);
456 // Need to calculate the index of the cell in the table, as
457 // rows won't be exposed to assistive technologies in GTK.
458 if (parent && parent->isTableRow() && coreObject->isTableCell())
459 return getIndexInParentForCellInRow(coreObject);
461 AccessibilityObject::AccessibilityChildrenVector children = parent->children();
462 unsigned count = children.size();
463 for (unsigned i = 0; i < count; ++i) {
464 if (children[i] == coreObject)
471 static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value)
473 AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
474 attribute->name = g_strdup(name);
475 attribute->value = g_strdup(value);
476 attributeSet = g_slist_prepend(attributeSet, attribute);
481 static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object)
483 AtkAttributeSet* attributeSet = 0;
484 attributeSet = addAttributeToSet(attributeSet, "toolkit", "WebKitGtk");
486 AccessibilityObject* coreObject = core(object);
490 int headingLevel = coreObject->headingLevel();
492 String value = String::number(headingLevel);
493 attributeSet = addAttributeToSet(attributeSet, "level", value.utf8().data());
496 // Set the 'layout-guess' attribute to help Assistive
497 // Technologies know when an exposed table is not data table.
498 if (coreObject->isAccessibilityTable() && !coreObject->isDataTable())
499 attributeSet = addAttributeToSet(attributeSet, "layout-guess", "true");
504 static AtkRole atkRole(AccessibilityRole role)
508 return ATK_ROLE_UNKNOWN;
510 return ATK_ROLE_PUSH_BUTTON;
511 case RadioButtonRole:
512 return ATK_ROLE_RADIO_BUTTON;
514 return ATK_ROLE_CHECK_BOX;
516 return ATK_ROLE_SLIDER;
518 return ATK_ROLE_PAGE_TAB_LIST;
521 return ATK_ROLE_ENTRY;
523 return ATK_ROLE_TEXT;
525 return ATK_ROLE_TREE;
527 return ATK_ROLE_MENU_BAR;
528 case MenuListPopupRole:
530 return ATK_ROLE_MENU;
531 case MenuListOptionRole:
533 return ATK_ROLE_MENU_ITEM;
535 //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
536 return ATK_ROLE_UNKNOWN; // Matches Mozilla
538 //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right?
539 return ATK_ROLE_LIST_ITEM; // Matches Mozilla
541 return ATK_ROLE_TOOL_BAR;
542 case BusyIndicatorRole:
543 return ATK_ROLE_PROGRESS_BAR; // Is this right?
544 case ProgressIndicatorRole:
545 //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp
546 return ATK_ROLE_PROGRESS_BAR;
548 return ATK_ROLE_WINDOW;
549 case PopUpButtonRole:
551 return ATK_ROLE_COMBO_BOX;
553 return ATK_ROLE_SPLIT_PANE;
555 return ATK_ROLE_SEPARATOR;
557 return ATK_ROLE_COLOR_CHOOSER;
559 return ATK_ROLE_LIST;
561 return ATK_ROLE_SCROLL_BAR;
563 return ATK_ROLE_SCROLL_PANE;
564 case GridRole: // Is this right?
566 return ATK_ROLE_TABLE;
567 case ApplicationRole:
568 return ATK_ROLE_APPLICATION;
571 return ATK_ROLE_PANEL;
572 case RowHeaderRole: // Row headers are cells after all.
573 case ColumnHeaderRole: // Column headers are cells after all.
575 return ATK_ROLE_TABLE_CELL;
577 case WebCoreLinkRole:
578 case ImageMapLinkRole:
579 return ATK_ROLE_LINK;
582 return ATK_ROLE_IMAGE;
584 return ATK_ROLE_TEXT;
586 //return ATK_ROLE_HTML_CONTAINER; // Is this right?
587 return ATK_ROLE_DOCUMENT_FRAME;
589 return ATK_ROLE_HEADING;
591 return ATK_ROLE_LIST;
593 case ListBoxOptionRole:
594 return ATK_ROLE_LIST_ITEM;
596 return ATK_ROLE_PARAGRAPH;
598 return ATK_ROLE_LABEL;
600 return ATK_ROLE_SECTION;
602 return ATK_ROLE_FORM;
604 return ATK_ROLE_UNKNOWN;
608 static AtkRole webkit_accessible_get_role(AtkObject* object)
610 AccessibilityObject* coreObject = core(object);
613 return ATK_ROLE_UNKNOWN;
615 // Note: Why doesn't WebCore have a password field for this
616 if (coreObject->isPasswordField())
617 return ATK_ROLE_PASSWORD_TEXT;
619 return atkRole(coreObject->roleValue());
622 static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection)
624 if (!coreObject || !coreObject->isAccessibilityRenderObject())
627 if (selection.isNone())
630 RefPtr<Range> range = selection.toNormalizedRange();
634 // We want to check that both the selection intersects the node
635 // AND that the selection is not just "touching" one of the
636 // boundaries for the selected node. We want to check whether the
637 // node is actually inside the region, at least partially.
638 Node* node = coreObject->node();
639 Node* lastDescendant = node->lastDescendant();
640 ExceptionCode ec = 0;
641 return (range->intersectsNode(node, ec)
642 && (range->endContainer() != node || range->endOffset())
643 && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant)));
646 static bool isTextWithCaret(AccessibilityObject* coreObject)
648 if (!coreObject || !coreObject->isAccessibilityRenderObject())
651 Document* document = coreObject->document();
655 Frame* frame = document->frame();
659 Settings* settings = frame->settings();
660 if (!settings || !settings->caretBrowsingEnabled())
663 // Check text objects and paragraphs only.
664 AtkObject* axObject = coreObject->wrapper();
665 AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID;
666 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
669 // Finally, check whether the caret is set in the current object.
670 VisibleSelection selection = coreObject->selection();
671 if (!selection.isCaret())
674 return selectionBelongsToObject(coreObject, selection);
677 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
679 AccessibilityObject* parent = coreObject->parentObject();
680 bool isListBoxOption = parent && parent->isListBox();
682 // Please keep the state list in alphabetical order
683 if (coreObject->isChecked())
684 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
686 // FIXME: isReadOnly does not seem to do the right thing for
687 // controls, so check explicitly for them. In addition, because
688 // isReadOnly is false for listBoxOptions, we need to add one
689 // more check so that we do not present them as being "editable".
690 if ((!coreObject->isReadOnly() ||
691 (coreObject->isControl() && coreObject->canSetValueAttribute())) &&
693 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
695 // FIXME: Put both ENABLED and SENSITIVE together here for now
696 if (coreObject->isEnabled()) {
697 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
698 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
701 if (coreObject->canSetExpandedAttribute())
702 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
704 if (coreObject->isExpanded())
705 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
707 if (coreObject->canSetFocusAttribute())
708 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
710 if (coreObject->isFocused() || isTextWithCaret(coreObject))
711 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
713 // TODO: ATK_STATE_HORIZONTAL
715 if (coreObject->isIndeterminate())
716 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
718 if (coreObject->isMultiSelectable())
719 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
721 // TODO: ATK_STATE_OPAQUE
723 if (coreObject->isPressed())
724 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
726 // TODO: ATK_STATE_SELECTABLE_TEXT
728 if (coreObject->canSetSelectedAttribute()) {
729 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
730 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED}
731 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
734 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
737 if (coreObject->isSelected()) {
738 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
739 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED}
740 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
743 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
746 // FIXME: Group both SHOWING and VISIBLE here for now
747 // Not sure how to handle this in WebKit, see bug
748 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
749 // issues with SHOWING vs VISIBLE within GTK+
750 if (!coreObject->isOffScreen()) {
751 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
752 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
755 // Mutually exclusive, so we group these two
756 if (coreObject->roleValue() == TextFieldRole)
757 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
758 else if (coreObject->roleValue() == TextAreaRole)
759 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
761 // TODO: ATK_STATE_SENSITIVE
763 // TODO: ATK_STATE_VERTICAL
765 if (coreObject->isVisited())
766 atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
769 static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object)
771 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object);
772 AccessibilityObject* coreObject = core(object);
774 if (coreObject == fallbackObject()) {
775 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
779 // Text objects must be focusable.
780 AtkRole role = atk_object_get_role(object);
781 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
782 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
784 setAtkStateSetFromCoreObject(coreObject, stateSet);
788 static AtkRelationSet* webkit_accessible_ref_relation_set(AtkObject* object)
790 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object);
791 AccessibilityObject* coreObject = core(object);
793 setAtkRelationSetFromCoreObject(coreObject, relationSet);
798 static void webkit_accessible_init(AtkObject* object, gpointer data)
800 if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize)
801 ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data);
803 WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data);
806 static void webkit_accessible_finalize(GObject* object)
808 // This is a good time to clear the return buffer.
809 returnString(String());
811 G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object);
814 static void webkit_accessible_class_init(AtkObjectClass* klass)
816 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
818 webkit_accessible_parent_class = g_type_class_peek_parent(klass);
820 gobjectClass->finalize = webkit_accessible_finalize;
822 klass->initialize = webkit_accessible_init;
823 klass->get_name = webkit_accessible_get_name;
824 klass->get_description = webkit_accessible_get_description;
825 klass->get_parent = webkit_accessible_get_parent;
826 klass->get_n_children = webkit_accessible_get_n_children;
827 klass->ref_child = webkit_accessible_ref_child;
828 klass->get_role = webkit_accessible_get_role;
829 klass->ref_state_set = webkit_accessible_ref_state_set;
830 klass->get_index_in_parent = webkit_accessible_get_index_in_parent;
831 klass->get_attributes = webkit_accessible_get_attributes;
832 klass->ref_relation_set = webkit_accessible_ref_relation_set;
836 webkit_accessible_get_type(void)
838 static volatile gsize type_volatile = 0;
840 if (g_once_init_enter(&type_volatile)) {
841 static const GTypeInfo tinfo = {
842 sizeof(WebKitAccessibleClass),
844 (GBaseFinalizeFunc) 0,
845 (GClassInitFunc) webkit_accessible_class_init,
846 (GClassFinalizeFunc) 0,
848 sizeof(WebKitAccessible), /* instance size */
849 0, /* nb preallocs */
850 (GInstanceInitFunc) 0,
854 GType type = g_type_register_static(ATK_TYPE_OBJECT,
855 "WebKitAccessible", &tinfo, GTypeFlags(0));
856 g_once_init_leave(&type_volatile, type);
859 return type_volatile;
862 static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i)
864 g_return_val_if_fail(i == 0, FALSE);
865 return core(action)->performDefaultAction();
868 static gint webkit_accessible_action_get_n_actions(AtkAction* action)
873 static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i)
875 g_return_val_if_fail(i == 0, 0);
876 // TODO: Need a way to provide/localize action descriptions.
881 static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i)
883 g_return_val_if_fail(i == 0, 0);
884 // FIXME: Construct a proper keybinding string.
885 return returnString(core(action)->accessKey().string());
888 static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i)
890 g_return_val_if_fail(i == 0, 0);
891 return returnString(core(action)->actionVerb());
894 static void atk_action_interface_init(AtkActionIface* iface)
896 iface->do_action = webkit_accessible_action_do_action;
897 iface->get_n_actions = webkit_accessible_action_get_n_actions;
898 iface->get_description = webkit_accessible_action_get_description;
899 iface->get_keybinding = webkit_accessible_action_get_keybinding;
900 iface->get_name = webkit_accessible_action_get_name;
903 // Selection (for controls)
905 static AccessibilityObject* listObjectForSelection(AtkSelection* selection)
907 AccessibilityObject* coreSelection = core(selection);
909 // Only list boxes and menu lists supported so far.
910 if (!coreSelection->isListBox() && !coreSelection->isMenuList())
913 // For list boxes the list object is just itself.
914 if (coreSelection->isListBox())
915 return coreSelection;
917 // For menu lists we need to return the first accessible child,
918 // with role MenuListPopupRole, since that's the one holding the list
919 // of items with role MenuListOptionRole.
920 AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
921 if (!children.size())
924 AccessibilityObject* listObject = children.at(0).get();
925 if (!listObject->isMenuListPopup())
931 static AccessibilityObject* optionFromList(AtkSelection* selection, gint i)
933 AccessibilityObject* coreSelection = core(selection);
934 if (!coreSelection || i < 0)
937 // Need to select the proper list object depending on the type.
938 AccessibilityObject* listObject = listObjectForSelection(selection);
942 AccessibilityObject::AccessibilityChildrenVector options = listObject->children();
943 if (i < static_cast<gint>(options.size()))
944 return options.at(i).get();
949 static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i)
951 // i is the ith selection as opposed to the ith child.
953 AccessibilityObject* coreSelection = core(selection);
954 if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || i < 0)
957 AccessibilityObject::AccessibilityChildrenVector selectedItems;
958 if (coreSelection->isListBox())
959 coreSelection->selectedChildren(selectedItems);
960 else if (coreSelection->isMenuList()) {
961 RenderObject* renderer = coreSelection->renderer();
965 SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
966 int selectedIndex = selectNode->selectedIndex();
967 const Vector<Element*> listItems = selectNode->listItems();
969 if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size()))
972 return optionFromList(selection, selectedIndex);
975 if (i < static_cast<gint>(selectedItems.size()))
976 return selectedItems.at(i).get();
981 static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i)
983 AccessibilityObject* coreSelection = core(selection);
987 AccessibilityObject* option = optionFromList(selection, i);
988 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
989 option->setSelected(true);
990 return option->isSelected();
996 static gboolean webkit_accessible_selection_clear_selection(AtkSelection* selection)
998 AccessibilityObject* coreSelection = core(selection);
1002 AccessibilityObject::AccessibilityChildrenVector selectedItems;
1003 if (coreSelection->isListBox() || coreSelection->isMenuList()) {
1004 // Set the list of selected items to an empty list; then verify that it worked.
1005 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
1006 listBox->setSelectedChildren(selectedItems);
1007 listBox->selectedChildren(selectedItems);
1008 return selectedItems.size() == 0;
1013 static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* selection, gint i)
1015 AccessibilityObject* option = optionFromSelection(selection, i);
1017 AtkObject* child = option->wrapper();
1018 g_object_ref(child);
1025 static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection)
1027 AccessibilityObject* coreSelection = core(selection);
1028 if (!coreSelection || !coreSelection->isAccessibilityRenderObject())
1031 if (coreSelection->isListBox()) {
1032 AccessibilityObject::AccessibilityChildrenVector selectedItems;
1033 coreSelection->selectedChildren(selectedItems);
1034 return static_cast<gint>(selectedItems.size());
1037 if (coreSelection->isMenuList()) {
1038 RenderObject* renderer = coreSelection->renderer();
1042 SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
1043 int selectedIndex = selectNode->selectedIndex();
1044 const Vector<Element*> listItems = selectNode->listItems();
1046 return selectedIndex >= 0 && selectedIndex < static_cast<int>(listItems.size());
1052 static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i)
1054 AccessibilityObject* coreSelection = core(selection);
1058 AccessibilityObject* option = optionFromList(selection, i);
1059 if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
1060 return option->isSelected();
1065 static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i)
1067 AccessibilityObject* coreSelection = core(selection);
1071 // TODO: This is only getting called if i == 0. What is preventing the rest?
1072 AccessibilityObject* option = optionFromSelection(selection, i);
1073 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
1074 option->setSelected(false);
1075 return !option->isSelected();
1081 static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection)
1083 AccessibilityObject* coreSelection = core(selection);
1084 if (!coreSelection || !coreSelection->isMultiSelectable())
1087 AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
1088 if (coreSelection->isListBox()) {
1089 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
1090 listBox->setSelectedChildren(children);
1091 AccessibilityObject::AccessibilityChildrenVector selectedItems;
1092 listBox->selectedChildren(selectedItems);
1093 return selectedItems.size() == children.size();
1099 static void atk_selection_interface_init(AtkSelectionIface* iface)
1101 iface->add_selection = webkit_accessible_selection_add_selection;
1102 iface->clear_selection = webkit_accessible_selection_clear_selection;
1103 iface->ref_selection = webkit_accessible_selection_ref_selection;
1104 iface->get_selection_count = webkit_accessible_selection_get_selection_count;
1105 iface->is_child_selected = webkit_accessible_selection_is_child_selected;
1106 iface->remove_selection = webkit_accessible_selection_remove_selection;
1107 iface->select_all_selection = webkit_accessible_selection_select_all_selection;
1112 static gchar* utf8Substr(const gchar* string, gint start, gint end)
1115 glong strLen = g_utf8_strlen(string, -1);
1116 if (start > strLen || end > strLen)
1118 gchar* startPtr = g_utf8_offset_to_pointer(string, start);
1119 gsize lenInBytes = g_utf8_offset_to_pointer(string, end + 1) - startPtr;
1120 gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1));
1121 return g_utf8_strncpy(output, startPtr, end - start + 1);
1124 // This function is not completely general, is it's tied to the
1125 // internals of WebCore's text presentation.
1126 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
1128 CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables);
1129 gchar* utf8String = utf8Substr(stringUTF8.data(), from, to);
1130 if (!g_utf8_validate(utf8String, -1, 0)) {
1134 gsize len = strlen(utf8String);
1135 GString* ret = g_string_new_len(0, len);
1136 gchar* ptr = utf8String;
1138 // WebCore introduces line breaks in the text that do not reflect
1139 // the layout you see on the screen, replace them with spaces
1142 pango_find_paragraph_boundary(ptr, len, &index, &start);
1143 g_string_append_len(ret, ptr, index);
1146 g_string_append_c(ret, ' ');
1152 return g_string_free(ret, FALSE);
1155 gchar* textForRenderer(RenderObject* renderer)
1157 GString* resultText = g_string_new(0);
1160 return g_string_free(resultText, FALSE);
1162 // For RenderBlocks, piece together the text from the RenderText objects they contain.
1163 for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) {
1164 if (object->isBR()) {
1165 g_string_append(resultText, "\n");
1169 RenderText* renderText;
1170 if (object->isText())
1171 renderText = toRenderText(object);
1173 if (object->isReplaced())
1174 g_string_append_unichar(resultText, objectReplacementCharacter);
1176 // We need to check children, if any, to consider when
1177 // current object is not a text object but some of its
1178 // children are, in order not to miss those portions of
1179 // text by not properly handling those situations
1180 if (object->firstChild())
1181 g_string_append(resultText, textForRenderer(object));
1186 InlineTextBox* box = renderText ? renderText->firstTextBox() : 0;
1188 gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end());
1189 g_string_append(resultText, text);
1190 // Newline chars in the source result in separate text boxes, so check
1191 // before adding a newline in the layout. See bug 25415 comment #78.
1192 // If the next sibling is a BR, we'll add the newline when we examine that child.
1193 if (!box->nextOnLineExists() && (!object->nextSibling() || !object->nextSibling()->isBR()))
1194 g_string_append(resultText, "\n");
1195 box = box->nextTextBox();
1199 // Insert the text of the marker for list item in the right place, if present
1200 if (renderer->isListItem()) {
1201 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1202 if (renderer->style()->direction() == LTR)
1203 g_string_prepend(resultText, markerText.utf8().data());
1205 g_string_append(resultText, markerText.utf8().data());
1208 return g_string_free(resultText, FALSE);
1211 gchar* textForObject(AccessibilityObject* coreObject)
1213 GString* str = g_string_new(0);
1215 // For text controls, we can get the text line by line.
1216 if (coreObject->isTextControl()) {
1217 unsigned textLength = coreObject->textLength();
1219 PlainTextRange range = coreObject->doAXRangeForLine(lineNumber);
1220 while (range.length) {
1221 // When a line of text wraps in a text area, the final space is removed.
1222 if (range.start + range.length < textLength)
1224 String lineText = coreObject->doAXStringForRange(range);
1225 g_string_append(str, lineText.utf8().data());
1226 g_string_append(str, "\n");
1227 range = coreObject->doAXRangeForLine(++lineNumber);
1229 } else if (coreObject->isAccessibilityRenderObject()) {
1230 GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
1231 g_string_append(str, rendererText.get());
1234 return g_string_free(str, FALSE);
1237 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset)
1239 AccessibilityObject* coreObject = core(text);
1241 int end = endOffset;
1242 if (endOffset == -1) {
1243 end = coreObject->stringValue().length();
1245 end = coreObject->textUnderElement().length();
1249 if (coreObject->isTextControl())
1250 ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
1252 ret = coreObject->stringValue();
1254 ret = coreObject->textUnderElement();
1257 if (!ret.length()) {
1258 // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
1259 ret = String(textForObject(coreObject));
1264 // Prefix a item number/bullet if needed
1265 if (coreObject->roleValue() == ListItemRole) {
1266 RenderObject* objRenderer = coreObject->renderer();
1267 if (objRenderer && objRenderer->isListItem()) {
1268 String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
1269 ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
1270 if (endOffset == -1)
1271 end += markerText.length();
1275 ret = ret.substring(startOffset, end - startOffset);
1276 return g_strdup(ret.utf8().data());
1279 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
1281 gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util");
1283 return static_cast<GailTextUtil*>(data);
1285 GailTextUtil* gailTextUtil = gail_text_util_new();
1286 gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1));
1287 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref);
1288 return gailTextUtil;
1291 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
1293 AccessibilityObject* coreObject = core(textObject);
1295 Document* document = coreObject->document();
1299 HostWindow* hostWindow = document->view()->hostWindow();
1302 PlatformPageClient webView = hostWindow->platformPageClient();
1306 // Create a string with the layout as it appears on the screen
1307 PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(coreObject));
1308 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref);
1312 static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1314 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset);
1317 static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1319 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset);
1322 static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1324 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset);
1327 static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset)
1333 static gint webkit_accessible_text_get_caret_offset(AtkText* text)
1335 // coreObject is the unignored object whose offset the caller is requesting.
1336 // focusedObject is the object with the caret. It is likely ignored -- unless it's a link.
1337 AccessibilityObject* coreObject = core(text);
1338 if (!coreObject->isAccessibilityRenderObject())
1341 Document* document = coreObject->document();
1345 Node* focusedNode = coreObject->selection().end().deprecatedNode();
1349 RenderObject* focusedRenderer = focusedNode->renderer();
1350 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
1353 // Don't ignore links if the offset is being requested for a link.
1354 if (!objectAndOffsetUnignored(focusedObject, offset, !coreObject->isLink()))
1357 RenderObject* renderer = coreObject->renderer();
1358 if (renderer && renderer->isListItem()) {
1359 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1361 // We need to adjust the offset for the list item marker.
1362 offset += markerText.length();
1365 // TODO: Verify this for RTL text.
1369 static int baselinePositionForRenderObject(RenderObject* renderObject)
1371 // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was
1372 // removed in r70072. The implementation looks incorrect though, because this is not the
1373 // baseline of the underlying RenderObject, but of the AccessibilityRenderObject.
1374 const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
1375 return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
1378 static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
1380 if (!object->isAccessibilityRenderObject())
1383 RenderObject* renderer = object->renderer();
1384 RenderStyle* style = renderer->style();
1386 AtkAttributeSet* result = 0;
1387 GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
1388 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
1390 Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
1391 if (bgColor.isValid()) {
1392 buffer.set(g_strdup_printf("%i,%i,%i",
1393 bgColor.red(), bgColor.green(), bgColor.blue()));
1394 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
1397 Color fgColor = style->visitedDependentColor(CSSPropertyColor);
1398 if (fgColor.isValid()) {
1399 buffer.set(g_strdup_printf("%i,%i,%i",
1400 fgColor.red(), fgColor.green(), fgColor.blue()));
1401 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
1404 int baselinePosition;
1405 bool includeRise = true;
1406 switch (style->verticalAlign()) {
1408 baselinePosition = -1 * baselinePositionForRenderObject(renderer);
1411 baselinePosition = baselinePositionForRenderObject(renderer);
1414 baselinePosition = 0;
1417 includeRise = false;
1422 buffer.set(g_strdup_printf("%i", baselinePosition));
1423 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
1426 int indentation = style->textIndent().calcValue(object->size().width());
1427 if (indentation != undefinedLength) {
1428 buffer.set(g_strdup_printf("%i", indentation));
1429 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
1432 String fontFamilyName = style->font().family().family().string();
1433 if (fontFamilyName.left(8) == "-webkit-")
1434 fontFamilyName = fontFamilyName.substring(8);
1436 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
1438 int fontWeight = -1;
1439 switch (style->font().weight()) {
1467 if (fontWeight > 0) {
1468 buffer.set(g_strdup_printf("%i", fontWeight));
1469 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
1472 switch (style->textAlign()) {
1479 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
1483 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
1487 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
1490 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
1493 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none");
1495 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
1497 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false");
1499 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
1501 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
1506 static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
1508 return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
1511 // Returns an AtkAttributeSet with the elements of a1 which are either
1512 // not present or different in a2. Neither a1 nor a2 should be used
1513 // after calling this function.
1514 static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* a1, AtkAttributeSet* a2)
1519 AtkAttributeSet* i = a1;
1520 AtkAttributeSet* found;
1521 AtkAttributeSet* toDelete = 0;
1524 found = g_slist_find_custom(a2, i->data, (GCompareFunc)compareAttribute);
1526 AtkAttributeSet* t = i->next;
1527 toDelete = g_slist_prepend(toDelete, i->data);
1528 a1 = g_slist_delete_link(a1, i);
1534 atk_attribute_set_free(a2);
1535 atk_attribute_set_free(toDelete);
1539 static guint accessibilityObjectLength(const AccessibilityObject* object)
1541 // Non render objects are not taken into account
1542 if (!object->isAccessibilityRenderObject())
1545 // For those objects implementing the AtkText interface we use the
1546 // well known API to always get the text in a consistent way
1547 AtkObject* atkObj = ATK_OBJECT(object->wrapper());
1548 if (ATK_IS_TEXT(atkObj)) {
1549 GOwnPtr<gchar> text(webkit_accessible_text_get_text(ATK_TEXT(atkObj), 0, -1));
1550 return g_utf8_strlen(text.get(), -1);
1553 // Even if we don't expose list markers to Assistive
1554 // Technologies, we need to have a way to measure their length
1555 // for those cases when it's needed to take it into account
1556 // separately (as in getAccessibilityObjectForOffset)
1557 RenderObject* renderer = object->renderer();
1558 if (renderer && renderer->isListMarker()) {
1559 RenderListMarker* marker = toRenderListMarker(renderer);
1560 return marker->text().length() + marker->suffix().length();
1566 static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
1568 const AccessibilityObject* result;
1569 guint length = accessibilityObjectLength(object);
1570 if (length > offset) {
1572 *endOffset = length;
1580 if (!object->firstChild())
1583 AccessibilityObject* child = object->firstChild();
1584 guint currentOffset = 0;
1585 guint childPosition = 0;
1586 while (child && currentOffset <= offset) {
1587 guint childLength = accessibilityObjectLength(child);
1588 currentOffset = childLength + childPosition;
1589 if (currentOffset > offset) {
1590 gint childStartOffset;
1591 gint childEndOffset;
1592 const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset);
1593 if (childStartOffset >= 0) {
1594 *startOffset = childStartOffset + childPosition;
1595 *endOffset = childEndOffset + childPosition;
1596 result = grandChild;
1599 childPosition += childLength;
1600 child = child->nextSibling();
1606 static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
1608 const AccessibilityObject *child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
1615 AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
1616 AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
1618 return attributeSetDifference(childAttributes, defaultAttributes);
1621 static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
1623 AccessibilityObject* coreObject = core(text);
1624 AtkAttributeSet* result;
1628 *endOffset = atk_text_get_character_count(text);
1633 offset = atk_text_get_caret_offset(text);
1635 result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
1637 if (*startOffset < 0) {
1638 *startOffset = offset;
1639 *endOffset = offset;
1645 static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text)
1647 AccessibilityObject* coreObject = core(text);
1648 if (!coreObject || !coreObject->isAccessibilityRenderObject())
1651 return getAttributeSetForAccessibilityObject(coreObject);
1654 static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
1656 gchar* textContent = webkit_accessible_text_get_text(text, startOffset, -1);
1657 gint textLength = g_utf8_strlen(textContent, -1);
1659 // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps.
1660 gint rangeLength = length;
1661 if (rangeLength < 0 || rangeLength > textLength)
1662 rangeLength = textLength;
1663 AccessibilityObject* coreObject = core(text);
1665 IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
1668 if (Document* document = coreObject->document())
1669 extents = document->view()->contentsToScreen(extents);
1679 static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
1681 IntRect extents = textExtents(text, offset, 1, coords);
1684 *width = extents.width();
1685 *height = extents.height();
1688 static void webkit_accessible_text_get_range_extents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
1690 IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
1691 rect->x = extents.x();
1692 rect->y = extents.y();
1693 rect->width = extents.width();
1694 rect->height = extents.height();
1697 static gint webkit_accessible_text_get_character_count(AtkText* text)
1699 return accessibilityObjectLength(core(text));
1702 static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords)
1704 // FIXME: Use the AtkCoordType
1705 // TODO: Is it correct to ignore range.length?
1707 PlainTextRange range = core(text)->doAXRangeForPosition(pos);
1711 static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
1713 if (!coreObject->isAccessibilityRenderObject())
1716 // Early return if the selection doesn't affect the selected node.
1717 if (!selectionBelongsToObject(coreObject, selection))
1720 // We need to find the exact start and end positions in the
1721 // selected node that intersects the selection, to later on get
1722 // the right values for the effective start and end offsets.
1723 ExceptionCode ec = 0;
1724 Position nodeRangeStart;
1725 Position nodeRangeEnd;
1726 Node* node = coreObject->node();
1727 RefPtr<Range> selRange = selection.toNormalizedRange();
1729 // If the selection affects the selected node and its first
1730 // possible position is also in the selection, we must set
1731 // nodeRangeStart to that position, otherwise to the selection's
1732 // start position (it would belong to the node anyway).
1733 Node* firstLeafNode = node->firstDescendant();
1734 if (selRange->isPointInRange(firstLeafNode, 0, ec))
1735 nodeRangeStart = firstPositionInOrBeforeNode(firstLeafNode);
1737 nodeRangeStart = selRange->startPosition();
1739 // If the selection affects the selected node and its last
1740 // possible position is also in the selection, we must set
1741 // nodeRangeEnd to that position, otherwise to the selection's
1742 // end position (it would belong to the node anyway).
1743 Node* lastLeafNode = node->lastDescendant();
1744 if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec))
1745 nodeRangeEnd = lastPositionInOrAfterNode(lastLeafNode);
1747 nodeRangeEnd = selRange->endPosition();
1749 // Calculate position of the selected range inside the object.
1750 Position parentFirstPosition = firstPositionInOrBeforeNode(node);
1751 RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
1753 // Set values for start and end offsets.
1754 startOffset = TextIterator::rangeLength(rangeInParent.get(), true);
1756 // We need to adjust the offsets for the list item marker.
1757 RenderObject* renderer = coreObject->renderer();
1758 if (renderer && renderer->isListItem()) {
1759 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1760 startOffset += markerText.length();
1763 RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
1764 endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true);
1767 static gint webkit_accessible_text_get_n_selections(AtkText* text)
1769 AccessibilityObject* coreObject = core(text);
1770 VisibleSelection selection = coreObject->selection();
1772 // Only range selections are needed for the purpose of this method
1773 if (!selection.isRange())
1776 // We don't support multiple selections for now, so there's only
1777 // two possibilities
1778 // Also, we don't want to do anything if the selection does not
1779 // belong to the currently selected object. We have to check since
1780 // there's no way to get the selection for a given object, only
1781 // the global one (the API is a bit confusing)
1782 return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
1785 static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
1787 // Default values, unless the contrary is proved
1788 *startOffset = *endOffset = 0;
1790 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1794 // Get the offsets of the selection for the selected object
1795 AccessibilityObject* coreObject = core(text);
1796 VisibleSelection selection = coreObject->selection();
1797 getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
1799 // Return 0 instead of "", as that's the expected result for
1800 // this AtkText method when there's no selection
1801 if (*startOffset == *endOffset)
1804 return webkit_accessible_text_get_text(text, *startOffset, *endOffset);
1807 static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset)
1813 static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
1815 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1819 AccessibilityObject* coreObject = core(text);
1820 if (!coreObject->isAccessibilityRenderObject())
1823 // Consider -1 and out-of-bound values and correct them to length
1824 gint textCount = webkit_accessible_text_get_character_count(text);
1825 if (startOffset < 0 || startOffset > textCount)
1826 startOffset = textCount;
1827 if (endOffset < 0 || endOffset > textCount)
1828 endOffset = textCount;
1830 // We need to adjust the offsets for the list item marker.
1831 RenderObject* renderer = coreObject->renderer();
1832 if (renderer && renderer->isListItem()) {
1833 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1834 int markerLength = markerText.length();
1835 if (startOffset < markerLength || endOffset < markerLength)
1838 startOffset -= markerLength;
1839 endOffset -= markerLength;
1842 PlainTextRange textRange(startOffset, endOffset - startOffset);
1843 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1847 coreObject->setSelectedVisiblePositionRange(range);
1851 static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selectionNum)
1853 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1857 // Do nothing if current selection doesn't belong to the object
1858 if (!webkit_accessible_text_get_n_selections(text))
1861 // Set a new 0-sized selection to the caret position, in order
1862 // to simulate selection removal (GAIL style)
1863 gint caretOffset = webkit_accessible_text_get_caret_offset(text);
1864 return webkit_accessible_text_set_selection(text, selectionNum, caretOffset, caretOffset);
1867 static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset)
1869 AccessibilityObject* coreObject = core(text);
1871 if (!coreObject->isAccessibilityRenderObject())
1874 RenderObject* renderer = coreObject->renderer();
1875 if (renderer && renderer->isListItem()) {
1876 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1877 int markerLength = markerText.length();
1878 if (offset < markerLength)
1881 // We need to adjust the offset for list items.
1882 offset -= markerLength;
1885 PlainTextRange textRange(offset, 0);
1886 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1890 coreObject->setSelectedVisiblePositionRange(range);
1894 static void atk_text_interface_init(AtkTextIface* iface)
1896 iface->get_text = webkit_accessible_text_get_text;
1897 iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset;
1898 iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset;
1899 iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset;
1900 iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset;
1901 iface->get_caret_offset = webkit_accessible_text_get_caret_offset;
1902 iface->get_run_attributes = webkit_accessible_text_get_run_attributes;
1903 iface->get_default_attributes = webkit_accessible_text_get_default_attributes;
1904 iface->get_character_extents = webkit_accessible_text_get_character_extents;
1905 iface->get_range_extents = webkit_accessible_text_get_range_extents;
1906 iface->get_character_count = webkit_accessible_text_get_character_count;
1907 iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point;
1908 iface->get_n_selections = webkit_accessible_text_get_n_selections;
1909 iface->get_selection = webkit_accessible_text_get_selection;
1912 iface->add_selection = webkit_accessible_text_add_selection;
1913 iface->remove_selection = webkit_accessible_text_remove_selection;
1914 iface->set_selection = webkit_accessible_text_set_selection;
1915 iface->set_caret_offset = webkit_accessible_text_set_caret_offset;
1920 static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset)
1926 static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string)
1928 // FIXME: string nullcheck?
1929 core(text)->setValue(String::fromUTF8(string));
1932 static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position)
1934 // FIXME: string nullcheck?
1936 AccessibilityObject* coreObject = core(text);
1937 // FIXME: Not implemented in WebCore
1938 //coreObject->setSelectedTextRange(PlainTextRange(*position, 0));
1939 //coreObject->setSelectedText(String::fromUTF8(string));
1941 Document* document = coreObject->document();
1942 if (!document || !document->frame())
1945 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0)));
1946 coreObject->setFocused(true);
1947 // FIXME: We should set position to the actual inserted text length, which may be less than that requested.
1948 if (document->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0))
1949 *position += length;
1952 static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos)
1957 static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos)
1962 static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos)
1964 AccessibilityObject* coreObject = core(text);
1965 // FIXME: Not implemented in WebCore
1966 //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos));
1967 //coreObject->setSelectedText(String());
1969 Document* document = coreObject->document();
1970 if (!document || !document->frame())
1973 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos)));
1974 coreObject->setFocused(true);
1975 document->frame()->editor()->performDelete();
1978 static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position)
1983 static void atk_editable_text_interface_init(AtkEditableTextIface* iface)
1985 iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes;
1986 iface->set_text_contents = webkit_accessible_editable_text_set_text_contents;
1987 iface->insert_text = webkit_accessible_editable_text_insert_text;
1988 iface->copy_text = webkit_accessible_editable_text_copy_text;
1989 iface->cut_text = webkit_accessible_editable_text_cut_text;
1990 iface->delete_text = webkit_accessible_editable_text_delete_text;
1991 iface->paste_text = webkit_accessible_editable_text_paste_text;
1994 static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0)
1996 FrameView* frameView = coreObject->documentFrameView();
1999 switch (coordType) {
2001 rect = frameView->contentsToWindow(rect);
2004 rect = frameView->contentsToScreen(rect);
2014 *width = rect.width();
2016 *height = rect.height();
2019 static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y)
2023 FrameView* frameView = coreObject->documentFrameView();
2025 switch (coordType) {
2027 return frameView->screenToContents(pos);
2029 return frameView->windowToContents(pos);
2036 static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType)
2038 IntPoint pos = atkToContents(core(component), coordType, x, y);
2040 AccessibilityObject* target = core(component)->accessibilityHitTest(pos);
2043 g_object_ref(target->wrapper());
2044 return target->wrapper();
2047 static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType)
2049 IntRect rect = core(component)->elementRect();
2050 contentsToAtk(core(component), coordType, rect, x, y, width, height);
2053 static gboolean webkit_accessible_component_grab_focus(AtkComponent* component)
2055 core(component)->setFocused(true);
2056 return core(component)->isFocused();
2059 static void atk_component_interface_init(AtkComponentIface* iface)
2061 iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point;
2062 iface->get_extents = webkit_accessible_component_get_extents;
2063 iface->grab_focus = webkit_accessible_component_grab_focus;
2068 static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType)
2070 IntRect rect = core(image)->elementRect();
2071 contentsToAtk(core(image), coordType, rect, x, y);
2074 static const gchar* webkit_accessible_image_get_image_description(AtkImage* image)
2076 return returnString(core(image)->accessibilityDescription());
2079 static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height)
2081 IntSize size = core(image)->size();
2084 *width = size.width();
2086 *height = size.height();
2089 static void atk_image_interface_init(AtkImageIface* iface)
2091 iface->get_image_position = webkit_accessible_image_get_image_position;
2092 iface->get_image_description = webkit_accessible_image_get_image_description;
2093 iface->get_image_size = webkit_accessible_image_get_image_size;
2098 static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column)
2100 AccessibilityObject* accTable = core(table);
2101 if (accTable->isAccessibilityRenderObject())
2102 return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row);
2106 static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable)
2108 // Calculate the cell's index as if we had a traditional Gtk+ table in
2109 // which cells are all direct children of the table, arranged row-first.
2110 AccessibilityObject::AccessibilityChildrenVector allCells;
2111 axTable->cells(allCells);
2112 AccessibilityObject::AccessibilityChildrenVector::iterator position;
2113 position = std::find(allCells.begin(), allCells.end(), axCell);
2114 if (position == allCells.end())
2116 return position - allCells.begin();
2119 static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index)
2121 AccessibilityObject* accTable = core(table);
2122 if (accTable->isAccessibilityRenderObject()) {
2123 AccessibilityObject::AccessibilityChildrenVector allCells;
2124 static_cast<AccessibilityTable*>(accTable)->cells(allCells);
2125 if (0 <= index && static_cast<unsigned>(index) < allCells.size()) {
2126 AccessibilityObject* accCell = allCells.at(index).get();
2127 return static_cast<AccessibilityTableCell*>(accCell);
2133 static AtkObject* webkit_accessible_table_ref_at(AtkTable* table, gint row, gint column)
2135 AccessibilityTableCell* axCell = cell(table, row, column);
2138 return axCell->wrapper();
2141 static gint webkit_accessible_table_get_index_at(AtkTable* table, gint row, gint column)
2143 AccessibilityTableCell* axCell = cell(table, row, column);
2144 AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table));
2145 return cellIndex(axCell, axTable);
2148 static gint webkit_accessible_table_get_column_at_index(AtkTable* table, gint index)
2150 AccessibilityTableCell* axCell = cellAtIndex(table, index);
2152 pair<int, int> columnRange;
2153 axCell->columnIndexRange(columnRange);
2154 return columnRange.first;
2159 static gint webkit_accessible_table_get_row_at_index(AtkTable* table, gint index)
2161 AccessibilityTableCell* axCell = cellAtIndex(table, index);
2163 pair<int, int> rowRange;
2164 axCell->rowIndexRange(rowRange);
2165 return rowRange.first;
2170 static gint webkit_accessible_table_get_n_columns(AtkTable* table)
2172 AccessibilityObject* accTable = core(table);
2173 if (accTable->isAccessibilityRenderObject())
2174 return static_cast<AccessibilityTable*>(accTable)->columnCount();
2178 static gint webkit_accessible_table_get_n_rows(AtkTable* table)
2180 AccessibilityObject* accTable = core(table);
2181 if (accTable->isAccessibilityRenderObject())
2182 return static_cast<AccessibilityTable*>(accTable)->rowCount();
2186 static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column)
2188 AccessibilityTableCell* axCell = cell(table, row, column);
2190 pair<int, int> columnRange;
2191 axCell->columnIndexRange(columnRange);
2192 return columnRange.second;
2197 static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column)
2199 AccessibilityTableCell* axCell = cell(table, row, column);
2201 pair<int, int> rowRange;
2202 axCell->rowIndexRange(rowRange);
2203 return rowRange.second;
2208 static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column)
2210 AccessibilityObject* accTable = core(table);
2211 if (accTable->isAccessibilityRenderObject()) {
2212 AccessibilityObject::AccessibilityChildrenVector allColumnHeaders;
2213 static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders);
2214 unsigned columnCount = allColumnHeaders.size();
2215 for (unsigned k = 0; k < columnCount; ++k) {
2216 pair<int, int> columnRange;
2217 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get());
2218 cell->columnIndexRange(columnRange);
2219 if (columnRange.first <= column && column < columnRange.first + columnRange.second)
2220 return allColumnHeaders[k]->wrapper();
2226 static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row)
2228 AccessibilityObject* accTable = core(table);
2229 if (accTable->isAccessibilityRenderObject()) {
2230 AccessibilityObject::AccessibilityChildrenVector allRowHeaders;
2231 static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders);
2232 unsigned rowCount = allRowHeaders.size();
2233 for (unsigned k = 0; k < rowCount; ++k) {
2234 pair<int, int> rowRange;
2235 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get());
2236 cell->rowIndexRange(rowRange);
2237 if (rowRange.first <= row && row < rowRange.first + rowRange.second)
2238 return allRowHeaders[k]->wrapper();
2244 static AtkObject* webkit_accessible_table_get_caption(AtkTable* table)
2246 AccessibilityObject* accTable = core(table);
2247 if (accTable->isAccessibilityRenderObject()) {
2248 Node* node = accTable->node();
2249 if (node && node->hasTagName(HTMLNames::tableTag)) {
2250 HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(node)->caption();
2252 return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper();
2258 static const gchar* webkit_accessible_table_get_column_description(AtkTable* table, gint column)
2260 AtkObject* columnHeader = atk_table_get_column_header(table, column);
2261 if (columnHeader && ATK_IS_TEXT(columnHeader))
2262 return webkit_accessible_text_get_text(ATK_TEXT(columnHeader), 0, -1);
2267 static const gchar* webkit_accessible_table_get_row_description(AtkTable* table, gint row)
2269 AtkObject* rowHeader = atk_table_get_row_header(table, row);
2270 if (rowHeader && ATK_IS_TEXT(rowHeader))
2271 return webkit_accessible_text_get_text(ATK_TEXT(rowHeader), 0, -1);
2276 static void atk_table_interface_init(AtkTableIface* iface)
2278 iface->ref_at = webkit_accessible_table_ref_at;
2279 iface->get_index_at = webkit_accessible_table_get_index_at;
2280 iface->get_column_at_index = webkit_accessible_table_get_column_at_index;
2281 iface->get_row_at_index = webkit_accessible_table_get_row_at_index;
2282 iface->get_n_columns = webkit_accessible_table_get_n_columns;
2283 iface->get_n_rows = webkit_accessible_table_get_n_rows;
2284 iface->get_column_extent_at = webkit_accessible_table_get_column_extent_at;
2285 iface->get_row_extent_at = webkit_accessible_table_get_row_extent_at;
2286 iface->get_column_header = webkit_accessible_table_get_column_header;
2287 iface->get_row_header = webkit_accessible_table_get_row_header;
2288 iface->get_caption = webkit_accessible_table_get_caption;
2289 iface->get_column_description = webkit_accessible_table_get_column_description;
2290 iface->get_row_description = webkit_accessible_table_get_row_description;
2293 static AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, gint index)
2295 AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children();
2296 if (index < 0 || static_cast<unsigned>(index) >= children.size())
2299 gint currentLink = -1;
2300 for (unsigned i = 0; i < children.size(); i++) {
2301 AccessibilityObject* coreChild = children.at(i).get();
2302 if (!coreChild->accessibilityIsIgnored()) {
2303 AtkObject* axObject = coreChild->wrapper();
2304 if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject))
2308 if (index != currentLink)
2311 return atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(axObject));
2318 static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext)
2320 AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children();
2321 if (!children.size())
2324 gint linksFound = 0;
2325 for (size_t i = 0; i < children.size(); i++) {
2326 AccessibilityObject* coreChild = children.at(i).get();
2327 if (!coreChild->accessibilityIsIgnored()) {
2328 AtkObject* axObject = coreChild->wrapper();
2329 if (axObject && ATK_IS_HYPERLINK_IMPL(axObject))
2337 static gint webkitAccessibleHypertextGetLinkIndex(AtkHypertext* hypertext, gint charIndex)
2339 size_t linksCount = webkitAccessibleHypertextGetNLinks(hypertext);
2343 for (size_t i = 0; i < linksCount; i++) {
2344 AtkHyperlink* hyperlink = ATK_HYPERLINK(webkitAccessibleHypertextGetLink(hypertext, i));
2345 gint startIndex = atk_hyperlink_get_start_index(hyperlink);
2346 gint endIndex = atk_hyperlink_get_end_index(hyperlink);
2348 // Check if the char index in the link's offset range
2349 if (startIndex <= charIndex && charIndex < endIndex)
2353 // Not found if reached
2357 static void atkHypertextInterfaceInit(AtkHypertextIface* iface)
2359 iface->get_link = webkitAccessibleHypertextGetLink;
2360 iface->get_n_links = webkitAccessibleHypertextGetNLinks;
2361 iface->get_link_index = webkitAccessibleHypertextGetLinkIndex;
2364 static AtkHyperlink* webkitAccessibleHyperlinkImplGetHyperlink(AtkHyperlinkImpl* hyperlink)
2366 AtkHyperlink* hyperlinkObject = ATK_HYPERLINK(g_object_get_data(G_OBJECT(hyperlink), "hyperlink-object"));
2367 if (!hyperlinkObject) {
2368 hyperlinkObject = ATK_HYPERLINK(webkitAccessibleHyperlinkNew(hyperlink));
2369 g_object_set_data(G_OBJECT(hyperlink), "hyperlink-object", hyperlinkObject);
2371 return hyperlinkObject;
2374 static void atkHyperlinkImplInterfaceInit(AtkHyperlinkImplIface* iface)
2376 iface->get_hyperlink = webkitAccessibleHyperlinkImplGetHyperlink;
2379 static const gchar* documentAttributeValue(AtkDocument* document, const gchar* attribute)
2381 Document* coreDocument = core(document)->document();
2385 String value = String();
2386 if (!g_ascii_strcasecmp(attribute, "DocType") && coreDocument->doctype())
2387 value = coreDocument->doctype()->name();
2388 else if (!g_ascii_strcasecmp(attribute, "Encoding"))
2389 value = coreDocument->charset();
2390 else if (!g_ascii_strcasecmp(attribute, "URI"))
2391 value = coreDocument->documentURI();
2392 if (!value.isEmpty())
2393 return returnString(value);
2398 static const gchar* webkit_accessible_document_get_attribute_value(AtkDocument* document, const gchar* attribute)
2400 return documentAttributeValue(document, attribute);
2403 static AtkAttributeSet* webkit_accessible_document_get_attributes(AtkDocument* document)
2405 AtkAttributeSet* attributeSet = 0;
2406 const gchar* attributes [] = {"DocType", "Encoding", "URI"};
2408 for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) {
2409 const gchar* value = documentAttributeValue(document, attributes[i]);
2411 attributeSet = addAttributeToSet(attributeSet, attributes[i], value);
2414 return attributeSet;
2417 static const gchar* webkit_accessible_document_get_locale(AtkDocument* document)
2420 // TODO: Should we fall back on lang xml:lang when the following comes up empty?
2421 String language = core(document)->language();
2422 if (!language.isEmpty())
2423 return returnString(language);
2428 static void atk_document_interface_init(AtkDocumentIface* iface)
2430 iface->get_document_attribute_value = webkit_accessible_document_get_attribute_value;
2431 iface->get_document_attributes = webkit_accessible_document_get_attributes;
2432 iface->get_document_locale = webkit_accessible_document_get_locale;
2436 static void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue)
2438 memset(gValue, 0, sizeof(GValue));
2439 g_value_init(gValue, G_TYPE_DOUBLE);
2440 g_value_set_double(gValue, core(value)->valueForRange());
2443 static void webkitAccessibleValueGetMaximumValue(AtkValue* value, GValue* gValue)
2445 memset(gValue, 0, sizeof(GValue));
2446 g_value_init(gValue, G_TYPE_DOUBLE);
2447 g_value_set_double(gValue, core(value)->maxValueForRange());
2450 static void webkitAccessibleValueGetMinimumValue(AtkValue* value, GValue* gValue)
2452 memset(gValue, 0, sizeof(GValue));
2453 g_value_init(gValue, G_TYPE_DOUBLE);
2454 g_value_set_double(gValue, core(value)->minValueForRange());
2457 static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GValue* gValue)
2459 if (!G_VALUE_HOLDS_DOUBLE(gValue) && !G_VALUE_HOLDS_INT(gValue))
2462 AccessibilityObject* coreObject = core(value);
2463 if (!coreObject->canSetValueAttribute())
2466 if (G_VALUE_HOLDS_DOUBLE(gValue))
2467 coreObject->setValue(String::number(g_value_get_double(gValue)));
2469 coreObject->setValue(String::number(g_value_get_int(gValue)));
2474 static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gValue)
2476 memset(gValue, 0, sizeof(GValue));
2477 g_value_init(gValue, G_TYPE_DOUBLE);
2479 // There's not such a thing in the WAI-ARIA specification, thus return zero.
2480 g_value_set_double(gValue, 0.0);
2483 static void atkValueInterfaceInit(AtkValueIface* iface)
2485 iface->get_current_value = webkitAccessibleValueGetCurrentValue;
2486 iface->get_maximum_value = webkitAccessibleValueGetMaximumValue;
2487 iface->get_minimum_value = webkitAccessibleValueGetMinimumValue;
2488 iface->set_current_value = webkitAccessibleValueSetCurrentValue;
2489 iface->get_minimum_increment = webkitAccessibleValueGetMinimumIncrement;
2492 static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
2493 {(GInterfaceInitFunc)atk_action_interface_init,
2494 (GInterfaceFinalizeFunc) 0, 0},
2495 {(GInterfaceInitFunc)atk_selection_interface_init,
2496 (GInterfaceFinalizeFunc) 0, 0},
2497 {(GInterfaceInitFunc)atk_editable_text_interface_init,
2498 (GInterfaceFinalizeFunc) 0, 0},
2499 {(GInterfaceInitFunc)atk_text_interface_init,
2500 (GInterfaceFinalizeFunc) 0, 0},
2501 {(GInterfaceInitFunc)atk_component_interface_init,
2502 (GInterfaceFinalizeFunc) 0, 0},
2503 {(GInterfaceInitFunc)atk_image_interface_init,
2504 (GInterfaceFinalizeFunc) 0, 0},
2505 {(GInterfaceInitFunc)atk_table_interface_init,
2506 (GInterfaceFinalizeFunc) 0, 0},
2507 {(GInterfaceInitFunc)atkHypertextInterfaceInit,
2508 (GInterfaceFinalizeFunc) 0, 0},
2509 {(GInterfaceInitFunc)atkHyperlinkImplInterfaceInit,
2510 (GInterfaceFinalizeFunc) 0, 0},
2511 {(GInterfaceInitFunc)atk_document_interface_init,
2512 (GInterfaceFinalizeFunc) 0, 0},
2513 {(GInterfaceInitFunc)atkValueInterfaceInit,
2514 (GInterfaceFinalizeFunc) 0, 0}
2531 static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
2535 return ATK_TYPE_ACTION;
2537 return ATK_TYPE_SELECTION;
2538 case WAI_EDITABLE_TEXT:
2539 return ATK_TYPE_EDITABLE_TEXT;
2541 return ATK_TYPE_TEXT;
2543 return ATK_TYPE_COMPONENT;
2545 return ATK_TYPE_IMAGE;
2547 return ATK_TYPE_TABLE;
2549 return ATK_TYPE_HYPERTEXT;
2551 return ATK_TYPE_HYPERLINK_IMPL;
2553 return ATK_TYPE_DOCUMENT;
2555 return ATK_TYPE_VALUE;
2558 return G_TYPE_INVALID;
2561 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
2563 guint16 interfaceMask = 0;
2565 // Component interface is always supported
2566 interfaceMask |= 1 << WAI_COMPONENT;
2568 AccessibilityRole role = coreObject->roleValue();
2571 // As the implementation of the AtkAction interface is a very
2572 // basic one (just relays in executing the default action for each
2573 // object, and only supports having one action per object), it is
2574 // better just to implement this interface for every instance of
2575 // the WebKitAccessible class and let WebCore decide what to do.
2576 interfaceMask |= 1 << WAI_ACTION;
2579 if (coreObject->isListBox() || coreObject->isMenuList())
2580 interfaceMask |= 1 << WAI_SELECTION;
2582 // Get renderer if available.
2583 RenderObject* renderer = 0;
2584 if (coreObject->isAccessibilityRenderObject())
2585 renderer = coreObject->renderer();
2587 // Hyperlink (links and embedded objects).
2588 if (coreObject->isLink() || (renderer && renderer->isReplaced()))
2589 interfaceMask |= 1 << WAI_HYPERLINK;
2591 // Text & Editable Text
2592 if (role == StaticTextRole || coreObject->isMenuListOption())
2593 interfaceMask |= 1 << WAI_TEXT;
2595 if (coreObject->isTextControl()) {
2596 interfaceMask |= 1 << WAI_TEXT;
2597 if (!coreObject->isReadOnly())
2598 interfaceMask |= 1 << WAI_EDITABLE_TEXT;
2600 if (role != TableRole) {
2601 interfaceMask |= 1 << WAI_HYPERTEXT;
2602 if (renderer && renderer->childrenInline())
2603 interfaceMask |= 1 << WAI_TEXT;
2606 // Add the TEXT interface for list items whose
2607 // first accessible child has a text renderer
2608 if (role == ListItemRole) {
2609 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
2610 if (children.size()) {
2611 AccessibilityObject* axRenderChild = children.at(0).get();
2612 interfaceMask |= getInterfaceMaskFromObject(axRenderChild);
2619 if (coreObject->isImage())
2620 interfaceMask |= 1 << WAI_IMAGE;
2623 if (role == TableRole)
2624 interfaceMask |= 1 << WAI_TABLE;
2627 if (role == WebAreaRole)
2628 interfaceMask |= 1 << WAI_DOCUMENT;
2631 if (role == SliderRole)
2632 interfaceMask |= 1 << WAI_VALUE;
2634 return interfaceMask;
2637 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
2639 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
2640 static char name[WAI_TYPE_NAME_LEN + 1];
2642 g_sprintf(name, "WAIType%x", interfaceMask);
2643 name[WAI_TYPE_NAME_LEN] = '\0';
2648 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
2650 static const GTypeInfo typeInfo = {
2651 sizeof(WebKitAccessibleClass),
2653 (GBaseFinalizeFunc) 0,
2655 (GClassFinalizeFunc) 0,
2657 sizeof(WebKitAccessible), /* instance size */
2658 0, /* nb preallocs */
2659 (GInstanceInitFunc) 0,
2663 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
2664 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
2665 GType type = g_type_from_name(atkTypeName);
2669 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE,
2671 &typeInfo, GTypeFlags(0));
2672 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
2673 if (interfaceMask & (1 << i))
2674 g_type_add_interface_static(type,
2675 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
2676 &AtkInterfacesInitFunctions[i]);
2682 WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject)
2684 GType type = getAccessibilityTypeFromObject(coreObject);
2685 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0));
2687 atk_object_initialize(object, coreObject);
2689 return WEBKIT_ACCESSIBLE(object);
2692 AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible)
2694 return accessible->m_object;
2697 void webkit_accessible_detach(WebKitAccessible* accessible)
2699 ASSERT(accessible->m_object);
2701 if (core(accessible)->roleValue() == WebAreaRole)
2702 g_signal_emit_by_name(accessible, "state-change", "defunct", true);
2704 // We replace the WebCore AccessibilityObject with a fallback object that
2705 // provides default implementations to avoid repetitive null-checking after
2707 accessible->m_object = fallbackObject();
2710 AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible)
2712 if (!accessible->m_object)
2715 RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement();
2719 return focusedObj->wrapper();
2722 AccessibilityObject* objectAndOffsetUnignored(AccessibilityObject* coreObject, int& offset, bool ignoreLinks)
2724 // Indication that something bogus has transpired.
2727 AccessibilityObject* realObject = coreObject;
2728 if (realObject->accessibilityIsIgnored())
2729 realObject = realObject->parentObjectUnignored();
2733 if (ignoreLinks && realObject->isLink())
2734 realObject = realObject->parentObjectUnignored();
2738 Node* node = realObject->node();
2740 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(node), DOWNSTREAM);
2741 VisiblePosition endPosition = realObject->selection().visibleEnd();
2743 if (startPosition == endPosition)
2745 else if (!isStartOfLine(endPosition)) {
2746 RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
2747 offset = TextIterator::rangeLength(range.get(), true) + 1;
2749 RefPtr<Range> range = makeRange(startPosition, endPosition);
2750 offset = TextIterator::rangeLength(range.get(), true);
2758 #endif // HAVE(ACCESSIBILITY)