summaryrefslogtreecommitdiffstats
path: root/docs/writing-rust-code/xpcom.md
blob: dbe297e368fc3dbfa74be9f488e5d5505f3c40f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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).