[CMake] Properly test if compiler supports compiler flags
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / atk / AccessibilityNotificationHandlerAtk.cpp
1 /*
2  * Copyright (C) 2013 Samsung Electronics Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "AccessibilityNotificationHandlerAtk.h"
22
23 #if HAVE(ACCESSIBILITY)
24
25 #include "InjectedBundle.h"
26 #include "InjectedBundlePage.h"
27 #include "JSWrapper.h"
28 #include <WebKit/WKBundleFrame.h>
29 #include <WebKit/WKBundlePage.h>
30 #include <WebKit/WKBundlePagePrivate.h>
31 #include <wtf/HashMap.h>
32 #include <wtf/Vector.h>
33 #include <wtf/glib/GUniquePtr.h>
34 #include <wtf/text/CString.h>
35 #include <wtf/text/WTFString.h>
36
37 namespace WTR {
38
39 namespace {
40
41 typedef HashMap<AtkObject*, AccessibilityNotificationHandler*> NotificationHandlersMap;
42
43 WTF::Vector<unsigned> listenerIds;
44 NotificationHandlersMap notificationHandlers;
45 AccessibilityNotificationHandler* globalNotificationHandler = nullptr;
46
47 gboolean axObjectEventListener(GSignalInvocationHint* signalHint, unsigned numParamValues, const GValue* paramValues, gpointer data)
48 {
49     // At least we should receive the instance emitting the signal.
50     if (!numParamValues)
51         return true;
52
53     AtkObject* accessible = ATK_OBJECT(g_value_get_object(&paramValues[0]));
54     if (!accessible || !ATK_IS_OBJECT(accessible))
55         return true;
56
57 #if PLATFORM(GTK)
58     WKBundlePageRef page = InjectedBundle::singleton().page()->page();
59     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page);
60     JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame);
61 #else
62     JSContextRef jsContext = nullptr;
63 #endif
64
65     GSignalQuery signalQuery;
66     const char* notificationName = nullptr;
67     Vector<JSValueRef> extraArgs;
68
69     g_signal_query(signalHint->signal_id, &signalQuery);
70
71     if (!g_strcmp0(signalQuery.signal_name, "state-change")) {
72         if (!g_strcmp0(g_value_get_string(&paramValues[1]), "checked"))
73             notificationName = "CheckedStateChanged";
74         else if (!g_strcmp0(g_value_get_string(&paramValues[1]), "invalid-entry"))
75             notificationName = "AXInvalidStatusChanged";
76         else if (!g_strcmp0(g_value_get_string(&paramValues[1]), "active"))
77             notificationName = "ActiveStateChanged";
78         else if (!g_strcmp0(g_value_get_string(&paramValues[1]), "busy"))
79             notificationName = "AXElementBusyChanged";
80     } else if (!g_strcmp0(signalQuery.signal_name, "focus-event")) {
81         if (g_value_get_boolean(&paramValues[1]))
82             notificationName = "AXFocusedUIElementChanged";
83     } else if (!g_strcmp0(signalQuery.signal_name, "selection-changed")) {
84         notificationName = "AXSelectedChildrenChanged";
85     } else if (!g_strcmp0(signalQuery.signal_name, "children-changed")) {
86         const gchar* childrenChangedDetail = g_quark_to_string(signalHint->detail);
87         notificationName = !g_strcmp0(childrenChangedDetail, "add") ? "AXChildrenAdded" : "AXChildrenRemoved";
88         gpointer child = g_value_get_pointer(&paramValues[2]);
89         if (ATK_IS_OBJECT(child))
90             extraArgs.append(toJS(jsContext, WTF::getPtr(WTR::AccessibilityUIElement::create(ATK_OBJECT(child)))));
91     } else if (!g_strcmp0(signalQuery.signal_name, "property-change")) {
92         if (!g_strcmp0(g_quark_to_string(signalHint->detail), "accessible-value"))
93             notificationName = "AXValueChanged";
94     } else if (!g_strcmp0(signalQuery.signal_name, "load-complete"))
95         notificationName = "AXLoadComplete";
96     else if (!g_strcmp0(signalQuery.signal_name, "text-caret-moved")) {
97         notificationName = "AXTextCaretMoved";
98         GUniquePtr<char> signalValue(g_strdup_printf("%d", g_value_get_int(&paramValues[1])));
99         JSRetainPtr<JSStringRef> jsSignalValue(Adopt, JSStringCreateWithUTF8CString(signalValue.get()));
100         extraArgs.append(JSValueMakeString(jsContext, jsSignalValue.get()));
101     } else if (!g_strcmp0(signalQuery.signal_name, "text-insert") || !g_strcmp0(signalQuery.signal_name, "text-remove"))
102         notificationName = "AXTextChanged";
103
104     if (!jsContext)
105         return true;
106
107     if (notificationName) {
108         JSRetainPtr<JSStringRef> jsNotificationEventName(Adopt, JSStringCreateWithUTF8CString(notificationName));
109         JSValueRef notificationNameArgument = JSValueMakeString(jsContext, jsNotificationEventName.get());
110         NotificationHandlersMap::iterator elementNotificationHandler = notificationHandlers.find(accessible);
111         JSValueRef arguments[5]; // this dimension must be >= 2 + max(extraArgs.size())
112         arguments[0] = toJS(jsContext, WTF::getPtr(WTR::AccessibilityUIElement::create(accessible)));
113         arguments[1] = notificationNameArgument;
114         size_t numOfExtraArgs = extraArgs.size();
115         for (size_t i = 0; i < numOfExtraArgs; i++)
116             arguments[i + 2] = extraArgs[i];
117         if (elementNotificationHandler != notificationHandlers.end()) {
118             // Listener for one element. As arguments, it gets the notification name
119             // and sometimes extra arguments.
120             JSObjectCallAsFunction(jsContext,
121                 const_cast<JSObjectRef>(elementNotificationHandler->value->notificationFunctionCallback()),
122                 0, numOfExtraArgs + 1, arguments + 1, 0);
123         }
124
125         if (globalNotificationHandler) {
126             // A global listener gets additionally the element as the first argument.
127             JSObjectCallAsFunction(jsContext,
128                 const_cast<JSObjectRef>(globalNotificationHandler->notificationFunctionCallback()),
129                 0, numOfExtraArgs + 2, arguments, 0);
130         }
131     }
132
133     return true;
134 }
135
136 } // namespace
137
138 AccessibilityNotificationHandler::AccessibilityNotificationHandler()
139     : m_platformElement(0)
140     , m_notificationFunctionCallback(0)
141 {
142 }
143
144 AccessibilityNotificationHandler::~AccessibilityNotificationHandler()
145 {
146     removeAccessibilityNotificationHandler();
147     disconnectAccessibilityCallbacks();
148 }
149
150 void AccessibilityNotificationHandler::setNotificationFunctionCallback(JSValueRef notificationFunctionCallback)
151 {
152     if (!notificationFunctionCallback) {
153         removeAccessibilityNotificationHandler();
154         disconnectAccessibilityCallbacks();
155         return;
156     }
157
158     m_notificationFunctionCallback = notificationFunctionCallback;
159
160 #if PLATFORM(GTK)
161     WKBundlePageRef page = InjectedBundle::singleton().page()->page();
162     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page);
163     JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame);
164 #else
165     JSContextRef jsContext = nullptr;
166 #endif
167     if (!jsContext)
168         return;
169
170     connectAccessibilityCallbacks();
171
172     JSValueProtect(jsContext, m_notificationFunctionCallback);
173     // Check if this notification handler is related to a specific element.
174     if (m_platformElement) {
175         NotificationHandlersMap::iterator currentNotificationHandler = notificationHandlers.find(m_platformElement.get());
176         if (currentNotificationHandler != notificationHandlers.end()) {
177             ASSERT(currentNotificationHandler->value->platformElement());
178             JSValueUnprotect(jsContext, currentNotificationHandler->value->notificationFunctionCallback());
179             notificationHandlers.remove(currentNotificationHandler->value->platformElement().get());
180         }
181         notificationHandlers.add(m_platformElement.get(), this);
182     } else {
183         if (globalNotificationHandler)
184             JSValueUnprotect(jsContext, globalNotificationHandler->notificationFunctionCallback());
185         globalNotificationHandler = this;
186     }
187 }
188
189 void AccessibilityNotificationHandler::removeAccessibilityNotificationHandler()
190 {
191 #if PLATFORM(GTK)
192     WKBundlePageRef page = InjectedBundle::singleton().page()->page();
193     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page);
194     JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame);
195 #else
196     JSContextRef jsContext = nullptr;
197 #endif
198     if (!jsContext)
199         return;
200
201     if (globalNotificationHandler == this) {
202         JSValueUnprotect(jsContext, globalNotificationHandler->notificationFunctionCallback());
203         globalNotificationHandler = nullptr;
204     } else if (m_platformElement.get()) {
205         NotificationHandlersMap::iterator removeNotificationHandler = notificationHandlers.find(m_platformElement.get());
206         if (removeNotificationHandler != notificationHandlers.end()) {
207             JSValueUnprotect(jsContext, removeNotificationHandler->value->notificationFunctionCallback());
208             notificationHandlers.remove(removeNotificationHandler);
209         }
210     }
211 }
212
213 void AccessibilityNotificationHandler::connectAccessibilityCallbacks()
214 {
215     // Ensure no callbacks are connected before.
216     if (!disconnectAccessibilityCallbacks())
217         return;
218
219     const char* signalNames[] = {
220         "ATK:AtkObject:state-change",
221         "ATK:AtkObject:focus-event",
222         "ATK:AtkObject:active-descendant-changed",
223         "ATK:AtkObject:children-changed",
224         "ATK:AtkObject:property-change",
225         "ATK:AtkObject:visible-data-changed",
226         "ATK:AtkDocument:load-complete",
227         "ATK:AtkSelection:selection-changed",
228         "ATK:AtkText:text-caret-moved",
229         "ATK:AtkText:text-insert",
230         "ATK:AtkText:text-remove",
231         0
232     };
233
234     // Register atk interfaces, otherwise add_global may fail.
235     GObject* dummyObject = (GObject*)g_object_new(G_TYPE_OBJECT, NULL, NULL);
236     g_object_unref(atk_no_op_object_new(dummyObject));
237     g_object_unref(dummyObject);
238
239     // Add global listeners for AtkObject's signals.
240     for (const char** signalName = signalNames; *signalName; signalName++) {
241         unsigned id = atk_add_global_event_listener(axObjectEventListener, *signalName);
242         if (!id) {
243             String message = String::format("atk_add_global_event_listener failed for signal %s\n", *signalName);
244             InjectedBundle::singleton().outputText(message);
245             continue;
246         }
247
248         listenerIds.append(id);
249     }
250 }
251
252 bool AccessibilityNotificationHandler::disconnectAccessibilityCallbacks()
253 {
254     // Only disconnect if there is no notification handler.
255     if (!notificationHandlers.isEmpty() || globalNotificationHandler)
256         return false;
257
258     // AtkObject signals.
259     for (size_t i = 0; i < listenerIds.size(); i++) {
260         ASSERT(listenerIds[i]);
261         atk_remove_global_event_listener(listenerIds[i]);
262     }
263     listenerIds.clear();
264     return true;
265 }
266
267 } // namespace WTR
268
269 #endif // HAVE(ACCESSIBILITY)