[jscorebus] Fix memory leaks related to callback argument arrays
[browser-dbus-bridge.git] / jscorebus / jscorebus-method.c
1 /**
2  * Browser D-Bus Bridge, JavaScriptCore version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include <glib.h>
24 #include <dbus/dbus.h>
25 #include <JavaScriptCore/JavaScript.h>
26
27 #include "jscorebus-marshal.h"
28 #include "jscorebus-method.h"
29
30 /* Private data for methods */
31 typedef struct _MethodPrivate
32 {
33   char *destination;
34   char *object_path;
35   char *method_name;
36   char *interface;
37   char *signature;
38
39   DBusConnection *connection;
40   DBusMessage *message;
41   DBusPendingCall *pending_reply;
42
43   JSGlobalContextRef context;
44   JSObjectRef this;
45   JSObjectRef onreply;
46   JSObjectRef onerror;
47   gboolean async;
48 } MethodPrivate;
49
50 /* Utility functions */
51 static
52 JSClassRef get_class ();
53
54 static
55 JSValueRef call_sync(JSContextRef context, MethodPrivate *priv);
56 static
57 JSValueRef call_async(JSContextRef context, MethodPrivate *priv);
58 static
59 void call_onreply(JSContextRef context,
60                   MethodPrivate *priv,
61                   DBusMessage *message);
62 static
63 void call_onerror(JSContextRef context,
64                   MethodPrivate *priv,
65                   DBusMessage *message);
66
67 /* JSCore methods */
68 static
69 void method_finalize(JSObjectRef object);
70
71 static
72 bool method_set_property(JSContextRef context,
73                          JSObjectRef object,
74                          JSStringRef propertyName,
75                          JSValueRef value,
76                          JSValueRef* exception);
77
78 static
79 JSValueRef method_call (JSContextRef context,
80                         JSObjectRef function,
81                         JSObjectRef thisObject,
82                         size_t argumentCount,
83                         const JSValueRef arguments[],
84                         JSValueRef *exception);
85
86 /* The Method Class */
87 static const
88 JSClassDefinition method_jsclass_def =
89 {
90   0,
91   kJSClassAttributeNone,
92   "DBusMethod",
93   NULL,
94
95   NULL,
96   NULL,
97   
98   NULL, /* Initialize */
99   method_finalize,
100   
101   NULL,
102   NULL,
103   method_set_property, /* SetProperty */
104   NULL,
105   NULL,
106   
107   method_call, /* CallAsFunction */
108   NULL, /* Constructor */
109   NULL,
110   NULL
111 };
112
113 /*** JSCore methods */
114
115 static
116 void method_finalize(JSObjectRef object)
117 {
118   MethodPrivate *priv = (MethodPrivate *)JSObjectGetPrivate(object);
119
120   if (priv != NULL)
121   {
122
123     if (priv->pending_reply != NULL)
124     {
125       dbus_pending_call_cancel(priv->pending_reply);
126     }
127   
128     g_free(priv->destination);
129     g_free(priv->object_path);
130     g_free(priv->method_name);
131     g_free(priv->interface);
132     g_free(priv->signature);
133     
134     dbus_connection_unref(priv->connection);
135
136     priv->this = NULL;
137     priv->onreply = NULL;
138     priv->onerror = NULL;
139     priv->context = NULL;
140     
141     g_free(priv);
142     priv = NULL;
143   }
144 }
145
146 static
147 bool method_set_property(JSContextRef context,
148                          JSObjectRef object,
149                          JSStringRef propertyName,
150                          JSValueRef value,
151                          JSValueRef* exception)
152 {
153   MethodPrivate *priv;
154
155   priv = (MethodPrivate *)JSObjectGetPrivate(object);
156   g_assert(priv != NULL);
157
158   if (JSStringIsEqualToUTF8CString(propertyName, "async"))
159   {
160     if (JSValueIsBoolean(context, value))
161     { 
162       priv->async = JSValueToBoolean(context, value);
163       return TRUE;
164     }
165     
166     /* TODO: set exception */
167     g_warning("Tried to set a non-boolean to 'async'");
168     return TRUE;
169   } else if (JSStringIsEqualToUTF8CString(propertyName, "onreply")) {
170     priv->onreply = function_from_jsvalue(context, value, exception);
171     return TRUE;
172   } else if (JSStringIsEqualToUTF8CString(propertyName, "onerror")) {
173     priv->onerror = function_from_jsvalue(context, value, exception);
174     return TRUE;
175   }
176
177   return FALSE;
178 }
179
180 static
181 JSValueRef method_call (JSContextRef context,
182                         JSObjectRef function,
183                         JSObjectRef thisObject,
184                         size_t argumentCount,
185                         const JSValueRef arguments[],
186                         JSValueRef *exception)
187 {
188   int i;
189   MethodPrivate *priv;
190   DBusMessageIter iter;
191   JSValueRef ret;
192         
193   priv = (MethodPrivate *)JSObjectGetPrivate(function);
194   g_assert(priv != NULL);
195
196   priv->message = dbus_message_new_method_call(priv->destination,
197                                                priv->object_path,
198                                                priv->interface,
199                                                priv->method_name);
200   
201   if (argumentCount > 0)
202   {
203     /* Push arguments to the message */
204     dbus_message_iter_init_append (priv->message, &iter);
205     if (!jsvalue_array_append_to_message_iter(context,
206                                               arguments, argumentCount,
207                                               &iter, priv->signature))
208     {
209       dbus_message_unref(priv->message);
210       priv->message = NULL;
211     }
212   }
213   
214   if (priv->async)
215   {
216     ret = call_async(context, priv);
217   } else {
218     ret = call_sync(context, priv);
219   }
220
221   if (priv->message != NULL)
222   {
223     dbus_message_unref(priv->message);
224   }
225   priv->message = NULL;
226
227   return ret;
228 }
229
230 /*** Utility functions */
231
232 static
233 JSClassRef get_class ()
234 {
235   JSClassRef jsclass = (JSClassRef)jsclass_lookup(&method_jsclass_def);
236   
237   if (G_LIKELY(jsclass != NULL))
238   {
239     return jsclass;
240   }
241   
242   jsclassdef_insert("DBusMethod", &method_jsclass_def);
243   jsclass = (JSClassRef)jsclass_lookup(&method_jsclass_def);
244
245   g_assert(jsclass != NULL);
246
247   return jsclass;
248 }
249
250 static
251 JSValueRef call_sync(JSContextRef context, MethodPrivate *priv)
252 {
253   /**
254    * Set up reply callback from the method.onReply property if available and
255    * send the message
256    */
257
258   if (priv->message == NULL)
259   {
260     call_onerror(context, priv, NULL);
261     return JSValueMakeUndefined(context);
262   }
263
264   if (priv->onreply != NULL)
265   {
266     DBusMessage *reply_message;
267     
268     reply_message = dbus_connection_send_with_reply_and_block(
269                       priv->connection,
270                       priv->message, -1, NULL);
271     if (reply_message != NULL)
272     {
273       if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_ERROR)
274       {
275         call_onerror(context, priv, reply_message);
276       } else if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
277         call_onreply(context, priv, reply_message);
278       } else {
279         g_warning("Unknown reply!");
280       }
281       dbus_message_unref(reply_message);
282     } else {
283       // TODO: set exception
284       g_warning("Failed to send message to %s", priv->destination);
285       call_onerror(context, priv, NULL);
286     }
287   } else {
288     if (!dbus_connection_send(priv->connection, priv->message, NULL))
289     {
290       // TODO: set exception
291       g_warning("Failed to send message to %s", priv->destination);
292       call_onerror(context, priv, NULL);
293     }
294   }  
295
296   return JSValueMakeUndefined(context);
297 }
298
299 static
300 void pending_call_notify(DBusPendingCall *pending,
301                          void *user_data)
302 {
303   DBusMessage *reply_message;
304   MethodPrivate *priv;
305   
306   g_assert(user_data != NULL);
307   
308   priv = (MethodPrivate *)user_data;
309
310   if (pending == NULL)
311   {
312     /* Disconnected!
313      * TODO: How do we handle these?
314      */
315     g_warning("Disconnected from the bus!");
316     priv->pending_reply = NULL;
317     return;
318   }
319  
320   priv->pending_reply = NULL;
321   reply_message = dbus_pending_call_steal_reply(pending);
322
323   if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_ERROR)
324   {
325     call_onerror(priv->context, priv, reply_message);
326   } else if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
327     call_onreply(priv->context, priv, reply_message);
328   } else {
329     g_warning("Unknown reply!");
330   }
331   if (reply_message != NULL)
332   {
333     dbus_message_unref(reply_message);
334   }
335 }
336
337 static
338 JSValueRef call_async(JSContextRef context, MethodPrivate *priv)
339 {
340         dbus_uint32_t serial = 0;
341
342   if (priv->message == NULL)
343   {
344     call_onerror(context, priv, NULL);
345   }
346
347   if (priv->onreply != NULL)
348   {
349     if (dbus_connection_send_with_reply(
350           priv->connection,
351           priv->message, &priv->pending_reply, -1))
352     {
353       dbus_pending_call_set_notify(priv->pending_reply, pending_call_notify,
354                                    priv, NULL);
355     } else {
356       // TODO: set exception
357       call_onerror(context, priv, NULL);
358     }
359   } else {
360     dbus_message_set_no_reply(priv->message, TRUE);
361
362     if (!dbus_connection_send(priv->connection, priv->message, &serial))
363     {
364       // TODO: set exception
365       call_onerror(context, priv, NULL);
366     }
367   }
368   
369   return JSValueMakeUndefined(context);
370 }
371
372 static
373 void call_onreply(JSContextRef context,
374                   MethodPrivate *priv,
375                   DBusMessage *message)
376 {
377   if (priv->onreply == NULL)
378   {
379     return;
380   }
381
382   call_function_with_message_args(context, priv->this, priv->onreply, message);
383 }
384
385 static
386 void call_onerror(JSContextRef context,
387                   MethodPrivate *priv,
388                   DBusMessage *message)
389 {
390   if (priv->onerror == NULL)
391   {
392     return;
393   }
394
395   if (message == NULL)
396   {
397     /* We couldn't send the message */
398     JSStringRef name = JSStringCreateWithUTF8CString("MessageError");
399     JSStringRef str = JSStringCreateWithUTF8CString("Could not send message");
400     JSValueRef *args = g_new(JSValueRef, 2);
401     
402     args[0] = JSValueMakeString(context, name);
403     args[1] = JSValueMakeString(context, str);
404     
405     JSObjectCallAsFunction(context, priv->onerror, priv->this,
406                            2, args, NULL);
407     JSStringRelease(name);
408     JSStringRelease(str);
409     g_free(args);
410     return;
411   }
412
413   call_function_with_message_args(context, priv->this, priv->onerror, message);
414 }
415
416 /*** Public API */
417
418 /* NOTE: Takes ownership of the arguments! */
419 JSObjectRef jscorebus_create_method (JSGlobalContextRef context,
420                                      DBusConnection *connection,
421                                      char *destination,
422                                      char *object_path,
423                                      char *method_name,
424                                      char *interface,
425                                      char *signature,
426                                      JSObjectRef thisObject,
427                                      JSValueRef* exception)
428 {
429   MethodPrivate *priv = NULL;
430
431   g_return_val_if_fail(destination != NULL
432                        && object_path != NULL
433                        && method_name != NULL,
434                        NULL);
435
436   priv = g_new0(MethodPrivate, 1);
437
438   /* TODO: Maybe these should be JSStrings after all, to avoid copies? */
439   priv->destination = destination;
440   priv->object_path = object_path;
441   priv->method_name = method_name;
442
443   priv->interface = interface;
444   priv->signature = signature;
445
446   priv->async = true;
447
448   priv->connection = dbus_connection_ref(connection);
449   
450   priv->context = context;
451
452   priv->this = thisObject;
453
454   return JSObjectMake(context, get_class(), priv);
455 }