全文为本人学习的随笔,以官网提供的内容为准,参考不可保证其正确性与权威性。
本文参考:www.bilibili.com/video/BV1x5… (极其推荐)
[ 浏览器组成 ]
-
用户界面: 用于展示除标签页窗口外的其他用户内容
-
浏览器引擎: 作为渲染引擎与用户界面的桥梁,标签页窗口也属于浏览器引擎
-
渲染引擎: 用于渲染用户界面的内容,网络与JS解析器属于渲染引擎中,渲染引擎由成为浏览器的内核。
[ 浏览器内核 ]
-
Trident: IE浏览器的内核
-
Gecko: Firefox浏览器的内核
-
Webkit: Safari浏览器的内核
-
Blink: 经Webkit优化产生的浏览器内核,被用于Chrome,Edge。
[ 进程与线程的区别 ]
进程
当执行某个程序时,会给该程序创建一个进程,同时会给该进程分配内存空间,用于执行,该程序的状态都会被保存在进程对应的内存空间中。
IPC进程通信管道
当程序有需要时,可以启动多个进程去执行程序任务,每个进程所分配的内存空间是独立的,因此两个进程间需要进行通信,则需要通过IPC通信管道来进行。因为每个进程是相互独立的,因此程序不会因为单个进程卡死而导致程序无法运行。
[ 线程 ]
进程可以在内部创建多个线程,以并行执行不同的任务,同一进程下的线程可以直接通信共享数据。
[ 早期浏览器单进程结构的缺陷 ]
早期的浏览器为单进程,其js解析,页面渲染等由多个线程完成。其会因为一个线程卡死导致整个浏览器无法运行,例如打开多个标签页时会因为单个标签页的卡死导致整个浏览器无法运行。由于线程直接能够直接共享数据,意味着JS线程有能够随意访问其他线程的数据的可能性,从而单进程浏览器结构的不够安全。同时单个线程负责较多功能会引发运行效率的问题。
[ 多进程浏览器结构 ]
-
浏览器进程 控制除用户界面外的标签页界面,包括地址栏,书签,后退前进按钮,以及协调浏览器的其他工作
-
网络进程 负责浏览器的网络请求的发起接受
-
GPU进程 负责整个浏览器界面的渲染
-
插件进程 负责控制浏览器使用到的插件,例如flash,于浏览器的插件商店不是一个概念
-
渲染器进程 控制显示标签页用户界面的所有内容,默认情况下会为每个标签页新建渲染器进程,其取决于浏览器决定的渲染器进程模型。
[ 渲染器进程模型 ]
-
Process-per-site-instance 字面意思是为每一个站点的每一个页面实例创建进程,即每一个标签页都会创建独立的渲染器进程。 相较于其他模式会占用更多的内存空间,但使得每个标签页渲染器进程独立从而更加安全,是相较最安全的渲染器进程模型。
-
Process-per-site 字面意思是为每一个站点创建进程,当标签页来自同一个站点,将共用一个渲染器进程。
-
Process-per-tab 字面意思是为每一个tab创建进程,即单个浏览器tab栏内的所有标签页,共用一个渲染器进程。
-
Single process 字面意思是单一进程,会让浏览器引擎与渲染引擎共用一个进程。
[ 地址栏输入内容并跳转的过程 ]
[ 浏览器进程工作流程 ]
-
[ UI线程识别内容 ] 浏览器的UI线程会捕捉用户的输入内容,并判断输入内容为网址还是关键词。当输入内容为关键词时,浏览器会使用默认配置的搜索引擎进行搜索查询。
-
[ 请求DNS进行域名解析 ] 输入内容为网址时,浏览器网络进程会启动一个网络线程进行域名解析,建立连接,获取目标服务器的页面数据。
-
[ SafeBorwsing安全检测 ] SafeBorwsing是chrome内部的站点安全系统,通过检查站点数据,或者比对谷歌黑名单,来判断站点是否为恶意站点。 获取到页面数据后,浏览器的SafeBorwsing会检查站点是否是恶意站点,当页面是恶意站点会向用户展示警告页面,用户可以选择依然访问。
-
[ 准备进入渲染流程 ] 上述过程都发生在浏览器进程中,UI线程与网络线程都是浏览器进程中创建的线程。 当SafeBorwsing安全检测工作结束后,会让网络线程通知UI线程,进行下一步工作。 浏览器会创建一个渲染器进程来渲染页面,浏览器进程中的页面数据需要通过IPC管道将数据传递给渲染器进程,从而进入渲染流程。
[ 渲染器进程工作流程 ]
- [ 创建DOM树 ]
- [ Tokeniser解析创建文档对象模型 ] 主线程通过Tokeniser标记化结合词法分析将输入的html内容,解析成多个标记,再根据标记进行文档对象模型构造。
- [ DOM树构建过程 ] 在文档模型对象构造过程,创建document对象,以document为根节点的DOM树,根据文档对象模型的构造,不断向DOM树中添加新的节点。
- [ 图片与CSS引入 ] html会引入的额外资源,包括图片,css,js等等。图片和css资源,需要通过网络下载或缓存中直接加载,但不会阻塞上述html的解析过程,因为图片与css的加载引入,不会影响已经生成的DOM树。
- [JavaScript引入 ] 但遇到js的引入,就会停止html解析流程,优先引入执行js的内容。 如果跳过加载执行js,继续构建DOM树,当js涉及到DOM操作引发DOM树变更时,先前构建的DOM树则毫无意义。 正因为浏览器无法提前预知当前引入加载的js是否会影响DOM树的构建,因此在DOM树构建过程遇到js的引入,则会优先加载执行js内容。
- [ 结论 ] 在DOM树创建完毕的阶段,可以得到页面需要的每个节点及其对应的样式,以及页面需要的图片,js。但不知道每个节点在页面中的位置。
- [ 创建layout树 ]
- [ 样式解析计算 ] 在DOM树构建完成后,根据先前引入的图片与css,进行样式的解析计算,并得到样式在DOM树上对应的位置。
- [ layout布局 ] 当DOM树样式解析结束后,需要知道DOM树每个节点对应的页面位置,及其节点占用区域。主线程通过遍历DOM树的方式,得到一个由页面坐标与占用尺寸组成,与DOM树对应的节点树,称为Layout树。 Layout树的节点并不与DOM树的节点严格一一对应,DOM树上样式为display: none的节点,在DOM树上存在,但在Layout树上不存在。而伪元素的content样式写入内容的节点,不会出现在DOM树上,但会出现在Layout树上,因为DOM树是通过解析HTML获得,并不关注样式,而Layout树是根据DOM树与解析后的样式生成的,因此与样式关联。
- [ 结论 ] 在layout树创建完毕的阶段,已经得到了页面需要的每个节点及其对应的样式和布局空间位置,但不知道页面的节点绘制顺序。
- [ 创建paint Record ] 根据Layout树与DOM树,创建绘制记录表(paint Record),用于记录每个节点的绘制顺序。z-index产生的页面层级就是通过绘制记录表实现,更晚的绘制顺序则层级在上层。
- [ 创建Layer树 ] 当DOM树,Layout树,绘制记录表都已经创建完毕后,渲染器进程的主线程会将上述内容转化为Layer树,Layer树记录了每个节点的图层绘制信息,并将其传递给合成器线程,让其绘制图层。
- [ GPU渲染页面 ]
- [ 合成页面图层 ] 合成器线程将根据Layer树,绘制页面图层,由此得到一层比整个页面面积相当,或者更大的图层。
- [ 栅格化图层切分 ] 栅格化是将图像由矢量图,或数据表示的不可显示图像转换为可显示图像的过程。 当合成器线程合成页面图层后,会将其切分为12等分或24等分的图块,以便于存储渲染,并传递给多个栅格化线程进行栅格化。
- [ 合成Compositor Frame ] 当栅格化线程完成了自身接受到的图块,并完成栅格化后,得到称为Draw Quads的图块信息,Draw Quads记录了图块对应的内存位置和页面绘制位置。当每个图块都完成栅格化并得到Draw Quads后,栅格化线程会将Draw Quads返回给合成器线程。 合成器线程在收到所有图块的Draw Quads后,会将其合成为一个合成器帧(Compositor Frame),并通过IPC传递给浏览器进程。
- [ GPU渲染合成器帧 ] 浏览器进程接受到合成器帧后,将其传递给GPU,由GPU完成对页面的视图渲染。 当用户在页面上进行了操作,页面需要进行视图更新,则会有合成器合成新的合成器帧,逐级交给GPU进行逐帧渲染。
[ 输入url到显示页面全过程总结 ]
用户输入内容,经过UI线程检查其内容为url或关键词,检查为关键词则调用浏览器默认的搜索配置进行关键词搜索。
当用户输入内容为url,浏览器进程会创建网络线程去获取页面数据,并通过IPC将页面数据传递给渲染器进程。
渲染器进程主线程解析html得到DOM树,确定页面需要的节点。
主线程进行样式计算得到每个DOM树节点对应的样式,并根据样式计算后的DOM树,得到Layout树,确定了每个节点对应的页面区域。
主线程再通过遍历Layout树,得到绘制顺序表(Paint Record),得到了页面的绘制顺序。
主线程将DOM树,Layout树,Paint Record的内容转化为合成器线程可用于图像绘制的Layer树,并传递给合成器线程,绘制出页面图层。
合成器线程在绘制合成出页面图层后,将页面图层分割为12等分或24等分,并将每等份的图块交由单个栅格化线程,进行栅格化。
栅格化线程在完成图块栅格化后,得到栅格化图块信息的内容Draw Quads,并传递回合成器线程。
合成器线程根据每个图块的Draw Quads合成得到合成器帧(Compositor Frame),并通过IPC传递给浏览器进程,浏览器进程再传递给GPU,完成页面图像渲染绘制。
当用户操作页面时,合成器又会产生新的合成器帧逐级传递给GPU进行逐帧渲染。
[ 页面更新 ]
-
[ 重排 ] 当一个节点的宽高发生改变,其需要重新进行样式计算,其页面所在区域便发生了改变,决定页面区域的是layout树,则需要重新创建layout树,layout树的重新创建,意味着依赖layout树的Paint Record与layer需要重新创建。 样式更新 -> layout树重建 -> Paint Record重建 -> layer树重建 这一流程即是页面重排。 可以得出结论,节点区域发生改变,则layout树重建会触发重排。
-
[ 重绘 ] 当一个节点的背景色发生改变,其样式需要重新计算,因为其区域并没有发生改变,则layout树依然是不变的,依赖于layout树的layer树也是不变的。 只需要在Paint Record上对节点的样式进行重新绘制即可。 样式更新 -> Paint Record重建 这一流程即是页面重绘。 可以得出结论,节点区域不变的样式更新,不会使layout树重建,从而只会引发重绘。
-
[ js引发的页面丢帧 ] js的执行与页面的重绘重排都是占用渲染器中的主线程,当主线程完成页面当前帧的渲染工作时,则会将主线程交由js执行占用。 当上一帧渲染结束后,一段js占用主线程时间过长,在下一帧渲染开始时还未结束,则会导致页面丢帧,产生页面卡顿。 requestAnimationFrame()能够将js拆分为多个部分进行执行,会在下一帧渲染开始时交换主线程的占用,在渲染结束后,继续执行。
-
[ transform ] transform动画的过程在合成器线程与栅格化线程中完成,因此transform动画不会与js抢占渲染器主线程。 由于transform动画全过程在合成器线程与栅格化线程中完成,其不涉及渲染器主线程的重绘重排,因此会引发重排的节点区域变化,如位置移动,宽高缩放,旋转等,可以通过transform代替,从而避免触发重排。