首页 > 代码库 > 浏览器渲染引擎工作原理

浏览器渲染引擎工作原理

  浏览器内核包括渲染引擎和JS引擎,由于js引擎越来越独立,内核就倾向于只指渲染引擎

  渲染引擎是一种对HTML文档进行解析并将其显示在页面上的工具。它负责取得网页的内容(HTML、XML、图象等等)、整理信息(例如加入CSS等),以及计算网页的显示方式然后会输出至显示器或打印机

渲染引擎工作流程

HTML解析器解析DOMM树(解析为DOM树上个节点,同时解析CSS样式)

渲染树结构(具有一定的视觉效果,并按照一定顺序排列在屏幕上)

布局渲染树(为每个节点分配固定坐标)

绘制DOM树(渲染引擎会遍历所有的节点,由UI后端绘制)

                                                                           Webkit工作流程

 

                                        技术分享

 

                                                                                Gecko工作流程

                                技术分享

 

Webkit和gecko属于不同,但流程基本相同。 Gecko将视觉化后的树称为“框架树”,webkit称为“渲染树”;对于元素的排放,webkit称为“布局layout”,gecko称为“重拍reflow”;gecko在html解析和DOM树间添加了“内容槽”,用于生成DOM元素;将DOM树和样式信息构建渲染树的过程,webkit称为“附加”,gecko称为“框架结构”。

 

1解析

  将文档转化为代码可以使用的结构,解析的结果代表了文档结构的节点树,也称为解析树。

  解析器分两个结构:词法分析和语法分析 词法分析负责将输入内容分解为有效标记符号;语法分析对语言应用语法规则,从而构建解析树。

  解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记。 如果没有规则可以匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误。

  转换:将解析树转换成机器代码语言。 自动生成解析器: WebKit 使用了两种解析器生成器:用于创建词法分析器的 Flex 和用于创建解析器的 Bison。Flex 的输入是包含标记的正则表达式定义的文件。Bison 的输入是采用 BNF 格式的语言语法规则。 HTML DTD Html适用DTD格式进行定义,这一格式是用于定义SGML家族的语言,包括了对所有允许元素及它们的属性和层次关系的定义。

  DOM 输出的树,也就是解析树,是由DOM元素及属性节点组成的。DOM是文档对象模型的缩写,它是html文档的对象表示,作为html元素的外部接口供js等调用。

 1-1 HTML解析器 将HTML解析器解析成解析树 解析算法: 此算法由两个阶段组成:标记化树构建

    标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。

    标记化算法 该算法的输出结果是 HTML 标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。

   树构建算法 在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。标记生成器发送的每个节点都会由树构建器进行处理。 规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式” 解析结束后的操作 在此阶段,浏览器会将文档标注为交互状态,并开始解析在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。

Webkit容错机制

(1)明显不能在某些外部标记中添加的元素。在此情况下,我们应该关闭所有标记,直到出现禁止添加的元素,然后再加入该元素。

(2)我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)

(3)向 inline 元素内添加 block 元素。关闭所有 inline 元素,直到出现下一个较高级的 block 元素。

(4)如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。

 1-2CSS解析器

  CSS 是上下文无关的语法。 词法语法(词汇)是针对各个标记用正则表达式定义 WebKit CSS 解析器 WebKit 使用 Flex 和 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。 Bison 会创建自下而上的移位归约解析器。Firefox 使用的是人工编写的自上而下的解析器。 都会将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则对象则包含选择器和声明对象,以及其他与 CSS 语法对应的对象。

技术分享

 

1-3 处理脚本和样式表的顺序

脚本

  网络的模型是同步的。 解析器遇到解析器遇到<script> 标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。在 HTML4 和 HTML5 规范中将脚本标注为“defer”,这样不会停止文档解析,而是等到解析结束才执行。HTML5 增加了一个选项,可将脚本标记为异步,以便由其他线程解析和执行。

预解析

  在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度。,预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

样式表

  应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。但脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复。解决方案:Firefox, 在样式表加载和解析的过程中,会禁止所有脚本。WebKit,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,才会禁止该脚本。

2渲染树构建

  作用:按照正确的顺序绘制内容。根据 display 属性的不同,针对同一个 DOM 节点应创建什么类型的呈现器。

  与DOM树区别:非可视化的 DOM 元素不会插入渲染树中,例如“head”元素。如果元素的 display 属性值为“none”,那么也不会显示在渲染树中(但是 visibility 属性值为“hidden”的元素仍会显示)。有一些 DOM 元素对应多个可视化对象。它们往往是具有复杂结构的元素,无法用单一的矩形来描述,如select;关于多呈现器的例子是格式无效的 HTML;渲染对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。浮动定位和绝对定位的元素

  构建渲染树流程: Firework,系统针对DOM更新注册展示层,作为侦听器。展示层将框架创建工作委托给FrameConstructor,由该构造器解析样式并创建框架。 Webkit,解析样式和创建render器的过程称为“附加”,每一个DOM节点都会有附加方法。 处理html和body标记就会创建渲染树的根节点。由根节点渲染对象就是CSS中所说的容器block,它是最顶层的block,其余的block都被它包含。它的大小就是浏览器视图的大小。渲染树的其余部分都是作为DOM树节点形式被插入到渲染树中的。

2-1计算样式

  计算渲染对象的可视化属性,就是计算每个元素的样式属性。 难点:样式数据大;元素匹配复杂;应用规则涉及到复杂的层叠规则

2-2 计算顺序

 

3 布局

  渲染树并不包含位置和大小信息,计算这些信息的过程称为布局或重排。

  HTML采用基于流的布局模型,只需要遍历一次就可以计算出所有的几何信息。处于流后面位置的元素不会影响前面元素的几何特征。布局可以按照从左至右,从上至下的顺序遍历文档。 布局是个递归的过程,它从根渲染器开始然后递归遍历所有子渲染器,具有子渲染器的渲染器继续递归遍历遍历所有子渲染器。每个渲染器都有一个“layout”或“reflow”方法,每个渲染器都会调用需要进行布局的子代的layout方法。

  Dirty位系统

  作用:避免细小的变化影响整体布局。 如果渲染器发生了变化或者其子代发生了变化就会被标注为”dirty”或children are dirty,需要进行布局。

  全局布局和增量布局

  全局布局:是指触发了整个渲染树的布局,触发原因:1 影响所有渲染器的全局样式发生了改变,比如size变化。2 屏幕尺寸发生变化。

  增量布局:只对标记为dirty的渲染器布局。 异步布局和同步布局 增量布局是异步执行的。Firefox将增量布局的“reflow 命令”加入队列,调度程序会触发批量触发这些命令。

Webkit 有一个定时器来执行增量布局,对渲染树遍历,并对有dirty标记的渲染器进行布局。 全局布局是同步触发的。初始布局完成之后,如果一些属性(如滚动位置)发生变化,布局就会作为回调触发。

  布局处理

  (1) 父呈现器确定自己的宽度。

  (2) 父呈现器依次处理子呈现器,并且: 放置子呈现器(设置 x,y 坐标)。 如果有必要,调用子呈现器的布局(如果子呈现器是 dirty 的,或者这是全局布局,或出于其他某些原因),这会计算子呈现器的高度。

  (3) 父呈现器根据子呈现器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父呈现器的父呈现器使用。

  (4) 将其 dirty 位设置为 false 宽度计算 呈现器宽度是根据容器块的宽度、呈现器样式中的“width”属性以及边距和边框计算得出的

4绘制

   遍历渲染树,并调用渲染器的“paint”方法,将渲染器的内容显示在屏幕上。绘制工具是使用用户界面基本组件完成的。

  全局绘制和增量绘制

  绘制也分为全局绘制和增量绘制。

  在增量绘制中,部分渲染器发生变化不影响整个树。 更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。在 Chrome 浏览器中,情况要更复杂一些,因为 Chrome 浏览器的呈现器不在主进程上。Chrome 浏览器会在某种程度上模拟 OS 的行为。展示层会侦听这些事件,并将消息委托给呈现根节点。然后遍历呈现树,直到找到相关的呈现器,该呈现器会重新绘制自己(通常也包括其子代)

绘制顺序

 绘制的顺序其实就是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。块呈现器的堆栈顺序如下:

  背景颜色,背景图片,边框,子代,轮廓

5动态变化

  在发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大“html”元素的字体)会导致缓存无效,使得整个呈现树都会进行重新布局和绘制。

6渲染引擎的线程

  除了网络操作以外,几乎所有操作都采用单线程。

  在 Firefox 和 Safari 中,该线程就是浏览器的主线程。而在 Chrome 浏览器中,该线程是标签进程的主线程。 网络操作可由多个并行线程执行。并行连接数是有限的(通常为 2 至 6 个,以 Firefox 3 为例是 6 个)。 事件循环 浏览器的主线程是事件循环,它是无线循环的,永远处于接收状态,并等待(如布局和绘制事件)发生,并进行处理

7 CSS2可视化模型

  画布

  CSS框模型

  定位方案

     (1) 普通,根据对象在文档中的位置进行定位。

     (2) 浮动,先按照普通流定位,再尽可能的左右上下移动

     (3) 局对,对象在渲染树中的位置和它在DOM树中的位置不同 定位方案是由“position”属性和“float”属性设置的。

          如果值是 static 和 relative,就是普通流 如果值是 absolute 和 fixed,就是绝对定位 框类型 block 框:形成一个 block,在浏览器窗口中拥有其自己的矩形区域 inline 框:没有自己的 block,但是位于容器 block 内 block 采用的是一个接一个的垂直格式,而 inline 采用的是水平格式

   定位

  分层展示

       这是由 z-index CSS 属性指定的。它代表了框的第三个维度,也就是沿“z 轴”方向的位置。 这些框分散到多个堆栈(称为堆栈上下文)中。在每一个堆栈中,会首先绘制后面的元素,然后在顶部绘制前面的元素,以便更靠近用户。如果出现重叠,新绘制的元素就会覆盖之前的元素。 堆栈是按照 z-index 属性进行排序的。具有“z-index”属性的框形成了本地堆栈。视口具有外部堆栈。

浏览器渲染引擎工作原理