1/*
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
6 *
7 * Portions from Mozilla a11y, copyright as follows:
8 *
9 * The Original Code is mozilla.org code.
10 *
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.
15 *
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.
20 *
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.
25 *
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.
30 */
31
32#include "config.h"
33#include "WebKitAccessibleWrapperAtk.h"
34
35#if HAVE(ACCESSIBILITY)
36
37#include "AXObjectCache.h"
38#include "Document.h"
39#include "Frame.h"
40#include "FrameView.h"
41#include "HTMLNames.h"
42#include "HTMLTableElement.h"
43#include "HostWindow.h"
44#include "RenderObject.h"
45#include "Settings.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>
64
65#if PLATFORM(GTK)
66#include <gtk/gtk.h>
67#endif
68
69using namespace WebCore;
70
71struct _WebKitAccessiblePrivate {
72    // Cached data for AtkObject.
73    CString accessibleName;
74    CString accessibleDescription;
75
76    // Cached data for AtkAction.
77    CString actionName;
78    CString actionKeyBinding;
79
80    // Cached data for AtkDocument.
81    CString documentLocale;
82    CString documentType;
83    CString documentEncoding;
84    CString documentURI;
85
86    // Cached data for AtkImage.
87    CString imageDescription;
88};
89
90#define WEBKIT_ACCESSIBLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessiblePrivate))
91
92static AccessibilityObject* fallbackObject()
93{
94    // FIXME: An AXObjectCache with a Document is meaningless.
95    static AXObjectCache* fallbackCache = new AXObjectCache(0);
96    static AccessibilityObject* object = 0;
97    if (!object) {
98        // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack
99        object = fallbackCache->getOrCreate(ListBoxOptionRole);
100        object->ref();
101    }
102
103    return object;
104}
105
106static AccessibilityObject* core(WebKitAccessible* accessible)
107{
108    if (!accessible)
109        return 0;
110
111    return accessible->m_object;
112}
113
114static AccessibilityObject* core(AtkObject* object)
115{
116    if (!WEBKIT_IS_ACCESSIBLE(object))
117        return 0;
118
119    return core(WEBKIT_ACCESSIBLE(object));
120}
121
122static const gchar* webkitAccessibleGetName(AtkObject* object)
123{
124    AccessibilityObject* coreObject = core(object);
125    if (!coreObject->isAccessibilityRenderObject())
126        return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue());
127
128    if (coreObject->isFieldset()) {
129        AccessibilityObject* label = coreObject->titleUIElement();
130        if (label) {
131            AtkObject* atkObject = label->wrapper();
132            if (ATK_IS_TEXT(atkObject))
133                return atk_text_get_text(ATK_TEXT(atkObject), 0, -1);
134        }
135    }
136
137    if (coreObject->isControl()) {
138        AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
139        if (label) {
140            AtkObject* atkObject = label->wrapper();
141            if (ATK_IS_TEXT(atkObject))
142                return atk_text_get_text(ATK_TEXT(atkObject), 0, -1);
143        }
144
145        // Try text under the node.
146        String textUnder = coreObject->textUnderElement();
147        if (textUnder.length())
148            return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, textUnder);
149    }
150
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);
156            if (!alt.isEmpty())
157                return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, alt);
158        }
159    }
160
161    // Fallback for the webArea object: just return the document's title.
162    if (coreObject->isWebArea()) {
163        Document* document = coreObject->document();
164        if (document)
165            return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, document->title());
166    }
167
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);
173
174    return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue());
175}
176
177static const gchar* webkitAccessibleGetDescription(AtkObject* object)
178{
179    AccessibilityObject* coreObject = core(object);
180    Node* node = 0;
181    if (coreObject->isAccessibilityRenderObject())
182        node = coreObject->node();
183    if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole)
184        return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject));
185
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 = static_cast<HTMLTableElement*>(node)->summary();
189        if (!summary.isEmpty())
190            return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, summary);
191    }
192
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);
198
199    return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject));
200}
201
202static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
203{
204    if (coreObject->isFieldset()) {
205        AccessibilityObject* label = coreObject->titleUIElement();
206        if (label)
207            atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
208        return;
209    }
210
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());
215                break;
216            }
217        }
218        return;
219    }
220
221    if (coreObject->isControl()) {
222        AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
223        if (label)
224            atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
225    } else {
226        AccessibilityObject* control = coreObject->correspondingControlForLabelElement();
227        if (control)
228            atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper());
229    }
230}
231
232static gpointer webkitAccessibleParentClass = 0;
233
234static bool isRootObject(AccessibilityObject* coreObject)
235{
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())
239        return false;
240
241    AccessibilityObject* firstChild = coreObject->firstChild();
242    if (!firstChild || !firstChild->isWebArea())
243        return false;
244
245    return true;
246}
247
248static AtkObject* atkParentOfRootObject(AtkObject* object)
249{
250    AccessibilityObject* coreObject = core(object);
251    AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
252
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();
258        if (!document)
259            return 0;
260
261#if PLATFORM(GTK)
262        HostWindow* hostWindow = document->view()->hostWindow();
263        if (hostWindow) {
264            PlatformPageClient scrollView = hostWindow->platformPageClient();
265            if (scrollView) {
266                GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView);
267                if (scrollViewParent)
268                    return gtk_widget_get_accessible(scrollViewParent);
269            }
270        }
271#endif // PLATFORM(GTK)
272    }
273
274    if (!coreParent)
275        return 0;
276
277    return coreParent->wrapper();
278}
279
280static AtkObject* webkitAccessibleGetParent(AtkObject* object)
281{
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;
286
287    // Parent not set yet, so try to find it in the hierarchy.
288    AccessibilityObject* coreObject = core(object);
289    if (!coreObject)
290        return 0;
291
292    AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
293
294    if (!coreParent && isRootObject(coreObject))
295        return atkParentOfRootObject(object);
296
297    if (!coreParent)
298        return 0;
299
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();
305
306    return coreParent->wrapper();
307}
308
309static gint getNChildrenForTable(AccessibilityObject* coreObject)
310{
311    AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
312    size_t tableChildrenCount = tableChildren.size();
313    size_t cellsCount = 0;
314
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();
320        } else
321            cellsCount++;
322    }
323
324    return cellsCount;
325}
326
327static gint webkitAccessibleGetNChildren(AtkObject* object)
328{
329    AccessibilityObject* coreObject = core(object);
330
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);
335
336    return coreObject->children().size();
337}
338
339static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index)
340{
341    AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children();
342    size_t tableChildrenCount = tableChildren.size();
343    size_t cellsCount = 0;
344
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();
356        else
357            cellsCount++;
358    }
359
360    // Shouldn't reach if the child was found.
361    return 0;
362}
363
364static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index)
365{
366    if (index < 0)
367        return 0;
368
369    AccessibilityObject* coreObject = core(object);
370    AccessibilityObject* coreChild = 0;
371
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);
376    else {
377        AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
378        if (static_cast<unsigned>(index) >= children.size())
379            return 0;
380        coreChild = children.at(index).get();
381    }
382
383    if (!coreChild)
384        return 0;
385
386    AtkObject* child = coreChild->wrapper();
387    atk_object_set_parent(child, object);
388    g_object_ref(child);
389
390    return child;
391}
392
393static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject)
394{
395    AccessibilityObject* parent = coreObject->parentObjectUnignored();
396    if (!parent)
397        return -1;
398
399    AccessibilityObject* grandParent = parent->parentObjectUnignored();
400    if (!grandParent)
401        return -1;
402
403    AccessibilityObject::AccessibilityChildrenVector rows = grandParent->children();
404    size_t rowsCount = rows.size();
405    size_t previousCellsCount = 0;
406
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())
410            continue;
411
412        AccessibilityObject::AccessibilityChildrenVector cells = rows[i]->children();
413        size_t cellsCount = cells.size();
414
415        if (rows[i] == parent) {
416            for (unsigned j = 0; j < cellsCount; ++j) {
417                if (cells[j] == coreObject)
418                    return previousCellsCount + j;
419            }
420        }
421
422        previousCellsCount += cellsCount;
423    }
424
425    return -1;
426}
427
428static gint webkitAccessibleGetIndexInParent(AtkObject* object)
429{
430    AccessibilityObject* coreObject = core(object);
431    AccessibilityObject* parent = coreObject->parentObjectUnignored();
432
433    if (!parent && isRootObject(coreObject)) {
434        AtkObject* atkParent = atkParentOfRootObject(object);
435        if (!atkParent)
436            return -1;
437
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);
443            if (childIsObject)
444                return i;
445        }
446    }
447
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);
452
453    if (!parent)
454        return -1;
455
456    size_t index = parent->children().find(coreObject);
457    return (index == WTF::notFound) ? -1 : index;
458}
459
460static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object)
461{
462    AtkAttributeSet* attributeSet = 0;
463#if PLATFORM(GTK)
464    attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk");
465#elif PLATFORM(EFL)
466    attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitEfl");
467#endif
468
469    AccessibilityObject* coreObject = core(object);
470    if (!coreObject)
471        return attributeSet;
472
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();
479        if (!id.isEmpty())
480            attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
481    }
482
483    int headingLevel = coreObject->headingLevel();
484    if (headingLevel) {
485        String value = String::number(headingLevel);
486        attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
487    }
488
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");
493
494    String placeholder = coreObject->placeholderValue();
495    if (!placeholder.isEmpty())
496        attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());
497
498    return attributeSet;
499}
500
501static AtkRole atkRole(AccessibilityRole role)
502{
503    switch (role) {
504    case UnknownRole:
505        return ATK_ROLE_UNKNOWN;
506    case ButtonRole:
507        return ATK_ROLE_PUSH_BUTTON;
508    case ToggleButtonRole:
509        return ATK_ROLE_TOGGLE_BUTTON;
510    case RadioButtonRole:
511        return ATK_ROLE_RADIO_BUTTON;
512    case CheckBoxRole:
513        return ATK_ROLE_CHECK_BOX;
514    case SliderRole:
515        return ATK_ROLE_SLIDER;
516    case TabGroupRole:
517    case TabListRole:
518        return ATK_ROLE_PAGE_TAB_LIST;
519    case TextFieldRole:
520    case TextAreaRole:
521        return ATK_ROLE_ENTRY;
522    case StaticTextRole:
523        return ATK_ROLE_TEXT;
524    case OutlineRole:
525        return ATK_ROLE_TREE;
526    case MenuBarRole:
527        return ATK_ROLE_MENU_BAR;
528    case MenuListPopupRole:
529    case MenuRole:
530        return ATK_ROLE_MENU;
531    case MenuListOptionRole:
532    case MenuItemRole:
533        return ATK_ROLE_MENU_ITEM;
534    case ColumnRole:
535        // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
536        return ATK_ROLE_UNKNOWN; // Matches Mozilla
537    case RowRole:
538        // return ATK_ROLE_TABLE_ROW_HEADER; // Is this right?
539        return ATK_ROLE_LIST_ITEM; // Matches Mozilla
540    case ToolbarRole:
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;
547    case WindowRole:
548        return ATK_ROLE_WINDOW;
549    case PopUpButtonRole:
550    case ComboBoxRole:
551        return ATK_ROLE_COMBO_BOX;
552    case SplitGroupRole:
553        return ATK_ROLE_SPLIT_PANE;
554    case SplitterRole:
555        return ATK_ROLE_UNKNOWN;
556    case ColorWellRole:
557        return ATK_ROLE_COLOR_CHOOSER;
558    case ListRole:
559        return ATK_ROLE_LIST;
560    case ScrollBarRole:
561        return ATK_ROLE_SCROLL_BAR;
562    case ScrollAreaRole:
563        return ATK_ROLE_SCROLL_PANE;
564    case GridRole: // Is this right?
565    case TableRole:
566        return ATK_ROLE_TABLE;
567    case ApplicationRole:
568        return ATK_ROLE_APPLICATION;
569    case GroupRole:
570    case RadioGroupRole:
571    case TabPanelRole:
572        return ATK_ROLE_PANEL;
573    case RowHeaderRole: // Row headers are cells after all.
574    case ColumnHeaderRole: // Column headers are cells after all.
575    case CellRole:
576        return ATK_ROLE_TABLE_CELL;
577    case LinkRole:
578    case WebCoreLinkRole:
579    case ImageMapLinkRole:
580        return ATK_ROLE_LINK;
581    case ImageMapRole:
582    case ImageRole:
583        return ATK_ROLE_IMAGE;
584    case ListMarkerRole:
585        return ATK_ROLE_TEXT;
586    case WebAreaRole:
587        // return ATK_ROLE_HTML_CONTAINER; // Is this right?
588        return ATK_ROLE_DOCUMENT_FRAME;
589    case HeadingRole:
590        return ATK_ROLE_HEADING;
591    case ListBoxRole:
592        return ATK_ROLE_LIST;
593    case ListItemRole:
594    case ListBoxOptionRole:
595        return ATK_ROLE_LIST_ITEM;
596    case ParagraphRole:
597        return ATK_ROLE_PARAGRAPH;
598    case LabelRole:
599    case LegendRole:
600        return ATK_ROLE_LABEL;
601    case DivRole:
602        return ATK_ROLE_SECTION;
603    case FormRole:
604        return ATK_ROLE_FORM;
605    case CanvasRole:
606        return ATK_ROLE_CANVAS;
607    case HorizontalRuleRole:
608        return ATK_ROLE_SEPARATOR;
609    case SpinButtonRole:
610        return ATK_ROLE_SPIN_BUTTON;
611    case TabRole:
612        return ATK_ROLE_PAGE_TAB;
613    default:
614        return ATK_ROLE_UNKNOWN;
615    }
616}
617
618static AtkRole webkitAccessibleGetRole(AtkObject* object)
619{
620    AccessibilityObject* coreObject = core(object);
621
622    if (!coreObject)
623        return ATK_ROLE_UNKNOWN;
624
625    // Note: Why doesn't WebCore have a password field for this
626    if (coreObject->isPasswordField())
627        return ATK_ROLE_PASSWORD_TEXT;
628
629    return atkRole(coreObject->roleValue());
630}
631
632static bool isTextWithCaret(AccessibilityObject* coreObject)
633{
634    if (!coreObject || !coreObject->isAccessibilityRenderObject())
635        return false;
636
637    Document* document = coreObject->document();
638    if (!document)
639        return false;
640
641    Frame* frame = document->frame();
642    if (!frame)
643        return false;
644
645    Settings* settings = frame->settings();
646    if (!settings || !settings->caretBrowsingEnabled())
647        return false;
648
649    // Check text objects and paragraphs only.
650    AtkObject* axObject = coreObject->wrapper();
651    AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID;
652    if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
653        return false;
654
655    // Finally, check whether the caret is set in the current object.
656    VisibleSelection selection = coreObject->selection();
657    if (!selection.isCaret())
658        return false;
659
660    return selectionBelongsToObject(coreObject, selection);
661}
662
663static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
664{
665    AccessibilityObject* parent = coreObject->parentObject();
666    bool isListBoxOption = parent && parent->isListBox();
667
668    // Please keep the state list in alphabetical order
669    if (coreObject->isChecked())
670        atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
671
672    // FIXME: isReadOnly does not seem to do the right thing for
673    // controls, so check explicitly for them. In addition, because
674    // isReadOnly is false for listBoxOptions, we need to add one
675    // more check so that we do not present them as being "editable".
676    if ((!coreObject->isReadOnly()
677        || (coreObject->isControl() && coreObject->canSetValueAttribute()))
678        && !isListBoxOption)
679        atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
680
681    // FIXME: Put both ENABLED and SENSITIVE together here for now
682    if (coreObject->isEnabled()) {
683        atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
684        atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
685    }
686
687    if (coreObject->canSetExpandedAttribute())
688        atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
689
690    if (coreObject->isExpanded())
691        atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
692
693    if (coreObject->canSetFocusAttribute())
694        atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
695
696    if (coreObject->isFocused() || isTextWithCaret(coreObject))
697        atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
698
699    if (coreObject->orientation() == AccessibilityOrientationHorizontal)
700        atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);
701    else if (coreObject->orientation() == AccessibilityOrientationVertical)
702        atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL);
703
704    if (coreObject->isIndeterminate())
705        atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
706
707    if (coreObject->isMultiSelectable())
708        atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
709
710    // TODO: ATK_STATE_OPAQUE
711
712    if (coreObject->isPressed())
713        atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
714
715    // TODO: ATK_STATE_SELECTABLE_TEXT
716
717    if (coreObject->canSetSelectedAttribute()) {
718        atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
719        // Items in focusable lists have both STATE_SELECT{ABLE,ED}
720        // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on
721        // the former.
722        if (isListBoxOption)
723            atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
724    }
725
726    if (coreObject->isSelected()) {
727        atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
728        // Items in focusable lists have both STATE_SELECT{ABLE,ED}
729        // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
730        // former.
731        if (isListBoxOption)
732            atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
733    }
734
735    // FIXME: Group both SHOWING and VISIBLE here for now
736    // Not sure how to handle this in WebKit, see bug
737    // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
738    // issues with SHOWING vs VISIBLE.
739    if (!coreObject->isOffScreen()) {
740        atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
741        atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
742    }
743
744    // Mutually exclusive, so we group these two
745    if (coreObject->roleValue() == TextFieldRole)
746        atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
747    else if (coreObject->roleValue() == TextAreaRole)
748        atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
749
750    // TODO: ATK_STATE_SENSITIVE
751
752    if (coreObject->isVisited())
753        atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
754}
755
756static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object)
757{
758    AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_state_set(object);
759    AccessibilityObject* coreObject = core(object);
760
761    if (coreObject == fallbackObject()) {
762        atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
763        return stateSet;
764    }
765
766    // Text objects must be focusable.
767    AtkRole role = atk_object_get_role(object);
768    if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
769        atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
770
771    setAtkStateSetFromCoreObject(coreObject, stateSet);
772    return stateSet;
773}
774
775static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object)
776{
777    AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_relation_set(object);
778    AccessibilityObject* coreObject = core(object);
779
780    setAtkRelationSetFromCoreObject(coreObject, relationSet);
781
782    return relationSet;
783}
784
785static void webkitAccessibleInit(AtkObject* object, gpointer data)
786{
787    if (ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize)
788        ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize(object, data);
789
790    WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object);
791    accessible->m_object = reinterpret_cast<AccessibilityObject*>(data);
792    accessible->priv = WEBKIT_ACCESSIBLE_GET_PRIVATE(accessible);
793}
794
795static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object)
796{
797    if (ATK_IS_DOCUMENT(object)) {
798        AccessibilityObject* coreObject = core(object);
799        if (!coreObject)
800            return 0;
801
802        // TODO: Should we fall back on lang xml:lang when the following comes up empty?
803        String language = coreObject->language();
804        if (!language.isEmpty())
805            return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language);
806
807    } else if (ATK_IS_TEXT(object)) {
808        const gchar* locale = 0;
809
810        AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object));
811        for (GSList* attributes = textAttributes; attributes; attributes = attributes->next) {
812            AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
813            if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) {
814                locale = cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, String::fromUTF8(atkAttribute->value));
815                break;
816            }
817        }
818
819        atk_attribute_set_free(textAttributes);
820
821        return locale;
822    }
823
824    return 0;
825}
826
827static void webkitAccessibleFinalize(GObject* object)
828{
829    G_OBJECT_CLASS(webkitAccessibleParentClass)->finalize(object);
830}
831
832static void webkitAccessibleClassInit(AtkObjectClass* klass)
833{
834    GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
835
836    webkitAccessibleParentClass = g_type_class_peek_parent(klass);
837
838    gobjectClass->finalize = webkitAccessibleFinalize;
839
840    klass->initialize = webkitAccessibleInit;
841    klass->get_name = webkitAccessibleGetName;
842    klass->get_description = webkitAccessibleGetDescription;
843    klass->get_parent = webkitAccessibleGetParent;
844    klass->get_n_children = webkitAccessibleGetNChildren;
845    klass->ref_child = webkitAccessibleRefChild;
846    klass->get_role = webkitAccessibleGetRole;
847    klass->ref_state_set = webkitAccessibleRefStateSet;
848    klass->get_index_in_parent = webkitAccessibleGetIndexInParent;
849    klass->get_attributes = webkitAccessibleGetAttributes;
850    klass->ref_relation_set = webkitAccessibleRefRelationSet;
851    klass->get_object_locale = webkitAccessibleGetObjectLocale;
852
853    g_type_class_add_private(klass, sizeof(WebKitAccessiblePrivate));
854}
855
856GType
857webkitAccessibleGetType(void)
858{
859    static volatile gsize typeVolatile = 0;
860
861    if (g_once_init_enter(&typeVolatile)) {
862        static const GTypeInfo tinfo = {
863            sizeof(WebKitAccessibleClass),
864            (GBaseInitFunc) 0,
865            (GBaseFinalizeFunc) 0,
866            (GClassInitFunc) webkitAccessibleClassInit,
867            (GClassFinalizeFunc) 0,
868            0, /* class data */
869            sizeof(WebKitAccessible), /* instance size */
870            0, /* nb preallocs */
871            (GInstanceInitFunc) 0,
872            0 /* value table */
873        };
874
875        GType type = g_type_register_static(ATK_TYPE_OBJECT, "WebKitAccessible", &tinfo, GTypeFlags(0));
876        g_once_init_leave(&typeVolatile, type);
877    }
878
879    return typeVolatile;
880}
881
882static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
883    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleActionInterfaceInit), 0, 0},
884    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleSelectionInterfaceInit), 0, 0},
885    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleEditableTextInterfaceInit), 0, 0},
886    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTextInterfaceInit), 0, 0},
887    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0},
888    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0},
889    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0},
890    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0},
891    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0},
892    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0},
893    {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleValueInterfaceInit), 0, 0}
894};
895
896enum WAIType {
897    WAI_ACTION,
898    WAI_SELECTION,
899    WAI_EDITABLE_TEXT,
900    WAI_TEXT,
901    WAI_COMPONENT,
902    WAI_IMAGE,
903    WAI_TABLE,
904    WAI_HYPERTEXT,
905    WAI_HYPERLINK,
906    WAI_DOCUMENT,
907    WAI_VALUE,
908};
909
910static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
911{
912    switch (type) {
913    case WAI_ACTION:
914        return ATK_TYPE_ACTION;
915    case WAI_SELECTION:
916        return ATK_TYPE_SELECTION;
917    case WAI_EDITABLE_TEXT:
918        return ATK_TYPE_EDITABLE_TEXT;
919    case WAI_TEXT:
920        return ATK_TYPE_TEXT;
921    case WAI_COMPONENT:
922        return ATK_TYPE_COMPONENT;
923    case WAI_IMAGE:
924        return ATK_TYPE_IMAGE;
925    case WAI_TABLE:
926        return ATK_TYPE_TABLE;
927    case WAI_HYPERTEXT:
928        return ATK_TYPE_HYPERTEXT;
929    case WAI_HYPERLINK:
930        return ATK_TYPE_HYPERLINK_IMPL;
931    case WAI_DOCUMENT:
932        return ATK_TYPE_DOCUMENT;
933    case WAI_VALUE:
934        return ATK_TYPE_VALUE;
935    }
936
937    return G_TYPE_INVALID;
938}
939
940static bool roleIsTextType(AccessibilityRole role)
941{
942    return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole || role == ListItemRole;
943}
944
945static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
946{
947    guint16 interfaceMask = 0;
948
949    // Component interface is always supported
950    interfaceMask |= 1 << WAI_COMPONENT;
951
952    AccessibilityRole role = coreObject->roleValue();
953
954    // Action
955    // As the implementation of the AtkAction interface is a very
956    // basic one (just relays in executing the default action for each
957    // object, and only supports having one action per object), it is
958    // better just to implement this interface for every instance of
959    // the WebKitAccessible class and let WebCore decide what to do.
960    interfaceMask |= 1 << WAI_ACTION;
961
962    // Selection
963    if (coreObject->isListBox() || coreObject->isMenuList())
964        interfaceMask |= 1 << WAI_SELECTION;
965
966    // Get renderer if available.
967    RenderObject* renderer = 0;
968    if (coreObject->isAccessibilityRenderObject())
969        renderer = coreObject->renderer();
970
971    // Hyperlink (links and embedded objects).
972    if (coreObject->isLink() || (renderer && renderer->isReplaced()))
973        interfaceMask |= 1 << WAI_HYPERLINK;
974
975    // Text & Editable Text
976    if (role == StaticTextRole || coreObject->isMenuListOption())
977        interfaceMask |= 1 << WAI_TEXT;
978    else {
979        if (coreObject->isTextControl()) {
980            interfaceMask |= 1 << WAI_TEXT;
981            if (!coreObject->isReadOnly())
982                interfaceMask |= 1 << WAI_EDITABLE_TEXT;
983        } else {
984            if (role != TableRole) {
985                interfaceMask |= 1 << WAI_HYPERTEXT;
986                if ((renderer && renderer->childrenInline()) || roleIsTextType(role))
987                    interfaceMask |= 1 << WAI_TEXT;
988            }
989
990            // Add the TEXT interface for list items whose
991            // first accessible child has a text renderer
992            if (role == ListItemRole) {
993                AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
994                if (children.size()) {
995                    AccessibilityObject* axRenderChild = children.at(0).get();
996                    interfaceMask |= getInterfaceMaskFromObject(axRenderChild);
997                }
998            }
999        }
1000    }
1001
1002    // Image
1003    if (coreObject->isImage())
1004        interfaceMask |= 1 << WAI_IMAGE;
1005
1006    // Table
1007    if (role == TableRole)
1008        interfaceMask |= 1 << WAI_TABLE;
1009
1010    // Document
1011    if (role == WebAreaRole)
1012        interfaceMask |= 1 << WAI_DOCUMENT;
1013
1014    // Value
1015    if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole)
1016        interfaceMask |= 1 << WAI_VALUE;
1017
1018    return interfaceMask;
1019}
1020
1021static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
1022{
1023#define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
1024    static char name[WAI_TYPE_NAME_LEN + 1];
1025
1026    g_sprintf(name, "WAIType%x", interfaceMask);
1027    name[WAI_TYPE_NAME_LEN] = '\0';
1028
1029    return name;
1030}
1031
1032static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
1033{
1034    static const GTypeInfo typeInfo = {
1035        sizeof(WebKitAccessibleClass),
1036        (GBaseInitFunc) 0,
1037        (GBaseFinalizeFunc) 0,
1038        (GClassInitFunc) 0,
1039        (GClassFinalizeFunc) 0,
1040        0, /* class data */
1041        sizeof(WebKitAccessible), /* instance size */
1042        0, /* nb preallocs */
1043        (GInstanceInitFunc) 0,
1044        0 /* value table */
1045    };
1046
1047    guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
1048    const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
1049    GType type = g_type_from_name(atkTypeName);
1050    if (type)
1051        return type;
1052
1053    type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, GTypeFlags(0));
1054    for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
1055        if (interfaceMask & (1 << i))
1056            g_type_add_interface_static(type,
1057                GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1058                &AtkInterfacesInitFunctions[i]);
1059    }
1060
1061    return type;
1062}
1063
1064WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject)
1065{
1066    GType type = getAccessibilityTypeFromObject(coreObject);
1067    AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0));
1068
1069    atk_object_initialize(object, coreObject);
1070
1071    return WEBKIT_ACCESSIBLE(object);
1072}
1073
1074AccessibilityObject* webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible)
1075{
1076    return accessible->m_object;
1077}
1078
1079void webkitAccessibleDetach(WebKitAccessible* accessible)
1080{
1081    ASSERT(accessible->m_object);
1082
1083    if (core(accessible)->roleValue() == WebAreaRole)
1084        g_signal_emit_by_name(accessible, "state-change", "defunct", true);
1085
1086    // We replace the WebCore AccessibilityObject with a fallback object that
1087    // provides default implementations to avoid repetitive null-checking after
1088    // detachment.
1089    accessible->m_object = fallbackObject();
1090}
1091
1092AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible* accessible)
1093{
1094    if (!accessible->m_object)
1095        return 0;
1096
1097    RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement();
1098    if (!focusedObj)
1099        return 0;
1100
1101    return focusedObj->wrapper();
1102}
1103
1104AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset)
1105{
1106    // Indication that something bogus has transpired.
1107    offset = -1;
1108
1109    Document* document = referenceObject->document();
1110    if (!document)
1111        return 0;
1112
1113    Node* focusedNode = referenceObject->selection().end().containerNode();
1114    if (!focusedNode)
1115        return 0;
1116
1117    RenderObject* focusedRenderer = focusedNode->renderer();
1118    if (!focusedRenderer)
1119        return 0;
1120
1121    AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
1122    if (!focusedObject)
1123        return 0;
1124
1125    // Look for the actual (not ignoring accessibility) selected object.
1126    AccessibilityObject* firstUnignoredParent = focusedObject;
1127    if (firstUnignoredParent->accessibilityIsIgnored())
1128        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1129    if (!firstUnignoredParent)
1130        return 0;
1131
1132    // Don't ignore links if the offset is being requested for a link.
1133    if (!referenceObject->isLink() && firstUnignoredParent->isLink())
1134        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1135    if (!firstUnignoredParent)
1136        return 0;
1137
1138    // The reference object must either coincide with the focused
1139    // object being considered, or be a descendant of it.
1140    if (referenceObject->isDescendantOfObject(firstUnignoredParent))
1141        referenceObject = firstUnignoredParent;
1142
1143    Node* startNode = 0;
1144    if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
1145        // We need to use the first child's node of the reference
1146        // object as the start point to calculate the caret offset
1147        // because we want it to be relative to the object of
1148        // reference, not just to the focused object (which could have
1149        // previous siblings which should be taken into account too).
1150        AccessibilityObject* axFirstChild = referenceObject->firstChild();
1151        if (axFirstChild)
1152            startNode = axFirstChild->node();
1153    }
1154    // Getting the Position of a PseudoElement now triggers an assertion.
1155    // This can occur when clicking on empty space in a render block.
1156    if (!startNode || startNode->isPseudoElement())
1157        startNode = firstUnignoredParent->node();
1158
1159    // Check if the node for the first parent object not ignoring
1160    // accessibility is null again before using it. This might happen
1161    // with certain kind of accessibility objects, such as the root
1162    // one (the scroller containing the webArea object).
1163    if (!startNode)
1164        return 0;
1165
1166    VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
1167    VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();
1168
1169    if (startPosition == endPosition)
1170        offset = 0;
1171    else if (!isStartOfLine(endPosition)) {
1172        RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
1173        offset = TextIterator::rangeLength(range.get(), true) + 1;
1174    } else {
1175        RefPtr<Range> range = makeRange(startPosition, endPosition);
1176        offset = TextIterator::rangeLength(range.get(), true);
1177    }
1178
1179    return firstUnignoredParent;
1180}
1181
1182const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value)
1183{
1184    WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv;
1185    CString* propertyPtr = 0;
1186
1187    switch (property) {
1188    case AtkCachedAccessibleName:
1189        propertyPtr = &priv->accessibleName;
1190        break;
1191
1192    case AtkCachedAccessibleDescription:
1193        propertyPtr = &priv->accessibleDescription;
1194        break;
1195
1196    case AtkCachedActionName:
1197        propertyPtr = &priv->actionName;
1198        break;
1199
1200    case AtkCachedActionKeyBinding:
1201        propertyPtr = &priv->actionKeyBinding;
1202        break;
1203
1204    case AtkCachedDocumentLocale:
1205        propertyPtr = &priv->documentLocale;
1206        break;
1207
1208    case AtkCachedDocumentType:
1209        propertyPtr = &priv->documentType;
1210        break;
1211
1212    case AtkCachedDocumentEncoding:
1213        propertyPtr = &priv->documentEncoding;
1214        break;
1215
1216    case AtkCachedDocumentURI:
1217        propertyPtr = &priv->documentURI;
1218        break;
1219
1220    case AtkCachedImageDescription:
1221        propertyPtr = &priv->imageDescription;
1222        break;
1223
1224    default:
1225        ASSERT_NOT_REACHED();
1226    }
1227
1228    // Don't invalidate old memory if not stricly needed, since other
1229    // callers might be still holding on to it.
1230    if (*propertyPtr != value.utf8())
1231        *propertyPtr = value.utf8();
1232
1233    return (*propertyPtr).data();
1234}
1235
1236#endif // HAVE(ACCESSIBILITY)
1237