I'm attaching some files which I'm calling nsJSCaller.cpp, etc....
The problem I'm trying to solve is that a number of people are calling
JS_PushArguments()/JS_PopArguments directly from their component in
order do create argument lists for calls like
window->Open()/window->OpenDialog() and so forth... this is mainly bad
because:
- we've had a history of people misusing JS_PopArgs (i.e. not calling it
when they should) which confuses the heck out of the JS GC
- this way you don't have to link against libmozjs.so just to open a
window.
so what I've created is a generic interface and object, accessable only
from C++, for creating an argument list, calling a function that
requires it, and then destroying the argument list..
I'm going to put this in xpfe/appshell (unless someone else has a better
idea.. maybe in libxpconnect?)
Comments? reviews/super reviews? Anyone got a better name?
Alec
#include "nsISupports.h"
#include "nsIScriptContext.h"
// stdarg must be before jsapi so we get JS_PushArgumentsVA
#include "stdarg.h"
#include "jsapi.h"
/*
* How to use this interface:
* Say you want to call nsIDOMWindowInternal::OpenDialog() and pass
* one object, an nsIFoo* as an argument.
// create the closure object - will get passed to your function
struct window_opener_args {
nsIDOMWindowInternal *parent;
nsIDOMWindowInternal *child;
};
// when this is called, argv has been set up for us
// note that we know we were called with 4 arguments.. this could also
// be passed in via the closure
static nsresult
openWindow(JSContext *cx, void *aClosure, jsval* argv, PRUint32 argc)
{
window_opener_args *wargs = (window_opener_args*)aClosure;
return wargs->parent->OpenDialog(cx, argv, 4,
&window_opener_args->child);
}
... later, in your code ...
// get the foo object so we can pass it in
nsCOMPtr<nsIFoo> foo;
someObject->GetFoo(getter_AddRefs(foo));
// the JSCaller object is a service because it contains no state
// (i.e. it can be shared amongst all consumers)
nsCOMPtr<nsIJSCaller> caller = do_GetService(NS_JSCALLER_PROGID, &rv);
// now make the call. Note that:
// - our closure contains both an in parameter, |parent|
// and an out parameter, |child|
// - passing in our callback as a pointer to a function
// - first 3 arguments to openDialog() are well known.
// - using %ip to pass a XPCOM object (see JS_PushArguments
// for other formatting rules)
// - manually casting the nsCOMPtr to the raw ptr, so it gets
// sent as a vararg correctly
// - |rv| is the result of your callback, so you can bubble up failure
window_opener_args wargs = { someWindow, nsnull };
rv = caller->CallFunction(cx, openWindow, (void *)wargs, "sss%ip",
"_blank", "chrome,modal",
"chrome://package/content/",
(nsIFoo*)foo);
// now, inside of the the window, window.arguments[0] is |foo|
// also at this point, wargs->child is the new nsIDOMWindow
*/
typedef nsresult (*JSCallerCallback)(JSContext *cx, void *aClosure,
jsval *argv);
#define NS_IJSCALLER_IID_STR "b05fac34-1dd1-11b2-ac07-cca38251f2f7"
#define NS_IJSCALLER_IID \
{0xb05fac34, 0x1dd1, 0x11b2, \
{ 0xac, 0x07, 0xcc, 0xa3, 0x82, 0x51, 0xf2, 0xf7 }}
class NS_NO_VTABLE nsIJSCaller : public nsISupports
{
public:
NS_DEFINE_STATIC_IID_ACCESSOR(NS_IJSCALLER_IID);
NS_IMETHOD CallFunction(nsIScriptContext *aContext,
JSCallerCallback *aCallback,
void *aClosure,
const char *formatString, ...) = 0;
};
#define NS_DECL_NSIJSCALLER \
NS_IMETHOD CallFunction(nsIScriptContext *aContext, JSCallerCallback *aCallback,
void *aClosure, const char *formatString, ...);
#include "nsIJSCaller.h"
#define NS_JSCALLER_CID \
{ /* 5ea4f5b2-044c-40d1-b9ea-0dcc14c5c367 */ \
0x5ea4f5b2, \
0x044c, \
0x40d1, \
{ 0xb9, 0xea, 0x0d, 0xcc, 0x14, 0xc5, 0xc3, 0x67 }}
#define NS_JSCALLER_CONTRACTID \
"@mozilla.org/javascript-caller-glue;1"
class nsJSCaller : public nsIJSCaller {
public:
nsJSCaller();
virtual ~nsJSCaller();
NS_DECL_ISUPPORTS
NS_DECL_NSIJSCALLER
private:
};
#include "nsJSCaller.h"
nsJSCaller::nsJSCaller()
{
NS_INIT_ISUPPORTS();
}
nsJSCaller::~nsJSCaller()
{
}
NS_IMPL_ISUPPORTS1(nsJSCaller, nsIJSCaller);
NS_IMETHODIMP
nsJSCaller::CallFunction(nsIScriptContext *aContext,
JSCallerCallback *aCallback,
void *aClosure,
const char *aFormatString, ...)
{
nsresult rv;
void *mark;
jsval *argv;
JSContext *cx = (JSContext *)aContext->GetNativeContext();
// set up arguments
va_list ap;
va_start(ap, aFormatString);
argv = JS_PushArgumentsVA(cx, &mark, aFormatString, ap);
va_end(ap);
if (!argv)
return NS_ERROR_FAILURE;
// now make the call
rv = (*aCallback)(cx, aClosure, argv);
// always pop the arguments, even in case of failure of rv!
JS_PopArguments(cx, mark);
return rv;
}