foreword

This article is a translation of Onion 's " inside look at modern web browser " series by Mario Kosaka . The translation here does not refer to literal translation, but to express what the author wants to express based on personal understanding, and will try to add some relevant content to help everyone understand better.

What's happening inside the rendering process

This article is the third in a four-part series exploring the inner workings of Chrome . Earlier we discussed Chrome's multi-process architecture and what happened to the navigation process . In this article, we'll take a peek at what's going on in the renderer process when it renders the page.

The rendering process affects many aspects of Web performance. There are so many things that happen when the page is rendered that this article can only give a general introduction. If you want to learn more about this, the Performance section of Web Fundamentals has a lot of resources to check out.

The rendering process processes the page content

The renderer process is responsible for everything that happens inside the label ( tab ). Inside the rendering process, the main thread ( main thread ) handles most of the code you send to the user. If you use web worker or service worker , the associated code will be handled by a worker thread ( worker thread ). Compositing ( compositor ) and raster ( raster ) threads run in the rendering process to render page content efficiently and smoothly.

The main task of the rendering process is to convert HTML , CSS , and JavaScript into web content that we can interact with .

img
Figure 1: The rendering process has: a main thread ( main thread ), several worker threads ( worker threads ), a compositing thread ( compositor thread ), and a rasterizer thread thread ( raster thread )

Parse

Build the DOM

As mentioned in the previous article, the rendering process will receive a message from the browser process to submit the navigation ( commit navigation ) at the end of the navigation, after which the rendering process will start to receive HTML data , and the main thread will also start to parse the received text data ( text string ) and convert it into a DOM ( Document Object Model ) object

DOM object is both the browser's internal representation of the current page, as well as the Web the data structure that the developer interacts with the web page through JavaScript and API .

How to parse a --- HTML document as a DOM object is defined in the HTML standard . However, in your web development career, you may have never encountered a browser error when parsing HTML . This is because browsers are very error tolerant about HTML . For example: if a paragraph is missing the closing p tag ( </p> ), the page will still be treated as a valid HTML ; Hi! <b>I'm <i>Chrome</b>!</i> ( b标签写在了闭合i标签的前面) ,虽然有语法错误, Hi! <b>I'm <i>Chrome</i></b><i>!</i> . If you want to know how browsers handle these errors, you can refer to An introduction to error handling and strange cases in the parser in the HTML specification.

subresource loading

In addition to the HTML file, the website usually uses some sub-resources such as images, CSS styles, and JavaScript scripts. These files are fetched from cache or from the network. The main thread will initiate network requests one by one according to the sequence of each resource encountered when building the DOM tree, but in order to improve efficiency, the browser will also run a "preload scan" ( preload scanner ) program. HTML文档<img> <link>标签,预加载扫描程序会在HTML token Find the corresponding resources to be acquired, and tell the network threads in the browser process about the resources to be acquired.

img
Figure 2: Parse HTML content and build DOM tree

JavaScript blocks HTML parsing

When the HTML parser encounters the script tag, it will stop the HTML parsing of the document and turn to the JavaScript code loading, and execution. Why do you want to do this? script标签中的JavaScript document.write()文档流( document )的形状,从而使整个DOM The structure of the tree has fundamentally changed ( HTML The overview of the parsing model in the specification has a good diagram). For this reason, the HTML parser has to wait for the JavaScript to complete before continuing to parse the HTML document stream. If you're wondering what happened to the execution of JavaScipt 2c856cc4d3beffae7404913606c8110f---, the V8 team has a lot of discussions and blogs on this topic.

img

Give the browser a hint on how to load the resource

Web Developers can tell the browser how to load the resources needed by the web page more gracefully in many ways. If your JavaScript does not use something like document.write() to change the content of the document stream, you can add an async or defer attribute to the script tag Make the JavaScript script load asynchronously. Of course, if it can meet your needs, you can also use JavaScript Module . At the same time <link rel="preload"> resource preloading can be used to tell the browser that the resource will definitely be used in the current navigation, and you want to load this resource as soon as possible. For more related content, you can read the article Resource Prioritization - Getting the Browser to Help You .

Style calculation - Style calculation

With the DOM tree we don't know enough about what the page looks like, because we usually set some styles for the elements of the page. The main thread parses the CSS of the page to determine the calculation style of each DOM node ( computed style ). The calculation style is the specific style that each DOM element should have calculated by the main thread according to the CSS style selector ( CSS selectors ), you can open devtools to see the calculation style for each DOM node.

img
Figure 3: Main thread parsing CSS to determine the calculation style of each element

Even if your page doesn't have any custom styles set, each DOM node will still have a computed style attribute, because each browser has its own default style sheet. Because of the existence of this style sheet, the h1 tag on the page will definitely be larger than the h2 tag, and different tags will have different magin and padding . If you want to know what the default style of Chrome looks like, you can check the code directly.

Layout - Layout

After the previous steps are completed, the rendering process already knows the specific document structure of the page and the style information owned by each node, but this information still cannot finalize the appearance of the page. For example, if you now want to tell your friend on the phone the content of a painting around you: "There is a large red circle and a blue square on the canvas", it is difficult for your friend to rely on this information alone He knows what the painting looks like, because he doesn't know where the big circle and square are on the page, whether the square is in front of the circle or the circle is in front of the square.

img
Figure 4: You stand in front of a picture and tell your friend over the phone what the picture is on

The same is true for rendering web pages. Only knowing the document flow of the website and the style of each node is far from enough to render the content of the page. You also need to use the layout ( layout ) to calculate the value of each node. Geometry information ( geometry ). The specific process of the layout is: the main thread will traverse the just constructed DOM tree, and calculate a layout tree ( layout tree ) according to the calculation style of the DOM node. Each node in the layout tree will have specific information about its x,y coordinates on the page and its box size ( bounding box sizes ). The layout tree looks similar to the previously constructed DOM tree, except that this tree only has information about the nodes that are visible ( visible ). For example, if a node is set to display:none , the node is invisible and will not appear on the layout tree (a node with visibility:hidden will appear on the layout tree, you can think about why). Likewise, if a pseudo-element ( pseudo class ) node has something like p::before{content:"Hi!"} , it will appear in the layout, but not in the DOM tree .

img
Figure 5: The main thread traverses the calculation style information of each DOM tree node to generate a layout tree

Even if the layout of the page is very simple, the layout process is very complex. For example a page is simply showing paragraph after paragraph from top to bottom, the process is complicated because you need to consider things like the font size in the paragraph and where the paragraph needs to be wrapped, which all affect the size of the paragraph and shape, which in turn affects the layout of subsequent paragraphs.

tutieshi_480x300_5s.gif
Figure 6: Browsers have to consider whether paragraphs should wrap

If you take into account CSS it will be more complicated, because CSS is a very powerful thing, it can make elements levitate ( float ) to the edge of the page , can also block the overflowing ( overflow ) elements, and can also change the writing direction of the content, so just think about it and you will know that the process of layout is a very difficult and complex task. For the Chrome browser, we have a whole team of engineers responsible for the layout process. If you want to know the specific content of their work, their related discussions at the BlinkOn Conference were recorded, you can watch them if you have time.

Painting - Paint

Knowing DOM node and its style and layout are not enough to render the page. why? For example, if you now want to draw an identical painting on a painting, you already know the size, shape and position of each element on the canvas, you still have to think about the drawing order of each element, because the canvas The elements above will occlude each other ( z-index ).

img
Figure 7: A person stands in front of a canvas with a paintbrush, thinking about whether to draw a circle or a square first

For example, if some elements on the page have a z-index attribute set, the order in which the elements are drawn will affect the correctness of the page.

img
Figure 8: Drawing the elements of the page simply in the order of the HTML layout is wrong because the z-index element of the element is not taken into account

During the drawing step, the main thread traverses the previous layout tree ( layout tree ) to generate a series of drawing records ( paint records ). A drawing record is a note on the drawing process, such as "first draw the background, then the text, and finally the rectangle". If you've ever used canvas JavaScript to draw elements on a canvas---293ef3b5a4298698aa948504ef22a326---, this process may seem familiar to you.

img
Figure 9: The main thread traverses the layout tree to generate drawing records

Expensive rendering pipeline updates

A very important point about the rendering pipeline is that each step of the pipeline must use the results of the previous step to generate new data, which means that if the content of a step changes, all steps after this step must be is re-executed to generate new records. For example, if something is changed in the layout tree, the drawing order of those affected parts of the document is regenerated.

tutieshi_480x300_4s.gif
Figure 10: DOM+Style , layout and painting tree

If your page elements are animated ( animating ), the browser has to go through the rendering pipeline to update the page elements between each rendering frame. Most of our monitors have a refresh rate of 60 times a second (60fps), and the human eye will see smooth animations if you can move elements through the pipeline between each rendered frame. However, if the update time of the pipeline is relatively long and the animation has dropped frames, the page will look very "stuck".

img
Figure 11: The pipeline update does not catch up with the screen refresh, and the animation is a bit stuck

Even if your rendering pipeline updates are in line with the screen refresh rate, these updates are running on the main thread, which means that it may be blocked by code that is also running on the main thread JavaScript .

img
Figure 12: Some animation frames are blocked by JavaScript

For this case, you can split the JavaScript operation to be executed into smaller chunks and put them in each animation by requestAnimationFrame this API executed in the frame. For more information on this, see Optimize JavaScript Execution . Of course you can also put JavaScript code in WebWorkers to avoid them blocking the main thread.

img
Figure 13: Running a small snippet on the animation frame JavaScript code

synthesis

How to draw a page?

So far, the browser already knows the following information about the page: the document structure, the styles of the elements, the geometric information of the elements, and the order in which they are drawn. So how does the browser use this information to draw the page? The process of converting this information into pixels for the display is called rasterizing .

Probably the easiest way to do this is to only rasterize the content of the web page within the viewport ( viewport ). If the user scrolls the page, move the raster frame ( rastered frame ) and rasterize more content to fill in the missing parts of the page. The first version of Chrome actually did just that. However, for modern browsers, they tend to take a more complex approach called compositing ( compositing ).

tutieshi_480x300_6s.gif
Figure 14: Simplest rasterization process

what is synthesis

Compositing is a technique of dividing a page into layers, rasterizing them separately, and finally merging them into a single page in a single thread - the compositing thread ( compositor thread ). When the user scrolls the page, since all layers of the page have been rasterized, all the browser needs to do is to synthesize a new frame to display the scrolling effect. The animation effect of the page is also similar, just move the layer on the page and build a new frame.

You can view how your website is divided into different layers by the browser in the Layers panel DevTools .

tutieshi_480x300_7s.gif
Figure 15: Page composition process

page layering

In order to determine which elements need to be placed at which level, the main thread needs to traverse the render tree to create a hierarchical tree ( Layer Tree ) (in DevTools this part of the work is called " Update Layer Tree "). If certain parts of the page should be placed on a separate layer (sliding menu) but are not, you can tell the browser to layer it by using the will-change CSS property.

img
Figure 16: The main thread traverses the layout tree to generate the hierarchy tree

You may want to give all elements on the page a single layer, however when the page has more than a certain number of layers, the layer compositing operation is slower than rasterizing a small part of the page in each frame, so It is very important to measure the rendering performance of your application. For more information on this, see the article Stick to Compositor-Only Properties and Manage Layer Count .

Rasterize and composite pages off the main thread

Once the page's hierarchy tree is created and the drawing order of page elements is determined, the main thread submits this information to the composition thread ( compositor thread ). The compositing thread then rasterizes each layer of the page. Because one layer of a page can be as big as an entire web page, the compositing thread needs to slice them into smaller tiles ( tiles ) and then send the tiles to a series of raster threads ( raster threads ). The raster thread rasterizes each tile and stores them in GPU in memory.

img
Figure 17: The raster thread creates a bitmap of the tile and sends it to GPU

The compositing thread can assign different priorities to different raster threads ( prioritize ), so that pages in or near the viewport can be rasterized first. In response to the user zooming in and out of the page, the page's layers ( layer ) are equipped with different tiles for different resolutions.

When the tiles above the layer are all rasterized, the compositing thread gathers information on the tiles called the paint quad ( draw quads ) to construct a composite frame ( compositor frame ).

  • Drawing Quad: Contains information such as the location of the tile in memory and the location of the tile on the page after layer composition.
  • Composite Frame: A collection of drawing quads that represent the content of one frame of the page.

After the above steps are completed, the composition thread will submit ( commit ) a rendered frame to the browser process ( browser process ) via IPC 29c002c661df37e6d7722aa6a71bf256---. At this time, another composite frame may be submitted by the UI thread ( UI thread ) of the browser process to change the browser's UI. These composite frames are sent to GPU for display on the screen. If the composition thread receives a page scroll event, the composition thread will construct another composition frame and send it to GPU to update the page.

img
Figure 18: The composition thread constructs the composition frame, which is sent to the browser process and then to GPU

The nice thing about compositing is that the process doesn't involve the main thread, so the compositing thread doesn't need to wait for style calculations and JavaScript to finish executing. This is why building page animations only through composition is a best practice for building smooth user experiences. If the page needs to be re-layout or painted, the main thread must be involved.

rendering process

step:

  • parse: The main thread parses the received text data ( text string ) and converts it into a DOM ( Document Object Model ) object
  • recalculate style : The main thread will parse the page's CSS to determine the calculation style of each DOM node ( computed style )
  • layout: The main thread will traverse the just constructed DOM tree and calculate a layout tree ( layout tree ) according to the calculation style of the DOM node. Each node on the layout tree will have its x,y coordinates and box size ( bounding box sizes ) specific information on the page
  • Update Layer Tree : In order to determine which elements need to be placed in which layer, the main thread needs to traverse the render tree to create a layer tree ( Layer Tree ) (this part of the work in DevTools is called " Update Layer Tree ")
  • paint: The main thread will traverse the previous layout tree ( layout tree ) to generate a series of painting records ( paint records ). A drawing record is a note on the drawing process, such as "first draw the background, then the text, and finally the rectangle"
  • Composite layers: Once the hierarchical tree of the page is created and the drawing order of the page elements is determined, the main thread submits this information to the composite thread ( compositor thread ). The compositing thread then rasterizes each layer of the page, and when the tiles above the layer are rasterized, the compositing thread gathers information on the tiles called the paint quads ( draw quads ) to build A composite frame ( compositor frame ).最后,合成线程IPC向浏览器进程( browser process )提交( commit )一个渲染帧, GPU to render the page

image-20220913164030789.png

Summarize

In this article, we explored the entire rendering pipeline from parsing HTML files to compositing pages. Hopefully, after you've read it, you'll be able to read some articles on page performance optimization for yourself.

In the next and final article of this series, we'll look at the compositing thread in more detail, to understand what the browser does when the user moves the mouse ( mouse move ) and clicks ( click ) on the page matter.


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。