导航时都发生了什么

这是关于Chrome内部工作原理系列的第2部分。 在上一篇文章中,我们研究了不同的进程与线程是怎样如何处理浏览器不同部分的。 在这一篇中,我们将会深入研究每个进程和线程是如何进行通信以显示网站内容的。

让我们看一下Web浏览的简单用例:你在浏览器中键入URL,然后浏览器从Internet获取数据并显示页面。 在这篇文章中,我们将重点关注用户请求网站的部分以及浏览器准备呈现页面的部分 - 也称为导航。

从浏览器进程开始

正如我们在第1部分(CPU,GPU,内存和多进程架构 )中所描述的,选项卡外部的所有内容都由浏览器进程处理。 浏览器进程具有很多线程,比如于UI线程用于绘制浏览器的按钮和输入框,网络线程负责处理网络堆栈以从互联网接收数据,存储线程控制对文件的访问等。 当在地址栏中键入URL时,你的输入将由浏览器进程的UI线程处理。

Browser processes

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

一个简单的导航过程

第1步:处理输入

当用户开始输入地址栏时,UI线程首先要判断的是“这是搜索查询还是URL?”。 因为在Chrome中,地址栏也是搜索输入框,因此UI线程需要解析并判断是将你的输入发送到搜索引擎还是去请求对应的网站。

Handling user input

图1:UI线程询问输入是搜索查询还是URL

第2步:开始导航

当用户敲回车时,UI线程启动网络调用以获取站点内容。 加载指示图标显示在选项卡的一角,网络线程使用适当的协议,如DNS解析和为请求建立TLS连接。

Navigation start

图2:UI线程与网络线程进行通信以导航到mysite.com

此时,网络线程可以接收像HTTP 301那样的服务器重定向头。在这种情况下,网络线程会通知UI线程服务器正在请求重定向。之后会启动另一个URL请求。

第3步:读取响应

一旦响应主体(有效负载)开始进入,网络线程会在必要时查看流的前几个字节。 响应中的Content-Type头应该说明它是什么类型的数据,但由于它可能丢失或发生错误,所以在这里完成 MIME类型嗅探。 这在源代码的注释中被称为“棘手的事情”。 你可以阅读这些注释,来了解不同的浏览器是如何处理内容类型与有效载荷的。

HTTP response

图3:包含Content-Type和有效负载的响应头,它是实际数据

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

MIME type sniffing

图4:网络线程询问响应数据是否来自安全站点的HTML

这也是进行 SafeBrowsing 检查的地方。 如果域和响应数据似乎与已知的恶意站点匹配,则网络线程会发出警告以显示警告页面。 此外,发生跨源读取阻止(CORB) 检查是为了确保敏感的跨站数据不会进入渲染器进程。

第3步:查找渲染器进程

完成所有检查并且网络线程确信浏览器应该导航到所请求的站点后,网络线程会告知UI线程数据已准备就绪。 然后UI线程找到渲染器进程以进行网页的渲染。

Find renderer process

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

由于网络请求可能需要几百毫秒才能得到响应,所以在这里进行了加速此过程的优化。 当UI线程在第2步向网络线程发送URL请求时,它已经知道他们正在导航到哪个站点。 UI线程尝试与网络请求并行地主动查找或启动渲染器进程。 如果一切按预期进行,当网络线程接收数据时,渲染器进程已处于备用状态。 如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下可能需要不同的进程。

第4步:提交导航

现在数据和渲染器进程已准备就绪,IPC将把导航从浏览器进程发送到渲染器进程以进行提交。它同时还传递数据流,因此渲染器进程可以继续接收HTML数据。 一旦浏览器进确认已经提交到了渲染器进程中,导航就完成了,文档加载阶段就开始了。

此时,地址栏会更新,安全指示器和站点设置UI会反映新页面的站点信息。 选项卡的会话历史记录将更新,因此后退/前进按钮将可以逐步浏览刚导航到的站点。为了便于在关闭选项卡或窗口时能够对选项卡/会话进行还原,会话的历史记录将被存储在磁盘上。

Commit the navigation

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

额外步骤:初始加载完成

提交导航后,渲染器进程继续加载资源并呈现页面。 我们将会在下一篇文章中详细介绍这一阶段的详情。 一旦渲染器进程“完成”渲染,它就会将一个IPC发送回浏览器进程(这发生在所有onload事件触发了页面中的所有帧并完成执行之后)。 此时,UI线程会停止选项卡上的加载指示器。

尽管已经“完成”,不过客户端 JavaScript 仍然可以加载额外的资源并在此之后呈现新的视图。

Page finish loading

图7:渲染器进程通过IPC通知浏览器进程页面已“加载完成”

导航到其他站点

简单的导航完成了! 但是如果用户再次将不同的URL放到地址栏会发生什么? 好吧,浏览器进程会通过相同的步骤导航到不同的站点。 但在它在做到这一点之前,还需要检查当前正在渲染的站点,如果他们关心beforeunload事件的话。

当你尝试重新导航或关闭选项卡时,beforeunload可以创建“要离开这个网站吗?” 警告。 由于选项卡内包含JavaScript代码的所有内容都由渲染器进程处理,因此浏览器进程必须在进行新导航请求时检查当前渲染器进程。

警告:不要添加无条件的beforeunload处理程序。 因为它会产生更多延迟,甚至在启动导航之前需要执行一些处理。 应该仅在需要时添加此事件处理,例如,如果需要警告用户他们可能会丢失在页面上输入的数据时。

beforeunload event handler

图8:浏览器进程通过IPC通知渲染器进程它将要导航到另一个站点

如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript执行window.location =“https://newsite.com”),那么渲染器进程会首先检查beforeunload处理。 然后,它经历与浏览器进程启动导航相同的过程。 唯一的区别是导航请求从渲染器进程发送到浏览器进程。

当新导航进入的站点与当前渲染的站点不同时,将会调用另一个单独的渲染进程来处理新导航,同时保持当前渲染进程以处理unload等事件。 有关更多信息,请参阅页面生命周期状态概述以及如何使用 页面生命周期 API 挂钩事件。

new navigation and unload

图9:从浏览器进程到新渲染器进程的2个IPC,通知新渲染器渲染页面并通知旧渲染器进程卸载

如果是Service Worker

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

要记住的重要一点是Service Worker是在渲染器进程中运行的JavaScript代码。 但是当导航请求到来时,浏览器进程怎么才能知道该站点有Service Worker?

注册Service Worker时,将保留Service Worker的范围作为参考(你可以在“Service Worker生命周期”一文中阅读有关范围的更多信息)。 当导航发生时,网络线程根据注册的Service Worker范围检查域,如果为该URL注册了Service Worker,则UI线程找到渲染器进程来执行Service Worker代码。 Service Worker可以从缓存加载数据,无需从网络请求数据,也可以从网络请求新资源。

Service worker scope lookup

图10:浏览器进程中的网络线程查找Service Worker范围

serviceworker navigation

图11:浏览器进程中的UI线程启动渲染器进程以处理Service Worker; 然后,渲染器进程中的工作线程从网络请求数据

导航预加载

可以看到,如果Service Worker最终决定从网络请求数据,则浏览器进程和渲染器进程之间的往返通信可能会导致延迟。 导航预加载是一种通过与Service Worker并行加载资源来加速此过程的机制。 它用header标记这些请求,允许服务器为这些请求发送不同的内容,例如:只更新部分数据而不是整个文档。

Navigation preload

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

总结

在本文中,我们研究了导航过程中发生的事情,以及响应头和客户端JavaScript等Web应用代码是如何与浏览器交互的。 了解浏览器通过网络获取数据的步骤,可以更容易地理解为什么开发导航预加载等API。 在下一篇文章中,我们将深入探讨浏览器如何处理HTML/ CSS/JavaScript来呈现页面。