深入了解现代网络浏览器(第 2 部分)
导航中会发生什么
在这篇文章中,我们将深入探讨了每个进程和线程如何进行通信以显示网站。
让我们看一个简单的网页浏览用例:你在浏览器中输入一个 URL,然后浏览器从互联网上获取数据并显示一个页面。在这篇文章中,我们将重点关注用户请求站点和浏览器准备呈现页面的部分——也称为导航。
它从浏览器进程开始
正如我们在 第 1 部分:CPU、GPU、内存和多进程架构中介绍的那样,选项卡之外的所有内容都由浏览器进程处理。浏览器进程又分为多个线程。
- UI thread : 控制浏览器上的按钮及输入框;
- network thread: 处理网络请求,从网上获取数据;
- storage thread: 控制文件等的访问;
当您在地址栏中键入 URL 时,您的输入由浏览器进程的 UI 线程处理。
image一个简单的导航
第 1 步:处理输入
当用户开始在地址栏中输入内容时,UI 线程询问的第一件事是“这是搜索查询还是 URL?”。在 Chrome 中,地址栏也是一个搜索输入字段,因此 UI 线程需要解析并决定是将您发送到搜索引擎还是您请求的站点。
image第 2 步:开始导航
当用户点击回车时,UI thread 通知 network thread 获取网页内容,并控制 tab 上的 spinner 展现,表示正在加载中。network thread通过适当的协议,如 DNS 查找和为请求建立 TLS 连接。
image此时,网络线程可能会收到 HTTP 301 之类的服务器重定向标头。在这种情况下,网络线程会与服务器正在请求重定向的 UI 线程通信。然后,将发起另一个 URL 请求。
第 3 步:读取响应
一旦响应主体开始加载,网络线程会在必要时查看流的前几个字节。响应的 Content-Type 标头应该说明它是什么类型的数据,但由于它可能丢失或错误, 因此在这里进行MIME 类型嗅探。如源代码中所述,这是一项“棘手的业务” 。您可以阅读评论以了解不同的浏览器如何处理内容类型/有效负载。
image如果响应是一个 HTML 文件,那么下一步就是将数据传递给渲染器进程,但如果它是一个 zip 文件或其他文件,那么这意味着它是一个下载请求,所以他们需要将数据传递给下载管理器。
image这也是进行安全浏览检查的地方。如果域和响应数据似乎与已知的恶意站点相匹配,则网络线程会发出警报以显示警告页面。此外, 发生跨源读取 (CORB)检查是为了确保敏感的 跨站点数据不会进入渲染器进程。
第 4 步:查找渲染器进程
一旦完成所有检查并且网络线程确信浏览器应该导航到请求的站点,网络线程就会告诉 UI 线程数据已准备好。UI线程然后找到一个渲染器进程来进行网页的渲染。
image由于网络请求可能需要数百毫秒才能得到响应,因此应用了加速此过程的优化。当 UI 线程在第 2 步向网络线程发送 URL 请求时,它已经知道他们正在导航到哪个站点。UI 线程尝试主动查找或启动与网络请求并行的渲染器进程。这样,如果一切按预期进行,当网络线程接收到数据时,渲染器进程已经处于待机位置。如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下,可能需要不同的进程。
第 5 步:提交导航
现在数据和渲染器进程已准备就绪,从浏览器进程向渲染器进程发送 IPC 以提交导航。它还传递数据流,因此渲染器进程可以继续接收 HTML 数据。一旦浏览器进程听到在渲染器进程中发生提交的确认,导航就完成了,文档加载阶段开始了。
此时,地址栏更新,安全指示器和站点设置 UI 反映了新页面的站点信息。该选项卡的会话历史记录将被更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了在您关闭选项卡或窗口时促进选项卡/会话恢复,会话历史记录存储在磁盘上。
image额外步骤:初始加载完成
提交导航后,渲染器进程会继续加载资源并渲染页面。我们将在下一篇文章中详细介绍此阶段发生的事情。一旦渲染器进程“完成”渲染,它会通过 IPC 将信息发送回浏览器进程(这是在 onload页面中的所有帧上触发所有事件并完成执行之后)。此时,UI 线程停止选项卡上的加载效果。
在此之后客户端仍然可以加载额外的资源并呈现新视图。
image导航到其他站点
简单的导航就完成了!但是如果用户再次将不同的 URL 放到地址栏会发生什么?好吧,浏览器进程通过相同的步骤导航到不同的站点。但在此之前,它需要检查当前呈现的站点是否关心 beforeunload事件。
beforeunload可以创建“离开此站点?” 当您尝试离开或关闭选项卡时发出警报。选项卡内的所有内容,包括您的 JavaScript 代码,都由渲染器进程处理,因此当新的导航请求进来时,浏览器进程必须检查当前的渲染器进程。
image如果导航是从渲染器进程启动的(例如用户单击链接或客户端 JavaScript 已运行window.location = "https://newsite.com"),则渲染器进程首先检查beforeunload处理程序。然后,它经历与浏览器进程启动导航相同的过程。唯一的区别是导航请求是从渲染器进程启动到浏览器进程的。
当新导航指向与当前呈现的站点不同的站点时,会调用一个单独的呈现进程来处理新的导航,而当前的呈现进程会被保留以处理诸如unload. 有关更多信息,请参阅页面生命周期状态概述 以及如何使用 页面生命周期 APIHook事件。
image如果是 Service Worker
这个导航过程最近的一个变化是引入了 service worker。Service Worker 是一种在应用程序代码中编写网络代理的方法;允许 Web 开发人员更好地控制本地缓存的内容以及何时从网络获取新数据。如果 Service Worker 设置为从缓存中加载页面,则无需向网络请求数据。
要记住的重要部分是service worker是在渲染器进程中运行的 JavaScript 代码。但是当导航请求进来时,浏览器进程如何知道站点有service worker?
image注册 Service Worker 时,Service Worker 的范围作为参考保留(您可以在这篇The Service Worker 生命周期文章中阅读有关范围的更多信息 )。当导航发生时,网络线程会根据注册的service worker范围检查域,如果为该 URL 注册了service worker,UI 线程会找到一个渲染器进程以执行service worker代码。Service Worker 可以从缓存中加载数据,从而无需从网络请求数据,或者它可以从网络请求新资源。
image导航预载
如果service worker最终决定从网络请求数据,您可以看到浏览器进程和渲染器进程之间的这种往返可能会导致延迟。 Navigation Preload是一种通过在service worker启动时并行加载资源来加速此过程的机制。它用标头标记这些请求,允许服务器决定为这些请求发送不同的内容;例如,仅更新数据而不是完整文档。
image