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#include "AccessibilityController.h"
33#include "DumpRenderTree.h"
34#include "GOwnPtr.h"
35#include "WebCoreSupport/DumpRenderTreeSupportGtk.h"
36#include <gtk/gtk.h>
37#include <webkit/webkit.h>
38
39static guint stateChangeListenerId = 0;
40static guint focusEventListenerId = 0;
41static guint activeDescendantChangedListenerId = 0;
42static guint childrenChangedListenerId = 0;
43static guint propertyChangedListenerId = 0;
44static guint visibleDataChangedListenerId = 0;
45
46static guint loadCompleteListenerId = 0;
47static guint loadStoppedListenerId = 0;
48static guint reloadListenerId = 0;
49
50static void printAccessibilityEvent(AtkObject* accessible, const gchar* signalName)
51{
52    // Sanity check.
53    if (!accessible || !ATK_IS_OBJECT(accessible) || !signalName)
54        return;
55
56    const gchar* objectName = atk_object_get_name(accessible);
57    guint objectRole = atk_object_get_role(accessible);
58
59    // Try to always provide a name to be logged for the object.
60    if (!objectName || *objectName == '\0')
61        objectName = "(No name)";
62
63    printf("Accessibility object emitted \"%s\" / Name: \"%s\" / Role: %d\n",
64           signalName, objectName, objectRole);
65}
66
67static gboolean axObjectEventListener(GSignalInvocationHint *signalHint,
68                                      guint numParamValues,
69                                      const GValue *paramValues,
70                                      gpointer data)
71{
72    // At least we should receive the instance emitting the signal.
73    if (numParamValues < 1)
74        return TRUE;
75
76    AtkObject* accessible = ATK_OBJECT(g_value_get_object(&paramValues[0]));
77    if (!accessible || !ATK_IS_OBJECT(accessible))
78        return TRUE;
79
80    GSignalQuery signal_query;
81    GOwnPtr<gchar> signalName;
82
83    g_signal_query(signalHint->signal_id, &signal_query);
84
85    if (!g_strcmp0(signal_query.signal_name, "state-change")) {
86        signalName.set(g_strdup_printf("state-change:%s = %d",
87                                       g_value_get_string(&paramValues[1]),
88                                       g_value_get_boolean(&paramValues[2])));
89    } else if (!g_strcmp0(signal_query.signal_name, "focus-event")) {
90        signalName.set(g_strdup_printf("focus-event = %d",
91                                       g_value_get_boolean(&paramValues[1])));
92    } else if (!g_strcmp0(signal_query.signal_name, "children-changed")) {
93        signalName.set(g_strdup_printf("children-changed = %d",
94                                       g_value_get_uint(&paramValues[1])));
95    } else if (!g_strcmp0(signal_query.signal_name, "property-change")) {
96        signalName.set(g_strdup_printf("property-change:%s",
97                                       g_quark_to_string(signalHint->detail)));
98    } else
99        signalName.set(g_strdup(signal_query.signal_name));
100
101    printAccessibilityEvent(accessible, signalName.get());
102
103    return TRUE;
104}
105
106static gboolean axDocumentEventListener(GSignalInvocationHint *signalHint,
107                                        guint numParamValues,
108                                        const GValue *paramValues,
109                                        gpointer data)
110{
111    // At least we should receive the instance emitting the signal.
112    if (numParamValues < 1)
113        return TRUE;
114
115    AtkObject* accessible = ATK_OBJECT(g_value_get_object(&paramValues[0]));
116    if (!accessible || !ATK_IS_DOCUMENT(accessible))
117        return TRUE;
118
119    GSignalQuery signal_query;
120    g_signal_query(signalHint->signal_id, &signal_query);
121
122    printAccessibilityEvent(accessible, signal_query.signal_name);
123
124    return TRUE;
125}
126
127void connectAccessibilityCallbacks()
128{
129    // Ensure no callbacks are connected before.
130    disconnectAccessibilityCallbacks();
131
132    // Ensure that accessibility is initialized for the WebView by querying for
133    // the root accessible object, which will create the full hierarchy.
134    DumpRenderTreeSupportGtk::getRootAccessibleElement(mainFrame);
135
136    // Add global listeners for AtkObject's signals.
137    stateChangeListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:state-change");
138    focusEventListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:focus-event");
139    activeDescendantChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:active-descendant-changed");
140    childrenChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:children-changed");
141    propertyChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:property-change");
142    visibleDataChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "Gtk:AtkObject:visible-data-changed");
143
144    // Ensure the Atk interface types are registered, otherwise
145    // the AtkDocument signal handlers below won't get registered.
146    GObject* dummyAxObject = G_OBJECT(g_object_new(ATK_TYPE_OBJECT, 0));
147    AtkObject* dummyNoOpAxObject = atk_no_op_object_new(dummyAxObject);
148    g_object_unref(G_OBJECT(dummyNoOpAxObject));
149    g_object_unref(dummyAxObject);
150
151    // Add global listeners for AtkDocument's signals.
152    loadCompleteListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:load-complete");
153    loadStoppedListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:load-stopped");
154    reloadListenerId = atk_add_global_event_listener(axDocumentEventListener, "Gtk:AtkDocument:reload");
155}
156
157void disconnectAccessibilityCallbacks()
158{
159    // AtkObject signals.
160    if (stateChangeListenerId) {
161        atk_remove_global_event_listener(stateChangeListenerId);
162        stateChangeListenerId = 0;
163    }
164    if (focusEventListenerId) {
165        atk_remove_global_event_listener(focusEventListenerId);
166        focusEventListenerId = 0;
167    }
168    if (activeDescendantChangedListenerId) {
169        atk_remove_global_event_listener(activeDescendantChangedListenerId);
170        activeDescendantChangedListenerId = 0;
171    }
172    if (childrenChangedListenerId) {
173        atk_remove_global_event_listener(childrenChangedListenerId);
174        childrenChangedListenerId = 0;
175    }
176    if (propertyChangedListenerId) {
177        atk_remove_global_event_listener(propertyChangedListenerId);
178        propertyChangedListenerId = 0;
179    }
180    if (visibleDataChangedListenerId) {
181        atk_remove_global_event_listener(visibleDataChangedListenerId);
182        visibleDataChangedListenerId = 0;
183    }
184
185    // AtkDocument signals.
186    if (loadCompleteListenerId) {
187        atk_remove_global_event_listener(loadCompleteListenerId);
188        loadCompleteListenerId = 0;
189    }
190    if (loadStoppedListenerId) {
191        atk_remove_global_event_listener(loadStoppedListenerId);
192        loadStoppedListenerId = 0;
193    }
194    if (reloadListenerId) {
195        atk_remove_global_event_listener(reloadListenerId);
196        reloadListenerId = 0;
197    }
198}
199
200