[译] 现代浏览器内部揭秘(第二部分)

导航时发生了什么

这是关于 Chrome 内部工做的 4 篇博客系列的第 2 篇。在上一篇文章中,咱们研究了不一样的进程和线程如何处理浏览器的不一样部分。在这篇文章中,咱们会更深刻研究每一个进程和线程如何进行通讯以展现网站。前端

让咱们看一个网络浏览的简单用例:你在浏览器中键入 URL,而后浏览器从互联网获取数据并显示一个页面。在这篇文章中,咱们将重点放在用户请求站点和浏览器准备渲染页面部分 —— 亦即导航。android

它以浏览器进程开始

浏览器进程

图 1:顶部是浏览器 UI,底部是拥有 UI、网络和存储线程的浏览器进程图ios

正如咱们在第 1 部分:CPU、GPU、内存和多进程架构中所述,tab 外的一切都被浏览器进程处理。浏览器进程有不少线程,例如绘制浏览器按钮和输入栏的 UI 线程、处理网络栈以从因特网获取数据的网络线程、控制文件访问的存储线程等。当你在地址栏中键入 URL 时,你的输入将由浏览器进程的 UI 线程处理。git

一个简单导航

第 1 步:处理输入

当用户开始在地址栏键入时,UI 线程要问的第一件事是 “这是一次搜索查询仍是一个 URL 地址?”。在 Chrome 中,地址栏同时也是一个搜索输入栏,因此 UI 线程须要解析和决定把你的请求发送到搜索引擎,或是你要请求的网站。github

处理用户输入

图 1:UI 线程询问输入内容是搜索查询仍是 URL 地址web

第 2 步:开始导航

当用户按下 Enter 键时,UI 线程启用网络调取去获取站点内容。加载动画会显示在标签页的一角,网络线程会经过适当的协议,像 DNS 查找和为请求创建 TLS 链接。后端

导航开始

图 2:UI 线程告诉网络线程要导航到 mysite.comapi

在这时,网络线程可能会收到像 HTTP 301 那样的服务器重定向头。这种状况下,网络线程会告诉 UI 线程,服务器正在请求重定向。而后,另外一个 URL 请求会被启动。跨域

第 3 步:读取响应

HTTP 响应

图 3:包含 Content-Type 的响应头以及做为实际数据的 payload浏览器

一旦开始收到响应主体(payload),网络线程会在必要时查看数据流的前几个字节。响应报文的 Content-Type 字段会声明数据的类型,可是它有可能会丢失或者错误,因此就有了 MIME 类型嗅探来解决这个问题。这是源码中评论的“棘手的问题”。你能够阅读注释看一下不一样浏览器是怎么匹配 content-type 和 payload 的。

若是响应是一个 HTML 文件,那么下一步就会把数据传给渲染进程,可是若是是一个压缩文件或是其余文件,那么意味着它是一个下载请求,所以须要将数据传递给下载管理器。

MIME 类型嗅探

图 4:网络线程询问一个响应数据是不是从安全网站来的 HTML

此时也会进行 SafeBrowsing 检查。若是域名和响应数据彷佛匹配到一个已知的恶意网站,那么网络线程会显示一个警告页面。除此以外,还会发生 Cross Origin Read Blocking(CORB检查,以确保敏感的跨域数据不被传给渲染进程。

第 4 步:查找渲染进程

一旦全部的检查执行完毕而且网络线程确信浏览器会导航到请求的站点,网络线程会告诉 UI 线程全部的数据准备完毕。UI 线程会寻找渲染进程去开始渲染 web 页面。

寻找渲染进程

图 5:网络线程告诉 UI 线程去查找渲染进程

因为网络请求会花费几百毫秒才获取回响应,所以能够应用一个优化措施。当第 2 步 UI 线程正发送一个 URL 请求给网络线程时,它已经知道它们会导航到哪一个站点。在网络请求的同时,UI 并行地线程尝试主动寻找或开启一个渲染进程。这样,若是一切按预期进行,渲染进程在网络线程接受到数据时就已经处于待命状态。若是导航跨域重定向,这个待命进程也许不会被用到,这种状况下也许会用到另外一个进程。

第 5 步:提交导航

如今数据和渲染进程已经就绪,浏览器进程会发送一个 IPC(进程间通讯)到渲染进程去提交导航。它也会传递数据流,因此渲染进程能够保持接收 HTML 数据。一旦浏览器进程收到渲染进程已经提交的确认消息,导航完毕而且文档加载解析开始。

这时,地址栏已经更新,安全指示器和站点设置 UI 会反映新页面的站点信息。此标签页的 session 历史记录会被更新,因此前进/后退按钮会走向刚导航过的站点。当你关闭标签页或者窗口,为了优化 tab/session 的还原,session 历史被保存在硬盘上。

提交导航

图 6:浏览器和渲染进程间的 IPC,请求渲染页面。

额外的步骤:初始加载完毕

一旦导航被提交,渲染进程开始加载资源和渲染页面。咱们将在下一篇文章中讲解这个阶段发生什么的细节。一旦渲染进程渲染“完毕”。它会发送一个 IPC 返回给浏览器进程(这会在页面全部的 frame 的 onload 事件已经触发和执行完毕后发生)。这时,UI 线程中止标签页上的加载动画。

我之因此说“结束”,是由于客户端 JavaScript 能够在这时以后仍然加载额外的资源而且渲染新视图。

页面加载结束

图 7:渲染进程发送 IPC 到浏览器进程通知页面“已被加载”

导航到另外一个站点

简单导航已经完毕!可是用户在地址栏输入另外一个 URL 会怎样呢?好吧,浏览器进程会执行相同的步骤来导航到一个不一样的站点。可是在它作这个以前,它会检查当前已经渲染的站点是否关心 beforeunload 事件。

beforeunload 能够在你试图导航离开或关闭标签页时建立“离开此站点?”警告。包括你的 JavaScript 代码,全部标签页内的东西都是由渲染进程处理,因此当新的导航请求到来时,浏览器进程必需要跟当前的渲染进程核对。

注意: 不要添加无条件的 beforeunload 处理程序。它会产生更多延迟,由于处理程序须要在导航开始以前执行。应仅在须要时添加此事件处理程序,例如若是须要警告用户他们可能会丢失他们在页面上输入的数据。

beforeunload 事件处理程序

图 8:浏览器进程向渲染进程发送 IPC 告诉它将要导航到另外一个站点

若是渲染进程已经启动了导航(像用户点击一个连接或者客户端 JavaScript 运行 window.location = "https://newsite.com"),渲染进程会先检查 beforeunload 事件处理程序。而后,它会像浏览器处理启动导航同样执行相同的步骤。惟一不一样的是导航请求是由渲染进程发送到浏览器进程的。

当新导航到的站点不一样于当前已渲染的站点时,会调用一个独立的渲染进程来处理新导航,同时保持当前的渲染进程来处理相似 unload 的事件。有关更多信息,请查看页面生命周期概览以及如何使用页面声明周期 API 挂钩事件。

新导航与 unload

图 9:2 个 IPC(从浏览器进程到新渲染进程)告知渲染页面并告知旧渲染进程卸载

若是有 Service Worker

最近对导航过程的改变是引入了 service worker。service worker 是一种在你的应用代码中编写网络代理的方法;容许 Web 开发者更好地控制本地缓存内容以及什么时候从网络获取新数据。若是将 service worker 设置为从缓存加载页面,则无需从网络请求数据。

要记住的重要部分是 Service Worker 是在渲染进程中运行的 JavaScript 代码。可是当导航请求进入时,浏览器进程如何知道该站点有 service worker?

service worker 做用域检查

图 10:浏览器进程中的网络线程查找 service worker 做用域

当注册一个 service worker 时,保持 service worker 的做用域做为一个引用(你能够在这篇文章 The Service Worker Lifecycle 中阅读更多关于做用域的知识)。当一个导航发生时,网络线程用已注册的 service worker 做用域来检查域名,若是已经为该 URL 注册了一个 service worker,UI 线程会找一个渲染线程来执行 service worker 的代码。service worker 可能从缓存中加载数据,无需从网络请求数据,或者能够从网络请求新资源。

service worker 导航

图 11:浏览器中的 UI 线程启动渲染进程来处理 service workers;而后,渲染进程中的工做线程从网络请求数据

导航预加载

你能够看到,若是 service worker 最终决定从网络请求数据,则浏览器进程和渲染器进程之间的往返可能会致使延迟。导航预加载是一种经过与 service worker 启动并行加载资源来加速此过程的机制。它用一个头部来标记这些请求,容许服务器决定为这些请求发送不一样的内容;例如,只更新数据而不是完整文档。

导航预加载

图 12:浏览器进程中的 UI 线程启动渲染进程以在并行启动网络请求的同时处理 service worker

总结

在这篇文章中,咱们研究了导航过程当中发生了什么,以及你的 Web 应用代码(例如响应头和客户端 JavaScript)如何与浏览器交互。了解浏览器经过网络获取数据的步骤,能够更容易地理解为何开发导航预加载等 API。在下一篇文章中,咱们将深刻探讨浏览器如何分析 HTML/CSS/JavaScript 以渲染页面。

你喜欢这篇文章吗?若是您对之后的文章有任何问题或建议,欢迎在下面的评论区或在 Twitter @kosamari 上留下您的宝贵意见。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索