共计 3564 个字符,预计需要花费 9 分钟才能阅读完成。
翻译:疯狂的技术宅原文:https://developers.google.com…
导航时都发生了什么
这是关于 Chrome 内部工作原理系列的第 2 部分。在上一篇文章中,我们研究了不同的进程与线程是怎样如何处理浏览器不同部分的。在这一篇中,我们将会深入研究每个进程和线程是如何进行通信以显示网站内容的。
让我们看一下 Web 浏览的简单用例:你在浏览器中键入 URL,然后浏览器从 Internet 获取数据并显示页面。在这篇文章中,我们将重点关注用户请求网站的部分以及浏览器准备呈现页面的部分 – 也称为导航。
从浏览器进程开始
正如我们在第 1 部分(CPU,GPU,内存和多进程架构)中所描述的,选项卡外部的所有内容都由浏览器进程处理。浏览器进程具有很多线程,比如于 UI 线程用于绘制浏览器的按钮和输入框,网络线程负责处理网络堆栈以从互联网接收数据,存储线程控制对文件的访问等。当在地址栏中键入 URL 时,你的输入将由浏览器进程的 UI 线程处理。
图 1:顶部的浏览器 UI,底部有 UI,网络和存储线程的浏览器进程图
一个简单的导航过程
第 1 步:处理输入
当用户开始输入地址栏时,UI 线程首先要判断的是“这是搜索查询还是 URL?”。因为在 Chrome 中,地址栏也是搜索输入框,因此 UI 线程需要解析并判断是将你的输入发送到搜索引擎还是去请求对应的网站。
图 1:UI 线程询问输入是搜索查询还是 URL
第 2 步:开始导航
当用户敲回车时,UI 线程启动网络调用以获取站点内容。加载指示图标显示在选项卡的一角,网络线程使用适当的协议,如 DNS 解析和为请求建立 TLS 连接。
图 2:UI 线程与网络线程进行通信以导航到 mysite.com
此时,网络线程可以接收像 HTTP 301 那样的服务器重定向头。在这种情况下,网络线程会通知 UI 线程服务器正在请求重定向。之后会启动另一个 URL 请求。
第 3 步:读取响应
一旦响应主体(有效负载)开始进入,网络线程会在必要时查看流的前几个字节。响应中的 Content-Type 头应该说明它是什么类型的数据,但由于它可能丢失或发生错误,所以在这里完成 MIME 类型嗅探。这在源代码的注释中被称为“棘手的事情”。你可以阅读这些注释,来了解不同的浏览器是如何处理内容类型与有效载荷的。
图 3:包含 Content-Type 和有效负载的响应头,它是实际数据
如果响应是 HTML 文件,那么下一步就是将数据传递给渲染器进程,但如果它是 zip 文件或其他文件,则表示它是一个下载请求,因此需要将数据传递给 下载管理器。
图 4:网络线程询问响应数据是否来自安全站点的 HTML
这也是进行 SafeBrowsing 检查的地方。如果域和响应数据似乎与已知的恶意站点匹配,则网络线程会发出警告以显示警告页面。此外,发生跨源读取阻止(CORB)检查是为了确保敏感的跨站数据不会进入渲染器进程。
第 3 步:查找渲染器进程
完成所有检查并且网络线程确信浏览器应该导航到所请求的站点后,网络线程会告知 UI 线程数据已准备就绪。然后 UI 线程找到渲染器进程以进行网页的渲染。
图 5:网络线程告诉 UI 线程找到渲染进程
由于网络请求可能需要几百毫秒才能得到响应,所以在这里进行了加速此过程的优化。当 UI 线程在第 2 步向网络线程发送 URL 请求时,它已经知道他们正在导航到哪个站点。UI 线程尝试与网络请求并行地主动查找或启动渲染器进程。如果一切按预期进行,当网络线程接收数据时,渲染器进程已处于备用状态。如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下可能需要不同的进程。
第 4 步:提交导航
现在数据和渲染器进程已准备就绪,IPC 将把导航从浏览器进程发送到渲染器进程以进行提交。它同时还传递数据流,因此渲染器进程可以继续接收 HTML 数据。一旦浏览器进确认已经提交到了渲染器进程中,导航就完成了,文档加载阶段就开始了。
此时,地址栏会更新,安全指示器和站点设置 UI 会反映新页面的站点信息。选项卡的会话历史记录将更新,因此后退 / 前进按钮将可以逐步浏览刚导航到的站点。为了便于在关闭选项卡或窗口时能够对选项卡 / 会话进行还原,会话的历史记录将被存储在磁盘上。
图 6:浏览器和渲染器进程之间的 IPC,请求呈现页面
额外步骤:初始加载完成
提交导航后,渲染器进程继续加载资源并呈现页面。我们将会在下一篇文章中详细介绍这一阶段的详情。一旦渲染器进程“完成”渲染,它就会将一个 IPC 发送回浏览器进程(这发生在所有 onload 事件触发了页面中的所有帧并完成执行之后)。此时,UI 线程会停止选项卡上的加载指示器。
尽管已经“完成”,不过客户端 JavaScript 仍然可以加载额外的资源并在此之后呈现新的视图。
图 7:渲染器进程通过 IPC 通知浏览器进程页面已“加载完成”
导航到其他站点
简单的导航完成了!但是如果用户再次将不同的 URL 放到地址栏会发生什么?好吧,浏览器进程会通过相同的步骤导航到不同的站点。但在它在做到这一点之前,还需要检查当前正在渲染的站点,如果他们关心 beforeunload 事件的话。
当你尝试重新导航或关闭选项卡时,beforeunload 可以创建“要离开这个网站吗?”警告。由于选项卡内包含 JavaScript 代码的所有内容都由渲染器进程处理,因此浏览器进程必须在进行新导航请求时检查当前渲染器进程。
警告:不要添加无条件的 beforeunload 处理程序。因为它会产生更多延迟,甚至在启动导航之前需要执行一些处理。应该仅在需要时添加此事件处理,例如,如果需要警告用户他们可能会丢失在页面上输入的数据时。
图 8:浏览器进程通过 IPC 通知渲染器进程它将要导航到另一个站点
如果导航是从渲染器进程启动的(例如用户单击链接或客户端 JavaScript 执行 window.location =”https://newsite.com”),那么渲染器进程会首先检查 beforeunload 处理。然后,它经历与浏览器进程启动导航相同的过程。唯一的区别是导航请求从渲染器进程发送到浏览器进程。
当新导航进入的站点与当前渲染的站点不同时,将会调用另一个单独的渲染进程来处理新导航,同时保持当前渲染进程以处理 unload 等事件。有关更多信息,请参阅页面生命周期状态概述以及如何使用 页面生命周期 API 挂钩事件。
图 9:从浏览器进程到新渲染器进程的 2 个 IPC,通知新渲染器渲染页面并通知旧渲染器进程卸载
如果是 Service Worker
最近对该导航过程的一个改变是引入了 service worker。service worker 是一种在应用代码中编写网络代理的方法;它允许 Web 开发人员更好地控制本地缓存内容以及何时从网络获取新数据。如果将 service worker 设置为从缓存加载页面,则无需从网络请求数据。
要记住的重要一点是 Service Worker 是在渲染器进程中运行的 JavaScript 代码。但是当导航请求到来时,浏览器进程怎么才能知道该站点有 Service Worker?
图 10:浏览器进程中的网络线程查找 Service Worker 范围
注册 Service Worker 时,将保留 Service Worker 的范围作为参考(你可以在“Service Worker 生命周期”一文中阅读有关范围的更多信息)。当导航发生时,网络线程根据注册的 Service Worker 范围检查域,如果为该 URL 注册了 Service Worker,则 UI 线程找到渲染器进程来执行 Service Worker 代码。Service Worker 可以从缓存加载数据,无需从网络请求数据,也可以从网络请求新资源。
图 11:浏览器进程中的 UI 线程启动渲染器进程以处理 Service Worker; 然后,渲染器进程中的工作线程从网络请求数据
导航预加载
可以看到,如果 Service Worker 最终决定从网络请求数据,则浏览器进程和渲染器进程之间的往返通信可能会导致延迟。导航预加载是一种通过与 Service Worker 并行加载资源来加速此过程的机制。它用 header 标记这些请求,允许服务器为这些请求发送不同的内容,例如:只更新部分数据而不是整个文档。
图 12:浏览器进程中的 UI 线程启动渲染器进程,在并行启动网络请求的同时处理 Service Worker
总结
在本文中,我们研究了导航过程中发生的事情,以及响应头和客户端 JavaScript 等 Web 应用代码是如何与浏览器交互的。了解浏览器通过网络获取数据的步骤,可以更容易地理解为什么开发导航预加载等 API。在下一篇文章中,我们将深入探讨浏览器如何处理 HTML/ CSS/JavaScript 来呈现页面。
本文首发微信公众号:jingchengyideng 点击下面链接查看其它章节文章
现代浏览器探秘 (part1):架构
现代浏览器探秘 (part2):导航
现代浏览器探秘 (part3):渲染