本系列博文由 4 部分组成,本文将重点介绍 Chrome 的内部运作方式,本文为第 2 部分。 在上一篇博文中,我们了解了不同进程和线程如何处理浏览器的不同部分。在这篇博文中,我们将深入探讨每个进程和线程如何通信以显示网站。
我们来看一个简单的网络浏览用例:您在浏览器中输入网址,然后浏览器从互联网提取数据并显示网页。在这篇博文中,我们将重点关注用户请求访问网站,然后浏览器准备呈现网页的部分(也称为导航)。
图 1:顶部的浏览器界面,底部的浏览器进程示意图,界面、网络和存储线程
正如我们在第 1 部分:CPU、GPU、内存和多进程架构中所介绍的,标签页以外的一切都由浏览器进程处理。浏览器进程具有如下线程:用于绘制浏览器的按钮和输入字段的界面线程、负责处理网络堆栈以从互联网接收数据的网络线程、用于控制文件访问的存储线程等。当您在地址栏中输入网址时,输入由浏览器进程的界面线程处理。
当用户开始在地址栏中输入内容时,界面线程会问的第一个问题是“这是搜索查询或网址吗?”。在 Chrome 中,地址栏也是一个搜索输入字段,因此界面线程需要解析并决定是将您转到搜索引擎还是您请求的网站。
图 1:询问输入项是搜索查询还是网址的界面线程
当用户点击 Enter 键时,界面线程会发起网络调用来获取网站内容。标签页的一角会显示“正在加载”旋转图标,并且网络线程会通过适当的协议(例如 DNS 查找)并为请求建立 TLS 连接。
图 2:与网络线程通信以导航到 mysite.com 的界面线程
此时,网络线程可能会收到类似于 HTTP 301 的服务器重定向标头。在这种情况下,网络线程会与服务器请求重定向的界面线程通信。然后,系统将发起另一个网址请求。
图 3:包含 Content-Type 和载荷(即实际数据)的响应标头
当响应正文(载荷)开始传入后,网络线程会根据需要查看流的前几个字节。响应的 Content-Type 标头应该会显示具体的数据类型,但由于可能缺失或错误,因此请在此处完成 MIME 类型嗅探。正如源代码中所述,这是一种“棘手的业务”。 您可以阅读相关评论,了解不同浏览器如何处理内容类型/载荷对。
如果响应是 HTML 文件,下一步就是将数据传递给渲染程序进程;但如果它是 ZIP 文件或其他某个文件,则意味着它是一个下载请求,所以它们需要将数据传递给下载管理器。
图 4:网络线程询问响应数据是否为来自安全网站的 HTML
系统还会在此处进行SafeBrowsing检查。 如果该网域和响应数据似乎与某个已知的恶意网站匹配,则网络线程会发出提醒并显示一个警告页面。此外,系统会进行CCOrigin Cead Clocking (C) 检查,以确保敏感的跨网站数据不会进入渲染程序进程。
在完成所有检查且网络线程确信浏览器应导航到所请求的网站后,网络线程会告知界面线程数据已准备就绪。然后,界面线程会找到渲染程序进程来继续渲染网页。
图 5:告知界面线程查找渲染程序进程的网络线程
由于网络请求可能需要数百毫秒才能收到响应,因此采用了可加快此过程的优化。当界面线程在第 2 步向网络线程发送网址请求时,已经知道要导航到哪个网站。界面线程会尝试主动查找或启动与网络请求并行的渲染程序进程。这样,如果一切按预期进行,渲染器进程在网络线程接收到数据时就已处于待机状态。如果导航跨网站重定向,则可能不会使用此待机进程(在这种情况下,可能需要其他进程)。
现在数据和渲染程序进程已准备就绪,浏览器进程会向渲染器进程发送 IPC 以提交导航。它还会传递数据流,以便渲染程序进程可以继续接收 HTML 数据。浏览器进程听到在渲染程序进程中发生提交的确认信息后,导航即告完成,文档加载阶段随即开始。
此时,地址栏会更新,安全指示器和网站设置界面会反映新页面的网站信息。系统会更新该标签页的会话历史记录,以便使用“往返”按钮 逐步跳转到用户刚刚访问过的网站为便于您在关闭标签页或窗口时恢复标签页/会话,系统会将会话历史记录存储在磁盘上。
图 6:浏览器与渲染器进程之间的 IPC,用于请求渲染网页
提交导航后,渲染器进程会继续加载资源并渲染页面。我们会在下一篇博文中详细介绍这一阶段会发生什么。渲染程序进程“完成”渲染后,会将 IPC 发送回浏览器进程(在网页中的所有帧上触发所有 onload
事件并完成执行后)。此时,界面线程会停止标签页上的加载旋转图标。
我说“finishes”,因为在此之后,客户端 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。Service Worker 是一种在应用代码中编写网络代理的方法,可让 Web 开发者更好地控制本地缓存的内容以及何时从网络获取新数据。如果 Service Worker 设置为从缓存加载页面,则无需从网络请求数据。
需要注意的重要部分是,Service Worker 是在渲染程序进程中运行的 JavaScript 代码。但是,当导航请求传入时,浏览器进程如何知道网站有 Service Worker?
图 10:浏览器进程中的网络线程查询 Service Worker 范围
注册 Service Worker 后,Service Worker 的作用域将作为参考进行保留(如需详细了解作用域,请参阅 Service Worker 生命周期文章)。导航发生时,网络线程会根据已注册的 Service Worker 作用域检查网域,如果已针对该网址注册了 Service Worker,界面线程会查找渲染程序进程以执行 Service Worker 代码。Service Worker 可以从缓存中加载数据,这样就无需从网络请求数据,也可以从网络请求新资源。
图 11:浏览器进程中的界面线程会启动渲染器进程以处理 Service Worker;然后,渲染器进程中的工作器线程会从网络请求数据
您可以看到,如果 Service Worker 最终决定从网络请求数据,浏览器进程和渲染器进程之间的这种往返可能会导致延迟。Navigation 预加载是一种通过在 Service Worker 启动过程中并行加载资源来加快此过程的机制。它使用标头来标记这些请求,以便服务器决定为这些请求发送不同的内容;例如,只更新数据而不是完整文档。
图 12:浏览器进程中的界面线程会启动渲染程序进程来处理 Service Worker,同时并行启动网络请求
在这篇博文中,我们介绍了导航期间发生的情况,以及您的 Web 应用代码(例如响应标头和客户端 JavaScript)如何与浏览器互动。了解浏览器从网络获取数据所经历的步骤,可以更轻松地理解导航预加载等 API 的开发原因。在下一篇博文中,我们将深入探讨浏览器如何评估 HTML/CSS/JavaScript 以呈现网页。