gecko-dev/docs/rust-components/uniffi-bindgen-gecko-js-dev-guide/js-callback-interfaces.md
Ben Dean-Kawamura f9b53c8833 Bug 1969457 - uniffi-bindgen-gecko-js doc updates, r=markh,bgruber
Added documents on how things work internally.

Also updated the "Developing Rust Components" section.  Added info on
threading issues and removed info covered by the internals docs.

Differential Revision: https://phabricator.services.mozilla.com/D252244
2025-06-18 22:40:40 +00:00

3 KiB

JavaScript Callback Interfaces

Background

JavaScript layer

The generated JavaScript bindings create a UniFFICallbackHandler 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, 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 to promise object.
    • The PromiseNativeHandler completes the promise by calling the complete callback as described in the UniFFI FFI internals doc.
    • 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.