用来输入URI的地址栏
· 前进、后退按钮
· 书签选项
· 用于刷新及暂停当前加载文档的刷新、暂停按钮
· 用于到达主页的主页按钮
浏览器的主要组件包括:
1. 用户界面 -- 包括地址栏、后腿/前进按钮、书签目录等,也就是你所看到的除了用来显示你锁清秋页面的主窗口之外的其他部分
2. 浏览器引擎- 用来查询及操作渲染引擎的接口
3. 渲染引擎 - 用来显示请求的年日用 (例如如果请求内容为html 它负责解析html 及css,并将解析后的结果显示出来)
4.网络 - 用来完成网络调用,例如http请求,他具有平台无关的接口,可以在不同平台上工作
5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
6.JS解释器- 用来解释执行js 代码
7. 数据存储- 属于持久层,浏览器需要在硬盘中保存类型cookie 的各种数据,html5定义了web database 技术,这是一种轻量级完整的客户端存储技术。
一、渲染引擎
需要注意的是,不同于大部分浏览器,chrom 为每个tab 分配了各自的渲染引擎实例,每个tab 就是独立的进程。
渲染引擎的职责是渲染,即在浏览器窗口中显示所请求的内容。
默认情况下,渲染引擎可以显示html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类数据,例如使用PDF阅读器插件,可以显示PDF格式,这里只讨论渲染引擎最主要的用途--- 显示应用了css 之后的html 及图片
渲染引擎首先通过网络获得所请求文档的内容,通常以8K 分块的方式完成。
下面是渲染引擎在取得内容之后的基本流程
解析html 以构建dom 树 -> 构建render 树 -> 布局render树 -> 绘制render 树
渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部css 文件及style 标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一个棵树 ---- render 树。
render 树由一些包含有颜色和大小等属性的矩形组成,他们被将按照正确的顺序显示到屏幕上
render 树构建好了之后,将会执行布局过程,它将确定要每个节点在屏幕上的确切坐标。在下一步就是绘制,即遍历render 树,并使用UI 后端层绘制每个节点。
值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后在去构建和布局render 树。 它是解析完一部分内容就显示一部分内容,同事可能还在通过网络下载其余内容。
二、解析
解析一个文档也就是将其转换为具有一定意义的结构(编码可以理解和使用的东西) 。解析的结构通常是通过表达文档结构的节点树,称为解析树或语法树。
HTML解析器的工作是将html 标识解析为解析树。
解析树,是有DOM 元素及属性节点组成的。DOM是文档对象模型的缩写,它是html 文档对象标识,作为html 元素的外部接口供js 等调用。
DOM和标签基本是一一对应的关系,例如,如下的标签:
<html>
<body>
<p>
Hello DOM
</p>
<div><img src=”example.png” /></div>
</body>
</html>
将会被转换为DOM树:
这里所谓的树包含了DOM节点是说树是由实现了DOM 接口的元素构建而成的,浏览器使用已被浏览器内容使用的其他属性的具体实现。解析算法
正如前面章节中讨论的,html 不能被一般的自顶向下或者向上的解析器所解析
原因是:
1. 这门语言本身的宽松特性
2.浏览器对一些常见的非法的html 有容错机制
3. 解析过程往往是反复的,通常源码不会在解析过程中发生改变,但在html 中,脚本标签包含的"document.write" 可能添加标签,这说明在解析过程中实际上修改了输入
不能使用正确解析技术,浏览器为html 定制了专属的解析器。
html解析流程
基本示例——符号化下面的html:
<html>
<body>
Hello world
</body>
</html>
初始状态为“Data State”,当遇到“<”字符,状态变为“Tag open state”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。
当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“<body>”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。
这样直到遇到“</body>”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“</html>”将和“</body>”一样处理。
树的创建过程
来看一下示例中树的创建过程:
<html>
<body>
Hello world
</body>
</html>
构建树这一阶段的输入是符号识别阶段生成的符号序列。
首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。
状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。
现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。
然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。
接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。
html树的构建过程
解析结束时的处理 Action when the parsing is finished
在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。
文档状态将被设置为完成,同时触发一个load事件。
浏览器容错 Browsers error tolerance
详见 http://www.aiuxian.com/article/p-1832762.html
三 、脚本解析 Parsing scripts
web的模式是同步的,开发者希望解析到一个script 标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完,如果脚本是外因的,则网络必须先请求到这个资源 --- 这个过程也是同步的,会阻塞文档的解析直到资源被请求到。
渲染树的构造 Render tree construction
RenderObject是Webkit的渲染对象基类,它的定义如下:
class RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node; //the DOM node
RenderStyle* style; // the computed style
RenderLayer* containgLayer; //the containing z-index layer
}
每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,正如css2所描述的那样,它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响(参考样式计算章节)。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。
webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();
...
RenderObject* o = 0;
switch (style->display()) {
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
...
}
return o;
}
元素的类型也需要考虑,例如,表单控件和表格带有特殊的框架。
染树和Dom树的关系 The render tree relation to the DOM tree
Dirty bit 系统
layout过程
layout一般有下面这几个部分:
1. 父渲染对象决定它自己的宽度
2. 父渲染对象读取chilidren,并:
Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。1. 放置child渲染对象(设置它的x和y)
2. 在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度
3. parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用
4. 将dirty标识设置为false
Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。
宽度计算
渲染对象的宽度使用容器的宽度、渲染对象样式中的宽度及margin、border进行计算。例如,下面这个div的宽度:
<div style="30%"/>
webkit中宽度的计算过程是(RenderBox类的calcWidth方法):
· 容器的宽度是容器的可用宽度和0中的最大值,这里的可用宽度为:contentWidth= clientWidth() - paddingLeft() - paddingRight(),clientWidth 和 clientHeight 代表一个对象内部的不包括border和滑动条的大小
· 元素的宽度指样式属性width的值,它可以通过计算容器的百分比得到一个绝对值
· 加上水平方向上的border和padding
到这里是最佳宽度的计算过程,现在计算宽度的最大值和最小值,如果最佳宽度大于最大宽度则使用最大宽度,如果小于最小宽度则使用最小宽度。最后缓存这个值,当需要layout 但宽度未改变时使用。
Line breaking
当一个渲染对象在布局过程中需要折行时,则暂停并告诉它的parent它需要折行,parent将创建额外的渲染对象并调用它们的layout。
绘制 Painting
绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件,这在UI的章节有更多的介绍。
全局和增量
和布局一样,绘制也可以是全局的-绘制完整的树-或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘这个对象(往往还包括它的children)。
css2定义了绘制过程的顺序。这个就是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。
Webkit矩形存储
重绘前,webkit将旧的矩形保存为位图,然后只绘制新旧矩形的差集。
浏览器总是试着以最小的动作响应一个变化,所以一个元素颜色的变化将只导致该元素的重绘,元素位置的变化将大致元素的布局和重绘,添加一个Dom节点,也会大致这个元素的布局和重绘。一些主要的变化,比如增加html元素的字号,将会导致缓存失效,从而引起整数的布局和重绘。
事件循环
浏览器主线程是一个时间循环,它被设计为无限循环比保持执行过程的可用,等待事件(例如layout 和paint事件)并执行它们。下面是Firefox的主要事件循环代码。while (!mExiting)
NS_ProcessNextEvent(thread);
CSS盒模型
所有的元素都有一个display属性,用来决定它们生成box的类型,例如:
block-生成块状box
inline-生成一个或多个行内box
none-不生成box
默认的是inline,但浏览器样式表设置了其他默认值,例如,div元素默认为block。
定位策略 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:
<p>
<img style="float:right" src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer...
</p>
将显示为:
Absolute和Fixed
这种情况下的布局完全不顾普通的文档流,元素不属于文档流的一部分,大小取决于容器。Fixed时,容器为viewport(可视区域)。
图17:fixed
注意-fixed即使在文档流滚动时也不会移动。
Layered representation
这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上下文),每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈,例如:
<STYLE type="text/css">
div {position: absolute;left: 2in;top: 2in;}
</STYLE>
<P>
<DIV style="z-index: 3;background-color:red; 1in; height: 1in; "></DIV>
<DIV style="z-index: 1;background-color:green; 2in; height: 2in;"> </DIV>
</p>
结果是:
虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,所以在根box的堆栈中更靠前。