From the input url to the web page content display around the course, and then in front of combed article browser and multi-process architecture continues to comb, here according to the specific content of the browser rendering, main sequence from the browser process, run to the browser kernel, to the rendering thread, to JS engine threads, and then to the mechanism of JS event loop. As mentioned in the previous article:

  • The browser is a multi-process architecture, in which the renderer is responsible for page rendering, JS foot execution, page event processing, etc
  • The process from entering url to displaying webpage content is divided into two parts in order: network resource request and browser rendering. The rendering process is responsible for the browser rendering process, that is, rendering the requested resources. Now let’s look at how the rendering process works, which is how the browser kernel works.

Browser rendering process

The browser renderer is a multithreaded model

The browser renderer process is a multithreaded model, consisting mainly of the following thread types

  1. GUI rendering thread
    • Parsing HTML, CSS, building DOM trees, building Render trees, layout and drawing, etc
    • Interface Repaint or reflow
  2. JS engine thread
    • Working with JavaScript programs
    • The JS engine waits for tasks to arrive in the task queue and then processes them
  3. Event-triggered thread When the JS engine executes a code block such as setTimeOut, or something else such as an asynchronous network request, a mouse click, etc., the corresponding task is added to the event thread. When the corresponding event that meets the trigger condition is triggered, the thread will add the event to the event queue to be processed when the JS engine is idle.
  4. Timer thread The thread where setInterval and setTimeout reside. When the timing is complete, the corresponding event is added to the event queue for processing when the JS engine is idle.
  5. The asynchronous network request thread processes the network request. If a callback function is set, the asynchronous thread generates a state change event and places the callback into the event queue. Wait for the JS engine to be idle.

The GUI rendering thread and the JS engine thread are mutually exclusive, and the GUI rendering thread is suspended when the JS engine thread executes. This is because JavaScript is DOM manipulable, and if you render the interface while modifying these element attributes (i.e., the JS thread and the UI thread are running at the same time), the element data obtained before and after the render thread may not be consistent.

When a script tag is encountered when parsing a web page, that is, there is JS code, the rendering process will save the GUI update in a queue, and then let the JS engine thread to process the JS code. When the JS program is finished and the JS engine is idle to block the thread, the GUI thread will start working again, and the GUI update will be executed immediately. So javascript scripts in web pages block rendering of web pages. If JS runs for too long, it will render the page incoherently.

Let’s take a look at the GUI rendering thread’s rendering flow, the key rendering path.

Rendering process

Critical Rendering Path is the sequence of steps a browser takes to convert HTML, CSS, and JavaScript into the actual pixels displayed on the screen.

The render path has five steps: Build DOM -> build CSSOM -> Build render tree -> Layout -> Draw

  • The browser takes the HTML and starts building the DOM (Document Object Model).
  • Get CSS and build CSSOM (CSS Object Model – CSS Object Model).
  • Combine DOM with CSSOM to create a Render Tree.
  • Layout, find out where all the content is on the page.
  • Painting. The browser starts painting pixels on the screen.

Parsing HTML elements to build a DOM tree

  • Tokenization A character string is converted into a token to identify whether the current token is a start label, end label, or text, which is used to maintain the relationship between nodes

  • Generate node objects Generate tokens while consuming tokens to generate node objects. Tokens with end tags do not create node objects. The node object contains all the attributes of the node
  • Generating a DOM tree After all the tokens have been generated and consumed, we have a complete DOM tree














Parse the CSS and build the CSSOM tree

There are two ways to introduce CSS styles into HTML: inline and inline.


The loading speed of CSS and the speed of building CSSOM will directly affect the first screen rendering speed. Because browser rendering requires the Render Tree, which requires the CSSOM tree, the loading of the stylesheet will block the rendering of the page. If an external stylesheet is being downloaded, then even if the HTML has been downloaded, It also waits for the external stylesheet to download and parse before building the Render Tree

So CSS header, can improve the performance of the page. Build early render early.

Build a Rendering Tree

Create Rendering Tree by DOM Tree and CSS Rule Tree. Rendering Tree has style information for each node.

layout

painting

JS with key render paths

Javascript scripts are often executed on web pages. This makes the rendering path of the page a lot more complicated. Attached JS code running mechanism

Case 1- Plain Script (synchronous download and execution)

<script src="script.js"></script>
Copy the code

When parsing a web page, if you encounter aScript tag without defer or async attributes, you will wait for CSSOM to finish downloading and building, and then execute JS. After JavaScript is finished, you will continue building DOM.

Thus, JS does not just block DOM building, it causes CSSOM to block DOM building as well. Typically, DOM and CSSOM are built in parallel.

This is because JavaScript can not only change the DOM, it can also change styles, which means it can change CSSOM. Because incomplete CSSOM cannot be used, JavaScript must get the full CSSOM when executing JavaScript if it wants to access it and change it. As a result, if the browser has not finished downloading and building CSSOM, and we want to run the script at this point, the browser will delay script execution and DOM building until it has finished downloading and building CSSOM. That is, in this case, the browser downloads and builds CSSOM, then executes JavaScript, and finally continues to build the DOM.

That is, if you want to render the first screen faster, you should not load JS files on the first screen, which is why it is recommended to place the script tag at the bottom of the body tag. This doesn’t mean script tags have to be at the bottom, though, because you can add defer or async attributes to script tags (the difference between the two is described below).

Case 2- Script with Async property (asynchronous download)

<script async src="script.js"></script>
Copy the code

Used for asynchronous download of script files, download immediately explain the execution of the code. However, JavaScript loaded this way still blocks the load event. In other words, async-script may be executed before or after DOMContentLoaded is fired, but must be executed before Load is fired.

The difference with defer is that

  • If it is already loaded, execution starts
  • When loading multiple JS scripts, async loads in a non-sequential manner, and the one downloaded first executes first, while defer loads sequentially and executes sequentially.

The DOMContentLoaded event is triggered to represent that the original HTML is fully loaded and parsed, without waiting for CSS, JS, and images to load. The Load event is triggered to indicate that the DOM, CSS, JS, and images in the page have all been loaded.

Case 3- Script with the defer attribute (deferred execution)

<script defer src="script.js"></script>
Copy the code

Use to start a new thread to download the script file and execute the script after the document has been parsed. Defer differs from regular script in two ways:

  • Loading JavaScript files does not block HTML parsing
  • The execution phase is put after the HTML tag has been parsed

The defer attribute represents delayed execution of the imported JavaScript, meaning that the HTML does not stop parsing when the JavaScript loads, and the two processes are parallel. After the entire Document has been parsed and deferred -script has loaded (in no particular order), all the JavaScript code loaded by deferred -script is executed and the DOMContentLoaded event is triggered.

Page redraw and reflow

redraw

When some elements in the Render tree need to update their properties, such as background-color, which only affect the appearance and style of the elements, but do not affect the layout, this process is called repaint

  • When the visibility of an element’s appearance changes, it does not affect the layout
  • Backflow causes redrawing

backflow

When part (or all) of a Render Tree needs to be rebuilt due to changes in element size, layout, show/hide, etc., this process is called reflow. At least one backflow occurs the first time a page loads. Backflow timing

  • Page render initialization.
  • Resize the window.
  • Change the font, such as the default font for web pages.
  • Add or remove stylesheets.
  • Content changes, such as changes in text or image size, resulting in changes in the width and height of calculated values.
  • Activate CSS pseudo-classes, such as hover
  • Manipulate the class property.
  • Scripts manipulate the DOM, adding, deleting, or modifying the DOM
  • Node, element size changes — margins, padding, borders, width, and height.
  • Calculate the offsetWidth and offsetHeight properties.
  • Sets the value of the style property.

When rewinding, the browser invalidates the affected portion of the Render Tree and reconstructs that portion of the render tree. When rewinding is complete, the browser redraws the affected portion to the screen, a process called redrawing. Therefore, backflow must cause redrawing, and redrawing does not necessarily cause backflow.

Reflow costs much more than Repaint. Every node in a DOM Tree has a reflow method, and a node’s reflow is likely to result in reflows for its children, or even its parents, and siblings.

Optimizing the Render path

  • Common methods to optimize network requests are: DNS Lookup, reduce redirection, avoid JS, CSS blocking, parallel requests, code compression, caching, load on demand, front-end modular
  • Try to avoid too long JS execution time, which will cause the page rendering to be inconsistent, resulting in the page rendering load blocking feeling

Page performance analysis indicators

Page rendering includes jump from browser, cache check, and then DNS, TCP connection, and then initiate the main document request, and then receive the last byte, and then the browser starts to download CSS, JS, images, and finally page rendering and interactive response. This process has a series of node metrics that can be used to analyze the time spent on the page at each stage.

document.readyState

The document.readyState property describes the loading state of the document

  • Uninitialized has not started loading yet

  • The “loading” document is still loading.

  • The “interactive” document has been parsed, the “loading” state has ended, and DOM elements can be accessed. But sub-resources such as images, stylesheets, and frames are still being loaded.

  • The “complete” document and all child resources have finished loading. The event representing the load state is about to be triggered.

conclusion

  • The render path has five steps: Build DOM -> build CSSOM -> Build render tree -> Layout -> Draw
  • GUI rendering threads are mutually exclusive with JS engine threads
  • The loading speed of CSS and the speed of building CSSOM will directly affect the first screen rendering speed.
  • JS doesn’t just block DOM building, it causes CSSOM to block DOM building as well.
  • CSS at the top and JS at the bottom can improve page performance

The resources

Critical path rendering optimization for front-end performance optimization

What you don’t know about browser rendering

“Performance Optimized” first screen time from 12.67s to 1.06s, how did I do it?