现代浏览器内部机制 Part 2 | 导航这件小事

原文: Inside Look at Modern Web Browser (part 2)

做者: Mariko Kosakaweb

译者: kyrieliusegmentfault

本文是这个系列的第二篇文章,会深刻到 Chrome 的内部工做。在上一篇文章中,咱们了解了线程和进程在浏览器中的不一样,而在这篇文章中,咱们会更加深刻的了解当浏览器为用户呈现一个页面时,这些进程和线程之间是如何通讯的。 api

让咱们以一个常见的例子做为起点:输入一个 url,浏览器会从服务端获取数据并将页面展现出来。本文会聚焦在用户经过浏览器向一个站点发起访问请求以及浏览器准备渲染这个页面的部分,这个过程我称之为导航。浏览器

以一个浏览器进程为起点

咱们在上一篇文章中提过,全部处于窗口以外的部分都由同一个浏览器进程进行掌管。浏览器的进程又同时拥有许多线程,掌管浏览器的不一样部分:UI 线程用来绘制顶部的操做按钮和输入框、网络线程负责处理并接收来自互联网的数据、存储线程控制着访问本地文件的权限等。当你将一个网站的 url 输入到浏览器的地址栏时,此刻正是浏览器进程中的 UI 线程在起做用。缓存

一次简单的导航

Step 1:处理用户输入

当用户开始在地址栏输入时,UI 线程首先会问:“大兄弟,你输入的是个查询字符串仍是网站地址?”。由于 Chrome 的地址栏同时仍是个搜索框,因此 UI 线程须要解析用户的输入,才能决定该直接访问网址仍是把用户的输入丢给搜索引擎处理。安全

Step 2:开始导航

当用户按下回车键后,UI 线程要求网络线程去获取网站的内容。窗口的 Tab 上会开始转菊花,网络线程会采用一系列的协议和操做(好比 DNS)查询必要的信息并为请求创建链接。 服务器

此时,网络线程可能会收到来自服务器的一个标记着重定向指令的头部好比 HTTP 301,在这种状况下,网络线程会把这件事情告诉 UI 线程,以后则会发起一次指向重定向地址的新的网络请求。网络

Step 3:读取响应

当响应的数据开始传送到浏览器时,网络线程会在必要的状况下检查一些来自响应的字段。响应数据的 Content-Type 字段会表示当前返回的是哪一种类型的数据,但它也不彻底靠谱,常常会出现丢失或者干脆不许确的状况,但也不用担忧,MIME 嗅探会完成确缺失的工做。正如源码的注释中写道,这是一个能够被解释为 hack 的方案,若是感兴趣的话,你也能够去阅读这些注释,这样就能了解不一样的浏览器是如何将实际的数据与 Content-Type 匹配了。 session

若是响应数据是一个 HTML 文件,那么接下来的一步会是把数据传递给浏览器的渲染进程;但若是数据是 zip 压缩文件或其余类型的文件,意味着这将被定位成一次下载动做,因而浏览器会将数据转交给下载管理器去处理。 多线程

一般这一步也是安全检测发生的时候:若是域名或响应数据和已知的恶意网站匹配时,网络进程会抛出一个警告,并展示一个告警的页面。另外,CORB 检测也会开始工做,确保那些来自敏感站点的跨站响应数据不会进入到浏览器的渲染进程中。

Step 4:渲染进程

网络线程以获取了所有的数据,并完成了全部须要的检查,此刻它自信的告诉 UI 线程:“小兄弟,数据准备好了!”。接着,UI 线程会唤起一个渲染进程去渲染页面。

因为网络状况的不可控,一个请求可能会花上好几百毫秒才能把响应数据拿回来,因此这里浏览器默认开启了用来加速这一过程的优化。在 Step 2 中,当 UI 线程将须要请求的 url 告诉网络线程时,其实它自己已经知道要导航到哪一个网站了,因而 UI 线程在把 url 传递给网络线程的同时,会尝试启动一个渲染进程。若是一切都按照预期正常进行的话,当网络线程拿到数据时,渲染进程就已经处于待命状态了。也会有例外的状况:好比导航重定向到一个另外的站点,那么预先启动好的渲染进程将不会被使用,这致使 UI 线程须要从新启动一个渲染进程。

Step 5:触发导航

如今咱们假设数据和渲染进程都准备好了,浏览器进程经过 IPC 告知渲染进程能够出发本次导航了。与此同时,数据流也将传递给渲染进程,这样后者就能继续接收 HTML 数据。一旦浏览器收到了来自渲染进程的导航启动信号,此次导航也就完成了,下一步进入文档的加载阶段。

到这会儿,浏览器的地址栏更新,安全指示符和站点的设置 UI 会将新页面的信息呈现出来。当前窗口的 session 将会更新,刚导航到的页面会被后退/前进按钮记录到窗口的页面历史中。为了便于在关闭窗口时恢复页面,历史的会话记录会保存在本地的磁盘上。

Extra Step:初始加载完成

当导航触发后,渲染进程会持续接收资源并渲染页面。咱们将在下一篇文章中讨论这一步的更多细节。当渲染进程“完成”渲染后,它会经过 IPC 告知浏览器进程(页面的 onload 事件均已执行完毕后),UI 线程也就再也不在 tab 上转菊花了。

上面的“完成”两个字,之因此打了双引号,由于在实际场景中,它一般并不真正意味着完成,由于客户端的 JavaScript 可能在此时持续地加载资源并渲染新的视图。

导航到另外一个网站

一次简单的导航截至目前已经完成了。假如这时用户输入了一个不一样的 url 会发生什么呢?其实也没啥,浏览器进程会按照上面的步骤导航到这个网站。但在这一切开始以前,浏览器会检查当前已经渲染好了的网站是否须要在网页卸载以前搞一点事情,这就是 beforeunload 事件。

beforeunload 事件中,咱们能够在用户即将跳转至其余页面或者关闭 Tab 的时候发起一个“确认离开当前页面?”的二次确认。Tab 中的全部东西都由渲染进程控制着,固然也包括开发者编写的 JavaScript,因此当一个新的导航请求即将到来时,浏览器进程会对当前的渲染进程作最后的检查。

咱们应当尽可能避免在 beforeunload 中添加总会执行的事件代码,这会形成更多的交互延时,毕竟它们总会在新的导航开始以前执行。只在须要的时候添加这些代码,好比提醒用户若是进入新的页面那么当前页面的数据会丢失。

若是导航是在渲染进程中被建立的(好比用户点击了页面上的某一连接或者在 JavaScript 运行了 window.location.href = 'https://kyrieliu.cn' ),则当前的渲染进程会首先检查是 beforeunload 中是否有东西须要执行。以后,它会经历与浏览器进程直接发起导航后同样的导航过程。

当新的导航将发往与当前页面不一样的站点时,浏览器将会建立一个新的渲染进程去处理这些新工做,旧的渲染进程则则用来在剩余的时间里处理诸如 unload 的页面事件。若是你想了解更多的话,能够看看页面生命周期概览页面生命周期 API这两篇文章。

若是有 Service Worker...

Service Worker 的引入会对页面的导航流程带来一些改变。Service Worker 是一种能够在应用代码中编写网络代理的方法;加强了开发者对于本地缓存以及什么时候发起网络请求的控制。若是 Service Worker 提早设置了从本地缓存中读取某一页面的数据,那么也就不须要发起网络请求了。

须要明确的一点是,即便 Service Worker 提供了听起来很高端的功能,但它实质上也是运行在渲染进程中的 JavaScript 代码。那么问题来了:当用户发起一次导航时,浏览器进程是如何知道目标站点存在一个 Service Worker 的呢?

当一个 Service Worker 注册后,它的做用域会保存在一个引用中(你能够经过 Service Worker 的生命周期 这篇文章了解我所说的“做用域”)。当导航发生时,网络线程会依据域名在已注册的 Service Worker 做用域集合中查询,若是找到某个对应的 Service Worker,UI 线程会发起一个渲染进程去执行 Service Worker 中的代码。Service Worker 能够从本地缓存中加载数据(无需发起网络请求),也能够选择经过网络请求获取最新的资源和数据。

导航预加载

相信你能够发现,若是 Service Worker 最终决定从网络中请求数据,那么以前在浏览器进程和渲染进程之间所发生的通讯都将成为致使响应延时的罪魁祸首。导航预加载就是用来加速这一进程的机制:与 Service Worker 并行启动去加载资源。它将为这些请求设置一个 Header,又服务端来决定为这些请求发送不一样的内容;好比,仅返回更新的数据而不是整个文档。

总结

在这篇文章中,咱们检视了在导航时都发生了什么,以及 Web 应用的代码好比响应头和客户端的 JavaScript 代码是如何与浏览器进行交互的。 了解了浏览器是如何一步步从网络中请求数据的,这能让咱们更好的理解不少 API 好比导航预加载的诞生初衷。

在下一篇文章中,咱们会深刻讨论浏览器是如何执行 HTML/CSS/JavaScript 代码从而完成一个页面的渲染的。

相关文章
相关标签/搜索