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.
You should pass your global object directly to jsapi.Init(), and treat it fallibly:
if (!jsapi.Init(myglobal)) {
return false;
}
// No need for a JSAutoCompartment here.
You should also not add new usages of JS_GetPendingException/JS_SetPendingException/JS_ReportPendingException. Use the API on AutoJSAPI instead.
@bholley,
All good things to know – thank you!
NS_ConvertUTF8toUTF16 accepts a const char* parameter, although the explicit dependent string doesn’t hurt.