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.

Implementing a Permissions Policy in Gecko

Adding a permissions policy (previously “feature policy”) to Gecko is pretty straight forward, thankfully. We have a great API that makes adding permissions policies really easy – see FeaturePolicy.h.

A Permissions Policy has a name and an “allowlist”. For instance, gamepad’s permission name is “gamepad” and its allow list is “self” (only allow same origin).

In Gecko, we can declare the above is FeaturePolicyUtils.cpp in either ” sSupportedFeatures[]” or “sExperimentalFeatures[]”. We treat “gamepad” as experimental for now:

static FeatureMap sExperimentalFeatures[] = {
  ... other permissions...
  {"gamepad", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
  ... 
}

Generally, Permissions Policy affect the functionality of APIs in the platform. If a permissions policy is denied to an iframe then calling a method, for instance, will cause that method to throw.

Here is an example from the Gamepad API, which is in navigator.cpp:

// Check if we are "allowed to use" some feature
// in this case, "gamepad" 
if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(),
u"gamepad"_ns))) {
  aRv.ThrowSecurityError(
   "Document's Permission Policy does not allow calling "
   "getGamepads() from this context.");
  return;
}

In JS, then, the following would throw in any third-party context (e.g., a remote iframe that doesn’t have an allowed=”gamepad” attribute declared):

navigator.getGamepads(); // throws SecurityError DOM Exception

Tricky rebases in Phabricator

Got into a tricky situation lately where I was working on feature in Gecko on an old branch. Behind the scenes, a few thousand files in Gecko had been changed due to giant reactor of string literals.

This caused git to get all sorts of confused, meaning it became impossible to a do a rebase.

git rebase branches/default/tip
Performing inexact rename detection: 0% (n/6385864772), done.
... merge conflicts... 

Talking to my colleague Dan Glastonbury, he suggested that I instead tell the “broken branch” to track a particular good branch. That allowed the rebase to work.

moz-phab patch D12345 (some patch for Phabricator...) 
git branch -u origin/bookmarks/central
git rebase

That solved it.