Web IDL Conversions when resolving Promises in Gecko

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.