Fast CSS: How Browsers Lay Out Web Pages

David Baron, Mozilla
http://dbaron.org/
@davidbaron
Slides: http://dbaron.org/talks/

This talk

This session explains the basic concepts in browser implementations of core Web document technologies (HTML, XHTML, CSS, and SVG), explains their general performance characteristics and how they interact with Javascript, and explains common optimizations (such as coalescing changes) that browsers make. Rather than presenting specific techniques authors can use, the goal is to build understanding of the performance characteristics of the Web platform so authors can have ideas of what things are likely to be fast or slow, and what types of changes they can experiment with to improve the performance of their Web pages.

Not in this talk

The Pipeline from Markup to Graphics

Parsing

<html><head
><title>Web page parsing</title></head
><div
  ><h1>Web page parsing</h1
  ><p>This is an example Web page.</p
></div>
html head body title "Web p..." div h1 p "Web p..." "This..."

Content tree (DOM tree)

html head body title "Web p..." div h1 "Web p..." svg line g ellipse text "This..."

The Pipeline from Markup to Graphics

Frame tree (rendering tree)

Content Tree / DOM Tree Frame Tree / Rendering Tree Document html head body title "Web p..." div h1 p "Web p..." "This..." Viewport Scroll Block Block Block Block Block Text Text

The Pipeline from Markup to Graphics

Optimization

Unoptimized
Redo everything for any DOM/style change
Optimizations
Skip entire steps
Skip part of a step
Coalesce changes

Optimizations can and do change

Skipping steps

Content Compute style Construct frames Layout Paint Now shown separately

Skipping steps

Skipping steps

<script>
  div.setAttribute("data-rating", "excellent");
</script>

Browsers can check whether there are selectors that care about this attribute, and as a result not even compute style.

Skipping steps

Skipping steps

<script>
  div.setAttribute("data-rating", "excellent");
</script>
<style>
  #list div[data-rating="excellent"] {
    background: yellow; color: black;
  }
</style>

Now there are selectors, so we have to rerun selector matching. But if the div isn't inside an element with id="list", nothing changed.

Skipping steps

Skipping steps

<script>
  div.setAttribute("data-rating", "excellent");
</script>
<style>
  div[data-rating="excellent"] {
    background: yellow; color: black;
  }
</style>

Now the rerunning of selector matching actually makes a new rule match that didn't before.

Skipping steps

Skipping steps

This is how one browser optimizes today. It might change.

Skipping steps: handling a style change

OK, so new rules matched? Let's figure out what properties changed.

Skipping steps: Some properties that make us reconstruct frames / rendering objects

display
position
float (from/to 'none')
transform (from/to 'none')
column-*
counter-*
quotes

Skipping steps: Some properties that make us reflow / lay out again

width
height
font-*
margin-*
padding-*
border-*-width
letter-spacing
word-spacing
line-height

Skipping steps: Some properties that make us repaint

color
background-*
border-*-color
z-index

Skipping steps: handling a style change

Or maybe nothing changed:

p { background: yellow }
p:hover { background: yellow }

Skipping steps

Skipping steps: Some properties that have custom optimizations

transform
cursor

Coalescing

Authors might change the same element twice:

element.style.position = "absolute";
element.style.overflow = "auto";

Coalescing

Authors might make changes where the work needed to handle one subsumes the work for the other:

element.style.backgroundColor = "blue";
element.parentNode.style.opacity = "0.8";

Coalescing

Browsers don't actually process the changes until:

  • It's time to redraw
  • Script asks for something (e.g., style, positions, sizes) that requires processing them

Coalescing: some things that flush style / frame construction

getComputedStyle(element, "").color

(Maybe Gecko-specific; might only flush style)

Coalescing: some things that flush layout

getComputedStyle(element, "").width
element.offsetTop

Coalescing

DO NOT DO THIS:

for (var i = 0; i < n; ++i) {
  var photo = document.getElementById("photo" + i);
  var label = document.getElementById("label" + i);
  label.style.top = photo.offsetHeight + "px";
}

These can sometimes be hidden in frameworks.

Skipping part of frame construction / rendering tree construction

  • Reconstructing a frame implies reconstructing all its descendants
  • There's a tiny cost resulting from the depth of the tree it's in
  • Otherwise cost is mostly proportional to the number of boxes / frames / rendering objects constructed

Skipping part of frame construction / rendering tree construction

<script>
/* likely Gecko-specific */
function flush_frames(elt)
  { getComputedStyle(elt, "").color; }
var start = Date.now();
for (var i = 0; i < 10000; ++i) {
  test_elt.style.display = "none";
  flush_frames(test_elt);
  test_elt.style.display = "";
  flush_frames(test_elt);
}
var time = (Date.now() - start);
</script>

Skipping part of reflow

  • When layout of one element changes, it can move others around
  • Reflow / re-layout runs from the top of the tree down to the things it needs to change
  • intrinsic width computation a separate issue
  • Some properties force relayout of all descendants; some don't
  • So cost of layout depends on surroundings
  • Experiment with surroundings.

Skipping part of reflow

<script>
function flush_layout(elt) { elt.offsetTop; }
var start = Date.now();
for (var i = 0; i < 10000; ++i) {
  test_elt.style.fontSize = "1px"; /* or width */
  flush_frames(test_elt);
  test_elt.style.fontSize = ""; /* or width */
  flush_frames(test_elt);
}
var time = (Date.now() - start);
</script>

Skipping part of painting

  • Repainting an area is about painting a rectangle.
  • Harder to measure because browser controls refresh rate
  • Some ways to spot very bad cases, but vary based on OS and browser

Thanks

This talk
http://dbaron.org/talks/
Other links
window.requestAnimationFrame for hooking in to the browser's refresh cycle
http://stevesouders.com/, for making sites load quickly