参考
- [译] 现代浏览器内部揭秘(第一部分)
- [译] 现代浏览器内部揭秘(第二部分)
- [译] 现代浏览器内部揭秘(第三部分)
- [译] 现代浏览器内部揭秘(第四部分)
- https://segmentfault.com/a/1190000022633988
- 深入浅出浏览器渲染原理
[
](https://blog.fundebug.com/2019/01/03/understand-browser-rendering/)
浏览器进程
Chrome 的多进程架构示意图。渲染进程下显示了多个层,表明 Chrome 为每个标签页运行多个渲染进程。
- 浏览器进程:控制应用中的 “Chrome” 部分,包括地址栏,书签,回退与前进按钮。以及处理 web 浏览器不可见的特权部分,如网络请求与文件访问。
- 渲染进程:控制标签页内网站展示。
- 插件进程:控制站点使用的任意插件,如 Flash。
- GPU进程:处理独立于其它进程的 GPU 任务。GPU 被分成不同进程,因为 GPU 处理来自多个不同应用的请求并绘制在相同表面。
导航时发生了什么
你在浏览器中键入 URL,然后浏览器从互联网获取数据并显示一个页面。 用户请求站点和浏览器准备渲染页面部分 —— 亦即导航。
第 1 步:处理输入
UI 线程询问输入内容是搜索查询还是 URL 地址
第 2 步:开始导航
UI线程告诉网络线程要导航到某网站
第3步:读取响应
如果响应是一个 HTML 文件,那么下一步就会把数据传给渲染进程,但是如果是一个压缩文件或是其他文件,那么意味着它是一个下载请求,因此需要将数据传递给下载管理器。
此时也会进行 SafeBrowsing 检查。如果域名和响应数据似乎匹配到一个已知的恶意网站,那么网络线程会显示一个警告页面。除此之外,还会发生 Cross Origin Read Blocking(CORB)检查,以确保敏感的跨域数据不被传给渲染进程。
第4步:查找渲染进程
一旦所有的检查执行完毕并且网络线程确信浏览器会导航到请求的站点,网络线程会告诉 UI 线程所有的数据准备完毕。UI 线程会寻找渲染进程去开始渲染 web 页面。
由于网络请求会花费几百毫秒才获取回响应,因此可以应用一个优化措施。当第 2 步 UI 线程正发送一个 URL 请求给网络线程时,它已经知道它们会导航到哪个站点。在网络请求的同时,UI 并行地线程尝试主动寻找或开启一个渲染进程。这样,如果一切按预期进行,渲染进程在网络线程接受到数据时就已经处于待命状态。如果导航跨域重定向,这个待命进程也许不会被用到,这种情况下也许会用到另一个进程。
第5步:提交导航
现在数据和渲染进程已经就绪,浏览器进程会发送一个 IPC(进程间通信)到渲染进程去提交导航。它也会传递数据流,所以渲染进程可以保持接收 HTML 数据。一旦浏览器进程收到渲染进程已经提交的确认消息,导航完毕并且文档加载解析开始。
额外的步骤:初始加载完毕
一旦渲染进程渲染“完毕”。它会发送一个 IPC 返回给浏览器进程(这会在页面所有的 frame 的 onload 事件已经触发和执行完毕后发生)。这时,UI 线程停止标签页上的加载动画。
渲染进程发送 IPC 到浏览器进程通知页面“已被加载”
渲染进程处理网站内容
渲染进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。
解析(Parsing)
DOM 的构建
当渲染进程收到导航的提交消息并开始接收 HTML 数据时,主线程开始解析文本字符串(HTML)并将其转换为文档对象模型(DOM)。
子资源加载
网站通常使用图像、CSS 和 JavaScript 等外部资源,这些文件需要从网络或缓存加载。在解析构建 DOM 时,主线程会按处理顺序逐个请求它们,但为了加快速度,“预加载扫描器(preload scanner)”会同时运行。如果 HTML 文档中有 或 之类的内容,则预加载扫描器会查看由 HTML 解析器生成的标记,并在浏览器进程中向网络线程发送请求。
JavaScript 阻塞解析
当 HTML 解析器遇到 <script>
标记时,会暂停解析 HTML 文档,开始加载、解析并执行 JavaScript 代码。为什么?因为JavaScript 可以使用诸如 document.write()
的方法来改写文档,这会改变整个 DOM 结构。
提示浏览器如何加载资源 Web 开发者可以通过多种方式向浏览器发送提示,以便很好地加载资源。如果你的 JavaScript 不使用
document.write()
,你可以在<script>
标签添加async
或defer
属性,这样浏览器会异步加载运行 JavaScript 代码,而不阻塞解析。如果合适,你也可以使用 JavaScript 模块。可以使用<link rel="preload">
告知浏览器当前导航肯定需要该资源,并且你希望尽快下载。
样式计算
主线程解析 CSS 并确定每个 DOM 节点计算后的样式。这是有关基于 CSS 选择器对每个元素应用何种样式的信息,这可以在 DevTools 的 computed 部分中看到。
主线程解析 CSS 以添加计算后样式
布局
主线程遍历计算样式后的 DOM 树,以此生成布局树。主线程遍历 DOM,计算样式并创建布局树,其中包含 x y 坐标和边界框大小等信息。布局树可能与 DOM 树结构类似,但它仅包含页面上可见内容相关的信息。
绘制
在绘制步骤中,主线程遍历布局树创建绘制记录。绘制记录是绘图过程的记录,就像是“背景优先,然后是文本,然后是矩形”。
更新渲染管道的成本很高
**
渲染管道中最重要的事情是:每个步骤中,前一个操作的结果用于后一个操作创建新数据。例如,如果布局树中的某些内容发生改变,需要为文档的受影响部分重新生成“绘制”指令。
合成
合成是一种将页面的各个部分分层,分别光栅化,并在称为合成线程的单独线程中合成为页面的技术。如果发生滚动,由于图层已经光栅化,因此它所要做的只是合成一个新帧。动画也可以以相同的方式(移动图层和合成新帧)实现。
分层
为了分清哪些元素位于哪些图层,主线程遍历布局树创建图层树(此部分在 DevTools 性能面板中称为“Update Layer Tree”)。如果页面的某些部分应该是单独图层(如滑入式侧面菜单)但没拆分出来,你可以使用 CSS 中的 will-change 属性来提示浏览器。
主线程遍历布局树生成图层树
一旦创建了图层树并确定了绘制顺序,主线程就会将该信息提交给合成线程。