Building the Style Tree
Building a Web Browser Engine: Parsing & the DOM
Chapter 8 · Building the Style Tree
Every piece this course has built so far answers one question in isolation: does a selector match (Chapter 5), which rule wins (Chapter 6), what's the final value for one property on one element (Chapter 7). None of them, on their own, hand Course 2's layout engine what it actually needs — one single tree, where every node already carries its own complete, resolved style. That's the style tree, and building it is this chapter's only job.
The StyledNode: a DOM Node Paired With Its Own Computed Style
A second tree, shaped roughly like the DOM tree underneath it, but genuinely distinct — some DOM nodes won't have a matching StyledNode at all, as this chapter's own testing is about to show.
A Naive First Attempt — and the Bug Testing It Reveals
Reasonable-looking: only elements get a computed style at all, so only recurse into element children. It even builds a tree that looks structurally fine at a glance.
<p id="greet">Hello <!-- a note --> <b>world</b></p>, run through build_style_tree_naive. Collecting all text out of the resulting tree gives [] — completely empty. <b> survives (it's an element), but the text "Hello " sitting directly in <p> is gone, and so is "world" inside <b>, since the recursive call on <b>'s own children applies the exact same element-only filter. A style tree with no text in it at all can't render a single visible word — for most real pages, text is the overwhelming majority of what actually gets painted to the screen.
The Fix: Three Cases, Handled Explicitly
<p id="greet">Hello <!-- a note --> <b>world</b></p>, this time through the fixed build_style_tree. Collected text comes back as ['Hello ', 'world'] — both real pieces survive, in the right order, correctly nested inside <b>'s own StyledNode. The comment is gone from the tree entirely, not just visually hidden. And crucially: both text nodes' own style dict shows color: blue — inherited from #greet's own rule — confirming a text node genuinely takes on its containing element's resolved style rather than trying (and failing) to match any selector of its own.
<div> containing a visible <span> and a hidden <div id="hid"> that itself contains a nested <span>. With #hid { display: none; }, the container's own style tree ends up with exactly one child — only the visible sibling. The hidden <div> and its own nested <span> are both gone in a single step, because build_style_tree returns None for #hid before ever recursing into its own children at all — a completely different outcome from merely styling #hid itself invisible while still building boxes for whatever's nested inside it.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| One combined tree, every node carrying its own final computed style | The exact structure Course 2's own layout engine consumes first — a StyledNode tree is what gets converted into a layout tree, not the raw DOM tree and not a separate cascade result per element |
| Recursing only into element children silently drops all text | Chapter 3's own TextNode/CommentNode classes existing as genuinely distinct kinds in the first place — this chapter is the first to actually need that distinction to matter for something beyond just "don't confuse a comment for a tag" |
| display:none removing a whole subtree in one step | Course 2's own upcoming distinction between "not rendered at all" (this chapter's display: none) and "rendered but invisible" (a future visibility: hidden-style property, which still occupies layout space) — a real, separate concept this course deliberately doesn't build |
Hands-On Exercises
Build a <div> whose only child is a single CommentNode — no text, no other elements. Run it through build_style_tree() and confirm the resulting StyledNode's own children list is empty, rather than the function crashing or producing a placeholder node standing in for the comment.
Call build_style_tree() directly on an element whose own matching rule sets display: none — not as someone else's child, but as the root node passed in. Confirm the function returns None outright, and explain why this has to be the same result as when that element is reached deeper inside a larger tree, rather than a special case only handled at the point where a parent decides whether to keep a child.
Reproduce this chapter's own naive-vs-fixed comparison directly: build <p id="greet">Hello <!-- a note --> <b>world</b></p>, run it through both build_style_tree_naive() and build_style_tree(), and collect all rendered text out of each resulting tree. Confirm the naive version returns an empty list while the fixed version returns both real text pieces in order, and identify the exact line in build_style_tree_naive responsible for the difference.
Chapter 8 Quick Reference
- StyledNode: a DOM node (Element or TextNode) paired with its own final computed style and a list of StyledNode children
- Real bug found and fixed: recursing only into element children silently drops every text node — since text is most of what a real page actually renders, this is a severe, not cosmetic, bug
- Three cases, handled explicitly: comments are excluded entirely; text nodes are included with their containing element's own inherited style, never their own rule match; elements get a real computed style via Chapter 6/7's own machinery
- Verified:
display: noneremoves an element and its entire subtree from the style tree in one step — not just that one element while still rendering its children - Next chapter: Default (User-Agent) Stylesheets — where values like
display: noneon<head>or block-level defaults for<div>actually come from, before any author stylesheet is even applied