深入了解现代网络浏览器(第 2 部分)

Mariko Kosaka

导航过程中会发生什么

这是介绍 Chrome 内部运作机制的 4 篇博文系列的第 2 篇。 在上篇文章中,我们介绍了不同的进程和线程如何处理浏览器的不同部分。在本文中,我们将深入探讨每个进程和线程如何通信以显示网站。

我们来看看网络浏览的一个简单用例:您在浏览器中输入网址,然后浏览器从互联网提取数据并显示网页。在本文中,我们将重点介绍用户请求网站并浏览器准备呈现网页(也称为导航)的部分。

它以浏览器进程开头

浏览器进程
图 1:顶部是浏览器界面,底部是浏览器进程示意图,其中包含界面、网络和存储线程

正如我们在第 1 部分:CPU、GPU、内存和多进程架构中所介绍的,标签页之外的所有内容均由浏览器进程处理。浏览器进程包含多个线程,例如用于绘制浏览器按钮和输入字段的界面线程、用于处理网络堆栈以接收来自互联网的数据的网络线程、用于控制对文件的访问权限的存储线程等。当您在地址栏中输入网址时,浏览器进程的界面线程会处理您的输入。

简单的导航栏

第 1 步:处理输入

当用户开始在地址栏中输入内容时,界面线程首先会询问“这是搜索查询还是网址?”。在 Chrome 中,地址栏也是一个搜索输入字段,因此界面线程需要进行解析,并决定是将您定向到搜索引擎,还是定向到您请求的网站。

处理用户输入
图 1:界面线程询问输入内容是搜索查询还是网址

第 2 步:开始导航

当用户按下 Enter 键时,界面线程会发起网络调用以获取网站内容。标签页角落会显示加载旋转图标,网络线程会遵循适当的协议(例如 DNS 查找和为请求建立 TLS 连接)。

导航开始
图 2:界面线程与网络线程通信以导航到 mysite.com

此时,网络线程可能会收到 HTTP 301 等服务器重定向标头。在这种情况下,网络线程会与界面线程通信,告知服务器正在请求重定向。然后,系统会发起另一个网址请求。

第 3 步:阅读回复

HTTP 响应
图 3:响应标头,其中包含 Content-Type 和实际数据的载荷

响应正文(载荷)开始传入后,网络线程会视需要查看数据流的前几个字节。响应的 Content-Type 标头应指明其数据类型,但由于该标头可能缺失或错误,因此此处会执行 MIME 类型嗅探。正如源代码中所述,这是一个“棘手的问题”。您可以阅读该注释,了解不同浏览器如何处理 content-type/payload 对。

如果响应是 HTML 文件,则下一步是将数据传递给渲染程序进程;但如果是 ZIP 文件或其他文件,则表示它是下载请求,因此需要将数据传递给下载管理器。

MIME 类型嗅探
图 4:网络线程,用于询问响应数据是否来自安全网站的 HTML

系统也会在此处执行 SafeBrowsing 检查。如果网域和响应数据似乎与已知的恶意网站匹配,则网络线程会发出提醒,以显示警告页面。此外,系统还会进行跨源读取锁定 (CORB) 检查,以确保敏感的跨网站数据不会进入呈现程序。

第 4 步:查找渲染程序进程

完成所有检查后,如果网络线程确信浏览器应导航到请求的网站,则网络线程会告知界面线程数据已准备就绪。然后,界面线程会查找渲染程序进程,以继续渲染网页。

查找渲染程序进程
图 5:网络线程告知界面线程查找渲染程序

由于网络请求可能需要几百毫秒才能收到响应,因此系统会应用优化来加快此流程。当界面线程在第 2 步向网络线程发送网址请求时,它已经知道要导航到哪个网站。界面线程会尝试在网络请求并行运行时主动查找或启动渲染程序。这样一来,如果一切按预期进行,当网络线程收到数据时,渲染程序进程就已经处于待机状态。如果导航会跨网站重定向,则可能不会使用此待机进程,在这种情况下,可能需要使用其他进程。

第 5 步:提交导航

现在,数据和渲染程序已准备就绪,系统会从浏览器进程向渲染进程发送 IPC 以提交导航。它还会传递数据流,以便渲染程序进程可以继续接收 HTML 数据。当浏览器进程收到确认在渲染程序中已提交的确认后,导航便会完成,文档加载阶段随即开始。

此时,地址栏会更新,安全信号和网站设置界面会反映新页面的网站信息。该标签页的会话历史记录将更新,因此返回/前进按钮将逐一显示刚刚导航到的网站。为了便于您在关闭标签页或窗口时恢复标签页/会话,系统会将会话记录存储在磁盘上。

提交导航
图 6:浏览器和渲染程序之间的 IPC,请求渲染网页

额外步骤:初始加载完成

导航提交后,渲染程序会继续加载资源并呈现页面。我们将在下一篇文章中详细介绍此阶段会发生的情况。渲染程序“完成”渲染后,会将 IPC 发送回浏览器进程(这是在网页中的所有帧上触发并完成执行所有 onload 事件之后)。此时,界面线程会停止标签页上的加载旋转图标。

之所以说“完成”,是因为客户端 JavaScript 在此之后仍可以加载其他资源并渲染新视图。

网页加载完毕
图 7:从渲染程序到浏览器进程的 IPC,用于通知页面已“加载”

简单的导航已完成!但是,如果用户再次在地址栏中输入其他网址,会发生什么情况?浏览器进程会按照相同的步骤转到其他网站。但在执行此操作之前,它需要向当前呈现的网站确认他们是否关心 beforeunload 事件。

当您尝试离开或关闭标签页时,beforeunload 可能会创建“离开此网站?”提醒。标签页中的所有内容(包括 JavaScript 代码)均由渲染程序进程处理,因此当有新的导航请求传入时,浏览器进程必须与当前的渲染程序进程进行检查。

beforeunload 事件处理脚本
图 8:浏览器进程向渲染程序发送 IPC,告知它即将导航到其他网站

如果导航是从渲染程序进程启动的(例如用户点击了链接或客户端 JavaScript 运行了 window.location = "https://newsite.com"),渲染程序进程会先检查 beforeunload 处理脚本。然后,它会经历与浏览器进程发起的导航相同的过程。唯一的区别在于,导航请求是从渲染程序发起到浏览器进程。

当新导航到与当前呈现的网站不同的网站时,系统会调用单独的呈现进程来处理新导航,同时保留当前呈现进程来处理 unload 等事件。如需了解详情,请参阅页面生命周期状态概览以及如何使用 Page Lifecycle API 钩入事件。

新的导航和卸载
图 9:从浏览器进程到新渲染程序的 2 个 IPC,用于指示渲染页面并指示旧渲染程序卸载

对于 Service Worker

该导航流程最近发生的一项变化是引入了服务工件。服务工件是一种在应用代码中编写网络代理的方法,可让 Web 开发者更好地控制要将哪些内容缓存在本地以及何时从网络获取新数据。如果将 Service Worker 设置为从缓存加载页面,则无需从网络请求数据。

请务必注意,Service Worker 是运行在渲染程序进程中的 JavaScript 代码。但是,当导航请求传入时,浏览器进程如何知道网站有服务工件?

Service Worker 范围查找
图 10:浏览器进程中查找服务工件作用域的网络线程

注册服务工件后,系统会将服务工件的范围保留为引用(您可以参阅这篇服务工件生命周期一文,详细了解范围)。发生导航时,网络线程会将网域与已注册的服务工件作用域进行检查,如果为该网址注册了服务工件,界面线程会查找渲染程序进程以执行服务工件代码。服务工件可以从缓存加载数据,从而无需从网络请求数据,也可以从网络请求新资源。

ServiceWorker 导航
图 11:浏览器进程中的界面线程启动渲染程序进程以处理服务工作器;然后,渲染程序进程中的工作器线程从网络请求数据

您可以看到,如果服务工件最终决定从网络请求数据,浏览器进程和渲染程序之间的此往返过程可能会导致延迟。导航预加载是一种机制,可通过在服务工件启动时并行加载资源来加快此过程。它会使用标头标记这些请求,以便服务器决定为这些请求发送不同的内容;例如,仅发送更新后的数据,而不是完整文档。

导航栏预加载
图 12:浏览器进程中的界面线程启动渲染程序进程来处理服务工作器,同时并发启动网络请求

小结

在本博文中,我们探讨了导航期间会发生的情况,以及 Web 应用代码(例如响应标头和客户端 JavaScript)如何与浏览器互动。了解浏览器从网络获取数据的步骤有助于您更轻松地理解为何开发了导航预加载等 API。在下一篇文章中,我们将深入探讨浏览器如何评估我们的 HTML/CSS/JavaScript 以呈现网页。

您喜欢这篇文章吗?如果您对日后发布的博文有任何疑问或建议,欢迎在下方的评论区留言,或在 Twitter 上通过 @kosamari 与我联系。

下一篇:渲染程序的内部运作方式