本文为学习整理笔记,原文来自于系列文章《浏览器是如何工作的系列》。感谢原作者及翻译者,受益匪浅。
本文涉及到的关键点:渲染引擎工作流程、一般文档解析方法、html解析器及其流程、树构造算法。
浏览器的主要概念
浏览器的主要工作的展示网页资源,即请求服务器,并将结果显示在浏览器窗口中,网页资源的格式主要是HTML,也有PDF、image等其它各种格式,资源的定位由URL来实现,更多请参考"网络"一节。
1.用户接口——包含地址栏、前进和后退按钮、书签等除了显示网页以外的部分。
2.浏览器引擎——查询与操作渲染引擎的接口。
3.渲染引擎——负责显示请求的内容,比如:如果请求的是一个HTML文档,它负责解析HTML和CSS,把解析的内容显示到屏幕上。
4.网络——用于网络请求,如HTTP请求,它包括与平台无关的接口和各平台独立的实现。
5.UI后端——用来绘制基础元件,像组合框和窗口。它提供与平台无关的接口,内部使用操作系统的接口方法。
6.javascript解释器——用于解释和执行javascript代码。
7.数据存储——这是一个持久层,浏览器需要把所有数据存储到硬盘上,如cookies.HTML5规定了一个完整的(虽然轻量级)数据库“web database”.
需要注意的是,与其它浏览器不同,chrome使用多个渲染引擎实例,每个Tab一个,每个Tab都是一个独立进程。
渲染引擎
一、渲染引擎工作流程
渲染引擎的功能就是渲染,在浏览器上显示请求的内容。
默认情况下,渲染引擎可以显示HTML和XML文档和图像。他也可以显示其他类型的插件(浏览器扩展)。例如显示PDF使用PDF浏览器插件。
渲染引擎有两个:gecko、webkit。FF使用Gecko——“自制”Mozilla渲染引擎。 Safrai和Chrome都使用Webkit引擎。
下图是渲染引擎的基本流程:
渲染引擎开始解析HTML文档,并且把HTML标签转化为一个被叫做“内容树”的DOM树,它将解析CSS样式,包括外部样式和内嵌样式。样式数据和HTML中的显示控制将共同用来创建另一棵树——渲染树。
渲染树包含带有颜色和尺寸等显示属性的矩形,这个矩形的顺序与显示顺序一致。
渲染树构建完成后就是“布局”处理——确定每个节点在屏幕上的确切显示位置。 下一个步骤就是绘制——遍历渲染树并用UI后端层将每一个节点绘制出来。
这是一个缓慢(渐进)的过程,为了更好的用户体验,渲染引擎将尽可能的把内容显示到屏幕上。
它不会等到所有的HTML被解析完才开始创建和布局渲染树。它会在处理后续内容的同时把已经处理过的内容显示出来。
下图是Webkit主要流程示例:
Webkit 渲染引擎基本流程
下图是Gecko主要流程示例:
Gecko 渲染引擎基本流程
从图2.1和图2.2可以看出虽然Webkit和Gecko使用术语稍微不同,主要流程还是基本相同的。一个非语义上的小差别是Gecko在HTML与DOM树之间有一个附加的层 ,称作”content sink”,是创建DOM对象的工厂。
二、解析和DOM树的建立
1、解析:
解析一个文档,意味着把它转换为一个有意义的结构——代码可以了解和使用的东西,解析 的结果通常是一个树的节点集合,用来表示文档结构,它被称为解析树或者语法树。
例子: 解析表达式“2+3-1”,返回树如下图3.1
解析过程是迭代的。解析器通常会从词法分析器获取新符号并尝试匹配句法规则。如果匹配成功,就在句法树上创建相应的节点,并继续从词法分析器获取下一个符号。
如果没有匹配的规则,解析器会内部保存这个符号,并继续从词法分析器获取符号,直到内部保存的所有符号能够成功匹配一个规则。
如果最终无法匹配,解析器会抛出异常。这意味着文档无效,含有句法错误。
3)、转换:
多数情况下,解析树并非是最终结果,解析经常被用于转换——输入文档转换为另一种格式,比如一个编译器要把源码编译成机器码,首先会解析成解析树,然后再转换成机器码,如下图:
关于解析的具体工作示例请参照:这里 。该示例为上下文无关的语法,词汇转换规则用正则表达式来表示,语法规则用BNF格式定义,常规解析器只能解析上下文无关语法的语言。这种语言的一个直觉的定义是它的句法可以用BNF完整的表达。
同时介绍了有两种基本类型——自上而下解析器和自下而上解析器各自的解析流程。
2.HTML解析器:
HTML解析器的工作是解析HTML标记到解析树。
1)HTML语法定义
HTML的词汇与句法定义在w3c组织创建的规范中。当前版本是HTML4,HTML5的工作正在进行中。
2)不是上下文无关语法
在对解析器的介绍中看到,语法可以用类似BNF的格式规范地定义。不幸的是所有常规解析器的讨论都不适用于HTML(我提及它们并不是为了娱乐,它们可以用于解析CSS和JavaScript)。HTML无法用解析器所需的上下文无关的语法来定义。
3)HTML DTD
HTML的定义使用DTD文件。这种格式用来定义SGML族语言,它包含对所有允许的元素的定义,包括它们的属性和层级关系。如我们前面所说,HTML DTD构不成上下文无关语法。 具体规则由W3C定义。
4)DOM
解析器输出的树是由DOM元素和属性节点组成的。DOM的全称为:Document Object Model。它是HTML文档的对象化描述,也是HTML元素与外界(如Javascript)的接口。
DOM与标签有着几乎一一对应的关系,如下:
<html> <body> <p>hello world</p> <div><img src="aa.png"/></div> </body> </html>
其对应的DOM树如下3.2.4图:
与HTML一样,DOM规范也由w3c组织制订。参考:http://www.w3.org/DOM/DOMTR.
5)解析算法
无法使用常规的解析技术,浏览器创建自定义的解析器解析HTML。解析流程如下:
HTML解析算法流程HTML解析算法分为两个阶段:标记化算法 + 树构造算法。
6)标记化算法
该算法的输出是一个HTML标记。该算法被表示为一个状态机。每个状态会消耗一个或多个字符输入流,根据这些字符更新下一个状态。这个决定会被当前的标记化状态和树建设状态所影响。这意味着在下一个正确的状态下消耗相同的字符会产生不同的结果。
让我们来看看一个简单的例子,帮助我们进一步的理解。
基本的例子 - 标记化下面的HTML:
`<HTML>
<BODY>
世界,你好
</BODY>
</HTML>`
初始状态是”Data state”,当遇到”<”时状态改为“Tag open state”。吃掉”a-z”字符组成的符号后产生了”Start tag token”,状态变更为“Tag name state”。我们一直保持此状态,直到遇到”>”。每个字符都被追加到新的符号名上。在我们的例子中,解出的符号就是”html”。
当碰到”>”时,当前符号完成,状态改回“Data state”。”<body>”标签将会以同样的方式处理。现在”html”与”body”标签都完成了,我们回到“Data state”状态。吃掉”H”(”Hello world”第一个字母)时会产生一个字符符号,直到碰到”</body>”的”<”符号,我们就完成了一个字符符号”Hello world”。
现在我们回到“Tag open state”状态。吃掉下一个输入”/”时会产生一个”end tag token”并变更为“Tag name state”状态。同样,此状态保持到我们碰到”>”时。这时新标签符号完成,我们又回到“Data state”。同样”</html>”也会被这样处理。具体流程如下图:3.2.6(输入源的分词处理):
7)树构造算法
当解析器被创建时,文档对象也被创建了。在树的构建过程中DOM树的根节点(Documen)将被修改,元素被添加到上面去。每个分词器完成的节点都会被树构建器处理。规范中定义了每一个符号与哪个DOM对象相关。除了把元素添加到DOM树外,它还会被添加到一个开放元素堆栈。这个堆栈用于纠正嵌套错误和标签未关闭错误。这个算法也用状态机描述,它的状态叫做”insertion modes”。
让我们看看下面输入的树构建过程:
<html> <body> Hello world </body> </html>
树的构建过程中,输入就是分词过程中得到的符号序列。第一个模式叫“initial mode”。接收 html 符号后会变成“before html”模式并重新处理此模式中的符号。这会创建一个HTMLHtmlElement元素并追加到根文档节点。
然后状态改变为“before head”。我们收到”body”时,会隐式创建一个HTMLHeadElement,尽管我们没有这个标签,它也会被创建并添加到树中。
现在我们进入“in head”模式,然后是“after head”,Body会被重新处理,创建HTMLBodyElement元素并插入,然后进入“in body”模式。字符符号”Hello world”收到后会创建一个”Text”节点,所有字符都被一一追加到上面。收到body结束标签后进入 “after body” 模式,收到html结束标签后进入“after after body”模式。所有符号处理完后将终止解析。
如下图(HTML树的创建)
8)当解析完成后的动作
在这一阶段浏览器会把文档标记为交互模式,并开始解析deferred模式的script。”deferred”意味着脚本应该在文档解析完成后执行。脚本处理完成后将进入”complete”状态,”load”事件发生。
HTML5规范中包含了完整的算法: http://www.w3.org/TR/html5/syntax.html#html-parser
3.CSS解析
记得在介绍中的解析概念吗?CSS不像HTML,它是一个与上下文无关语法和能被在介绍中的解析器类型解析,其实CSS规范定义CSS的词法和句法语法。
具体的 词法语法 与 语法规则请点击:这里。
webkit CSS解析器:
webkit 使用 flex 和 bison 解析器发生器从CSS 语法文件去自动创建解析器,在解析器介绍中,bison创建一个自下而上的解析器。Firefox使用自上而下的手工编写解析器。在这两种情况下,每一个CSS文件被解析到一个样式表对象,每个对象都包含CSS规则。CSS规则对象包含选择器和声明对象和其他对象相对应的CSS语法。如图所示:
5.处理脚本和样式表的顺序
脚本:
web模式是同步模式,作者们期望当解析器解析到一个<script>标签时,脚本能被解析和立即执行。脚本被执行,文档的解析暂停,如果<script>脚本是由外部引入的,必须先从网络上获取——这也是同步的,解析暂停,直到资源被获取。这是多年来使用的模式,也被写入到HTML4和HTML5规范中。
作者可以给<script>标签添加一个defer=”defer“属性,这样不会暂停文档解析,文档解析完成后,再执行脚本。
**HTML5,增加给<script>增加了一个属性async,可以使文档的解析和脚本的执行在不同的线程。 **
投机性解析:
WebKit和火狐都这样做优化。执行脚本时,另一个线程解析文档的其余部分,并找出需要从网络加载的其他资源,并加载它们。在这些方式的资源可以被并行链接加载的整体速度是更好的。注意 - 投机解析器不修改DOM树,节点,主分析器,它仅仅解析外部脚本,样式表和图片等外部资源的引用。
样式表:
样式表在另一方面有不同的模式,从概念上讲,它似乎是因为样式表没有改变的DOM树,没有任何理由等待和停止解析文档。有一个问题,当文档解析时脚本访问样式信息,如果样式么有加载和解析,脚本将会得到错误的回答以及会引起一系列问题。这看起来像一种边缘情况,但是相当普遍,
火狐中有一个样式表一直在加载和解析时,会阻止所有脚本。Webkit的块的脚本,只有当他们试图访问某些可能卸载样式表的样式属性影响时,才会阻止所有脚本。
网友评论