Open Bug 1651223 Opened 4 years ago Updated 4 years ago

Make it easier to use dictionaries in XPIDL

Categories

(Core :: XPConnect, task, P3)

task

Tracking

()

People

(Reporter: lina, Unassigned)

References

(Blocks 1 open bug)

Details

Right now, if you want to work with plain-old JS objects in XPIDL (pass them as arguments and return them from methods), you have a few choices:

  1. Make a new interface with attributes for each member.
  2. Use a jsval.
  3. Use a webidl dictionary.
  4. Use an nsIPropertyBag.
  5. Stringify the object to JSON, and deserialize it on the other side.

Each has its benefits and drawbacks, and I wanted to capture those in this bug, and see if there's interest in making POJOs or dictionaries more ergonomic to use.

(1) is nice because the set of attributes is fixed. It's also easy to use from JS, thanks to XPConnect magically implementing the interface for you, and turning members into properties, so there's no need to declare a whole class with a QueryInterface method. Unfortunately, these are awkward to work with in C++ and Rust: every property get is fallible, and the unwrapping code can get out of hand quickly, not to mention each get is now a virtual method call.

Implementing the interface in C++ allows for [infallible] attribute getters, but adds lots of boilerplate—a component entry, a class declaration, NS_IMPL_*/NS_DECL_*, and a getter-setter pair for each attribute. This boilerplate and unwrapping can add up if any members are themselves dictionaries. The payment request code ran into some of these troubles.

(2) is easy to use in JS, but it feels awkward to unpack and construct jsvals in C++. It feels like there are subtelties in using JSAPI correctly, and I'm not sure if they're all documented or obvious. Bug 1496249 is a good example: in what cases do we need to clear exceptions on JSContext, and when are they safe to propagate back to the caller?

I can imagine the surface area for mistakes adding up the more complex your type is, especially if you want to construct a new JS object in C++ to pass back to JS. These also can't be used from rust-xpcom at all, since we don't link Rust SpiderMonkey bindings.

(3) feels like the nicest option, working with type-safe WebIDL wrappers in C++, and an ergonomic API for constructing and unpacking them. Like interfaces, the set of properties is fixed and strongly typed. Under the hood, all the JSAPI calls are autogenerated, so there's no need to operate on the raw JS::Values directly, and the wrappers are type-safe.

I know we're encouraged to avoid chrome-webidl except for hot code, but is it OK to split the definitions between the XPIDL file with the interface declaration, and a WebIDL file with just dictionary types?

Like jsvals, webidl dictionary types can't be used from Rust, and supporting them would require SpiderMonkey bindings and codegen work. As covered in the thread above, I think the generated C++ also enforces strict security invariants that are required by the spec, but that don't really matter in chrome-only code.

(4) is what password manager and boomark sync do, relying on XPConnect to automatically box and unbox values into nsIVariants on the JS side. They're supported in JS, C++, and Rust, but pretty awkward to use from all three. I also feel like this boxing and unboxing would be slow.

(5) is what we've been using for our sync and storage components, and there's some precedent for this in other parts of the tree. I'd expect this to be faster than (1) and (4) for sure, and maybe (2). Serde is efficient in Rust, and SpiderMonkey's JSON parser is lightning-fast. JSON strings are easy to create in C++, with JSONWriter, but harder to parse—I guess that's where we get back to using JSAPI to parse JSON strings into objects.

Are there other options? If (3) is the best one, are there ways we could make that easier to use?

(In reply to Lina Cambridge [:lina] from comment #0)

(3) feels like the nicest option, working with type-safe WebIDL wrappers in C++, and an ergonomic API for constructing and unpacking them. Like interfaces, the set of properties is fixed and strongly typed. Under the hood, all the JSAPI calls are autogenerated, so there's no need to operate on the raw JS::Values directly, and the wrappers are type-safe.

I know we're encouraged to avoid chrome-webidl except for hot code, but is it OK to split the definitions between the XPIDL file with the interface declaration, and a WebIDL file with just dictionary types?

Yeah I think it's fine to put dictionaries in e.g. ChromeUtils.webidl (or make a separate Dictionaries.webidl in chrome-webidl). With bug 1444991 it's nicely ergonomic to use those dictionaries in xpidl declarations.

Priority: -- → P3
Severity: -- → N/A
You need to log in before you can comment on or make changes to this bug.