Understanding the Browser Lifecycle and Events

Photo by Glenn Carstens-Peters on Unsplash

As JavaScript developers, we spend most of our time interacting with the web browser. We write code that manipulates the Document Object Model (DOM), styles elements, handles user interactions, and fetches data from servers. But how does all of this actually happen within the browser? Understanding the browser’s lifecycle and the sequence of events that occur from the moment a user requests a webpage to the moment it’s fully rendered and interactive is crucial for writing efficient, performant, and bug-free web applications. This article will break down this process, covering key lifecycle stages and the critical events associated with them. A solid grasp of these concepts will give you more control over your code and enable you to optimize how your web pages load and behave.

The Browser Lifecycle: A Step-by-Step Breakdown

The browser lifecycle can be broadly divided into these main phases:

  1. Navigation and Resource Fetching:
  2. HTML Parsing and DOM Construction:
  3. CSS Parsing and CSSOM Construction:
  4. Render Tree Construction:
  5. Layout (Reflow):
  6. Painting:
  7. Interactive phase and beyond

Let’s examine each phase in detail:

1. Navigation and Resource Fetching

This is where it all begins. When you type a URL into the browser’s address bar and hit Enter, or click a link, the following happens:

  • DNS Resolution: The browser first needs to figure out the IP address associated with the domain name (e.g., www.example.com). It does this by querying DNS (Domain Name System) servers.
  • TCP Connection: Once the IP address is known, the browser establishes a TCP (Transmission Control Protocol) connection with the server. This is a reliable connection that ensures data is transmitted in order. For HTTPS connections, a TLS (Transport Layer Security) handshake also occurs to establish a secure connection.
  • HTTP Request: The browser sends an HTTP request to the server, asking for the resource (e.g., the HTML file). This request includes headers that provide information about the browser, the requested resource, and other details.
  • HTTP Response: The server processes the request and sends back an HTTP response. This response includes:
  • Status Code: A numerical code indicating the success or failure of the request (e.g., 200 OK, 404 Not Found, 500 Internal Server Error).
  • Headers: Information about the response, such as the content type (e.g., text/html), content length, and caching instructions.
  • Body: The actual content of the response, which is typically the HTML file in the initial request.

2. HTML Parsing and DOM Construction

Once the browser receives the HTML, it starts parsing it. This is a crucial step where the HTML code is transformed into a structured representation that the browser can understand and work with: the Document Object Model (DOM).

  • Tokenization: The HTML parser breaks down the raw HTML text into a series of tokens. These tokens represent different parts of the HTML, such as start tags (<div>), end tags (</div>), attributes (class=”container”), and text content.
  • Tree Construction: The parser uses these tokens to build the DOM tree. The DOM is a hierarchical tree structure where each node represents an HTML element. For example:
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a paragraph.</p>
</body>
</html>
  • The DOM tree for this HTML would look something like this (simplified):
document
└── html
├── head
│ └── title
│ └── "My Page" (text node)
└── body
├── h1
│ └── "Hello, World!" (text node)
└── p
└── "This is a paragraph." (text node)
  • Script Execution (and Blocking!): Crucially, when the parser encounters a <script> tag, it stops parsing the HTML and executes the JavaScript code (if the script is synchronous, which is the default). This is known as parser blocking. Why is this important? Because the JavaScript code might modify the DOM! If the DOM isn’t fully built yet, the script might try to access elements that don’t exist.

defer and async: To mitigate parser blocking, you can use the defer and async attributes on your <script> tags:

  • async: The script is downloaded in parallel with the HTML parsing, but execution still blocks the parser when the script is ready. The order of execution for multiple async scripts is not guaranteed.
  • defer: The script is downloaded in parallel, but execution is deferred until after the HTML parsing is complete. The order of execution for multiple defer scripts is preserved (they execute in the order they appear in the HTML).
<script src="script.js"></script>
<script async src="script1.js"></script>
<script defer src="script2.js"></script>

In the example above, the browser will stop parsing and loading the other elements of the page when finds the first `<script>` tag, if the `script.js` file takes 5 seconds to be downloaded and executed, the page will remain “frozen” by that amount of time. When the browser find the second line, it will download the script *in parallel* to parsing the HTML, but it will pause the parser and any other process, until the `script1.js` file is executed. Finally, the line with `defer`, will be downloaded in parallel with the HTML, but it won’t be executed until all the HTML is parsed.

3. CSS Parsing and CSSOM Construction

While the HTML is being parsed, the browser also encounters <link> tags referencing CSS files (or inline <style>tags). The browser then parses the CSS and creates the CSS Object Model (CSSOM).

  • CSSOM: The CSSOM is another tree-like structure, similar to the DOM, but it represents the styles that apply to the elements. It includes information about selectors, properties, and values.
body {
font-family: sans-serif;
color: #333;
}

h1 {
font-size: 2em;
}

The CSSOM constructed will contain nodes that represents each style rule, for instance body will have font-family with value sans-serif and color with value #333

  • Render Blocking: CSS is considered render-blocking. The browser won’t render anything to the screen until it has both the DOM and the CSSOM. This ensures that the page is rendered with the correct styles from the start, preventing a “flash of unstyled content” (FOUC).

4. Render Tree Construction

Once the DOM and CSSOM are ready, the browser combines them to create the Render Tree. The Render Tree only contains the elements that will actually be visible on the page (and their computed styles).

  • Visibility: Elements with display: none; are not included in the Render Tree (they are in the DOM, but not the Render Tree). Elements with visibility: hidden; are included in the Render Tree (they take up space, but are invisible).

5. Layout (Reflow)

This is where the browser calculates the size and position of each element in the Render Tree. This process is also known as “reflow.”

  • Geometry and Position: The browser determines the exact coordinates and dimensions of each box on the page, based on the CSS styles, the viewport size, and the relationships between elements.
  • Expensive Operation: Reflow is a computationally expensive operation. Changes that affect the layout of the page (e.g., changing the width of an element, adding or removing elements, changing the font size) will trigger a reflow. Excessive reflows can lead to performance problems (“jank”).

6. Painting

After the layout is calculated, the browser “paints” the pixels onto the screen. This involves filling in the boxes defined by the layout with their actual colors, text, images, and other visual content.

  • Rasterization: The browser converts the Render Tree nodes into actual pixels on the screen.
  • Repaints: Repaints occur when the visual appearance of an element changes without affecting its layout (e.g., changing the background color, text color, or visibility). Repaints are generally less expensive than reflows, but can still impact performance if they happen frequently.

7. Interactive Phase and Beyond

Once the initial paint completes the browser is in an interactive state. Key events signal different parts of this phase:

  • DOMContentLoaded: This event fires when the HTML has been completely parsed and the DOM tree is built, butexternal resources like images and stylesheets may still be loading. This is often a good time to start running JavaScript code that interacts with the DOM.
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM fully loaded and parsed');
// Initialize your application logic here
});

The code inside the function will be executed after the HTML document has been completely parsed, ensuring that all elements are available in the DOM.

  • load: This event fires when all resources, including images, stylesheets, and iframes, have finished loading.
window.addEventListener('load', function() {
console.log('All resources finished loading');
});

The code inside the function will be executed only after the entire page, including images, styles, and other external resources, has fully loaded.

  • beforeunload and unload: These events fire when the user is about to leave the page (e.g., by closing the tab, navigating to another page, or refreshing). beforeunload allows you to show a confirmation dialog to the user, asking if they really want to leave. unload can be used for cleanup tasks, but its reliability is not guaranteed in modern browsers.
window.addEventListener('beforeunload', function(event) {
event.preventDefault(); // Required for some browsers
event.returnValue = ''; // Required for Chrome
});

This code will display a confirmation dialog box when the user attempts to leave the page. The event.preventDefault() and event.returnValue lines are necessary to ensure the dialog appears consistently across different browsers.

Other Important Events

Beyond the lifecycle events, numerous other events are essential for creating interactive web applications:

  • User Input Events:
  • click: Fires when an element is clicked.
  • mousedown, mouseup: More granular events for mouse button presses and releases.
  • mousemove: Fires repeatedly as the mouse moves over an element.
  • keydown, keyup, keypress: Events for keyboard interactions.
  • focus, blur: Fires when an element gains or loses focus.
  • input, change: Events for form input changes.
  • submit: Fires when a form is submitted.
  • Network Events:
  • progress, load, error (on XMLHttpRequest or fetch): Events for tracking the progress of network requests.
  • Timer Events:
  • setTimeout, setInterval are not technically browser “lifecycle” event, but their use is intrinsically related to the lifecycle, because they are managed by the browser’s event loop.
  • Mutation Observer Events:
  • MutationObserver: Provides a way to observe changes to the DOM. This is much more efficient than repeatedly polling the DOM for changes.
const observer = new MutationObserver(function(mutationsList, observer) {
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
});

const targetNode = document.getElementById('myElement');
const config = { attributes: true, childList: true, subtree: true };
observer.observe(targetNode, config);

This code sets up a MutationObserver that watches for changes to the DOM element with the ID myElement. It logs messages to the console whenever child nodes are added or removed, or when attributes of myElement or its descendants are changed. The config object specifies which types of mutations to observe.

Understanding the browser lifecycle and its associated events is fundamental to becoming a proficient JavaScript developer. By knowing how the browser processes HTML, CSS, and JavaScript, and by being aware of the sequence of events that occur during page loading and interaction, you can write more efficient code, debug issues more effectively, and create smoother, more responsive user experiences. Key takeaways include:

  • Minimize Parser Blocking: Use async and defer attributes on your <script> tags to avoid blocking the HTML parser.
  • Optimize for Render Tree Construction: Avoid unnecessary DOM manipulations and CSS that triggers reflows.
  • Use the Right Events: Choose the appropriate events (DOMContentLoaded, load, etc.) for your tasks.
  • Leverage MutationObserver: For efficient DOM change tracking.

By mastering these concepts, you’ll be well-equipped to build high-performance web applications that deliver a great user experience. And I will be one dollar richer.


Understanding the Browser Lifecycle and Events was originally published in CarlosRojasDev on Medium, where people are continuing the conversation by highlighting and responding to this story.

Scroll to Top