• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Nuanti Ltd.
3  * Copyright (C) 2009 Igalia S.L.
4  * Copyright (C) 2009 Jan Alonzo
5  *
6  * Portions from Mozilla a11y, copyright as follows:
7  *
8  * The Original Code is mozilla.org code.
9  *
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.
14  *
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.
19  *
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.
24  *
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.
29  */
30 
31 #include "config.h"
32 #include "AccessibilityObjectWrapperAtk.h"
33 
34 #if HAVE(ACCESSIBILITY)
35 
36 #include "AXObjectCache.h"
37 #include "AccessibilityListBox.h"
38 #include "AccessibilityRenderObject.h"
39 #include "AtomicString.h"
40 #include "CString.h"
41 #include "Document.h"
42 #include "Editor.h"
43 #include "Frame.h"
44 #include "FrameView.h"
45 #include "HostWindow.h"
46 #include "HTMLNames.h"
47 #include "InlineTextBox.h"
48 #include "IntRect.h"
49 #include "NotImplemented.h"
50 #include "RenderText.h"
51 #include "TextEncoding.h"
52 
53 #include <atk/atk.h>
54 #include <glib.h>
55 #include <glib/gprintf.h>
56 #include <libgail-util/gail-util.h>
57 #include <pango/pango.h>
58 
59 using namespace WebCore;
60 
fallbackObject()61 static AccessibilityObject* fallbackObject()
62 {
63     static AXObjectCache* fallbackCache = new AXObjectCache();
64     static AccessibilityObject* object = 0;
65     if (!object) {
66         // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack
67         object = fallbackCache->getOrCreate(ListBoxOptionRole);
68         object->ref();
69     }
70 
71     return object;
72 }
73 
74 // Used to provide const char* returns.
returnString(const String & str)75 static const char* returnString(const String& str)
76 {
77     static CString returnedString;
78     returnedString = str.utf8();
79     return returnedString.data();
80 }
81 
core(WebKitAccessible * accessible)82 static AccessibilityObject* core(WebKitAccessible* accessible)
83 {
84     if (!accessible)
85         return 0;
86 
87     return accessible->m_object;
88 }
89 
core(AtkObject * object)90 static AccessibilityObject* core(AtkObject* object)
91 {
92     if (!WEBKIT_IS_ACCESSIBLE(object))
93         return 0;
94 
95     return core(WEBKIT_ACCESSIBLE(object));
96 }
97 
core(AtkAction * action)98 static AccessibilityObject* core(AtkAction* action)
99 {
100     return core(ATK_OBJECT(action));
101 }
102 
core(AtkText * text)103 static AccessibilityObject* core(AtkText* text)
104 {
105     return core(ATK_OBJECT(text));
106 }
107 
core(AtkEditableText * text)108 static AccessibilityObject* core(AtkEditableText* text)
109 {
110     return core(ATK_OBJECT(text));
111 }
112 
core(AtkComponent * component)113 static AccessibilityObject* core(AtkComponent* component)
114 {
115     return core(ATK_OBJECT(component));
116 }
117 
core(AtkImage * image)118 static AccessibilityObject* core(AtkImage* image)
119 {
120     return core(ATK_OBJECT(image));
121 }
122 
webkit_accessible_get_name(AtkObject * object)123 static const gchar* webkit_accessible_get_name(AtkObject* object)
124 {
125     return returnString(core(object)->stringValue());
126 }
127 
webkit_accessible_get_description(AtkObject * object)128 static const gchar* webkit_accessible_get_description(AtkObject* object)
129 {
130     // TODO: the Mozilla MSAA implementation prepends "Description: "
131     // Should we do this too?
132     return returnString(core(object)->accessibilityDescription());
133 }
134 
webkit_accessible_get_parent(AtkObject * object)135 static AtkObject* webkit_accessible_get_parent(AtkObject* object)
136 {
137     AccessibilityObject* coreParent = core(object)->parentObject();
138 
139     if (!coreParent)
140         return NULL;
141 
142     return coreParent->wrapper();
143 }
144 
webkit_accessible_get_n_children(AtkObject * object)145 static gint webkit_accessible_get_n_children(AtkObject* object)
146 {
147     return core(object)->children().size();
148 }
149 
webkit_accessible_ref_child(AtkObject * object,gint index)150 static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index)
151 {
152     AccessibilityObject* coreObject = core(object);
153 
154     g_return_val_if_fail(index >= 0, NULL);
155     g_return_val_if_fail(static_cast<size_t>(index) < coreObject->children().size(), NULL);
156 
157     AccessibilityObject* coreChild = coreObject->children().at(index).get();
158 
159     if (!coreChild)
160         return NULL;
161 
162     AtkObject* child = coreChild->wrapper();
163     // TODO: Should we call atk_object_set_parent() here?
164     //atk_object_set_parent(child, object);
165     g_object_ref(child);
166 
167     return child;
168 }
169 
webkit_accessible_get_index_in_parent(AtkObject * object)170 static gint webkit_accessible_get_index_in_parent(AtkObject* object)
171 {
172     // FIXME: This needs to be implemented.
173     notImplemented();
174     return 0;
175 }
176 
atkRole(AccessibilityRole role)177 static AtkRole atkRole(AccessibilityRole role)
178 {
179     switch (role) {
180     case UnknownRole:
181         return ATK_ROLE_UNKNOWN;
182     case ButtonRole:
183         return ATK_ROLE_PUSH_BUTTON;
184     case RadioButtonRole:
185         return ATK_ROLE_RADIO_BUTTON;
186     case CheckBoxRole:
187         return ATK_ROLE_CHECK_BOX;
188     case SliderRole:
189         return ATK_ROLE_SLIDER;
190     case TabGroupRole:
191         return ATK_ROLE_PAGE_TAB_LIST;
192     case TextFieldRole:
193     case TextAreaRole:
194         return ATK_ROLE_ENTRY;
195     case StaticTextRole:
196         return ATK_ROLE_TEXT;
197     case OutlineRole:
198         return ATK_ROLE_TREE;
199     case MenuBarRole:
200         return ATK_ROLE_MENU_BAR;
201     case MenuRole:
202         return ATK_ROLE_MENU;
203     case MenuItemRole:
204         return ATK_ROLE_MENU_ITEM;
205     case ColumnRole:
206         //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
207         return ATK_ROLE_UNKNOWN; // Matches Mozilla
208     case RowRole:
209         //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right?
210         return ATK_ROLE_LIST_ITEM; // Matches Mozilla
211     case ToolbarRole:
212         return ATK_ROLE_TOOL_BAR;
213     case BusyIndicatorRole:
214         return ATK_ROLE_PROGRESS_BAR; // Is this right?
215     case ProgressIndicatorRole:
216         //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp
217         return ATK_ROLE_PROGRESS_BAR;
218     case WindowRole:
219         return ATK_ROLE_WINDOW;
220     case ComboBoxRole:
221         return ATK_ROLE_COMBO_BOX;
222     case SplitGroupRole:
223         return ATK_ROLE_SPLIT_PANE;
224     case SplitterRole:
225         return ATK_ROLE_SEPARATOR;
226     case ColorWellRole:
227         return ATK_ROLE_COLOR_CHOOSER;
228     case ListRole:
229         return ATK_ROLE_LIST;
230     case ScrollBarRole:
231         return ATK_ROLE_SCROLL_BAR;
232     case GridRole: // Is this right?
233     case TableRole:
234         return ATK_ROLE_TABLE;
235     case ApplicationRole:
236         return ATK_ROLE_APPLICATION;
237     //case LabelRole: // TODO: should this be covered in the switch?
238     //    return ATK_ROLE_LABEL;
239     case GroupRole:
240     case RadioGroupRole:
241         return ATK_ROLE_PANEL;
242     case CellRole:
243         return ATK_ROLE_TABLE_CELL;
244     case LinkRole:
245     case WebCoreLinkRole:
246     case ImageMapLinkRole:
247         return ATK_ROLE_LINK;
248     case ImageMapRole:
249     case ImageRole:
250         return ATK_ROLE_IMAGE;
251     case ListMarkerRole:
252         return ATK_ROLE_TEXT;
253     case WebAreaRole:
254         //return ATK_ROLE_HTML_CONTAINER; // Is this right?
255         return ATK_ROLE_DOCUMENT_FRAME;
256     case HeadingRole:
257         return ATK_ROLE_HEADING;
258     case ListBoxRole:
259         return ATK_ROLE_LIST;
260     case ListBoxOptionRole:
261         return ATK_ROLE_LIST_ITEM;
262     default:
263         return ATK_ROLE_UNKNOWN;
264     }
265 }
266 
webkit_accessible_get_role(AtkObject * object)267 static AtkRole webkit_accessible_get_role(AtkObject* object)
268 {
269     AccessibilityObject* AXObject = core(object);
270 
271     if (!AXObject)
272         return ATK_ROLE_UNKNOWN;
273 
274     // WebCore does not seem to have a role for list items
275     if (AXObject->isGroup()) {
276         AccessibilityObject* parent = AXObject->parentObjectUnignored();
277         if (parent && parent->isList())
278             return ATK_ROLE_LIST_ITEM;
279     }
280 
281     // WebCore does not know about paragraph role
282     if (AXObject->isAccessibilityRenderObject()) {
283         Node* node = static_cast<AccessibilityRenderObject*>(AXObject)->renderer()->node();
284         if (node && node->hasTagName(HTMLNames::pTag))
285             return ATK_ROLE_PARAGRAPH;
286     }
287 
288     // Note: Why doesn't WebCore have a password field for this
289     if (AXObject->isPasswordField())
290         return ATK_ROLE_PASSWORD_TEXT;
291 
292     return atkRole(AXObject->roleValue());
293 }
294 
setAtkStateSetFromCoreObject(AccessibilityObject * coreObject,AtkStateSet * stateSet)295 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
296 {
297     // Please keep the state list in alphabetical order
298 
299     if (coreObject->isChecked())
300         atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
301 
302     // FIXME: isReadOnly does not seem to do the right thing for
303     // controls, so check explicitly for them
304     if (!coreObject->isReadOnly() ||
305         (coreObject->isControl() && coreObject->canSetValueAttribute()))
306         atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
307 
308     // FIXME: Put both ENABLED and SENSITIVE together here for now
309     if (coreObject->isEnabled()) {
310         atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
311         atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
312     }
313 
314     if (coreObject->canSetFocusAttribute())
315         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
316 
317     if (coreObject->isFocused())
318         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
319 
320     // TODO: ATK_STATE_HORIZONTAL
321 
322     if (coreObject->isIndeterminate())
323         atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
324 
325     if (coreObject->isMultiSelect())
326         atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
327 
328     // TODO: ATK_STATE_OPAQUE
329 
330     if (coreObject->isPressed())
331         atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
332 
333     // TODO: ATK_STATE_SELECTABLE_TEXT
334 
335     if (coreObject->isSelected())
336         atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
337 
338     // FIXME: Group both SHOWING and VISIBLE here for now
339     // Not sure how to handle this in WebKit, see bug
340     // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
341     // issues with SHOWING vs VISIBLE within GTK+
342     if (!coreObject->isOffScreen()) {
343         atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
344         atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
345     }
346 
347     // Mutually exclusive, so we group these two
348     if (coreObject->roleValue() == TextFieldRole)
349         atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
350     else if (coreObject->roleValue() == TextAreaRole)
351         atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
352 
353     // TODO: ATK_STATE_SENSITIVE
354 
355     // TODO: ATK_STATE_VERTICAL
356 
357     if (coreObject->isVisited())
358         atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
359 }
360 
361 static gpointer webkit_accessible_parent_class = NULL;
362 
webkit_accessible_ref_state_set(AtkObject * object)363 static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object)
364 {
365     AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object);
366     AccessibilityObject* coreObject = core(object);
367 
368     if (coreObject == fallbackObject()) {
369         atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
370         return stateSet;
371     }
372 
373     setAtkStateSetFromCoreObject(coreObject, stateSet);
374 
375     return stateSet;
376 }
377 
webkit_accessible_init(AtkObject * object,gpointer data)378 static void webkit_accessible_init(AtkObject* object, gpointer data)
379 {
380     if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize)
381         ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data);
382 
383     WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data);
384 }
385 
webkit_accessible_finalize(GObject * object)386 static void webkit_accessible_finalize(GObject* object)
387 {
388     // This is a good time to clear the return buffer.
389     returnString(String());
390 
391     G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object);
392 }
393 
webkit_accessible_class_init(AtkObjectClass * klass)394 static void webkit_accessible_class_init(AtkObjectClass* klass)
395 {
396     GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
397 
398     webkit_accessible_parent_class = g_type_class_peek_parent(klass);
399 
400     gobjectClass->finalize = webkit_accessible_finalize;
401 
402     klass->initialize = webkit_accessible_init;
403     klass->get_name = webkit_accessible_get_name;
404     klass->get_description = webkit_accessible_get_description;
405     klass->get_parent = webkit_accessible_get_parent;
406     klass->get_n_children = webkit_accessible_get_n_children;
407     klass->ref_child = webkit_accessible_ref_child;
408     klass->get_role = webkit_accessible_get_role;
409     klass->ref_state_set = webkit_accessible_ref_state_set;
410     klass->get_index_in_parent = webkit_accessible_get_index_in_parent;
411 }
412 
413 GType
webkit_accessible_get_type(void)414 webkit_accessible_get_type(void)
415 {
416     static volatile gsize type_volatile = 0;
417 
418     if (g_once_init_enter(&type_volatile)) {
419         static const GTypeInfo tinfo = {
420             sizeof(WebKitAccessibleClass),
421             (GBaseInitFunc)NULL,
422             (GBaseFinalizeFunc)NULL,
423             (GClassInitFunc)webkit_accessible_class_init,
424             (GClassFinalizeFunc)NULL,
425             NULL, /* class data */
426             sizeof(WebKitAccessible), /* instance size */
427             0, /* nb preallocs */
428             (GInstanceInitFunc)NULL,
429             NULL /* value table */
430         };
431 
432         GType type = g_type_register_static(ATK_TYPE_OBJECT,
433                                             "WebKitAccessible", &tinfo, GTypeFlags(0));
434         g_once_init_leave(&type_volatile, type);
435     }
436 
437     return type_volatile;
438 }
439 
webkit_accessible_action_do_action(AtkAction * action,gint i)440 static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i)
441 {
442     g_return_val_if_fail(i == 0, FALSE);
443     return core(action)->performDefaultAction();
444 }
445 
webkit_accessible_action_get_n_actions(AtkAction * action)446 static gint webkit_accessible_action_get_n_actions(AtkAction* action)
447 {
448     return 1;
449 }
450 
webkit_accessible_action_get_description(AtkAction * action,gint i)451 static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i)
452 {
453     g_return_val_if_fail(i == 0, NULL);
454     // TODO: Need a way to provide/localize action descriptions.
455     notImplemented();
456     return "";
457 }
458 
webkit_accessible_action_get_keybinding(AtkAction * action,gint i)459 static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i)
460 {
461     g_return_val_if_fail(i == 0, NULL);
462     // FIXME: Construct a proper keybinding string.
463     return returnString(core(action)->accessKey().string());
464 }
465 
webkit_accessible_action_get_name(AtkAction * action,gint i)466 static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i)
467 {
468     g_return_val_if_fail(i == 0, NULL);
469     return returnString(core(action)->actionVerb());
470 }
471 
atk_action_interface_init(AtkActionIface * iface)472 static void atk_action_interface_init(AtkActionIface* iface)
473 {
474     iface->do_action = webkit_accessible_action_do_action;
475     iface->get_n_actions = webkit_accessible_action_get_n_actions;
476     iface->get_description = webkit_accessible_action_get_description;
477     iface->get_keybinding = webkit_accessible_action_get_keybinding;
478     iface->get_name = webkit_accessible_action_get_name;
479 }
480 
481 // Text
482 
webkit_accessible_text_get_text(AtkText * text,gint startOffset,gint endOffset)483 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset)
484 {
485     AccessibilityObject* coreObject = core(text);
486     String ret;
487     unsigned start = startOffset;
488     int length = endOffset - startOffset;
489 
490     if (coreObject->isTextControl())
491         ret = coreObject->doAXStringForRange(PlainTextRange(start, length));
492     else
493         ret = coreObject->textUnderElement().substring(start, length);
494 
495     return g_strdup(ret.utf8().data());
496 }
497 
getGailTextUtilForAtk(AtkText * textObject)498 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
499 {
500     gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util");
501     if (data)
502         return static_cast<GailTextUtil*>(data);
503 
504     GailTextUtil* gailTextUtil = gail_text_util_new();
505     gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1));
506     g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref);
507     return gailTextUtil;
508 }
509 
utf8Substr(const gchar * string,gint start,gint end)510 static gchar* utf8Substr(const gchar* string, gint start, gint end)
511 {
512     ASSERT(string);
513     glong strLen = g_utf8_strlen(string, -1);
514     if (start > strLen || end > strLen)
515         return 0;
516     gchar* startPtr = g_utf8_offset_to_pointer(string, start);
517     gsize lenInBytes = g_utf8_offset_to_pointer(string, end) -  startPtr + 1;
518     gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1));
519     return g_utf8_strncpy(output, startPtr, end - start + 1);
520 }
521 
522 // This function is not completely general, is it's tied to the
523 // internals of WebCore's text presentation.
convertUniCharToUTF8(const UChar * characters,gint length,int from,int to)524 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
525 {
526     CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables);
527     gchar* utf8String = utf8Substr(stringUTF8.data(), from, to);
528     if (!g_utf8_validate(utf8String, -1, NULL)) {
529         g_free(utf8String);
530         return 0;
531     }
532     gsize len = strlen(utf8String);
533     GString* ret = g_string_new_len(NULL, len);
534 
535     // WebCore introduces line breaks in the text that do not reflect
536     // the layout you see on the screen, replace them with spaces
537     while (len > 0) {
538         gint index, start;
539         pango_find_paragraph_boundary(utf8String, len, &index, &start);
540         g_string_append_len(ret, utf8String, index);
541         if (index == start)
542             break;
543         g_string_append_c(ret, ' ');
544         utf8String += start;
545         len -= start;
546     }
547 
548     g_free(utf8String);
549     return g_string_free(ret, FALSE);
550 }
551 
getPangoLayoutForAtk(AtkText * textObject)552 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
553 {
554     AccessibilityObject* coreObject = core(textObject);
555 
556     HostWindow* hostWindow = coreObject->document()->view()->hostWindow();
557     if (!hostWindow)
558         return 0;
559     PlatformWidget webView = hostWindow->platformWindow();
560     if (!webView)
561         return 0;
562 
563     GString* str = g_string_new(NULL);
564 
565     AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject);
566     if (!accObject)
567         return 0;
568     RenderText* renderText = toRenderText(accObject->renderer());
569     if (!renderText)
570         return 0;
571 
572     // Create a string with the layout as it appears on the screen
573     InlineTextBox* box = renderText->firstTextBox();
574     while (box) {
575         gchar *text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end());
576         g_string_append(str, text);
577         g_string_append(str, "\n");
578         box = box->nextTextBox();
579     }
580 
581     PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), g_string_free(str, FALSE));
582     g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref);
583     return layout;
584 }
585 
webkit_accessible_text_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)586 static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
587 {
588     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset);
589 }
590 
webkit_accessible_text_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)591 static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
592 {
593     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset);
594 }
595 
webkit_accessible_text_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)596 static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
597 {
598     return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset);
599 }
600 
webkit_accessible_text_get_character_at_offset(AtkText * text,gint offset)601 static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset)
602 {
603     notImplemented();
604     return NULL;
605 }
606 
webkit_accessible_text_get_caret_offset(AtkText * text)607 static gint webkit_accessible_text_get_caret_offset(AtkText* text)
608 {
609     // TODO: Verify this for RTL text.
610     return core(text)->selection().end().offsetInContainerNode();
611 }
612 
webkit_accessible_text_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)613 static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* start_offset, gint* end_offset)
614 {
615     notImplemented();
616     return NULL;
617 }
618 
webkit_accessible_text_get_default_attributes(AtkText * text)619 static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text)
620 {
621     notImplemented();
622     return NULL;
623 }
624 
webkit_accessible_text_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)625 static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
626 {
627     IntRect extents = core(text)->doAXBoundsForRange(PlainTextRange(offset, 1));
628     // FIXME: Use the AtkCoordType
629     // Requires WebCore::ScrollView::contentsToScreen() to be implemented
630 
631 #if 0
632     switch(coords) {
633     case ATK_XY_SCREEN:
634         extents = core(text)->document()->view()->contentsToScreen(extents);
635         break;
636     case ATK_XY_WINDOW:
637         // No-op
638         break;
639     }
640 #endif
641 
642     *x = extents.x();
643     *y = extents.y();
644     *width = extents.width();
645     *height = extents.height();
646 }
647 
webkit_accessible_text_get_character_count(AtkText * text)648 static gint webkit_accessible_text_get_character_count(AtkText* text)
649 {
650     AccessibilityObject* coreObject = core(text);
651 
652     if (coreObject->isTextControl())
653         return coreObject->textLength();
654     else
655         return coreObject->textUnderElement().length();
656 }
657 
webkit_accessible_text_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)658 static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords)
659 {
660     // FIXME: Use the AtkCoordType
661     // TODO: Is it correct to ignore range.length?
662     IntPoint pos(x, y);
663     PlainTextRange range = core(text)->doAXRangeForPosition(pos);
664     return range.start;
665 }
666 
selectionBelongsToObject(AccessibilityObject * coreObject,VisibleSelection & selection)667 static bool selectionBelongsToObject(AccessibilityObject *coreObject, VisibleSelection& selection)
668 {
669     if (!coreObject->isAccessibilityRenderObject())
670         return false;
671 
672     Node* node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node();
673     return node == selection.base().containerNode();
674 }
675 
webkit_accessible_text_get_n_selections(AtkText * text)676 static gint webkit_accessible_text_get_n_selections(AtkText* text)
677 {
678     AccessibilityObject* coreObject = core(text);
679     VisibleSelection selection = coreObject->selection();
680 
681     // We don't support multiple selections for now, so there's only
682     // two possibilities
683     // Also, we don't want to do anything if the selection does not
684     // belong to the currently selected object. We have to check since
685     // there's no way to get the selection for a given object, only
686     // the global one (the API is a bit confusing)
687     return !selectionBelongsToObject(coreObject, selection) || selection.isNone() ? 0 : 1;
688 }
689 
webkit_accessible_text_get_selection(AtkText * text,gint selection_num,gint * start_offset,gint * end_offset)690 static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selection_num, gint* start_offset, gint* end_offset)
691 {
692     AccessibilityObject* coreObject = core(text);
693     VisibleSelection selection = coreObject->selection();
694 
695     // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
696     // Also, we don't want to do anything if the selection does not
697     // belong to the currently selected object. We have to check since
698     // there's no way to get the selection for a given object, only
699     // the global one (the API is a bit confusing)
700     if (selection_num != 0 || !selectionBelongsToObject(coreObject, selection)) {
701         *start_offset = *end_offset = 0;
702         return NULL;
703     }
704 
705     *start_offset = selection.start().offsetInContainerNode();
706     *end_offset = selection.end().offsetInContainerNode();
707 
708     return webkit_accessible_text_get_text(text, *start_offset, *end_offset);
709 }
710 
webkit_accessible_text_add_selection(AtkText * text,gint start_offset,gint end_offset)711 static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset)
712 {
713     notImplemented();
714     return FALSE;
715 }
716 
webkit_accessible_text_remove_selection(AtkText * text,gint selection_num)717 static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selection_num)
718 {
719     notImplemented();
720     return FALSE;
721 }
722 
webkit_accessible_text_set_selection(AtkText * text,gint selection_num,gint start_offset,gint end_offset)723 static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selection_num, gint start_offset, gint end_offset)
724 {
725     notImplemented();
726     return FALSE;
727 }
728 
webkit_accessible_text_set_caret_offset(AtkText * text,gint offset)729 static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset)
730 {
731     AccessibilityObject* coreObject = core(text);
732 
733     // FIXME: We need to reimplement visiblePositionRangeForRange here
734     // because the actual function checks the offset is within the
735     // boundaries of text().length(), but text() only works for text
736     // controls...
737     VisiblePosition startPosition = coreObject->visiblePositionForIndex(offset);
738     startPosition.setAffinity(DOWNSTREAM);
739     VisiblePosition endPosition = coreObject->visiblePositionForIndex(offset);
740     VisiblePositionRange range = VisiblePositionRange(startPosition, endPosition);
741 
742     coreObject->setSelectedVisiblePositionRange(range);
743     return TRUE;
744 }
745 
atk_text_interface_init(AtkTextIface * iface)746 static void atk_text_interface_init(AtkTextIface* iface)
747 {
748     iface->get_text = webkit_accessible_text_get_text;
749     iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset;
750     iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset;
751     iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset;
752     iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset;
753     iface->get_caret_offset = webkit_accessible_text_get_caret_offset;
754     iface->get_run_attributes = webkit_accessible_text_get_run_attributes;
755     iface->get_default_attributes = webkit_accessible_text_get_default_attributes;
756     iface->get_character_extents = webkit_accessible_text_get_character_extents;
757     iface->get_character_count = webkit_accessible_text_get_character_count;
758     iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point;
759     iface->get_n_selections = webkit_accessible_text_get_n_selections;
760     iface->get_selection = webkit_accessible_text_get_selection;
761 
762     // set methods
763     iface->add_selection = webkit_accessible_text_add_selection;
764     iface->remove_selection = webkit_accessible_text_remove_selection;
765     iface->set_selection = webkit_accessible_text_set_selection;
766     iface->set_caret_offset = webkit_accessible_text_set_caret_offset;
767 }
768 
769 // EditableText
770 
webkit_accessible_editable_text_set_run_attributes(AtkEditableText * text,AtkAttributeSet * attrib_set,gint start_offset,gint end_offset)771 static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset)
772 {
773     notImplemented();
774     return FALSE;
775 }
776 
webkit_accessible_editable_text_set_text_contents(AtkEditableText * text,const gchar * string)777 static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string)
778 {
779     // FIXME: string nullcheck?
780     core(text)->setValue(String::fromUTF8(string));
781 }
782 
webkit_accessible_editable_text_insert_text(AtkEditableText * text,const gchar * string,gint length,gint * position)783 static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position)
784 {
785     // FIXME: string nullcheck?
786 
787     AccessibilityObject* coreObject = core(text);
788     // FIXME: Not implemented in WebCore
789     //coreObject->setSelectedTextRange(PlainTextRange(*position, 0));
790     //coreObject->setSelectedText(String::fromUTF8(string));
791 
792     if (!coreObject->document() || !coreObject->document()->frame())
793         return;
794     coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0)));
795     coreObject->setFocused(true);
796     // FIXME: We should set position to the actual inserted text length, which may be less than that requested.
797     if (coreObject->document()->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0))
798         *position += length;
799 }
800 
webkit_accessible_editable_text_copy_text(AtkEditableText * text,gint start_pos,gint end_pos)801 static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos)
802 {
803     notImplemented();
804 }
805 
webkit_accessible_editable_text_cut_text(AtkEditableText * text,gint start_pos,gint end_pos)806 static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos)
807 {
808     notImplemented();
809 }
810 
webkit_accessible_editable_text_delete_text(AtkEditableText * text,gint start_pos,gint end_pos)811 static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos)
812 {
813     AccessibilityObject* coreObject = core(text);
814     // FIXME: Not implemented in WebCore
815     //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos));
816     //coreObject->setSelectedText(String());
817 
818     if (!coreObject->document() || !coreObject->document()->frame())
819         return;
820     coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos)));
821     coreObject->setFocused(true);
822     coreObject->document()->frame()->editor()->performDelete();
823 }
824 
webkit_accessible_editable_text_paste_text(AtkEditableText * text,gint position)825 static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position)
826 {
827     notImplemented();
828 }
829 
atk_editable_text_interface_init(AtkEditableTextIface * iface)830 static void atk_editable_text_interface_init(AtkEditableTextIface* iface)
831 {
832     iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes;
833     iface->set_text_contents = webkit_accessible_editable_text_set_text_contents;
834     iface->insert_text = webkit_accessible_editable_text_insert_text;
835     iface->copy_text = webkit_accessible_editable_text_copy_text;
836     iface->cut_text = webkit_accessible_editable_text_cut_text;
837     iface->delete_text = webkit_accessible_editable_text_delete_text;
838     iface->paste_text = webkit_accessible_editable_text_paste_text;
839 }
840 
contentsToAtk(AccessibilityObject * coreObject,AtkCoordType coordType,IntRect rect,gint * x,gint * y,gint * width=0,gint * height=0)841 static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0)
842 {
843     FrameView* frameView = coreObject->documentFrameView();
844 
845     if (frameView) {
846         switch (coordType) {
847         case ATK_XY_WINDOW:
848             rect = frameView->contentsToWindow(rect);
849             break;
850         case ATK_XY_SCREEN:
851             rect = frameView->contentsToScreen(rect);
852             break;
853         }
854     }
855 
856     if (x)
857         *x = rect.x();
858     if (y)
859         *y = rect.y();
860     if (width)
861         *width = rect.width();
862     if (height)
863         *height = rect.height();
864 }
865 
atkToContents(AccessibilityObject * coreObject,AtkCoordType coordType,gint x,gint y)866 static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y)
867 {
868     IntPoint pos(x, y);
869 
870     FrameView* frameView = coreObject->documentFrameView();
871     if (frameView) {
872         switch (coordType) {
873         case ATK_XY_SCREEN:
874             return frameView->screenToContents(pos);
875         case ATK_XY_WINDOW:
876             return frameView->windowToContents(pos);
877         }
878     }
879 
880     return pos;
881 }
882 
webkit_accessible_component_ref_accessible_at_point(AtkComponent * component,gint x,gint y,AtkCoordType coordType)883 static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType)
884 {
885     IntPoint pos = atkToContents(core(component), coordType, x, y);
886     AccessibilityObject* target = core(component)->doAccessibilityHitTest(pos);
887     if (!target)
888         return NULL;
889     g_object_ref(target->wrapper());
890     return target->wrapper();
891 }
892 
webkit_accessible_component_get_extents(AtkComponent * component,gint * x,gint * y,gint * width,gint * height,AtkCoordType coordType)893 static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType)
894 {
895     IntRect rect = core(component)->elementRect();
896     contentsToAtk(core(component), coordType, rect, x, y, width, height);
897 }
898 
webkit_accessible_component_grab_focus(AtkComponent * component)899 static gboolean webkit_accessible_component_grab_focus(AtkComponent* component)
900 {
901     core(component)->setFocused(true);
902     return core(component)->isFocused();
903 }
904 
atk_component_interface_init(AtkComponentIface * iface)905 static void atk_component_interface_init(AtkComponentIface *iface)
906 {
907     iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point;
908     iface->get_extents = webkit_accessible_component_get_extents;
909     iface->grab_focus = webkit_accessible_component_grab_focus;
910 }
911 
912 // Image
913 
webkit_accessible_image_get_image_position(AtkImage * image,gint * x,gint * y,AtkCoordType coordType)914 static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType)
915 {
916     IntRect rect = core(image)->elementRect();
917     contentsToAtk(core(image), coordType, rect, x, y);
918 }
919 
webkit_accessible_image_get_image_description(AtkImage * image)920 static const gchar* webkit_accessible_image_get_image_description(AtkImage* image)
921 {
922     return returnString(core(image)->accessibilityDescription());
923 }
924 
webkit_accessible_image_get_image_size(AtkImage * image,gint * width,gint * height)925 static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height)
926 {
927     IntSize size = core(image)->size();
928 
929     if (width)
930         *width = size.width();
931     if (height)
932         *height = size.height();
933 }
934 
atk_image_interface_init(AtkImageIface * iface)935 static void atk_image_interface_init(AtkImageIface* iface)
936 {
937     iface->get_image_position = webkit_accessible_image_get_image_position;
938     iface->get_image_description = webkit_accessible_image_get_image_description;
939     iface->get_image_size = webkit_accessible_image_get_image_size;
940 }
941 
942 static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
943     {(GInterfaceInitFunc)atk_action_interface_init,
944      (GInterfaceFinalizeFunc) NULL, NULL},
945     {(GInterfaceInitFunc)atk_editable_text_interface_init,
946      (GInterfaceFinalizeFunc) NULL, NULL},
947     {(GInterfaceInitFunc)atk_text_interface_init,
948      (GInterfaceFinalizeFunc) NULL, NULL},
949     {(GInterfaceInitFunc)atk_component_interface_init,
950      (GInterfaceFinalizeFunc) NULL, NULL},
951     {(GInterfaceInitFunc)atk_image_interface_init,
952      (GInterfaceFinalizeFunc) NULL, NULL}
953 };
954 
955 enum WAIType {
956     WAI_ACTION,
957     WAI_EDITABLE_TEXT,
958     WAI_TEXT,
959     WAI_COMPONENT,
960     WAI_IMAGE
961 };
962 
GetAtkInterfaceTypeFromWAIType(WAIType type)963 static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
964 {
965   switch (type) {
966   case WAI_ACTION:
967       return ATK_TYPE_ACTION;
968   case WAI_EDITABLE_TEXT:
969       return ATK_TYPE_EDITABLE_TEXT;
970   case WAI_TEXT:
971       return ATK_TYPE_TEXT;
972   case WAI_COMPONENT:
973       return ATK_TYPE_COMPONENT;
974   case WAI_IMAGE:
975       return ATK_TYPE_IMAGE;
976   }
977 
978   return G_TYPE_INVALID;
979 }
980 
getInterfaceMaskFromObject(AccessibilityObject * coreObject)981 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
982 {
983     guint16 interfaceMask = 0;
984 
985     // Component interface is always supported
986     interfaceMask |= 1 << WAI_COMPONENT;
987 
988     // Action
989     if (!coreObject->actionVerb().isEmpty())
990         interfaceMask |= 1 << WAI_ACTION;
991 
992     // Text & Editable Text
993     AccessibilityRole role = coreObject->roleValue();
994 
995     if (role == StaticTextRole)
996         interfaceMask |= 1 << WAI_TEXT;
997 
998     if (coreObject->isAccessibilityRenderObject() && coreObject->isTextControl()) {
999         if (coreObject->isReadOnly())
1000             interfaceMask |= 1 << WAI_TEXT;
1001         else
1002             interfaceMask |= 1 << WAI_EDITABLE_TEXT;
1003     }
1004 
1005     // Image
1006     if (coreObject->isImage())
1007         interfaceMask |= 1 << WAI_IMAGE;
1008 
1009     return interfaceMask;
1010 }
1011 
getUniqueAccessibilityTypeName(guint16 interfaceMask)1012 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
1013 {
1014 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
1015     static char name[WAI_TYPE_NAME_LEN + 1];
1016 
1017     g_sprintf(name, "WAIType%x", interfaceMask);
1018     name[WAI_TYPE_NAME_LEN] = '\0';
1019 
1020     return name;
1021 }
1022 
getAccessibilityTypeFromObject(AccessibilityObject * coreObject)1023 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
1024 {
1025     static const GTypeInfo typeInfo = {
1026         sizeof(WebKitAccessibleClass),
1027         (GBaseInitFunc) NULL,
1028         (GBaseFinalizeFunc) NULL,
1029         (GClassInitFunc) NULL,
1030         (GClassFinalizeFunc) NULL,
1031         NULL, /* class data */
1032         sizeof(WebKitAccessible), /* instance size */
1033         0, /* nb preallocs */
1034         (GInstanceInitFunc) NULL,
1035         NULL /* value table */
1036     };
1037 
1038     guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
1039     const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
1040     GType type = g_type_from_name(atkTypeName);
1041     if (type)
1042         return type;
1043 
1044     type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE,
1045                                   atkTypeName,
1046                                   &typeInfo, GTypeFlags(0));
1047     for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
1048         if (interfaceMask & (1 << i))
1049             g_type_add_interface_static(type,
1050                                         GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1051                                         &AtkInterfacesInitFunctions[i]);
1052     }
1053 
1054     return type;
1055 }
1056 
webkit_accessible_new(AccessibilityObject * coreObject)1057 WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject)
1058 {
1059     GType type = getAccessibilityTypeFromObject(coreObject);
1060     AtkObject* object = static_cast<AtkObject*>(g_object_new(type, NULL));
1061 
1062     atk_object_initialize(object, coreObject);
1063 
1064     return WEBKIT_ACCESSIBLE(object);
1065 }
1066 
webkit_accessible_get_accessibility_object(WebKitAccessible * accessible)1067 AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible)
1068 {
1069     return accessible->m_object;
1070 }
1071 
webkit_accessible_detach(WebKitAccessible * accessible)1072 void webkit_accessible_detach(WebKitAccessible* accessible)
1073 {
1074     ASSERT(accessible->m_object);
1075 
1076     // We replace the WebCore AccessibilityObject with a fallback object that
1077     // provides default implementations to avoid repetitive null-checking after
1078     // detachment.
1079     accessible->m_object = fallbackObject();
1080 }
1081 
webkit_accessible_get_focused_element(WebKitAccessible * accessible)1082 AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible)
1083 {
1084     if (!accessible->m_object)
1085         return 0;
1086 
1087     RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement();
1088     if (!focusedObj)
1089         return 0;
1090 
1091     return focusedObj->wrapper();
1092 }
1093 
1094 #endif // HAVE(ACCESSIBILITY)
1095