Another short one this week.
You can pass DOM Promises back through XPIDL
XPIDL is what we use to define XPCOM interfaces in Gecko. I think we’re trying to avoid XPCOM where we can, but sometimes you have to work with pre-existing XPCOM interfaces, and, well, you’re just stuck using it unless you want to rewrite what you’re working on.
What I’m working on lately is nsIProfiler, which is the interface to “SPS”, AKA the Gecko Profiler. nsIProfiler allows me to turn profiling on and off with various features, and then retrieve those profiles to send to a file, or to Cleopatra1.
What I’ve been working on recently is Bug 1116188 – [e10s] Stop using sync messages for Gecko profiler, which will probably have me adding new methods to nsIProfiler for async retrieval of profiles.
In the past, doing async stuff through XPCOM / XPIDL has meant using (or defining a new) callback interface which can be passed as an argument to the async method.
I was just about to go down that road, when ehsan (or was it jrmuizel? One of them, anyhow) suggested that I just pass a DOM Promise back.
I find that Promises are excellent. I really like them, and if I could pass a Promise back, that’d be incredible. But I had no idea how to do it.
It turns out that if I can ensure that the async methods are called such that there is a JS context on the stack, I can generate a DOM Promise, and pass it back to the caller as an “nsISupports”. According to ehsan, XPConnect will do the necessary magic so that the caller, upon receiving the return value, doesn’t just get this opaque nsISupports thing, but an actual DOM Promise. This is because, I believe, that DOM Promise is something that is defined via WebIDL. I think. I can’t say I fully understand the mechanics of XPConnect2, but this all sounded wonderful.
I even found an example in our new Service Worker code:
From dom/workers/ServiceWorkerManager.cpp (I’ve edited the method to highlight the Promise stuff):
// If we return an error code here, the ServiceWorkerContainer will
// automatically reject the Promise.
NS_IMETHODIMP
ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
nsIURI* aScopeURI,
nsIURI* aScriptURI,
nsISupports** aPromise)
{
AssertIsOnMainThread();
// XXXnsm Don't allow chrome callers for now, we don't support chrome
// ServiceWorkers.
MOZ_ASSERT(!nsContentUtils::IsCallerChrome());
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
// ...
nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
ErrorResult result;
nsRefPtr<Promise> promise = Promise::Create(sgo, result);
if (result.Failed()) {
return result.StealNSResult();
}
// ...
nsRefPtr<ServiceWorkerResolveWindowPromiseOnUpdateCallback> cb =
new ServiceWorkerResolveWindowPromiseOnUpdateCallback(window, promise);
nsRefPtr<ServiceWorkerRegisterJob> job =
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
queue->Append(job);
promise.forget(aPromise);
return NS_OK;
}
Notice that the outparam aPromise is an nsISupports**, and yet, I do believe the caller will end up handling a DOM Promise. Wicked!