大家可以看一看先浏览器进程和线程的文章,在我之前的文章有写。
tab外面发生的一切事情都是由浏览器主进程决定,首先输入URL网址,其实是UI线程在处理我们的输入。
我们按下回车首先UI线程会判断我们输入的是一些搜索关键词还是一个URL地址,因为URL也可以搜索关键信息。
这时候主进程会去查看Service Worker中注册状态。service worker能够决定哪些数据缓存到本地,哪些数据需要重新获取。 service其实是跑在渲染进程中的js代码。假如遇到了注册过的请求,就会启动一个渲染进程来执行他的代码,进行渲染页面。
假如service worker未注册的话这时候UI线程通过IPC通信通知网络进程了,告诉他让他去请求网址。我们的 我们的网络进程首先会去检查一下HTTP的缓存。关于HTTP这方面可以看一下小编的文章HTTP , Websocket
- 网络进程按顺序检查:
- 内存缓存(200 from memory cache)
- 磁盘缓存中的强缓存(200 from disk cache)
- 协商缓存验证(304 Not Modified)
js
graph TD
A[请求发起] --> B{内存缓存有效?}
B -->|是| C[返回200 from memory]
B -->|否| D{磁盘强缓存有效?}
D -->|是| E[返回200 from disk]
D -->|否| F{磁盘有验证信息?}
F -->|是| G[发送条件请求]
G --> H{服务器返回304?}
H -->|是| I[更新缓存并返回304]
H -->|否| J[返回新内容]
F -->|否| K[正常网络请求]
如果没有缓存,这时候就要开始我们的网络请求了。
DNS解析
这时候网络进程首先会进行DNS解析。
DNS也有多级缓存机制。
x.com 没有缓存时的完整流程:
- 浏览器缓存未命中 →
- 检查hosts文件无记录 →
- 系统缓存未命中 →
- 查询路由器缓存未命中 →
- 向本地DNS服务器发起递归查询
详解:
首先他会去查询我们浏览器中的DNS缓存。Chrome中DNS缓存是有一个大概1MB的区域。他是60s清除一次。当我们关闭浏览器会清除缓存或者打开隐私模式不会缓存。
假如我们超出60s或没查询到信息,则会去查看我们的系统缓存。 首先会去查看本地的host文件。这是一个静态文件,能够让用户自己配置DNS解析。
还没查询到,这时候就会去查看操作系统缓存,这里记录了所有浏览器的查询记录,所有浏览器共享(每个DNS返回有个TTL,最大10min更新)。
假如还是没查询到,这时候就会去向路由发送请求,家用路由器同样会缓存DNS,通常为10-30min。
假如还是没查询到,会向域名服务器发起请求来解析。首先会去查询本地域名服务器(本地运营商提供:电信,移动)。如果还没查询到,本地域名服务器会向根服务器请求,他会返回顶级域名服务器的地址,接着顶级域名服务器会返回权威域名服务器的地址,最终在权威域名服务器中查询到DNS纪录。
-
递归查询:客户端只发一次请求,DNS服务器负责全部查询工作
-
迭代查询:本地DNS服务器向更高级DNS服务器发起的是迭代查询,即如果对方不知道答案,会返回"我不知道,但你可以去问这个服务器"
TCP 三次握手
接着就是建立TCP连接的过程,这里详细复习一下
刚开始客户端处于closed(关闭)状态,服务器处于listen(监听)状态。
第一次握手: 客户端发送一个SYN报文给服务器,并指明客户端的初始化序列号为SN。此时客户端处于SYN_SENT(同步发送)状态。
第二次握手:服务器收到了客户端的SYN报文的SYN报文应答,同时指定服务器的初始化序列为ISN,同时把 客户端的ISN+1作为ACK的值返回。此时服务器处于SYN_REVD(同步接收)状态。
第三次握手客户端收到服务器的SYN之后,一样将服务器的ISN序列+1当成ACK的值进行返回此时客户端处于establised状态。
接着服务器收到后,也处于establised(建立)状态,连接建立。
接收响应
网络进程在接收到HTTP的主体流之后,相应类型一般可以通过HTTP请求头的content-Type进行确定。但是有时候他是缺失或者错误的,浏览器解析失败,这时候浏览器就要 进行MIME类型嗅探来判断主体是什么媒体类型。(这时候会对响应数据的前几个字节(魔数)进行判断,以进行资源类型的检查。等等)
- 如果获取的是一个HTML文件,还会对内容做一些检查。 会通过IPC交给渲染进程进行渲染,(在UI进程进行)。
- 如果是一个压缩文件或者是其他文件类型,则会交给下载管理器进行下载。
渲染进程接收到提交之后,导航栏开始更新,tab的会话历史也会更新,用户能够回退进行恢复当前会话。这时候,渲染进程开始工作了。
渲染进程发生的事
1.构建DOM树 CSSOM树
我们的GUI渲染线程会去解析负责HTML文档的解析,生成DOM树。它具有很大的容错能力,例如标签没闭合也不会报错。
当遇到了CSS样式,便会跟DOM树同步构建CSSOM树。(因为我们的样式复杂,会有优先级,继承)。
解析的途中可能会碰到一些例如图片,script,css这种需要下载的样式。他会逐步进行下载。不过现在的浏览器具备预加载扫描器。 可以同时的发起多个请求。并将需要的资源通过ICP告诉网络进程。
- 注意: 浏览器遇到script标签就会阻塞DOM树的构建,暂停HTML的解析。因为我们的js脚本可能更改我们的DOM树。等到js下载并且执行完成之后才会继续构建DOM树。 我们可以通过给script标签加上一个async(异步下载,下载后立即执行) 或者defer(异步下载,延迟到DomContentLoaded前执行)属性来使得script脚本进行异步加载。也可以使用preload属性指定当前页面的关键资源,优先加载。
- 但是我们的CSS样式的下载不会阻塞DOM树的构建,但是他会阻塞render树的合成过程,因为需要CSS样式。
样式计算 Style caculation
当DOM树和CSSOM树都构建完成,我们便会开始计算每个节点的最终样式(样式计算)。即使每个节点没有设置样式,仍然DOM节点都会有样式,因为每个浏览器有自己的默认样式表,这也就是为什么有时候我们的body会有默认的margin,每个浏览器还不同。
然后构建起最终的渲染树(会过滤掉不可见的节点 display:none)
布局 layout
我们已经有了具体的几何图形,但是你想一想,假设我喊你画一个圆,一个长方形,你知道怎么画吗?放在哪儿?画多大? 浏览器会根据文档流的规则给页面进行布局。生成一颗布局树,包括每个元素的具体xy坐标,以及盒子大小,层级关系的具体信息。
绘制 分层
知道了具体的位置,还是不能清楚怎么画,因为你想,我们绘画的图形是有遮挡的,会有顺序。假如设置了z-index,我们该先画谁后画谁呢?绘画这个步骤,会根据之前生成的layout布局树,生成对应的绘画指令。然后发送给GPU。(这里会生成一个RenderLayerTree 用来处理绘制顺序,堆叠层次)
分层式浏览器出于性能优化,他在某些特定的情况会对页面进行分层。他跟z-index不一样,z-index是影响的堆叠上下文,也就是绘制的顺序。分层决定的是渲染的一个处理方式。
当使用一些特定的css属性的时候会触发新图层的创建,例如translate3D,translateZ, will-change: transform/opacity/filter , 固定定位,vedio标签,opacity小于1且有动画。
采用分层,这样我们的一些图像分到了独立层中,不必每次微小改变就引起原来层的重绘重排,只需要最后进行图层合成即可。(这里分层之后会生成一个GraphicsLayer树 合成层树)
当使用了translatez后,会创建图层。浏览器会将元素渲染成位图(光栅化)。这个位图被上传到GPU内存成为纹理。GPU就可以直接对这个纹理进行移动,缩放。
光栅化
- 接收绘制指令:浏览器生成一系列绘制命令,如"画矩形"、"填充文本"、"应用渐变"等
- 几何处理:
- 计算每个形状的精确边界
- 确定形状之间的重叠区域
- 处理剪切区域(clipping regions)
- 三角形分解:
- 复杂形状被分解成三角形网格
- 这是因为三角形是图形硬件能高效处理的基本单位
- 像素映射:
- 确定哪些像素落在几何形状内部
- 使用采样算法确定边缘像素是否被覆盖
- 纹理映射与着色:
- 应用颜色、渐变、图案等
- 计算每个像素的最终颜色值
- 抗锯齿处理:
- 处理边缘像素,使线条和曲线看起来更平滑
- 常用技术包括MSAA(多重采样抗锯齿)
合成
页面会在合成线程中合并成一个页面。各个页面已经光栅化了,浏览器只需要根据合成层次树合成一个新的帧来展示滚动后的效果。页面的动画效果也是类似的。将页面上的层进行移动构建出一个新的帧即可。