Browser & Network
Browser Principles
Rendering pipeline, JavaScript execution, event loop, and browser APIs
Browser Principles
Understanding browser internals helps write more efficient code and debug performance issues effectively.
Rendering Pipeline
Critical Rendering Path
Critical Rendering Path
├── 1. Parse HTML → DOM Tree
├── 2. Parse CSS → CSSOM
├── 3. JavaScript Execution (can block)
├── 4. Render Tree = DOM + CSSOM
├── 5. Layout (Reflow)
├── 6. Paint
└── 7. CompositeDOM Construction
<!-- HTML -->
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Hello</h1>
<p>World</p>
</div>
<script src="app.js"></script>
</body>
</html>DOM Tree:
Document
└── html
├── head
│ └── link
└── body
├── div.container
│ ├── h1 → "Hello"
│ └── p → "World"
└── scriptCSSOM Construction
/* styles.css */
body { font-size: 16px; }
.container { display: flex; }
h1 { color: blue; }
p { margin: 0; }CSSOM Tree:
StyleSheet
├── body { font-size: 16px }
├── .container { display: flex }
├── h1 { color: blue }
└── p { margin: 0 }Render Tree
Only visible elements are included:
Render Tree (DOM + CSSOM):
RenderView
└── RenderBody (font-size: 16px)
└── RenderBlock.container (display: flex)
├── RenderBlock h1 (color: blue)
│ └── RenderText "Hello"
└── RenderBlock p (margin: 0)
└── RenderText "World"
Excluded from Render Tree:
- <head> and its contents
- display: none elements
- <script> tagsLayout and Paint
Layout (Reflow)
Layout calculates the exact position and size of each element.
// Operations that trigger layout:
element.offsetTop, offsetLeft, offsetWidth, offsetHeight
element.clientTop, clientLeft, clientWidth, clientHeight
element.scrollTop, scrollLeft, scrollWidth, scrollHeight
element.getBoundingClientRect()
window.getComputedStyle()
window.innerWidth, innerHeightPaint
Paint fills in pixels for each render layer.
// Operations that trigger paint (but not layout):
element.style.color
element.style.backgroundColor
element.style.visibility
element.style.boxShadowComposite
Compositing combines painted layers into the final image.
/* Properties handled by compositor (GPU-accelerated): */
.fast-animation {
transform: translateX(100px); /* Compositor only */
opacity: 0.5; /* Compositor only */
}
/* Promote to compositor layer: */
.promoted {
will-change: transform;
/* or */
transform: translateZ(0);
}JavaScript Engine
Execution Context
// Global Execution Context
var globalVar = 'global';
function outer() {
// Function Execution Context
var outerVar = 'outer';
function inner() {
// Function Execution Context
var innerVar = 'inner';
console.log(globalVar, outerVar, innerVar);
}
inner();
}
outer();Call Stack:
┌─────────────────────┐
│ inner() context │ ← Top (currently executing)
├─────────────────────┤
│ outer() context │
├─────────────────────┤
│ Global context │ ← Bottom
└─────────────────────┘Hoisting
// What you write:
console.log(x); // undefined (var is hoisted)
console.log(y); // ReferenceError (let is TDZ)
console.log(fn()); // "hello" (function is hoisted)
console.log(arrow()); // TypeError (var arrow is hoisted as undefined)
var x = 5;
let y = 10;
function fn() { return 'hello'; }
var arrow = () => 'arrow';
// What engine sees:
var x; // Hoisted
var arrow; // Hoisted
function fn() { return 'hello'; } // Hoisted completely
console.log(x); // undefined
console.log(y); // ReferenceError (TDZ)
console.log(fn()); // "hello"
console.log(arrow()); // TypeError
x = 5;
let y = 10; // TDZ ends here
arrow = () => 'arrow';Event Loop
Single-Threaded Model
console.log('1'); // Sync
setTimeout(() => {
console.log('2'); // Macro task
}, 0);
Promise.resolve().then(() => {
console.log('3'); // Micro task
});
console.log('4'); // Sync
// Output: 1, 4, 3, 2Task Queues
Event Loop:
┌────────────────────────────────────────────────────┐
│ Call Stack │
│ (sync code) │
└────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Microtask Queue │
│ (Promise.then, queueMicrotask, MutationObserver) │
└────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Macrotask Queue │
│ (setTimeout, setInterval, I/O, UI rendering) │
└────────────────────────────────────────────────────┘requestAnimationFrame
// rAF runs before paint, after microtasks
function animate() {
// Update DOM
element.style.transform = `translateX(${x}px)`;
// Schedule next frame
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
// Timing:
// 1. Execute sync code
// 2. Execute all microtasks
// 3. Execute rAF callbacks
// 4. Render (Layout, Paint, Composite)
// 5. Execute one macrotask
// 6. RepeatrequestIdleCallback
// Runs when browser is idle
function processBackgroundWork(deadline) {
while (deadline.timeRemaining() > 0 && workQueue.length > 0) {
const work = workQueue.shift();
doWork(work);
}
if (workQueue.length > 0) {
requestIdleCallback(processBackgroundWork);
}
}
requestIdleCallback(processBackgroundWork, { timeout: 2000 });Web APIs
Intersection Observer
// Efficient visibility detection
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Element is visible
loadImage(entry.target);
observer.unobserve(entry.target);
}
});
},
{
root: null, // viewport
rootMargin: '100px', // trigger earlier
threshold: 0.1, // 10% visible
}
);
document.querySelectorAll('img[data-src]').forEach((img) => {
observer.observe(img);
});Resize Observer
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`Size: ${width}x${height}`);
// Respond to size changes
if (width < 600) {
entry.target.classList.add('compact');
} else {
entry.target.classList.remove('compact');
}
}
});
resizeObserver.observe(document.querySelector('.container'));Mutation Observer
const mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
console.log('Children changed:', mutation.addedNodes, mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log('Attribute changed:', mutation.attributeName);
}
}
});
mutationObserver.observe(document.body, {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['class', 'style'],
});Memory Management
Garbage Collection
// Objects become eligible for GC when unreachable
// Reference counting issue (circular reference)
function problematic() {
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// Modern engines handle this with mark-and-sweep
}
// Memory leak patterns
class Component {
constructor() {
// Leak: event listener holds reference to this
window.addEventListener('resize', this.handleResize);
}
handleResize = () => {
// Uses this
};
destroy() {
// Fix: remove listener
window.removeEventListener('resize', this.handleResize);
}
}WeakMap and WeakSet
// Weak references allow GC
const cache = new WeakMap();
function getCachedData(obj) {
if (!cache.has(obj)) {
cache.set(obj, computeExpensiveData(obj));
}
return cache.get(obj);
}
// When obj is no longer referenced elsewhere,
// it can be garbage collected along with its cached dataBest Practices
Browser Optimization Guidelines
- Minimize DOM manipulation, batch updates
- Use transform/opacity for animations
- Debounce scroll and resize handlers
- Use Intersection Observer for lazy loading
- Avoid forced synchronous layouts
- Clean up event listeners and observers
- Use Web Workers for heavy computation
- Profile with DevTools Performance panel