There are an interesting situations in specifications where you have something like:
interface PaymentRequest { Promise<PaymentResponse> show( optional Promise<PaymentDetailsUpdate> detailsPromise ); }
The optional promise is expected to resolve to an IDL PaymentDetailsPromise
, but what happens if we call:
const paymentRequest.show(Promise.resolve()); const paymentRequest.show(Promise.resolve(null)); const paymentRequest.show(Promise.resolve("a string")); // and so on...
Naturally, awaiting the resolved value should eventually cause show()
to reject with some DOMException
.
In Gecko (c++), to await pending promises to fulfill we use a neat thing called a “NativeHandler”. This is really easy to use, you just do:
// PaymentRequest::Show() method implementation if (aDetailsPromise.WasPassed()) { aDetailsPromise.Value().AppendNativeHandler(this); }
Calling AppendNativeHandler
on this
implies, “Call this
object back when the promise settles. Naturally, that expects this
to have some way of getting the callback using a standard protocol:
// Promise resolver! void PaymentRequest::ResolvedCallback(JSContext* aCx, JS::Handle aValue) {} // Promise rejector! void PaymentRequest::RejectedCallback(JSContext* aCx, JS::Handle aValue) {}
Now, to do the actual conversion we have aValue
coming in from a certain js context (aCx
). So, in our situation, we need to do the conversion from any JS value, to a PaymentDetailsUpdate
dictionary.
The conversion is done like so:
// first we declare that we want a PaymentDetailsUpdate // as a rooted dictionary (whatever that means doesn't matter) RootedDictionary details(aCx); // Now, let's try to do the conversion. // What's interesting here is that if it fails, // it will return false AND it will put an JS exception // pending on the JS Context as a side effect! if (!details.Init(aCx, aValue)) { // we tell the JS engine that we are going to throw // the exception. result.MightThrowJSException(); // but we want to throw out own exception, so we literally "steal it" // from the JS context. result.StealExceptionFromJSContext(aCx); // And now we can craft our own exception to throw... result.ThrowAbortError("Conversion to PaymentDetailsUpdate failed."); // do other stuff... return; } // Otherwise, conversion worked! we are good... keep going.
As we can see above, the “conversion” from JS to WebIDL happened in details.Init()
. That means that Init()
is really in the best position to handle any JS type checks, be it undefined, null, number, string, etc.