Files
fx-urlpattern-impl/firefox-webapi-with-crates.md
T
2026-08-24 19:32:39 -04:00

301 lines
8.3 KiB
Markdown

---
marp: true @marp-team/marp-cli v4.4.0 (w/ @marp-team/marp-core v4.3.0)
title: Firefox Web APIs with crates over FFI
author: Ed Guloien
date: Sept 2026
paginate: true
footer: <span> <img src="images/qb-logo-mark.png" width="60"/> Sept 2026 </span>
html: "true"
---
<style>
/* font setup */
section {
@font-face {
font-family: 'Montserrat';
font-weight: 400;
src: url('./fonts/Montserrat-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'Montserrat';
font-weight: 700;
src: url('./fonts/Montserrat-Bold.woff2') format('woff2');
}
font-family: 'Montserrat', sans-sarif;
}
footer { width: 100%; text-align: left; }
/* title setup */
h1 { color: #4ca783; border-bottom: 2px solid #fc8a09;}
/* general title and text setup */
h2 { color: #4ca783; position: absolute; top: 50px; left: 60px;
border-bottom: 2px solid #fc8a09; }
section { font-family: 'Montserrat', sans-serif; color: #5d5d5c }
/* references setup */
h4 { color: #4ca783; position: absolute; top: 100px; left: 60px;
border-bottom: 2px solid #fc8a09;
margin-top: 200px; }
p { margin-top: 300px; font-family: 'Montserrat', sans-serif; }
</style>
# Firefox Web APIs with Crates (over FFI)
### Ed Guloien
<!--
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.
-->
<!-- disable page numbering on title page -->
<!-- _paginate: false -->
---
## about://ed-guloien
<style scoped>
.container { display: grid;
grid-template-columns: 1fr 320px; grid-template-rows: auto 1fr;
gap: 24px; height: 100%;
margin-top: 60px;}
.logos {
grid-column: 1; grid-row: 2; display: flex; gap: 20px; align-items: center; }
.bio {
grid-column: 1; grid-row: 1; }
.bio ul {
list-style: none; padding: 0; margin: 0; line-height: 1.8; }
.headshot {
grid-column: 2; grid-row: 1 / 3; align-items: center; justify-content: left; }
.headshot img { width: 600px; border-radius: 8px; }
</style>
<div class="container">
<div class="logos">
<img src="images/qb-logo-text.png" width="250px">
<img src="images/fx-browser-logo.jpg" width="250px">
</div>
<div class="bio">
<ul>
<li> Software Developer @ Quantum Bridge: </li>
<li> Distributed Symmetric Key Exchange </li>
<li> Former Mozillian: Platform, Networking</li>
<li> Interests: Privacy, Security, Systems</li>
<li> ed.guloien@qubridge.io </li>
</ul>
</div>
<div class="headshot">
<img src="images/headshot.jpeg" width="250px">
</div>
</div>
<!--
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
![bg width:600px](images/interop-2025-2.png)
![bg width:600px](images/interop-2025-3.png)
<!--
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
```rust
// JS
// build a pattern
const pattern = new URLPattern({ pathname: "/books/:id" });
// check for match
pattern.test("https://example.com/books/123");
// get match info by component
pattern.exec("https://example.com/books/123").pathname.groups
```
<!--
URLPattern is basically pattern matching for URLs and their components.
We can construct a pattern object and match strings against it.
-->
---
## Denoland & Firefox
![bg width:400px](images/denoland-urlpattern.png)
![bg width:600px](images/urlpattern-fission.drawio.png)
<!--
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.
-->
-------
## Web API Call Flow
![bg center width:1000px](images/urlpattern-call-flow.drawio.png)
<!--
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
![bg center width:850px](images/urlpattern-details-simple.drawio.png)
<!--
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
```
let pattern = quirks::parse::<SpiderMonkeyRegexp>(input);
let results = quirks::process_match(pattern, input);
// call it a day
```
<!--
Easy Peazy, right?
well,
not so fast.
-->
-----
## Reality Sets In (Memory Concerns)
```
#[repr(transparent)]
pub struct UrlPatternGlue(pub *mut c_void);
if let Ok(pattern) = quirks::parse::<SpiderMonkeyRegexp>(input, ...) {
unsafe {
*res = UrlPatternGlue(Box::into_raw(Box::new(pattern)) as *mut _);
}
}
glue::process_match(..., res: *mut MatchResults, other: MaybeString)
glue::doing_thing(..., res: &mut ThinVec<>);
```
<!--
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; Marshaling
![bg center width:1000px](images/urlpattern-pattern-reuse.drawio.png)
<!--
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%
```
regex::Regex != EcmaScript Regex
```
<!--
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 (many)
![bg center width:825px](images/urlpattern-spidermonkey-challenges.drawio.png)
<!--
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!
![width:900px](images/urlpattern-interop-2025-results.png)
<style scoped> img { position: absolute; top: 150px; left: 15%; } </style>
#### References
- http://qubridge.io
- https://wpt.fyi/interop-2025
- https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API
- https://searchfox.org/firefox-main
- https://github.com/denoland/rust-urlpattern
- See also: https://manuelbucher.com/blog/rust-gecko/
<!--
You're done.
Now you can add your own crates and Web API's to your favourite open source browser.
-->