浏览器是如何渲染页面?

170 阅读7分钟

浏览器结构

浏览器结构分为用户界面、渲染引擎、浏览器引擎。

  1. 用户界面用于展示除标签页窗口之外的其他用户界面内容。
  2. 渲染引擎负责渲染用户请求的页面内容。
  • 渲染引擎下面有很多功能模块 负责网络请求的网络模块 解析和执行js的js解释器 保存数据的数据存储层 等等
  • 渲染引擎是一个浏览器的核心与灵魂(也叫浏览器内核),不同浏览器使用的内核是不同的,IE使用的Trident,Firefox是Gecko,Sarafi使用的webkit, 并将其开源,Chrome是使用的基于webkit改造优化的Blink渲染引擎,也将其开源,Opera和Edge也是使用的Blink。
  1. 在用户界面和渲染引擎之间有个浏览器引擎,用于在用户界面和渲染引擎之间传递数据。

   

以Chrome为例

先拆解一下浏览器的组成结构,浏览器是运行在操作系统上的一个应用程序,应用程序启动进程执行功能 进程又去创建线程去执行

  •   进程是操作系统进行资源分配的调度的基本单位,可以申请和拥有计算机资源,晋城市程序的基本执行实体
  • 线程是操作系统能够进行运算调度的最小单位,一个进程进程中可以并发多个线程,每条线程执行不同的任务

当我们启动某个程序时,就会创建进程来执行任务代码 并且为他分配内存空间 当关闭应用时 该内存空间被回收 进程可以启动多个进程来执行任务,进程通过进程间的通信管道IPC来传递 一般的应用程序都是多进程的 进程之间相互独立 进程可以将任务分成更小的任务 通过创建多个线程去执行不同的任务,并且同一线程之间是可以直接通信共享数据的

  浏览器就是一个多进程的结构,根据进程功能不同来拆解浏览器,可分为浏览器进程,GPU进程 插件进程 渲染器进程 缓存进程 网络进程

  1. 浏览器进程负责与浏览器的其他进程协调工作 用来协调各个进程部门
  2.   网络进程负责发起网络请求
  3.   GPU进程负责图形渲染
  4.   插件进程富足控制网站使用的所有插件
  5.   渲染器进程用来控制显示tab标签内的所有内容 浏览器在默认情况下会为每个标签也都创建一个进程 确保来自不同站点的页面都是独立呈现的 互不影响相互独立
  6.   缓存进程

渲染具体的流程

1.当你在url输入地址时,浏览器进程的UI线程会捕捉你的输入内容 如果访问的是网址 则UI线程会启动一个网络线程来请求DNS进行域名解析接着开始连接服务器获取数据 如果你的输入不是网址而是一串关键字 浏览器就会知道你是搜索 于是就会使用你的默认配置的搜索引擎来查询  

2.当网络线程获取到数据后 会通过SafeBrowing来检查改站点是否是恶意站点 如果是则会展示一个警告页面 阻止你的访问 可以强行访问

3.当返回数据准备完毕并且安全校验通过 网络线程会通知UI线程 UI线程会创建一个渲染器进程来渲染页面 浏览器进程通过IPC管道将数据传递给渲染器进程 正式进入渲染流程(此时地址栏的状态更新 比如history更新 此时可以点击导航懒得后退 )

4.渲染器收到的数据 也就是html 渲染器进程的核心就是把html css js img 等资源渲染成用户可交互的web页面 ,渲染器进程的主线程将html进行解析,构造DOM数据结构。 DOM文档对象模型是浏览器对其页面在其内部表现形式,是web程序员可以通过JavaScript与之交互的数据结构和API 。HTML首先经过Tokeniser标记化,在DOM 树构造过程中会创建Docunment为根节点的DOM树不断进行修改,向其中添加各种元素。(HTML代码中往往会引入一些额外的资源,比如图片,css和js脚本等。图片和css这些资源需要通过网络下载或者从缓存中直接加载。这些资源不会阻塞html的解析,因为他们不会影响DOM的生成,但当html解析过程中遇到script标签,将停止html解析流程,转而去加载解析并且执行js。你可能就会问了?为什么不直接跳过js的加载和执行这一过程,等html解析完后再加载运行js呢?这是因为,浏览器不知道js的执行是否会改变当前页面的html的结构,如果js代码了调用document.write方法来修改html,那之前的html的解析就没有任何意义了。这也就是为什么我们一直说要把script标签要放在合适的位置,或者使用async 或defer属性来异步加载执行js。)

5.当html解析完,我们就获得了一个 dom tree,但我们还不知道dom tree上每个节点应该长什么样子,在知道dom结构和每个节点的样式后,我们需要知道每个节点需要放在页面上的哪个位置,以及该节点需要占用多大的区域。这个阶段被称为layout布局,layout tree是和最后展示在屏幕上的节点对应的

6.现在,我们知道了元素的大小,形状,和位置。这还不够,还需要知道是以什么顺序绘制各个节点的(z-index这个属性就会影响节点绘制的层级关系),为了保证在屏幕上展示正确的层级,在绘制阶段,主线程遍历layout tree 创建了一个绘制记录表,该表记录绘制的顺序。

7.也知道了文档的绘制顺序了,然后主线程将这些信息传递给compositor线程。合成器线程将每个图层栅格化(一层可能像页面的整个长度一样大,因此合成器线程将他们分为多个土块,然后将每个图块发送给栅格线程)。栅格线程栅格化每个图块并将他们存储到GPU内存中,对图块进行栅格化后,合成器线程可以给不同的栅格线程分别优先级。当图块栅格化完成后,合成器线程将手机称为‘draw quads’的图块化信息,根据这些数据合成器线程生成一个合成器Frame。然后这个合成器frame通过IPC传送给浏览器进程,接着浏览器进程将compositor frame 传到GPU,然后GPU渲染展示到屏幕上,则会生成一个新的compositor feame ,新的frame再传给GPU。再次渲染到屏幕上。

总结一下:

浏览器进程的网络线程请求获取到html数据和通过IPC将数据传给渲染器进程的主线程,主线程讲html解析构造DOM树,然后计算样式,根据DOM树和样式生成layout Tree,通过遍历layout tree生成绘制顺序表,然后主线程将layout Tree和绘制顺序信息一起传给合成器线程,合成器线程按规则进程分图层,并把图层分为更小的图块传给栅格线程进行栅格化,栅格化完成后,合成器线程会获得栅格线程传过来的"draw quads"图块信息,根据这些信息,合成器线程合成了一个frame,然后将该合成frame通过IPC传回给浏览器进程,浏览器进程在传到GPU进行渲染,最后就展示到你的屏幕上了。

原文章来自微信公众号:Obj.Tube