导航会发生什么
这是本系列博客的第 2 部分. 在上一篇文章中, 我们研究了不同的进程和线程如何处理浏览器的不同部分. 在这篇文章中, 我们深入将深入研究每个进程和线程如何进行通信以显示网站.
让我们看一下 web 浏览的简单用例: 您在浏览器中键入 URL, 然后浏览器从 Internet 获取数据并显示页面. 在这篇文章中, 我们将重点关注用户请求网站的部分以及浏览器准备呈现页面的部分 - 也称为导航.
从浏览器进程开始
正如我们在 第 1 部分中所述: CPU,GPU, 内存和多进程架构, 选项卡外部的所有内容都由浏览器进程处理. 浏览器进程具有诸如 UI 线程之类的线程, 其绘制浏览器的按钮和输入字段, 处理网络堆栈从互联网接收数据的网络线程, 控制对文件的访问的存储线程等. 当您在地址栏中键入 URL 时, 您的输入将先由浏览器进程的 UI 线程处理.
图 1: 浏览器 UI, 网络和存储线程
一个简单的导航
1. 处理输入
当用户开始在地址栏输入时, UI 线程首先要知道的是 "这是搜索查询还是 URL?". 在 Chrome 中, 地址栏也可以输入搜索字段, 因此 UI 线程需要解析并确定是将您的输入发送到搜索引擎还是发送到您请求的网站.
图 2:UI 线程询问输入是搜索查询还是 URL
2. 开始导航
当用户点击回车时, UI 线程启动网络调用以获取站点内容. 加载标志显示在选项卡的一侧, 网络线程通过适当的协议, 如 DNS 为请求来查找和建立 TLS 连接.
图 3:UI 线程与网络线程通信以导航到 mysite
此时, 网络线程可以接收像 HTTP 301 这样的服务器重定向头. 比如, 当服务器请求重定向时, 网络线程会与 UI 线程通信, 然后启动另一个 URL 请求.
3. 读取服务器响应
一旦响应主体 (有效载荷) 开始进入, 网络线程会在必要时查看流的前几个字节. 响应的 Content-Type 字段应该说明它是什么类型的数据, 但由于它可能丢失或错误, 所以在这里会完成 MIME 类型嗅探. 这是源代码中注释的 "棘手的业务" 源代码. 您可以阅读注释, 以了解不同的浏览器如何处理内容类型 / 有效载荷对.
图 3: 包含 Content-Type 和有效载荷的响应头
如果响应是 html 文件, 那么下一步就是将数据传递给渲染器进程, 但如果它是 zip 文件或其他文件, 则表示它是下载请求, 因此需要将数据传递给下载管理器.
图 4: 网络线程询问响应数据是否是来自安全站点的 HTML
这也是 SafeBrowsing 检查发生的地方. 如果域和响应数据似乎与已知的恶意站点匹配, 则网络线程会发出警告以显示警告页面. 此外, 还会发生 Cross Origin Read Blocking(CORB)检查, 以确保敏感的跨站点数据无法进入渲染器进程.
4. 查找渲染器进程
完成所有检查并且网络线程确信浏览器应导航到所请求的站点后, 网络线程会告知 UI 线程数据已准备就绪. 然后, UI 线程找到渲染器进程以进行网页的渲染.
图 5: 网络线程与 ui 线程
由于网络请求可能需要几百毫秒才能得到响应, 因此做了加速此过程的优化. 当 UI 线程在步骤 2 向网络线程发送 URL 请求时, 它已经知道他们正在导航到哪个站点. UI 线程尝试与网络请求并行地主动查找或启动渲染器进程. 这样, 如果一切按预期进行, 则当网络线程接收数据时, 渲染器进程已处于备用位置. 如果导航重定向跨站点, 则可能不会使用此备用进程, 在这种情况下可能需要其他不同的进程;
5. 提交导航
现在数据和渲染器进程已准备就绪, 浏览器进程将通过 IPC 发送到渲染器进程以提交导航. 它还会传递数据流, 因此渲染器进程可以继续接收 HTML 数据. 一旦浏览器进程监听到确认提交已在渲染器进程中发生, 导航就完成了, 文档加载阶段就开始了.
此时, 地址栏会更新, 安全指示器和站点设置 UI 会反映新页面的站点信息. 选项卡的会话历史记录将被更新, 因此后退 / 前进按钮也会被应用新的记录. 为了在关闭选项卡或窗口时简化选项卡 / 会话还原, 会话历史记录会存储在磁盘上.
图 6: 浏览器进程和渲染器进程间的 ipc, 请求渲染页面
最后, 初始化加载完成
提交导航后, 渲染器进程继续加载资源并呈现页面. 我们将在下一篇文章中详细介绍现阶段的情况. 一旦渲染器进程 "完成" 渲染, 它就会将一个 IPC 发送回浏览器进程(这是在所有 onload 事件触发了包含页面中的所有 frame, 并且完成执行 event handler 之后). 此时, UI 线程会停止选项卡上的加载标志. 我说的 "完成", 因为客户端 JavaScript 仍然可以加载额外的资源并在此之后呈现新的视图.
图 7: 渲染器进程告知浏览器进程渲染完成
导航到其他的站点
简单的导航到此已经完成, 但是当用户在地址栏再次键入了其他的地址将会发生什么呢? 是的, 浏览器进程会进行相同的步骤去导航到不同的网址; 但是, 在这之前, 浏览器进程会检查当前被渲染的网站是否具有 beforeupload.
beforeupload 会创建一个 "你确定要离开此站点" 的警告当你想导航到其他的站点或者关闭这个标签; 你的所有 JavaScript 代码将被渲染器进程处理, 所以浏览器进程必须在新导航进入的时候检查当前的渲染器进程;
警告: 不要添加无条件的 beforeunload 事件处理程序. 它会产生更多延迟, 因为在启动导航之前需要执行处理程序. 应仅在需要时添加此事件处理程序, 例如, 警告用户他们可能会丢失他们在页面上输入的数据.
图 8: 浏览器进程告知渲染器进程导航将要变更
如果导航是从渲染器进程启动的(如用户单击链接或客户端 JavaScript 已运行 Windows.location ="https://newsite.com"), 则渲染器进程首先检查 beforeunload 处理程序. 然后, 它进行与浏览器进程启动导航相同的步骤. 唯一的区别是导航请求从渲染器进程到浏览器进程触发的.
当新导航进入与当前渲染不同的站点时, 将调用单独的渲染过程来处理新导航, 同时保持当前渲染过程以处理卸载等事件. 有关更多信息, 请参阅页面生命周期状态概述以及如何使用 Page Lifecycle API 钩子事件.
图 9: 浏览器告知渲染器进程需要渲染页面和卸载页面
service worker
最近有关导航的一个改变是引入了 service work.service work 是一种在应用程序代码中编写网络代理的方法; 允许 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 最终决定从网络请求数据, 则浏览器进程渲染器进程之间的往返可能会导致延迟. 导航预加载 ( Navigation Preload) 是一种通过与 service worker 并行加载资源来加速此过程的机制. 它会标记这些请求, 允许服务器决定为这些请求发送不同的内容; 例如, 只更新数据而不是完整文档.
图 12: 浏览器进程中的 UI 线程启动渲染器进程以在并行的启动网络请求的同时处理 service work
总结
在这篇文章中, 我们研究了导航过程中发生的情况以及响应头和客户端 JavaScript 等 Web 应用程序代码如何与浏览器交互. 了解浏览器通过网络获取数据的步骤, 可以更容易地理解为什么开发导航预加载等 API. 在下一篇文章中, 我们将深入探讨浏览器如何处理 HTML / CSS / JavaScript 以呈现页面.
来源: https://juejin.im/post/5baee9c46fb9a05ce95c6b4e