4.1 KiB
(Title page) Have you ever wanted to add your own crate Or write your own Web API to your favourite browser? My name is Ed Guloien and today I'm going to tell you how.
(about://ed-guloien) I'm a Senior Software Developer, Formerly working on the Web Platform Networking team at Mozilla. Currently writing rust full time Quantum Bridge working on Distributed Symmetric Key Exchange.
(Interop 2025) There is an initiative to improve web compatibility by improving Web API implementations across the major browsers. It's called Interop and it happens every year. And a hundred years ago, for Interop 2025, I implemented the URLPattern Web API.
(URLPattern Web API) URLPattern is basically pattern matching for URLs and their components. We can construct a pattern object and match strings against it.
(Denoland crate and Firefox) Luckily for us there is an existing open source implementation by Denoland, written in rust. So all we have to do is figure out how to incorporate it into Firefox. This is Firefox's Fission Architecture, it isolates web content from other web content. We need URLPattern everywhere:
- for each web content process
- and for Compression Dictionaries in the main process. So we add it to libXUL, a common library within Firefox.
(WebAPI call flow to crate) We fork Denoland's crate by vetting, vendoring and telling the build system to compile it. This works just ffine. But Firefox's Browser Engine is mostly C++ and we're missing a call path.
- So we generate JS_callable C++ bindings from a webIDL file
- Implement a DOM wrapper that will forward the calls to our gloue crate
- And Tell the build system about our FFI glue crate
- It uses cbindgen to generate headers for C++ compilation against rust
- And include a C++ convenience wrapper and make our rust call the crate
(UrlPattern crate API) The crate API exposes a trait called RegExp and a few functions to go with it. We simply implement trait, and call the functions to create patterns and match against them.
(Glue Crate) Easy Peazy, right? well, not so fast.
(Reality Sets In) Non-POD objects need to be passed-by-reference across the FFI.
- And since our common C ABI doesn't have references, cbindgen degrades our references to raw pointers.
- And if the object exposes internal types unknown to the other side you need an opaque pointer to hide those details.
- This leads to all sorts of unsafe pointer voodoo
Similarly, to represent options we use pointers, or write wrapper bindings to hide the gory details. But sometimes we get lucky and find a specialized FFI type that can handle the ownership model difference between C++ and Rust
(Pattern re-use) We get that working and quickly notice that Pattern construction is the most expensive part of using URLPattern. So we cannot feasibly do it on every match.
- So to save some cycles we pass the pattern object to the closest persistent object,
- all the way back in the generated DOM bindings,
- and write the additional marshaling to do it. With that done, when the user requests a match: we pass the pattern along to the crate. To avoid memory leaks, when the DOM binding goes out of scope the pattern is destroyed by it's rust creator.
(99.8%) If you get this far You're pretty much there, with decent performance. But if you want the whole banana, you have to know
- that rust::regex is not EcmaRegex compliant
- and the urlpattern crate uses rust::regex by default. Luckily for us, we're in Firefox, we already HAVE an EcmaRegex in SpiderMonkey, the Javascript engine
(Spidermonkey Challenges) But it comes at the cost of toil. We write similar FFI going the back to C++ to access SpiderMonkey. But this API was not designed for C++ use.
- It requires JS context, which we lost through all the marshaling.
- It uses Garbage Collection.
- Calling JS can be interrupted to prioritize other JS. So we cast some spells:
- we spin up a JS context,
- we prevent Spidermonkey from interrupting execution,
- we root the objects to a context that is safe from GC
- AND convert between UTF-8 and 16 as needed.
And if you do all that...
(Phew!) You're done. Now you can add your own crates and Web API's to your favourite open source browser.