「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」
渲染器进程处理 Web 内容
渲染器进程负责选项卡内发生的所有事情。在渲染器进程中,主线程处理大部分代码。如果使用 web worker 或 service worker,有时部分 JavaScript 由工作线程处理。合成器和光栅线程也在渲染器进程内部运行,以高效、流畅地渲染页面。
渲染器进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。
解析
DOM的构建
当渲染器进程接收到导航的提交消息并开始接收 HTML 数据时,主线程开始解析文本字符串 (HTML) 并将其转换为文档对象模型 ( DOM )。
DOM 是浏览器对页面的内部表示,也是 Web 开发人员可以通过 JavaScript 与之交互的数据结构和 API。
浏览器接收到的 HTML 数据也是字节流,因此要将其转换成浏览器能认识及转换的 token 标签。
-
解码:浏览器将接收的字节流(
Bytes)基于编码方式解析为字符(characters)。 -
分词:通过分词器(词法分析)将字符转换为
Token,分为Tag Token和文本Token。 -
将
tokens标签转换为nodes节点,随后将nodes节点添加至DOM树上,这两步是并行执行的,在这期间,主要是通过栈的数据结构来进行维护(类似于括号匹配),当遇到开标签时,将对应node推入栈中,并且添加至DOM树上,当遇到文本标签时,就直接将文本node添加至DOM树上即可,当遇到闭合标签时,就进行出栈操作。
html.spec.whatwg.org/multipage/p…
将 HTML 文档解析为 DOM 由 HTML 标准定义。HTML 提供给浏览器永远不会引发错误。例如,缺少结束</p>标记是一个有效的 HTML。像(b 标签在 i 标签之前关闭)这样的错误标记。这是因为 HTML 规范会处理这些错误。
子资源加载
网站通常使用图像、CSS 和 JavaScript 等外部资源。这些文件需要从网络或缓存中加载。主线程可以在解析构建 DOM 的过程中找到它们时一一请求,但为了加快速度,“预加载扫描器”是并发运行的。如果 HTML 文档中有类似的东西<img>,<link>预加载扫描器会查看 HTML 解析器生成的令牌,并向浏览器进程中的网络线程发送请求。
JavaScript 可以阻止解析
当 HTML 解析器找到一个<script>标签时,它会暂停 HTML 文档的解析,并且必须加载、解析和执行 JavaScript 代码。为什么?document.write()因为 JavaScript 可以通过改变整个 DOM 结构之类的东西来改变文档的形状。这就是为什么 HTML 解析器必须等待 JavaScript 运行才能恢复对 HTML 文档的解析。
提示浏览器如何加载资源
Web 开发人员可以通过多种方式向浏览器发送提示,以便更好地加载资源。也可以为 JavaScript 添加 async 或 defer 属性。浏览器会异步加载和运行 JavaScript 代码,并且不会阻止解析。<link rel="preload"> 是一种通知浏览器当前导航肯定需要该资源并且希望尽快下载的方法。
样式计算
拥有 DOM 不足以知道页面的外观,可以在 CSS 中设置页面元素的样式。主线程解析 CSS 并确定每个 DOM 节点的计算样式。
在将各个引入方式进行解析后,我们就要将这些样式赋予我们的 DOM 节点,浏览器会结合 CSS 的 继承 , 优先级层叠 等规则,形成 CSS 规则树,可以通过浏览器的Element->Computed 查看一个DOM 节点上的具体样式。
布局(Layout)
现在渲染器进程知道文档的结构和每个节点的样式,但这还不足以渲染页面。
布局是查找元素几何形状的过程。主线程遍历 DOM 和计算样式,并创建包含 x,y 坐标和边界框大小等信息的布局树。布局树可能类似于 DOM 树的结构,但它只包含与页面上可见内容相关的信息。存在display: none,则该元素不是布局树的一部分(但是,具有的元素visibility: hidden在布局树中)。类似地,如果p::before{content:"Hi!"}应用了元素,它会包含在布局树中,即使它不在 DOM 中。
Paint
拥有 DOM、样式和布局仍然不足以呈现页面。假设尝试复制一幅画。知道元素的大小、形状和位置,但仍然必须知道绘制它们的顺序。
在此绘制步骤中,主线程遍历布局树以创建绘制记录。绘画记录是“先背景,后文字,后矩形”的绘画过程的记录。可以类比于 <canvas> 绘制元素。
Compositing
既然浏览器知道了文档的结构、每个元素的样式、页面的几何形状以及绘制顺序,将这些信息转换为屏幕上的像素称为光栅化。
一开始是讲在视口内光栅化部分。如果用户滚动页面,则移动光栅框架,并通过光栅更多来填充缺失的部分。这就是 Chrome 在首次发布时处理光栅化的方式。然而,现代浏览器运行一个更复杂的过程,称为合成。
什么是合成
合成是一种技术,可以将页面的各个部分分成图层,分别将它们光栅化,然后在合成器线程的单独线程中合成为页面。如果发生滚动,由于图层已经被光栅化,它所要做的就是合成一个新的帧。可以通过移动图层并合成新帧以相同的方式实现动画。
主线程的光栅和合成
一旦创建了图层树并确定了绘制顺序,主线程就会将该信息提交给合成器线程。合成器线程然后光栅化每一层。一个层可能像一页的整个长度一样大,因此合成器线程将它们分成小块并将每个小块发送到光栅线程。光栅线程光栅化每个图块并将它们存储在 GPU 内存中。
合成器线程可以优先考虑不同的光栅线程,以便可以首先对视口(或附近)内的事物进行光栅化。一个图层还具有针对不同分辨率的多个平铺,以处理诸如放大操作之类的事情。
一旦 tiles 被光栅化,合成器线程收集称为 draw quads 的 tiles 信息以创建compositor frame。
然后通过 IPC 将合成器框架提交给浏览器进程。此时,可以从 UI 线程添加另一个合成器框架以更改浏览器 UI,或从其他渲染器进程添加用于扩展。这些合成器帧被发送到 GPU 以在屏幕上显示。如果出现滚动事件,合成器线程会创建另一个合成器帧以发送到 GPU。
合成的好处是它是在不涉及主线程的情况下完成的。合成器线程不需要等待样式计算或 JavaScript 执行。如果需要再次计算布局或绘制,则必须涉及主线程。