<https://webkit.org/b/98350> [GTK] accessibility/aria-invalid.html times out
[WebKit-https.git] / Tools / DumpRenderTree / atk / AccessibilityCallbacksAtk.cpp
1 /*
2  * Copyright (C) 2011 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "AccessibilityCallbacks.h"
31
32 #if HAVE(ACCESSIBILITY)
33
34 #include "AccessibilityController.h"
35 #include "AccessibilityNotificationHandlerAtk.h"
36 #include "DumpRenderTree.h"
37 #include "JSRetainPtr.h"
38 #include <atk/atk.h>
39 #include <wtf/gobject/GOwnPtr.h>
40
41 #if PLATFORM(GTK)
42 #include "WebCoreSupport/DumpRenderTreeSupportGtk.h"
43 #include <webkit/webkit.h>
44 #endif
45
46 #if PLATFORM(EFL)
47 #include "DumpRenderTreeChrome.h"
48 #include "WebCoreSupport/DumpRenderTreeSupportEfl.h"
49 #endif
50
51 static guint stateChangeListenerId = 0;
52 static guint focusEventListenerId = 0;
53 static guint activeDescendantChangedListenerId = 0;
54 static guint childrenChangedListenerId = 0;
55 static guint propertyChangedListenerId = 0;
56 static guint visibleDataChangedListenerId = 0;
57 static HashMap<PlatformUIElement, AccessibilityNotificationHandler*> notificationHandlers;
58
59 extern bool loggingAccessibilityEvents;
60
61 static void printAccessibilityEvent(AtkObject* accessible, const gchar* signalName, const gchar* signalValue)
62 {
63     // Do not handle state-change:defunct signals, as the AtkObject
64     // associated to them will not be valid at this point already.
65     if (!signalName || !g_strcmp0(signalName, "state-change:defunct"))
66         return;
67
68     if (!accessible || !ATK_IS_OBJECT(accessible))
69         return;
70
71     const gchar* objectName = atk_object_get_name(accessible);
72     AtkRole objectRole = atk_object_get_role(accessible);
73
74     // Try to always provide a name to be logged for the object.
75     if (!objectName || *objectName == '\0')
76         objectName = "(No name)";
77
78     GOwnPtr<gchar> signalNameAndValue(signalValue ? g_strdup_printf("%s = %s", signalName, signalValue) : g_strdup(signalName));
79     printf("Accessibility object emitted \"%s\" / Name: \"%s\" / Role: %d\n", signalNameAndValue.get(), objectName, objectRole);
80 }
81
82 static gboolean axObjectEventListener(GSignalInvocationHint *signalHint, guint numParamValues, const GValue *paramValues, gpointer data)
83 {
84     // At least we should receive the instance emitting the signal.
85     if (numParamValues < 1)
86         return TRUE;
87
88     AtkObject* accessible = ATK_OBJECT(g_value_get_object(&paramValues[0]));
89     if (!accessible || !ATK_IS_OBJECT(accessible))
90         return TRUE;
91
92     GSignalQuery signalQuery;
93     GOwnPtr<gchar> signalName;
94     GOwnPtr<gchar> signalValue;
95     String notificationName;
96
97     g_signal_query(signalHint->signal_id, &signalQuery);
98
99     if (!g_strcmp0(signalQuery.signal_name, "state-change")) {
100         signalName.set(g_strdup_printf("state-change:%s", g_value_get_string(&paramValues[1])));
101         signalValue.set(g_strdup_printf("%d", g_value_get_boolean(&paramValues[2])));
102         if (!g_strcmp0(g_value_get_string(&paramValues[1]), "checked"))
103             notificationName = "CheckedStateChanged";
104         else if (!g_strcmp0(g_value_get_string(&paramValues[1]), "invalid-entry"))
105             notificationName = "AXInvalidStatusChanged";
106     } else if (!g_strcmp0(signalQuery.signal_name, "focus-event")) {
107         signalName.set(g_strdup("focus-event"));
108         signalValue.set(g_strdup_printf("%d", g_value_get_boolean(&paramValues[1])));
109         notificationName = "AXFocusedUIElementChanged";
110     } else if (!g_strcmp0(signalQuery.signal_name, "children-changed")) {
111         signalName.set(g_strdup("children-changed"));
112         signalValue.set(g_strdup_printf("%d", g_value_get_uint(&paramValues[1])));
113     } else if (!g_strcmp0(signalQuery.signal_name, "property-change"))
114         signalName.set(g_strdup_printf("property-change:%s", g_quark_to_string(signalHint->detail)));
115     else
116         signalName.set(g_strdup(signalQuery.signal_name));
117
118     if (loggingAccessibilityEvents)
119         printAccessibilityEvent(accessible, signalName.get(), signalValue.get());
120
121 #if PLATFORM(GTK)
122     JSGlobalContextRef jsContext = webkit_web_frame_get_global_context(mainFrame);
123 #else
124     JSContextRef jsContext = 0;
125 #endif
126     if (!jsContext)
127         return TRUE;
128
129     if (notificationName.length()) {
130         for (HashMap<PlatformUIElement, AccessibilityNotificationHandler*>::iterator it = notificationHandlers.begin(); it != notificationHandlers.end(); ++it) {
131             if (it->key == accessible || it->key == GlobalNotificationKey) {
132                 JSRetainPtr<JSStringRef> jsNotificationEventName(Adopt, JSStringCreateWithUTF8CString(reinterpret_cast<const char*>(notificationName.utf8().data())));
133                 JSValueRef notificationNameArgument = JSValueMakeString(jsContext, jsNotificationEventName.get());
134                 AccessibilityNotificationHandler* notificationHandler = it->value;
135                 if (notificationHandler->platformElement()) {
136                     JSValueRef argument = notificationNameArgument;
137                     // Listener for one element just gets one argument, the notification name.
138                     JSObjectCallAsFunction(jsContext, notificationHandler->notificationFunctionCallback(), 0, 1, &argument, 0);
139                 } else {
140                     // A global listener gets the element and the notification name as arguments.
141                     JSValueRef arguments[2];
142                     arguments[0] = AccessibilityUIElement::makeJSAccessibilityUIElement(jsContext, AccessibilityUIElement(accessible));
143                     arguments[1] = notificationNameArgument;
144                     JSObjectCallAsFunction(jsContext, notificationHandler->notificationFunctionCallback(), 0, 2, arguments, 0);
145                 }
146             }
147         }
148     }
149
150     return TRUE;
151 }
152
153 void connectAccessibilityCallbacks()
154 {
155     // Ensure no callbacks are connected before.
156     disconnectAccessibilityCallbacks();
157
158     // Ensure that accessibility is initialized for the WebView by querying for
159     // the root accessible object, which will create the full hierarchy.
160 #if PLATFORM(GTK)
161     DumpRenderTreeSupportGtk::getRootAccessibleElement(mainFrame);
162 #elif PLATFORM(EFL)
163     DumpRenderTreeSupportEfl::rootAccessibleElement(browser->mainFrame());
164 #endif
165
166     // Add global listeners for AtkObject's signals.
167     stateChangeListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:state-change");
168     focusEventListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:focus-event");
169     activeDescendantChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:active-descendant-changed");
170     childrenChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:children-changed");
171     propertyChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:property-change");
172     visibleDataChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:visible-data-changed");
173
174     // Ensure the Atk interface types are registered, otherwise
175     // the AtkDocument signal handlers below won't get registered.
176     GObject* dummyAxObject = G_OBJECT(g_object_new(ATK_TYPE_OBJECT, 0));
177     AtkObject* dummyNoOpAxObject = atk_no_op_object_new(dummyAxObject);
178     g_object_unref(G_OBJECT(dummyNoOpAxObject));
179     g_object_unref(dummyAxObject);
180 }
181
182 void disconnectAccessibilityCallbacks()
183 {
184     // Only disconnect if logging is off and there is no notification handler.
185     if (loggingAccessibilityEvents || !notificationHandlers.isEmpty())
186         return;
187
188     // AtkObject signals.
189     if (stateChangeListenerId) {
190         atk_remove_global_event_listener(stateChangeListenerId);
191         stateChangeListenerId = 0;
192     }
193     if (focusEventListenerId) {
194         atk_remove_global_event_listener(focusEventListenerId);
195         focusEventListenerId = 0;
196     }
197     if (activeDescendantChangedListenerId) {
198         atk_remove_global_event_listener(activeDescendantChangedListenerId);
199         activeDescendantChangedListenerId = 0;
200     }
201     if (childrenChangedListenerId) {
202         atk_remove_global_event_listener(childrenChangedListenerId);
203         childrenChangedListenerId = 0;
204     }
205     if (propertyChangedListenerId) {
206         atk_remove_global_event_listener(propertyChangedListenerId);
207         propertyChangedListenerId = 0;
208     }
209     if (visibleDataChangedListenerId) {
210         atk_remove_global_event_listener(visibleDataChangedListenerId);
211         visibleDataChangedListenerId = 0;
212     }
213 }
214
215 void addAccessibilityNotificationHandler(AccessibilityNotificationHandler* notificationHandler)
216 {
217     if (!notificationHandler)
218         return;
219
220 #if PLATFORM(GTK)
221     JSGlobalContextRef jsContext = webkit_web_frame_get_global_context(mainFrame);
222 #else
223     JSContextRef jsContext = 0;
224 #endif
225     if (!jsContext)
226         return;
227
228     JSValueProtect(jsContext, notificationHandler->notificationFunctionCallback());
229     // Check if this notification handler is related to a specific element.
230     if (notificationHandler->platformElement()) {
231         if (notificationHandlers.contains(notificationHandler->platformElement())) {
232             JSValueUnprotect(jsContext, notificationHandlers.find(notificationHandler->platformElement())->value->notificationFunctionCallback());
233             notificationHandlers.remove(notificationHandler->platformElement());
234         }
235         notificationHandlers.add(notificationHandler->platformElement(), notificationHandler);
236     } else {
237         if (notificationHandlers.contains(GlobalNotificationKey)) {
238             JSValueUnprotect(jsContext, notificationHandlers.find(GlobalNotificationKey)->value->notificationFunctionCallback());
239             notificationHandlers.remove(GlobalNotificationKey);
240         }
241         notificationHandlers.add(GlobalNotificationKey, notificationHandler);
242     }
243
244     connectAccessibilityCallbacks();
245 }
246
247 void removeAccessibilityNotificationHandler(AccessibilityNotificationHandler* notificationHandler)
248 {
249     if (!notificationHandler)
250         return;
251
252 #if PLATFORM(GTK)
253     JSGlobalContextRef jsContext = webkit_web_frame_get_global_context(mainFrame);
254 #else
255     JSGlobalContextRef jsContext = 0;
256 #endif
257     if (!jsContext)
258         return;
259
260     for (HashMap<PlatformUIElement, AccessibilityNotificationHandler*>::iterator it = notificationHandlers.begin(); it != notificationHandlers.end(); ++it) {
261         if (it->value == notificationHandler) {
262             JSValueUnprotect(jsContext, notificationHandler->notificationFunctionCallback());
263             notificationHandlers.remove(it);
264         }
265     }
266 }
267
268 #endif