现代浏览器设计原理--从导航开始

116 阅读6分钟

本章我们将讨论,在显示网站内容时各个进行和线程是如何通信的。

从浏览器进程开始

在我们上一章提起,tab之外的一切东西都是浏览器进程处理的。

浏览器进程有一些线程,如绘制浏览器按钮和输入字段的用户界面线程、处理网络堆栈以从互联网接收数据的网络线程、控制文件访问的存储线程等等。当您在地址栏中键入网址时,您的输入由浏览器进程的用户界面线程处理。

当你在地址栏输入一个URL时,你的输入会被浏览器的UI线程处理。

简单的导航

第一步:处理Input输入

当用户在地址栏输入时,UI线程会先提出一个问题‘这是一个搜索语句还是一个URL’。在Chrome中,地址栏也是一个搜索栏 ,所以UI线程需要去转换输入的内容,并决定是讲内容发送到搜索引擎还是你请求的网站。

第二步: 开始导航

当用户点击enter键时,UI线程会初始化一个网络请求,来获取你请求的网站内容。同时tab的角标会显示一个加载中的Icon。网络线程将会按照合适的协议进行操作,比如DNS查询和建立TLS链接。

此时,网络线程可能会接受到一个服务器定向,比如HTTP 301。此时,网络线程将会跟UI线程通信,告诉它服务器正在重定向。然后,另一个URL请求经会被初始化。

第三步: 读取响应

一旦响应体开始进入,若有必有,网络线程会读取响应流的初始一些字段。

响应头的Content-Type描述响应的是什么类型的数据,但是由于这个字段可能会被填错或者丢失,需要做MIME类型嗅探。这是一个非常有难度的工作,你可以读取这段注释看不同的浏览器是如何处理content-type/payload键值对的。

如果响应内容是HTML文件,则下一步将把数据传递给渲染器进程;但如果它是zip文件或其他文件,则意味着它是下载请求,因此需要将数据传递给下载管理器。

这里也是浏览器安全检查的地方。如果域名和相应数据,跟已知的恶意网站匹配,网络线程就会触发一个警告并显示一个警告页面。

除此之外,还会进行CORS检查,以确保不会给渲染线程传递敏感的跨站数据。

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

一旦所有的检查都结束了,网络线程确定浏览器可以导航到请求的网站上了,网络线程就会告诉UI线程一切就绪。UI线程就找一个渲染线程,来负责对网页的渲染。

由于网络请求往往需要几百毫秒来等待响应,可以在此使用一个优化首单来加速渲染。在第2步时,UI线程给网络线程发送了URL请求,它已经知道将导航至哪里了。UI线程尝试在网络请求的同时主动查找或启动渲染器进程。这样,当一切按预期进行时,当网络线程接收好数据的时候,一个渲染进程已经就绪了。如果导航被重定向到一个新的跨站站点,这个进程就不会被使用,这种情况下会走其他的过程。

第五步:提交导航

现在数据和渲染进程都已经就绪了,浏览器进程向渲染进程发送一个IPC,来提交导航。同时向渲染进程传递数据流,以持续接受HTML数据。一旦浏览器进程接收到渲染进程开始提交的确认信息,导航就结束了,然后就进入文档加载阶段了。

此时,地址栏已更新,安全指示器和站点设置 UI 反映了新页面的站点信息。Tab的会话历史也会被更新,以保证前进/后退功能的正常工作。为了在用户关闭tab或者窗口时,

便于恢复tab和session,sessioin历史将会被存储在硬盘上。

额外的步骤:初始化加载完成

一旦导航被提交了,渲染进程负载资源加载和页面渲染。我们将在下一章中讲述这个阶段的详情。

一旦渲染进程完成渲染,它将会给浏览器进程回发一个IPC(这是在页面中所有框架的onload事件都已触发并完成执行后。)。UI线程就会停止tab上的加载旋转。

所谓的‘完成’,因为客户端JavaScript仍然可以在此时加载其他资源并呈现新视图。

导航到不同的站点

恭喜,完成了一个简单的导航。但是如果用户突然输入了一个不同的URL地址怎么办?

浏览器会按照上述步骤再走一次,但是在执行之前,它需要检查当前渲染的网站是否关心 beforeunload 事件。

当你离开或者关闭当前网页,beforeunload可以创建‘离开此网站’的提醒。

tab里的所有事,包括JS的执行都是在渲染进程中处理的。所以当一个新的导航请求进来时,需要检查一下当前的进程。

如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript运行window.location =newsite.com ,渲染进程会首先检查beforeunload句柄。然后重复一个导航初始化的流程。唯一不同的是,导航的请求是从渲染进程发起的。

如果当前已经渲染的页面,导航到了一个全新的站点,将会调用一个权限的渲染进程来处理它,当前的渲染进程需要去处理一下unload事件。

ServiceWork 场景

导航进程最新引入的一个改变就是service worker。它可以在你的代码中写网络代理。允许web开发者控制往本地缓存什么内容,以及何时从网络中请求数据。如果sever worker 被设定为从缓存中加载页面,就不需要从网络中请求数据了。

需要注意的是,service worker是运行在渲染进程中的JS代码。但是当导航请求到达时,浏览器进程如何知道网站上有server worker呢。

当一个service worker注册之后,它的作用范围会被作为引用保存起来。当发生导航时,网络线程会检查域名和已经注册的worker,如果URL可以匹配,UI线程就会寻找一个渲染进程来执行worker代码,woker可以从缓存中加载数据从而避免网络请求,当然,它也可以从网络中重新请求数据