My first steps contributing to Servo (and learning Rust)

I remember first learning about web technologies as a teenager. This was back in the days when the majority of websites still used tables for layout, and a “standards based” approach was just starting to gain popularity. The CSS Zen Garden was in vogue.

Back then, it was common that a large proportion of the time spent putting together a CSS-based website would be spent working around browser bugs. Internet Explorer was the main culprit – its dominance had allowed Microsoft to ignore web standards and interoperability. On the other side of the fence, Firefox was gaining popularity. It was a better browser than IE, with features like tabs and pop-up blocking, and Mozilla was much more committed to properly implementing web standards too. I remember being incredibly enthusiastic about Firefox and trying to encourage anybody who would listen to switch to it!

A few years later, Google first released Chrome and started eating away at the market share of other browsers. Whilst Google has generally behaved better than Microsoft did in the IE 5/6 era, we’re again in a situation where one browser maker is dominant and uses that power to influence the web.

I have to admit that for quite a few years Chrome had the edge over Firefox, particularly in terms of performance. But I held on to Firefox for for mainly ideological reasons. I wanted to support the independent candidate, and also thought it was important for developers in particular to use a diversity of browsers. As a Firefox user I’ve unfortunately seen plenty of breakage where developers clearly haven’t bothered to test extensively outside of Chrome.

Firefox Quantum

I think that a world where the web is largely dominated by one commercial company, whose revenue comes almost entirely from advertising, is a scary prospect. So I was delighted when I read about Mozilla’s plans for Firefox Quantum, promising a great leap in performance. I was even more delighted when the promise became a reality. I’m once again proud to be a Firefox user.

A big part of the speed-up in Firefox Quantum came from its reuse of the CSS engine in Servo, a new and experiemental browser engine. Lin Clark wrote an amazing article about it.

Servo

All this got me quite inspired. Servo is written in Rust, which I had been interested in learning about anyway. And whilst Servo as a whole is a long way from being a production-ready browser, it is already showing results. So I decided to have a go at contributing to Servo.

My first step was to learn a bit more about Rust. I started reading The Rust Programming Language which is the “official” book about the language. After I had covered the basics I got impatient and wanted to find something concrete to work on.

The Servo project is keen to encourage new contributions, and good entry-level tasks are tagged with E-easy (The “E” stands for “Effort”; there’s also E-less easy and E-hard.)

I picked one off which related to the implementation of the text control selection DOM API. This API allows you to programmatically manipulate the selected text shown in an <input> or <textarea> element, and Servo’s implementation needed to be fleshed out.

My first pull request was quite constrained in scope, but I quickly realised that there were large parts of this API which were unimplemented. There was also duplication (and subtle differences) between the implementations for <input> and <textarea>. So I set about fixing these things over quite a few additional PRs (2, 3, 4, 5, 6).

The contribution process

There are contributing and hacking quickstart guides to get you going. On a normal Rust project, most operations are performed through the cargo utility, but Servo has its own ./mach tool for doing almost everything (some ./mach commands wrap around cargo underneath):

$ ./mach build -d # creates a debug build of Servo
$ ./mach run -d -- https://www.mozilla.org/ # runs the browser
$ ./mach test-wpt [...] # runs some tests (see below)

Before submitting a pull request, you need to:

  1. Run ./mach test-tidy to check for compliance with Servo’s code style rules
  2. Ensure the code builds without compilation errors
  3. Ensure there are tests for the changes

There is also quite a keen focus on documentation, and I’ve often received pull request feedback asking me to add more documentation (I should learn from this really!)

Once you’ve submitted a pull request, a bot comes along and assigns a reviewer. It also notifies a bunch of people who have been configured as watchers based on the file paths you have changed.

If you want to change the reviewer (e.g. because you know it should be someone else), then you can comment with “r? @theirname” and the bot will dutifully comply.

Hopefully the reviewer will now provide you with some feedback. In my experience this has generally happened really quickly, although there have been some instances where I’ve needed to bug people in the IRC channel, including one PR which took about a month and a half to get merged.

Once the reviewer is happy, they’ll summon another bot with “@bors-servo r+”. This bot is an installation of bors, and is in charge of actually merging the code. The bot has a queue of PRs to merge, and does so one at a time, after running all the tests with the merged code. This is designed to ensure that it’s impossible for the master branch to contain failing tests, since the tests are always run on a commit before it becomes the new master.

Whilst some checks are run on your PR as soon as it is submitted, the full test suite is not run until @bors-servo tries to do the merge. Therefore, it is fairly common to see failures and have to make further updates.

Web Platform Tests

I was interested to find out that most of Servo’s functionality is verified via the web platform tests suite, which I hadn’t heard of before. This is a cool project created by the W3C which aims to create a complete test suite for all the standardised web technologies. Thus any browser maker could run the suite to verify their conformance to the specifications.

There are a few different types of tests. The two main ones do the following:

  1. Open a page with a JS test harness included, which verifies some assertions via JS and reports the results.
  2. Open a page and grab the exact rendered output. Then open a “reference” page and grab the exact rendered output too. If the two renderings are identical, the test passes.

Obviously there are loads of things which are currently broken or unimplemented in Servo. So as well as the tests, there is a bunch of metadata specifying which tests pass and which ones fail. When the builder runs the tests, it will refuse the merge if this metadata is out of sync with reality. So it checks that all the failing tests are actually marked failing, and all the passing tests are actually marked passing. This ensures that you notice if a change has caused some tests to start passing. (Running the full WPT test suite takes absolutely ages, so it is generally left for the builder to do rather than developers running it themselves.)

Compilation is slow 😞

The Servo project is huge, and it takes a long time to build. One of the really cool things about Rust is how it provides a guarantee that certain classes of errors simply cannot occur in your programs. But this does mean that it has to do more work at compile time, which definitely shows when building something as large as Servo.

On my machine, building from scratch takes something like 45 minutes. After making a change, I don’t have to rebuild everything again, but I’m often still waiting for at least 3-5 minutes.

I have a relatively decent Dell XPS 9350 “ultrabook” laptop, but it’s really optimised for physical size rather than raw performance. Up until now I’ve found its processing power to be more than enough for the work I do, but now I’m building a really large project in a compiled language I am certainly seeing its limitations!

In particular, my laptop contains “only” 8GB of memory, which is non-upgradeable since it is soldered directly to the motherboard to reduce size. Sometimes when compiling, I’ll max out my memory and the kernel’s OOM killer will step in and kill rustc. Fortunately this doesn’t seem to lose all the work that has been done, and I can usually successfully compile on the next attempt.

One thing that does help is ./mach check. This is equivalent to the cargo check command in a normal Rust project. It checks that the code has no compilation errors, without actually building it. That’s much faster, and if I don’t actually need to run the browser or tests right now, then it provides a more pleasant feedback loop.

As already noted, the Servo codebase is really large. There is auto-generated documentation at doc.servo.org which can be a bit of an easier way to make sense of the many data structures and their relationships.

When I’m in the actual code, I’ve found myself using ctags quite a bit to jump around between files. I use rusty-tags to generate the tags and have it integrated in my vim config.

Debugging

Generally, I am a puts debuggerer. But when the compile cycle is 3-5 minutes, you start to wonder whether there is a quicker way to figure out logical misunderstandings.

I have tried to use gdb a bit, with the help of this blog post. I can’t say it has been particularly successful. I’ve often managed to get the program paused at the correct location, but have struggled to inspect the state in any useful way. Most of the time I have something in a variable, and I can print out a representation of it, but I can’t manage to get GDB to call methods on it. In the end, if I manage to find out anything useful it generally seems to take longer than puts debuggering anyway. I think the tooling around debugging for Rust still has some way to go.

On the other hand, Servo itself has some really useful debugging tools built in which are accessed via the -Z option. You can see them by running ./mach run -d -- -Z help.

There’s also the RUST_LOG=debug environment variable which causes Rust to print out debug messages in the source code (i.e. those which use the debug! macro).

Next steps

More recently I’ve been learning about other areas of the Servo codebase. One thing that I was particularly pleased with was a change to where <li> markers are placed when there are adjacent floats. This comes under the banner of “layout”, i.e. how Servo decides, based on your HTML and CSS, what should appear where on the page. It’s pretty complicated, at least for a first-timer, and it took me several days of reading code and specifications to submit that pull request.

I’m finding it really refreshing to work on things which are so challenging and new to me, so I hope that I can fix more layout bugs in the future! In general there are lots of complicated and interesting areas in a browser, so I’m excited to level up and dig into some of them in the future.

Comments

I'd love to hear from you here instead of on corporate social media platforms! You can also contact me privately.

Add your comment