56 stories

Inside a super fast CSS engine: Quantum CSS (aka Stylo)

1 Comment

You may have heard of Project Quantum… it’s a major rewrite of Firefox’s internals to make Firefox fast. We’re swapping in parts from our experimental browser, Servo, and making massive improvements to other parts of the engine.

The project has been compared to replacing a jet engine while the jet is still in flight. We’re making the changes in place, component by component, so that you can see the effects in Firefox as soon as each component is ready.

And the first major component from Servo—a new CSS engine called Quantum CSS (previously known as Stylo)—is now available for testing in our Nightly version. You can make sure that it’s turned on for you by going to about:config and setting layout.css.servo.enabled to true.

This new engine brings together state-of-the-art innovations from four different browsers to create a new super CSS engine.

4 browser engines feeding in to Quantum CSS

It takes advantage of modern hardware, parallelizing the work across all of the cores in your machine. This means it can run up to 2 or 4 or even 18 times faster.

On top of that, it combines existing state-of-the-art optimizations from other browsers. So even if it weren’t running in parallel, it would still be one fast CSS engine.

Racing jets

But what does the CSS engine do? First let’s look at the CSS engine and how it fits into the rest of the browser. Then we can look at how Quantum CSS makes it all faster.

What does the CSS engine do?

The CSS engine is part of the browser’s rendering engine. The rendering engine takes the website’s HTML and CSS files and turns them into pixels on the screen.

Files to pixels

Each browser has a rendering engine. In Chrome, it’s called Blink. In Edge, it’s called EdgeHTML. In Safari, it’s called WebKit. And in Firefox, it’s called Gecko.

To get from files to pixels, all of these rendering engines basically do the same things:

  1. Parse the files into objects the browser can understand, including the DOM. At this point, the DOM knows about the structure of the page. It knows about parent/child relationships between elements. It doesn’t know what those elements should look like, though.Parsing the HTML into a DOM tree
  2. Figure out what the elements should look like. For each DOM node, the CSS engine figures out which CSS rules apply. Then it figures out values for each CSS property for that DOM node.Styling each DOM node in the tree by attaching computed styles
  3. Figure out dimensions for each node and where it goes on the screen. Boxes are created for each thing that will show up on the screen. The boxes don’t just represent DOM nodes… you will also have boxes for things inside the DOM nodes, like lines of text.Measuring all of the boxes to create a frame tree
  4. Paint the different boxes. This can happen on multiple layers. I think of this like old-time hand drawn animation, with onionskin layers of paper. That makes it possible to just change one layer without having to repaint things on other layers.Painting layers
  5. Take those different painted layers, apply any compositor-only properties like transforms, and turn them into one image. This is basically like taking a picture of the layers stacked together. This image will then be rendered on the screen.Assembling the layers together and taking a picture

This means when it starts calculating the styles, the CSS engine has two things:

  • a DOM tree
  • a list of style rules

It goes through each DOM node, one by one, and figures out the styles for that DOM node. As part of this, it gives the DOM node a value for each and every CSS property, even if the stylesheets don’t declare a value for that property.

I think of it kind of like somebody going through and filling out a form. They need to fill out one of these forms for each DOM node. And for each form field, they need to have an answer.

Blank form with CSS properties

To do this, the CSS engine needs to do two things:

  • figure out which rules apply to the node — aka selector matching
  • fill in any missing values with values from the parent or a default value—aka the cascade

Selector matching

For this step, we’ll add any rule that matches the DOM node to a list. Because multiple rules can match, there may be multiple declarations for the same property.

Person putting check marks next to matching CSS rules

Plus, the browser itself adds some default CSS (called user agent style sheets). How does the CSS engine know which value to pick?

This is where specificity rules come in. The CSS engine basically creates a spreadsheet. Then it sorts the declarations based on different columns.

Declarations in a spreadsheet

The rule that has the highest specificity wins. So based on this spreadsheet, the CSS engine fills out the values that it can.

Form with some CSS properties filled in

For the rest, we’ll use the cascade.

The cascade

The cascade makes CSS easier to write and maintain. Because of the cascade, you can set the color property on the body and know that text in p, and span, and li elements will all use that color (unless you have a more specific override).

To do this, the CSS engine looks at the blank boxes on its form. If the property inherits by default, then the CSS engine walks up the tree to see if one of the ancestors has a value. If none of the ancestors have a value, or if the property does not inherit, it will get a default value.

Form will all CSS properties filled in

So now all of the styles have been computed for this DOM node.

A sidenote: style struct sharing

The form that I’ve been showing you is a little misrepresentative. CSS has hundreds of properties. If the CSS engine held on to a value for each property for each DOM node, it would soon run out of memory.

Instead, engines usually do something called style struct sharing. They store data that usually goes together (like font properties) in a different object called a style struct. Then, instead of having all of the properties in the same object, the computed styles object just has pointers. For each category, there’s a pointer to the style struct that has the right values for this DOM node.

Chunks of the form pulled out to separate objects

This ends up saving both memory and time. Nodes that have similar properties (like siblings) can just point to the same structs for the properties they share. And because many properties are inherited, an ancestor can share a struct with any descendants that don’t specify their own overrides.

Now, how do we make that fast?

So that is what style computation looks like when you haven’t optimized it.

Steps in CSS style computation: selector matching, sorting by specificity, and computing property values

There’s a lot of work happening here. And it doesn’t just need to happen on the first page load. It happens over and over again as users interact with the page, hovering over elements or making changes to the DOM, triggering a restyle.

Initial styling plus restyling for hover, DOM nodes added, etc

This means that CSS style computation is a great candidate for optimization… and browsers have been testing out different strategies to optimize it for the past 20 years. What Quantum CSS does is take the best of these strategies from different engines and combine them to create a superfast new engine.

So let’s look at the details of how these all work together.

Run it all in parallel

The Servo project (which Quantum CSS comes from) is an experimental browser that’s trying to parallelize all of the different parts of rendering a web page. What does that mean?

A computer is like a brain. There’s a part that does the thinking (the ALU). Near that, there’s some short term memory (the registers). These are grouped together on the CPU. Then there’s longer term memory, which is RAM.

CPU with ALU (the part that does the thinking) and registers (short term memory)

Early computers could only think one thing at a time using this CPU. But over the last decade, CPUs have shifted to having multiple ALUs and registers, grouped together in cores. This means that the CPU can think multiple things at once — in parallel.

CPU chip with multiple cores containing ALUs and registers

Quantum CSS makes use of this recent feature of computers by splitting up style computation for the different DOM nodes across the different cores.

This might seem like an easy thing to do… just split up the branches of the tree and do them on different cores. It’s actually much harder than that for a few reasons. One reason is that DOM trees are often uneven. That means that one core will have a lot more work to do than others.

Imbalanced DOM tree being split between multiple cores so one does all the work

To balance the work more evenly, Quantum CSS uses a technique called work stealing. When a DOM node is being processed, the code takes its direct children and splits them up into 1 or more “work units”. These work units get put into a queue.

Cores segmenting their work into work units

When one core is done with the work in its queue, it can look in the other queues to find more work to do. This means we can evenly divide the work without taking time up front to walk the tree and figure out how to balance it ahead of time.

Cores that have finished their work stealing from the core with more work

In most browsers, it would be hard to get this right. Parallelism is a known hard problem, and the CSS engine is very complex. It’s also sitting between the two other most complex parts of the rendering engine — the DOM and layout. So it would be easy to introduce a bug, and parallelism can result in bugs that are very hard to track down, called data races. I explain more about these kinds of bugs in another article.

If you’re accepting contributions from hundreds or thousands of engineers, how can you program in parallel without fear? That’s what we have Rust for.

Rust logo

With Rust, you can statically verify that you don’t have data races. This means you avoid tricky-to-debug bugs by just not letting them into your code in the first place. The compiler won’t let you do it. I’ll be writing more about this in a future article. In the meantime, you can watch this intro video about parallelism in Rust or this more in-depth talk about work stealing.

With this, CSS style computation becomes what’s called an embarrassingly parallel problem — there’s very little keeping you from running it efficiently in parallel. This means that we can get close to linear speed ups. If you have 4 cores on your machine, then it will run close to 4 times faster.

Speed up restyles with the Rule Tree

For each DOM node, the CSS engine needs to go through all of the rules to do selector matching. For most nodes, this matching likely won’t change very often. For example, if the user hovers over a parent, the rules that match it may change. We still need to recompute style for its descendants to handle property inheritance, but the rules that match those descendants probably won’t change.

It would be nice if we could just make a note of which rules match those descendants so we don’t have to do selector matching for them again… and that’s what the rule tree—borrowed from Firefox’s previous CSS engine— does.

The CSS engine will go through the process of figuring out the selectors that match, and then sorting them by specificity. From this, it creates a linked list of rules.

This list is going to be added to the tree.

A linked list of rules being added to the rule tree

The CSS engine tries to keep the number of branches in the tree to a minimum. To do this, it will try to reuse a branch wherever it can.

If most of the selectors in the list are the same as an existing branch, then it will follow the same path. But it might reach a point where the next rule in the list isn’t in this branch of the tree. Only at that point will it add a new branch.

The last item in the linked list being added to the tree

The DOM node will get a pointer to the rule that was inserted last (in this example, the div#warning rule). This is the most specific one.

On restyle, the engine does a quick check to see whether the change to the parent could potentially change the rules that match children. If not, then for any descendants, the engine can just follow the pointer on the descendant node to get to that rule. From there, it can follow the tree back up to the root to get the full list of matching rules, from most specific to least specific. This means it can skip selector matching and sorting completely.

Skipping selector matching and sorting by specificity

So this helps reduce the work needed during restyle. But it’s still a lot of work during initial styling. If you have 10,000 nodes, you still need to do selector matching 10,000 times. But there’s another way to speed that up.

Speed up initial render (and the cascade) with the style sharing cache

Think about a page with thousands of nodes. Many of those nodes will match the same rules. For example, think of a long Wikipedia page… the paragraphs in the main content area should all end up matching the exact same rules, and have the exact same computed styles.

If there’s no optimization, then the CSS engine has to match selectors and compute styles for each paragraph individually. But if there was a way to prove that the styles will be the same from paragraph to paragraph, then the engine could just do that work once and point each paragraph node to the same computed style.

That’s what the style sharing cache—inspired by Safari and Chrome—does. After it’s done processing a node, it puts the computed style into the cache. Then, before it starts computing styles on the next node, it runs a few checks to see whether it can use something from the cache.

Those checks are:

  • Do the 2 nodes have the same ids, classes, etc? If so, then they would match the same rules.
  • For anything that isn’t selector based—inline styles, for example—do the nodes have the same values? If so, then the rules from above either won’t be overridden, or will be overridden in the same way.
  • Do both parents point to the same computed style object? If so, then the inherited values will also be the same.

Computed styles being shared by all siblings, and then asking the question of whether a cousin can share. Answer: yes

Those checks have been in earlier style sharing caches since the beginning. But there are a lot of other little cases where styles might not match. For example, if a CSS rule uses the :first-child selector, then two paragraphs might not match, even though the checks above suggest that they should.

In WebKit and Blink, the style sharing cache would give up in these cases and not use the cache. As more sites use these modern selectors, the optimization was becoming less and less useful, so the Blink team recently removed it. But it turns out there is a way for the style sharing cache to keep up with these changes.

In Quantum CSS, we gather up all of those weird selectors and check whether they apply to the DOM node. Then we store the answers as ones and zeros. If the two elements have the same ones and zeros, we know they definitely match.

A scoreboard showing 0s and 1s, with the columns labeled with selectors like :first-child

If a DOM node can share styles that have already been computed, you can skip pretty much all of the work. Because pages often have many DOM nodes with the same styles, this style sharing cache can save on memory and also really speed things up.

Skipping all of the work


This is the first big technology transfer of Servo tech to Firefox. Along the way, we’ve learned a lot about how to bring modern, high-performance code written in Rust into the core of Firefox.

We’re very excited to have this big chunk of Project Quantum ready for users to experience first-hand. We’d be happy to have you try it out, and let us know if you find any issues.

Read the whole story
25 days ago
Fantastic explainer!
Share this story

Lesson #3209 - Bees

1 Share

I told all of my professorial colleagues in a departmental retreat today that if I had been making comics under my real name and not under a pseudonym, I would never have gotten the job I had. In consideration of this comic, I stand by that statement.

Read the whole story
88 days ago
Share this story

RotterZwam: Abandoned Water Park Turned Indoor Mushroom Farm

1 Comment
[ By SA Rogers in Abandoned Places & Architecture. ]

Bags of old coffee grounds hang in the dank dressing rooms of an abandoned Rotterdam water park, growing oyster mushrooms. Two men turned the former Tropicana space, an old teen hangout, into the perfect damp, dim environment for their business, making use of the structure while the city council decides what to do with it. ‘RotterZwam’ rents the building on an anti-squat lease and have transformed it into a fascinating example of adaptive reuse and urban farming.

Tropicana is fairly infamous among Rotterdam locals, but closed after the former owner went bankrupt in 2010. The space had been plagued with problems, from hygiene to sexual assault. It sat empty until Siemen Cox and mark Slegers, RotteZwam’s owners, realized it looked like a giant greenhouse.

Though they hope that central glassed-in space – formerly the pool – will eventually become a greenhouse, for now, they’re making use of the dressing rooms and basement, which offer ideal conditions for fungal growth. The crew hangs bags of coffee grinds from the old Tropicana clothes hangers, and before long, they sprout oyster mushrooms.

They collect the coffee from local cafes, transport it in their carrier bicycle, and give the compost to worms to create an extremely low-waste operation. The produce about 20-50kg of mushrooms every week, and sell it to local restaurants, bakeries and food trucks. They also offer DIY mushroom-growing kits.

“Cities like Rotterdam produce nothing but waste and commuters,” they say in an interview with Vice’s Munchies. “This entertainment park represents that perfectly – we build things and, when we don’t want them anymore, we need others to clean it up, to sweep up our garbage. That’s not how nature works, though – in nature wast doesn’t exist. In this building we hardly ever buy a thing, because eery material or nail is already here.”

Share on Facebook

[ By SA Rogers in Abandoned Places & Architecture. ]

[ WebUrbanist | Archives | Galleries | Privacy | TOS ]

Read the whole story
145 days ago
This is incredible!
Share this story

Hexels: grid-based art app for painting pixelated worlds

1 Comment and 2 Shares

Hexels is a charming and powerful art-making app built around grids: perfect for making isometric worlds, geometric illustrations or traditional pixel art.

Hexels is an exciting grid-based painting tool that enables you to effortlessly create brilliant works of art. Whether you’re a pint sized pierogi, a professional cuddler, or my imaginary friend, Hexels’ shape-shifting personality will captivate you. We’ve added advanced features like animation and layers, a sharp, customizable interface, refined painting tools, and a slew of other improvements to bring you the most dynamic version of Hexels to date!

Here's a video that makes clear how it works: https://vimeo.com/149068069

Read the whole story
167 days ago
This looks so neat...
Share this story

Lesson #3131 - Immunity

1 Comment

Medical vaccines are preventative, to help to make sure that fewer people catch diseases. Legal vaccines, on the other hand, are a cure for some! But definitely not for others. But what do you expect when disease is running rampant? Not everyone can afford to get immunity. I mean, they've all got access to it, which, really, isn't that more than enough freedom in itself?

Read the whole story
175 days ago
This is incredible.
Share this story

Social Security Cards Explained

1 Share
From: CGP Grey
Duration: 07:49

The Social Security card and number explained.

Discuss this video: http://reddit.com/r/cgpgrey

Sponsor: www.squarespace.com/grey

Special Thanks:

Stephen P. Morse, PhD. http://stevemorse.org

Ralph Gross, Postdoctoral Fellow, Carnegie Mellon University. https://peexlab.com

Alessandro Acquisti, Professor, Carnegie Mellon University

Mark Govea, Thomas J Miller Jr MD, Bob Kunz, John Buchan, Andres Villacres, Nevin Spoljaric, Christian Cooper, Michael Little, Ripta Pasay, Tony DiLascio, Richard Jenkins, Chris Chapin, Saki Comandao, Tod Kurt, Jason Lewandowski, Michael Mrozek, Phil Gardner, سليمان العقل, Jordan Melville, Martin , Steven Grimm, rictic , Ian , Faust Fairbrook, Chris Woodall, Kozo Ota, Colin Millions, Guillermo , Timothy Basanov, Chris Harshman, ChoiceMechanicalDenver.com , Donal Botkin, David Michaels, Ron Bowes, Tómas Árni Jónasson, Mikko , Derek Bonner, Derek Jackson, Orbit_Junkie , Alistair Forbes, Robert Grünke (trainfart), Veronica Peshterianu, Paul Tomblin, Travis Wichert, chrysilis , Ryan E Manning, Erik Parasiuk, Rhys Parry, Maarten van der Blij, Kevin Anderson, Ryan Nielsen, Esteban Santana Santana, Dag Viggo Lokøen, Tristan Watts-Willis, John Rogers, Edward Adams, Leon , ken mcfarlane, Brandon Callender, Timothy Moran, Peter Lomax, Emil , Tijmen van Dien, ShiroiYami , Alex Schuldberg, Bear , Jacob Ostling, Solon Carter, Rescla , Andrew Proue, Tor Henrik Lehne, David Palomares, Cas Eliëns, Freddi Hørlyck, Ernesto Jimenez, Osric Lord-Williams, Maxime Zielony, Lachlan Holmes , John Bevan, John Lee, Ian N Riopel, AUFFRAY Clement, David , Alex Morales, Alexander Kosenkov, Elizabeth Keathley, Kevin , Pierre Perrott, Tadeo Kondrak, James Bissonette, Jahmal O'Neil, Naturally Curious, Nantiwat , Tianyu Ge, Kevin Jeun, Jason Ruel, JoJo Chehebar, Danny Lunianga Xavier, Jeremy Peng, Jennifer Richardson, Rustam Anvarov

Music by: http://www.davidreesmusic.com

Read the whole story
176 days ago
Share this story
Next Page of Stories