渲染进程的内部工作原理[Chrome 浏览器内部工作原理(三)]
2023年10月13日星期五
渲染进程涉及许多方面的影响网页性能。由于渲染进程中发生了很多事情,本文仅提供一个概述。
渲染器处理网页内容
渲染进程负责标签内发生的所有事情。在渲染进程中,主线程处理发送给用户的大部分代码。如果使用Web Worker
或Service Worker
,JavaScript
部分可能由Worker线程处理。合成器和光栅线程也在渲染进程中运行,以高效、平滑地渲染页面。
渲染器进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以交互的网页。
1. HTML 解析
构建 DOM
当渲染器进程收到导航的提交消息并开始接收 HTML 数据时,主线程开始解析文本字符串(HTML)并将其转换为文档对象模型( DOM
)。
DOM 是浏览器的页面内部表示,也是 Web 开发人员可以通过 JavaScript 进行交互的数据结构和 API。
将 HTML 文档解析为 DOM 的定义是 HTML标准。 向浏览器提供 HTML 永远不会引发错误。例如,缺少结束</p>
标签是有效的 HTML。像<b>I'm <i>Chrome</b>!</i>
这样的错误标签<b>I'm <i>Chrome</b>!</i>
(b 标签在 i 标签之前关闭)被视为的 <b>I'm <i>Chrome</i></b><i>!</i>
。这是因为 HTML 规范旨在优雅地处理这些错误。
“解析器中的错误处理和奇怪情况简介” 说明了这些事情是如何完成的。
子资源加载
网站通常使用图像、CSS 和 JavaScript 等外部资源。这些文件需要从网络或缓存加载。主线程可以在解析构建 DOM 时一一请求它们,但为了加快速度,“预加载扫描器”是并发运行的。如果 HTML 文档中存在<img>
或<link>
之类的内容,则预加载扫描程序会查看 HTML 解析器生成的标签,并将请求发送到浏览器进程中的网络线程。
JavaScript 可以阻止解析
当 HTML 解析器发现<script>
标签时,它会暂停 HTML 文档的解析,并必须加载、解析和执行 JavaScript 代码。为什么?因为 JavaScript 可以使用document.write()
之类的东西来改变文档的形状,这会改变整个 DOM 结构。这就是为什么 HTML 解析器必须等待 JavaScript 在恢复 HTML 文档的解析之前运行。
向浏览器提示要如何加载资源
可以将async
或defer
属性添加到<script>
标签。然后浏览器异步加载并运行 JavaScript 代码,并且不会阻止解析。也可以使用 JavaScript 模块。<link rel="preload">
是一种通知浏览器当前导航肯定需要该资源并且希望尽快下载的方式。
2. 样式计算
拥有 DOM 不足以了解页面的外观,因为可以在 CSS 中设置页面元素的样式。主线程解析 CSS 并确定每个 DOM 节点的计算样式。基于 CSS 选择器对每个元素应用哪种样式。可以在 DevTools
的computed
部分中查看样式信息。
即使不提供任何 CSS,每个 DOM 节点也有一个计算样式。 <h1>
标记显示得比<h2>
标记大,并且为每个元素定义了边距。这是因为浏览器有一个 默认样式表 。
3. 布局
布局是寻找元素几何形状的过程。主线程遍历 DOM 和计算样式并创建布局树,其中包含 xy 坐标和边界框大小等信息。布局树可能与 DOM 树的结构类似,但它只包含与页面上可见内容相关的信息。如果应用了display: none
,则该元素不是布局树的一部分(但是,具有visibility: hidden
元素位于布局树中)。类似地,如果应用了类似p::before{content:"Hi!"}
的伪元素,它就会包含在布局树中,即使它不在 DOM 中。
4. 分层
为了找出哪些元素需要位于哪些层中,主线程会遍历布局树来创建层树(这部分在DevTools
性能面板中称为Update Layer Tree
)。滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,如果页面的某些部分应该是单独的层(例如滑入式侧面菜单),但没有获得单独的层,那么可以使用 CSS 中的will-change
属性向浏览器提示。
5. 绘制
主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。合成线程首先对每个图层进行分块,将其划分为更多的小区域。它会从线程池中拿取多个线程来完成分块工作。
6. 光栅化
合成器线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。GPU 进程会开启多个线程来完成光栅化,合成器线程可以对不同的光栅线程进行优先级排序,以便可以首先对视口内(或附近)内的事物进行光栅化。图层还具有针对不同分辨率的多个平铺,以处理诸如放大操作之类的操作。一旦图块被光栅化,合成器线程就会收集称为 Draw quads 的图块信息来创建合成器框架。
- Draw quads:包含诸如图块在内存中的位置以及考虑到页面合成的情况下在页面中绘制图块的位置等信息。
- 合成器框架:代表页面框架的绘制四边形的集合。
7. 画
合成器框架通过 IPC 提交给浏览器进程。此时,可以从 UI 线程添加另一个合成器框架以进行浏览器 UI 更改,或者从其他渲染器进程添加以进行扩展。这些合成器帧被发送到 GPU 以将其显示在屏幕上。如果出现滚动事件,合成器线程将创建另一个合成器帧并发送到 GPU,完成最终的屏幕成像。
- 合成的好处是它是在不涉及主线程的情况下完成的。合成器线程不需要等待样式计算或 JavaScript 执行。旋转、缩放等变形发生在合成线程,与渲染主线程无关,这就是
transform
效率高的本质原因。