Do JavaScript and CSS build and render DOM trees
Let’s make a summary and then move on to specific analysis:
CSS does not block DOM tree parsing, but it does affect JavaScript,
JavaScript prevents parsing the DOM tree,
Eventually CSS (CSSOM) will affect the rendering of DOM trees, and ultimately the generation of layout trees.
CSS does not block DOM parsing, but it blocks DOM rendering, or more precisely, CSS blocks render tree generation, which in turn blocks DOM rendering. JS blocks DOM parsing, CSS blocks JS execution encountered by the browser
Chrome has made a lot of improvements, but one of the main ones is pre-parsing. When the rendering engine receives the byte stream, it will start a pre-parsing thread to analyze JavaScript, CSS and other relevant files contained in the HTML file. After parsing the relevant files, the pre-parsing thread will download these files in advance. (During the pre-parsing process after receiving the HTML data, the HTML pre-parser recognizes that there are CSS files and JavaScript files that need to be downloaded, and then initiates a download request for both files.)
It is important to note that the downloading process of the two files overlaps, so the download time is calculated according to the file with the oldest.
Either the CSS file or the JavaScript file arrives first, wait until the CSS file is downloaded and CSSOM is generated, then execute the JavaScript script, and finally continue to build the DOM, build the layout tree, and draw the page.
2. What is DOM and how is a DOM tree generated
1. What is DOM
The byte stream of HTML files sent from the network to the rendering engine is not directly understood by the rendering engine, so it is converted into an internal structure that the rendering engine can understand, which is DOM. DOM provides a representation of the structure of an HTML document.
DOM tree structure diagram:
In the rendering engine, DOM serves three purposes:
1. From a page perspective, the DOM is the basic data structure from which pages are generated. 2. From the perspective of JavaScript script, DOM provides an interface for JavaScript script operation. Through this interface, JavaScript can access the DOM structure, thus changing the structure, style and content of the document. 3. From a security perspective, the DOM is a line of security, and unsafe content is shut out during DOM parsing.
In short, the DOM is the internal data structure that expresses HTML, connects Web pages to JavaScript scripts, and filters out unsafe content.
2. How is the DOM tree generated
Inside the rendering engine, there is a module called HTMLParser, whose responsibility is to convert HTML stream into DOM structure. So here we need to understand how the HTML parser works. Before I get into HTML parsers, I want to explain a question that has been asked many times in the comments section: Does an HTML parser wait for the entire HTML document to load, or does it parse as the HTML document loads? The HTML parser does not wait for the entire document to load, but rather parses as much data as the network process loads.
What is the detailed process? After receiving the response header, the web process will determine the type of the file based on the content-Type field in the request header. For example, if the value of the content-Type is “text/ HTML”, the browser will determine that this is an HTML file and select or create a renderer process for the request. Once the renderer process is ready, the network and renderer create a shared data pipeline. The network process receives the data and places it in the pipeline, while the renderer constantly reads the data from the other end of the pipeline and “feeds” the data to the HTML parser. You can think of this pipe as a “water pipe” into which the bytes received by the network process are poured like water, and on the other end of this pipe is the renderer’s HTML parser, which dynamically receives the bytes and parses them into the DOM.
With that out of the way, it’s time to talk more about the DOM generation process.
Earlier we said that code is sent over the network as a byte stream, so how is the byte stream subsequently converted into the DOM? You can refer to the following image:
Byte stream is converted to DOM
As you can see from the figure, there are three stages for byte stream conversion to DOM.
1. Byte stream conversion to DOM requires three phases
In the first phase, byte streams are converted into tokens through a word splitter.
In front of the 14 | compiler and interpreter: V8 is how to perform a JavaScript code?” As mentioned in this article, the first step in V8’s JavaScript compilation process is to do lexical analysis, breaking the JavaScript into tokens. The same is true for parsing HTML. You need to convert the byte stream into a Token through a tokenizer, including Tag Token and text Token. The tokens generated by lexical analysis of the HTML code above are as follows:
As can be seen from the figure, the Tag Token is divided into StartTag and EndTag. For example, StartTag and EndTag are respectively blue and red blocks in the figure, and the text Token corresponds to the green block.
As for the second and third subsequent phases, which occur simultaneously, the Token needs to be resolved into a DOM node and the DOM node added to the DOM tree.
The HTMLParser maintains a Token stack structure, which is mainly used to calculate parent-child relationships between nodes. Tokens generated in the first stage are pushed into this stack in sequence. The specific processing rules are as follows:
If the StartTag Token is pushed onto the stack, the HTML parser creates a DOM node for the Token and adds the node to the DOM tree, its parent being the node generated by the next element in the stack. If the tokenizer resolves to be a text Token, a text node is generated and added to the DOM tree. The text Token does not need to be pushed onto the stack. Its parent node is the DOM node corresponding to the current top Token. If the parser returns an EndTag, such as an EndTag div, the HTML parser checks to see if the element at the top of the Token stack is a StarTag DIV. If so, it pops the StartTag div from the stack to indicate that the div element has been parsed.
The new tokens generated by the tokenizer are pushed and pushed, and the whole parsing process continues until the tokenizer has split all the byte streams.
2.DOM tree generation process case
In order to understand the whole process more intuitively, let’s analyze the DOM tree generation process step by step with a piece of HTML code (below).
<html>
<body>
<div>1</div>
<div>test</div>
</body>
</html>
Copy the code
This code is passed as a byte stream to the HTML parser, which processes the word splitter. The first Token parsed is StartTag HTML. The parsed Token is pushed onto the stack, and an HTML DOM node is created and added to the DOM tree.
Note that when the HTML parser starts working, it creates an empty DOM structure with root document and pushes a StartTag Document Token to the bottom of the stack. The first StartTag HTML Token parsed by the tokenizer is then pushed onto the stack and an HTML DOM node is created and added to the document, as shown below:
1. State when parsing to StartTag HTML
Then the StartTag body and StartTag div are resolved according to the same process, and the status of their Token stack and DOM is shown in the figure below:
2. Parse to StartTag div
The rendering engine creates a text node for this Token and adds the Token to the DOM. Its parent node is the node corresponding to the top element of the current Token stack, as shown below:
Parse the state of the first text Token
Next, the parser parses the first EndTag div. At this point, the HTML parser determines whether the element at the top of the stack is a StartTag div. If so, it pops the StartTag div from the top of the stack, as shown below:
4. Schematic diagram of element popup Token stack
In accordance with the same rules, parsing all the way, the final result is as follows:
5. Final analysis results
From the introduction above, you have a good idea of how the DOM is generated. However, in a real production environment, the HTML source files contain CSS and JavaScript as well as images, audio, and video files, so the process is far more complex than the above demonstration. But now that we understand this simple Demo generation process, we can move on to more complex scenarios.
The impact of JavaScript on DOM tree building and rendering
The impact of JavaScript on DOM tree construction and rendering is divided into three types:
The script is in an HTML page
Case study:
<html>
<body>
<div>1</div>
<script>
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'
</script>
<div>test</div>
</body>
</html>
Copy the code
I inserted a JavaScript script between the two divs, and the parsing of this script is a little different.
From the previous analysis of the DOM generation process, we already know that when a script script tag is parsed, its DOM tree structure looks like this:
The HTML parser pauses, the JavaScript engine steps in, and executes this script in the script tag. Because this JavaScript script modifies the contents of the first DIV in the DOM, after executing this script, The contents of the div node have been changed to time.geekbang. After the script is executed, the HTML parser resumes parsing and continues parsing until the final DOM is generated.
The above process should be fairly straightforward, but in addition to embedding JavaScript scripts directly into the page, we often need to introduce JavaScript files into the page. This parsing process is slightly more complicated, as in the following example:
2. Introduce JavaScript files into HTML pages
Case study:
//foo.js
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'
<html>
<body>
<div>1</div>
<script type="text/javascript" src='foo.js'></script>
<div>test</div>
</body>
</html>
Copy the code
The function of this code is the same as the previous one, except that I have changed the embedded JavaScript script to be loaded through a JavaScript file. The entire execution process is the same. When the JavaScript tag is executed, the parsing of the DOM is suspended and the JavaScript code is executed, but the JavaScript code needs to be downloaded before executing the JavaScript. It is important to focus on the download environment because DOM parsing is blocked during the download of JavaScript files, and downloading is often time consuming, affected by network environment, JavaScript file size, and so on.
But Chrome has made a lot of improvements, and one of the main ones is pre-parsing. When the rendering engine receives the byte stream, it will start a pre-parsing thread to analyze JavaScript, CSS and other relevant files contained in the HTML file. After parsing the relevant files, the pre-parsing thread will download these files in advance.
Back to DOM parsing, we know that introducing JavaScript threads will block the DOM, but there are related strategies to avoid this, such as using CDN to speed up the loading of JavaScript files and reduce the size of JavaScript files. Alternatively, if there is no DOM-related code in the JavaScript file to manipulate, you can set the JavaScript script to load asynchronously and mark the code with async or defer as follows:
<script async type="text/javascript" SRC ='foo.js'></script> or <script defer type="text/javascript" src='foo.js'></script>Copy the code
Async: Scripts are loaded in parallel and executed immediately after the loading is complete. The execution time is uncertain. HTML parsing may still be blocked and the execution time is before the load event is sent
Defer: The scripts are loaded in parallel and executed in the order they were loaded after the HTML parsing is complete, before the DOMContentLoaded event is delivered
3. CSS styles in HTML pages
Case study:
//theme.css div {color:blue} <html> <head> <style src='theme.css'></style> </head> <body> <div>1</div> <script> let div1 = document. GetElementsByTagName (' div ') [0] div1. The innerText = 'time. Geekbang' / / need DOM div1. Style. The color = 'red' / / need CSSOM </script> <div>test</div> </body> </html>Copy the code
This is an important passage:
In this example, the JavaScript code appears with the statement div1.style.color = ‘red’, which is used to manipulate CSSOM, so all CSS styles above the JavaScript statement need to be parsed before executing the JavaScript. Therefore, if the code references an external CSS file, it needs to wait for the external CSS file to be downloaded and the CSSOM object to be parsed before executing JavaScript.
The JavaScript engine doesn’t know if JavaScript is manipulating CSSOM until it parses JavaScript, so the rendering engine will perform a CSS file download whenever it encounters a JavaScript script, regardless of whether the script is manipulating CSSOM. Parse the operation, then execute the JavaScript script.
So JavaScript scripts rely on stylesheets, which adds another blocking process.
Through the above analysis, we know that JavaScript will block DOM generation, and style files will block JavaScript execution, so in the actual project need to focus on JavaScript files and style sheet files, improper use will affect the page performance.
Note:
As an additional note, the rendering engine also has a security check module called XSSAuditor, which is used to check for lexical security. After the toener parses the tokens, it checks whether the modules are secure, such as referencing external scripts, complying with CSP specifications, and making cross-site requests. XSSAuditor intercepts the script or the download task if non-conforming content occurs. We will cover the details in the security module later, so we don’t need to go over them here.
The DOMContentLoaded event is triggered after the DOM parsing of the page is complete.
4. The impact of CSS on DOM tree building and rendering
1. CSS from the perspective of the rendering pipeline
Let’s look at the simplest rendering flow:
Case study:
//theme.css
div{
color : coral;
background-color:black
}
<html>
<head>
<link href="theme.css" rel="stylesheet">
</head>
<body>
<div>geekbang com</div>
<script>
console.log('time.geekbang.org')
</script>
<div>geekbang com</div>
</body>
</html>
Copy the code
These two pieces of code are composed of CSS files and HTML files respectively. Let’s analyze the rendering pipeline when opening this HTML file. You can refer to the following rendering pipeline schematic diagram:
A page rendering pipeline with CSS
Above we analyze the rendering pipeline of this page file.
The first is to make a request from the main page, which can be a renderer or a browser process, and is sent to the web process for execution. After the network process receives the returned HTML data, it sends it to the renderer process, which parses the HTML data and builds the DOM. Note that there is an idle time between requesting HTML data and building the DOM, and this idle time can become a bottleneck for page rendering.
As we mentioned, when the renderer receives a stream of HTML files, it starts a preparse thread. If it encounters a JavaScript file or CSS file, the preparse thread will download the data in advance. For the above code, the pre-parsing thread will parse an external theme.css file and initiate a download of theme.css. There is also a free time here that you should notice. After the DOM is built and before the theme. CSS file is downloaded, the rendering line has nothing to do because the next step is to compose the layout tree. So you need to wait for the CSS to finish loading and parse into CSSOM.
Many of you must be wondering why the rendering assembly line needs CSSOM.
Like HTML, a rendering engine cannot understand the content of a CSS file directly, so it needs to be parsed into a structure that the rendering engine can understand, which is CSSOM. Like DOM, CSSOM serves two purposes. The first is to provide JavaScript with the ability to manipulate style sheets, and the second is to provide basic style information for composing layout trees. This CSSOM is embodied in the DOM as Document. StyleSheets. You can look up the specific structure of the relevant materials, here I will not introduce more, you know how CSSOM two functions are on the line.
With DOM and CSSOM, next the tree is synthetic layout, we in front of the process (top) : 05 | rendering HTML, CSS, and JavaScript files, how become page?” This article explained the layout tree construction process, here let’s briefly review. Once both the DOM and CSSOM are built, the rendering engine constructs the layout tree (some versions are called rendering trees).
The structure of the layout tree basically copies the structure of the DOM tree, except that elements in the DOM tree that do not need to be displayed are filtered out, such as display: None elements, head tags, script tags, and so on. After copying the basic layout tree structure, the rendering engine selects the corresponding style information for the corresponding DOM element, a process known as style calculation. Once the style calculation is complete, the rendering engine also needs to calculate the geometric position of each element in the layout tree, which is called calculating the layout. The final layout tree is built through style calculation and computed layout. After that, it’s time for the subsequent drawing operations.
These are some of the main processes that involve CSS in the rendering process.
Add JavaScript code inside the body tag
Case study:
//theme.css
div{
color : coral;
background-color:black
}
<html>
<head>
<link href="theme.css" rel="stylesheet">
</head>
<body>
<div>geekbang com</div>
<script>
console.log('time.geekbang.org')
</script>
<div>geekbang com</div>
</body>
</html>
Copy the code
This code is a little modified version of the initial code, adding a simple JavaScript inside the body tag. With JavaScript, the rendering pipeline is a little different, as shown here:
A page rendering pipeline with JavaScript and CSS
So let’s combine this picture to analyze the page rendering pipeline containing external CSS files and JavaScript code. In the last article, we mentioned that in the process of parsing DOM, if JavaScript scripts are encountered, then we need to suspend DOM parsing to execute JavaScript. It is possible for JavaScript to modify the DOM in its current state.
But before a JavaScript script can be executed, if the page contains references to external CSS files or has CSS content built in via the style tag, the rendering engine needs to convert that content to CSSOM because JavaScript has the ability to modify CSSOM. So you need to rely on CSSOM before you can execute JavaScript. This means that CSS can block DOM generation in some cases.
3. The body contains JavaScript external reference files
Case study:
//theme.css
div{
color : coral;
background-color:black
}
//foo.js
console.log('time.geekbang.org')
<html>
<head>
<link href="theme.css" rel="stylesheet">
</head>
<body>
<div>geekbang com</div>
<script src='foo.js'></script>
<div>geekbang com</div>
</body>
</html>
Copy the code
HTML files contain CSS external references and JavaScript external files. What is their rendering pipeline? Please refer to the picture below:
A rendering pipeline for pages containing JavaScript files and CSS files
As can be seen from the figure, in the pre-parsing process after receiving HTML data, the HTML pre-parser recognizes that CSS files and JavaScript files need to be downloaded, and then initiates the download request of these two files at the same time. It should be noted that the downloading process of these two files overlaps. So the download time is based on the oldest file.
Either the CSS file or the JavaScript file arrives first, wait until the CSS file is downloaded and CSSOM is generated, then execute the JavaScript script, and finally continue to build the DOM, build the layout tree, and draw the page.
5. Factors affecting page display and optimization strategies
Why did we spend so much text analyzing the rendering pipeline? The main reason is that the rendering pipeline affects the speed of the first page display, and the speed of the first page display directly affects the user experience. Therefore, the purpose of our analysis of the rendering pipeline is to find out some factors that affect the first screen display, and then make some targeted adjustments based on these factors.
Start with the URL request and display the content of the page for the first time
1, a phase, such as after the request is sent, to submit the data phase, at this time the page display is still the previous page content. About submitting data you can refer to the front the 04 | navigation process: from the input URL to the page display, what happened in this time?” This article. 2. In the second stage, after submitting the data, the renderer creates a blank page, which is often referred to as the parsing white screen, waits for the CSS and JavaScript files to load, generates CSSOM and DOM, synthesizes the layout tree, and finally goes through a series of steps to prepare the first rendering. 3. The third stage, after the first rendering, is the generation stage of the complete page, and then the page is drawn bit by bit.
The factors that affect the first stage are mainly network or server processing, which we have already covered in the previous article and will not continue to analyze here. As for the third stage, we will analyze it in a subsequent article, so we won’t introduce it here.
Now let’s focus on the second phase, where the main problem is the white screen time. If the white screen time is too long, the user experience will be affected. In order to shorten the white screen time, let’s analyze the main tasks of this stage one by one, including parsing HTML, downloading CSS, downloading JavaScript, generating CSSOM, executing JavaScript, generating layout tree, and drawing a series of operations.
Common bottlenecks are CSS file downloads, JavaScript file downloads, and JavaScript execution. Therefore, in order to shorten the blank screen duration, the following strategies can be adopted:
1. Remove these two types of file downloads by inlining JavaScript and CSS so that once the HTML file is retrieved, the rendering process can begin directly. 2. Inlining is not suitable for all situations, so you can minimize the file size by removing unnecessary comments and compressing JavaScript files with tools like 3, 3, webpack, etc. You can also sync or defer JavaScript tags that don’t need to be used in the HTML parsing phase. 4. For large CSS files, you can use media to query their properties and split them into multiple CSS files for different purposes. In this way, specific CSS files can be loaded only in specific scenarios.
The above strategies can shorten the duration of the white screen display, but in actual projects, there are always a variety of situations, these strategies can not be arbitrarily quoted, so it is necessary to adjust the best solution according to the actual situation.
Reference article:
DOM trees: How does JavaScript affect DOM tree building?
Time.geekbang.org/column/arti…
2. Rendering pipeline: How does CSS affect the white screen time when first loading?
Time.geekbang.org/column/arti…
3. Whether JS and CSS block DOM rendering and parsing
Juejin. Cn/post / 697394…
4. Does CSS block DOM parsing?
Mp.weixin.qq.com/s?__biz=Mzg…
Consider:
1. What is the loading order of CSS and JS files during pre-parsing?
2. Does the browser have a mechanism for rendering pages as quickly as possible? For example, sometimes open a web page slowly, slowly after the display of the style of the page, it is obvious that the CSS has not loaded the build, but still see a page out, but the style is a bit messy.
3. Is there any point in optimizing if script is in place so it doesn’t block rendering?
4. At what stage will async and defer be executed
Do DOM tree and Render Tree build at the same time or do you build render Tree after both DOM Tree and CSSOM are built?
6, pre-parsing, download JS file process, will block DOM tree parsing?