转:现代浏览器的工作原理
?
这篇文章很好,忍不住转载在此。
本文来自:http://blog.jobbole.com/12749/
?
简介
浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工 作原理,我们将看到,从你在地址栏输入google.com到你看到google主页过程中都发生了什么。
将讨论的浏览器
今天,有五种主流浏览器——IE、Firefox、Safari、Chrome及Opera。
本文将基于一些开源浏览器的例子——Firefox、 Chrome及Safari,Safari是部分开源的。
根据W3C(World Wide Web Consortium 万维网联盟)的浏览器统计数据,当前(2011年9月),Firefox、Safari及Chrome的市场占有率综合已快接近50%。(原文为2009年10月,数据没有太大变化)因此,可以说开源浏览器将近占据了浏览器市场的半壁江山。
浏览器的主要功能
浏览器的主要功能是将用户选择得web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform Resource Identifier 统一资源标识符)来指定所请求资源的位置,在网络一章有更多讨论。
HTML和CSS规范中规定了浏览器解释html文档的方式,由 W3C组织对这些规范进行维护,W3C是负责制定web标准的组织。
HTML规范的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5还在制定中(译注:两年前),最新的CSS规范版本是2(http://www.w3.org/TR/CSS2),CSS3也还正在制定中(译注:同样两年前)。
这些年来,浏览器厂商纷纷开发自己的扩展,对规范的遵循并不完善,这为web开发者带来了严重的兼容性问题。
但是,浏览器的用户界面则差不多,常见的用户界面元素包括:
▲用来输入URI的地址栏
▲前进、后退按钮
▲书签选项
▲用于刷新及暂停当前加载文档的刷新、暂停按钮
▲用于到达主页的主页按钮
奇怪的是,并没有哪个正式公布的规范对用户界面做出规定,这些是多年来各浏览器厂商之间相互模仿和不断改进得结果。
HTML5并没有规定浏览器必须具有的UI元素,但列出了一些常用元素,包括地址栏、状态栏及工具栏。还有一些浏览器有自己专有得功能,比如Firefox得下载管理。更多相关内容将在后面讨论用户界面时介绍。
浏览器的主要构成High Level Structure
浏览器的主要组件包括:
▲用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
▲浏览器引擎- 用来查询及操作渲染引擎的接口
▲渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
▲网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
▲JS解释器- 用来解释执行JS代码
▲数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术
图1:浏览器主要组件
需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。
对于构成浏览器的这些组件,后面会逐一详细讨论。
组件间的通信 Communication between the components
Firefox和Chrome都开发了一个特殊的通信结构,后面将有专门的一章进行讨论。
渲染引擎 The rendering engine
渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。
默认情况下,渲染引擎可以显示html、xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读器插件,可以显示PDF格式,将由专门一章讲解插件及扩展,这里只讨论渲染引擎最主要的用途——显示应用了CSS之后的html及图片。
渲染引擎 Rendering engines
本文所讨论得浏览器——Firefox、Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都使用webkit。
Webkit是一款开源渲染引擎,它本来是为linux平台研发的,后来由Apple移植到Mac及Windows上,相关内容请参考http://webkit.org。
主流程 The main flow
渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。
下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树->构建render树->布局render树->绘制render树
图2:渲染引擎基本流程
渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。
Render树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。
Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。
值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
?
图3:webkit主流程
?
图4:Mozilla的Geoko 渲染引擎主流程
从图3和4中可以看出,尽管webkit和Gecko使用的术语稍有不同,他们的主要流程基本相同。Gecko称可见的格式化元素组成的树为frame树,每个元素都是一个frame,webkit则使用render树这个名词来命名由渲染对象组成的树。Webkit中元素的定位称为布局,而Gecko中称为回流。Webkit称利用dom节点及样式信息去构建render树的过程为attachment,Gecko在html和dom树之间附加了一层,这层称为内容接收器,相当制造dom元素的工厂。下面将讨论流程中的各个阶段。
解析 Parsing-general
既然解析是渲染引擎中一个非常重要的过程,我们将稍微深入的研究它。首先简要介绍一下解析。
解析一个文档即将其转换为具有一定意义的结构——编码可以理解和使用的东西。解析的结果通常是表达文档结构的节点树,称为解析树或语法树。
例如,解析“2+3-1”这个表达式,可能返回这样一棵树。
图5:数学表达式树节点
文法 Grammars
解析基于文档依据的语法规则——文档的语言或格式。每种可被解析的格式必须具有由词汇及语法规则组成的特定的文法,称为上下文无关文法。人类语言不具有这一特性,因此不能被一般的解析技术所解析。
解析器-词法分析器 Parser-Lexer combination
解析可以分为两个子过程——语法分析及词法分析
词法分析就是将输入分解为符号,符号是语言的词汇表——基本有效单元的集合。对于人类语言来说,它相当于我们字典中出现的所有单词。
语法分析指对语言应用语法规则。
解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符。
图6:从源文档到解析树
解析过程是迭代的,解析器从词法分析器处取道一个新的符号,并试着用这个符号匹配一条语法规则, 如果匹配了一条规则,这个符号对应的节点将被添加到解析树上,然后解析器请求另一个符号。如果没有匹配到规则,解析器将在内部保存该符号,并从词法分析器 取下一个符号,直到所有内部保存的符号能够匹配一项语法规则。如果最终没有找到匹配的规则,解析器将抛出一个异常,这意味着文档无效或是包含语法错误。
转换 Translation
很多时候,解析树并不是最终结果。解析一般在转换中使用——将输入文档转换为另一种格式。编译就是个例子,编译器在将一段源码编译为机器码的时候,先将源码解析为解析树,然后将该树转换为一个机器码文档。
图7:编译流程
解析实例 Parsing example
图5中,我们从一个数学表达式构建了一个解析树,这里定义一个简单的数学语言来看下解析过程。
词汇表:我们的语言包括整数、加号及减号。
语法:
1. 该语言的语法基本单元包括表达式、term及操作符
2. 该语言可以包括多个表达式
3. 一个表达式定义为两个term通过一个操作符连接
4. 操作符可以是加号或减号
5. term可以是一个整数或一个表达式
现在来分析一下“2+3-1”这个输入
第一个匹配规则的子字符串是“2”,根据规则5,它是一个term,第二个匹配的是“2+3”,它符合第2条规则——一个操作符连接两个term,下一次匹配发生在输入的结束处。“2+3-1”是一个表达式,因为我们已经知道“2+3”是一个term,所以我们有了一个term紧跟着一个操作符及另一个term。“2++”将不会匹配任何规则,因此是一个无效输入。
词汇表及语法的定义
词汇表通常利用正则表达式来定义。
例如上面的语言可以定义为:
?
图8:示例标签对应的DOM树
和html一样,DOM的规范也是由W3C组织制定的。访问http://www.w3.org/DOM/DOMTR,这是使用文档的一般规范。一个模型描述一种特定的html元素,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm?查看html定义。
这里所谓的树包含了DOM节点是说树是由实现了DOM接口的元素构建而成的,浏览器使用已被浏览器内部使用的其他属性的具体实现。
解析算法 The parsing algorithm
正如前面章节中讨论的,hmtl不能被一般的自顶向下或自底向上的解析器所解析。
原因是:
1. 这门语言本身的宽容特性
2. 浏览器对一些常见的非法html有容错机制
3. 解析过程是往复的,通常源码不会在解析过程中发生改变,但在html中,脚本标签包含的“document.write ”可能添加标签,这说明在解析过程中实际上修改了输入
不能使用正则解析技术,浏览器为html定制了专属的解析器。
Html5规范中描述了这个解析算法,算法包括两个阶段——符号化及构建树。
符号化是词法分析的过程,将输入解析为符号,html的符号包括开始标签、结束标签、属性名及属性值。
符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入。
图9:HTML解析流程
符号识别算法 The tokenization algorithm
算法输出html符号,该算法用状态机表示。每次读取输入流中的一个或多个字符,并根据这些字符转移到下一个状态,当前的符号状态及构建树状态共同影响结果,这意味着,读取同样的字符,可能因为当前状态的不同,得到不同的结果以进入下一个正确的状态。
这个算法很复杂,这里用一个简单的例子来解释这个原理。
基本示例——符号化下面的html:
?
图10:符号化示例输入
树的构建算法 Tree construction algorithm
在树的构建阶段,将修改以Document为根的DOM树,将元素附加到树上。每个由符号识别器识别生成的节点将会被树构造器进行处理,规范中定义了每个符号相对应的Dom元素,对应的Dom元素将会被创建。这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签,这个算法也是用状态机来描述,所有的状态采用插入模式。
来看一下示例中树的创建过程:
?
图11:示例html树的构建过程
解析结束时的处理 Action when the parsing is finished
在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。
文档状态将被设置为完成,同时触发一个load事件。
Html5规范中有符号化及构建树的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。
浏览器容错 Browsers error tolerance
你从来不会在一个html页面上看到“无效语法”这样的错误,浏览器修复了无效内容并继续工作。
以下面这段html为例:
?
图12:解析css
脚本解析 Parsing scripts
本章将介绍Javascript。
处理脚本及样式表的顺序 The order of processing scripts and style sheets
脚本
web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。
预解析 Speculative parsing
Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。
样式表 Style sheets
样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。
渲染树的构造 Render tree construction当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。
Firefox将渲染树中的元素称为frames,webkit则用renderer或渲染对象来描述这些元素。
一个渲染对象直到怎么布局及绘制自己及它的children。
RenderObject是Webkit的渲染对象基类,它的定义如下:
?
图12:渲染树及对应的Dom树
创建树的流程 The flow of constructing the tree
Firefox中,表述为一个监听Dom更新的监听器,将frame的创建委派给Frame Constructor,这个构建器计算样式(参看样式计算)并创建一个frame。
Webkit中,计算样式并生成渲染对象的过程称为attachment,每个Dom节点有一个attach方法,attachment的过程是同步的,调用新节点的attach方法将节点插入到Dom树中。
处理html和body标签将构建渲染树的根,这个根渲染对象对应被css规范称为containing block的元素——包含了其他所有块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,Firefox称它为viewPortFrame,webkit称为RenderView,这个就是文档所指向的渲染对象,树中其他的部分都将作为一个插入的Dom节点被创建。
样式计算 Style Computation
创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到。
样式包括各种来源的样式表,行内样式元素及html中的可视化属性(例如bgcolor),可视化属性转化为css样式属性。
样式表来源于浏览器默认样式表,及页面作者和用户提供的样式表——有些样式是浏览器用户提供的(浏览器允许用户定义喜欢的样式,例如,在Firefox中,可以通过在Firefox Profile目录下放置样式表实现)。
计算样式的一些困难:
1. 样式数据是非常大的结构,保存大量的样式属性会带来内存问题
2. 如果不进行优化,找到每个元素匹配的规则会导致性能问题,为每个元素查找匹配的规则都需要遍历整个规则表,这个过程有很大的工作量。选择符可能有复杂的结构,匹配过程如果沿着一条开始看似正确,后来却被证明是无用的路径,则必须去尝试另一条路径。
例如,下面这个复杂选择符
图14:Firefox样式上下文树
样式上下文包含最终值,这些值是通过以正确顺序应用所有匹配的规则,并将它们由逻辑值转换为具体的值,例如,如果逻辑值为屏幕的百分比,则通过计算将其转化为绝对单位。样式树的使用确实很巧妙,它使得在节点中共享的这些值不需要被多次计算,同时也节省了存储空间。
所有匹配的规则都存储在规则树中,一条路径中的底层节点拥有最高的优先级,这棵树包含了所找到的 所有规则匹配的路径(译注:可以取巧理解为每条路径对应一个节点,路径上包含了该节点所匹配的所有规则)。规则树并不是一开始就为所有节点进行计算,而是 在某个节点需要计算样式时,才进行相应的计算并将计算后的路径添加到树中。
我们将树上的路径看成辞典中的单词,假如已经计算出了如下的规则树:
假如需要为内容树中的另一个节点匹配规则,现在知道匹配的规则(以正确的顺序)为B-E-I,因为我们已经计算出了路径A-B-E-I-L,所以树上已经存在了这条路径,剩下的工作就很少了。
现在来看一下树如何保存。
结构化
样式上下文按结构划分,这些结构包括类似border或color这样的特定分类的样式信息。一个结构中的所有特性不是继承的就是非继承的,对继承的特性,除非元素自身有定义,否则就从它的parent继承。非继承的特性(称为reset特性)如果没有定义,则使用默认的值。
样式上下文树缓存完整的结构(包括计算后的值),这样,如果底层节点没有为一个结构提供定义,则使用上层节点缓存的结构。
使用规则树计算样式上下文
当为一个特定的元素计算样式时,首先计算出规则树中的一条路径,或是使用已经存在的一条,然后使 用路径中的规则去填充新的样式上下文,从样式的底层节点开始,它具有最高优先级(通常是最特定的选择器),遍历规则树,直到填满结构。如果在那个规则节点 没有定义所需的结构规则,则沿着路径向上,直到找到该结构规则。
如果最终没有找到该结构的任何规则定义,那么如果这个结构是继承型的,则找到其在内容树中的parent的结构,这种情况下,我们也成功的共享了结构;如果这个结构是reset型的,则使用默认的值。
如果特定的节点添加了值,那么需要做一些额外的计算以将其转换为实际值,然后在树上的节点缓存该值,使它的children可以使用。
当一个元素和它的一个兄弟元素指向同一个树节点时,完整的样式上下文可以被它们共享。
来看一个例子:假设有下面这段html
?
上下文树如下(节点名:指向的规则节点)
假设我们解析html,遇到第二个div标签,我们需要为这个节点创建样式上下文,并填充它的样式结构。
我们进行规则匹配,找到这个div匹配的规则为1、2、6,我们发现规则树上已经存在了一条我们可以使用的路径1、2,我们只需为规则6新增一个节点添加到下面(就是规则树中的F)。
然后创建一个样式上下文并将其放到上下文树中,新的样式上下文将指向规则树中的节点F。
现在我们需要填充这个样式上下文,先从填充margin结构开始,既然最后一个规则节点没有添加margin结构,沿着路径向上,直到找到缓存的前面插入节点计算出的结构,我们发现B是最近的指定margin值的节点。因为已经有了color结构的定义,所以不能使用缓存的结构,既然color只有一个属性,也就不需要沿着路径向上填充其他属性。计算出最终值(将字符串转换为RGB等),并缓存计算后的结构。
第二个span元素更简单,进行规则匹配后发现它指向规则G,和前一个span一样,既然有兄弟节点指向同一个节点,就可以共享完整的样式上下文,只需指向前一个span的上下文。
因为结构中包含继承自parent的规则,上下文树做了缓存(color特性是继承来的,但Firefox将其视为reset并在规则树中缓存)。
例如,如果我们为一个paragraph的文字添加规则:
p {font-family:Verdana;font size:10px;font-weight:bold}
那么这个p在内容树中的子节点div,会共享和它parent一样的font结构,这种情况发生在没有为这个div指定font规则时。
Webkit中,并没有规则树,匹配的声明会被遍历四次,先是应用非important的高优先级属性(之所以先应用这些属性,是因为其他的依赖于它们-比如display),其次是高优先级important的,接着是一般优先级非important的,最后是一般优先级important的规则。这样,出现多次的属性将被按照正确的级联顺序进行处理,最后一个生效。
总结一下,共享样式对象(结构中完整或部分内容)解决了问题1和3,Firefox的规则树帮助以正确的顺序应用规则。
对规则进行处理以简化匹配过程
样式规则有几个来源:
· 外部样式表或style标签内的css规则
· 行内样式属性
· html可视化属性(映射为相应的样式规则)
后面两个很容易匹配到元素,因为它们所拥有的样式属性和html属性可以将元素作为key进行映射。
就像前面问题2所提到的,css的规则匹配可能很狡猾,为了解决这个问题,可以先对规则进行处理,以使其更容易被访问。
解析完样式表之后,规则会根据选择符添加一些hash映射,映射可以是根据id、class、标签名或是任何不属于这些分类的综合映射。如果选择符为id,规则将被添加到id映射,如果是class,则被添加到class映射,等等。
这个处理是匹配规则更容易,不需要查看每个声明,我们能从映射中找到一个元素的相关规则,这个优化使在进行规则匹配时减少了95+%的工作量。
来看下面的样式规则:
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
第一条规则将被插入class映射,第二条插入id映射,第三条是标签映射。
下面这个html片段:
<p class=”error”>an error occurred </p>
<div id=” messageDiv”>this is a message</div>
我们首先找到p元素对应的规则,class映射将包含一个“error”的key,找到p.error的规则,div在id映射和标签映射中都有相关的规则,剩下的工作就是找出这些由key对应的规则中哪些确实是正确匹配的。
例如,如果div的规则是
table div {margin:5px}
这也是标签映射产生的,因为key是最右边的选择符,但它并不匹配这里的div元素,因为这里的div没有table祖先。
Webkit和Firefox都会做这个处理。
以正确的级联顺序应用规则
样式对象拥有对应所有可见属性的属性,如果特性没有被任何匹配的规则所定义,那么一些特性可以从parent的样式对象中继承,另外一些使用默认值。
这个问题的产生是因为存在不止一处的定义,这里用级联顺序解决这个问题。
样式表的级联顺序
一个样式属性的声明可能在几个样式表中出现,或是在一个样式表中出现多次,因此,应用规则的顺序至关重要,这个顺序就是级联顺序。根据css2的规范,级联顺序为(从低到高):
1. 浏览器声明
2. 用户声明
3. 作者的一般声明
4. 作者的important声明
5. 用户important声明
浏览器声明是最不重要的,用户只有在声明被标记为important时才会覆盖作者的声明。具有同等级别的声明将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css声明,它们被视为最低优先级的作者规则。
Specifity
Css2规范中定义的选择符specifity如下:
· 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a)
· 计算选择器中id属性的数量(=b)
· 计算选择器中class及伪类的数量(=c)
· 计算选择器中元素名及伪元素的数量(=d)
连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity。这里使用的基数由分类中最高的基数定义。例如,如果a为14,可以使用16进制。不同情况下,a为17时,则需要使用阿拉伯数字17作为基数,这种情况可能在这个选择符时发生html body div div …(选择符中有17个标签,一般不太可能)。
一些例子:
?
图20:增量 layout
异步和同步layout
增量layout的过程是异步的,Firefox为增量layout生成了reflow队列,以及一个调度执行这些批处理命令。Webkit也有一个计时器用来执行增量layout-遍历树,为dirty状态的渲染对象重新布局。
另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。
全局的layout一般都是同步触发。
有些时候,layout会被作为一个初始layout之后的回调,比如滑动条的滑动。
优化
当一个layout因为resize或是渲染位置改变(并不是大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会重新计算。
一般情况下,如果只有子树发生改变,则layout并不从根开始。这种情况发生在,变化发生在元素自身并且不影响它周围元素,例如,将文本插入文本域(否则,每次击键都将触发从根开始的重排)。
layout过程
layout一般有下面这几个部分:
1. parent渲染对象决定它的宽度
2. parent渲染对象读取chilidren,并:
1. 放置child渲染对象(设置它的x和y)
2. 在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度
3. parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用
4. 将dirty标识设置为false
Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。
Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。
宽度计算
渲染对象的宽度使用容器的宽度、渲染对象样式中的宽度及margin、border进行计算。例如,下面这个div的宽度:
每个节点生成0-n个这样的box。
所有的元素都有一个display属性,用来决定它们生成box的类型,例如:
block-生成块状box
inline-生成一个或多个行内box
none-不生成box
默认的是inline,但浏览器样式表设置了其他默认值,例如,div元素默认为block。可以访问http://www.w3.org/TR/CSS2/sample.html查看更多的默认样式表示例。
定位策略 Position scheme
这里有三种策略:
1. normal-对象根据它在文档的中位置定位,这意味着它在渲染树和在Dom树中位置一致,并根据它的盒模型和大小进行布局
2. float-对象先像普通流一样布局,然后尽可能的向左或是向右移动
3. absolute-对象在渲染树中的位置和Dom树中位置无关
static和relative是normal,absolute和fixed属于absolute。
在static定位中,不定义位置而使用默认的位置。其他策略中,作者指定位置——top、bottom、left、right。
Box布局的方式由这几项决定:box的类型、box的大小、定位策略及扩展信息(比如图片大小和屏幕尺寸)。
Box类型
Block box:构成一个块,即在浏览器窗口上有自己的矩形

Inline box:并没有自己的块状区域,但包含在一个块状区域内

block一个挨着一个垂直格式化,inline则在水平方向上格式化。

Inline盒模型放置在行内或是line box中,每行至少和最高的box一样高,当box以baseline对齐时——即一个元素的底部和另一个box上除底部以外的某点对齐,行高可以比最高的box高。当容器宽度不够时,行内元素将被放到多行中,这在一个p元素中经常发生。

定位 Position
Relative
相对定位——先按照一般的定位,然后按所要求的差值移动。

Floats
一个浮动的box移动到一行的最左边或是最右边,其余的box围绕在它周围。下面这段html:
?
Absolute和Fixed
这种情况下的布局完全不顾普通的文档流,元素不属于文档流的一部分,大小取决于容器。Fixed时,容器为viewport(可视区域)。

图17:fixed
注意-fixed即使在文档流滚动时也不会移动。
Layered representation
这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上下文),每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈,例如:
?
虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,所以在根box的堆栈中更靠前。
国外也有网友根据浏览器的工作原理绘制了几张工作流程图,方便大家通过简易的图片来了解这个辛苦的过程:

原文:http://taligarsiel.com/Projects/howbrowserswork1.htm
编译:zzzaquarius