浏览进程与线程&页面导航与渲染
进程 Process
进程(Process) 是系统进行资源调度和分配的基本单位。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体
单进程架构的早期浏览器
顾名思义,单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JS 运行环境、渲染引擎和页面等
单进程架构的浏览器主要以下两个问题:
- 不稳定:一个功能模块出了问题卡顿或者崩溃,整个浏览器都会随之卡顿或崩溃
- 不安全:插件程序和页面脚本程序可以访问到操作系统的任意资源,无法防备恶意程序
多进程架构的现代浏览器
多进程架构的现代浏览器解决了早期浏览器的问题。由于进程间是相互隔离的,即便某个子进程出了问题,对其他进程的影响也有限。对于页面脚本程序和插件的安全性问题,只需将其所在的进程使用沙箱(sandbox) 进行隔离即可
当然也有其缺点:
- 更高的系统资源占用。以现代设备的发展速度而言,问题不大
- 更复杂的体系架构。这是浏览器的开发团队考虑的问题
打开一个现代浏览器,大致会有以下这些进程:
-
浏览器进程(Browser Process) :浏览器的主进程,负责界面展示,用户交互,子进程管理等
-
GPU进程(GPU Process) :负责整个浏览器的 UI 渲染
-
标签页进程(Tab Process) :负责将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。渲染引擎和 JS 引擎都在该进程中,默认情况下,浏览器会为每个标签页创建一个标签页进程。渲染进程运行在沙箱环境中
-
服务进程(Service Process) :现代浏览器拥有灵活的弹性架构,默认情况下,浏览器会为每项基础服务创建一个进程,但如果设备资源受限,会将很多服务整合到一个进程中,从而节省内存占用
-
网络服务(Network Service) :提供网络功能
-
存储服务(Storage Service) :提供存储功能
以上两项服务是在浏览器一打开就会启动的,还有许多基础服务会在需要的时候启动
-
-
插件进程(Plugin Process) :负责插件的运行,浏览器会为每个插件创建一个插件进程。插件进程运行在沙箱环境中
线程 Thread
线程(Thread) 是CPU调度的的基本单位。在进程中使用多线程并行处理能提升运算效率,进程将任务分成很多细小的任务,再创建多个线程并发执行
标签页进程中的线程
一个标签页进程中,大致有如下进程
- GUI 渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行
- JS 引擎线程:负责执行 JS 脚本,一个标签页中只有一个 JS 引擎线程。GUI 渲染线程与 JS 引擎线程是互斥的,GUI 更新会被保存在一个队列中,等到 JS 引擎空闲时执行
- Worker 线程:负责执行 Worker 环境下的 JS 脚本。Woker 环境无法访问操作 BOM 和 DOM。Worker 线程的数量和内存一般都有限制
- 计时器线程:负责
setTimeout与setInterval。定时器并不是不是由 JS 本身来计时的 - 异步Http请求线程:负责异步网络请求,
XMLHttpRequest在连接后浏览器会新开一个线程请求 - 事件线程:负责控制事件循环。除了页面的交互事件,定时器,网络请求等回调也属于事件
从地址栏输入到页面展示
1.浏览器进程处理用户输入
将用户输入转换成 URL(用户输入可能是 URL 也可能是搜索内容)后提交给网络进程
2.网络进程发起请求
DNS 域名解析
DNS 域名解析大致分为以下步骤
- 检查浏览器缓存
- 检查操作系统缓存
- 检查 host 配置文件
- 向本地 DNS 服务器发起域名解析请求
- 本地 DNS 服务器进行后续查询直到取得域名对应的 IP 地址
建立 TCP/IP 连接
利用 IP 地址和服务器进行三次握手建立 TCP/IP 连接
- 客户端发送一个带有 SYN 标志的数据报给服务器,表示请求连接
- 服务器返回一个 SYN/ACK 标志的数据包给客户端,表示已收到请求
- 客户端再发送一个带 ACK 标志的数据包给服务器,表示已建立连接
建立 TLS 连接
如果是 HTTPS 请求,在建立 TCP/IP 连接后还需进行 TLS 连接
- 客户端发送
Client Hello消息,消息中附带 TLS 版本号、支持的密码套件列表,以及生成的客户端随机数(Client Random) - 服务端返回
Server Hello消息,消息中附带确认的 TLS 版本号,选中的密码套件以及一个服务端随机数(Server Random)。之后服务端继续返回Server Certificate消息,消息中附带自己的数字证书。最后服务端返回Server Hello Done消息 - 客户端验证完数字证书后,生成一个新的随机数 (pre-master),用服务器的 RSA 公钥加密该随机数,通过
Client Key Exchange消息传给服务端。之后客户端根据三个随机数,生成会话密钥(Master Secret),用于对后续的 HTTP 请求/响应的数据加解密。再然后客户端发送Change Cipher Spec消息,告诉服务端开始使用加密方式发送消息。最后,客户端再发一个Encrypted Handshake Message(Finishd)消息,把之前所有发送的数据做个摘要,并再用会话密钥(master secret)加密一下,让服务器验证加密通信是否可用和之前握手信息是否有被中途篡改过 - 服务端以同样的操作发送
Change Cipher Spec和Encrypted Handshake Message(Finishd)消息给客户端,如果双方都验证加密和解密没问题,那么握手正式完成,连接建立
构建请求报文,发起请求
地址栏请求的请求方式为 GET,无请求体
请求报文构建完成后,通过 TCP/IP 协议,将请求报文发送到服务端指定端口(HTTP 为80,HTTPS 为443)
接受响应,解析响应报文
-
根据状态码选择下一步操作
1XX: 请求已接收,继续等待响应
2XX: 请求成功,开始解析响应头
3XX: 重定向,发起新的请求或使用本地缓存
4XX: 客户端请求错误,展示4XX错误页面
5XX: 服务器响应错误,展示5XX错误页面
-
解析响应头,判断
Content-Type类型Content-Type 为
text/html:通知浏览器进程准备渲染页面Content-Type 为下载类型:交由下载管理器处理
3.浏览器进程更新浏览器状态
浏览器进程收到网络进程的消息后,通知标签页进程与网络进程建立数据传输通道
数据传输完毕后浏览器进程更新浏览器界面状态,包括安全状态、地址栏的URL、前进后退的历史状态,并更新页面内容
4.标签页进程渲染页面
参考:渲染流程(下):HTML、CSS和JavaScript是如何变成页面的 | 前端基础相关 (poetries.top)
标签页进程接收 HTML 文档后,按以下步骤开始渲染流程
构建 DOM 树
在构建 DOM 树的过程中如果遇到了非阻塞资源,例如图片,浏览器会请求这些资源并且继续解析。当遇到一个 CSS 文件时,解析也可以继续进行。但是对于 <script> 标签(特别是没有 async 或者 defer 属性的),则会阻塞渲染并停止 HTML 的解析,当这种情况发生时,浏览器会通过预加载器解析后面的html,寻找需要下载的资源,如 CSS、JavaScript 和 web 字体
构建 CSSOM 树
浏览器遍历 CSS 中的每个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树,并将 CSS 规则转换为浏览器可以理解和使用的样式映射。构建 CSSOM 非常非常快,创建 CSSOM 的总时间通常小于一次 DNS 查询所需的时间
构建布局(Layout)树
布局树是结合 DOM 树和 CSSOM 树生成的。样式为 display: none 的元素以及 <head> 中的元素会被布局树忽略
构建分层(Layer)树
拥有层叠上下文属性的元素会被创建为单独的图层,如 position 属性不为 static 的元素,transform 属性不为 none 的元素
需要剪裁(clip)的地方会被创建为单独的图层,比如文字超出元素内容区域时便会被裁剪,此时渲染引擎会为文字部分单独创建图层,如果出现滚动条,滚动条也会有独立的图层
有一些特定的元素会被创建为单独的图层,如 <video> 和 <canvas>
绘制(Paint)
大致步骤如下:
- 渲染引擎主线程把每个图层的绘制拆分成很多小的绘制指令,并按顺序组成绘制列表,然后将绘制列表提交给渲染引擎中的合成线程
- 合成线程将图层划分为图块(tiles) ,再通过光栅化(raster) 将视口附近的图块转化为位图。光栅化通常会使用 GPU 来加速
- 合成线程将绘制图块的指令 draw quad 提交给浏览器进程
5.浏览器进程展示页面
浏览器进程根据 draw quad 指令将页面内容绘制到内存中,再将内存中的内容展示在浏览器标签页中