Servo is an awesome project about making a web browser engine in Rust! It is really well maintained with friendly contributors and mentors. Because Servo is a new web browser engine, there are so many relatively easy tasks waiting for new contributors. One of the things you as a contributor can do is implementing a DOM API for Servo.
Malisa and I implemented a few DOM APIs over the summer. There are lots of great documentation on how to get started, but we still felt a bit lost in the beginning. So we decided to make a tutorial for people who are relatively new to Servo and want a little more concrete example on implementing a new DOM API for Servo.
In this blog post, I will implement the up and coming DOM API called ✨Doge✨. This API has a spec that describes the standard Doge API should follow. You should give it a very brief skim before continuing.
Doge is a relatively simple DOM API. You can create a new Doge object that has an associated such list. If an initializer is given, that will be used to populate the new Doge. You can add more words to such list through the
append method. You can get a random word from such list through the
random method. Simple! So, how do we start?
These are the most relevant directories for us.
script is a crate, which includes a module
webidls hosts all the Web IDL files for the DOM APIs:
First, we should get the Web IDL that is in the spec. Copy them, and put them in
componenets/script/dom/webidls/Doge.webidl. For now, we’ll comment out the methods for Doge in the Web IDL. It’s easiest to comment them all out, and then implement one method at a time. It will look like below after adding the license and the spec link:
If this is the first time adding a Web IDL to Servo from this specific domain, make sure to add it to
python/tidy/servo_tidy/tidy.py so that
test-tidy will know it’s a valid Web IDL standard.
Listing Doge as a submodule
doge.rs is a submodule under
dom so we should make it discoverable by the rest of Servo. We do so by listing the submodule in the
mod.rs at the root of the module. In our case, we would add the following line to
Doge is a dom struct and has an associated such list. So we can write that like the following in
reflector_, which provides a pointer to the JS object. We want
such_list to be able to be modified, so we’ll allow interior mutability by wrapping it with
DOMRefCell is like
RefCell, but it enables JS garbage collector tracing.
Let’s write the functions required for creating a new Doge object!
We need three functions for creating a new Doge object:
Constructor is called by the JS world, which calls
new, which in turn calls
new_inherited. Let’s work on
new, and then
new_inherited: This is where an instance of the Doge struct is created. Sometimes, parameters may be used here, but Doge doesn’t require any (because Doge is a strong independent doggo💕):
new: This is where a connection between the JS world (
global) and the Rust Doge object is created. It does so by calling a function
reflect_dom_object, which takes three parameters including one called
DogeWrapis a DOM bindings generated code (more on that later) that sets up the JS reflector (
reflector_) for the Doge type.
newmethod returns a JS Rooted object (
Constructor: This function constructs the new Doge object, and will add some fields if an initializer (
init) is given. In our case, Doge can be given a
DogeInitthat is a sequence of
DOMString. The spec for the constructor method states that:
- Let doge be a new
- If init is given, for each word in init, append word to the associated such list.
- Return doge.
So, we follow the spec, and write the constructor!
Constructor() must return the
Fallible type, which is one of Servo’s custom
We can almost create a Doge object. This won’t compile yet, because the append method is not implemented. Awesome! Let’s start writing the Doge methods.
impl DogeMethods for Doge
Let’s uncomment the
append method from the Web IDL.
Now we’ll try to compile. This will definitely fail! However, the DOM bindings generator creates a code related to Doge. You can find that in
target/debug/build/script-[hash]/out/Bindings/DogeBinding.rs or something like that. This is also where you can find
DogeWrap, which I mentioned earlier. Search for
DogeMethods, and it will show:
Oh how neat! Now we know exactly how to start writing the
The spec for append states that:
append(word)method, when invoked, must append
wordto the associated such list.
So we know that we should write the method like the following:
One down, one more to go!
The spec states:
random()method, when invoked, must run these steps:
- Let list be the associated such list.
- If list is empty, throw a
- Return a randomly chosen word from list.
random method can throw an error, we should specify it in our Web IDL. Add
[Throws] in front of
random like this:
Let’s now write the
random method per spec:
That’s it! We implemented the Doge API for Servo. Let’s try it out!
Wow! Such amazing! Very cool.
To see the full implementation, check out the doge commit in my Servo fork.
This tutorial does not cover everything related to implementing a DOM API, but I hope this helped you get started! Sometimes the spec will be unclear, and sometimes it will be impossible to implement an API exactly as the spec states. When you encounter a situation like that or when you need help, reach out to Servo and spec maintainers! They are friendly and helpful people. 😊
Malisa wrote a blog post about the DOM API testing process in Servo, so read about it!
Many thanks to Josh Matthews (
jdm) for reviewing the blog post.