301 lines
8.3 KiB
Markdown
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
|
|
|
|

|
|

|
|
|
|
<!--
|
|
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
|
|
|
|

|
|
|
|

|
|
|
|
<!--
|
|
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
|
|
|
|

|
|
|
|
<!--
|
|
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
|
|
|
|
```
|
|
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
|
|
|
|

|
|
|
|
<!--
|
|
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)
|
|
|
|

|
|
|
|
<!--
|
|
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!
|
|
|
|
|
|

|
|
|
|
<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.
|
|
--> |