本系列分4个部分来讲述Chrome浏览器是如何工作的。
如果你想知道浏览器如何将你写好的代码(HTML/CSS/JAVASCRIPT)变成你所看到的网页这一过程所发生的细节,那么本文章适合你。
目录如下
- Chrome浏览器的架构
- 介绍计算机的核心术语以及Chrome浏览器的架构--->【多进程多线程架构】
- 浏览器如何展示一个页面
从浏览器的【导航栏(UI Thread)】出发,深挖在【多进程多线程】的架构下 各个进程 :: Process 各个线程 :: Thread 之间如何【通信】 ------⏬------ 来最终展示网页
- 浏览器内核是如何工作的
- Chrome浏览器内核---> 渲染进程(Renderer)是如何工作的
- 浏览器如何与用户交互
- 浏览器如何处理与用户的交互,并反馈至网页
1. Chrome浏览器的架构
准备工作
浏览器是一个应用程序(Application),在一个计算机上运行一个应用程序,都需要哪些东西呢?
最基本的3点(把大象装冰箱):
- 硬件(Machine Hardware)
- 操作系统(Operating System)
- 应用程序(Application) tips: 计算机体系结构的三层。机器硬件在底部,操作系统在中间,应用程序在顶部
浏览器的工作离不开硬件和操作系统的支持,关于这块,我们需要知道的知识有:
- CPU (中央处理器)
- GPU (图形处理单元)
- Process (进程)
- Thread (线程)
这里可以做一个简单的解释(看图说话)
具体细节可以自行去了解,特别是进程与线程的概念。
- CPU && GPU
- Process && Thread
正式开始
知道了进程和线程的概念。
那么如何使用进程和线程构建Web浏览器呢?
其实答案并不是唯一的
- 可以使用一个进程包含许多线程(图左👈)
- 多个进程,每个进程包含一些线程,进程之间相互通信(图右👉)
这里我们研究的是最近的架构,即多进程多线程架构。
浏览器的主要进程有:
- Browser Process
- 控制应用程序的chrome部分,包括地址栏、书签、后退和前进按钮(UI thread)
- 处理Web浏览器一些UI上未显示的功能,例如网络请求和文件访问等(network thread)
- Renderer Process
- 控制显示网站的标签内的所有内容
- Plugin Process
- 网站使用的所有插件
- GPU Process
- 与其他进程隔离地处理GPU任务。GPUs处理来自多个不同应用程序的请求并把它们画在屏幕上
其中最顶部是【Browser Process】,其负责协调应用程序的其他进程。
对于渲染进程【Renderer Process】,每一个Tab栏都可理解为创建了一个渲染进程。
整个浏览器还有其他的进程,例如Extension process 和 utility process。
具体查看浏览器运行的时候都开启了哪些进程,可以点击右上角的:按钮,选择【更多工具】,点击【任务管理器】。这里可以看到浏览器开启的所有进程。
浏览器多进程多架构的好处
有哪些好处其实随便想想就有很多(暂不详述)。
- 单个页面崩溃不会影响其他页面
- 安全性
- 健壮性
节约内存
Chrome正在进行架构更改,以将浏览器程序的每个部分作为一项服务运行,从而可以轻松拆分为不同的进程或整合为一个进程。
站点隔离
网站隔离是Chrome最近引入的功能,可为每个跨网站iframe运行单独的渲染器进程。 自Chrome 67起,默认情况下在桌面上启用了网站隔离功能,标签中的每个跨网站iframe都会获得一个单独的渲染器进程(可通过chrome内部的【任务管理器】查看)
小结
本节只需要理解:
- Chrome浏览器是多进程多线程架构的
- Chrome浏览器的主要进程有
- Browser Process(负责统筹全局)
- GPU Process(负责图像处理)
- Plugin Process(负责运行插件)
- Renderer Process(负责渲染页面)
- Chrome浏览器多进程多架构的好处
- 页面渲染互不影响
- 安全性,沙箱
- 健壮性
- 站点隔离
- 每个页面中引入iframe都会开启一个进程
2. 浏览器如何展示一个页面
有一道很常见的面试题: 【从地址栏输入url都发生了什么】
我们就从浏览器的导航开始,从浏览器的地址栏输入url,都发生了什么:
从 Browser process开始
- 当地址栏中输入URL时,输入将由浏览器进程的UI线程处理
Step1: 处理输入
- 浏览器会判断你在地址栏的输入是一个搜索查询还是一个URL,以便决定是将你的输入放入搜索引擎去查询,还是帮你发送一个网页请求。
2.Step2:开始导航
- 键盘敲入enter,UI线程启动网络调用以获取网站内容。Tab项的左端Loading的那个小logo就会开始转圈,并且network线程会通过适当的协议(UDP)进行DNS查询并且建立TLS连接。
- 此时如果network请求收到来自服务端的响应例如HTTP重定向301,那么就会跟UI Thread通信,告诉UI Thread服务端redirect了,那么另一个URL请求将会被启动。重复进行这一步骤。
3.Step3:处理响应
- 一旦响应主体(有效负载)开始出现,网络线程将在必要时查看流的前几个字节,响应的Content-Type标头应说明数据的类型,但由于可能丢失或错误,因此在此处进行MIME类型嗅探。
- 如果响应是HTML文件,则下一步是将数据传递到渲染器进程,但是如果是zip文件或其他文件,则意味着这是下载请求,因此他们需要将数据传递到下载管理器
- network线程从安全站点询问响应数据是否为HTML
- 这也是进行安全浏览检查的地方
- 如果域和响应数据与已知的恶意站点匹配,则network线程发出警报以显示警告页面
- 此外,还会进行跨源读取阻止(CORB)检查,以确保敏感的跨站点数据不会进入渲染器进程 tips: 响应标头,其中包含Content-Type和有效载荷,这是实际数据
4. Step4:找到渲染引擎
- 一旦完成所有检查,并且Network线程确信浏览器应导航到请求的站点,则Network线程将告知UI线程数据已准备就绪。然后,UI线程找到一个渲染器进程来进行网页渲染
tips: network线程告诉UI线程查找渲染器进程 - 由于网络请求可能需要数百毫秒才能获得响应,因此将应用优化来加快此过程。当UI线程在步骤2向网络线程发送URL请求时,它已经知道他们正在导航到哪个站点。 UI线程尝试与网络请求并行地主动查找或启动渲染器进程。这样,如果一切按预期进行,则当网络线程接收到数据时,渲染器进程已经处于备用位置。如果导航重定向跨站点,则可能不会使用此备用过程,在这种情况下,可能需要其他过程
5. Step5:更新导航
- 现在已经准备好数据和渲染器进程,将IPC从浏览器进程发送到渲染器进程以提交导航。它还会传递数据流,因此渲染器进程可以继续接收HTML数据。一旦浏览器进程听到确认提交已在渲染器进程中完成,导航就完成了,文档加载阶段开始了
- 此时,地址栏已更新,安全指示符和站点设置UI反映了新页面的站点信息。标签的会话历史记录将被更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了在关闭选项卡或窗口时方便选项卡/会话还原,会话历史记录存储在磁盘上 tips:浏览器和渲染器进程之间的IPC,请求渲染页面
6. Step6:一旦导航更新以后,渲染引擎拿到了所有资源然后开始渲染页面。下一节会讲。
导航到其他站点
简单的导航就完成了!但是,如果用户再次将不同的URL放入地址栏会发生什么?
浏览器过程将通过相同的步骤导航到不同的站点。
但在此之前,它需要与当前渲染的站点进行检查,看看他们是否注册了beforeunload事件
beforeunload可以创建“离开此网站?”尝试导航或关闭选项卡时发出警报。选项卡内的所有内容(包括您的JavaScript代码)都由渲染器进程处理,因此,当有新的导航请求出现时,浏览器进程必须与当前渲染器进程进行核对
不要添加无条件的beforeunload处理程序。它创建了更多的延迟,因为需要在甚至开始导航之前执行处理程序。仅在需要时才添加此事件处理程序,例如,如果需要警告用户,他们可能会丢失在页面上输入的数据
tips:从浏览器进程到渲染器进程的IPC告诉它即将导航到其他站点如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript已运行window.location =“ newsite.com”),则渲染器进程首先检查beforeunload处理程序。然后,它经历与浏览器过程启动的导航相同的过程。唯一的区别是导航请求从渲染器进程到浏览器进程开始了
当新的导航与当前渲染的站点不在同一个站点上时,将调用一个单独的渲染过程来处理新的导航,而当前的渲染过程将保留下来以处理诸如卸载之类的事件.
tips: 从浏览器进程到新渲染器进程的IPC,告诉渲染页面并告诉旧渲染器进程卸载小结
浏览器如何渲染一个页面(六脉神剑):
- 处理输入(Handling input) UI Thread 判断是search query 还是 URL
- 开始导航(Start navigation)
- UI Thread 把 URL 给 network Thread
- DNS && TLS
- 如果响应的 HTTP码是 301,拿到的URL传给UI Thread,更新navigation.重新开启DNS&&TLS
- 读取响应(Read response: check with response)
- 拿到响应数据(结果)
- Network thread 检查数据是否是从安全站点返回的,如果不是则显示警告页
- Network thread CORB check,检查跨域问题
- Network check 响应数据的Content-type
- 如果是: HTML data --> Renderer process
- 如果是: zip file or other file --> download manager
- 找到渲染引擎(Find a renderer process)
- UI thread 收到 network thread成功返回数据的信号
- UI thread 开启一个 renderer thread
- (优化==>UI thread拿到URL,传给network thread以后就直接做开启renderer process 的准备,等network返回数据完毕后,开的renderer process直接使用)
- 更新导航(Commit navigation)
- network thread 把拿到的数据给 renderer process
- UI thread receive 收到 renderer process 确认拿到数据的信号
- navigation completed and the document loading phase begins.
- 初始加载完成(Initial load complete)
- renderer process(loading finished),然后通知browser process
- UI thread(UI 线程属于Browser 线程) stops the loading spinner on the tab
- 导航到其他站点
-
- 如果从导航栏输入其他站点的URL,先在当前renderer process中去检查是否有beforeunload事件
- 2.如果从当前render process内做跳转,也就是当前页面脚本中有类似window.href="xxx"这段代码
- 首先检查当前页面是否有beforeunload事件
- 1.2点的区别就在于导航请求是从外部(Browser process)还是内部(Renderer process)发起的。
-
- 当新的导航与当前渲染的站点不在同一个站点上时,将调用一个单独的渲染过程来处理新的导航,而当前的渲染过程将保留下来以处理诸如卸载unload之类的事件
这里涉及到页面生命周期API可以参考
- 当新的导航与当前渲染的站点不在同一个站点上时,将调用一个单独的渲染过程来处理新的导航,而当前的渲染过程将保留下来以处理诸如卸载unload之类的事件
-