原文
1. 简介
- 浏览器的主要组件:
-
用户界面:即除了主窗口显示您请求的页面以外的部分
- 地址栏 - 前进/后退按钮 - 书签菜单
- 浏览器引擎:在用户界面和呈现引擎之间传送指令
- 渲染引擎:显示请求的内容
- 网络:由于网络调用,如HTTP请求。其接口与平台无关,并为所有平台提供底层实现
- 用户界面后端:用于绘制基本的窗口小界面
- JavaScript解释器:用于解析和执行JavaScript代码
- 数据存储:持久层。如Cookie
2. 渲染引擎
- Gecko:Firefox所使用的是Mozilla公司的渲染引擎。
- Webkit:Safri和Chrome所使用的的渲染引擎,是一种开放代码渲染引擎。
2.1 主流程
- 基本流程:
2.2 主流程示例
- Webkit主流程
- Gecko主流程
3. 解析和DOM树构建
3.1 解析-综述
3.1.2 解析器和词法分析器的组合
- 解析过程:分为两个子过程,词法分析和语法分析
- 词法分析:将输入内容分割成大量标记的过程
- 语法分析:应用语言的语法规则的过程
3.1.3 编译
3.1.6 解析器类型
- 自上而下解析器:从语法的高层结构出发。
- 自下而上解析器:从低层规则出发,又称位移归约解析器,因为输入在向右位移,并且逐渐归约到语法规则上。
3.1.7 自动生成解析器
-
WebKit使用了两种非常有名的解析器:
- Flex:用于创建词法分析器,输入的是保护标记的正则表达式定义的文件。
- Bison:用于创建解析器,输入的是采用BNF格式的语言语法规则。
3.2 HTML解析器
3.2.4 DOM
-
定义:
- DOM是文档对象模型(Document Object Model)的缩写。
- 是HTML文档的对象表示,同时也是外部内容(如:JavaScript)与HTMl元素之间的接口。
- 解析树的根节点是Document对象。
3.2.5 解析算法
-
HTML无法使用自上而下或自下而上的解析器进行解析的原因:
- 语言的宽容本质;
- 浏览器历来对一些常见的无效HTML用法采取包容态度;
- 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是HTML中若包含脚本标记document.write等,就会添加额外的标记,这样解析过程就更改了输入内容;
- 浏览器创建的自定义解析器:由标记化和树构建两个阶段组成。
3.2.6 标记化算法
- 标记化:词法分析过程,将输入内容解析成多个标记。HTML标记包括起始标记、结束标记、属性名称和属性值。
3.2.7 树构建算法
3.2.8 解析结束后的操作
- 浏览器将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本(即应在文档解析完成后才执行的脚本)
- 文档状态设置为“完成”,一个“加载”时间将随之触发
3.3 CSS解析
- Webkit使用Flex和Bison解析器生成器,通过CSS语法文件自动创建解析器,Bison会创建自下而上的位移归约解析器。
- Firefox使用的是人工编写的自上而下的解析器。
-
以上两种解析器都会将CSS文件解析成:
- StyleSheet对象:每个对象都包含CSS规则
- CSS规则:包含选择器、声明对象、其他与CSS语法对应的对象
3.4 处理脚本和样式表的顺序
3.4.1 脚本
- 网络的模型是同步的,在遇到
<script>
标记时立即解析并执行脚本,文档解析将停止,直到脚本执行完毕。 - 若脚本是外部的,解析过程会停止,直到从网络同步抓取资源后再继续。
- 可将脚本标注为“defer”,这样就不会停止文档解析,而是等到解析结束才执行。
3.4.2 预解析
- Webkit和Firefox都进行了这项优化。
- 在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。
- 预解析器不会修改DOM树,只会解析外部资源(如:外部脚本、样式表、图片)的引用。
3.4.3 样式表
- 理论上,应用样式表不会更改DOM树,所以没必要等待样式表并停止文档解析。
- 但脚本在文档解析阶段会请求样式信息,若还未加载和解析样式就会报错。
- FireFox在样式表加载和解析过程中,会禁止所有脚本。
- Webkit仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,才禁止该脚本。
4. 渲染树构建
-
渲染树:
- 在DOM树构建的同时,浏览器还会构建的另一个树结构。
- 由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。
- 作用是按照正确的顺序绘制内容。
- Firefox称渲染树中的元素为“框架”,Webkit称为渲染器/渲染对象。
4.1 渲染树和DOM树的关系
- 两者相对应,但非一一对应;
- 非可视的DOM元素(如:head元素、display属性为none的元素)不会插入到渲染树中;
- 浮动定位和绝对定位的元素处在正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是占位框架;
4.2 构建渲染树的流程
- Firefox中,系统针对DOM更新注册展示层,作为侦听器,展示层将框架创建工作委托给FrameConstructor,由该构造器解析样式并创建框架。
- Webkit中,解析样式和创建渲染器的过程称为“附加”。每个DOM节点都有“attach”方法。附加是同步进行的,将节点插入DOM树需要调用新的节点“attach”方法。
-
处理html和body标记会构建渲染树根节点。
- 这是最上层的容器block,包含了其他所有block;
- 它的尺寸就是视口(即浏览器窗口显示区域的尺寸),Firefox称其ViewPortFrame,Webkit称其RenderView;
- 是文档所指向的渲染对象;
- 其余部分以DOM树节点插入的形式来构建;
4.3 样式计算
-
样式来源:
- 样式表:浏览器的默认样式表、网页作者提供的样式表、浏览器用户提供的用户样式表
- inline样式
- HTML中的可视化属性
4.3.2 Firefox规则树
-
Firefox还采用另外两种树来简化样式计算:
- 规则树
- 样式上下文树
4.3.2.2 使用规则树计算样式上下文
TODO:规则树的生成规则还不太清楚= =先跳过
4.3.4 以正确的层叠顺序应用规则
4.3.4.1 样式表的层叠顺序
- 浏览器声明
- 用户普通声明
- 作者普通声明
- 作者重要声明
- 用户重要声明
4.3.4.2 特异性
-
将四个数字按 a-b-c-d 这样连接起来(位于大数进制的数字系统中),构成特异性:
- 如果声明来自于“style”属性,而不是带有选择器的规则,则a记为1,否则a记为0
- 选择器中ID属性的个数记为b
- 选择器中其他属性和伪类的个数记为c
- 选择器中元素名称和伪元素的个数记为d
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */ li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */ li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */ h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */ ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */ li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */ #x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */ style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
5. 布局
- 渲染器在创建完成并添加到渲染树时,并不包含位置和大小信息,计算这些值的过程称为布局或重排。
- 所有的渲染器都有一个“layout”或者“reflow”方法,每一个渲染器都会调用其需要进行布局的子代的layout方法。
5.1 脏位系统(Dirty bit system)
- 用于避免对所有细小更改都进行整体布局;
- 若某个渲染器发生更改,将自身或子代标注为“dirty”,表示需要进行布局;
-
两种标记:
- “dirty”
- “children are dirty”:表示尽管渲染器自身没有变化,但其子代至少有一个需要布局
5.2 全局布局和增量布局
-
全局布局:指触发了整个渲染树范围的布局,如:
- 影响所有渲染器的全局样式更改,如字体大小更改;
- 屏幕大小调整;
-
增量布局:只对脏位渲染器进行布局(这样可能存在需要进行额外布局的弊端),如:
- 但来自网络的额外内容添加到DOM树之后,新的渲染树附加到了渲染树中;
5.3 异步布局和同步布局
- 同步布局:全局布局往往是同步触发的,有时初始布局完成后,若一些属性(如滚动位置)发生变化,布局就会作为回调而触发。
- 异步布局:增量布局是异步的,请求样式信息(如:offsetHeight)的脚本可同步触发增量布局。
5.5 布局处理
-
布局模式:
- 父渲染器确定自己的宽度;
-
父渲染器依次处理子渲染器,并:
- 放置子渲染器(设置x,y坐标)
- 若有必要,调用子渲染器的布局,这会计算子渲染器的高度
- 父渲染器根据子渲染器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父渲染器的父渲染器使用;
- 将其dirty位设置为false;
5.6 宽度计算
5.7 换行
- 若渲染器在布局过程中需要换行,会立即停止布局,并告知其父代需要换行。父代会创建额外的渲染器,并对其调用布局。
6. 绘制
- 在绘制阶段,系统会遍历渲染树,并调用渲染器的“paint”方法,将渲染器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。
6.1 全局绘制和增量绘制
6.2 绘制顺序
- 背景颜色
- 背景图片
- 边框
- 子代
- 轮廓
6.3 Firefox 显示列表
6.4 WebKit 矩形存储
- 在重新绘制之前,WebKit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。
7. 动态变化
- 在发生变化时,浏览器会尽可能做出最小的响应;
- 若元素改变颜色,则只对改元素进行重绘;
- 若元素改变位置,则只对该元素及其子元素(可能还有同级元素)进行布局和重绘;
- 添加DOM节点,则只对该节点进行布局和重绘;
- 一些重大变化(如:增大html元素字体)会导致缓存无效,使得整个渲染树都会进行重新布局和绘制;
8. 渲染引擎的线程
-
渲染引擎采用单线程:
- Firefox和Safari中,该线程就是浏览器的主线程;
- Chrome中,该线程是标签进程的主线程;
- 几乎所有操作(除了网络操作)都是在单线程中进行的;
- 网络操作可由多个并行线程执行,并行连接数是有限的(通常为2-6个,如:Firefox3有6个)
8.1 事件循环
- 浏览器的主线程是事件循环,它是一个无限循环,永远处于接受处理状态,并等待事件发生(如:布局、绘制),并进行处理。
9. CSS2可视化模型
9.1 画布
- 定义:用来渲染格式化结构的空间,即供浏览器绘制内容的区域,画布的空间尺寸大小是无限的,但是浏览器会根据视口的尺寸选择一个初始宽度。
9.2 CSS框模型
- 所有元素都有display属性,决定了对应的框类型。默认值为inline,但是浏览器样式表设置了其他默认值,如:div的display属性为block
9.3 定位方案
-
三种定位方案:
- 普通:根据对象在文档中的位置进行定位,也就是说对象在呈现树中的位置和它在 DOM 树中的位置相似,并根据其框类型和尺寸进行布局。
- 浮动:对象先按照普通流进行布局,然后尽可能地向左或向右移动。
- 绝对:对象在呈现树中的位置和它在 DOM 树中的位置不同。
-
框的布局方式由以下因素决定:
- 框类型
- 框尺寸
- 定位方案
- 外部信息,如:图片大小和屏幕大小
9.4 框类型
-
block框:
- 形成一个block,在浏览器窗口中拥有其自己的矩形区域。
- 采用的是一个接一个的垂直格式。
-
inline框:
- 没有自己的block,但是位于容器block内。
- 采用的是一个接一个的水平格式。
9.5 定位
- 相对定位:先按照普通方式定位,然后根据所需偏移量进行移动。
- 浮动定位:框会移动到行的左边或右边。有趣的特征在于,其他框会浮动在它的周围。
- 绝对定位和固定定位:与普通流无关。元素不参与普通流。尺寸是相对于容器而言的。在固定定位中,容器就是可视区域。
9.6 分层展示
- 由z-index属性指定的。它代表了框的第三个维度,也就是沿“z 轴”方向的位置。
- 这些框分散到多个堆栈(称为堆栈上下文)中。在每一个堆栈中,会首先绘制后面的元素,然后在顶部绘制前面的元素,以便更靠近用户。如果出现重叠,新绘制的元素就会覆盖之前的元素。
- 堆栈是按照 z-index 属性进行排序的。具有“z-index”属性的框形成了本地堆栈。视口具有外部堆栈。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。