浏览器
单进程浏览器
早期时代的浏览器是单进程的,顾名思义。单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程的,包括网络、插件、js 运行环境、渲染引擎和页面等。
单进程浏览器的架构如下图:
问题显而易见:
- 不稳定: 网络、插件、js 运行环境、渲染引擎和页面任何一个意外的崩溃,都会导致整个浏览器崩溃
- 不流畅:比如页面线程包含了 js 运行环境,页面渲染,和其他的模块,意味着同一线程同一时刻只能有一个模块运行,如果 js 过大,或者进入无限循环,那么其他模块就没有机会运行,造成卡顿的感觉
页面的内存泄漏
也是单进程变慢的原因,运行一个复杂点的页面再关闭页面。会存在内存不能完全回收的情况,这样导致约积占用内存越高,浏览器变得越慢 - 不安全:前面所说,插件的模块也是存在于同一个进程,那么其他模块就有可能收到插件模块的影响,通过插件可以获取系统任意资源,当你在页面运行一个插件时,意味着这个插件能够操作你的电脑,可能释放病毒,窃取密码等
多进程浏览器
现在的浏览器就是多进程浏览器
,先看看现在的多进程浏览器架构
,以Chrome
为例,扔一张图看看:
从图中可以看出最新的Chrome
浏览器: 1个浏览器主进程
、1个GPU进程
、1个网络进程
、多个渲染进程
和多个插件进程
- 浏览器进程:负责界面显示、子进程管理等,同时提供存储功能,localStorage、cookie、session 能力
- 渲染进程:核心任务将 HTML、CSS 和 JS 转换为网页,排版引擎,V8 引擎都是运行在该进程中,默认情况,一个 tab 创建一个渲染进程,同域共享同一个渲染进程,处于安全考虑,运行在沙箱模式下
- GPU 进程:用于 UI 界面 GPU 绘制
- 网络进程:负责网络资源加载
- 插件进程:负责浏览器插件运行
- 等等
可以看出,多进程的浏览器提升了浏览器的稳定性
、流畅性
、以及安全性
,但不可避免带来一些问题:
- 更高的资源占用: 这么多的进程,那么意味着浏览器会消耗更多的内存(其实现在电脑配置都这么高了,这点其实不主要)
- 更复杂的体系架构: 浏览器各模块之间的扩展性差等问题,导致现在架构很难适应新需求
不过有好消息,为了解决以上问题,Chrome
团队使用 面向服务的架构(SOA) 思想,就是说Chrome
整体架构会朝向现代操作系统所采用的 面向服务的架构(SOA),如图:
Chrome
最终要把 UI、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务,这一步正在逐步构建。
从输入 url 到页面展示,发生了什么?
用户输入
- 判断关键词
- 如果是搜索内容,则地址栏会使用浏览器搜索引擎
- 如果是符合
URL
规则,则合成完整的URL
- 回车之后,浏览器会给当前的页面执行一次
beforeunload
,可以用来取消导航
URL 请求过程
- 进入资源请求过程,浏览器进程通过
(IPC)
把URL
请求发送到网络进程,网络进程发起请求 - 检查本地是否有缓存,如果有直接返回资源给浏览器进程,如果没有,直接进入网络请求
DNS
解析,获取IP
地址,如果是HTTPS
还要建立TLS
连接- 利用
IP
地址建立TCP
连接,浏览器端构建请求行、请求头,相关域cookie
等数据加到请求头,向服务器发送请求信息 - 服务器根据请求信息生成响应数据,并发给网络进程,然后网络进程开始解析响应头等信息
(1) 重定向
如果网络进程在解析响应头,发现状态码是 301
或者 302
,那么说明需要浏览器重定向到响应头 Location
指定的 URL
,这时,请求再次从头开始
(2) 响应数据类型处理
网络进程在解析响应头时,去告诉浏览器,然后根据不同的 Content-Type
类型,来决定如何显示响应体内容。
需要注意的时:如果服务器配置 Conent-Type
不正确,比如将 text/html
类型的配置成 application/octet-stream
类型,那么浏览器会曲解文件内容。一个 html
文件变成了一个下载文件,这时请求就会交给浏览器的下载管理,并不会交给渲染进程,这个时候导航流程
就结束了。如果时 html
文件,那么将继续准备渲染进程
准备渲染进程
- 默认情况,浏览器会为每个页面分配一个渲染进程。有一些情况会复用同一个渲染进程,比如:同域(根域名)的情况。这种策略叫做
process-per-site-instance
- 准备好之后,等待网络进程请求的资源,准备解析文档
提交文档
所谓提交文档,就是浏览器进程将网络进程接受的 HTML 数据提交给渲染进程,这个过程,浏览器进程
、网络进程
和渲染进程
时紧密联系的
浏览器进程
接收到网络进程
的响应头之后,向渲染进程
发起提交文档的消息渲染进程
收到提交文档的信息后,和网络进程
建立传输数据的管道- 等
渲染进程
和网络进程
数据传输完成之后,渲染进程
会返回确认提交的信息给浏览器进程
浏览器进程
收到确认提交之后,会更新浏览器的界面状态,比如,安全状态、前进后退状态、tab 的转圈圈状态
渲染阶段
构建 DOM
树
样式计算
样式由:选择器
、属性
和值
组成
- 把
CSS
转换为浏览器理解的结构(CSS
样式来源主要有三种)
- 通过 link 外部引入
<style>
标记内的文件- 元素的
style
属性内联CSS
- 转换样式表中的属性值,使其标准化
- 比如:
em->px
,bold->700
,bule->rbg(0, 0, 255)
计算出 DOM
树每个节点的具体样式
- 计算过程遵循
CSS
的继承
和样式层叠
两个规则
继承:继承如图
fonst-size
解释:样式层叠:它定义了如何合并来自多个源的属性值的算法
布局
- 创建布局树
在显示之前,我们还要额外构建一个包含可见元素的布局树
,由DOM
树和CSS
生成布局树
- 遍历
DOM
树中可见节点,把节点加到布局树
中
- 不可见的会被忽略 。如
head
标签下的全部内容、display:none
的元素
- 布局计算
计算布局树
节点的坐标位置,(布局操作时,会把布局计算结果重新写回布局树中)
分层
一些复杂的 3D 变换、页面滚动、或者使用 z-indexing 做 z 轴排序等,还涉及到图层,渲染引擎需要为特定的节点生成专用的图层,并生成一颗对应的图层树(LayerTree),
图层和布局树之间的关系:
不是布局树每个节点都包含一个图层,节点没有对应的图层,则从属父节点的图层。
满足下面任意条件,渲染引擎会为其创建新的图层:
- 拥有层叠上下文属性的元素
position
值为absolute(绝对定位)
或relative(相对定位
)且z-index
值不为auto
的元素position
值为fixed(固定定位)
或sticky(粘滞定位)
的元素
- 需要剪裁(clip)的地方
- 出现滚动条,滚动条也会被提升为单独层
- 元素设置
overflow:auto
图层绘制
在该图中,区域 1 就是 document 的绘制列表,拖动区域 2 中的进度条可以重现列表的绘制过程。
栅格化(raster)操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,实际上绘制操作是由渲染引擎中的合成线程来完成的。
合成线程
将图层划分为图块合成线程
会按照视口附近的图块优先生成位图,所谓栅格化,是指将图块转化为位图- 渲染进程维护一个栅格化的线程池,图块栅格化在该线程池执行
- 栅格化使用
GPU
加速生成(GPU 栅格化
或者快速栅格化
),生成位图在GPU
进程执行,生成的位图保存在GPU
内存 - 涉及到跨进程操作,
渲染进程
->GPU进程
,以下图参考:
合成和显示
所有图块都被栅格化,合成线程就会生成一个绘制图块的命令——DrawQuad
,提交给浏览器进程,最终将页面绘制到内存中,再将内存显示在屏幕上
总结
- 渲染进程将
HTML
内容转换为能够读懂的DOM
树结构。 - 渲染引擎将
CSS
样式表转化为浏览器可以理解的styleSheets
,计算出DOM
节点的样式。 - 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令
DrawQuad
给浏览器进程。 - 浏览器进程根据
DrawQuad
消息生成页面,并显示到显示器上。
回流和重绘
- 了解浏览器的渲染机制
- 浏览器采用
流式布局模型
- 浏览器会将 HTML 解析成
DOM
,把CSS
解析成CSSOM
,把CSSOM
和DOM
结合成一个render tree
- 有了
render tree
之后,就知道了节点样式,然后浏览器会计算节点的位置,然后再绘制再页面上
- 回流:当我们
render tree
中的一些元素的结构或者尺寸等发生改变,浏览器重新渲染部分或者全部文档的过程就做回流
,导致回流的操作如下
- 页面首页渲染
- 浏览器窗口大小变化
- 内容变化
- 添加或者删除节点
- 激活 CSS 伪类
- 等等
- 重绘:当页面中样式的改变不影响它在文档中的位置,浏览器会将新样式赋予给元素,这个过程叫做
重绘
,导致重绘的操作如下
- background…
- visibility
- 等等
- 性能影响:回流的性能消耗比重绘大
- 避免性能影响
CSS:
- 避免使用
table
布局 - 避免设置多层内联样式
JS:
- 避免大量频繁操作
DOM
- 对于大量插入
DOM
的操作,建议使用文档片段,也就是documentFragment
- 结合图来看:
- 回流
- 重绘
- 直接合成(相对回流和重绘大大提升绘制效率)
- 回流
浏览器存储
浏览器窗口通讯
发送端
1 | otherWindow.postMessage(message, targetOrigin, [transfer]); |
接收端
1 | window.addEventListener('message', function (e) { // 监听 message 事件 |