When I first started learning about Web development, I always had a question — when did the code I was writing actually work? Does the page change every time I change the code? Of course, this was certainly a mistake now. After a while of work and study, the path of code-to-page conversion became clearer in my mind, although the question “What happens between entering the URL and displaying the page?” It’s a platitudinarian question, but I’d like to say it as I see it.
Browser architecture
First started our most familiar friends, Web development cannot leave the browser, when I was in check information had a habit of a lot of TAB, every time I open the task manager can see Chrome outshine others in terms of memory usage, can also see the back of the name of the application of a number in brackets, as shown in the figure below, But the TAB I have open is less than 23, so what’s the rest of the process?
Let’s take a look at a classic diagram that depicts the positions and functions of four processes in Chrome:
- Browser Process: It is responsible for the forward, back, address, bookmarks of the Browser’s TAB, and handles some of the Browser’s underlying operations, such as network requests and file access.
- Renderer Process: The Renderer Process is responsible for the display work within a Tab. Also known as the Renderer Engine.
- Plugin processes: Plugins responsible for controlling the use of web pages
- GPU processes: GPU tasks that handle the entire application
Renderer processes are special. Each option card requires a renderer process, which is the core of web rendering. This process is explained in detail in the next section, and can be viewed in detail in the browser’s process manager:
Due to the frequent multi-browser compatibility and the frequent opening of several browsers at the same time, even without careful comparison, it can be found that Chrome browser has a relatively high memory footprint, while Firefox has a relatively low memory footprint. This is because Firefox’s Tab process and IE’s Tab process both adopt a similar strategy: There are multiple Tab processes, but they don’t have to be one Tab process for each page, one Tab process may be responsible for rendering multiple pages. Chrome, by contrast, operates on a one-page, one-render process, with site isolation. While it is true that the memory footprint is high, this multi-process architecture also has unique advantages:
- Higher fault tolerance. In today’s WEB applications, HTML, JavaScript and CSS are becoming more and more complex. The code running in the rendering engine is frequently bugged, and some of the bugs will directly cause the rendering engine to crash. The multi-process architecture allows each rendering engine to run in its own process, which does not affect each other. When one of the pages crashes and dies, the other pages can still run normally.
- Greater safety and sanboxing. Rendering engines often encounter untrusted or even malicious code on the network. They can use these vulnerabilities to install malicious software on your computer. To address this problem, browsers restrict different permissions for different processes and provide sandbox environments to make them more secure and reliable
- Higher response times. In a single-process architecture, tasks compete with each other for CPU resources, which makes the browser slow to respond. A multi-process architecture avoids this shortcoming.
Web page rendering
Roughly speaking, there are five steps to enter the URL before the page is rendered:
- The DNS query
- A TCP connection
- The HTTP request is the response
- Server response
- Client-side rendering
First of all, if the input is a domain name, the browser will have to find from the hosts file corresponding Settings, nearby if you don’t have access to the DNS server for DNS queries to obtain the IP address of the right, after a TCP connection, through the three-way handshake began after processing HTTP requests to establish a connection, the server receives the request returns a response document, The browser that gets the response document starts rendering the page using the rendering engine.
The rendering engine we’re talking about here is the browser content we often talk about, such as WebKit, Gecko, etc.
Rendering engine
The browser kernel is multithreaded. Threads cooperate with each other to keep in sync under the control of the kernel. A browser usually consists of the following resident threads:
- GUI Render Thread
- Responsible for rendering the browser interface, parsing HTML, CSS, building DOM and RenderObject trees, layout and rendering, etc.
- This thread executes when the interface needs to be repainted or reflow due to an operation
- The GUI rendering thread and the JS engine thread are mutually exclusive. The GUI thread is suspended (frozen) while the JS engine is executing, and GUI updates are kept in a queue until the JS engine is idle and executed immediately.
- JavaScript Engine Threads
- Also known as the JS kernel, it handles JavaScript scripts. (e.g. V8 engine)
- The JavaScript engine thread is responsible for parsing the JavaScript script and running the code.
- The JS engine waits for tasks to arrive in the task queue and then processes them. At any given time, there is only one JS thread running a JS application in a Tab page (the renderer process)
- Also note that the GUI rendering thread and the JS engine thread are mutually exclusive, so if the JS execution takes too long, this will cause the page to render incoherently, resulting in the page rendering load blocking.
- Timer trigger thread
- The thread on which setInterval and setTimeout are located
- The browser timing counter is not counted by the JavaScript engine (because the JavaScript engine is single-threaded, and if the thread is blocked, it will affect the accuracy of the timing).
- Therefore, the timer is timed by a separate thread and the timer is triggered (after the timer is finished, add it to the event queue and wait for execution when the JS engine is idle).
- Event-triggered thread
- Owned by the browser, not the JS engine, and used to control the event loop (understandably, the JS engine itself is too busy, so the browser needs to open another thread to help).
- When the JS engine executes a block of code such as setTimeout (or from other threads in the browser kernel, such as mouse clicks, Ajax asynchronous requests, etc.), the corresponding task is added to the event thread
- When the corresponding event meets the trigger conditions and is triggered, the thread will add the event to the back of the queue to be processed and wait for the JS engine to process it
- Note that due to JS’s single-threaded relationship, events in these pending queues are queued up for the JS engine to process (and are executed only when the JS engine is idle).
- Asynchronous HTTP request thread
- After XMLHttpRequest is connected, a new thread request is opened through the browser
- When a state change is detected, if a callback function is set, the asynchronous thread generates a state change event and puts the callback back into the event queue. This is then executed by the JavaScript engine.
Each of these five threads does its job, but let’s focus on GUI rendering:
Rendering process
- Process the HTML tags and build the DOM tree.
- Process the CSS tags and build the CSSOM tree
- Merge DOM and CSSOM into one render tree.
- Layout according to the render tree to calculate the geometry of each node.
- Draw the individual nodes onto the screen.
1. Construction of DomTree (Document Object Model)
Step 1 (parsing) : The raw HTML bytecode read from the network or disk is converted to characters by setting the charset encoding
The second step (toKEN) : The string is parsed into tokens using the lexer, which indicates whether the current token is a start tag, an end tag, or a text tag, etc.
Step 3 (Generate Nodes and build a DOM tree) : The browser connects Tokens to each other based on the start and end tags recorded in Tokens (Tokens with end tags do not generate Nodes).
2. Build CSSOMTree (CSS Object Model)
When your HTML code encounters the <link> tag, the browser will send a request to get the CSS file that is marked in that tag (using inline CSS can speed up the request by skipping the steps, but you don’t need to lose modularity and maintainability for this speed).
Once the browser gets the data from the external CSS file, it starts building the CSSOM tree just as it was building the DOM tree, and there is no special difference.
Font-size :16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px; font-size:16px Because of the inheritance-like nature of the style, the browser has a rule that CSSOMTree cannot be used until it is fully built, because subsequent properties may override previous Settings. For example, if you add a line of code to the CSS code above, then the 16px set will be overwritten to 12px.
See here, it feels like what is missing? Our pages don’t just contain HTML and CSS, JavaScript is often a big part of the page, and JavaScript is also an important factor that can cause performance problems. Here are a few answers to explain JavaScript in page rendering.
Question: how to handle JS files encountered in the rendering process?
Because JavaScript can manipulate the DOM, if you render the interface at the same time as modifying these element attributes (i.e. the JS thread and the UI thread run at the same time), then the element data obtained before and after the render thread may not be consistent.
Therefore, to prevent unexpected rendering results, the browser sets up a mutually exclusive relationship between the GUI rendering thread and the JS engine. The GUI thread is suspended while the JS engine is executing, and GUI updates are kept in a queue until the JS engine thread is idle and executed immediately.
That is, if the HTML parser encounters JavaScript while building the DOM, it pauses the DOM building, hands control over to the JavaScript engine, and when the JavaScript engine is finished, the browser picks up the DOM building from where it left off.
Question: Why do browsers sometimes report errors when accessing the DOM in JS?
If the script tag is not set async/defer, the loading process is to download and execute the entire code before the DOM tree is completely created. If JavaScript attempts to access a DOM element after a script tag, the browser will throw an error that the DOM element cannot be found.
Question: When it comes to page performance optimization, it is often emphasized that CSS files should be introduced in front of HTML documents, and JS files should be introduced in the back. What is the reason for doing this?
Originally, DOM build and CSSOM build are two processes. Assuming that the DOM build takes 1s, the CSSOM build also takes 1s, and a link tag is found when the DOM is built for 0.2s, the time required to complete the operation is about 1.2s, as shown in the figure below:
However, JS can also modify CSS styles to affect the final result of a CSSomTree. As mentioned earlier, an incomplete CSSomTree should not be used.
Question: What happens if JS tries to manipulate CSS styles before the browser has finished downloading and building CSSomTree?
We inserted a piece of JS code in the middle of the HTML document, and found the script tag in the middle of the DOM construction. Assuming that this JS code only takes 0.0001 seconds, the time required to complete the operation will be:
So if we put CSS first and JS last, the build time will be:
As can be seen from this, even though we just inserted a small piece of JS code that only runs 0.0001s, the timing of the introduction will seriously affect the construction speed of DomTree.
In short, introducing a large number of dependencies between DOM, CSSOM, and JavaScript execution can cause browsers to experience significant delays in processing render resources:
- When the browser encounters a Script tag, the DomTree build is paused until the script finishes executing
- JavaScript can query and modify DomTree and CSSOMTree
- JavaScript does not execute until the CSSOM is built
- The placement of the script within the document is important
3. Render tree construction
Once we have generated the DOM tree and CSSOM tree, we need to combine the two trees into a render tree.
- Each node on the Render tree is called: renderObject.
- RenderObject is almost one-to-one with a DOM node. When a visible DOM node is added to the DOM tree, the kernel generates a corresponding RenderObject for it to add to the Render tree.
-
Where, visible DOM nodes do not include:
- Some nodes will not appear in the render output (
<html><script><link>
… .). “Will be ignored. - Nodes hidden by CSS. The span node in the figure above, for example, is ignored when RenderObject is generated because a CSS explicit rule sets the display:none property on that node.
- Some nodes will not appear in the render output (
- The Render tree is the bridge between the browser’s typography engine and the rendering engine. It is the output of the typography engine and the input of the rendering engine.
Layout of 4.
So far, the browser has figured out which nodes are visible and their information and style, and then it needs to figure out the exact location and size of those nodes within the device viewport, a process we call “layout.”
The final output of the layout is a “box model” that converts all relative measurements into absolute pixels on the screen.
5. Apply colours to a drawing
When the Layout Layout event is completed, the browser will immediately issue Paint Setup and Paint events to draw the rendering tree into pixels. The rendering time required is proportional to the complexity of the CSS style. After the rendering is completed, the user can see the final rendering effect of the page.
conclusion
It may take only a second or two to send a request to a page and get the rendered page, but the browser has already done a lot of the work described above. To summarize the entire process of the browser’s critical rendering path:
- Process HTML tag data and generate a DOM tree.
- Process the CSS tag data and generate the CSSOM tree.
- Merge the DOM tree with the CSSOM tree to produce a rendering tree.
- Start the layout by traversing the render tree and calculating the position of each node.
- Draw each node to the screen.
Issues related to
Defer and async
We also mentioned a small point above: when the script tag does not set the async/defer property, the loading process downloads and executes the entire code. How would these two properties be different if they were set?
The blue line represents JavaScript loading; The red line represents JavaScript execution; The green line represents HTML parsing.
- Case 1
<script src="script.js"></script>
Without defer or async, the browser loads and executes the specified script immediately, which means that it does not wait for a document element to be loaded later, it loads and executes as it reads it.
- Case 2
<script async src="script.js"></script>
(Asynchronous download)
The async property represents the JavaScript introduced by asynchronous execution, and the difference from defer is that if it’s already loaded, execution will start — either at this point in the HTML parsing phase or after DOMContentLoaded is triggered. Note that JavaScript loaded in this way will still block the load event. In other words, async-script may be executed before or after DOMContent-Loaded fires, but it must be executed before Load fires.
- Case 3
<script defer src="script.js"></script>
(Delayed execution)
The defer attribute indicates that the HTML parsing does not stop when the incoming JavaScript is loaded, and the two processes run in parallel. After the entire document is resolved and the defer-script has been loaded (in no particular order), all the JavaScript code loaded by defer-script is executed, and the DOMContentLoaded event is fired.
There are two differences between deferred and a regular script:
- The JavaScript file is loaded without blocking the parsing of the HTML, and the execution phase is put until the parsing of the HTML tag is complete.
- When loading multiple JS scripts, async is loaded out of order, and defer is loaded in order.
Reflow and repaint
We know that when the page is generated, it will be rendered at least once. It will continue to be re-rendered as the user accesses it. A rerender will repeat step 4 (reflow)+ step 5 (redraw) or only step 5 (redraw) from the image above.
- Redraw: When elements in a Render Tree need to be updated with attributes that affect the appearance and style of the element rather than the layout, such as background-color.
- Backflow: When part (or all) of a Render Tree needs to be rebuilt due to changes in element size, layout, hiding, etc
Reflow is bound to redraw, and redraw does not necessarily cause reflow. Redraw and reflow occur frequently when we set node styles, and can significantly affect performance. The cost of reflux is much higher than that of reflux, and changing the children of the parent node is likely to cause a series of reflux of the parent node.
Common properties and methods of causing backflow
Any operation that changes the element’s geometry (its position and size) triggers reflow to add or remove visible DOM elements:
- Element size changes — margin, fill, border, width, and height
- Content changes, such as the user entering text in the Input box
- Browser window size changes — when a resize event occurs
- Calculate the offsetWidth and offsetHeight properties
- Sets the value of the Style property
How to reduce backflow and redraw
- Transform instead of top
- Use visibility to replace display: none, because the former will only cause a redraw and the latter will cause reflow (changed layout)
- Do not use a table layout, as a small change can cause the entire table to be rearranged
- The speed at which the animation is implemented. The faster the animation is, the more times it reflows, or you can optionally use requestAnimationFrame
- CSS selectors match from right to left to avoid excessive node hierarchy
- Set nodes that frequently redraw or reflow as layers to prevent the rendering behavior of that node from affecting other nodes. For the video tag, for example, the browser will automatically turn this node into a layer.
Why is DOM slow to manipulate
DOM belongs to the rendering engine, and JS belongs to the JS engine. When we manipulate the DOM through JS, which involves communication between two threads, there is bound to be some performance loss. If you manipulate the DOM more than once, you’ll be communicating between threads all the time, and manipulating the DOM can also cause redraw backflow, which can lead to performance problems.
Js uses a virtual DOM. If there are 10 updates to the DOM in one operation, the virtual DOM will not immediately manipulate the DOM. Instead, the diff content of the 10 updates will be stored in a local JS object. Finally, the JS object will be attch to the DOM tree once, and then the subsequent operations, to avoid a lot of unnecessary calculation.