[XPCOM] Keep the JS context with the Service/Method objects, we
[browser-dbus-bridge.git] / xpcom-dbusservice / DBusService.cpp
1 /**
2  * Browser D-Bus Bridge, XPCOM version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Lauri Mylläri, <lauri.myllari@movial.fi>
7  *           Kalle Vahlman, <kalle.vahlman@movial.com>
8  *
9  * The contents of this file are subject to the Mozilla Public License
10  * Version 1.1 (the "License"); you may not use this file except in
11  * compliance with the License. You may obtain a copy of the License at
12  * http://www.mozilla.org/MPL/
13  *
14  * Software distributed under the License is distributed on an "AS IS"
15  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
16  * License for the specific language governing rights and limitations
17  * under the License.
18  *
19  * The Original Code is the Browser D-Bus Bridge, XPCOM version.
20  *
21  * The Initial Developer of the Original Code is Movial Creative Technologies
22  * Inc. Portions created by Initial Developer are Copyright (C) 2008
23  * Movial Creative Technologies Inc. All Rights Reserved.
24  *
25  */
26
27 #include <stdio.h>
28 #include <glib.h>
29 #include <dbus/dbus-glib-lowlevel.h>
30 #include <dbus/dbus-glib.h>
31
32
33 #include "nsIGenericFactory.h"
34 #include "nsIInterfaceRequestorUtils.h"
35 #include "nsComponentManagerUtils.h"
36 #include "nsServiceManagerUtils.h"
37
38 #include "nsEmbedString.h"
39 #include "nsIMutableArray.h"
40 #include "nsArrayUtils.h"
41 #include "nsIXPConnect.h"
42
43 #include "IDBusService.h"
44
45 #include "DBusService.h"
46 #include "DBusMethod.h"
47 #include "DBusSignal.h"
48 #include "DBusMarshaling.h"
49
50 #include "bdb-debug.h"
51
52 //
53 // DBusService implementation
54 //
55
56 static
57 DBusHandlerResult _signal_filter(DBusConnection *connection,
58                                  DBusMessage *message,
59                                  void *user_data);
60
61 static DBusService *gDBusService = nsnull;
62
63 NS_IMPL_ISUPPORTS1(DBusService, IDBusService);
64
65 DBusService::DBusService() :
66     mSystemBus(nsnull),
67     mSessionBus(nsnull),
68     mSystemBusHasFilter(PR_FALSE),
69     mSessionBusHasFilter(PR_FALSE)
70 {
71     BDBLOG(("DBusService::DBusService()\n"));
72     mSystemBusSignalObservers.Init();
73     mSessionBusSignalObservers.Init();
74 }
75
76 DBusService::~DBusService()
77 {
78     BDBLOG(("DBusService::~DBusService()\n"));
79     /* FIXME - check if connections need to be released */
80 }
81
82 NS_IMETHODIMP
83 DBusService::GetSignal(PRUint32 aBusType,
84                        const nsACString& aInterfaceName,
85                        const nsACString& aSignalName,
86                        const nsACString& aSender,
87                        const nsACString& aObjectPath,
88                        IDBusSignal **_retval)
89 {
90     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
91     *_retval = nsnull;
92
93     GetConnection(aBusType);
94
95     if (aBusType == SYSTEM)
96     {
97         if (!mSystemBusHasFilter)
98         {
99             signalObservers = &mSystemBusSignalObservers;
100             mSystemBusHasFilter = PR_TRUE;
101         }
102     }
103     else if (aBusType == SESSION)
104     {
105         if (!mSessionBusHasFilter)
106         {
107             signalObservers = &mSessionBusSignalObservers;
108             mSessionBusHasFilter = PR_TRUE;
109         }
110     }
111     else
112     {
113         BDBLOG(("DBusService::GetSignal(): unknown bus type %d\n", aBusType));
114         return NS_ERROR_ILLEGAL_VALUE;
115     }
116
117     /* add filter only once for each connection */
118     if (signalObservers)
119         dbus_connection_add_filter(GetConnection(aBusType),
120                                    _signal_filter,
121                                    signalObservers,
122                                    nsnull);
123
124     IDBusSignal *signal = new DBusSignal(this,
125                                          aBusType,
126                                          aInterfaceName,
127                                          aSignalName,
128                                          aSender,
129                                          aObjectPath,
130                                          GetCurrentJSContext());
131
132     NS_ENSURE_TRUE(signal, NS_ERROR_OUT_OF_MEMORY);
133
134     NS_ADDREF(*_retval = signal);
135
136     return NS_OK;
137 }
138
139 NS_IMETHODIMP
140 DBusService::GetMethod(PRUint32 aBusType,
141                        const nsACString& aDestination,
142                        const nsACString& aObjectPath,
143                        const nsACString& aMethodName,
144                        const nsACString& aInterfaceName,
145                        const nsACString& aSignature,
146                        IDBusMethod **_retval)
147 {
148     *_retval = nsnull;
149
150     if (!GetConnection(aBusType))
151     {
152         BDBLOG(("DBusService::GetMethod()): invalid bus type %d\n",
153                aBusType));
154         return NS_ERROR_ILLEGAL_VALUE;
155     }
156
157     if (!dbus_signature_validate(PromiseFlatCString(aSignature).get(), nsnull))
158     {
159         BDBLOG(("DBusService::GetMethod()): invalid method signature '%s'\n",
160                PromiseFlatCString(aSignature).get()));
161         return NS_ERROR_ILLEGAL_VALUE;
162     }
163
164     IDBusMethod *method = new DBusMethod(this,
165                                          aBusType,
166                                          aDestination,
167                                          aObjectPath,
168                                          aMethodName,
169                                          aInterfaceName,
170                                          aSignature,
171                                          GetCurrentJSContext());
172
173     NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY);
174
175     NS_ADDREF(*_retval = method);
176
177     return NS_OK;
178 }
179
180 DBusPendingCall *DBusService::SendWithReply(PRUint32 aConnType,
181                                             DBusMessage *aMessage,
182                                             PRUint32 aTimeout)
183 {
184     DBusPendingCall *retval = nsnull;
185     DBusConnection *conn = GetConnection(aConnType);
186
187     if (!conn)
188         return nsnull;
189
190     if (!dbus_connection_send_with_reply(conn,
191                                          aMessage,
192                                          &retval,
193                                          aTimeout))
194         return nsnull;
195
196     return retval;
197 }
198
199 DBusMessage *DBusService::SendWithReplyAndBlock(PRUint32 aConnType,
200                                                 DBusMessage *aMessage,
201                                                 PRUint32 aTimeout,
202                                                 DBusError *aError)
203 {
204     DBusConnection *conn = GetConnection(aConnType);
205
206     if (!conn)
207         return nsnull;
208
209     return dbus_connection_send_with_reply_and_block(conn,
210                                                      aMessage,
211                                                      aTimeout,
212                                                      aError);
213 }
214
215 static
216 DBusHandlerResult _signal_filter(DBusConnection *connection,
217                                  DBusMessage *message,
218                                  void *user_data)
219 {
220     if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
221     {
222         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
223     }
224
225     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *observerHash =
226         (nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *) user_data;
227
228     BDBLOG(("_signal_filter: %s.%s\n",
229            dbus_message_get_interface(message),
230            dbus_message_get_member(message)));
231
232     nsCAutoString observerKey;
233
234     observerKey.Assign(dbus_message_get_interface(message));
235     observerKey.Append(NS_LITERAL_CSTRING("."));
236     observerKey.Append(dbus_message_get_member(message));
237
238     BDBLOG(("  observerKey: '%s'\n",
239            PromiseFlatCString(observerKey).get()));
240
241     nsTArray<nsWeakPtr> *observerList = nsnull;
242
243     observerHash->Get(observerKey, &observerList);
244     if (observerList)
245     {
246         BDBLOG(("  got observerList\n"));
247         DBusService *service = DBusService::GetSingleton();
248         
249         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
250             nsCAutoString t;
251             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
252             signal->GetInterfaceName(t);
253             BDBLOG(("    interface : %s\n", PromiseFlatCString(t).get()));
254             signal->GetSignalName(t);
255             BDBLOG(("    signal    : %s\n", PromiseFlatCString(t).get()));
256             signal->GetSender(t);
257             BDBLOG(("    sender    : %s\n", PromiseFlatCString(t).get()));
258             if (!t.IsEmpty() && !t.Equals(dbus_message_get_sender(message)))
259             {
260                 BDBLOG(("    sender does not match\n"));
261                 break;
262             }
263             signal->GetObjectPath(t);
264             BDBLOG(("    object    : %s\n", PromiseFlatCString(t).get()));
265             if (!t.IsEmpty() && !t.Equals(dbus_message_get_path(message)))
266             {
267                 BDBLOG(("    objectPath does not match\n"));
268                 break;
269             }
270
271             /* do callback */
272
273             DBusMessageIter iter;
274             nsCOMPtr<nsIMutableArray> args_array;
275
276             dbus_message_iter_init(message, &iter);
277             JSContext *cx;
278             signal->GetJSContext(&cx);
279             args_array = getArrayFromIter(cx, &iter);
280
281             PRUint32 arg_items;
282             args_array->GetLength(&arg_items);
283             BDBLOG(("  arg_items: %d items\n", arg_items));
284
285             /* arguments are packed as an array into an nsIVariant */
286             nsIVariant **callback_args = new nsIVariant*[arg_items];
287             nsCOMPtr<nsIWritableVariant> args = do_CreateInstance("@mozilla.org/variant;1");
288             for (int i = 0; i < arg_items; i++)
289             {
290                 nsCOMPtr<nsIVariant> arg = do_QueryElementAt(args_array, i);
291                 callback_args[i] = arg;
292                 NS_ADDREF(arg);
293             }
294             args->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
295                              &NS_GET_IID(nsIVariant),
296                              arg_items,
297                              callback_args);
298             for (int i = 0; i < arg_items; i++)
299                 NS_RELEASE(callback_args[i]);
300             delete[] callback_args;
301
302             nsCOMPtr<IDBusSignalObserver> callback;
303             signal->GetOnEmit(getter_AddRefs(callback));
304
305             service->SetInsideEmit(TRUE);
306             callback->OnSignal(args);
307             service->SetInsideEmit(FALSE);
308
309         }
310         
311         /* Check if we have queued observer changes */
312         service->CheckSignalObserverQueue();
313         
314         return DBUS_HANDLER_RESULT_HANDLED;
315     }
316     else
317     {
318         BDBLOG(("  no observer found\n"));
319         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
320     }
321 }
322
323 static
324 void BuildRule(IDBusSignal *aSignal, nsACString& aRetval)
325 {
326     nsCAutoString tmp;
327
328     aRetval.Assign(NS_LITERAL_CSTRING("type='signal',interface='"));
329     aSignal->GetInterfaceName(tmp);
330     aRetval.Append(tmp);
331     aRetval.Append(NS_LITERAL_CSTRING("',member='"));
332     aSignal->GetSignalName(tmp);
333     aRetval.Append(tmp);
334     aRetval.Append(NS_LITERAL_CSTRING("'"));
335 }
336
337 void DBusService::CheckSignalObserverQueue()
338 {
339     BDBLOG((__FUNCTION__));
340
341     PRUint32 i = mRemovedSignals.Length();
342     while (i-- > 0)
343     {
344         RemoveSignalObserver(mRemovedSignals[i]);
345         mRemovedSignals.RemoveElementAt(i);
346     }
347     i = mAddedSignals.Length();
348     while (i-- > 0)
349     {
350         AddSignalObserver(mAddedSignals[i]);
351         mAddedSignals.RemoveElementAt(i);
352     }
353 }
354
355 void DBusService::AddSignalObserver(IDBusSignal *aSignal)
356 {
357     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
358     nsCAutoString observerKey;
359     nsCAutoString tmp;
360
361     if (mInsideEmit)
362     {
363         mAddedSignals.AppendElement(aSignal);
364         return;
365     }
366
367     BDBLOG(("DBusService::AddSignalObserver()\n"));
368
369     aSignal->GetInterfaceName(tmp);
370     BDBLOG(("  aInterface : %s\n", PromiseFlatCString(tmp).get()));
371     observerKey.Assign(tmp);
372     observerKey.Append(".");
373
374     aSignal->GetSignalName(tmp);
375     BDBLOG(("  aSignal    : %s\n", PromiseFlatCString(tmp).get()));
376     observerKey.Append(tmp);
377
378     BDBLOG(("  observerKey: %s\n", PromiseFlatCString(observerKey).get()));
379
380     nsTArray<nsWeakPtr> *observerList = nsnull;
381
382     PRUint32 bus_type = 0;
383     aSignal->GetBusType(&bus_type);
384     if (bus_type == SYSTEM)
385         signalObservers = &mSystemBusSignalObservers;
386     else if (bus_type == SESSION)
387         signalObservers = &mSessionBusSignalObservers;
388     signalObservers->Get(observerKey, &observerList);
389     if (observerList)
390     {
391         /* append to list */
392         BDBLOG(("  got observerList\n"));
393         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
394         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
395         observerList->AppendElement(weakPtr);
396     }
397     else
398     {
399         /* create a new list */
400         BDBLOG(("  no observerList found\n"));
401         observerList = new nsTArray<nsWeakPtr>;
402         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
403         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
404         observerList->AppendElement(weakPtr);
405         signalObservers->Put(observerKey, observerList);
406         
407         /* add match rule for interface.signal */
408         PRUint32 busType;
409         nsCAutoString matchRule;
410
411         aSignal->GetBusType(&busType);
412         BuildRule(aSignal, matchRule);
413         BDBLOG(("  new match rule: %s\n", PromiseFlatCString(matchRule).get()));
414         dbus_bus_add_match(GetConnection(busType),
415                            PromiseFlatCString(matchRule).get(),
416                            nsnull);
417     }
418 }
419
420 void DBusService::RemoveSignalObserver(IDBusSignal *aSignal)
421 {
422     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
423     nsCAutoString observerKey;
424     nsCAutoString tmp;
425
426     if (mInsideEmit)
427     {
428         mRemovedSignals.AppendElement(aSignal);
429         return;
430     }
431
432     BDBLOG(("DBusService::RemoveSignalObserver()\n"));
433
434     aSignal->GetInterfaceName(tmp);
435     BDBLOG(("  aInterface : %s\n", PromiseFlatCString(tmp).get()));
436     observerKey.Assign(tmp);
437     observerKey.Append(".");
438
439     aSignal->GetSignalName(tmp);
440     BDBLOG(("  aSignal    : %s\n", PromiseFlatCString(tmp).get()));
441     observerKey.Append(tmp);
442
443     BDBLOG(("  observerKey: %s\n", PromiseFlatCString(observerKey).get()));
444
445     nsTArray<nsWeakPtr> *observerList = nsnull;
446
447     PRUint32 bus_type = 0;
448     aSignal->GetBusType(&bus_type);
449     if (bus_type == SYSTEM)
450         signalObservers = &mSystemBusSignalObservers;
451     else if (bus_type == SESSION)
452         signalObservers = &mSessionBusSignalObservers;
453     signalObservers->Get(observerKey, &observerList);
454     if (observerList)
455     {
456         BDBLOG(("  got observerList\n"));
457         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
458         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
459         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
460             nsCAutoString t;
461             nsCAutoString ob;
462             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
463             signal->GetInterfaceName(t);
464             ob.Assign(t);
465             ob.Append(".");
466             signal->GetSignalName(t);
467             ob.Append(t);
468             BDBLOG(("    signal : %s\n", PromiseFlatCString(ob).get()));
469         }
470         BDBLOG(("  call observerList->RemoveElement\n"));
471         observerList->RemoveElement(weakPtr);
472         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
473             nsCAutoString t;
474             nsCAutoString ob;
475             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
476             signal->GetInterfaceName(t);
477             ob.Assign(t);
478             ob.Append(".");
479             signal->GetSignalName(t);
480             ob.Append(t);
481             BDBLOG(("    signal : %s\n", PromiseFlatCString(ob).get()));
482         }
483
484         // if list is empty, remove match rule
485         if (observerList->Length() == 0)
486         {
487             PRUint32 busType;
488             nsCAutoString matchRule;
489
490             aSignal->GetBusType(&busType);
491             BuildRule(aSignal, matchRule);
492             BDBLOG(("  remove match rule: %s\n", PromiseFlatCString(matchRule).get()));
493             dbus_bus_remove_match(GetConnection(busType),
494                                   PromiseFlatCString(matchRule).get(),
495                                   nsnull);
496             signalObservers->Remove(observerKey); 
497         }
498         BDBLOG(("  done\n"));
499     }
500     else
501     {
502         BDBLOG(("  ERROR: no observerList found!\n"));
503     }
504 }
505
506 JSContext *DBusService::GetCurrentJSContext()
507 {
508     // try to get a JS context (code borrowed from xpcsample1.cpp)
509
510     // get the xpconnect service
511     nsresult rv;
512     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
513     if(NS_FAILED(rv))
514         return nsnull;
515     BDBLOG(("    got nsIXPConnect\n"));
516
517     // get the xpconnect native call context
518     nsAXPCNativeCallContext *callContext = nsnull;
519     xpc->GetCurrentNativeCallContext(&callContext);
520     if(!callContext)
521     {
522     BDBLOG(("    callContext :(\n"));
523         return nsnull;
524     }
525     // Get JSContext of current call
526     JSContext* cx;
527     rv = callContext->GetJSContext(&cx);
528     if(NS_FAILED(rv) || !cx)
529         return nsnull;
530     BDBLOG(("    got JSContext\n"));
531
532     return cx;
533 }
534
535 DBusConnection *DBusService::GetConnection(PRUint32 aConnType)
536 {
537     BDBLOG(("DBusService::GetConnection(%d)\n", aConnType));
538
539     if (aConnType == SYSTEM)
540     {
541         if (mSystemBus == nsnull)
542         {
543             mSystemBus = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
544             if (!mSystemBus)
545                 return nsnull;
546             dbus_connection_set_exit_on_disconnect(mSystemBus, PR_FALSE);
547             dbus_connection_setup_with_g_main(mSystemBus, NULL);
548         }
549         return mSystemBus;
550     }
551     else if (aConnType == SESSION)
552     {
553         if (mSessionBus == nsnull)
554         {
555             mSessionBus = dbus_bus_get(DBUS_BUS_SESSION, NULL);
556             if (!mSessionBus)
557                 return nsnull;
558             dbus_connection_set_exit_on_disconnect(mSessionBus, PR_FALSE);
559             dbus_connection_setup_with_g_main(mSessionBus, NULL);
560         }
561         return mSessionBus;
562     }
563     return nsnull;
564 }
565
566 DBusService *
567 DBusService::GetSingleton()
568 {
569     BDBLOG(("DBusService::GetSingleton() called: "));
570
571     if (!gDBusService)
572     {
573         BDBLOG(("creating new DBusService\n"));
574         gDBusService = new DBusService();
575     }
576
577     if (gDBusService)
578     {
579         BDBLOG(("adding reference to existing DBusService\n"));
580         NS_ADDREF(gDBusService);
581     }
582
583     return gDBusService;
584 }
585
586
587
588 //
589 // Module implementation
590 //
591
592 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DBusService, DBusService::GetSingleton);
593
594 static const nsModuleComponentInfo components[] =
595 {
596     {
597         "DBus service",
598         DBUSSERVICE_CID,
599         "@movial.fi/dbus/service;1",
600         DBusServiceConstructor
601     },
602     {
603         "DBus method",
604         DBUSMETHOD_CID,
605         "@movial.fi/dbus/method;1",
606         nsnull
607     },
608     {
609         "DBus signal",
610         DBUSSIGNAL_CID,
611         "@movial.fi/dbus/signal;1",
612         nsnull
613     }
614 };
615
616 NS_IMPL_NSGETMODULE(nsDBusServiceModule, components);
617
618
619
620
621 /* vim: set cindent ts=4 et sw=4: */