原文地址:https://developers.google.com/web/updates/2018/09/inside-browser-part2
导航栏发生了什么?
这是介绍Chrome内部工作博客系列的第2部分。在上一篇博客中,我们学习了不同的进程和线程去处理浏览器的不同部分。在这篇文章,我们深入去了解为了展示一个网页每个进程和线程是如何通信的。
我们看一下web浏览器的一个简单示例:当你在浏览器中输入一个url,浏览器从网络获取数据并且显示一个页面。在本文中,我们将重点介绍用户请求站点和浏览器准备渲染一个页面(也称之为导航)。
从浏览器进程开始
正如我们在第一部分中介绍的CPU、GPU和多进程架构,选项卡之外的所有内容都是浏览器进程处理的。浏览器进程具有线程,例如用于绘制按钮和输入部分的UI线程,用于处理网络堆栈和从网络获取数据的网络线程,用于控制访问文件的存储线程。在地址栏中输入URL,你的输入将由浏览器进程的UI线程处理。
简单的导航
步骤1:处理输入
当一个用户开始在地址栏中输入内容,UI线程首先问“这是搜索还是一个URL?”。在Chrome中,地址栏也是一个搜索输入区域,因此UI线程需要解析并且决定将内容发送给搜索引擎,还是你请求的网站。
步骤2:开始导航
当用户按下Enter键,UI线程会开始调用网络请求去获取站点内容。在选项卡的角上显示loading旋转,网络线程通过相应的协议,例如DNS查找,为请求建立TLS连接。
此时,网络线程可能会收到服务器的重定向头比如http 301。在这种情况下,网络线程与UI线程开始通信,服务器请求重定向。然后,会启动另一个URL请求。
步骤3:读取响应
一旦响应体开始回来,网络进程将在必要的时候查看流的前几个字节。响应的Content-Type头会说明是哪种数据类型,但是可能会丢失或者错误,MIME Type嗅探会在这里完成。就像源码中描述的一样这是一个“棘手的业务”。你可以阅读注释去了解不同浏览器如何处理content-type/payload对。
MIME Type嗅探MDN解释:
在缺失 MIME 类型或客户端认为文件设置了错误的 MIME 类型时,浏览器可能会通过查看资源来进行MIME嗅探。每一个浏览器在不同的情况下会执行不同的操作。因为这个操作会有一些安全问题,有的 MIME 类型表示可执行内容而有些是不可执行内容。浏览器可以通过请求头Content-Type
来设置X-Content-Type-Options
以阻止MIME嗅探。
如果响应是一个HTML文件,那么下一步需要将数据传递到渲染进程,但是如果是zip文件或者其他文件,则意味着这是一个下载请求,因此需要将数据传递到下载管理器。
也是在这里进行安全浏览检查。如果域名和响应数据与已知的恶意站点匹配,那么网络进程会弹出一个警告去显示一个警告页。此外,Cross Origin Read Blocking(CORB)触发是为了确保敏感站点数据不传递给渲染进程。
在谷歌的官网是这么解释的:
Cross-Origin Read Blocking (CORB) is an algorithm that can identify and block dubious cross-origin resource loads in web browsers before they reach the web page. CORB reduces the risk of leaking sensitive data by keeping it further from cross-origin web pages. In most browsers, it keeps such data out of untrusted script execution contexts. In browsers with Site Isolation, it can keep such data out of untrusted renderer processes entirely, helping even against side channel attacks like Spectre.
步骤4:查找渲染进程
当所有的检查都完成,并且网络线程确认导航到请求的站点,网络线程就会通知UI线程数据准备好了。UI线程就会查找一个渲染进程负责渲染web页面。
由于网络强求需要花费数百毫秒才能得到响应,因此有一个优化用来加快此进程。当UI线程在第2步发送一个URL请求给网络线程的时候,它一句知道了需要导航到的站点是哪个。UI线程主动地尝试查找,并且启动一个与网络请求并行的渲染进程。这样,如果一切符合预期,当一个网络进程接收到数据的时候,渲染进程已经处于准备好的状态。如果导航重定向跨站点那么该准备好的进程就无法使用,在这种情况下,可能需要另外一个进程。
步骤5:提交导航
现在数据和渲染进程都已经准备好,从浏览器进程往渲染进程发送一个IPC用于提交导航。它也会传递数据流,因此渲染进程可以不断接收HTML数据。一旦浏览器进程收到确认提交在渲染进程发生,导航完成并且文档加载阶段开始。
此时,地址栏会更新,安全指示和站点设置UI将会反应出新页面的站点信息。选项卡的会话历史将会engine,因此“前进/后退”按钮将会单步浏览刚刚导航的站点。为了便于恢复,你关闭的一个选项卡或者窗口,会话历史会被存储在硬盘上。
额外步骤:初始化记载完成
导航提交后,渲染进程将继续加载资源并且渲染页面。我们将在下一篇文章中详细介绍在这个阶段发生了什么。当渲染进程“完成”渲染,它会将一个IPC发送回浏览器进程(这里是当页面所有frame的onload事件触发并且执行完成)。此时,UI进程将停止在选项卡上加载loading旋转。
我说的“完成”,因为在此时客户端Javscript可以继续加载额外的资源,并且渲染新的视图。
导航到其他站点
简单的导航完成了!但是如果一个用户再次在地址栏输入了另外的URL会发生什么?当然,浏览器进程要经过相同的步骤导航到另外的站点。但在此之前,它需要检查当前渲染的站点,如果它们有 beforeunload 的相关事件。
当你尝试离开或者关闭选项卡的时候,** beforeunload **可以创建“离开此站点吗”的警告。选项卡中的所有内容包含Javascript代码都是渲染进程处理的,因此当一个新的导航请求进来时,浏览器进程必须检查当前的渲染进程。
⚠️ 警告:不要添加无条件的 **beforeunload **处理。因为需要在导航前执行处理程序,因为创建了更多的延迟。这个事件处理仅仅在需要的时候再去添加,例如需要警告用户可能会丢失她们在页面上输入的数据。
如果导航是从渲染进程启动的(例如用户点击了一个链接或者客户端javascript运行了 window.location = "https://newsite.com"),渲染进程会首先检查 **beforeunload **处理程序。然后,它将会经过与浏览器进程启动导航的相同步骤。唯一的区别就是导航请求从渲染进程到浏览器进程启动。
当新导航到一个与当前渲染的不同站点时,将会调用一个独立的渲染进程去处理新的导航,当前的渲染进程会继续处理像 **unload **之类的事件。更多信息可以查看 页面生命周期转台概述,以及如何使用 页面生命周期API 。
Service Worker 的情况下
这种导航进程的最近更新是引入了Service Worker。Service Worker是在你的应用程序中编写网络代理的一种方式;允许web开发者更好地控制本地缓存数据和什么时候从网络获取新数据。如果 Service Worker 设置从缓存中读取页面,则无需从网络去请求数据。
需要记住的最重要的部分是 Service Worker 是运行在渲染进程的 Javascript 代码。但是当导航请求进入的时候,但是浏览器进程如何知道站点是否有 Service Worker?
当一个 Service Worker 注册后,Service Worker的作用域会保留一个引用(你可以在Service Worker 生命周期的文章中阅读到更多信息)。当导航开始的时候,网络线程去检查域名是否注册了 Service Worker,如果为一个url注册了Service Worker,UI进程会查找渲染进程去执行Service Worker代码。Service Worker可以从缓存中加载数据,无需从网络中请求数据,或者也可以从网络中请求新的资源。
导航预加载
如果Service Worker最终决定从网络请求数据,那么可以看到在浏览器进程和渲染进程之间的往返会造成延迟。导航预加载 是一种通过Service Worker启动的同时并行加载资源来加快这个过程的机制。它使用标记头标记这些请求,使服务器为这些请求发送不同的内容,例如,只更新数据而不是整个文档。
总结
在这篇文章中,我们学习了导航期间发生了什么,web应用程序比如响应头和客户端Javascript与浏览器是如何交互的。了解浏览器从网络获取数据需要经历的步骤,使得理解为什么开发了例如导航预加载这样的API。在下一篇文章中,我们将深入探讨浏览器如何评估我们的HTML/CSS/JavaScript去渲染页面。