导航中发生了什么

这是本系列文章的第二部分。在上一篇文章中,我们探寻了浏览器中进程与线程处理的差异。在本文中,我们将会探究为了渲染一个页面进程与线程是如何去沟通的。

让我们来看一个简单的例子:你在地址栏输入了一段地址,然后浏览器通过网络请求数据,然后渲染至页面。在这篇文章中,我们将重点介绍用户请求网站和浏览器如何准备呈现页面的部分——导航。

从浏览器进程开始

正如第一篇文章所提到的,一个页面之外的所有东西都在被浏览器进程所控制。浏览器进程有许多线程,比如UI线程,它会在浏览器中渲染按钮和输入框,网络线程通过网络栈处理从网络上接收的数据,存储线程控制文件的访问权限。当你在地址栏输入一个地址时,你的输入正在被UI线程所服务。browserprocesses.png
图1:浏览器UI在最上面,浏览器进程所包含的UI,存储,网络等线程在下面

一个简单的导航

第一步:接管输入

当用户在地址栏输入时,UI线程的第一件事是判断这是一个地址还是一个查询关键词。在Chrome中,地址栏也是一个查询输入框,因此UI线程需要判断以便决定去搜索引擎搜索这个关键词还是跳转至用户输入的地址。input.png
图1:UI线程判断用户输入是搜索关键词还是url地址

第二步:开始导航

当用户点击回车时,UI线程发起一个请求并且得到响应的过程中,页面的顶部Tab上会出现一个加载图标,网络线程会通过设当的协议,比如DNS查询去建立一个TLS链接。navstart.png
图2:UI线程和网络线程交互以便导航至指定网站
在这一部分中,网络线程或许会收到一些重定向header,比如301状态。这种情况下,网络进程将会和UI进程协作去重定向请求,去请求新的地址。sniff.png
图4:网络线程判断响应是否是HTML
这也是安全浏览会检查的地方。如果这个域名的响应匹配到了一个恶意的网站,然后浏览器就会弹出一个警告页面。此外,跨站读取阻止(CORB)会去检查跨站数据是否被带到了渲染进程。

第三步:找到一个渲染进程

一旦所有检查完成并且网络线程确定请求到了正确的网址,网络线程就会告诉UI线程请求完成了,数据准备好了,然后UI线程就会去找一个渲染进程帮助其渲染出页面。findrenderer.png
图5:网络线程通知UI线程渲染页面
由于网络请求一般都要耗费几百毫秒才能得到响应,所以会有一些优化措施去加速访问。当UI线程发送一个url请求给网络线程的时候,它已经知道将会跳转到那个网站。与此同时UI线程将会去找一个渲染进程。如果一切按预期进行,那么网络请求完成的时候,渲染进程也准备好了,此时就可以立即进行渲染。如果重定向的话,那么这个进程就不会被应用,并且会有一些其他处理。

第四步:提交导航

现在数据和渲染进程都准备好了,然后浏览器进程就会通过IPC向渲染进程提交这个导航,通过它也传递HTML数据流给渲染进程,一旦浏览器进程收到渲染进程的确认,那么导航就完成了,文档加载阶段就开始了。commit.png
图6:浏览器进程和渲染进程通过IPC通信,发送请求渲染页面

最后一步:初次加载完成

提交导航后,浏览器进程将会持续加载资源并渲染。我们将会在下篇文章中研究这个阶段的细节。当渲染进程“完成”渲染后,它将会发送一个IPC回复给浏览器进程(这是在页面所有onload事件触发并且执行完毕后)。在这个阶段,UI线程将会停止他的加载图标(Tab坐标的加载动画)。
这里说“完成”是因为客户端的js还在加载其他资源并且渲染。loaded.png
图7:渲染进程和浏览器进程的IPC通信,渲染进程通知浏览器进程渲染完成

导航至一个新的网站

简单的导航就完成了!但是,如果用户再次将不同的URL放入地址栏会发生什么?好吧,浏览器过程将通过相同的步骤导航到不同的站点。但在此之前,它需要与当前渲染的站点进行核对,以了解他们是否关心 beforeunload事件。
beforeunload可以创建离开此网站的警告, 当用户尝试重定向或关闭选项卡时发出警报。选项卡内部的所有内容(包括您的JavaScript代码)都由渲染器进程处理,因此,当新的导航请求出现时,浏览器进程必须与当前渲染器进程进行检查(不要随便添加beforeunload的处理,因为它要在导航前解析,会延迟页面加载)。beforeunload.png
图8:浏览器进程告诉渲染进程要重定向,有没有要处理的任务
如果导航是从渲染器进程发起的(例如用户单击链接或客户端JavaScript已运行window.location = "https://newsite.com"),则渲染器进程首先检查beforeunload处理程序。然后,它经历与浏览器过程启动的导航相同的过程。唯一的区别是导航请求从渲染器进程通知到浏览器进程。
当将新导航导航到与当前渲染站点不同的站点时,将调用一个单独的渲染进程来处理新导航,同时保留当前渲染进程来处理诸如之类的事件unload。有关更多信息,请参见页面生命周期状态概述 以及如何使用Page Lifecycle API挂接事件 。unload.png
图9:浏览器进程与新旧渲染进程的IPC通信

注意Service Worker

导航最近有个变更就是 Service Worker的引入。Service worker是在应用程序代码中编写网络代理的一种方法。使Web开发人员可以更好地控制在本地缓存以及何时从网络获取新数据。如果将Service worker设置为从缓存加载页面,则无需从网络请求数据。
要记住的重要部分是Service worker是在渲染器进程中运行的JavaScript代码。但是,当导航请求出现时,浏览器进程如何知道该站点有Service worker?scope_lookup.png
图10:浏览器进程中的网络线程查找服务工作者作用域
当一个Service worker被注册时,将会保存一个Service worker的引用(您可以在《服务工作者生命周期》这篇文章中阅读有关范围的更多信息 )。导航发生时,网络线程将对照已注册的Service worker检查其作用域,如果为该URL注册了Service worker,则UI线程会找到渲染器进程以执行Service worker的代码。Service worker可以从缓存中加载数据,从而无需从网络请求数据,也可以从网络请求新资源。serviceworker.png
图11:UI线程通知渲染进程处理Service worker,worker线程从网络中请求数据

预加载导航

您可以看到,如果Service worker最终决定从网络请求数据,那么浏览器进程与渲染器进程的交互就会导致一定延迟。 导航预加载是一种通过与服务工作者启动并行加载资源来加快此过程的机制。它用header标记这些请求,使服务器可以决定为这些请求发送不同的内容。例如,仅更新数据而不是完整文档。navpreload.png
图12:UI线程启动渲染进程处理Service worker,同事启动网络请求

写在最后

在本文中,我们研究了导航期间发生了什么以及响应header和客户端JavaScript之类的代码如何与浏览器交互。了解了浏览器从网络获取数据所需的步骤,可以更轻松地理解为什么开发了诸如导航预加载之类的功能。在下一篇文章中,我们将深入探讨浏览器如何评估HTML / CSS / JavaScript来呈现页面。