Chrome浏览器内部揭秘(三)

1,213 阅读6分钟

本文转自 Inside look at modern web browser (part 3) - Chrome Developers,原作者为 Mariko Kosaka

阅读后觉得内容非常不错,为了加深记忆和理解,决定翻译一些重要的部分。(虽然掘金里已经有好几篇本文翻译了)

渲染进程对网页内容的处理

渲染进程负责处理标签页内的所有事情,其中主线程处理了大部分的代码,worker 线程会协助处理一些 JS 代码,合成器线程和栅格线程则负责更快地渲染页面。

一言蔽之,渲染进程的核心任务是把 HTML,CSS 和 JavaScript 变成用户可交互的页面。

解析

构建 DOM Tree

当渲染进程收到浏览器进程的 commit 和 data,主线程就开始将 HTML 解析成 DOM。

DOM 是浏览器内部用于表示页面的数据结构,同时也是开发者可以交互的 API。

HTML文档的解析由 HTML标准决定,但是你很少会见到浏览器报错,这是因为浏览器被设计成对错误更宽松,比如说它会把像这样的错误标记 Hi! <b>I'm <i>Chrome</b>!</i> 解析成 Hi! <b>I'm <i>Chrome</i></b><i>!</i>(自动补上没有关闭/打开的 标签)。

网络资源加载

网页通常需要加载一些图片、CSS 和 JS 之类的外部资源,主线程在解析HTML文档的时候遇到它们便会逐个发起请求,不过,为了加快速度,“预加载扫描器”会同时运行。预加载扫描器会检查 HTML解析器生成的标记,如果里面有, 标签,那么就会发送请求到浏览器进程的网络线程。

JavaScript 会堵塞解析

当 HTML 解析器遇到一个

告诉浏览器要怎样加载资源

如果你的 JS 脚本并不会改变 DOM 结构,可以在

注意:

asyn 属性表示会异步加载脚本,一旦脚本加载完毕就会开始解析和执行,这意味着多个 asyn 的脚本可能不会按预设的顺序执行。如果你的脚本之间有依赖关系,那么这时候就会出现问题。

defer 属性同样表示异步加载脚本,但是它会在 HTML 的解析完成之后在开始解析和执行(asyn 属性的脚本可能会在 DOM 构建完成之前解析和执行),并且会按照标签的顺序。

样式计算

完成 DOM 的构建之后还不足以渲染页面,因为 CSS 的样式会对页面有所影响。主线程会解析 CSS 并决定每个 DOM 节点的计算样式。

即使你没有设置任何 CSS,主线程也会按照浏览器的默认样式给每个节点设置计算样式,

布局

主线程会历遍 DOM 和计算样式并够着 layout tree。layout tree 上每个节点的 x、y 坐标以及盒子的边界尺寸。layout tree 和 DOM tree 并不是一一对应的,除了 display:none 的节点之外都会被添加进去,包括 visibility:hidden 的节点。类似的,伪元素也会被添加进 layout tree,但是却不会出现在 DOM tree。

绘制

在这个阶段,主线程会历遍 layout tree 从而创建一个绘制记录。绘制记录是一个关于绘制顺序的笔记,比如说“背景 >> 文字 >> 正方形”。

不断更新渲染流程消耗巨大

渲染过程中,每一步都是依据上一步的结果来生成的,比如说,如果 layout tree 发生了一些变化,那么绘制顺序就需要重新生成。

如果你对一个元素应用动画(60fps),主线程需要一帧一帧地渲染,因为动画和 JS 都是在主线程上运行的,如果此时有 JS 占用了过长的时间,就会导致帧渲染不及时,从而动画掉帧。

这当然有解决方案,比如使用框架把 JS 拆分成小块运行,这样 JS 就能及时归还主线程,从而保证每帧都能获得渲染。

合成

合成是一种将页面拆分成不同的 layers,分别将他们栅格化,然后在一个独立的线程——合成器线程里再合成为一个页面。一旦页面滚动,合成器就会将栅格化好的层合成一个新的帧。

为了确认如何分层,主线程会历遍 layout tree 并构建 layer tree,如果页面的某个部分应该被分为一层,那么可以在 CSS 里添加 will-change 属性来告诉浏览器。

注意:不要给太多的元素设置层,不然会因为合成太多的层而导致速度变慢。

栅格化和合成

一旦 layer tree 和 paint order 都创建好了,主线程会把这些数据递交给合成器线程。合成器线程负责把每一层栅格化,因为一层可能会太大,于是合成器线程把一层分割成小的图块,并交由栅格化线程,栅格化线程再将这些小图块栅格化并储存到 GPU 内存里。

合成器线程能够对不同的栅格线程进行优先处理,所以出现在窗口内的图块会被首先栅格。

一旦图块栅格化完成,合成器线程从栅格线程收集图块的信息—— draw quads,从而创建一个合成器帧。

Draw quads:包含图块在内存中的位置信息以及图块在页面的绘制位置信息。

合成器帧:收集了一个页面帧所有 Draw quads 的集合。

合成器帧会通过 IPC 提交给浏览器进程,浏览器进程将合成器帧传给 GPU,GPU 将该帧显示到屏幕上。如果页面滚动了,合成器线程就会合成一个新的合成器帧给 GPU 渲染。

合成的好处是可以在主线程之外运行,所以合成器线程不用等待样式计算或者是 JS 执行,这也是为什么用合成器生成动画能够提供更好的性能。否则如果 layout 和 paint 需要不停地计算,从而导致主线程被大量占用。