How to get an nsIGlobalObject* from a JSContext*
I’m working on a patch for bug 1116188 to make gathering profiles from subprocesses asynchronous. In order to do that, I’m exposing a new method on nsIProfiler called getProfileDataAsync that is returning a DOM Promise. What’s interesting about this is that I’m returning a DOM Promise from C++! 1
In order to construct a DOM Promise in C++, I need to hand it something that implements nsIGlobalObject. I suspect that this helps the Promise determine which memory region that it belongs to.
My new method gets a JSContext* because I’ve got the [implicit_jscontext] bit about the method definition in the nsIProfiler.idl file… so how do I go about turning that into an nsIGlobalObject?
Here’s the maneuver:
// Where aCX is your JSContext*:
nsIGlobalObject* go = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
That will, as the name suggests, return either an nsIGlobalObject*, or a nullptr.
Resolving a DOM Promise from C++ with a JS Object
For my patch for bug 1116188, it’s all well and good to create a DOM Promise, but you have to resolve or reject that Promise for it to have any real value.
In my case, I wanted to take a string, parse it into a JS Object, and resolve with that.
Resolving or rejecting a DOM Promise in Javascript is pretty straight-forward – you’re given back resolve / reject function, and you just need to call those with your results and you’re done.
In C++, things get a little hairier. As I discovered in my most recent episode of The Joy of Coding, conditions need to be right in order for this to work out.
Here’s what I ended up doing (I’ve simplified the method somewhat to remove noise):
void
ProfileGatherer::Finish()
{
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
JSAutoCompartment ac(cx, mPromise->GlobalJSObject());
// Now parse the JSON so that we resolve with a JS Object.
JS::RootedValue val(cx);
{
UniquePtr<char[]> buf = mWriter.WriteFunc()->CopyData();
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get()));
MOZ_ALWAYS_TRUE(JS_ParseJSON(cx, static_cast<const char16_t*>(js_string.get()),
js_string.Length(), &val));
}
mPromise->MaybeResolve(val);
}
The key parts here are getting the AutoJSAPI on the stack, initting it, gettings its JSContext, and then putting the JSAutoCompartment on the stack. Note that I had to pass not only the JSContext, but the global JS Object for the Promise as well – I suspect that’s, again, to ensure that the right compartment is being entered. Otherwise, I start failing assertions like crazy.
Note that the code above is by no means perfect – I’m missing error handling functions for when the JSON parsing goes wrong. In that case, I should probably reject the Promise instead. bz pointed me to a good example of that going on here in Fetch.cpp:
if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
if (!JS_IsExceptionPending(cx)) {
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
JS::Rooted<JS::Value> exn(cx);
DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
MOZ_ASSERT(gotException);
JS_ClearPendingException(cx);
localPromise->MaybeReject(cx, exn);
return;
}
localPromise->MaybeResolve(cx, json);
return;
I’ll probably end up doing something similar in the next iteration of my patch.