summaryrefslogtreecommitdiffstats
path: root/docs/writing-rust-code
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /docs/writing-rust-code
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--docs/writing-rust-code/basics.md84
-rw-r--r--docs/writing-rust-code/cpp-interop.md240
-rw-r--r--docs/writing-rust-code/index.md16
-rw-r--r--docs/writing-rust-code/uniffi.md70
-rw-r--r--docs/writing-rust-code/update-policy.md150
-rw-r--r--docs/writing-rust-code/xpcom.md120
6 files changed, 680 insertions, 0 deletions
diff --git a/docs/writing-rust-code/basics.md b/docs/writing-rust-code/basics.md
new file mode 100644
index 0000000000..d91288133b
--- /dev/null
+++ b/docs/writing-rust-code/basics.md
@@ -0,0 +1,84 @@
+# Basics
+
+## Formatting Rust code
+
+To format all the Rust code within a directory `$DIR`, run:
+```
+./mach lint -l rustfmt --fix $DIR
+```
+
+## Using Cargo
+
+Many Cargo commands can be run on individual crates. Change into the directory
+containing the crate's `Cargo.toml` file, and then run the command with
+`MOZ_TOPOBJDIR` set appropriately. For example, to generate and view rustdocs
+for the `xpcom` crate, run these commands:
+
+```
+cd xpcom/rust/xpcom
+MOZ_TOPOBJDIR=$OBJDIR cargo doc
+cd -
+firefox target/doc/xpcom/index.html
+```
+where `$OBJDIR` is the path to the object directory.
+
+## Using static prefs
+
+Static boolean/integer prefs can be easily accessed from Rust code. Add a
+`rust: true` field to the pref definition in
+[modules/libpref/init/StaticPrefList.yaml](https://searchfox.org/mozilla-central/source/modules/libpref/init/StaticPrefList.yaml),
+like this:
+```yaml
+- name: my.lucky.pref
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
+ rust: true
+```
+The pref can then be accessed via the `pref!` macro, like this:
+```
+let my_lucky_pref = static_prefs::pref!("my.lucky.pref");
+```
+
+## Helper crates
+
+The following in-tree helper crates provide idiomatic support for some common patterns.
+- [nserror](https://searchfox.org/mozilla-central/source/xpcom/rust/nserror/src/lib.rs)
+reflects `nsresult` codes into Rust.
+- [nsstring](https://searchfox.org/mozilla-central/source/xpcom/rust/nsstring/src/lib.rs)
+ exposes bindings for XPCOM string types. You can use the same `ns{A,C}String`
+ types as C++ for owned strings and pass them back and forth over the
+ boundary. There is also `ns{A,C}Str` for dependent or borrowed strings.
+- [xpcom](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src)
+ provides multiple building blocks for a component's implementation.
+ - The `RefPtr` type is for managing reference-counted pointers.
+ - XPCOM component getters are generated by
+ [xpcom/components/gen_static_components.py](https://searchfox.org/mozilla-central/source/xpcom/components/gen_static_components.py),
+ and can be called like this:
+ ```
+ use xpcom::{interfaces::nsIPrefService, RefPtr};
+ let pref_service: RefPtr<nsIPrefService> = xpcom::components::Preferences::service()?;
+ ```
+ - There is also a `get_service` function that works like `do_GetService` in
+ C++, as an alternative.
+ - A set of `derive` macros help with declaring interface implementations. The
+ [docs](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/xpcom_macros/src/lib.rs)
+ have details and examples.
+- [moz_task](https://searchfox.org/mozilla-central/source/xpcom/rust/moz_task/src/lib.rs)
+ wraps XPCOM's threading functions in order to make it easy and safe to write
+ threaded code. It has helpers for getting and creating threads, dispatching
+ async runnables, and thread-safe handles.
+- [storage](https://searchfox.org/mozilla-central/source/storage/rust/src/lib.rs)
+ is an interface to mozStorage, our wrapper for SQLite. It can wrap an
+ existing storage connection, and prepare and execute statements. This crate
+ wraps the synchronous connection API, and lets you execute statements
+ asynchronously via `moz_task`.
+- [storage_variant](https://searchfox.org/mozilla-central/source/storage/variant/src/lib.rs)
+ is for working with variants. It also provides a `HashPropertyBag` type
+ that's useful for passing hash maps over XPCOM to JS.
+
+Unfortunately, rustdocs are [not yet generated and
+hosted](https://bugzilla.mozilla.org/show_bug.cgi?id=1428139) for crates within
+mozilla-central. Therefore, the crate links shown above link to files
+containing the relevant rustdocs source where possible. However, you can
+generate docs locally using the `cargo doc` command described above.
diff --git a/docs/writing-rust-code/cpp-interop.md b/docs/writing-rust-code/cpp-interop.md
new file mode 100644
index 0000000000..f10fad4221
--- /dev/null
+++ b/docs/writing-rust-code/cpp-interop.md
@@ -0,0 +1,240 @@
+# Rust/C++ interop
+
+This document describes how to use FFI in Firefox to get Rust code and C++ code to interoperate.
+
+## Transferable types
+
+Generally speaking, the more complicated is the data you want to transfer, the
+harder it'll be to transfer across the FFI boundary.
+
+Booleans, integers, and pointers cause little trouble.
+- C++ `bool` matches Rust `bool`
+- C++ `uint8_t` matches Rust `u8`, `int32_t` matches Rust `i32`, etc.
+- C++ `const T*` matches Rust `*const T`, `T*` matches Rust `*mut T`.
+
+Lists are handled by C++ `nsTArray` and Rust `ThinVec`.
+
+For strings, it is best to use the `nsstring` helper crate. Using a raw pointer
+plus length is also possible for strings, but more error-prone.
+
+If you need a hashmap, you'll likely want to decompose it into two lists (keys
+and values) and transfer them separately.
+
+Other types can be handled with tools that generate bindings, as the following
+sections describe.
+
+## Accessing C++ code and data from Rust
+
+To call a C++ function from Rust requires adding a function declaration to Rust.
+For example, for this C++ function:
+
+```
+extern "C" {
+bool UniquelyNamedFunction(const nsCString* aInput, nsCString* aRetVal) {
+ return true;
+}
+}
+```
+add this declaration to the Rust code:
+```rust
+extern "C" {
+ pub fn UniquelyNamedFunction(input: &nsCString, ret_val: &mut nsCString) -> bool;
+}
+```
+
+Rust code can now call `UniquelyNamedFunction()` within an `unsafe` block. Note
+that if the declarations do not match (e.g. because the C++ function signature
+changes without the Rust declaration being updated) crashes are likely. (Hence
+the `unsafe` block.)
+
+Because of this unsafety, for non-trivial interfaces (in particular when C++
+structs and classes must be accessed from Rust code) it's common to use
+[rust-bindgen](https://github.com/rust-lang/rust-bindgen), which generates Rust
+bindings. The documentation is
+[here](https://rust-lang.github.io/rust-bindgen/).
+
+## Accessing Rust code and data from C++
+
+A common option for accessing Rust code and data from C++ is to use
+[cbindgen](https://github.com/eqrion/cbindgen), which generates C++ header
+files. for Rust crates that expose a public C API. cbindgen is a very powerful
+tool, and this section only covers some basic uses of it.
+
+### Basics
+
+First, add suitable definitions to your Rust. `#[no_mangle]` and `extern "C"`
+are required.
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_canonicalize(
+ langid: &nsCString,
+ ret_val: &mut nsCString
+) -> bool {
+ ret_val.assign("new value");
+ true
+}
+```
+
+Then, add a `cbindgen.toml` file in the root of your crate. It may look like this:
+
+```toml
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
+#ifndef mozilla_intl_locale_MozLocaleBindings_h
+#error "Don't include this file directly, instead include MozLocaleBindings.h"
+#endif
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+# Put FFI calls in the `mozilla::intl::ffi` namespace.
+namespaces = ["mozilla", "intl", "ffi"]
+
+# Export `ThinVec` references as `nsTArray`.
+[export.rename]
+"ThinVec" = "nsTArray"
+```
+
+Next, extend the relevant `moz.build` file to invoke cbindgen.
+
+```python
+if CONFIG['COMPILE_ENVIRONMENT']:
+ CbindgenHeader('unic_langid_ffi_generated.h',
+ inputs=['/intl/locale/rust/unic-langid-ffi'])
+
+ EXPORTS.mozilla.intl += [
+ '!unic_langid_ffi_generated.h',
+ ]
+```
+
+This tells the build system to run cbindgen on
+`intl/locale/rust/unic-langid-ffi` to generate `unic_langid_ffi_generated.h`,
+which will be placed in `$OBJDIR/dist/include/mozilla/intl/`.
+
+Finally, include the generated header into a C++ file and call the function.
+
+```c++
+#include "mozilla/intl/unic_langid_ffi_generated.h"
+
+using namespace mozilla::intl::ffi;
+
+void Locale::MyFunction(nsCString& aInput) const {
+ nsCString result;
+ unic_langid_canonicalize(aInput, &result);
+}
+```
+
+### Complex types
+
+Many complex Rust types can be exposed to C++, and cbindgen will generate
+appropriate bindings for all `pub` types. For example:
+
+```rust
+#[repr(C)]
+pub enum FluentPlatform {
+ Linux,
+ Windows,
+ Macos,
+ Android,
+ Other,
+}
+
+extern "C" {
+ pub fn FluentBuiltInGetPlatform() -> FluentPlatform;
+}
+```
+
+```c++
+ffi::FluentPlatform FluentBuiltInGetPlatform() {
+ return ffi::FluentPlatform::Linux;
+}
+```
+
+For an example using cbindgen to expose much more complex Rust types to C++,
+see [this blog post].
+
+[this blog post]: https://crisal.io/words/2020/02/28/C++-rust-ffi-patterns-1-complex-data-structures.html
+
+### Instances
+
+If you need to create and destroy a Rust struct from C++ code, the following
+example may be helpful.
+
+First, define constructor, destructor and getter functions in Rust. (C++
+declarations for these will be generated by cbindgen.)
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_new() -> *mut LanguageIdentifier {
+ let langid = LanguageIdentifier::default();
+ Box::into_raw(Box::new(langid))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_destroy(langid: *mut LanguageIdentifier) {
+ drop(Box::from_raw(langid));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_as_string(
+ langid: &mut LanguageIdentifier,
+ ret_val: &mut nsACString,
+) {
+ ret_val.assign(&langid.to_string());
+}
+```
+
+Next, in a C++ header define a destructor via `DefaultDelete`.
+
+```c++
+#include "mozilla/intl/unic_langid_ffi_generated.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+template <>
+class DefaultDelete<intl::ffi::LanguageIdentifier> {
+ public:
+ void operator()(intl::ffi::LanguageIdentifier* aPtr) const {
+ unic_langid_destroy(aPtr);
+ }
+};
+
+} // namespace mozilla
+```
+
+(This definition must be visible any place where
+`UniquePtr<intl::ffi::LanguageIdentifier>` is used, otherwise C++ will try to
+free the code, which might lead to strange behaviour!)
+
+Finally, implement the class.
+
+```c++
+class Locale {
+public:
+ explicit Locale(const nsACString& aLocale)
+ : mRaw(unic_langid_new()) {}
+
+ const nsCString Locale::AsString() const {
+ nsCString tag;
+ unic_langid_as_string(mRaw.get(), &tag);
+ return tag;
+ }
+
+private:
+ UniquePtr<ffi::LanguageIdentifier> mRaw;
+}
+```
+
+This makes it possible to instantiate a `Locale` object and call `AsString()`,
+all from C++ code.
+
+## Other examples
+
+For a detailed explanation of an interface in Firefox that doesn't use cbindgen
+or rust-bindgen, see [this blog post](https://hsivonen.fi/modern-cpp-in-rust/).
diff --git a/docs/writing-rust-code/index.md b/docs/writing-rust-code/index.md
new file mode 100644
index 0000000000..721a9be811
--- /dev/null
+++ b/docs/writing-rust-code/index.md
@@ -0,0 +1,16 @@
+# Writing Rust Code
+
+This page explains how to write and work with Rust code in Firefox, with an
+emphasis on interoperation with C++ code.
+
+The [build documentation](/build/buildsystem/rust.rst) explains how to add
+new Rust code to Firefox. The [test documentation](/testing-rust-code/index.md)
+explains how to test and debug Rust code in Firefox.
+
+```{toctree}
+:titlesonly:
+:maxdepth: 1
+:glob:
+
+*
+```
diff --git a/docs/writing-rust-code/uniffi.md b/docs/writing-rust-code/uniffi.md
new file mode 100644
index 0000000000..56ffc0935a
--- /dev/null
+++ b/docs/writing-rust-code/uniffi.md
@@ -0,0 +1,70 @@
+# Generating Javascript bindings with UniFFI
+
+Firefox supports auto-generating JS bindings for Rust components using [UniFFI](https://mozilla.github.io/uniffi-rs/).
+
+## How it works
+
+The Rust crate contains a
+[UniFFI Definition Language (UDL) file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html), which describes the
+interface to generate bindings for.
+
+The UniFFI core generates the scaffolding: Rust code which acts as the FFI layer from the UDL file. The functions of
+this layer all use the C calling convention and all structs use a C layout, this is the de facto standard for FFI
+interoperability.
+
+The [`uniffi-bindgen-gecko-js`](https://searchfox.org/mozilla-central/source/toolkit/components/uniffi-bindgen-gecko-js)
+tool, which lives in the Firefox source tree, generates 2 things:
+ - A JS interface for the scaffolding code, which uses [WebIDL](/dom/bindings/webidl/index.rst)
+ - A module that uses the scaffolding to provide the bindings API.
+
+Currently, this generated code gets checked in to source control. We are working on a system to avoid this and
+auto-generate it at build time instead (see [bugzilla 1756214](https://bugzilla.mozilla.org/show_bug.cgi?id=1756214)).
+
+## Before creating new bindings with UniFFI
+
+Keep a few things in mind before you create a new set of bindings:
+
+ - **UniFFI was not written to maximize performance.** It's code is efficient enough to handle many use cases, but at this
+ point should probably be avoided for performance critical components.
+ - **uniffi-bindgen-gecko-js bindings run with chrome privileges.** Make sure this is acceptable for your project
+ - **Only a subset of Rust types can be exposed via the FFI.** Check the [UniFFI Book](https://mozilla.github.io/uniffi-rs/) to see what
+ types are compatible with UniFFI.
+
+If any of these are blockers for your work, consider discussing it further with the UniFFI devs to see if we can support
+your project:
+
+ - Chat with us on `#uniffi` on Matrix/Element
+ - File an issue on [mozilla/uniffi](https://github.com/mozilla/uniffi-rs/)
+
+## Creating new bindings with UniFFI
+
+You can see an example of this feature in use: [when application-services swapped the tabs js sync engine with rust](https://bugzilla.mozilla.org/show_bug.cgi?id=1791851)
+
+Here's how you can create a new set of bindings using UniFFI:
+
+ 1. UniFFI your crate (if it isn't already):
+ - [Create a UDL file to describe your interface](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+ - [Set up your Rust crate to generate scaffolding](https://mozilla.github.io/uniffi-rs/tutorial/Rust_scaffolding.html)
+ 2. Add your crate as a Firefox dependency (if it isn't already)
+ - **If the code will exist in the mozilla-central repo:**
+ - Create a new directory for the Rust crate
+ - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library path
+ - **If the code exists in an external repo:**
+ - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library URL
+ - Run `mach vendor rust` to vendor in your Rust code
+ 3. Generate bindings code for your crate
+ - Add the path of your UDL (that you made in step 1) in `toolkit/components/uniffi-bindgen-gecko-js/mach_commands.py`
+ - Run `./mach uniffi generate`
+ - add your newly generated `Rust{udl-name}.sys.mjs` file to `toolkit/components/uniffi-bindgen-gecko-js/components/moz.build`
+ - Then simply import your module to the file you want to use it in and start using your APIs!
+
+ Example from tabs module:
+
+ ``` js
+ ChromeUtils.defineESModuleGetters(lazy, {
+ ...
+ TabsStore: "resource://gre/modules/RustTabs.sys.mjs",
+ });
+ ...
+ this._rustStore = await lazy.TabsStore.init(path);
+ ```
diff --git a/docs/writing-rust-code/update-policy.md b/docs/writing-rust-code/update-policy.md
new file mode 100644
index 0000000000..27d575a36e
--- /dev/null
+++ b/docs/writing-rust-code/update-policy.md
@@ -0,0 +1,150 @@
+# Rust Update Policy
+
+We document here the decision making and planning around when we update the
+Rust toolchains used and required to build Firefox.
+
+This allows contributors to know when new features will be usable, and
+downstream packagers to know what toolchain will be required for each Firefox
+release. Both benefit from the predictability of a schedule.
+
+## Policy
+
+### Official builds
+
+_We ship official stable Firefox with a stable Rust._
+
+As a general rule, we update the Rust version used to build Firefox Nightly
+soon after its release, unless it's less than 7 days away from a soft-freeze,
+in which case we wait for the next Nightly train.
+
+We don't upgrade the Rust version in the beta or release branches of Firefox.
+
+The following exceptions apply:
+
+- We may use a Rust version from the Rust beta or nightly channels for new
+ platforms (e.g. we did so for Android, arm64 Windows and arm64 macOS), and
+ later upgrade when that Rust version becomes stable (we may even do so on the
+ Firefox beta branch).
+
+- We may skip the update (or backout the update) if major problems are
+ encountered (typically, we've had to do so because of build problems, crash
+ reporting bustage, or performance issues).
+
+### Developer builds
+
+_Local developer builds use whatever Rust toolchain is available on the
+system._
+
+Someone building Firefox can maintain the latest stable Rust with the `rustup`
+or `mach bootstrap` tools, or try other variations.
+
+### Minimum Supported Rust Version
+
+_We will update the Minimum Supported Rust Version (MSRV) when required._
+
+The MSRV will generally remain unchanged, until a newer version is required
+by some code.
+
+When that happens, we'll prefer to update the MSRV to the strict minimum
+required at that moment (e.g. if we require version 1.47.0, the currently used
+Rust version is 1.51.0, and a crate needs 1.50.0, we'll prefer to update the
+MSRV to 1.50.0 rather than 1.51.0).
+
+The MSRV won't be updated to a version of Rust that hasn't been used for
+Firefox Nightly for at least 14 days.
+
+We expect ESR releases will keep their MSRV, so backporting security fixes may
+require Rust compatibility work.
+
+### Rationale
+
+Historically, the Rust ecosystem quickly required new features provided by new
+Rust compilers, which made it necessary to update the minimum supported version
+quite often, and as such, a scheduled update was deemed a better trade-off.
+
+Fast-forward several years, and new Rust compiler releases more rarely sport
+ground-breaking new features, which has reduced the necessity to update quite
+significantly.
+
+On the flip side, in some instances, we have had to stick to specific versions
+of the Rust compiler for extended periods of time because of e.g. regressions,
+going against the schedule.
+
+## Schedule
+
+Here are the Rust versions for each Firefox version.
+
+- The "Uses" column indicates the version of Rust used to build
+ releases shipped to users.
+
+- The "MSRV" column indicates the minimum supported Rust version to build
+ the sources.
+
+| Firefox Version | Uses | MSRV | Rust "Uses" release date | Nightly Soft Freeze | Firefox release date |
+|-----------------|------|----------|--------------------------|---------------------|----------------------|
+| Firefox 56 | Rust 1.19.0 | 1.17.0 | 2017 April 27 | | 2017 September 26
+| Firefox 57 | Rust 1.19.0 | 1.19.0 | 2017 July 20 | | 2017 November 14
+| Firefox 58 | Rust 1.21.0 | 1.21.0 | 2017 October 12 | | 2018 January 16
+| Firefox 59 | Rust 1.22.1 | 1.22.1 | 2017 November 23 | | 2018 March 13
+| Firefox 60 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 May 9
+| Firefox 61 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 June 26
+| Firefox 62 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 September 5
+| Firefox 63 | Rust 1.28.0 | 1.28.0 | 2018 August 2 | | 2018 October 23
+| Firefox 64 | Rust 1.29.2 | 1.29.0 | 2018 September 13 | 2018 October 15 | 2018 December 11
+| Firefox 65 | Rust 1.30.0 | 1.30.0 | 2018 October 25 | 2018 December 3 | 2019 January 29
+| Firefox 66 | Rust 1.31.0 | 1.31.0 | 2018 December 6 | 2019 January 21 | 2019 March 19
+| Firefox 67 | Rust 1.32.0 | 1.32.0 | 2019 January 17 | 2019 March 11 | 2019 May 21
+| Firefox 68 | Rust 1.34.0 | 1.34.0 | 2019 April 11 | 2019 May 13 | 2019 July 9
+| Firefox 69 | Rust 1.35.0 | 1.35.0 | 2019 May 23 | 2019 July 1 | 2019 September 3
+| Firefox 70 | Rust 1.37.0 | 1.36.0 | 2019 July 4 | 2019 August 26 | 2019 October 22
+| Firefox 71 | Rust 1.37.0 | 1.37.0 | 2019 August 15 | 2019 October 14 | 2019 December 3
+| Firefox 72 | Rust 1.38.0 | 1.37.0 | 2019 August 15 | 2019 November 25 | 2020 January 7
+| Firefox 73 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 January 1 | 2020 February 11
+| Firefox 74 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 February 6 | 2020 March 10
+| Firefox 75 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 March 5 | 2020 April 7
+| Firefox 76 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 April 2 | 2020 May 5
+| Firefox 77 | Rust 1.41.1 | 1.41.0 | 2020 January 30 | 2020 April 30 | 2020 June 2
+| Firefox 78 | Rust 1.43.0 | 1.41.0 | 2020 April 23 | 2020 May 28 | 2020 June 30
+| Firefox 79 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 June 26 | 2020 July 28
+| Firefox 80 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 July 23 | 2020 August 25
+| Firefox 81 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 August 20 | 2020 September 22
+| Firefox 82 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 September 17 | 2020 October 20
+| Firefox 83 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 October 15 | 2020 November 17
+| Firefox 84 | Rust 1.47.0 | 1.43.0 | 2020 October 8 | 2020 November 12 | 2020 December 15
+| Firefox 85 | Rust 1.48.0 | 1.47.0 | 2020 November 19 | 2020 December 10 | 2021 January 26
+| Firefox 86 | Rust 1.49.0 | 1.47.0 | 2020 December 31 | 2021 January 21 | 2021 February 23
+| Firefox 87 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 February 18 | 2021 March 23
+| Firefox 88 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 March 18 | 2021 April 19
+| Firefox 89 | Rust 1.51.0 | 1.47.0 | 2021 March 25 | 2021 April 15 | 2021 June 1
+| Firefox 90 | Rust 1.52.0 | 1.47.0 | 2021 May 6 | 2021 May 27 | 2021 June 29
+| Firefox 91 | Rust 1.53.0 | 1.51.0 | 2021 June 17 | 2021 July 8 | 2021 August 10
+| Firefox 92 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 August 5 | 2021 September 7
+| Firefox 93 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 September 2 | 2021 October 5
+| Firefox 94 | Rust 1.55.0 | 1.53.0 | 2021 September 9 | 2021 September 30 | 2021 November 2
+| Firefox 95 | Rust 1.56.0 | 1.53.0 | 2021 October 21 | 2021 October 28 | 2021 December 7
+| Firefox 96 | Rust 1.57.0 | 1.53.0 | 2021 December 2 | 2021 December 2 | 2022 January 11
+| Firefox 97 | Rust 1.57.0 | 1.57.0 | 2021 December 2 | 2022 January 6 | 2022 February 8
+| Firefox 98 | Rust 1.58.0 | 1.57.0 | 2022 January 13 | 2022 February 2 | 2022 March 8
+| Firefox 99 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 3 | 2022 April 5
+| Firefox 100 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 31 | 2022 May 3
+| Firefox 101 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 April 28 | 2022 May 31
+| Firefox 102 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 May 26 | 2022 June 28
+| Firefox 103 | Rust 1.61.0 | 1.59.0 | 2022 May 19 | 2022 June 23 | 2022 July 27
+| Firefox 104 | Rust 1.62.0 | 1.59.0 | 2022 June 30 | 2022 July 21 | 2022 August 23
+| Firefox 105 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 August 18 | 2022 September 20
+| Firefox 106 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 September 15 | 2022 October 18
+| Firefox 107 | Rust 1.64.0 | 1.61.0 | 2022 September 22 | 2022 October 13 | 2022 November 15
+| Firefox 108 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 November 10 | 2022 December 13
+| Firefox 109 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 December 8 | 2023 January 17
+| Firefox 110 | Rust 1.66.0 | 1.65.0 | 2022 December 15 | 2023 January 12 | 2023 February 14
+| Firefox 111 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 February 9 | 2023 March 14
+| Firefox 112 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 March 9 | 2023 April 11
+| Firefox 113 | Rust 1.68.0 | 1.65.0 | 2023 March 9 | 2023 April 6 | 2023 May 9
+| Firefox 114 | Rust 1.69.0 | 1.65.0 | 2023 April 20 | 2023 May 4 | 2023 June 6
+| **Estimated** |
+| Firefox 115 | Rust 1.69.0 | 1.66.0 | 2023 April 20 | 2023 June 1 | 2023 July 4
+| Firefox 116 | Rust 1.70.0 | ? | 2023 June 1 | 2023 June 29 | 2023 August 1
+| Firefox 117 | Rust 1.71.0 | ? | 2023 July 13 | 2023 July 27 | 2023 August 29
+| Firefox 118 | Rust 1.71.0 | ? | 2023 July 13 | 2023 August 24 | 2023 September 26
+| Firefox 119 | Rust 1.72.0 | ? | 2023 August 24 | 2023 September 21 | 2023 October 24
+| Firefox 120 | Rust 1.73.0 | ? | 2023 October 4 | 2023 October 19 | 2023 November 21
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).