How the browser renders a Web page? — DOM, CSSOM, and Rendering

preface

When you develop a website, some things are necessary for a good user experience. Some common problems that websites may encounter may be slow resource loading, waiting for unnecessary files to download during initial rendering, flash of unstyled content (FOUC), etc. To avoid similar problems, we need to understand the browser rendering life cycle of a typical web page.

First of all, we need to understand what isDOM. When the browser sends a request to the server, it gets oneHTMLWhen you document, the server will return a binary stream formatHTMLPage, which is basically a text file with the response headerContent-TypeSet totext/html; charset-UTF-8. Here,text/htmlIs aThe MIME typeIt tells the browser that this is aHTMLThe document,charset=UTF-8Tell the browser that it isUTF-8charactercoding. Using this information, the browser converts the binary format into a readable text file. As shown in the figure below.

If the header is missing, the browser will not understand how to process the file, and it will be rendered in plain text. But if all goes well, after this transformation, the browser can start reading the HTML document. A typical HTML document looks something like this:

<! DOCTYPEhtml>
<html>
  <head>
    <title>Rendering Test</title>

    <! -- stylesheet -->
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div class="container">
      <h1>Hello World!</h1>
      <p>This is a sample paragraph.</p>
    </div>

    <! -- script -->
    <script src="./main.js"></script>
  </body>
</html>
Copy the code

In the document above, our web page relies onstyle.cssHTMLThe element provides the style,main.jsTo perform someJavaScriptOperation. Through someCSSStyle, our top page will look like this:

The question remains, however, how does a browser render a beautiful web page from a simple, text-only HTML file? To do this, we need to know what are DOM, CSSOM, and Render Tree

Document Object Model (DOM)

When the browser reads HTML code, it creates a JavaScript object named Node whenever it encounters HTML elements such as body and div.

Since each HTML element has a different attribute, Node objects will be created from different classes (constructors). For example, the Node object for the div element is created by HTMLDivElement, which inherits from the Node class. For our previous HTML document, we can use a simple test to visualize these nodes, as shown below:

The browser comes with HTMLDivElement, HTMLScriptElement, Node and other built-in classes.

Once the browser has created nodes from the HTML document, it creates these Node objects into a tree structure. Since our HTML elements are nested within each other in the HTML file, the browser needs to copy them using the Node object we created earlier. This will help browsers render and manage web pages efficiently throughout their life cycle.

The DOM tree for our previous HTML document looks like this. A DOM tree starts with the uppermost HTML element and branches based on the presence and nesting of HTML elements in the document. Whenever an HTML element is found, it creates a DOM Node (Node) object (constructor) from its respective class.

A DOM node does not always have to be an HTML element. When the browser creates a DOM tree, it also stores comments, attributes, text, and so on as separate nodes in the tree. But for the sake of simplicity, let’s just consider the DOM nodes of HTML elements, which are DOM elements. Here is a list of all DOM node types.

You can be inGoogle Chrome DevTools ConsoleseeDOMTree, as shown below. This will showDOMElement hierarchy (a high-level view of the DOM tree) and eachDOMAttribute of the element.

JavaScript doesn’t understand what the DOM is; it’s not part of the JavaScript specification. DOM is an advanced Web API provided by browsers for rendering Web pages efficiently, allowing developers to manipulate DOM elements dynamically for various purposes.

Using the DOM API, developers can add or remove HTML elements, change their appearance, or bind event listeners. Using the DOM API, YOU can create or clone HTML elements in memory and modify them without affecting the DOM tree. This gives developers the ability to build highly dynamic web pages and provide rich user experiences.

CSS Object Model (CSSOM)

When we design a website, we aim to make it look as good as possible. We do this by providing styles for HTML elements. In HTML pages, we use CSS(Cascading Style Sheets) to Style HTML elements. Using CSS selectors, we can set a value for style properties, such as color or font size, for DOM elements.

There are different ways to apply styles to HTML elements, such as using external CSS files, embedding CSS with

For example, for our previous example, we will use the following CSS style (this is not the CSS used for the card shown in the screenshot). For the sake of simplicity, we’re not going to worry about how to import CSS styles into an HTML page.

html {
  padding: 0;
  margin: 0;
}

body {
  font-size: 14px;
}

.container {
  width: 300px;
  height: 200px;
  color: black;
}

.container > h1 {
  color: gray;
}

.container > p {
  font-size: 12px;
  display: none;
}
Copy the code

After building the DOM, the browser reads CSS from all sources (external, embedded, embedded, user-agent, etc.) and builds a CSSOM. CSSOM, short for CSS object model, is a tree structure like DOM.

Each node in the tree contains CSS style information that is applied to the DOM element (specified by the selector) for which it is targeted. However, CSSOM does not contain DOM elements that cannot be printed on screen, such as
,

As we know, most browsers come with a style sheet, called a User-agent style sheet. The browser first overrides the user-agent style with CSS provided by the developer properties (using specific rules), calculates the final CSS properties of the DOM element, and then constructs a node.

Even if the CSS property of an HTML element (such as display) is not defined by the developer or browser, its value is set to the default value of that property as specified by the W3C CSS standard. When selecting default values for CSS properties, inheritance rules are used if a property conforms to inheritance rules mentioned in the W3C documentation.

For example, if an HTML element lacks attributes such as color and font size, those attributes inherit the values of the parent element. So you can imagine having these attributes on an HTML element, and all of its children will inherit from it. This is called cascading style sheets, which is why CSS stands for cascading style sheets. This is why browsers build a tree-structured CSSOM to compute styles according to CSS cascading rules.

You can use the Chrome DevTools console in the Element panel to view the computed styles of HTML elements. Select any HTML element from the left pane, and then click the Calculation TAB in the right pane.

We can visualize the CSSOM tree of the previous example using the following figure. For the sake of simplicity, we’ll ignore the user agent styles and focus on the CSS styles mentioned earlier.

As you can see from the figure above, our CSSOM tree does not contain ,

,

Render Tree

Render-Tree is also a Tree structure that combines the DOM Tree with the CSSOM Tree. The browser uses the Render Tree to calculate the layout of each visible element and draw them on the screen. Therefore, if the Render Tree is not built, nothing will be printed on the screen, which is why we need both DOM and CSSOM trees.

Since a Render-Tree is a low-level representation of what will eventually be printed on the screen, it will not contain nodes in the pixel matrix (page) that do not contain any region (in some cases, it can be considered: space-free). For example, display: none; The dimensions of the elements are 0px X 0px, so they do not appear in the render-tree.

As you can see from the figure above, Render-Tree combines DOM and CSSOM to produce a Tree structure containing only the elements to be printed on the screen.

Because in CSSOM, the p element inside the div is set to display: None; Style, so it and its children don’t appear in the Render-Tree because it doesn’t take up space on the screen. However, if you have elements that visibility:hidden or opacity:0, they will take up screen space, so they will appear in the render-tree.

Unlike the DOM API, which has access to DOM elements in the BROWser-built DOM tree, CSSOM is hidden from the user. But because browsers combine DOM and CSSOM to form the Render Tree, browsers expose the CSSOM nodes of DOM elements by providing high-level apis in the DOM elements themselves. This allows developers to access or change the CSS properties of CSSOM nodes.

Since manipulating the styles of elements using JavaScript is beyond the scope of this article, here is a link to the CSS Tricks article, which covers the broad scope of the CSSOM API. We also have the new CSS Typed Object API in JavaScript, which can manipulate the style of elements more accurately.

Rendering order

Now that we have a good understanding of what DOM, CSSOM, and Render-Tree are, let’s look at how browsing can use them to Render a typical web page. Having a minimum understanding of this process is critical for any Web developer, as it will help us design our sites for maximum user experience (UX) and performance.

When a web page is loaded, the browser first reads the HTML text and builds a DOM tree from it. It then processes CSS, whether embedded, embedded, or external, and builds the CSSOM tree out of it.

After building these trees, it builds Render- trees from them. Once the render-tree is built, the browser starts printing the elements on the screen.

Layout of the operation

First the browser creates the layout of each individual Render-Tree node. The layout includes the size (in pixels) of each node and where it will be printed on the screen. This process is called layout because the browser is calculating the layout information for each node.

This process, also known as reflow or browser reflow, also happens when you scroll, resize a window, or manipulate DOM elements. Here is a list of events that trigger element layout/reflow.

We should avoid multiple layout operations for trivial reasons, as this is an expensive operation. Here is an article by Paul Lewis on how we can avoid complex and expensive layout manipulation and layout disruption.

Drawing operations

Until now, we have a list of geometry that needs to be printed on the screen. Because elements (or subtrees) in a Render-Tree can overlap with each other, and they can have CSS properties that make them frequently change their appearance, position, or geometric display (such as animation), the browser creates a layer for it.

Creating layers helps browsers efficiently perform drawing operations throughout the life of a web page, such as when scrolling or resizing a browser window. Having layers also helps the browser to properly draw elements in stack order (along the Z-axis) as intended by the developer.

Now that we have layers, we can combine them and draw them on the screen. But browsers don’t draw all the layers at once. Each layer is drawn individually first.

Within each layer, the browser fills in individual pixels for any visible attributes of the element, such as borders, background colors, shadows, text, etc. This process is also known as rasterization. To improve performance, browsers can use different threads to perform rasterization.

The layer analogy in Photoshop can also be applied to the way browsers render web pages. You can see different layers on the page from Chrome DevTools. Open DevTools and select “Layers” from the more tool options. You can also see the layer borders from the “Rendering” panel.

Rasterization is usually done in the CPU, which makes it slow and expensive, but we now have new technologies that can improve performance in the GPU. This Intel article details the subject of painting and is a must read. This is a must-read article to understand the concept of layers in more detail.

Synthetic operation

So far, we haven’t drawn a single pixel on the screen. What we have are different layers (bitmap images) that should be drawn on the screen in a specific order. During the compositing operation, these layers are sent to the GPU and eventually drawn on the screen.

It is obviously inefficient to send the entire layer to be drawn every time you backflow (layout) or redraw. Thus, a layer is broken up into different blocks, which are then drawn on the screen. You can also see these blocks in Chrome’s DevTool Rendering panel.

From this information, we can build an entire browser’s sequence of events from a web page to rendering things on the screen from simple HTML and CSS text content.

This sequence of events is also known as the critical render path

Mariko Kosaka has written a beautiful article about the process, with cool illustrations and a broader explanation of each concept. Highly recommended.

Browser engine

The work of creating DOM trees, CSSOM trees, and processing rendering logic is done by a browser process called the browser engine (also known as the rendering engine or layout engine), which resides inside the browser. This browser engine contains all the elements and logic necessary to render a web page from HTML code to actual pixels on the screen.

If you hear people talking about WebKit, they’re talking about a browser engine. WebKit is used by Apple’s Safari browser and is the default rendering engine for Google’s Chrome browser. So far, the Chromium project uses Blink as the default rendering engine. Here is a list of the different browser engines used by some of the top web browsers.

The browser’s rendering flow

We all know that the JavaScript language is standardized through the ECMAScript standard, but since JavaScript is trademarked, we just call it ECMAScript. Therefore, every JavaScript engine provider, such as V8, Chakra, Spider Monkey, etc., has to follow the rules of this standard.

With standards, we can get a consistent JavaScript experience across all JavaScript runtimes, such as browsers, Node, Deno, etc. This is great for consistent and flawless development of multi-platform JavaScript (and Web) applications.

However, this is not how browsers render. HTML, CSS, or JavaScript, these languages are all standardized by an entity or an organization. However, how browsers manage them together to render things on the screen is not standardized. Google Chrome’s browser engine may do different things than Safari’s.

Therefore, it is difficult to predict the rendering order of a particular browser and the mechanism behind it. However, the HTML5 specification has made some efforts to standardize theoretically how rendering should work, but how browsers comply with this standard is entirely up to them.

Despite these inconsistencies, there are generally some common principles across all browsers. Let’s take a look at the common ways in which browsers render things on the screen and the lifecycle events of the process.

Parsing and external resources

Parsing is the process of reading HTML content and building a DOM tree from it. Therefore, this process is also called DOM parsing, and the program that does it is called a DOM parser.

Most browsers provide itDOMParserWeb API to build a DOM tree from HTML code.DOMParserClass representing a DOM parser, used byparseFromStringWith the prototype approach, we can parse the raw HTML text (code) into a DOM tree (as shown in the figure below).

When a browser requests a web page and the server responds with some HTML text (the Content-Type header is set to text/ HTML), the browser may begin parsing the HTML as soon as a few characters or lines of the entire document are available. Therefore, the browser can build the DOM tree step by step, one node at a time. Browsers parse HTML from top to bottom, not anywhere in between, because HTML represents a nested tree structure.

In the example above, we accessed the increment.html file from the Node server and set the network speed to only 10kbps (from the network panel). Because it takes a long time for the browser to load (download) this file (because it contains 1000 H1 elements), the browser builds a DOM tree from the first few bytes and prints them on the screen (because it downloads the rest of the HTML file in the background).

If you look at the performance graph of the request above, you can see some events in the Timing line. These events are often referred to as performance indicators. The user experience is better when these events are placed as close as possible and occur as early as possible.

FP stands for First Paint, which means the time the browser starts printing something on the screen (it can be as simple as the First pixel of the text background color).

FCP stands for First Contentful Paint, which is the time a browser renders the First pixel of content such as text or image. LCP, short for Largest Contentful Paint, refers to the time it takes a browser to render a large chunk of text or image.

L stands for onLoad event, which is emitted by the browser on the Window object. Similarly, the DCL stands for the DOMContentLoaded event, which is emitted on the Document object but bubbles up on the Window, so you can listen for it on the Window as well. These events are a bit complicated to understand, so we’ll discuss them later.

Whenever the browser encounters an external resource, For example, in a script file (JavaScript) via the element, in a stylesheet file (CSS) via the tag, in a element image file or any other external resource that the browser will start downloading in the background (outside the main thread of JavaScript execution).

Most importantly, DOM parsing usually happens on the main thread. Therefore, if the main JavaScript executing thread is busy, DOM parsing will not take place until the thread is idle. Why is that important, you might ask? Script elements block the parser. With the exception of script (.js) file requests, every external file request, such as images, stylesheets, PDFS, videos, etc., does not block DOM building (parsing).

Parser-blocking Scripts

A parser blocking script is a script (JavaScript) file/code that stops parsing HTML. When the browser encounters a script element, if it is an embedded script, it executes the script first, then proceeds to parse the HTML and build the DOM tree. So all embedded scripts are parser blocking, end of discussion.

If the script element is an external script file, the browser will start downloading the external script file outside the main thread, but will stop executing the main thread until the file has been downloaded. This means that no DOM parsing takes place until the script file is downloaded.

Once the script file has been downloaded, the browser will first execute the downloaded script file on the main thread and then proceed with DOM parsing. If the browser finds another script element in the HTML again, it does the same. So why does the browser stop DOM parsing until JavaScript is downloaded and executed?

The browser exposes the DOM API to the JavaScript runtime, which means we can access and manipulate DOM elements from JavaScript. This is how dynamic Web frameworks like React and Angular work. But if the browser wants to run DOM parsing and script execution in parallel, race conditions can arise between the DOM parsing thread and the main thread, which is why DOM parsing must be done on the main thread.

However, stopping DOM parsing when script files are downloaded in the background is completely unnecessary in most cases. Therefore, HTML5 provides us with the async property of script tags. When the DOM parser encounters an external script element with an async attribute, it does not stop parsing when the script file is downloaded in the background. But once the file is downloaded, parsing stops and script is executed.

We also set up a fancy defer property for the Script element, which works in a similar way to the Async property, but unlike the Async property, the script does not execute even after the file has been completely downloaded. Once the parser has parsed all the HTML, meaning that the DOM tree is fully built, all the defer scripts will be executed. Unlike asynchronous scripts, all deferred scripts are executed in the order in which they appear in the HTML document (or DOM tree).

All ordinaryscriptBoth (embedded or external) are parser blocking because they stop DOM building. All asynchronousscriptThe parser is not blocked until download. Once an asynchronousscriptWhen downloaded, it becomes the blocker of the parserscript. However, all of themdeferScripts are non-blocking parsers because they do not block the parser and are executed after the DOM tree is fully built.

In the example above, the parser-blocking. HTML file contains a script that blocks parsing after 30 elements, which is why the browser starts displaying 30 elements, stops DOM parsing, and starts loading the script file. The second script file does not prevent parsing because it has the defer attribute, so it will execute once the DOM tree is fully built.

If we look at the Performance panel, FP and FCP happen as soon as possible (hidden behind the Timings tag) because the browser starts building a DOM tree as soon as it has HTML content, so it can render some pixels on the screen.

LCP happens after 5 seconds because the script blocking the parser has already blocked DOM parsing for 5 seconds (its download time), and when the DOM parser is blocked, only 30 text elements are rendered on the screen, which is not enough to be called maximum content rendering (according to Google Chrome standards). But once the script is downloaded and executed, DOM parsing resumes and a large amount of content is rendered on the screen, causing the LCP event to fire.

Parser-blocking is also called render-blocking because rendering doesn’t happen until the DOM tree is constructed, but those are completely different things, as we’ll see later

Some browsers may include a speculative parsing strategy, where HTML parsing (but not DOM tree building) is mounted into a separate thread so that the browser can read links (CSS), script, IMG, etc., and download these resources earlier.

This is useful in cases where you have three script elements that fit together, but because the DOM parser can’t read the second script element, the browser can’t start downloading the second script until the first one has been downloaded. We can easily solve this problem by using the async tag, but asynchronous scripts are not guaranteed to execute sequentially.

Speculative parsing is called because the browser is making a prediction that a resource will load in the future, so it’s best to load it in the background now. However, if some JavaScript manipulates the DOM, or removes/hides elements with external resources, then the conjecture fails and the files load for nothing.

Each browser has its own specification, so there is no guarantee when or if speculative parsing will occur. However, you can use the element to ask the browser to load some resources in advance.

Render blocking CSS

As we’ve seen, any external resource request does not block the DOM parsing process, other than blocking the parser’s script file. Therefore, CSS (including embedded) does not directly block the DOM parser. Wait, yes, CSS can prevent DOM parsing, but before we do that, we need to understand the rendering process.

The browser engine inside the browser uses the HTML content received from the server as a text document to build the DOM tree. Likewise, it builds the CSSOM tree based on the embedded (and inline) CSS stylesheet content in an external CSS file or HTML.

The DOM and CSSOM trees are both built on the main thread, and they are built simultaneously. Together they make up the Render Tree for printing things on the screen, and the Render Tree is built as the DOM Tree is built.

We’ve already seen that DOM tree generation is incremental, meaning that when the browser reads the HTML, it adds DOM elements to the DOM tree. Not so for the CSSOM tree. Unlike DOM trees, CSSOM trees are not built incrementally and must be built in a specific way.

When the browser finds the

However, things change dramatically when the browser encounters an external stylesheet file. Unlike an external script file, an external stylesheet file is not a parsers blocking resource, so the browser can silently download it in the background and DOM parsing continues.

But unlike HTML files, which are used for DOM building, browsers do not process the stylesheet file content byte by byte. This is because the browser cannot incrementally build the CSSOM tree as it reads CSS content. The reason is that CSS rules at the end of the file may overwrite CSS rules written at the top of the file.

Therefore, if the browser starts incrementing the CSSOM as it parses the stylesheet content, it will result in multiple renders of the render tree, because the style overrides rule will cause the same CSSOM nodes to be updated by new stylesheet files appearing later. When the CSS is parsed, you can see the element style change on the screen, which can be an unpleasant user experience. Because CSS styles are cascading, a rule change can affect many elements.

As a result, the browser does not process the external CSS files step by step, and the CSSOM tree update is done once all the CSS rules in the stylesheet have been processed. After the CSSOM tree is updated, the render tree is updated and rendered to the screen.

CSS is a rendering blocking resource. As soon as the browser requests an external stylesheet, the Render Tree build stops. As a result, the critical render path (CRP) is also stuck and nothing is rendered to the screen, as shown below. However, when the stylesheet is downloaded in the background, the DOM tree is still being built.

Browsers can use the old state of the CSSOM Tree to generate the Render Tree because the HTML is being parsed to Render things on the screen incrementally. But there is a huge drawback. In this case, once the stylesheet is downloaded and parsed and the CSSOM is updated, the Render Tree is updated and rendered on the screen. Now, the Render Tree node generated with the old CSSOM will redraw the new style, which may also cause Flash of Unstyled Content (FOUC), which is very detrimental to the user experience.

Therefore, the browser waits until the stylesheet is loaded and parsed. Once the stylesheet is parsed and the CSSOM is updated, the Render Tree is updated and THE CRP continues to Render the Tree on the screen. For this reason, it is recommended to load all external stylesheets as early as possible.

Let’s imagine a scenario where the browser has started parsing HTML and encounters an external stylesheet file. It will start downloading files in the background, block CRP, and continue DOM parsing. But it encounters a script tag, so it starts downloading external script files in the background and prevents DOM parsing. Now the browser is sitting back and waiting for the stylesheet and script files to download completely.

But this time the external script file is completely downloaded, while the stylesheet is still downloaded in the background. Should the browser execute this script file? Is there any harm in doing so?

As we know, CSSOM provides a high-level JavaScript API to interact with the styles of DOM elements. For example, you can use the elem. Style. BackgroundColor property to read or update a DOM element background color. The style object associated with the ELEm element exposes CSSOM’s API, and there are many other apis that do the same (read the CSS-Tricks article).

When a stylesheet is downloaded in the background, JavaScript can still execute because the main thread is not blocked by the loaded stylesheet. If our JavaScript program accesses the CSS attributes of a DOM element (through the CSSOM API), we get an appropriate value (based on the current state of CSSOM).

But once the stylesheet is downloaded and parsed, resulting in a CSSOM update, our JavaScript now has an outdated CSS value for the element, because the new CSSOM update may have changed the CSS properties of that DOM element. For this reason, it is not safe to execute JavaScript while downloading the stylesheet.

According to the HTML5 specification, a browser can download a script file, but won’t execute it unless all the previous stylesheets have been parsed. When a stylesheet blocks the execution of a script, it is called a script-blocking stylesheet or script-blocking CSS.

In the above example, script-blocking. HTML contains a link tag (for external stylesheets) followed by a script tag (for external JavaScript). The script here downloads very quickly with no delay, but the stylesheet takes 6 seconds to complete. So, although we can see from the network panel that the script is fully downloaded, the browser does not execute it immediately. Only after the stylesheet loads do we see a Hello World message printed by the script.

Just as the async or defer attributes prevent script elements from blocking parsing, external stylesheets can also prevent them from blocking rendering through the Media attribute. Using the media property value, the browser can intelligently decide when to load the stylesheet

DOMContentLoader event for the document

The DOMContentLoaded(DCL) event marks the point in time when the browser has built a complete DOM tree from all available HTML. However, when a DCL event is triggered, many of the factors involved change.

document.addEventListener("DOMContentLoaded".function (e) {
  console.log("DOM is fully parsed!");
});
Copy the code

If we didn’t include any scripts in our HTML, DOM parsing would not be blocked and the DCL would fire as the browser parsed the entire HTML. If we have parse-blocking scripts, the DCL must wait for all parse-blocking scripts to be downloaded and executed.

Things get a little more complicated when the stylesheet is thrown into the page. Even if you don’t have an external script, the DCL will wait until all the stylesheets are loaded. The DCL marks the point at which the entire DOM tree is ready, but access to the DOM is not secure (for style information) until CSSOM is also fully built. Therefore, most browsers wait until all external stylesheets are loaded and parsed.

Script-blocking style sheets obviously delay the DCL. In this case, the DOM tree is not constructed because the script is waiting for the stylesheet to load.

DCL is one of the performance indicators of a website. We should optimize the DCL to make it as small as possible. One of the best practices is to use defer and async tags as much as possible to process script elements so that the browser can do other things while the script downloads in the background. Second, we should optimize script-blocking and render blocking style sheets.

Of the windowloadThe event

We know that JavaScript can prevent DOM tree generation, but the same cannot be said for external stylesheets and files such as images, videos, and so on.

The DOMContentLoaded event marks the point at which the DOM tree is fully constructed and safe to access, and the window.onload event marks the point at which the external stylesheet and files have been downloaded and our Web application has finished downloading.

window.addEventListener( 'load'.function(e) {
  console.log( 'Page is fully loaded! '); })Copy the code

In the example above, there is an external style sheet in the header of the rendering. HTML file that takes about 5 seconds to download. Since it is in the header section, FP and FCP occur 5 seconds later, because the stylesheet prevents rendering of anything below it (that is, it blocks CRP).

After that, we have an IMG element and load an image that takes about 10 seconds to download. So the browser keeps downloading the file in the background and continues parsing and rendering the DOM (because external image resources don’t block the parser or render).

Next, we have three external JavaScript files that take 3s, 6s, and 9s to download, and most importantly, they are not asynchronous. This means that the total load time should be close to 18 seconds, since subsequent scripts will not start downloading until the previous one executes. However, from the DCL events, it seems that our browser adopted a speculative strategy of pre-downloading the script files, so the total load time was close to 9 seconds.

Since the last downloaded file that could affect the DCL was the last script file that took 9 seconds to load (because the stylesheet was already downloaded in 5 seconds), the DCL event occurred in about 9.1 seconds.

One of our external resources is the image file, which is always loading in the background. When it is fully downloaded (which takes 10 seconds), the window’s load event is activated 10.2 seconds later, indicating that the web page (application) is fully loaded.