Inside the Browser Rendering Engine: Parsing, Painting, and Compositing

Team 4 min read

#browser-engine

#rendering-pipeline

#webperf

#frontend

Introduction

Browsers render web pages through a complex, highly optimized pipeline inside the rendering engine. The journey from raw HTML and CSS to pixels on your screen hinges on three core stages: parsing, painting, and compositing. Understanding these stages helps you write faster, smoother interfaces and diagnose rendering hiccups more effectively.

The Rendering Pipeline at a Glance

At a high level, the rendering process follows this flow:

  • The parser builds a DOM from HTML and a CSSOM from CSS.
  • The render tree is created by combining DOM and CSSOM, filtering out non-visual nodes.
  • The layout (reflow) computes geometry for each node.
  • Painting fills in pixels for visible content.
  • Compositing merges painted layers, potentially on the GPU, to produce the final image.
  • The result is composited to the screen and updated continuously as inputs change.

Parsing: Turning HTML into a DOM

  • Tokenization: The HTML string is scanned to produce tokens representing tags, attributes, and text.
  • Tree construction: Tokens are assembled into a DOM tree. The parser follows the HTML5 parsing algorithm, which includes recovery rules for malformed markup.
  • Error handling: If the HTML is not well-formed, the browser makes reasonable guesses to produce a stable DOM, ensuring a usable page even with imperfect markup.
  • Impact on performance: Parsing is front-loaded; large HTML documents can stall initial render if parsing blocks the main thread. Techniques like streaming HTML (where supported) and progressive rendering can help.

Painting: From Style to Screen

  • CSSOM: Separately, the CSS is parsed into a CSS Object Model, which encodes style rules and their specificity.
  • Render tree: The DOM and CSSOM are combined to form the render tree, which represents visible elements and their render properties (size, position, color, etc.). Non-visual elements are excluded.
  • Layout (reflow): The engine computes the geometry — where each box sits, how big it is, and how text flows within it.
  • Painting: Each node is painted in a sequence (the paint order) onto one or more bitmap buffers. This step fills in pixels for backgrounds, borders, text, images, shadows, and other visuals.
  • Repaint vs reflow: Repaint occurs when visuals change without affecting layout (e.g., color). Reflow happens when layout changes (e.g., content size changes or window resize) and can trigger additional paints.
  • Performance considerations: Complex CSS (deep shadows, gradients, filters) and large paint regions can increase paint work. Techniques like reducing compositing boundaries and isolating costly effects can help.

Compositing: Merging Layers on the GPU

  • Layer creation: The compositor decides which parts of the render tree can be promoted to layers (e.g., elements with transforms, opacity changes, or fixed positioning). Layers are rendered separately.
  • Rasterization: Each layer is rasterized into an off-screen texture. This is where the actual pixel data for a layer is generated.
  • GPU acceleration: The compositing step often leverages the GPU to merge layers efficiently, applying transforms, opacity, and other effects.
  • Final compositing: The compositor combines layer textures into the final screen image, respecting z-order and scrolling.
  • Why it matters: Properly promoted layers can reduce the frequency of full-page repaints and improves animation smoothness. Mispromoted layers can cause overdraw and memory pressure, hurting performance.

Putting it Together: The Full Cycle

  • Initial load: HTML parses into DOM, CSS parses into CSSOM, the render tree is built, layout runs, painting fills pixels, and compositing delivers the final image to the screen.
  • Interactions and updates: User input or script changes trigger reflows (layout), repaints, or re-compositing as needed. Browsers try to minimize work by batching changes and prioritizing visible content.
  • 60fps goal: For smooth animations, browsers strive to complete the full update cycle within roughly 16.7 milliseconds per frame, often splitting work across the CPU and GPU.

Performance and Debugging Tips

  • Minimize layout thrash: Avoid reading layout properties (like offsetWidth) in tight loops; batch DOM changes.
  • Prefer transforms and opacity for animations: They often get promoted to compositor layers and can animate smoothly on the GPU.
  • Use will-change wisely: Hint elements that will change to help the engine optimize, but don’t overuse it.
  • Reduce paint cost: Simplify paint paths, avoid heavy shadows, and limit complex textures in frequently repainted regions.
  • Tooling: Leverage browser performance and rendering panels to identify long tasks, layout shifts, and paint hot spots. Look for frequent reflows or repaints and address the root cause.
  • Content visibility: Consider content-visibility: auto or isolating off-screen content to reduce wasted painting work.

Final Thoughts

Understanding parsing, painting, and compositing offers a practical lens for diagnosing rendering issues and optimizing frontend performance. By aligning code and layouts with how the rendering engine processes content, you can reduce jank, improve frame times, and deliver smoother user experiences.