Processes and threads
The browser, renderer, GPU, and network processes, and why almost everything you write runs on one main thread. Measured in Chrome: processes, threads, a blocked page and a worker.
- 01Name the processesSay what the browser process, the renderers, the GPU process and the network service each do, and why each site gets its own renderer.
- 02Know the main thread's jobsList the jobs a page's main thread does, one at a time, and explain why one long task holds all of them up.
- 03Move work off itPick compositor-only animations, Web Workers and OffscreenCanvas for work that doesn't need the main thread.
One browser, many processes
Asha opens the Leaf and Kettle page in Chrome on her laptop. It's one tab. The page shows the shop's name, a box of reviews, and a small map.
The reviews and the map are iframes. An iframe is a page shown inside another page. The reviews come from the shop's own website. The map comes from a different website. In the course's copy of the page, a second address stands in for the map's website.
To Asha, it's one page. Inside Chrome, it's many separate jobs. Something has to fetch the files, run the shop's JavaScript, draw the pixels, and answer her clicks. So who does all that work? Is Chrome one program, doing one job after another? And if one part gets stuck, does everything get stuck?
The course loaded this page in Chrome 156 on a Mac, and asked Chrome where each part of it ran:
// Where Chrome ran the Leaf and Kettle page and its iframes.// Each number is a process ID: the number the system gave that process.const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120 }; const renderers = new Set(Object.values(ranIn));const iframes = Object.keys(ranIn).length - 1;console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);- Lines 1 and 2 are comments. A process is one running program. The operating system gives each process a number, its ID.
- Line 3 holds what Chrome recorded. The page and the reviews ran in process 78121. The map ran in process 78120.
- Line 5 puts the IDs in a
Set. A Set keeps each value only once, so this one holds two IDs. - Line 6 counts the iframes: three parts, minus the page itself, makes two.
- Line 7 prints
The page and its 2 iframes ran in 2 renderer processes.
Here is Asha's tab, with a line from each part of it to the process it ran in:
So one tab used two programs. A process that shows web pages is called a renderer, because to render a page is to draw it. The page and its reviews shared one renderer. The map, from another website, got a renderer of its own.
And those two weren't all. Chrome also ran a process for its own window, one for the screen, and one for the network.
Inside each process, the work is split again, into threads. A thread lives inside a process and runs part of its program. It does one thing at a time. Several threads in one process can work at the same time, and they share that process's memory. Here is the whole picture:
Picture a big building with several restaurants inside. Each restaurant has its own kitchen, with its own cooks and its own recipes. One night, a pan catches fire in one kitchen. That kitchen closes, but the other restaurants keep serving dinner. And no cook can walk into another kitchen and read its recipes.
- In real life: The building
- In the browser: Chrome
- In real life: A kitchen, with its own room and its own recipes
- In the browser: A process, with its own memory
- In real life: One kitchen for each restaurant
- In the browser: One renderer process for each site
- In real life: The cooks in a kitchen
- In the browser: The threads in a process
- In real life: A fire that closes one kitchen
- In the browser: A crash that stops one renderer, and only that one
Chrome is many programs, called processes. On a computer, each site gets a renderer process of its own, so one site can't crash or read another. Inside a renderer, one main thread runs your JavaScript and works out styles, layout and paint, one job at a time. Other threads help. They put frames together, turn the page into pixels, and compile scripts. Keep the main thread free, and the page answers the person quickly.
The rest of the lesson takes this picture apart, one piece at a time:
- the processes, and why each site gets a renderer of its own;
- the main thread, and the many jobs it does one at a time;
- the compositor thread, which keeps the screen moving while the main thread is busy;
- the work you can move off the main thread.
The JavaScript lesson Browser architecture and the system design lesson Inside the browser tell the same story in brief. This lesson measures it in Chrome.
The multi-process browser
Chrome starts several processes before it shows any page at all. The course started Chrome with no page open, and asked it what it was running:
// What Chrome was running before any page was open.const before = ["browser", "GPU", "network service", "storage service", "notifications"]; const renderers = before.filter((type) => type === "renderer");console.log(`Before any page, Chrome ran ${before.length} processes.`);console.log(`${renderers.length} of them were renderers.`);- Line 1 is a comment.
- Line 2 holds the five processes Chrome listed, by type.
- Line 4 keeps only the renderers.
- Lines 5 and 6 print
Before any page, Chrome ran 5 processes.and0 of them were renderers.
No page, no renderer. Renderers came only once there was a page to show. Each of the other processes does one job, for the whole browser:
- The browser process runs everything around the page: the tabs, the address bar, the back and forward buttons, and the bookmarks. It starts the other processes and coordinates them.
- The GPU process works with the GPU, the computer's graphics chip, to put pictures on the screen. It runs on its own because the GPU takes drawing requests from many apps and shows them all on one screen.
- The network service fetches files from the internet, for every tab.
- Two smaller services finish the list: one for storage, and one for the Mac's notifications.
Back to the building of restaurants. At the entrance there's one front desk. It opens the doors, answers the phones, and takes deliveries for every restaurant. When a guest walks in, the desk sends them to the right restaurant. The kitchens never go out to the street themselves.
- In real life: The front desk
- In the browser: The browser process: the tabs, the address bar and the buttons
- In real life: Sending each guest to the right restaurant
- In the browser: Passing a click to the renderer of the page it landed on
- In real life: Answering the phones and taking deliveries
- In the browser: The network service, which fetches files for every tab
- In real life: Kitchens that never go out to the street
- In the browser: Renderers, which Chrome keeps away from your files
Here is that desk at work. When Asha clicks, the browser process gets the click first. It only knows where on the screen the click landed. So it sends the click on to the renderer showing that page, and the renderer runs the page's click handler. Mariko Kosaka follows a touch along this same path in Inside look at modern web browser, her series on Chrome's developer site.
One renderer for each site
Now back to the question from the start. Why did the map get its own renderer, when the reviews shared the page's?
Because Chrome gives each site a renderer of its own. A site is a scheme, such as https, plus a main domain name. https://example.com is one site. So www.example.com and shop.example.com belong to the same site. This rule is called Site Isolation, and it covers iframes too.
The reviews came from the shop's own site, so they shared the page's renderer. The map came from another site, so it got its own. In the course's copy, the page came from 127.0.0.1 and the map from localhost. Both names lead to the course's own computer. But they're different names, so Chrome treated them as different sites. That's the separate kitchens from the start: one for each restaurant.
The first picture showed the processes Chrome was running once the page had loaded. Chrome listed four renderers in all at that moment, and the page used two of them. It also listed a tracing service, which ran because the course was recording a trace.
Why does Chrome go to all this trouble? There are three reasons, and one cost:
- A crash stays in one place. If a renderer crashes or hangs, only its pages stop. Kosaka puts the other case plainly: "If all tabs are running on one process, when one tab becomes unresponsive, all the tabs are unresponsive."
- A sandbox. The operating system lets Chrome limit what a process may do. Renderers run whatever code a site sends, so Chrome keeps them away from your files.
- Sites can't read each other. One process can't see another's memory. With each site in its own renderer, a harmful site can't reach another site's data in memory. Site Isolation has been on by default on desktop since Chrome 67.
- The cost is memory. Each process keeps its own copy of shared parts, such as V8, Chrome's JavaScript engine. So Chrome limits how many processes it starts, based on your device's memory and CPU. On Android, only some sites get a process of their own, such as sites you log in to.
Why it matters: on a computer, a site that crashes or freezes in another tab leaves Asha's page working. But inside her page's renderer, most of the work still depends on one thread. That thread is next.
The main thread's many jobs
Step inside the shop's renderer. Most of the work there happens on one thread, the main thread. In Kosaka's words, "the main thread handles most of the code you send to the user."
In each kitchen, one head cook makes every dish. She reads the order, cooks it, plates it, and only then starts the next one. She never makes two dishes at once. So if one dish takes ten minutes, every order behind it waits ten minutes.
- In real life: The head cook
- In the browser: The main thread
- In real life: One dish
- In the browser: One task, like running a script or working out the layout
- In real life: Never two dishes at once
- In the browser: One task at a time
- In real life: Orders waiting behind a slow dish
- In the browser: A click handler waiting behind a long task
Here are the main thread's jobs:
- It reads the HTML and builds the DOM, the tree of the page's elements.
- It reads the CSS and works out each element's styles.
- It runs your JavaScript: scripts, timers, promise callbacks, and event handlers such as a click handler.
- It works out the layout: where each element goes, and how big it is.
- It works out the paint: a list of what to draw, and in what order.
- It does part of the garbage collection: clearing out memory the page no longer uses.
Each piece of work the main thread picks up is a task. It does one task at a time, like the head cook with one dish.
The Leaf and Kettle page is tiny, so its main thread had little to do. To watch a main thread at work, the course loaded a real site: its own home page, completefrontend.com. It loaded the page five times and kept the middle load. Then it added up what the main thread did, until one second after the page finished loading:
// The course's home page: what its main thread did, in ms.const work = { parsing: 5.8, script: 131, layout: 78.6, paint: 33.3, other: 57.9 }; const busy = Object.values(work).reduce((sum, ms) => sum + ms, 0);console.log(`The main thread was busy for ${busy.toFixed(1)} ms.`);for (const [job, ms] of Object.entries(work)) { console.log(`${job}: ${Math.round((ms / busy) * 100)}% of that time`);}- Line 1 is a comment.
- Line 2 holds the recorded time for each kind of job, in milliseconds (ms). A millisecond is a thousandth of a second.
- Line 4 adds them up. Line 5 prints
The main thread was busy for 306.6 ms. - Lines 6 to 8 print each job's share of that time, one per line:
parsing: 2% of that time,script: 43% of that timeandlayout: 26% of that time. Then comepaint: 11% of that timeandother: 19% of that time.
The five names on line 2 group the trace's work like this:
parsingis reading the HTML and the CSS.scriptis running JavaScript. It includes compiling scripts and clearing out unused memory.layoutincludes working out styles.paintincludes decoding images and handing the result to the compositor.otheris the rest of the time inside the main thread's tasks.
So where did the main thread's time go? Running JavaScript took the biggest share, 43%. Styles and layout came next, at 26%. On this page, scripts are the first place to look for a less busy main thread.
Why it matters: all of these jobs share one thread. While a script runs, nothing else on that list can happen. No layout, no paint, and no click handler. One long task holds up everything behind it, like the slow dish in the kitchen. The next lesson looks at that line of waiting tasks in detail.
The compositor and raster threads
So what happens when the main thread is stuck on a long script? Does the whole page freeze? Not all of it. Part of the screen can keep moving, thanks to a second thread in the renderer: the compositor thread.
To see how, follow the page after the main thread has worked out the paint. Kosaka describes these steps:
- The main thread splits the page into layers: parts of the page that are drawn separately, like sheets of tracing paper stacked on top of each other.
- It commits the layers to the compositor thread. To commit is to hand them over.
- The compositor thread cuts each layer into tiles, small squares, and sends the tiles to raster threads.
- Raster threads turn each tile into pixels. That's called rasterizing. The pixels go into the GPU's memory.
- The compositor thread puts the tiles together into a frame: one full picture of the page. The frame goes on to the GPU process, which puts it on the screen.
The screen shows a new frame many times a second, like the pages of a flip book. Here is the hand-off in the home page's load, with how long each thread was busy:
Back in the kitchen, a waiter carries the plates out to the tables. When the head cook is stuck on a hard dish, the waiter keeps working. He can carry out plates that are already made, and slide them along the table. But if a guest wants a dish made differently, that waits for the cook.
- In real life: The waiter
- In the browser: The compositor thread
- In real life: Plates that are already made
- In the browser: Layers the main thread has already handed over
- In real life: Carrying a plate out, or sliding it along the table
- In the browser: A new frame: scrolling, or moving a layer with
transform - In real life: A dish the guest wants made differently
- In the browser: A change that needs layout, like moving a box with
left
The layers are already turned into pixels. So to scroll, the compositor thread shows a different part of them. To move a layer, it puts the layer in a new place in the next frame. Neither needs the main thread. In Kosaka's words, the compositor thread "does not need to wait on style calculation or JavaScript execution."
Only some changes work this way. Paul Lewis, writing on web.dev, names the CSS properties the compositor can change on its own. In his words: "Today there are only two properties for which that is true - transforms and opacity." A change to the layout, like a new left, needs the main thread for every frame.
The course tested this with a page of two boxes. Both slide to the right, the same distance at the same speed. The green box moves with transform. The orange box moves with left. One second in, a script keeps the main thread busy for one more second. Here are five frames Chrome drew during that busy second:

At 1,030 mstransform 150 px, left 147 px

At 1,280 mstransform 187 px, left 147 px

At 1,513 mstransform 222 px, left 147 px

At 1,763 mstransform 260 px, left 147 px

At 1,997 mstransform 295 px, left 147 px
The course read where each box was from the frames' pixels. Here are the first and the last frame of the busy second:
// Two boxes sliding right while a script kept the main thread busy.// When Chrome drew each frame (ms), and where each box was (px).const firstFrame = { ms: 1030, transform: 150, left: 147 };const lastFrame = { ms: 1996.5, transform: 295, left: 147 }; console.log(`The transform box moved ${lastFrame.transform - firstFrame.transform} px.`);console.log(`The left box moved ${lastFrame.left - firstFrame.left} px.`);- Lines 1 and 2 are comments. Each time counts from when the page started loading.
- Line 3 is the first frame, at 1030 ms. Line 4 is the last, at 1996.5 ms, almost a second later.
- Line 6 prints
The transform box moved 145 px. - Line 7 prints
The left box moved 0 px.
The transform box kept sliding through the busy second. The compositor thread moved it, without the main thread. The left box froze until the script ended, because each of its frames needed layout. And Chrome never stopped drawing. It drew just as many frames in the busy second as in the calm second before it.
Scrolling works the same way. Where the page has no input listeners, the compositor thread scrolls it on its own. Where it has some, the compositor sends the input to the main thread first, and waits. A listener marked passive tells it not to wait. The listener still runs, but the scroll goes ahead.
Why it matters: animate transform and opacity, and an animation keeps going while a script runs. Animate a property that changes the layout, like left, and it stops whenever the main thread is busy. Try it in your own browser:
-
transform: translateX() -
left
stillnone yet59Start the boxes. Then block the main thread for a second, and watch which box keeps moving.
What can run off the main thread
The compositor isn't the main thread's only help. The renderer also keeps a pool of worker threads for jobs that don't need the page itself. These are Chrome's own threads, and you don't start them. In the trace, the raster work ran on them, for example.
The head cook has helpers in the back of the kitchen. They chop the vegetables and make the sauces. They never serve a table. They hand what they made to the cook, and the cook puts it on the plate.
- In real life: The helpers in the back
- In the browser: Worker threads
- In real life: Chopping vegetables and making sauces
- In the browser: Work that doesn't need the page: raster, compiling scripts, a photo filter
- In real life: Handing the bowl to the cook
- In the browser: A Web Worker sending its result back to the main thread
- In real life: Helpers never serve a table
- In the browser: A worker can't change the DOM
How much work did the worker threads take off the main thread on the home page? Here is the renderer's busy time:
// The home page's renderer: how long each kind of thread was busy, in ms.const busy = { "main thread": 306.7, "worker threads": 139, "compositor thread": 20.2 }; const total = Object.values(busy).reduce((sum, ms) => sum + ms, 0);const offMain = total - busy["main thread"];console.log(`The renderer's threads were busy for ${total.toFixed(1)} ms in all.`);console.log(`${Math.round((offMain / total) * 100)}% of that was off the main thread.`);- Line 1 is a comment.
- Line 2 holds how long each kind of thread was busy, from the same trace as before.
- Line 4 adds them up. Line 5 takes away the main thread's part.
- Line 6 prints
The renderer's threads were busy for 465.9 ms in all. - Line 7 prints
34% of that was off the main thread.
So a third of the renderer's work never touched the main thread. Two jobs took most of the worker threads' time. They rastered tiles for 37.3 ms. And they compiled and optimized JavaScript for 44.1 ms.
The main thread still parsed and compiled scripts for 47.2 ms. V8's Addy Osmani explains why. Most of that work can happen on a background thread, but "some work still has to happen on the main thread."
So the browser already moves what it can. The table shows what can run off the main thread. Read each row across: the work, the thread that does it, and what you can do.
| What work? | Which thread does it? | What should you do? |
|---|---|---|
Scrolling, and animations of transform and opacity | The compositor thread. | Animate only transform and opacity. Mark touch listeners as passive. |
| Turning tiles into pixels | Raster threads, then the GPU process. | Nothing. The browser does it for you. |
| Parsing and compiling scripts | Background threads in V8, for most of it. | Nothing. V8 starts as the bytes arrive. |
| Your own heavy JavaScript | A Web Worker. | Start one with new Worker(), and send it work with postMessage(). It can't touch the DOM. |
| Drawing on a canvas | A worker, with OffscreenCanvas. | Hand the canvas to the worker with transferControlToOffscreen(). |
Your own heavy work: Web Workers
Say Leaf and Kettle adds a page where Asha can put a filter on a photo of her tea. The filter is plain JavaScript, so it runs on the main thread. While it runs, the page can't answer Asha's taps.
A Web Worker is a thread you start yourself, for your own JavaScript. MDN calls workers "a simple means for web content to run scripts in background threads." Two rules come with them:
- A worker can't change the DOM. Only the main thread changes the page.
- The page and the worker talk by sending messages, with
postMessage(). A message's data is copied, not shared.
The course tested a worker on a small page. 400 ms of busy work stands in for the filter. 100 ms after the work starts, the course taps the page's button: a real mouse click on it. It did this five times with the work on the main thread, and five times with it in a worker. Each time, the button's click handler wrote down how long the tap had waited:
// How long each of five taps waited for its click handler, in ms.const waited = { "on the main thread": [298.2, 298.3, 298.4, 300.8, 303.9], "in a worker": [0.8, 0.8, 0.9, 1, 1],}; for (const [where, taps] of Object.entries(waited)) { const middle = [...taps].sort((a, b) => a - b)[2]; console.log(`With the filter ${where}, the middle tap waited ${middle} ms.`);}- Line 1 is a comment.
- Line 3 holds the five waits with the filter on the main thread. Line 4 holds the five with it in a worker.
- Line 8 sorts each list and picks the third of the five: the middle value, called the median.
- Line 9 prints
With the filter on the main thread, the middle tap waited 298.4 ms.andWith the filter in a worker, the middle tap waited 0.9 ms.
Same filter, same tap. On the main thread, the tap waited about 300 ms: the rest of the filter's run. In a worker, the main thread was free, and the click handler ran in under a millisecond.
Why it matters: the worker didn't make the filter faster. It took 400 ms either way. What changed is that the main thread stayed free to answer Asha. Try it with your own taps:
// page.js: send the photo to a worker, and show the result when it comes back.const worker = new Worker("filter-worker.js", { type: "module" });worker.onmessage = (event) => showPhoto(event.data);worker.postMessage(photo); // filter-worker.js: the filter runs here, on the worker's own thread.onmessage = (event) => postMessage(filter(event.data));not yetnot yet0none yetRun the filter on the main thread or in a worker. Then tap the button while it runs. The panel times how long each tap waited for its click handler.
The code in the playground shows both sides of a worker:
- Line 2 starts a worker. It runs the file
filter-worker.json a thread of its own. - Line 3 says what to do when the worker sends a message back: show the filtered photo.
- Line 4 sends the photo to the worker.
- Line 7 is the worker's side. When a message arrives, it runs the filter and sends the result back with
postMessage().
Two features here have support boxes. A module worker, the type: "module" on line 2, runs its file as a JavaScript module, so the file can use import. OffscreenCanvas lets a worker draw on a canvas.
The JavaScript lesson How workers work shows what a worker costs to start, and to talk to.
Main thread, or another thread?
Every piece of work below runs on some thread. Some of it needs the main thread. The rest can happen on another thread. Sort each one:
- Your click handler
- Layout after an element's width changes
- Reading the HTML into the DOM
- An animation of
left - An animation of
transform - Scrolling a page whose touch listeners are all passive
- Turning tiles into pixels
- Code in
new Worker("filter.js")
Sort each piece of work by the thread that does it.
Common mix-ups
- "JavaScript is single-threaded, so the browser is too." No. A page's JavaScript runs on one main thread, but Chrome uses many threads. Loading the home page kept 23 of them busy.
- "Each tab gets one process." No. Each site gets its own renderer, iframes included. Asha's one tab used two renderers, because the map came from another site.
- "A worker can update the page." No. A worker can't change the DOM. It sends its result back, and the main thread changes the page.
- "Every CSS animation keeps going when the main thread is busy." No. Only animations of
transformandopacitydo. The box moved withleftfroze. - "A busy main thread only delays JavaScript." No. It delays styles, layout, paint and click handlers too. A tap waited 298.4 ms behind the filter.
- "A worker makes the work faster." No. The filter took 400 ms either way. The worker kept the main thread free.
Practice exercises
Answer first, then use the hints.
Leaf and Kettle adds a third iframe: a booking form from a booking website. What does the example print now?
// Where Chrome ran the Leaf and Kettle page and its iframes.
// Each number is a process ID: the number the system gave that process.
const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120, "the booking form": 78125 };
const renderers = new Set(Object.values(ranIn));
const iframes = Object.keys(ranIn).length - 1;
console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);The page and its 3 iframes ran in 3 renderer processes. The booking form comes from a third site, so it gets a third process. The Set now holds three different IDs.
The home page's layout gets twice as fast: 39.3 ms instead of 78.6. What does the first line print now?
// The course's home page: what its main thread did, in ms.
const work = { parsing: 5.8, script: 131, layout: 39.3, paint: 33.3, other: 57.9 };
const busy = Object.values(work).reduce((sum, ms) => sum + ms, 0);
console.log(`The main thread was busy for ${busy.toFixed(1)} ms.`);
for (const [job, ms] of Object.entries(work)) {
console.log(`${job}: ${Math.round((ms / busy) * 100)}% of that time`);
}The main thread was busy for 267.3 ms. Layout lost 39.3 ms, and nothing else changed. Script's share grows to 49%, because the same script time is now part of a smaller total.
The boxes playground's panel shows how many frames Chrome drew during the course's busy second. How many was it?
59. That's about one frame every 17 ms, just as many as in the calm second before. The compositor thread kept making frames while the main thread was stuck.
With the filter in a worker, how much sooner did the middle tap's click handler run?
297.5 ms sooner. It was the same tap both times. The only difference was a free main thread.
In DevTools' Performance panel, what is the track for the page's main thread called?
Main. That track shows what the page's main thread did, task by task.
Which window in Chrome lists every process it's running, with how much memory and CPU each one uses?
Task Manager. It lists every process Chrome is running, with how much CPU and memory each one uses.
Check your understanding
For each question, ask two things: which process does the work, and then which thread?
Question 1 of 8How many renderer processes does the iframes example count?
Read the code, then predict// Where Chrome ran the Leaf and Kettle page and its iframes. // Each number is a process ID: the number the system gave that process. const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120 }; const renderers = new Set(Object.values(ranIn)); const iframes = Object.keys(ranIn).length - 1; console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);Choose an answer to see the explanation.
Question 2 of 8Why did the map iframe get a renderer process of its own?
Choose an answer to see the explanation.
Question 3 of 8Asha clicks a button on the Leaf and Kettle page. Which process gets her click first?
Choose an answer to see the explanation.
Question 4 of 8What does the last line of the boxes example print?
Read the code, then predict// Two boxes sliding right while a script kept the main thread busy. // When Chrome drew each frame (ms), and where each box was (px). const firstFrame = { ms: 1030, transform: 150, left: 147 }; const lastFrame = { ms: 1996.5, transform: 295, left: 147 }; console.log(`The transform box moved ${lastFrame.transform - firstFrame.transform} px.`); console.log(`The left box moved ${lastFrame.left - firstFrame.left} px.`);Choose an answer to see the explanation.
Question 5 of 8Which CSS properties can the compositor thread animate without the main thread?
Choose an answer to see the explanation.
Question 6 of 8What can't a Web Worker do?
Choose an answer to see the explanation.
Question 7 of 8In the recording, why did the tap wait so long with the filter on the main thread?
Choose an answer to see the explanation.
Question 8 of 8What do raster threads do?
Choose an answer to see the explanation.
Key takeaways
- Chrome is many programs, called processes: a browser process, a GPU process, services such as the network service, and renderers.
- On a computer, each site gets a renderer of its own, iframes included. A crash, or a harmful site, stays inside its own process.
- The cost is memory, so Chrome limits how many processes it starts.
- Inside a renderer, one main thread runs your JavaScript, styles, layout and paint, one task at a time.
- The compositor thread puts frames together from layers that are already drawn. Scrolling, and animations of
transformandopacity, can keep going while the main thread is busy. - Worker threads raster tiles and compile scripts. A Web Worker runs your own heavy JavaScript.
- A worker doesn't make work faster. It keeps the main thread free for the person.
Remember the one-liner.
Almost everything you write runs on one main thread. Keep it free, and let the other threads do the rest.
Sources
The facts on this page come from these docs and articles:
- Inside look at modern web browser (part 1), Mariko Kosaka, Chrome for Developers.
- Process Model and Site Isolation, Chromium docs.
- Inside look at modern web browser (part 3), Mariko Kosaka, Chrome for Developers.
- Inside look at modern web browser (part 4), Mariko Kosaka, Chrome for Developers.
- Stick to compositor-only properties and manage layer count, Paul Lewis, web.dev.
- The cost of JavaScript in 2019, Addy Osmani, V8.
- Using Web Workers, MDN.
- Worker() constructor, MDN.
- OffscreenCanvas, MDN.
- Performance features reference, Chrome DevTools.
Coming next: The event loop and frames, also in How the browser loads and renders. How the main thread picks its next task, when the browser gets a chance to draw, and why a long task freezes everything.