diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /docs/writing-rust-code/xpcom.md | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'docs/writing-rust-code/xpcom.md')
-rw-r--r-- | docs/writing-rust-code/xpcom.md | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/docs/writing-rust-code/xpcom.md b/docs/writing-rust-code/xpcom.md new file mode 100644 index 0000000000..dbe297e368 --- /dev/null +++ b/docs/writing-rust-code/xpcom.md @@ -0,0 +1,120 @@ +# XPCOM components in Rust + +XPCOM components can be written in Rust. + +## A tiny example + +The following example shows a new type that implements `nsIObserver`. + +First, create a new empty crate (e.g. with `cargo init --lib`), and add the +following dependencies in its `Cargo.toml` file. + +```toml +[dependencies] +libc = "0.2" +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +xpcom = { path = "../../../xpcom/rust/xpcom" } +``` + +(The number of `../` occurrences will depend on the depth of the crate in the +file hierarchy.) + +Next hook it into the build system according to the [build +documentation](/build/buildsystem/rust.rst). + +The Rust code will need to import some basic types. `xpcom::interfaces` +contains all the usual `nsI` interfaces. + +```rust +use libc::c_char; +use nserror::nsresult; +use std::sync::atomic::{AtomicBool, Ordering}; +use xpcom::{interfaces::nsISupports, RefPtr}; +``` + +The next part declares the implementation. + +```rust +#[xpcom(implement(nsIObserver), atomic)] +struct MyObserver { + ran: AtomicBool, +} +``` + +This defines the implementation type, which will be refcounted in the specified +way and implement the listed xpidl interfaces. It will also declare a second +initializer struct `InitMyObserver` which can be used to allocate a new +`MyObserver` using the `MyObserver::allocate` method. + +Next, all interface methods are declared in the `impl` block as `unsafe` methods. + +```rust +impl MyObserver { + #[allow(non_snake_case)] + unsafe fn Observe( + &self, + _subject: *const nsISupports, + _topic: *const c_char, + _data: *const u16, + ) -> nsresult { + self.ran.store(true, Ordering::SeqCst); + nserror::NS_OK + } +} +``` + +These methods always take `&self`, not `&mut self`, so we need to use interior +mutability: `AtomicBool`, `RefCell`, `Cell`, etc. This is because all XPCOM +objects are reference counted (like `Arc<T>`), so cannot provide exclusive access. + +XPCOM methods are unsafe by default, but the +[xpcom_method!](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src/method.rs) +macro can be used to clean this up. It also takes care of null-checking and +hiding pointers behind references, lets you return a `Result` instead of an +`nsresult,` and so on. + +To use this type within Rust code, do something like the following. + +```rust +let observer = MyObserver::allocate(InitMyObserver { + ran: AtomicBool::new(false), +}); +let rv = unsafe { + observer.Observe(x.coerce(), + cstr!("some-topic").as_ptr(), + ptr::null()) +}; +assert!(rv.succeeded()); +``` + +The implementation has an (auto-generated) `allocate` method that takes in an +initialization struct, and returns a `RefPtr` to the instance. + +`coerce` casts any XPCOM object to one of its base interfaces; in this case, +the base interface is `nsISupports`. In C++, this would be handled +automatically through inheritance, but Rust doesn’t have inheritance, so the +conversion must be explicit. + +## Bigger examples + +The following XPCOM components are written in Rust. + +- [kvstore](https://searchfox.org/mozilla-central/source/toolkit/components/kvstore), + which exposes the LMDB key-value store (via the [Rkv + library](https://docs.rs/rkv)) The API is asynchronous, using `moz_task` to + schedule all I/O on a background thread, and supports getting, setting, and + iterating over keys. +- [cert_storage](https://searchfox.org/mozilla-central/source/security/manager/ssl/cert_storage), + which stores lists of [revoked intermediate certificates](https://blog.mozilla.org/security/2015/03/03/revoking-intermediate-certificates-introducing-onecrl/). +- [bookmark_sync](https://searchfox.org/mozilla-central/source/toolkit/components/places/bookmark_sync), + which [merges](https://mozilla.github.io/dogear) bookmarks from Firefox Sync + with bookmarks in the Places database. + [There's also some docs on how Rust interacts with Sync](/services/sync/rust-engines.rst) +- [webext_storage_bridge](https://searchfox.org/mozilla-central/source/toolkit/components/extensions/storage/webext_storage_bridge), + which powers the WebExtension storage.sync API. It's a self-contained example + that pulls in a crate from application-services for the heavy lifting, wraps + that up in a Rust XPCOM component, and then wraps the component in a JS + interface. There's also some boilerplate there around adding a + `components.conf` file, and a dummy C++ header that declares the component + constructor. [It has some in-depth documentation on how it hangs together](../toolkit/components/extensions/webextensions/webext-storage.rst). |