The Map
A browser is a program that takes untrusted text from strangers, turns it into pixels and behaviour on your machine, and does so without letting the stranger touch anything else. This part is the map: the processes, the threads inside a renderer, the pipeline from URL to frame, the two budgets every page spends, and how the rest of the course is laid out against them.
What a browser actually is
"It's a program that shows web pages. What else is there to know?"
A browser is a program that takes untrusted text from strangers, turns it into pixels and behaviour on your machine, and does so without letting the stranger touch anything else. Every design decision in it follows from those three clauses.
- Turn text into pixels. HTML, CSS and images become geometry and paint. This is the rendering pipeline, and it has to finish a frame in 16.7 ms to feel smooth. Parts 2 to 5.
- Run a stranger's program. JavaScript from any origin executes on your CPU with access to the page. That needs an engine, a scheduler and a memory model. Parts 6 and 7.
- Contain it. The page must not read your files, your other tabs or your bank's cookies. That is the process model, the sandbox, the same-origin policy and everything in Part 9.
a browser is not one program. in chrome today it is: 1 browser process (UI, navigation, privilege) N renderer processes (one per site, sandboxed) 1 GPU process 1 network process K utility processes (audio, storage, printing, ...) the thing you call "the browser" is the coordinator. the thing that runs your page is one of the N.
The multi-process model
Early browsers were one process. One bad page took the whole window with it, and one compromised page could read every other page's memory. Chrome shipped in 2008 with a process per tab, and every major browser has converged on a version of the same split since.
- Browser process. Owns the window, the tab strip, the address bar, bookmarks, downloads, and navigation. It is the only process that talks to the OS with full privilege. It never executes page JavaScript, which is the point.
- Renderer process. One per site (Chrome) or per group of sites under memory pressure. Runs Blink and V8: parsing, style, layout, paint recording, and all your JavaScript. Sandboxed: it cannot open files, sockets or the GPU directly.
- GPU process. Owns the graphics context. Receives compositing layers from renderers and draws the frame. Isolated because GPU drivers crash and because a renderer with GPU access is a renderer that can read other renderers' frames.
- Network process. Owns sockets, DNS, TLS, the HTTP cache and the cookie jar. Renderers request resources over IPC and receive bytes back. A renderer never sees a raw socket.
- Utility processes. Audio, storage, printing, PDF, the speech service. Short-lived or long-lived, each sandboxed to one job, each disposable.
Site isolation, and why a tab is not a process
"Is each tab its own process?"
Not quite, and the difference matters for security. The unit of isolation is the site, which is the registrable domain plus scheme: https://bank.example. Two tabs on the same site may share a renderer. One tab containing an iframe from a different site puts that iframe in a different renderer from the page around it.
a tab on bank.example with an embedded ads.example iframe: renderer A runs bank.example's document renderer B runs ads.example's iframe the browser process stitches them into one tab the ad's JavaScript is in a different address space from your balance. it cannot read bank.example's memory even with a renderer exploit.
- Spectre (2018). Speculative-execution side channels let JavaScript read any memory in its own process. If the ad and the bank share a process, timing attacks read the bank. The only fix that holds is to not share.
- Renderer exploits. Blink and V8 have bugs. A compromised renderer that only holds one site's data can only leak that site.
- Cross-origin read blocking. The network process refuses to deliver certain cross-site responses to a renderer that should not hold them, so even a compromised renderer cannot fetch the bank's JSON into its memory.
Inside a renderer: the threads
A renderer process is where the course spends most of its time, so it is worth knowing its threads by name before anything else.
- Main thread. HTML parsing, CSS parsing and resolution, layout, paint recording, JavaScript execution, event dispatch, and the DOM itself. One thread, one task queue. The phrase "blocking the main thread" means blocking all of that.
- Compositor thread. Takes the layer tree and paint records the main thread produced and builds frames from them. Handles scrolling, and animations of transform and opacity, without asking the main thread.
- Raster threads. A pool that paints tiles of each layer into bitmaps. Parallel, so a large layer is painted by several threads at once.
- Worker threads. Each Web Worker gets a thread with its own event loop and no DOM access. Communication is by message only.
- IO and housekeeping threads. IPC, file access via the browser process, timers. Invisible unless you profile.
A request's life at 10,000 feet
Here is the whole thing once, fast, so the rest of the course has a map to pin to. Each stage below is a part of its own.
- Navigation. The browser process parses what you typed, decides it is a URL, checks the HTTP cache, picks or spawns a renderer for the site, and asks the network process to fetch.
- Network. DNS lookup, TCP handshake, TLS handshake, HTTP request, first byte. On HTTP/3 the handshakes collapse into fewer round trips. The cache and resource hints can short-circuit any of this.
- Parse. Bytes become characters, characters become tokens, tokens become DOM nodes. A preload scanner reads ahead for scripts, styles and images so their fetches start before the parser reaches them.
- Style. CSS becomes the CSSOM. For every DOM element, matching selectors are found and the cascade produces a computed value for every property.
- Layout. Geometry. Every box gets position and size through the formatting context it lives in.
- Paint. A display list of drawing commands in paint order, split into compositing layers where the engine decides a layer pays for itself.
- Composite. Raster threads paint tiles, the compositor assembles layers, the GPU process presents the frame. First Contentful Paint is the first time this reaches the screen with content.
- Loop. JavaScript runs, input arrives, timers fire. Whatever they change reruns the pipeline from that stage forward, ideally inside the next 16.7 ms frame.
The critical rendering path, named
"Why does a 40 KB stylesheet delay the whole page when a 2 MB image doesn't?"
Because the stylesheet is on the critical rendering path and the image is not. The path is the minimum set of work between the first byte and the first pixel, and it is defined by what blocks what.
the blocking rules, which are the whole story: HTML parsing blocks on a synchronous <script> (it might document.write) script execution blocks on all CSS above it (it might read a computed style) first render blocks on all CSS in <head> (unstyled flash is worse than waiting) nothing blocks on images, async scripts, fonts (mostly) so: CSS blocks render, sync JS blocks parsing, and JS waits for CSS. a sync script after a stylesheet pays for both.
- Fewer bytes on it. Critical CSS inline, the rest loaded with
mediaor deferred. - Fewer round trips on it. Preconnect to the origins it needs; preload the font or stylesheet the parser will not discover early.
- Nothing synchronous in the head that does not have to be.
deferfor scripts that touch the DOM;asyncfor scripts that do not care about order. - Server-render the shell so the first paint does not wait for JavaScript at all.
Where the time goes: two budgets
There are two clocks in a browser, and conflating them causes most bad performance advice.
a realistic load budget on a mid-range Android over 4G: DNS + TCP + TLS ~300 ms TTFB ~400 ms HTML + critical CSS ~200 ms parse + style + layout ~150 ms JS parse + execute ~900 ms ← the usual villain images for LCP ~500 ms a frame budget, where it all has to fit: input handling ~1 ms JS ≤ 8 ms style + layout ~3 ms paint + composite ~3 ms slack ~1 ms a 50 ms click handler is three dropped frames. the user calls that lag.
Three engines, and where they differ
Three rendering engines matter: Blink (Chrome, Edge, Opera, Brave, Samsung Internet, every Android WebView), WebKit (Safari, and every browser on iOS until recently), and Gecko (Firefox). They implement the same specifications and the same pipeline shape, with real differences underneath.
| Concern | Blink | WebKit | Gecko |
|---|---|---|---|
| JS engine | V8 | JavaScriptCore | SpiderMonkey |
| Layout engine | LayoutNG (rewritten 2019 to 2022) | Legacy with incremental rewrites | Layout rewritten in parts; Stylo for style (Rust, parallel) |
| Style resolution | Single-threaded, bloom-filter accelerated | Single-threaded | Parallel across threads (Stylo) |
| Compositing | cc, GPU process | Core Animation on Apple platforms | WebRender (GPU-driven, Rust) |
| Process model | Site isolation by default | Per-tab, site isolation partial | Fission: site isolation |
| Where it bites you | Memory | iOS forced it on everyone until 2024; feature lag | Smallest share, so the least-tested path |
How to read this course
- The mechanism first. What the engine does, drawn step by step. Press play on the figure or step through it; every figure expands to full screen.
- Then the cost. What is cheap, what is expensive, and why, with numbers where numbers exist.
- Then the lens. Which DevTools panel shows this stage and what to look for. The DevTools course has the companion app where you cause each problem on purpose.
- Then the consequence. What you should do differently in code because of it, with the trick named as a consequence rather than a rule.
- Parts 1 to 5 are the load pipeline in order: network, parse, style, layout, paint and composite.
- Parts 6 and 7 are the runtime: the event loop and JavaScript in the page.
- Parts 8 to 11 are the rest of the platform: storage, security, navigation, input and media.
- Parts 12 and 13 are measurement.
- Part 14 builds a browser. Everything before it is the specification for what you build.
The lab, and the habit
This course is paired with the DevTools course and its companion app. The app is a small React application where every route triggers one specific pathology: a layout thrash, a long task, a render-blocking stylesheet, a memory leak, a hydration mismatch, a blocked request. The DevTools course walks each panel; this course points at the app whenever a mechanism is easier to see than to read.
- Look before guessing. Open the Performance panel before changing a line.
- Name the stage. Network, parse, style, layout, paint, composite, script. The fix depends entirely on which one.
- Find the thread. Main thread or compositor. Scroll jank and click lag are different bugs.
- Change one thing, measure again. The profile is the proof; the before-and-after screenshot is the pull request description.
modules/devtools/lab/. Chapters in this course that say "go to the lab" name the exact route and what to look for. Until that course is built, the chapters still stand on their own.