LEARN BROWSER INTERNALS
Learn Browser Internals: From URL to Pixels
Goal: Deeply understand how web browsers work—from typing a URL to painting pixels on screen. Master the rendering pipeline, JavaScript execution, networking, security, and build your own browser components.
Why Learn Browser Internals?
Every web developer uses browsers daily, but few understand what happens under the hood. When you type a URL and press Enter, an incredibly complex symphony begins:
- DNS resolution finds the server’s IP address
- TCP/TLS handshakes establish secure connections
- HTTP requests fetch HTML, CSS, JavaScript
- Parsers tokenize and build trees
- The rendering engine calculates layout and paints pixels
- The JavaScript engine interprets and JIT-compiles code
- GPU acceleration composites layers for smooth animations
Understanding this unlocks:
- Better performance optimization - Know what causes jank and how to fix it
- Deeper debugging skills - Understand why things break
- Security awareness - Know how browsers protect users
- Framework design insight - See why React/Vue work the way they do
- Career opportunities - Browser engineers are in high demand
After completing these projects, you will:
- Build your own HTML parser and DOM
- Create a CSS selector engine
- Implement a layout algorithm
- Write a JavaScript interpreter
- Understand the complete rendering pipeline
- Build a mini browser from scratch
Core Concept Analysis
The Browser’s Journey: URL to Pixels
┌─────────────────────────────────────────────────────────────────────────────┐
│ USER TYPES URL │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. NETWORKING │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DNS Lookup │→ │ TCP Handshake│→ │ TLS Handshake│→ │ HTTP Request │ │
│ │ (find IP) │ │ (SYN-ACK) │ │ (encryption) │ │ (GET /page) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼ HTML/CSS/JS received
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. PARSING │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HTML Parser │→ │ DOM Tree │ │ CSS Parser │→ │ CSSOM Tree │ │
│ │ (tokenize) │ │ (build tree) │ │ (tokenize) │ │ (build tree) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼ DOM + CSSOM ready
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. RENDERING │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Render Tree │→ │ Layout │→ │ Paint │→ │ Composite │ │
│ │ (combine) │ │ (geometry) │ │ (draw) │ │ (GPU layers) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼ Pixels on screen!
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. JAVASCRIPT EXECUTION (parallel to parsing) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Parse JS │→ │ Compile │→ │ Execute │→ │ Optimize │ │
│ │ (tokenize) │ │ (bytecode) │ │ (interpret) │ │ (JIT) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Key Concepts Explained
1. Browser Architecture (Multi-Process)
Modern browsers like Chrome use a multi-process architecture for security and stability:
┌─────────────────────────────────────────────────────────────────────────────┐
│ BROWSER PROCESS │
│ • UI (address bar, tabs, bookmarks) │
│ • Network requests │
│ • Storage access │
│ • Coordinates other processes │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ RENDERER │ │ RENDERER │ │ GPU PROCESS │ │ NETWORK │
│ PROCESS │ │ PROCESS │ │ │ │ PROCESS │
│ (Tab 1) │ │ (Tab 2) │ │ • Drawing │ │ │
│ │ │ │ │ • Compositing│ │ • DNS │
│ • Blink │ │ • Blink │ │ • WebGL │ │ • TCP/TLS │
│ • V8 │ │ • V8 │ │ │ │ • HTTP │
│ • DOM │ │ • DOM │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
Benefits:
• Tab crash doesn't bring down browser
• Site isolation for security
• Better memory management
• Parallel processing
2. The Rendering Pipeline
HTML String → Tokenizer → DOM Tree
│
├──────────────────┐
▼ │
CSS String → Parser → CSSOM Tree │
│ │
▼ │
Attachment │
(Style Calculation) │
│ │
▼ │
Render Tree │
(only visible nodes) │
│ │
▼ │
Layout │
(calculate geometry) │
│ ▼
▼ JavaScript
Paint (can modify)
(draw to bitmap) │
│ │
▼ │
Composite ◄──────────────┘
(layer to screen)
Each step can trigger the next:
• JavaScript modifies DOM → Style → Layout → Paint → Composite
• CSS animation on transform/opacity → Composite only (fast!)
3. DOM Tree Structure
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Page</title>
</head>
<body>
<div id="app">
<h1>Hello</h1>
<p>World</p>
</div>
</body>
</html>
DOM Tree:
Document
│
html (Element)
/ \
head (Element) body (Element)
│ │
title (Element) div#app (Element)
│ / \
"Page" (Text) h1 (Element) p (Element)
│ │
"Hello" (Text) "World" (Text)
Node Types:
• Document - Root node
• Element - HTML tags
• Text - Text content
• Comment - HTML comments
• DocumentFragment - Lightweight container
4. CSS Specificity & Cascade
Specificity Calculation:
┌─────────────────────────────────────────────────────────────────┐
│ Selector │ Inline │ ID │ Class │ Element │ Total │
├─────────────────────────────────────────────────────────────────┤
│ style="..." │ 1 │ 0 │ 0 │ 0 │ 1,0,0,0│
│ #header │ 0 │ 1 │ 0 │ 0 │ 0,1,0,0│
│ .nav.active │ 0 │ 0 │ 2 │ 0 │ 0,0,2,0│
│ div.container p │ 0 │ 0 │ 1 │ 2 │ 0,0,1,2│
│ ul li a │ 0 │ 0 │ 0 │ 3 │ 0,0,0,3│
│ * │ 0 │ 0 │ 0 │ 0 │ 0,0,0,0│
└─────────────────────────────────────────────────────────────────┘
Cascade Order (highest to lowest):
1. !important declarations
2. Inline styles
3. ID selectors
4. Class/attribute/pseudo-class selectors
5. Element/pseudo-element selectors
6. Order of appearance (later wins)
5. JavaScript Engine Pipeline (V8)
JavaScript Source
│
▼
┌──────────────┐
│ Parser │ ─── Parse into AST (Abstract Syntax Tree)
└──────────────┘
│
▼
┌──────────────┐
│ Ignition │ ─── Compile to bytecode (fast startup)
│ (Interpreter)│ Execute immediately
└──────────────┘
│
│ Hot function detected
│ (called many times)
▼
┌──────────────┐
│ TurboFan │ ─── Compile to optimized machine code
│(Optimizing JIT)│ Uses type feedback
└──────────────┘
│
│ Type assumption wrong?
▼
┌──────────────┐
│ Deoptimization│ ─── Fall back to bytecode
│ │ Re-collect type info
└──────────────┘
Hidden Classes:
┌────────────────────────────────────────────────────────┐
│ const obj1 = { x: 1, y: 2 } → Hidden Class C0 │
│ const obj2 = { x: 3, y: 4 } → Hidden Class C0 (same!)│
│ │
│ obj1.z = 5 → Creates Hidden Class C1 (transition) │
│ │
│ Monomorphic: All objects same shape → FAST │
│ Polymorphic: 2-4 shapes → slower │
│ Megamorphic: Many shapes → SLOW (no caching) │
└────────────────────────────────────────────────────────┘
6. Event Loop & Task Queues
┌─────────────────────────────────────────────────────────────────────────────┐
│ CALL STACK │
│ (synchronous code execution) │
└─────────────────────────────────────────────────────────────────────────────┘
↑
│ Execute one task
│
┌─────────────────────────────────────────────────────────────────────────────┐
│ EVENT LOOP │
│ 1. Execute oldest task from Task Queue │
│ 2. Execute ALL microtasks │
│ 3. Render if needed (rAF callbacks, then paint) │
│ 4. Repeat │
└─────────────────────────────────────────────────────────────────────────────┘
↑ ↑ ↑
│ │ │
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ TASK QUEUE │ │MICROTASK QUEUE│ │ rAF QUEUE │
│ (Macrotasks) │ │ │ │ │
├───────────────┤ ├───────────────┤ ├───────────────┤
│ • setTimeout │ │ • Promise.then│ │ • requestAnim │
│ • setInterval │ │ • queueMicro │ │ Frame │
│ • I/O events │ │ • MutationObs │ │ │
│ • UI events │ │ │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
Execution order example:
console.log('1'); // Sync
setTimeout(() => console.log('2')); // Task queue
Promise.resolve().then(() => console.log('3')); // Microtask
requestAnimationFrame(() => console.log('4')); // rAF queue
console.log('5'); // Sync
Output: 1, 5, 3, 4, 2 (microtasks before tasks, rAF before paint)
7. Browser Caching Layers
Request comes in:
│
▼
┌──────────────────────────────────────────────────────────────┐
│ MEMORY CACHE │
│ • Fastest (RAM) │
│ • Short-lived (until tab closes) │
│ • Images, scripts loaded on current page │
└──────────────────────────────────────────────────────────────┘
│ Miss
▼
┌──────────────────────────────────────────────────────────────┐
│ SERVICE WORKER CACHE │
│ • Programmable (Cache API) │
│ • Persists across sessions │
│ • Enables offline support │
│ • You control the caching strategy │
└──────────────────────────────────────────────────────────────┘
│ Miss
▼
┌──────────────────────────────────────────────────────────────┐
│ HTTP CACHE │
│ • Disk-based (persistent) │
│ • Controlled by HTTP headers │
│ • Cache-Control, ETag, Last-Modified │
│ • Automatic browser management │
└──────────────────────────────────────────────────────────────┘
│ Miss
▼
┌──────────────────────────────────────────────────────────────┐
│ NETWORK │
│ • DNS → TCP → TLS → HTTP │
│ • Slowest option │
│ • Response may be cached for next time │
└──────────────────────────────────────────────────────────────┘
8. Security Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ SAME-ORIGIN POLICY (SOP) │
│ │
│ Origin = protocol + host + port │
│ │
│ https://example.com:443/page │
│ │ │ │ │
│ └──────┬─┴────────┘ │
│ │ │
│ Origin │
│ │
│ Same origin examples: │
│ ✓ https://example.com/a and https://example.com/b │
│ │
│ Different origin: │
│ ✗ http://example.com (different protocol) │
│ ✗ https://sub.example.com (different host) │
│ ✗ https://example.com:8080 (different port) │
└─────────────────────────────────────────────────────────────────────────────┘
│
Can be relaxed with │
▼
┌───────────────────────────────────┴───────────────────────────────────────┐
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ CORS │ │ CSP │ │
│ │ │ │ │ │
│ │ Server says: │ │ Server says: │ │
│ │ "Allow requests │ │ "Only allow │ │
│ │ from origin X" │ │ scripts from │ │
│ │ │ │ these sources" │ │
│ │ Access-Control- │ │ Content-Security │ │
│ │ Allow-Origin: │ │ -Policy: │ │
│ │ https://foo.com │ │ script-src 'self'│ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Project List
The following 17 projects will take you from understanding to building browser components.
Project 1: Build an HTML Tokenizer
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, Python, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Parsing / Lexical Analysis
- Software or Tool: WHATWG HTML spec
- Main Book: “Compilers: Principles, Techniques, and Tools” (Dragon Book)
What you’ll build: An HTML tokenizer that converts raw HTML strings into a stream of tokens (start tags, end tags, text, comments, doctype).
Why it teaches browser internals: Tokenization is the first step in HTML parsing. Understanding the state machine behind it reveals how browsers handle malformed HTML.
Core challenges you’ll face:
- State machine implementation → maps to different parsing contexts
- Character reference handling → maps to & < entities
- Self-closing tags → maps to br, img, input, etc.
- Error handling → maps to how browsers fix malformed HTML
Resources for key challenges:
- WHATWG HTML Parsing Spec - The official standard
- HTML Parser Idiosyncrasies - Quirks explained
- “Let’s Build a Browser Engine” Part 2 - Matt Brubeck
Key Concepts:
- Tokenizer State Machine: WHATWG Spec Section 13.2.5
- Token Types: “Compilers” Ch. 3 - Aho et al.
- Error Recovery: HTML Parser Idiosyncrasies
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Understanding of state machines, basic parsing concepts
Real world outcome:
const html = `<div class="container"><p>Hello, <b>world</b>!</p></div>`;
const tokens = tokenize(html);
console.log(tokens);
// Output:
// [
// { type: 'StartTag', name: 'div', attributes: { class: 'container' } },
// { type: 'StartTag', name: 'p', attributes: {} },
// { type: 'Character', data: 'Hello, ' },
// { type: 'StartTag', name: 'b', attributes: {} },
// { type: 'Character', data: 'world' },
// { type: 'EndTag', name: 'b' },
// { type: 'Character', data: '!' },
// { type: 'EndTag', name: 'p' },
// { type: 'EndTag', name: 'div' }
// ]
Implementation Hints:
The HTML tokenizer uses a state machine:
States:
• Data State (default)
• Tag Open State (saw '<')
• Tag Name State (reading tag name)
• Attribute Name State
• Attribute Value State
• Self-Closing Start Tag State (saw '/')
• End Tag Open State (saw '</')
• Comment Start State (saw '<!--')
... and many more (80+ states in the spec!)
Simplified state transitions:
Data State:
'<' → Tag Open State
'&' → Character Reference State
EOF → Emit EOF token
other → Emit character token
Tag Open State:
'!' → Markup Declaration Open State
'/' → End Tag Open State
letter → Tag Name State, create start tag token
'?' → Bogus Comment State
Tag Name State:
whitespace → Before Attribute Name State
'/' → Self-Closing Start Tag State
'>' → Data State, emit tag token
letter → Append to tag name
Questions to guide implementation:
- How do you handle
<br/>vs<br>vs<br >? - What happens with
<div class="foo>bar">? - How do you handle
<script>and<style>differently? - What about
<!DOCTYPE html>?
Learning milestones:
- Tokenize simple HTML → Basic state machine works
- Handle attributes → Quote handling, escaping
- Handle edge cases → Malformed HTML, entities
- Match spec behavior → Test against browser output
Project 2: Build a DOM Tree
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, Python
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Tree Construction / Data Structures
- Software or Tool: WHATWG DOM spec
- Main Book: N/A (MDN Web Docs)
What you’ll build: A DOM tree implementation with Document, Element, Text, and Comment nodes, supporting traversal and manipulation.
Why it teaches browser internals: The DOM is the central data structure browsers use. Understanding its shape unlocks understanding of event handling, CSS matching, and rendering.
Core challenges you’ll face:
- Node hierarchy → maps to parent/child/sibling relationships
- Tree construction algorithm → maps to how tokens become nodes
- Implicit elements → maps to html, head, body auto-creation
- DOM API methods → maps to querySelector, appendChild, etc.
Resources for key challenges:
- WHATWG DOM Spec - Official standard
- MDN DOM Documentation
- Tree Construction Spec
Key Concepts:
- Node Types: DOM Spec - Node interface
- Tree Traversal: MDN - TreeWalker
- Tree Construction: WHATWG HTML Spec Section 13.2.6
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1 (HTML Tokenizer)
Real world outcome:
const html = `<html><head><title>Test</title></head><body><p>Hello</p></body></html>`;
const doc = parse(html);
console.log(doc.body.firstChild.textContent); // "Hello"
const p = doc.querySelector('p');
console.log(p.tagName); // "P"
console.log(p.parentNode.tagName); // "BODY"
const newDiv = doc.createElement('div');
newDiv.textContent = 'New content';
doc.body.appendChild(newDiv);
console.log(doc.body.innerHTML);
// "<p>Hello</p><div>New content</div>"
Implementation Hints:
Node class hierarchy:
Node (base class)
├── Document
├── DocumentType
├── DocumentFragment
├── Element
│ ├── HTMLElement
│ │ ├── HTMLDivElement
│ │ ├── HTMLParagraphElement
│ │ └── ...
├── CharacterData
│ ├── Text
│ └── Comment
└── Attr
Tree construction uses a stack of open elements:
Processing: <html><body><div><p>text</p></div></body></html>
Stack: [html]
Stack: [html, body]
Stack: [html, body, div]
Stack: [html, body, div, p]
- Create Text node "text", append to p
Stack: [html, body, div] ← </p> pops p
Stack: [html, body] ← </div> pops div
Stack: [html] ← </body> pops body
Stack: [] ← </html> pops html
Key methods to implement:
appendChild(child)- Add node to endinsertBefore(newNode, referenceNode)- Insert at positionremoveChild(child)- Remove and returnquerySelector(selector)- Find first matchquerySelectorAll(selector)- Find all matchestextContent- Get/set text
Learning milestones:
- Build tree from tokens → Basic construction works
- Implement traversal → firstChild, nextSibling, etc.
- Add manipulation methods → appendChild, removeChild
- Implement querySelector → CSS selector matching
Project 3: Build a CSS Tokenizer and Parser
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, Python
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Parsing / CSS Syntax
- Software or Tool: CSS Syntax Module spec
- Main Book: “CSS: The Definitive Guide”
What you’ll build: A CSS parser that produces an AST of rules, selectors, and declarations.
Why it teaches browser internals: CSS parsing is simpler than HTML but has its own quirks. Understanding it is essential for selector matching and cascading.
Core challenges you’ll face:
- Tokenizing CSS → maps to identifiers, numbers, strings, operators
- Parsing selectors → maps to combinators, pseudo-classes
- Parsing declarations → maps to property: value pairs
- At-rules → maps to @media, @keyframes, @import
Resources for key challenges:
- CSS Syntax Module Level 3
- “Let’s Build a Browser Engine” Part 3 - Matt Brubeck
- Jotform CSS Parser
Key Concepts:
- CSS Tokenization: CSS Syntax Module Section 4
- Selector Parsing: CSS Selectors Level 4
- Value Parsing: CSS Values Module
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic parsing knowledge
Real world outcome:
const css = `
.container {
display: flex;
background: #f0f0f0;
}
.container > .item:hover {
color: red;
transform: scale(1.1);
}
`;
const stylesheet = parseCSS(css);
console.log(JSON.stringify(stylesheet, null, 2));
// {
// rules: [
// {
// selectors: [{ type: 'class', name: 'container' }],
// declarations: [
// { property: 'display', value: 'flex' },
// { property: 'background', value: '#f0f0f0' }
// ]
// },
// {
// selectors: [
// {
// type: 'compound',
// parts: [
// { type: 'class', name: 'container' },
// { type: 'combinator', value: '>' },
// { type: 'class', name: 'item' },
// { type: 'pseudo-class', name: 'hover' }
// ]
// }
// ],
// declarations: [
// { property: 'color', value: 'red' },
// { property: 'transform', value: 'scale(1.1)' }
// ]
// }
// ]
// }
Implementation Hints:
CSS grammar is straightforward:
stylesheet: rule*
rule: selector-list '{' declaration-list '}'
selector-list: selector (',' selector)*
declaration-list: declaration (';' declaration)*
declaration: property ':' value
Selector types:
- Type:
div,p,span - Class:
.className - ID:
#idName - Attribute:
[attr=value] - Pseudo-class:
:hover,:nth-child(n) - Pseudo-element:
::before,::after - Combinators: ` ` (descendant),
>(child),+(adjacent),~(sibling)
Questions:
- How do you handle nested parentheses in values like
calc(100% - 20px)? - How do you parse
@mediaqueries? - How do you handle vendor prefixes?
Learning milestones:
- Tokenize CSS → Handle all token types
- Parse rules → Selector + declarations
- Parse complex selectors → Combinators, pseudo-classes
- Handle at-rules → @media, @keyframes
Project 4: Build a CSS Selector Matching Engine
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, Python
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: DOM Traversal / Pattern Matching
- Software or Tool: CSS Selectors Level 4 spec
- Main Book: N/A (Specs and implementations)
What you’ll build: A selector engine that matches CSS selectors against DOM elements, implementing querySelector and querySelectorAll.
Why it teaches browser internals: Selector matching is called millions of times during page load. Understanding its algorithms reveals browser optimization strategies.
Core challenges you’ll face:
- Right-to-left matching → maps to why browsers match from right
- Specificity calculation → maps to which rule wins
- Combinator handling → maps to traversing the tree
- Pseudo-class matching → maps to :first-child, :nth-child
Resources for key challenges:
- CSS Selectors Level 4
- Selery (GitHub) - Reference implementation
- web.dev Specificity
Key Concepts:
- Right-to-Left Matching: Browser optimization technique
- Specificity Algorithm: CSS Cascade Module
- Pseudo-class Semantics: Selectors Level 4
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 2-3 (DOM, CSS Parser)
Real world outcome:
const doc = parseHTML(`
<div class="container">
<ul id="menu">
<li class="item active">Home</li>
<li class="item">About</li>
<li class="item">Contact</li>
</ul>
</div>
`);
// querySelector
const active = querySelector(doc, '.item.active');
console.log(active.textContent); // "Home"
// querySelectorAll
const items = querySelectorAll(doc, '.container > ul > li');
console.log(items.length); // 3
// Complex selectors
const firstLi = querySelector(doc, 'ul#menu li:first-child');
console.log(firstLi.textContent); // "Home"
// Specificity
console.log(calculateSpecificity('#menu .item')); // [0, 1, 1, 0]
console.log(calculateSpecificity('ul li.active')); // [0, 0, 1, 2]
Implementation Hints:
Why right-to-left matching?
Selector: .container ul li a
Left-to-right (naive):
1. Find all .container elements
2. For each, check if it has ul descendants
3. For each ul, check if it has li descendants
4. For each li, check if it has a descendants
→ Very expensive if .container is near root!
Right-to-left (browsers do this):
1. Find all <a> elements (usually few)
2. For each <a>, check parent is <li>
3. Check that li's ancestor is <ul>
4. Check that ul's ancestor is .container
→ Usually eliminates candidates quickly!
Matching algorithm:
match(element, selector):
if selector is simple:
return matchSimple(element, selector)
if selector has combinator:
rightPart = lastSimpleSelector(selector)
if not matchSimple(element, rightPart):
return false
leftPart = selectorWithoutLastPart(selector)
combinator = getCombinator(selector)
if combinator is ' ' (descendant):
for ancestor in element.ancestors:
if match(ancestor, leftPart):
return true
return false
if combinator is '>' (child):
return match(element.parent, leftPart)
// ... other combinators
Learning milestones:
- Match simple selectors → Type, class, ID, attribute
- Handle combinators → Descendant, child, sibling
- Implement pseudo-classes → :first-child, :nth-child
- Calculate specificity → Score selectors correctly
Project 5: Implement the CSS Cascade
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, Python
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Style Resolution / Algorithm Design
- Software or Tool: CSS Cascade Module spec
- Main Book: “CSS: The Definitive Guide”
What you’ll build: A style resolver that takes a DOM tree and stylesheet, and computes the final styles for each element.
Why it teaches browser internals: The cascade is how browsers determine which CSS rule wins. Understanding it explains unexpected styling behavior.
Core challenges you’ll face:
- Origin sorting → maps to author, user, user-agent styles
- Specificity comparison → maps to which selector wins
- Inheritance → maps to font, color inherit by default
- Initial and computed values → maps to percentage resolution
Resources for key challenges:
Key Concepts:
- Cascade Algorithm: CSS Cascade Module
- Inheritance: CSS Cascade Section 3
- Computed Values: CSS Values Module
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Projects 2-4 (DOM, CSS Parser, Selector Engine)
Real world outcome:
const html = `
<div id="app" class="container" style="color: blue;">
<p class="text">Hello</p>
</div>
`;
const css = `
p { color: black; font-size: 16px; }
.container p { color: green; }
#app .text { color: red; }
`;
const doc = parseHTML(html);
const stylesheet = parseCSS(css);
const styles = computeStyles(doc, stylesheet);
// Check computed styles
const pStyles = styles.get(doc.querySelector('p'));
console.log(pStyles.color); // "red" (ID selector wins)
console.log(pStyles.fontSize); // "16px" (from base rule)
// But inline styles win!
const divStyles = styles.get(doc.querySelector('#app'));
console.log(divStyles.color); // "blue" (inline style)
Implementation Hints:
Cascade algorithm steps:
1. Find all declarations that match the element
2. Sort by origin and importance:
a. User-agent normal (browser defaults)
b. User normal (user stylesheets)
c. Author normal (website CSS)
d. Author !important
e. User !important
f. User-agent !important
3. For same origin, sort by specificity:
- Compare (inline, id, class, type) tuples
- [1,0,0,0] > [0,1,0,0] > [0,0,1,0] > [0,0,0,1]
4. For same specificity, use source order:
- Later declaration wins
Handle inheritance:
Inherited properties (partial list):
- color
- font-family, font-size, font-weight
- line-height
- text-align
- visibility
Non-inherited properties (partial list):
- background
- border
- margin, padding
- width, height
- display, position
Learning milestones:
- Match rules to elements → Collect applicable declarations
- Sort by origin → User-agent < user < author
- Compare specificity → Calculate and compare scores
- Handle inheritance → Propagate inherited values
Project 6: Build a Layout Engine (Box Model)
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust, C++
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Layout / Geometry Calculation
- Software or Tool: CSS Box Model spec
- Main Book: “Computer Graphics from Scratch”
What you’ll build: A layout engine that calculates the position and size of every element in a document.
Why it teaches browser internals: Layout is one of the most complex parts of a browser. Understanding it reveals why certain CSS is slow and how to optimize.
Core challenges you’ll face:
- Block layout → maps to vertical stacking
- Inline layout → maps to text flow, line breaks
- Box model calculation → maps to content + padding + border + margin
- Containing blocks → maps to percentage resolution
Resources for key challenges:
- CSS Box Model
- “Let’s Build a Browser Engine” Part 5 - Matt Brubeck
- CSS Visual Formatting Model
Key Concepts:
- Box Model: CSS Box Model Module
- Block Formatting Context: CSS Display Module
- Containing Block: CSS Positioned Layout Module
Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Projects 2-5 (DOM, CSS, Cascade)
Real world outcome:
const html = `
<div style="width: 200px; padding: 10px; border: 1px solid black;">
<p style="margin: 10px;">Hello</p>
<p style="margin: 10px;">World</p>
</div>
`;
const doc = parseHTML(html);
const layoutTree = layout(doc, viewportWidth: 800, viewportHeight: 600);
// Each node has computed layout
const divLayout = layoutTree.root.firstChild;
console.log(divLayout);
// {
// x: 0,
// y: 0,
// width: 222, // 200 + 10*2 + 1*2 (content + padding + border)
// height: 82, // content height + padding + border
// contentBox: { x: 11, y: 11, width: 200, height: 60 },
// paddingBox: { x: 1, y: 1, width: 220, height: 80 },
// borderBox: { x: 0, y: 0, width: 222, height: 82 },
// children: [...]
// }
Implementation Hints:
Box model visualization:
┌───────────────────────────────────────────────┐
│ MARGIN │
│ ┌─────────────────────────────────────────┐ │
│ │ BORDER │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ PADDING │ │ │
│ │ │ ┌─────────────────────────────┐ │ │ │
│ │ │ │ CONTENT │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ width × height │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────┘
Block layout algorithm:
layoutBlock(node, containingWidth):
// 1. Calculate width
contentWidth = resolveWidth(node.style.width, containingWidth)
// 2. Layout children
y = node.paddingTop + node.borderTop
for child in node.children:
if child.isBlock:
layoutBlock(child, contentWidth)
child.x = node.marginLeft
child.y = y
y += child.totalHeight + marginBetween
// 3. Calculate height
if node.style.height == 'auto':
node.height = y - node.paddingTop
else:
node.height = resolveHeight(node.style.height)
Learning milestones:
- Calculate box dimensions → Content, padding, border, margin
- Layout block elements → Vertical stacking
- Handle percentages → Resolve against containing block
- Handle auto margins → Centering, margin collapse
Project 7: Build a Simple Paint System
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript (with Canvas)
- Alternative Programming Languages: Rust (with graphics library)
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Graphics / Rendering
- Software or Tool: Canvas API, WebGL
- Main Book: “Computer Graphics from Scratch”
What you’ll build: A paint system that takes a layout tree and draws it to a canvas.
Why it teaches browser internals: Painting is where the abstract DOM becomes visible pixels. Understanding it reveals optimization strategies like layer promotion.
Core challenges you’ll face:
- Drawing order (z-index) → maps to stacking contexts
- Clipping and overflow → maps to overflow: hidden
- Background and borders → maps to proper rendering order
- Text rendering → maps to font metrics, line height
Resources for key challenges:
- CSS Backgrounds and Borders
- Canvas API (MDN)
- “Computer Graphics from Scratch” - Gabriel Gambetta
Key Concepts:
- Painting Order: CSS Painting Order
- Stacking Contexts: CSS Positioned Layout
- Canvas Drawing: MDN Canvas Tutorial
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 6 (Layout Engine)
Real world outcome:
const html = `
<div style="background: #f0f0f0; padding: 20px; border: 2px solid #333;">
<h1 style="color: #0066cc; font-size: 24px;">Hello Browser!</h1>
<p style="color: #333;">This is rendered by our engine.</p>
</div>
`;
const doc = parseHTML(html);
const layoutTree = layout(doc, 800, 600);
const canvas = document.getElementById('output');
const ctx = canvas.getContext('2d');
// Paint the layout tree to canvas
paint(layoutTree, ctx);
// Result: A visual rendering of the HTML on the canvas!
Implementation Hints:
Painting order (for each stacking context):
1. Background of root element
2. Child stacking contexts with negative z-index
3. Block-level descendants (non-positioned)
4. Floats
5. In-flow inline content
6. Positioned descendants with z-index: auto or 0
7. Child stacking contexts with positive z-index
Paint commands:
paint(node, ctx):
// 1. Paint background
if node.style.background:
ctx.fillStyle = node.style.background
ctx.fillRect(node.x, node.y, node.width, node.height)
// 2. Paint border
if node.style.border:
ctx.strokeStyle = node.style.borderColor
ctx.lineWidth = node.style.borderWidth
ctx.strokeRect(...)
// 3. Paint children (recursive)
for child in node.children:
paint(child, ctx)
// 4. Paint text
if node.isText:
ctx.fillStyle = node.style.color
ctx.font = `${node.style.fontSize} ${node.style.fontFamily}`
ctx.fillText(node.text, node.x, node.y)
Learning milestones:
- Render backgrounds → Fill rectangles
- Render borders → Stroke rectangles
- Render text → Font rendering
- Handle z-index → Stacking contexts
Project 8: Build a JavaScript Interpreter
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: C, Rust
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Interpreters / Language Implementation
- Software or Tool: Acorn parser (or build your own)
- Main Book: “Crafting Interpreters” by Robert Nystrom
What you’ll build: A JavaScript interpreter that can execute a subset of JavaScript, including variables, functions, closures, and objects.
Why it teaches browser internals: V8 and other JS engines are at the heart of browsers. Understanding interpretation unlocks performance optimization.
Core challenges you’ll face:
- Parsing to AST → maps to syntax analysis
- Environment/scope → maps to lexical scoping, closures
- Function execution → maps to call stack, activation records
- Object semantics → maps to prototype chain, this binding
Resources for key challenges:
- Crafting Interpreters - Free online book
- Building an Interpreter from Scratch - Dmitry Soshnikov
- Build a JS Interpreter with Acorn
Key Concepts:
- AST Interpretation: “Crafting Interpreters” Part II
- Closures: “Crafting Interpreters” Chapter 11
- Objects: “Crafting Interpreters” Chapter 12
Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: Understanding of programming language concepts
Real world outcome:
const code = `
function makeCounter() {
let count = 0;
return function() {
count = count + 1;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
`;
const result = interpret(code);
// Output:
// 1
// 2
// 3
Implementation Hints:
Interpreter architecture:
Source Code
│
▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Lexer │ ──▶ │ Parser │ ──▶ │Evaluator│ ──▶ Result
└─────────┘ └─────────┘ └─────────┘
tokens AST execution
Environment for scoping:
class Environment {
values: Map<string, any>;
parent: Environment | null;
get(name) {
if (this.values.has(name)) {
return this.values.get(name);
}
if (this.parent) {
return this.parent.get(name);
}
throw new Error(`Undefined variable: ${name}`);
}
set(name, value) {
this.values.set(name, value);
}
}
Evaluating expressions:
evaluate(node, env):
switch (node.type):
case 'NumberLiteral':
return node.value
case 'BinaryExpression':
left = evaluate(node.left, env)
right = evaluate(node.right, env)
switch (node.operator):
case '+': return left + right
case '-': return left - right
// ...
case 'Identifier':
return env.get(node.name)
case 'FunctionDeclaration':
// Capture current environment for closure
return { params: node.params, body: node.body, closure: env }
case 'CallExpression':
func = evaluate(node.callee, env)
args = node.arguments.map(a => evaluate(a, env))
// Create new env with func.closure as parent
// Bind params to args
// Evaluate func.body
Learning milestones:
- Evaluate expressions → Numbers, operators, variables
- Implement functions → Declaration, calling
- Implement closures → Capture environment
- Add objects → Properties, methods, this
Project 9: Implement the Event Loop
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Python, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Concurrency / Async Programming
- Software or Tool: N/A (custom implementation)
- Main Book: N/A (JavaScript.info)
What you’ll build: A browser-like event loop with task queue, microtask queue, and requestAnimationFrame handling.
Why it teaches browser internals: The event loop determines JavaScript’s execution model. Understanding it is crucial for writing performant, non-blocking code.
Core challenges you’ll face:
- Task vs microtask priority → maps to Promise timing
- Microtask drain → maps to all microtasks before next task
- Rendering integration → maps to rAF timing
- setTimeout resolution → maps to minimum 4ms delay
Resources for key challenges:
Key Concepts:
- Task Queue: HTML Spec - Event Loop Processing Model
- Microtasks: ECMA-262 Jobs and Job Queues
- Animation Frames: HTML Spec - Update the Rendering
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 8 (JavaScript Interpreter)
Real world outcome:
// Your event loop simulates browser behavior
const loop = new EventLoop();
loop.queueTask(() => console.log('task 1'));
loop.queueMicrotask(() => console.log('microtask 1'));
loop.queueTask(() => console.log('task 2'));
loop.queueMicrotask(() => console.log('microtask 2'));
loop.setTimeout(() => console.log('timeout'), 0);
loop.queueTask(() => {
console.log('task 3');
loop.queueMicrotask(() => console.log('microtask 3'));
});
loop.run();
// Output (correct order):
// task 1
// microtask 1
// microtask 2
// task 2
// task 3
// microtask 3
// timeout
Implementation Hints:
Event loop structure:
class EventLoop {
taskQueue: Task[] = [];
microtaskQueue: Microtask[] = [];
rafCallbacks: Function[] = [];
run() {
while (true) {
// 1. Run oldest task
if (this.taskQueue.length > 0) {
const task = this.taskQueue.shift();
task.run();
}
// 2. Run ALL microtasks
while (this.microtaskQueue.length > 0) {
const microtask = this.microtaskQueue.shift();
microtask.run();
}
// 3. Render if needed (~16ms has passed)
if (this.shouldRender()) {
// Run rAF callbacks
const callbacks = this.rafCallbacks;
this.rafCallbacks = [];
for (const cb of callbacks) {
cb(performance.now());
}
// Actually render (in real browser)
this.render();
}
// 4. Idle (wait for new tasks)
if (this.taskQueue.length === 0) {
this.waitForTasks();
}
}
}
}
Key insight: Microtasks run after every task, not just between tasks:
task 1 runs
└── spawns microtask A
└── spawns microtask B
microtask A runs
└── spawns microtask C
microtask B runs
microtask C runs ← All microtasks drain before next task!
task 2 runs
Learning milestones:
- Basic task queue → FIFO task execution
- Add microtasks → Priority over tasks
- Microtask drain → Run all before next task
- Add rAF → Frame-aligned callbacks
Project 10: Build an HTTP Client
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript (Node.js)
- Alternative Programming Languages: Python, Go, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Networking / Protocol Implementation
- Software or Tool: Node.js net/tls modules
- Main Book: “TCP/IP Illustrated”
What you’ll build: An HTTP/1.1 client that handles DNS, TCP, TLS, and HTTP protocol parsing.
Why it teaches browser internals: Understanding the network stack reveals how browsers optimize loading (keep-alive, multiplexing, caching).
Core challenges you’ll face:
- DNS resolution → maps to hostname to IP lookup
- TCP connection → maps to three-way handshake
- HTTP parsing → maps to headers, chunked encoding
- Connection reuse → maps to keep-alive, pooling
Resources for key challenges:
- HTTP/1.1 RFC 7230
- The HTTP Request Lifecycle
- “TCP/IP Illustrated” - W. Richard Stevens
Key Concepts:
- HTTP Message Format: RFC 7230 Section 3
- Keep-Alive: RFC 7230 Section 6
- Chunked Transfer: RFC 7230 Section 4
Difficulty: Intermediate Time estimate: 2 weeks Prerequisites: Basic networking knowledge
Real world outcome:
const client = new HttpClient();
// Simple GET request
const response = await client.get('https://example.com/api/users');
console.log(response.status); // 200
console.log(response.headers['content-type']); // 'application/json'
console.log(response.body); // '{"users": [...]}'
// POST with body
const postResponse = await client.post('https://api.example.com/data', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test' })
});
// Connection reuse (keep-alive)
const r1 = await client.get('https://example.com/a'); // New connection
const r2 = await client.get('https://example.com/b'); // Reuses connection!
Implementation Hints:
HTTP request format:
GET /path HTTP/1.1\r\n
Host: example.com\r\n
User-Agent: MyBrowser/1.0\r\n
Accept: */*\r\n
Connection: keep-alive\r\n
\r\n
HTTP response parsing:
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 1234\r\n
\r\n
<html>...</html>
Parsing state machine:
1. Read status line: "HTTP/1.1 200 OK\r\n"
- Parse version, status code, reason
2. Read headers until empty line: "\r\n"
- Parse "Header-Name: value\r\n"
- Build headers object
3. Read body:
- If Content-Length: Read exactly that many bytes
- If Transfer-Encoding: chunked: Read chunks
- Otherwise: Read until connection closes
Learning milestones:
- DNS resolution → Resolve hostname to IP
- TCP connection → Connect and send data
- HTTP parsing → Parse response headers and body
- Keep-alive → Reuse connections
Project 11: Implement Same-Origin Policy
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Any
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Security / Access Control
- Software or Tool: N/A
- Main Book: “Web Application Security” by Andrew Hoffman
What you’ll build: A security layer that enforces same-origin policy for DOM access, cookies, and XHR.
Why it teaches browser internals: SOP is the foundation of web security. Understanding it reveals why certain attacks work and how to prevent them.
Core challenges you’ll face:
- Origin comparison → maps to protocol + host + port
- Cross-origin restrictions → maps to what’s blocked
- Exceptions → maps to images, scripts, CORS
- Storage isolation → maps to localStorage per origin
Resources for key challenges:
- Same-Origin Policy (MDN)
- PortSwigger SOP
- “Web Application Security” - Hoffman
Key Concepts:
- Origin Definition: RFC 6454
- Cross-Origin Restrictions: Fetch Spec
- CORS: Fetch Spec - CORS Protocol
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Projects 2, 8 (DOM, JS Interpreter)
Real world outcome:
// Your mini-browser enforces SOP
// Page from https://a.com tries to access DOM of https://b.com
const pageA = browser.createWindow('https://a.com');
const pageB = browser.createWindow('https://b.com');
// This should FAIL
try {
pageA.frames[0].document.body; // Accessing b.com's DOM
} catch (e) {
console.log(e); // "SecurityError: Cross-origin access denied"
}
// This should FAIL
try {
const xhr = pageA.XMLHttpRequest();
xhr.open('GET', 'https://b.com/api/secret');
xhr.send();
} catch (e) {
console.log(e); // "SecurityError: Cross-origin request blocked"
}
// But images and scripts are allowed (with restrictions)
pageA.document.createElement('img').src = 'https://b.com/image.png'; // OK
Implementation Hints:
Origin structure:
class Origin {
scheme: string; // 'https'
host: string; // 'example.com'
port: number; // 443
equals(other: Origin): boolean {
return this.scheme === other.scheme &&
this.host === other.host &&
this.port === other.port;
}
toString(): string {
return `${this.scheme}://${this.host}:${this.port}`;
}
}
SOP checks:
canAccessDOM(from: Origin, to: Origin): boolean {
return from.equals(to);
}
canMakeRequest(from: Origin, to: Origin, withCredentials: boolean): boolean {
// For simple requests, always allowed (but response may be blocked)
// For non-simple requests, need preflight
// Check CORS headers in response
}
canAccessStorage(origin: Origin, key: string): Storage {
// Each origin gets isolated storage
return storageByOrigin.get(origin.toString());
}
What’s allowed cross-origin:
- Embedding:
<img>,<script>,<link>,<iframe>,<video> - Writes: Links, redirects, form submissions
- Reads: BLOCKED by default (need CORS)
Learning milestones:
- Define origins → Parse and compare
- Block DOM access → Cross-origin frame blocking
- Block XHR/Fetch → Without CORS headers
- Isolate storage → Per-origin localStorage
Project 12: Build a Service Worker
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: JavaScript
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Caching / Offline Support
- Software or Tool: Service Worker API
- Main Book: N/A (web.dev guides)
What you’ll build: A simplified service worker runtime that intercepts fetch requests and manages caches.
Why it teaches browser internals: Service workers revolutionized web apps by enabling offline support and programmatic caching.
Core challenges you’ll face:
- Lifecycle management → maps to install, activate, fetch events
- Request interception → maps to proxy pattern
- Cache management → maps to Cache API
- Update flow → maps to waiting, skipWaiting
Resources for key challenges:
Key Concepts:
- Service Worker Lifecycle: MDN Service Worker API
- Cache API: MDN Cache interface
- Fetch Event: Service Worker Spec
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 9-10 (Event Loop, HTTP)
Real world outcome:
// Your service worker runtime
const runtime = new ServiceWorkerRuntime();
// Register a service worker
runtime.register('/sw.js', { scope: '/' });
// Service worker code (sw.js)
const sw = `
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll(['/index.html', '/styles.css', '/app.js']);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
`;
// First load: Files fetched from network, cached
runtime.fetch('https://example.com/index.html');
// Second load (offline): Served from cache!
runtime.goOffline();
const cached = runtime.fetch('https://example.com/index.html');
console.log(cached); // Returns cached response
Implementation Hints:
Service worker lifecycle:
┌──────────────┐
│ parsed │
└──────┬───────┘
│ install event
▼
┌──────────────┐
│ installing │
└──────┬───────┘
│ installed
▼
┌──────────────┐
│ waiting │ ← Wait for old SW to stop controlling pages
└──────┬───────┘
│ activate event (old SW gone)
▼
┌──────────────┐
│ activated │ ← Now controls all pages in scope
└──────┬───────┘
│
▼
┌──────────────┐
│ running │ ← Handles fetch events
└──────────────┘
Fetch interception:
class ServiceWorkerRuntime {
async fetch(request) {
const sw = this.getControllingWorker(request.url);
if (sw) {
// Create FetchEvent
const event = new FetchEvent(request);
// Dispatch to service worker
sw.dispatchEvent('fetch', event);
// If respondWith was called, use that response
if (event.response) {
return event.response;
}
}
// Fall through to network
return this.networkFetch(request);
}
}
Learning milestones:
- Lifecycle events → Install, activate
- Fetch interception → respondWith
- Cache API → Store and retrieve responses
- Update flow → New versions, skipWaiting
Project 13: Implement GPU Compositing Simulation
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript (with Canvas/WebGL)
- Alternative Programming Languages: Rust, C++
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Graphics / GPU Programming
- Software or Tool: WebGL, Canvas
- Main Book: “WebGL Programming Guide”
What you’ll build: A compositing system that separates elements into layers and composites them on the GPU.
Why it teaches browser internals: GPU compositing is how browsers achieve smooth 60fps animations. Understanding it reveals why transform/opacity are fast.
Core challenges you’ll face:
- Layer creation → maps to when to promote to layer
- Texture upload → maps to rasterizing to GPU memory
- Layer transforms → maps to matrix operations
- Compositing → maps to blending layers together
Resources for key challenges:
Key Concepts:
- Layer Tree: Chromium Design Docs
- Texture Atlases: GPU optimization
- Transform Matrices: Linear algebra basics
Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Project 7 (Paint System), linear algebra
Real world outcome:
// Elements with transforms get their own GPU layer
const html = `
<div style="transform: translateZ(0);">
<p>This is on a GPU layer!</p>
</div>
<div style="opacity: 0.5; transition: opacity 0.3s;">
<p>This too, because opacity!</p>
</div>
`;
const compositor = new Compositor();
const layerTree = compositor.buildLayerTree(doc);
console.log(layerTree);
// {
// root: Layer {
// children: [
// Layer { reason: 'transform', ... },
// Layer { reason: 'opacity', ... }
// ]
// }
// }
// Animate opacity (no repaint, just compositor!)
compositor.animateOpacity(layerTree.children[1], 1.0, duration: 300);
// Composite layers with WebGL
compositor.composite(layerTree);
Implementation Hints:
When browsers create layers:
Reasons for layer promotion:
• 3D transform (translateZ, rotate3d, perspective)
• will-change: transform, opacity
• Accelerated CSS animation
• <video>, <canvas>, <iframe>
• Overlapping a composited layer
Layer tree structure:
class Layer {
bounds: Rect;
transform: Matrix4x4;
opacity: number;
texture: WebGLTexture; // Rasterized content
children: Layer[];
}
Compositing process:
1. Build layer tree from render tree
- Identify elements needing own layer
- Create layer hierarchy
2. Rasterize each layer to texture
- Paint layer contents to bitmap
- Upload bitmap to GPU as texture
3. Composite layers
- For each layer (back to front):
- Apply transform matrix
- Apply opacity
- Draw texture to screen
Transform animation (GPU only):
animateTransform(layer, from, to, duration):
// No repaint needed!
// Just update the transform matrix each frame
requestAnimationFrame((time) => {
const t = (time - start) / duration;
layer.transform = interpolate(from, to, t);
compositor.composite(); // Just recomposite!
});
Learning milestones:
- Create layer tree → Identify layers
- Rasterize to textures → Paint to GPU
- Apply transforms → Matrix operations
- Composite → Blend layers together
Project 14: Build a Simple DevTools
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: JavaScript
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Developer Tools / Debugging
- Software or Tool: Chrome DevTools Protocol
- Main Book: N/A (Chrome DevTools docs)
What you’ll build: Developer tools that inspect DOM, show computed styles, and profile performance.
Why it teaches browser internals: DevTools are a window into browser internals. Building them requires understanding the inspection APIs.
Core challenges you’ll face:
- DOM inspection → maps to tree visualization
- Style computation → maps to showing matched rules
- Performance profiling → maps to measuring paint/layout
- Network inspection → maps to timing breakdown
Resources for key challenges:
- Chrome DevTools Protocol
- How DevTools Works
- Chrome DevTools source code
Key Concepts:
- DOM Protocol: CDP DOM domain
- CSS Protocol: CDP CSS domain
- Performance Protocol: CDP Performance domain
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1-7 (all rendering projects)
Real world outcome:
// Your DevTools inspect a running page
const devtools = new DevTools(browser);
// Elements panel
const domTree = devtools.getDOMTree();
console.log(domTree);
// {
// nodeId: 1,
// nodeName: 'HTML',
// children: [
// { nodeId: 2, nodeName: 'HEAD', ... },
// { nodeId: 3, nodeName: 'BODY', ... }
// ]
// }
// Computed styles
const styles = devtools.getComputedStyle(nodeId: 5);
console.log(styles);
// {
// color: { value: 'rgb(0, 0, 0)', from: 'p { color: black }' },
// fontSize: { value: '16px', from: 'user-agent stylesheet' }
// }
// Performance timeline
devtools.startProfiling();
// ... user interacts with page ...
const timeline = devtools.stopProfiling();
console.log(timeline);
// [
// { type: 'script', duration: 50, function: 'handleClick' },
// { type: 'style', duration: 2 },
// { type: 'layout', duration: 15 },
// { type: 'paint', duration: 8 }
// ]
Implementation Hints:
DevTools architecture:
┌─────────────────┐ Protocol ┌─────────────────┐
│ DevTools │ ◄───────────────► │ Browser │
│ (Frontend) │ (JSON-RPC) │ (Backend) │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Elements UI │ │ DOM Agent │
│ Styles Panel │ │ CSS Agent │
│ Network Panel │ │ Network Agent │
│ Performance │ │ Profiler │
└─────────────────┘ └─────────────────┘
DOM inspection API:
getDocument() → { root: Node }
querySelector(nodeId, selector) → nodeId
getOuterHTML(nodeId) → string
setOuterHTML(nodeId, html) → void
getComputedStyle(nodeId) → { property: value }[]
getMatchedStyles(nodeId) → { rule, selectors, declarations }[]
Performance API:
startProfiling()
stopProfiling() → Timeline
Timeline = {
events: [
{ type: 'script', startTime, endTime, callStack },
{ type: 'style', startTime, endTime, elementsAffected },
{ type: 'layout', startTime, endTime, rootBounds },
{ type: 'paint', startTime, endTime, clipRect }
]
}
Learning milestones:
- DOM inspection → Tree view, selection
- Style inspection → Computed and matched rules
- Network timeline → Request waterfall
- Performance profiler → Script/layout/paint timing
Project 15: Build a Toy Web Browser
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: Rust
- Alternative Programming Languages: C++, TypeScript
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 5: Master
- Knowledge Area: Complete Browser / System Integration
- Software or Tool: GTK/Qt for UI, Servo for inspiration
- Main Book: “browser.engineering” (online book)
What you’ll build: A complete (minimal) web browser that can render simple web pages with HTML, CSS, and basic JavaScript.
Why it teaches browser internals: This capstone project integrates everything you’ve learned into a working browser.
Core challenges you’ll face:
- Integration → maps to connecting all components
- Window management → maps to tabs, navigation
- Resource loading → maps to parallel fetching
- User interaction → maps to scrolling, clicking
Resources for key challenges:
- browser.engineering - Step-by-step book
- Building a Browser from Scratch
- Servo (GitHub) - Reference implementation
Key Concepts:
- Integration: All previous concepts
- Window Management: Platform-specific UI
- Navigation: History, back/forward
Difficulty: Master Time estimate: 3-6 months Prerequisites: All previous projects
Real world outcome:
$ ./my-browser https://example.com
# A window opens showing the rendered page!
# Features:
# - Address bar with navigation
# - Back/forward buttons
# - Page title in tab
# - Clickable links
# - Basic CSS layout
# - Simple JavaScript execution
# The browser can render:
# - Static HTML pages
# - Pages with external CSS
# - Basic interactive pages
Implementation Hints:
Browser architecture:
┌─────────────────────────────────────────────────────────────┐
│ BROWSER SHELL │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Address Bar │ Back │ Forward │ Reload │ Menu │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ RENDER AREA │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ RENDERED CONTENT │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Main loop:
main():
window = createWindow()
while window.isOpen():
// Handle UI events
for event in window.pollEvents():
if event.type == 'addressBarEnter':
navigate(event.url)
elif event.type == 'click':
handleClick(event.x, event.y)
elif event.type == 'scroll':
handleScroll(event.delta)
// Run event loop tick (timers, promises)
eventLoop.tick()
// Render if needed
if needsRender:
layout()
paint()
composite()
window.present()
Navigation flow:
navigate(url):
1. Resolve URL (relative → absolute)
2. Fetch main resource (HTML)
3. Parse HTML → DOM
4. Fetch subresources (CSS, JS, images)
5. Parse CSS → CSSOM
6. Execute JavaScript
7. Compute styles
8. Layout
9. Paint
10. Display
Learning milestones:
- Window with address bar → Basic UI
- Load and render HTML → Static pages
- Support CSS → Styled pages
- Basic interactivity → Links, forms
- JavaScript support → Dynamic pages
Project 16: Implement WebAssembly Runtime (Bonus)
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: Rust
- Alternative Programming Languages: C++
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 5: Master
- Knowledge Area: Virtual Machines / Low-Level Execution
- Software or Tool: WASM spec
- Main Book: “WebAssembly: The Definitive Guide”
What you’ll build: A WebAssembly interpreter that can execute WASM modules.
Why it teaches browser internals: WebAssembly is how browsers run near-native code. Understanding it reveals the future of web performance.
Core challenges you’ll face:
- Binary format parsing → maps to WASM module structure
- Stack machine execution → maps to operand stack
- Memory management → maps to linear memory
- Host function binding → maps to JavaScript interop
Resources for key challenges:
- WebAssembly Spec
- Writing a WASM Interpreter
- “WebAssembly: The Definitive Guide” - Brian Sletten
Key Concepts:
- Module Structure: WASM Binary Format
- Stack Machine: WASM Execution
- Linear Memory: WASM Memory Model
Difficulty: Master Time estimate: 2-3 months Prerequisites: Understanding of VMs, binary formats
Real world outcome:
// Load and execute WASM
const wasm = new WasmRuntime();
// Load a module that exports a factorial function
const module = await wasm.loadModule('factorial.wasm');
// Call the exported function
const result = module.exports.factorial(5);
console.log(result); // 120
// With imported functions
const imports = {
env: {
log: (n) => console.log('WASM says:', n)
}
};
const moduleWithImports = await wasm.loadModule('example.wasm', imports);
moduleWithImports.exports.run(); // "WASM says: 42"
Project 17: Build a Performance Profiler
- File: LEARN_BROWSER_INTERNALS.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 3: Advanced
- Knowledge Area: Performance / Profiling
- Software or Tool: Performance APIs
- Main Book: N/A (web.dev Performance guides)
What you’ll build: A performance profiler that measures and visualizes rendering performance.
Why it teaches browser internals: Understanding what causes jank requires measuring every step of the rendering pipeline.
Core challenges you’ll face:
- Instrumentation → maps to adding timing points
- Frame timing → maps to 60fps = 16.67ms budget
- Long task detection → maps to > 50ms blocks input
- Visualization → maps to flame charts, timelines
Resources for key challenges:
Key Concepts:
- Frame Budget: 16.67ms for 60fps
- Long Tasks: Tasks > 50ms
- Core Web Vitals: LCP, FID, CLS
Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Projects 6-9 (Layout, Paint, Event Loop)
Real world outcome:
const profiler = new PerformanceProfiler(browser);
profiler.start();
// User interacts with page...
const report = profiler.stop();
console.log(report);
// {
// frames: [
// { timestamp: 0, duration: 12, breakdown: { script: 5, style: 2, layout: 3, paint: 2 } },
// { timestamp: 16, duration: 45, breakdown: { script: 40, style: 1, layout: 2, paint: 2 } }, // JANK!
// { timestamp: 62, duration: 14, breakdown: { script: 8, style: 2, layout: 2, paint: 2 } },
// ],
// longTasks: [
// { startTime: 16, duration: 45, attribution: { function: 'handleScroll', file: 'app.js', line: 234 } }
// ],
// fps: {
// average: 45,
// min: 22,
// max: 60,
// jankFrames: 3
// }
// }
// Generate flame chart
profiler.generateFlameChart(report); // Visual output
Implementation Hints:
Instrumentation points:
Frame lifecycle:
┌─────────────────────────────────────────────────────────┐
│ Frame Start │
│ ├── Input events ← Measure │
│ ├── requestAnimationFrame ← Measure │
│ ├── JavaScript execution ← Measure │
│ ├── Style calculation ← Measure │
│ ├── Layout ← Measure │
│ ├── Paint ← Measure │
│ └── Composite ← Measure │
│ Frame End │
│ │
│ Budget: 16.67ms total │
└─────────────────────────────────────────────────────────┘
Learning milestones:
- Instrument rendering → Add timing points
- Detect jank → Frames > 16.67ms
- Detect long tasks → Script > 50ms
- Visualize → Flame charts, timelines
Project Comparison Table
| # | Project | Difficulty | Time | Key Skill | Fun |
|---|---|---|---|---|---|
| 1 | HTML Tokenizer | ⭐⭐ | 1-2 weeks | State Machine | ⭐⭐⭐ |
| 2 | DOM Tree | ⭐⭐ | 1-2 weeks | Data Structures | ⭐⭐⭐ |
| 3 | CSS Parser | ⭐⭐ | 1-2 weeks | Parsing | ⭐⭐⭐ |
| 4 | Selector Engine | ⭐⭐⭐ | 2 weeks | Algorithm Design | ⭐⭐⭐⭐ |
| 5 | CSS Cascade | ⭐⭐ | 1-2 weeks | Algorithm | ⭐⭐⭐ |
| 6 | Layout Engine | ⭐⭐⭐⭐ | 3-4 weeks | Geometry | ⭐⭐⭐⭐ |
| 7 | Paint System | ⭐⭐⭐ | 2-3 weeks | Graphics | ⭐⭐⭐⭐⭐ |
| 8 | JS Interpreter | ⭐⭐⭐⭐ | 4-6 weeks | Languages | ⭐⭐⭐⭐⭐ |
| 9 | Event Loop | ⭐⭐⭐ | 1-2 weeks | Concurrency | ⭐⭐⭐⭐ |
| 10 | HTTP Client | ⭐⭐ | 2 weeks | Networking | ⭐⭐⭐ |
| 11 | Same-Origin Policy | ⭐⭐ | 1 week | Security | ⭐⭐⭐ |
| 12 | Service Worker | ⭐⭐⭐ | 2 weeks | Caching | ⭐⭐⭐⭐ |
| 13 | GPU Compositing | ⭐⭐⭐⭐ | 3-4 weeks | GPU | ⭐⭐⭐⭐⭐ |
| 14 | DevTools | ⭐⭐⭐ | 2-3 weeks | Tools | ⭐⭐⭐⭐ |
| 15 | Toy Browser | ⭐⭐⭐⭐⭐ | 3-6 months | Integration | ⭐⭐⭐⭐⭐ |
| 16 | WASM Runtime | ⭐⭐⭐⭐⭐ | 2-3 months | VMs | ⭐⭐⭐⭐⭐ |
| 17 | Profiler | ⭐⭐⭐ | 2 weeks | Performance | ⭐⭐⭐⭐ |
Recommended Learning Path
Phase 1: Foundations (6-8 weeks)
Build the core parsing and data structures:
- Project 1: HTML Tokenizer - Understand HTML parsing
- Project 2: DOM Tree - Build the core data structure
- Project 3: CSS Parser - Parse stylesheets
- Project 4: Selector Engine - Match CSS to elements
- Project 5: CSS Cascade - Compute final styles
Phase 2: Rendering (6-8 weeks)
Build the visual pipeline:
- Project 6: Layout Engine - Calculate positions/sizes
- Project 7: Paint System - Draw to canvas
- Project 13: GPU Compositing - Smooth animations
Phase 3: JavaScript (4-6 weeks)
Add dynamic capabilities:
- Project 8: JS Interpreter - Execute code
- Project 9: Event Loop - Async execution model
Phase 4: Networking & Security (3-4 weeks)
Handle resources and security:
- Project 10: HTTP Client - Fetch resources
- Project 11: Same-Origin Policy - Security model
- Project 12: Service Worker - Offline support
Phase 5: Integration & Tools (2-4 months)
Put it all together:
- Project 14: DevTools - Inspect your work
- Project 17: Profiler - Measure performance
- Project 15: Toy Browser - The capstone!
Bonus:
- Project 16: WASM Runtime - Advanced topic
Summary
| # | Project | Main Language |
|---|---|---|
| 1 | HTML Tokenizer | TypeScript |
| 2 | DOM Tree | TypeScript |
| 3 | CSS Parser | TypeScript |
| 4 | CSS Selector Engine | TypeScript |
| 5 | CSS Cascade | TypeScript |
| 6 | Layout Engine (Box Model) | TypeScript |
| 7 | Paint System | TypeScript |
| 8 | JavaScript Interpreter | TypeScript |
| 9 | Event Loop | TypeScript |
| 10 | HTTP Client | TypeScript |
| 11 | Same-Origin Policy | TypeScript |
| 12 | Service Worker Runtime | TypeScript |
| 13 | GPU Compositing Simulation | TypeScript |
| 14 | DevTools | TypeScript |
| 15 | Toy Web Browser | Rust |
| 16 | WebAssembly Runtime | Rust |
| 17 | Performance Profiler | TypeScript |
Resources
Essential Reading
- browser.engineering - Step-by-step browser building
- How Browsers Work (web.dev) - Classic article
- Chromium Design Docs - Official architecture
- WHATWG Standards - HTML, DOM, Fetch specs
Tutorials & Articles
- Let’s Build a Browser Engine - Matt Brubeck’s Rust tutorial
- Inside Chrome (4-part series) - Mariko Kosaka
- Rendering Pipeline (web.dev)
- V8 Blog - JavaScript engine internals
Books
- “Crafting Interpreters” by Robert Nystrom - For JS interpreter
- “Computer Graphics from Scratch” by Gabriel Gambetta - For rendering
- “TCP/IP Illustrated” by W. Richard Stevens - For networking
- “Web Application Security” by Andrew Hoffman - For security
Reference Implementations
- Servo (GitHub) - Mozilla’s experimental browser engine
- Chromium Source - Chrome’s source code
- WebKit - Safari’s engine
Specifications
- HTML Standard - WHATWG
- CSS Specifications - W3C
- ECMAScript - JavaScript standard
- Fetch Standard - Network requests
Total Estimated Time: 12-18 months of dedicated study
After completion: You’ll understand every step from typing a URL to seeing pixels on screen. This knowledge is invaluable for web performance optimization, framework design, security research, and potentially contributing to browser development itself.