引言
从浏览器多进程架构可知页面打开应该也涉及多个进程,它们之间是如何配合的?
本文通过从输入url到页面展示都发生了什么来说明这个问题。我们通过导航流程和渲染流程两个阶段来详细说明。
导航流程
- 浏览器进程接收到用户输入的url请求,然后通过进程间通信(IPC)把url请求转发给网络进程;
- 1.1 如果是搜索内容,地址栏使用默认的搜索引擎,合成新的带搜索关键字的URL
- 1.2 如果是URL,浏览器会根据规则合成完整的URL
另外浏览器还给当前页面执行beforeunload事件运行页面退出之前执行一些数据清理操作或者是否确认离开页面操作
- 网络进程发起真正的url请求
-
2.1 网络进程会查找本地缓存是否缓存了该资源。如有缓存,则返回资源给浏览器进程,如无缓存,直接进入网络请求流程
-
2.2 请求前要进行DNS解析,以获取请求域名的服务器IP地址和端口号(没有端口号则http默认80.https默认443),如果是https,还需要建立TLS连接
-
2.3 chrome浏览器同一域名最多建立6个TCP连接(http1.1协议),如果同一域名超过6个会进入排队状态,少于6个会直接建立TCP连接
-
2.4 利用IP地址和服务器3次握手建立TCP连接,http请求头加上TCP头部向下传给网络层,网络层在数据包上加上IP头部继续向下传输给底层,底层通过物理网络传输给服务器主机;
-
2.5 服务器主机网络层接受到数据包,解析出IP头部识别出数据部分,将解开的数据包向上传输给传输层,传输层解析出TCP头部,识别端口,将解开的数据包向上传输到应用层
-
2.6 应该层HTTP解析请求头和请求体,如果需要重定向响应状态码是301或302,同时在请求头的location字段附上地址;
-
2.7 如果不是重定向,服务器会根据if-none-match值判断资源是否被更新,如果没有更新返回304,否则返回新数据200状态码,如果想要浏览器缓存数据的话会假如cache-control字段
-
2.8 服务器接生成的响应数据(响应行、响应头和响应体等信息)沿着应用层、传输层、网络层、底层、底层、网络层、传输层、应用层的顺序返回到网络进程
-
2.9 输入传输完成,TCP四次挥手断开连接。如何http头部有connection:keep-alive字段,则TCP就一直保持连接
- 网络进程接受到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程
- 3.1 如果状态码是301或者302,需要重定向,网络进程会从响应头的location字段读取重定向地址,然后发起新的请求
- 3.2 如果状态码是200,浏览器通过content-type字段判断服务器响应体的数据类型,来决定显示响应体的内容
- 3.3 如果content-type是application/octet-stream浏览器按照下载类型请求文件,导航流程结束;如果是text/html,则继续导航流程,开始准备渲染进程
- 浏览器进程接受到网络进程的响应头数据后,发送'提交导航'消息到渲染进程,渲染进程是否创建单独进程根据以下两点
- 4.1 打开新页面都会使用单独的渲染进程
- 4.2 如果从A页面打开B页面,且A和B都属于同一站点(相同的协议和根域名),那么B页面复用A页面的渲染进程;其他情况会为B创建新的渲染进程
- 渲染进程收到消息后,便开始准备接收html数据,接收数据的方式是直接和网络进程建立数据管道
- 等文档数据传输完成后,渲染进程会向浏览器进程确认提交
- 浏览器进程接收到渲染进程消息后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态
到这里,完整的导航流程就走完了,接下来要进入渲染阶段了。
渲染流程
1. 构建DOM树
由HTML解析器将html转换为浏览器能够理解的结构
2. 样式计算
- 2.1 渲染引擎把css转换为浏览器能够理解的结构styleSheets;
- 2.2 转换样式表中的属性值进行标准化操作(例如将em->px, red->rgb(255,0,0))
- 2.3 计算出DOM树中每个节点的具体样式,遵守css的继承和层叠两个规则,输出每个DOM节点的样式
3. 布局阶段
- 3.1 创建布局树:由于DOM树含有很多不可见的元素(head标签和display:none属性的元素),所以需要额外构建一棵只包含可见元素的布局树
- 3.2 布局计算:计算每个元素的几何坐标位置,并保存到布局树中
4. 分层
因为页面中有很多复杂的效果(如3D变换、页面滚动或者z-index等),为了提升性能渲染引擎为这些特定的节点生成专用的图层,并生成一棵对应的图层树(layerTree)。
- 4.1 拥有层叠上下文属性的元素会被提升为单独的一层(明确定位属性的元素如fixed、z-index、透明度属性的元素、使用css滤镜的元素)
- 4.2 需要裁剪的地方也会被创建为图层
5. 绘制
渲染引擎会对图层树的每个图层进行绘制,每一个图层的绘制拆分很多小的绘制指令,然后再把这些指令按照顺序组成一个绘制列表,交给合成线程。
6. 分块
有些图层比较大,为了提升性能,合成线程会将图层划分为图块(256x256或者512x512)
7. 光栅化
合成线程会按照视口附近的图块来优先生成位图,这个操作就是光栅化。
图块是删格化执行的最小单位,渲染进程维护了一个删格化的线程池。通常删格化都会使用GPU来加速生成,使用GPU生成位图的过程叫快速删格化,生成的位图保存在GPU内存中。
8. 合成和显示
所有图标都被光栅化合成线程就会生成一个绘制图块的命令drawQuad,交给浏览器进程。浏览器进程有个viz组件接收这个命令后将页面内容绘制到内存中,最后显示在屏幕上。
经过以上流程,我们的页面就展示出来了。
扩展
页面展示出来后,如果修改元素的属性,渲染进程会如何处理?其实要看具体修改了元素的什么属性,然出做出不同的处理。
- 更新元素的几何属性(宽度、高度等):会触发重新布局,开销最大(重排)
- 更新元素的绘制属性(颜色):会从绘制阶段开始重新执行,省去了布局和分层阶段,比重排效率高(重绘)
- 直接合成阶段:会避开重排和重绘,例如css的transform来实现动画效果,可以大大提升绘制效率。
总结
通过本文内容可知,从输入url到页面展现经历了很多流程,理解这个过程对于页面的性能优化将会有很大启发,可以通过整条链路来分析和优化性能。