深入了解现代web浏览器(第二部分)
本文为译文,内容尽可能保持和原文的一致性,如果翻译有误,欢迎批评指正。
原文作者:Mariko Kosaka
原文标题:Inside look at modern web browser (part 2)
导航中会发生什么?
这是关于Chrome内部工作原理的4部分博客系列的第2部分。在中,我们研究了不同的进程和线程如何处理浏览器的不同部分。在这篇文章中,我们将深入探讨每个进程和线程如何通信来显示网站。
让我们看一个简单的网页浏览用例:你在浏览器中输入一个URL,然后浏览器从互联网上获取数据并显示一个页面。在这篇文章中,我们将重点介绍用户请求站点和浏览器准备呈现页面的部分--也称为导航。
它从浏览器进程开始
正如我们在第1部分:CPU、GPU、内存和进程架构中介绍的那样,选项卡之外的所有内容都由浏览器进程处理。浏览器进程有一些线程,比如绘制浏览器按钮和输入字段的UI线程,处理网络堆栈以从Internet接收数据的网络线程,控制对文件等访问的存储线程。当你在地址栏中输入URL时,你的输入由浏览器进程的UI线程处理。

图1:顶部的浏览器UI,底部的浏览器进程图,其中包含UI、网络和存储线程
一个简单的导航
第一步:处理输入
当用户开始在地址栏中键入内容时,UI线程首先询问的是“这是搜索查询还是URL?”。在Chrome中,地址栏也是一个搜索输入字段,因此UI线程需要解析并决定是将你发送到搜索引擎,还是发送到你请求的站点。

图2:UI线程询问输入是搜索查询还是URL
第2步:开始导航
当用户按下回车键时,UI线程会发起网络调用以获取站点内容。Loading spinner显示在选项卡的一角,网络线程通过适当的协议,如DNS查找和为请求建立TLS连接。

图3:UI线程和网络线程通信以导航到mysite.com
此时,网络线程可能会接收到服务器重定向标头如HTTP301。在这种情况下,网络线程会和服务器请求重定向的UI线程进行通信,然后将发起另一个URL请求。
第3步:读取响应
一旦响应主体(有效负载)开始进入,网络线程会在必要时查看流的前几个字节。响应的Content-Type标头应该说明它是什么类型的数据,但由于它可能丢失或错误,因此在这里完成MIME类型嗅探。正如源代码中所评论的那样,这是一项“棘手的业务”。你可以阅读评论以了解不同浏览器如何处理内容类型/有效负载对。

图4:包含Content-Type和实际数据的有效负载的响应头
如果响应式一个HTML文件,那么下一步就是将数据传递给渲染器进程,但如果它是一个zip文件或其他一些文件,那么这意味着它是一个下载请求,所以他们需要将数据传递给下载管理器。

图5:网络线程询问数据是否是来自安全站点的HTML
这也是安全浏览检查发生的地方。如果域和相应数据似乎和已知恶意站点匹配,那么网络线程会通过警告页面来发出警告。此外,还会进行跨源读取阻止(CORB)检查,以确保敏感的跨站点数据不会进入渲染器进程。
第4步:查找渲染器进程
一旦完成所有检查并且网络线程确信浏览器应该导航到请求的站点,网络线程就会告诉UI线程数据已准备就绪。UI线程然后找到一个渲染器进程来进行网页的渲染。

图6:网络线程告诉UI线程查找渲染器进程
由于网络请求可能需要数百毫秒才能获得响应,因此应用了优化以加快此过程。当UI线程在第2步向网络线程发送URL请求时,它已经知道他们要导航到哪个站点。UI线程尝试与网络请求并行地主动查找或启动渲染器进程。这样如果一切按照预期进行,当网络线程接收到数据时,渲染器进程已经处于待机状态。如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下,可能需要不同的进程。
第5步:提交导航
现在数据和渲染器进程已经准备就绪,一个IPC从浏览器进程发送到渲染器进程以提交导航。它还传递数据流,因此渲染器进程可以继续接收HTML数据。一旦浏览器进程听到在渲染器进程中发生提交的确认,那么导航就完成了,文档加载阶段开始。
此时地址栏已经更新,安全指示器和站点设置UI反映了新页面的站点信息。该选项卡的会话历史将更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了便于选项卡/会话恢复当在你关闭选项卡或窗口时,会话历史记录会存储在磁盘上。

图7:浏览器和渲染器进程之间的通信,请求渲染页面
额外步骤:初始加载完成
一旦提交导航后,渲染器进程会继续加载资源并渲染页面。我们将在下一篇文章中详细介绍这个阶段发生的事情。渲染器进程“完成”渲染后,它会IPC发送回浏览器进程(这是在页面中所有帧上触发所有onload事件并完成执行之后)。此时,UI线程停止选项卡上的加载微调器。
我说“完成”,因为在此之后客户端JavaScript仍然可以加载额外的资源并呈现新的视图。

图8:IPC从渲染器到浏览器进程通知页面已“加载”
导航到其他站点
简单的导航就完成了!但是如果用户再次将不同的URL放入地址栏会发生什么呢?好吧,浏览器进程通过相同的步骤导航到不同的站点。但是在此之前,它需要检查当前呈现的站点是否关心事件。
beforeunload可以创建“离开次站点?”当你尝试离开或关闭选项卡时发出警报。选项卡内的所有内容(包括你的JavaScript代码)都由渲染器进程处理,因此当新的导航请求传入时,浏览器进程必须检查当前的渲染器进程。
注意:不要添加无条件的beforeunload处理程序。它会产生更多的延迟,因为需要在导航开始之前执行处理程序。仅在需要时才应添加此事件处理程序,例如,如果需要警告用户他们可能会丢失在页面上输入的数据。

图9:从浏览器进程到渲染器进程的IPC通信 告诉它它即将导航到不同的站点
如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript已运行window.location = "https://newsite.com"),则渲染器进程首先检查beforeupload处理程序。然后,它经历与浏览器进程启动导航相同的过程。唯一的区别是导航请求从渲染器进程启动到浏览器进程。
当新导航到达与当前呈现的站点不同的站点时,将调用一个单独的呈现进程来处理新导航,同时保留当前呈现进程以处理卸载等事件。有关更多信息,请参阅页面声明周期状态概述以及如何使用页面生命周期API钩子事件。

图10:2个浏览器进程到新渲染器进程的IPC通信,告诉渲染页面并告诉旧渲染器进程卸载
如果是 Service Worker
最近对该导航过程的一项更改是引入了 。Service Worker 是一种在应用程序代码中编写网络代理的方法;允许 Web 开发人员更好地控制本地缓存的内容以及何时从网络获取新数据。如果 Service Worker 设置为从缓存加载页面,则无需从网络请求数据。
要记住的重要部分是 Service Worker 是在渲染器进程中运行的 JavaScript 代码。但是当导航请求进来时,浏览器进程如何知道该站点有 service worker ?
注册 Service Worker 后,Service Worker 的作用域将作为参考保留(你可以在这篇 The Service Worker Lifecyle 文章中阅读有关作用域的更多信息)。当导航发生时,网络线程会根据已注册的 Service Worker 范围检查域,如果 Service Worker 已为该 URL 注册,则 UI 线程会查找渲染器进程以执行 Service Worker 代码。Service Worker 可能会从缓存中加载数据,从而无需从网络请求数据,或者它可能会从网络请求新资源。

图11: 浏览器进程中的网络线程查找 Service Worker 作用域

图12:浏览器进程中的UI线程启动渲染器进程来处理service worker;渲染器进程中的工作线程然后从网络请求数据
导航预加载
你可以看到,如果 Service Worker 最终决定从网络请求数据,浏览器进程和渲染器进程之间的这种往返可能会导致延迟。是一种通过在 Service Worker 启动的同时加载资源来加速此过程的机制。它用标头标记这些请求,允许服务器决定为这些请求发送不同的内容;例如,只是更新数据而不是完整文档。

图13:浏览器进程中的 UI 线程启动渲染器进程来处理 Service Worker,同时并行启动网络请求
总结
在这篇文章中,我们研究了导航过程中发生的事情,以及你的 web 应用程序代码(例如响应头和客户端 JavaScript)如何与浏览器交互。了解浏览器从网络获取数据的补助可以更容易地理解为什么要开发导航预加载等API。在下一篇文章中,我们将深入探讨浏览器如何评估我们的 HTML/CSS/JavaScript 以呈现页面。
你喜欢这篇文章吗?如果你对以后的帖子有任何问题或建议,我很乐意在下面的评论部分或推特上的 @kosamari 收到你的来信。