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 .
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.
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.
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.
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.
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 .
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.
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
).
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.
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.
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.
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".
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
.
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.
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
).
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
.
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.
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.
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.
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 aDOM
( Document Object Model ) object - recalculate style : The main thread will parse the page's
CSS
to determine the calculation style of eachDOM
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 theDOM
node. Each node on the layout tree will have itsx,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 inDevTools
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
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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。