Source code

Revision control

Copy as Markdown

Other Tools

# JavaScript Callback Interfaces
## Background
* [Threading concerns](../developing-rust-components/threading.md)
* [Lifting and Lowering](./lifting-and-lowering.md)
## JavaScript layer
The generated JavaScript bindings create a [UniFFICallbackHandler](https://searchfox.org/mozilla-central/rev/b5ab48b8c33faf53817cb2ef64c8523a469ef695/dom/chrome-webidl/UniFFI.webidl#75) for each callback interface.
This stores the callback interface implementations that are manually written by Firefox engineers.
These are stored in a map, where the key is an integer handle for the callback interface.
`UniFFICallbackHandler.callAsync` is used by the C++ layer to invoke callback interface methods.
See below for why we only currently have `callAsync()`.
`UniFFICallbackHandler.callAsync()` inputs:
* The object handle
* The method index
* Each argument for the callback method, after being lowered by JavaScript.
`UniFFICallbackHandler.callAsync()` returns a `UniFFIScaffoldingCallResult`.
Like with [Rust calls](./rust-calls.md), this is a `UniffiCallStatus` combined with a return value.
For each callback interface, the JavaScript layer calls `UniFFIScaffolding.registerCallbackHandler()` with the `UniFFICallbackHandler` for that interface.
Like with Rust calls, the bindings code generates a unique ID to identify each callback interface.
## C++ layer
The C++ layer acts as a bridge between the generated Rust code and the generated JavaScript code.
It registers a vtable with the Rust code where each field points to a generated C function that:
* Looks up the `UniFFICallbackHandler` registered with `UniFFIScaffolding.registerCallbackHandler()`
* Lifts all passed arguments and passes them to the `UniFFICallbackHandler`.
* For fire-and-forget calls:
* Calls `UniFFICallbackHandler.callAsync()` with the lifted arguments then discards the returned Promise.
* Note: sync calls are currently always wrapped to be "fire-and-forget" callbacks
* For async calls:
* Calls `UniFFICallbackHandler.callAsync()` with the lifted arguments getting back a Promise object.
* Appends a [PromiseNativeHandler](https://searchfox.org/mozilla-central/source/dom/promise/PromiseNativeHandler.h) to promise object.
* The `PromiseNativeHandler` completes the promise by calling the complete callback as described in the [UniFFI FFI internals doc](https://mozilla.github.io/uniffi-rs/latest/internals/async-ffi.html#completing-async-methods-with-complete_func).
* The `PromiseNativeHandler` also has code to handle a rejected promise by calling the complete callback with `RustCallStatusCode::UnexpectedError`.
## Freeing Callback Interface Objects
Each VTable also has a `uniffi_free` method.
When the Rust code drops the callback interface object, the generated UniFFI code arranges for `uniffi_free` to be called.
When this happens, the C++ generated function calls `UniFFICallbackHandler.destroy()`.
The generated JavaScript handles that by removing the entry from the callback interface map.