Docs For AI
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. Composite

DOM 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"
        └── script

CSSOM 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> tags

Layout 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, innerHeight

Paint

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.boxShadow

Composite

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, 2

Task 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. Repeat

requestIdleCallback

// 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 data

Best Practices

Browser Optimization Guidelines

  1. Minimize DOM manipulation, batch updates
  2. Use transform/opacity for animations
  3. Debounce scroll and resize handlers
  4. Use Intersection Observer for lazy loading
  5. Avoid forced synchronous layouts
  6. Clean up event listeners and observers
  7. Use Web Workers for heavy computation
  8. Profile with DevTools Performance panel

On this page