2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L.
5 * Copyright (C) 2013 Samsung Electronics
7 * Portions from Mozilla a11y, copyright as follows:
9 * The Original Code is mozilla.org code.
11 * The Initial Developer of the Original Code is
12 * Sun Microsystems, Inc.
13 * Portions created by the Initial Developer are Copyright (C) 2002
14 * the Initial Developer. All Rights Reserved.
16 * This library is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU Library General Public
18 * License as published by the Free Software Foundation; either
19 * version 2 of the License, or (at your option) any later version.
21 * This library is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * Library General Public License for more details.
26 * You should have received a copy of the GNU Library General Public License
27 * along with this library; see the file COPYING.LIB. If not, write to
28 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
29 * Boston, MA 02110-1301, USA.
33 #include "WebKitAccessibleWrapperAtk.h"
35 #if HAVE(ACCESSIBILITY)
37 #include "AXObjectCache.h"
40 #include "FrameView.h"
41 #include "HTMLNames.h"
42 #include "HTMLTableElement.h"
43 #include "HostWindow.h"
44 #include "RenderObject.h"
46 #include "TextIterator.h"
47 #include "VisibleUnits.h"
48 #include "WebKitAccessibleHyperlink.h"
49 #include "WebKitAccessibleInterfaceAction.h"
50 #include "WebKitAccessibleInterfaceComponent.h"
51 #include "WebKitAccessibleInterfaceDocument.h"
52 #include "WebKitAccessibleInterfaceEditableText.h"
53 #include "WebKitAccessibleInterfaceHyperlinkImpl.h"
54 #include "WebKitAccessibleInterfaceHypertext.h"
55 #include "WebKitAccessibleInterfaceImage.h"
56 #include "WebKitAccessibleInterfaceSelection.h"
57 #include "WebKitAccessibleInterfaceTable.h"
58 #include "WebKitAccessibleInterfaceText.h"
59 #include "WebKitAccessibleInterfaceValue.h"
60 #include "WebKitAccessibleUtil.h"
61 #include "htmlediting.h"
62 #include <glib/gprintf.h>
63 #include <wtf/text/CString.h>
69 using namespace WebCore;
71 struct _WebKitAccessiblePrivate {
72 // Cached data for AtkObject.
73 CString accessibleName;
74 CString accessibleDescription;
76 // Cached data for AtkAction.
78 CString actionKeyBinding;
80 // Cached data for AtkDocument.
81 CString documentLocale;
83 CString documentEncoding;
86 // Cached data for AtkImage.
87 CString imageDescription;
90 #define WEBKIT_ACCESSIBLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessiblePrivate))
92 static AccessibilityObject* fallbackObject()
94 // FIXME: An AXObjectCache with a Document is meaningless.
95 static AXObjectCache* fallbackCache = new AXObjectCache(0);
96 static AccessibilityObject* object = 0;
98 // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack
99 object = fallbackCache->getOrCreate(ListBoxOptionRole);
106 static AccessibilityObject* core(WebKitAccessible* accessible)
111 return accessible->m_object;
114 static AccessibilityObject* core(AtkObject* object)
116 if (!WEBKIT_IS_ACCESSIBLE(object))
119 return core(WEBKIT_ACCESSIBLE(object));
122 static const gchar* webkitAccessibleGetName(AtkObject* object)
124 AccessibilityObject* coreObject = core(object);
125 if (!coreObject->isAccessibilityRenderObject())
126 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue());
128 if (coreObject->isFieldset()) {
129 AccessibilityObject* label = coreObject->titleUIElement();
131 AtkObject* atkObject = label->wrapper();
132 if (ATK_IS_TEXT(atkObject))
133 return atk_text_get_text(ATK_TEXT(atkObject), 0, -1);
137 if (coreObject->isControl()) {
138 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
140 AtkObject* atkObject = label->wrapper();
141 if (ATK_IS_TEXT(atkObject))
142 return atk_text_get_text(ATK_TEXT(atkObject), 0, -1);
145 // Try text under the node.
146 String textUnder = coreObject->textUnderElement();
147 if (textUnder.length())
148 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, textUnder);
151 if (coreObject->isImage() || coreObject->isInputImage()) {
152 Node* node = coreObject->node();
153 if (node && node->isHTMLElement()) {
154 // Get the attribute rather than altText String so as not to fall back on title.
155 String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr);
157 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, alt);
161 // Fallback for the webArea object: just return the document's title.
162 if (coreObject->isWebArea()) {
163 Document* document = coreObject->document();
165 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, document->title());
168 // Nothing worked so far, try with the AccessibilityObject's
169 // title() before going ahead with stringValue().
170 String axTitle = accessibilityTitle(coreObject);
171 if (!axTitle.isEmpty())
172 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, axTitle);
174 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue());
177 static const gchar* webkitAccessibleGetDescription(AtkObject* object)
179 AccessibilityObject* coreObject = core(object);
181 if (coreObject->isAccessibilityRenderObject())
182 node = coreObject->node();
183 if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole)
184 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject));
186 // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here.
187 if (coreObject->roleValue() == TableRole) {
188 String summary = toHTMLTableElement(node)->summary();
189 if (!summary.isEmpty())
190 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, summary);
193 // The title attribute should be reliably available as the object's descripton.
194 // We do not want to fall back on other attributes in its absence. See bug 25524.
195 String title = toHTMLElement(node)->title();
196 if (!title.isEmpty())
197 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, title);
199 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject));
202 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
204 if (coreObject->isFieldset()) {
205 AccessibilityObject* label = coreObject->titleUIElement();
207 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
211 if (coreObject->roleValue() == LegendRole) {
212 for (AccessibilityObject* parent = coreObject->parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) {
213 if (parent->isFieldset()) {
214 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, parent->wrapper());
221 if (coreObject->isControl()) {
222 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
224 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
226 AccessibilityObject* control = coreObject->correspondingControlForLabelElement();
228 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper());
232 static gpointer webkitAccessibleParentClass = 0;
234 static bool isRootObject(AccessibilityObject* coreObject)
236 // The root accessible object in WebCore is always an object with
237 // the ScrolledArea role with one child with the WebArea role.
238 if (!coreObject || !coreObject->isScrollView())
241 AccessibilityObject* firstChild = coreObject->firstChild();
242 if (!firstChild || !firstChild->isWebArea())
248 static AtkObject* atkParentOfRootObject(AtkObject* object)
250 AccessibilityObject* coreObject = core(object);
251 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
253 // The top level object claims to not have a parent. This makes it
254 // impossible for assistive technologies to ascend the accessible
255 // hierarchy all the way to the application. (Bug 30489)
256 if (!coreParent && isRootObject(coreObject)) {
257 Document* document = coreObject->document();
262 HostWindow* hostWindow = document->view()->hostWindow();
264 PlatformPageClient scrollView = hostWindow->platformPageClient();
266 GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView);
267 if (scrollViewParent)
268 return gtk_widget_get_accessible(scrollViewParent);
271 #endif // PLATFORM(GTK)
277 return coreParent->wrapper();
280 static AtkObject* webkitAccessibleGetParent(AtkObject* object)
282 // Check first if the parent has been already set.
283 AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->get_parent(object);
284 if (accessibleParent)
285 return accessibleParent;
287 // Parent not set yet, so try to find it in the hierarchy.
288 AccessibilityObject* coreObject = core(object);
292 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
294 if (!coreParent && isRootObject(coreObject))
295 return atkParentOfRootObject(object);
300 // We don't expose table rows to Assistive technologies, but we
301 // need to have them anyway in the hierarchy from WebCore to
302 // properly perform coordinates calculations when requested.
303 if (coreParent->isTableRow() && coreObject->isTableCell())
304 coreParent = coreParent->parentObjectUnignored();
306 return coreParent->wrapper();
309 static gint getNChildrenForTable(AccessibilityObject* coreObject)
311 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
312 size_t tableChildrenCount = tableChildren.size();
313 size_t cellsCount = 0;
315 // Look for the actual index of the cell inside the table.
316 for (unsigned i = 0; i < tableChildrenCount; ++i) {
317 if (tableChildren[i]->isTableRow()) {
318 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children();
319 cellsCount += rowChildren.size();
327 static gint webkitAccessibleGetNChildren(AtkObject* object)
329 AccessibilityObject* coreObject = core(object);
331 // Tables should be treated in a different way because rows should
332 // be bypassed when exposing the accessible hierarchy.
333 if (coreObject->isAccessibilityTable())
334 return getNChildrenForTable(coreObject);
336 return coreObject->children().size();
339 static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index)
341 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
342 size_t tableChildrenCount = tableChildren.size();
343 size_t cellsCount = 0;
345 // Look for the actual index of the cell inside the table.
346 size_t current = static_cast<size_t>(index);
347 for (unsigned i = 0; i < tableChildrenCount; ++i) {
348 if (tableChildren[i]->isTableRow()) {
349 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children();
350 size_t rowChildrenCount = rowChildren.size();
351 if (current < cellsCount + rowChildrenCount)
352 return rowChildren.at(current - cellsCount).get();
353 cellsCount += rowChildrenCount;
354 } else if (cellsCount == current)
355 return tableChildren[i].get();
360 // Shouldn't reach if the child was found.
364 static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index)
369 AccessibilityObject* coreObject = core(object);
370 AccessibilityObject* coreChild = 0;
372 // Tables are special cases because rows should be bypassed, but
373 // still taking their cells into account.
374 if (coreObject->isAccessibilityTable())
375 coreChild = getChildForTable(coreObject, index);
377 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
378 if (static_cast<unsigned>(index) >= children.size())
380 coreChild = children.at(index).get();
386 AtkObject* child = coreChild->wrapper();
387 atk_object_set_parent(child, object);
393 static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject)
395 AccessibilityObject* parent = coreObject->parentObjectUnignored();
399 AccessibilityObject* grandParent = parent->parentObjectUnignored();
403 AccessibilityObject::AccessibilityChildrenVector rows = grandParent->children();
404 size_t rowsCount = rows.size();
405 size_t previousCellsCount = 0;
407 // Look for the actual index of the cell inside the table.
408 for (unsigned i = 0; i < rowsCount; ++i) {
409 if (!rows[i]->isTableRow())
412 AccessibilityObject::AccessibilityChildrenVector cells = rows[i]->children();
413 size_t cellsCount = cells.size();
415 if (rows[i] == parent) {
416 for (unsigned j = 0; j < cellsCount; ++j) {
417 if (cells[j] == coreObject)
418 return previousCellsCount + j;
422 previousCellsCount += cellsCount;
428 static gint webkitAccessibleGetIndexInParent(AtkObject* object)
430 AccessibilityObject* coreObject = core(object);
431 AccessibilityObject* parent = coreObject->parentObjectUnignored();
433 if (!parent && isRootObject(coreObject)) {
434 AtkObject* atkParent = atkParentOfRootObject(object);
438 unsigned count = atk_object_get_n_accessible_children(atkParent);
439 for (unsigned i = 0; i < count; ++i) {
440 AtkObject* child = atk_object_ref_accessible_child(atkParent, i);
441 bool childIsObject = child == object;
442 g_object_unref(child);
448 // Need to calculate the index of the cell in the table, as
449 // rows won't be exposed to assistive technologies.
450 if (parent && parent->isTableRow() && coreObject->isTableCell())
451 return getIndexInParentForCellInRow(coreObject);
456 size_t index = parent->children().find(coreObject);
457 return (index == WTF::notFound) ? -1 : index;
460 static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object)
462 AtkAttributeSet* attributeSet = 0;
464 attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk");
466 attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitEfl");
469 AccessibilityObject* coreObject = core(object);
473 // Hack needed for WebKit2 tests because obtaining an element by its ID
474 // cannot be done from the UIProcess. Assistive technologies have no need
475 // for this information.
476 Node* node = coreObject->node();
477 if (node && node->isElementNode()) {
478 String id = toElement(node)->getIdAttribute().string();
480 attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
483 int headingLevel = coreObject->headingLevel();
485 String value = String::number(headingLevel);
486 attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
489 // Set the 'layout-guess' attribute to help Assistive
490 // Technologies know when an exposed table is not data table.
491 if (coreObject->isAccessibilityTable() && !coreObject->isDataTable())
492 attributeSet = addToAtkAttributeSet(attributeSet, "layout-guess", "true");
494 String placeholder = coreObject->placeholderValue();
495 if (!placeholder.isEmpty())
496 attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());
498 if (coreObject->ariaHasPopup())
499 attributeSet = addToAtkAttributeSet(attributeSet, "aria-haspopup", "true");
504 static AtkRole atkRole(AccessibilityRole role)
508 return ATK_ROLE_UNKNOWN;
510 return ATK_ROLE_PUSH_BUTTON;
511 case ToggleButtonRole:
512 return ATK_ROLE_TOGGLE_BUTTON;
513 case RadioButtonRole:
514 return ATK_ROLE_RADIO_BUTTON;
516 return ATK_ROLE_CHECK_BOX;
518 return ATK_ROLE_SLIDER;
521 return ATK_ROLE_PAGE_TAB_LIST;
524 return ATK_ROLE_ENTRY;
526 return ATK_ROLE_TEXT;
528 return ATK_ROLE_TREE;
530 return ATK_ROLE_MENU_BAR;
531 case MenuListPopupRole:
533 return ATK_ROLE_MENU;
534 case MenuListOptionRole:
536 return ATK_ROLE_MENU_ITEM;
538 // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
539 return ATK_ROLE_UNKNOWN; // Matches Mozilla
541 // return ATK_ROLE_TABLE_ROW_HEADER; // Is this right?
542 return ATK_ROLE_LIST_ITEM; // Matches Mozilla
544 return ATK_ROLE_TOOL_BAR;
545 case BusyIndicatorRole:
546 return ATK_ROLE_PROGRESS_BAR; // Is this right?
547 case ProgressIndicatorRole:
548 // return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp
549 return ATK_ROLE_PROGRESS_BAR;
551 return ATK_ROLE_WINDOW;
552 case PopUpButtonRole:
554 return ATK_ROLE_COMBO_BOX;
556 return ATK_ROLE_SPLIT_PANE;
558 return ATK_ROLE_UNKNOWN;
560 return ATK_ROLE_COLOR_CHOOSER;
562 return ATK_ROLE_LIST;
564 return ATK_ROLE_SCROLL_BAR;
566 return ATK_ROLE_SCROLL_PANE;
567 case GridRole: // Is this right?
569 return ATK_ROLE_TABLE;
570 case ApplicationRole:
571 return ATK_ROLE_APPLICATION;
575 return ATK_ROLE_PANEL;
576 case RowHeaderRole: // Row headers are cells after all.
577 case ColumnHeaderRole: // Column headers are cells after all.
579 return ATK_ROLE_TABLE_CELL;
581 case WebCoreLinkRole:
582 case ImageMapLinkRole:
583 return ATK_ROLE_LINK;
586 return ATK_ROLE_IMAGE;
588 return ATK_ROLE_TEXT;
590 // return ATK_ROLE_HTML_CONTAINER; // Is this right?
591 return ATK_ROLE_DOCUMENT_FRAME;
593 return ATK_ROLE_HEADING;
595 return ATK_ROLE_LIST;
597 case ListBoxOptionRole:
598 return ATK_ROLE_LIST_ITEM;
600 return ATK_ROLE_PARAGRAPH;
603 return ATK_ROLE_LABEL;
605 return ATK_ROLE_SECTION;
607 return ATK_ROLE_FORM;
609 return ATK_ROLE_CANVAS;
610 case HorizontalRuleRole:
611 return ATK_ROLE_SEPARATOR;
613 return ATK_ROLE_SPIN_BUTTON;
615 return ATK_ROLE_PAGE_TAB;
617 return ATK_ROLE_UNKNOWN;
621 static AtkRole webkitAccessibleGetRole(AtkObject* object)
623 AccessibilityObject* coreObject = core(object);
626 return ATK_ROLE_UNKNOWN;
628 // Note: Why doesn't WebCore have a password field for this
629 if (coreObject->isPasswordField())
630 return ATK_ROLE_PASSWORD_TEXT;
632 return atkRole(coreObject->roleValue());
635 static bool isTextWithCaret(AccessibilityObject* coreObject)
637 if (!coreObject || !coreObject->isAccessibilityRenderObject())
640 Document* document = coreObject->document();
644 Frame* frame = document->frame();
648 if (!frame->settings().caretBrowsingEnabled())
651 // Check text objects and paragraphs only.
652 AtkObject* axObject = coreObject->wrapper();
653 AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID;
654 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
657 // Finally, check whether the caret is set in the current object.
658 VisibleSelection selection = coreObject->selection();
659 if (!selection.isCaret())
662 return selectionBelongsToObject(coreObject, selection);
665 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
667 AccessibilityObject* parent = coreObject->parentObject();
668 bool isListBoxOption = parent && parent->isListBox();
670 // Please keep the state list in alphabetical order
671 if (coreObject->isChecked())
672 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
674 // FIXME: isReadOnly does not seem to do the right thing for
675 // controls, so check explicitly for them. In addition, because
676 // isReadOnly is false for listBoxOptions, we need to add one
677 // more check so that we do not present them as being "editable".
678 if ((!coreObject->isReadOnly()
679 || (coreObject->isControl() && coreObject->canSetValueAttribute()))
681 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
683 // FIXME: Put both ENABLED and SENSITIVE together here for now
684 if (coreObject->isEnabled()) {
685 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
686 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
689 if (coreObject->canSetExpandedAttribute())
690 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
692 if (coreObject->isExpanded())
693 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
695 if (coreObject->canSetFocusAttribute())
696 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
698 if (coreObject->isFocused() || isTextWithCaret(coreObject))
699 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
701 if (coreObject->orientation() == AccessibilityOrientationHorizontal)
702 atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);
703 else if (coreObject->orientation() == AccessibilityOrientationVertical)
704 atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL);
706 if (coreObject->isIndeterminate())
707 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
709 if (coreObject->isMultiSelectable())
710 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
712 // TODO: ATK_STATE_OPAQUE
714 if (coreObject->isPressed())
715 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
717 if (coreObject->isRequired())
718 atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED);
720 // TODO: ATK_STATE_SELECTABLE_TEXT
722 if (coreObject->canSetSelectedAttribute()) {
723 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
724 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
725 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on
728 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
731 if (coreObject->isSelected()) {
732 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
733 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
734 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
737 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
740 // FIXME: Group both SHOWING and VISIBLE here for now
741 // Not sure how to handle this in WebKit, see bug
742 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
743 // issues with SHOWING vs VISIBLE.
744 if (!coreObject->isOffScreen()) {
745 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
746 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
749 // Mutually exclusive, so we group these two
750 if (coreObject->roleValue() == TextFieldRole)
751 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
752 else if (coreObject->roleValue() == TextAreaRole)
753 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
755 // TODO: ATK_STATE_SENSITIVE
757 if (coreObject->isVisited())
758 atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
761 static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object)
763 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_state_set(object);
764 AccessibilityObject* coreObject = core(object);
766 if (coreObject == fallbackObject()) {
767 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
771 // Text objects must be focusable.
772 AtkRole role = atk_object_get_role(object);
773 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
774 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
776 setAtkStateSetFromCoreObject(coreObject, stateSet);
780 static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object)
782 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_relation_set(object);
783 AccessibilityObject* coreObject = core(object);
785 setAtkRelationSetFromCoreObject(coreObject, relationSet);
790 static void webkitAccessibleInit(AtkObject* object, gpointer data)
792 if (ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize)
793 ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize(object, data);
795 WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object);
796 accessible->m_object = reinterpret_cast<AccessibilityObject*>(data);
797 accessible->priv = WEBKIT_ACCESSIBLE_GET_PRIVATE(accessible);
800 static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object)
802 if (ATK_IS_DOCUMENT(object)) {
803 AccessibilityObject* coreObject = core(object);
807 // TODO: Should we fall back on lang xml:lang when the following comes up empty?
808 String language = coreObject->language();
809 if (!language.isEmpty())
810 return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language);
812 } else if (ATK_IS_TEXT(object)) {
813 const gchar* locale = 0;
815 AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object));
816 for (AtkAttributeSet* attributes = textAttributes; attributes; attributes = attributes->next) {
817 AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
818 if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) {
819 locale = cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, String::fromUTF8(atkAttribute->value));
823 atk_attribute_set_free(textAttributes);
831 static void webkitAccessibleFinalize(GObject* object)
833 G_OBJECT_CLASS(webkitAccessibleParentClass)->finalize(object);
836 static void webkitAccessibleClassInit(AtkObjectClass* klass)
838 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
840 webkitAccessibleParentClass = g_type_class_peek_parent(klass);
842 gobjectClass->finalize = webkitAccessibleFinalize;
844 klass->initialize = webkitAccessibleInit;
845 klass->get_name = webkitAccessibleGetName;
846 klass->get_description = webkitAccessibleGetDescription;
847 klass->get_parent = webkitAccessibleGetParent;
848 klass->get_n_children = webkitAccessibleGetNChildren;
849 klass->ref_child = webkitAccessibleRefChild;
850 klass->get_role = webkitAccessibleGetRole;
851 klass->ref_state_set = webkitAccessibleRefStateSet;
852 klass->get_index_in_parent = webkitAccessibleGetIndexInParent;
853 klass->get_attributes = webkitAccessibleGetAttributes;
854 klass->ref_relation_set = webkitAccessibleRefRelationSet;
855 klass->get_object_locale = webkitAccessibleGetObjectLocale;
857 g_type_class_add_private(klass, sizeof(WebKitAccessiblePrivate));
861 webkitAccessibleGetType(void)
863 static volatile gsize typeVolatile = 0;
865 if (g_once_init_enter(&typeVolatile)) {
866 static const GTypeInfo tinfo = {
867 sizeof(WebKitAccessibleClass),
869 (GBaseFinalizeFunc) 0,
870 (GClassInitFunc) webkitAccessibleClassInit,
871 (GClassFinalizeFunc) 0,
873 sizeof(WebKitAccessible), /* instance size */
874 0, /* nb preallocs */
875 (GInstanceInitFunc) 0,
879 GType type = g_type_register_static(ATK_TYPE_OBJECT, "WebKitAccessible", &tinfo, GTypeFlags(0));
880 g_once_init_leave(&typeVolatile, type);
886 static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
887 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleActionInterfaceInit), 0, 0},
888 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleSelectionInterfaceInit), 0, 0},
889 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleEditableTextInterfaceInit), 0, 0},
890 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTextInterfaceInit), 0, 0},
891 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0},
892 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0},
893 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0},
894 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0},
895 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0},
896 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0},
897 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleValueInterfaceInit), 0, 0}
914 static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
918 return ATK_TYPE_ACTION;
920 return ATK_TYPE_SELECTION;
921 case WAI_EDITABLE_TEXT:
922 return ATK_TYPE_EDITABLE_TEXT;
924 return ATK_TYPE_TEXT;
926 return ATK_TYPE_COMPONENT;
928 return ATK_TYPE_IMAGE;
930 return ATK_TYPE_TABLE;
932 return ATK_TYPE_HYPERTEXT;
934 return ATK_TYPE_HYPERLINK_IMPL;
936 return ATK_TYPE_DOCUMENT;
938 return ATK_TYPE_VALUE;
941 return G_TYPE_INVALID;
944 static bool roleIsTextType(AccessibilityRole role)
946 return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole || role == ListItemRole;
949 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
951 guint16 interfaceMask = 0;
953 // Component interface is always supported
954 interfaceMask |= 1 << WAI_COMPONENT;
956 AccessibilityRole role = coreObject->roleValue();
959 // As the implementation of the AtkAction interface is a very
960 // basic one (just relays in executing the default action for each
961 // object, and only supports having one action per object), it is
962 // better just to implement this interface for every instance of
963 // the WebKitAccessible class and let WebCore decide what to do.
964 interfaceMask |= 1 << WAI_ACTION;
967 if (coreObject->isListBox() || coreObject->isMenuList())
968 interfaceMask |= 1 << WAI_SELECTION;
970 // Get renderer if available.
971 RenderObject* renderer = 0;
972 if (coreObject->isAccessibilityRenderObject())
973 renderer = coreObject->renderer();
975 // Hyperlink (links and embedded objects).
976 if (coreObject->isLink() || (renderer && renderer->isReplaced()))
977 interfaceMask |= 1 << WAI_HYPERLINK;
979 // Text & Editable Text
980 if (role == StaticTextRole || coreObject->isMenuListOption())
981 interfaceMask |= 1 << WAI_TEXT;
983 if (coreObject->isTextControl()) {
984 interfaceMask |= 1 << WAI_TEXT;
985 if (!coreObject->isReadOnly())
986 interfaceMask |= 1 << WAI_EDITABLE_TEXT;
988 if (role != TableRole) {
989 interfaceMask |= 1 << WAI_HYPERTEXT;
990 if ((renderer && renderer->childrenInline()) || roleIsTextType(role))
991 interfaceMask |= 1 << WAI_TEXT;
994 // Add the TEXT interface for list items whose
995 // first accessible child has a text renderer
996 if (role == ListItemRole) {
997 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
998 if (children.size()) {
999 AccessibilityObject* axRenderChild = children.at(0).get();
1000 interfaceMask |= getInterfaceMaskFromObject(axRenderChild);
1007 if (coreObject->isImage())
1008 interfaceMask |= 1 << WAI_IMAGE;
1011 if (role == TableRole)
1012 interfaceMask |= 1 << WAI_TABLE;
1015 if (role == WebAreaRole)
1016 interfaceMask |= 1 << WAI_DOCUMENT;
1019 if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole)
1020 interfaceMask |= 1 << WAI_VALUE;
1022 return interfaceMask;
1025 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
1027 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
1028 static char name[WAI_TYPE_NAME_LEN + 1];
1030 g_sprintf(name, "WAIType%x", interfaceMask);
1031 name[WAI_TYPE_NAME_LEN] = '\0';
1036 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
1038 static const GTypeInfo typeInfo = {
1039 sizeof(WebKitAccessibleClass),
1041 (GBaseFinalizeFunc) 0,
1043 (GClassFinalizeFunc) 0,
1045 sizeof(WebKitAccessible), /* instance size */
1046 0, /* nb preallocs */
1047 (GInstanceInitFunc) 0,
1051 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
1052 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
1053 GType type = g_type_from_name(atkTypeName);
1057 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, GTypeFlags(0));
1058 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
1059 if (interfaceMask & (1 << i))
1060 g_type_add_interface_static(type,
1061 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1062 &AtkInterfacesInitFunctions[i]);
1068 WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject)
1070 GType type = getAccessibilityTypeFromObject(coreObject);
1071 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0));
1073 atk_object_initialize(object, coreObject);
1075 return WEBKIT_ACCESSIBLE(object);
1078 AccessibilityObject* webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible)
1080 return accessible->m_object;
1083 void webkitAccessibleDetach(WebKitAccessible* accessible)
1085 ASSERT(accessible->m_object);
1087 if (core(accessible)->roleValue() == WebAreaRole)
1088 g_signal_emit_by_name(accessible, "state-change", "defunct", true);
1090 // We replace the WebCore AccessibilityObject with a fallback object that
1091 // provides default implementations to avoid repetitive null-checking after
1093 accessible->m_object = fallbackObject();
1096 AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible* accessible)
1098 if (!accessible->m_object)
1101 RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement();
1105 return focusedObj->wrapper();
1108 AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset)
1110 // Indication that something bogus has transpired.
1113 Document* document = referenceObject->document();
1117 Node* focusedNode = referenceObject->selection().end().containerNode();
1121 RenderObject* focusedRenderer = focusedNode->renderer();
1122 if (!focusedRenderer)
1125 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
1129 // Look for the actual (not ignoring accessibility) selected object.
1130 AccessibilityObject* firstUnignoredParent = focusedObject;
1131 if (firstUnignoredParent->accessibilityIsIgnored())
1132 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1133 if (!firstUnignoredParent)
1136 // Don't ignore links if the offset is being requested for a link.
1137 if (!referenceObject->isLink() && firstUnignoredParent->isLink())
1138 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1139 if (!firstUnignoredParent)
1142 // The reference object must either coincide with the focused
1143 // object being considered, or be a descendant of it.
1144 if (referenceObject->isDescendantOfObject(firstUnignoredParent))
1145 referenceObject = firstUnignoredParent;
1147 Node* startNode = 0;
1148 if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
1149 // We need to use the first child's node of the reference
1150 // object as the start point to calculate the caret offset
1151 // because we want it to be relative to the object of
1152 // reference, not just to the focused object (which could have
1153 // previous siblings which should be taken into account too).
1154 AccessibilityObject* axFirstChild = referenceObject->firstChild();
1156 startNode = axFirstChild->node();
1158 // Getting the Position of a PseudoElement now triggers an assertion.
1159 // This can occur when clicking on empty space in a render block.
1160 if (!startNode || startNode->isPseudoElement())
1161 startNode = firstUnignoredParent->node();
1163 // Check if the node for the first parent object not ignoring
1164 // accessibility is null again before using it. This might happen
1165 // with certain kind of accessibility objects, such as the root
1166 // one (the scroller containing the webArea object).
1170 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
1171 VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();
1173 if (startPosition == endPosition)
1175 else if (!isStartOfLine(endPosition)) {
1176 RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
1177 offset = TextIterator::rangeLength(range.get(), true) + 1;
1179 RefPtr<Range> range = makeRange(startPosition, endPosition);
1180 offset = TextIterator::rangeLength(range.get(), true);
1183 return firstUnignoredParent;
1186 const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value)
1188 WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv;
1189 CString* propertyPtr = 0;
1192 case AtkCachedAccessibleName:
1193 propertyPtr = &priv->accessibleName;
1196 case AtkCachedAccessibleDescription:
1197 propertyPtr = &priv->accessibleDescription;
1200 case AtkCachedActionName:
1201 propertyPtr = &priv->actionName;
1204 case AtkCachedActionKeyBinding:
1205 propertyPtr = &priv->actionKeyBinding;
1208 case AtkCachedDocumentLocale:
1209 propertyPtr = &priv->documentLocale;
1212 case AtkCachedDocumentType:
1213 propertyPtr = &priv->documentType;
1216 case AtkCachedDocumentEncoding:
1217 propertyPtr = &priv->documentEncoding;
1220 case AtkCachedDocumentURI:
1221 propertyPtr = &priv->documentURI;
1224 case AtkCachedImageDescription:
1225 propertyPtr = &priv->imageDescription;
1229 ASSERT_NOT_REACHED();
1232 // Don't invalidate old memory if not stricly needed, since other
1233 // callers might be still holding on to it.
1234 if (*propertyPtr != value.utf8())
1235 *propertyPtr = value.utf8();
1237 return (*propertyPtr).data();
1240 #endif // HAVE(ACCESSIBILITY)