浏览器是如何工做的

浏览器能够被认为是使用最普遍的软件,本文将介绍浏览器的工做原理,咱们将看到从你在地址栏输入google.cn 到你看到google 主页过程当中都发生了什么。接下来将基于一些开源浏览器的例子---firfox 、chrom及safari。

浏览器的主要功能
浏览器主要功能是将用户选择得web 资源呈现出来,它须要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式一般是HTML,也包括PDF、Image 及其余格式。用户用URI(统一资源标识符) 来指定所请求资源的位置。
html 和css 规范中规定了浏览器解释 html 文档的方式,由W3C 组织对这些规范进行维护,W3C 是负责指定web 标准的组织。

浏览器常见的用户界面元素包括:

用来输入URI的地址栏css

· 前进、后退按钮html

· 书签选项html5

· 用于刷新及暂停当前加载文档的刷新、暂停按钮node

· 用于到达主页的主页按钮web


浏览器的主要组件包括:算法

1. 用户界面 -- 包括地址栏、后腿/前进按钮、书签目录等,也就是你所看到的除了用来显示你锁清秋页面的主窗口以外的其余部分chrome

2. 浏览器引擎- 用来查询及操做渲染引擎的接口后端

3. 渲染引擎 - 用来显示请求的年日用 (例如若是请求内容为html 它负责解析html 及css,并将解析后的结果显示出来)浏览器

4.网络 - 用来完成网络调用,例如http请求,他具备平台无关的接口,能够在不一样平台上工做缓存

5. UI后端 - 用来绘制相似组合选择框及对话框等基本组件,具备不特定于某个平台的通用接口,底层使用操做系统的用户接口

6.JS解释器-  用来解释执行js 代码

7. 数据存储- 属于持久层,浏览器须要在硬盘中保存类型cookie 的各类数据,html5定义了web database 技术,这是一种轻量级完整的客户端存储技术。


1、渲染引擎

须要注意的是,不一样于大部分浏览器,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 树。 它是解析完一部份内容就显示一部份内容,同事可能还在经过网络下载其他内容。


2、解析

解析一个文档也就是将其转换为具备必定意义的结构(编码能够理解和使用的东西) 。解析的结构一般是经过表达文档结构的节点树,称为解析树或语法树。

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 标签时当即解析执行脚本,并阻塞文档的解析直到脚本执行完,若是脚本是外因的,则网络必须先请求到这个资源 --- 这个过程也是同步的,会阻塞文档的解析直到资源被请求到。


预解析
webkit 和firefox 都作了这个优化,当执行脚本时,另外一个线程解析剩下的文档,并加载后面须要经过网络加载的资源,这种方式可使 资源并行加载从而使总体速度更快。须要注意的是, 预解析并不改变dom树,它将这个工做留给主解析过程,本身只解析外面资源的引用,好比外部脚本、样式表及图片

样式表
样式表采用另外一种不一样的模式。理论上,既然样式表不能改变dom 树,也就没有必要停下文档的解析等待它们,而后存在一个问题,脚本可能在文档的解析过程当中请求样式信息,若是样式尚未加载和解析,脚本将获得错误的值,显然这将会致使很度问题,这看起来是个边缘状况,但确实很常见。
firefox 在存在样式表还在加载和解析时阻塞全部的脚本,而chrom 只在当脚本视图访问某些可能被未被加载的样式表所影响的特定的样式属性时才阻塞这些脚本。

渲染树的构造 Render tree construction

当Dom树构建完成时,浏览器开始构建另外一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。
Firefox将渲染树中的元素称为frames
webkit则用renderer或渲染对象来描述这些元素。
一个渲染对象知道怎么布局及绘制本身及它的children。

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

渲染对象和dom 元素相对应,但这种对应关系不是一对一的,不可见dom 元素不会被插入渲染树,例如head 元素。另外display 属性为none 的元素也不会在渲染树中出现(visibility 属性为hidden 的元素将出如今渲染树中。)

四: 布局
当渲染对象被建立并添加到树中,它们比你更没有位置和大小,计算这些值的过程称为layout 和reflow
html 是基于流的布局模型, 意味着发部分时间,能够以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性。因此布局能够在文档中从右到左、自上而下进行。也存在一些例外,好比html table
坐标系统相对于根frame ,使用top 和left 坐标
布局是一个递归的过程,由根渲染对象开始,它对应html 文档元素,布局继续递归的过程一些或全部的frame 层级,为我每一个须要几何信息的渲染对象进行计算。
根渲染对象的位置是0,1 ,它的大小是viewport -- 浏览器窗口的可见部分。
全部的渲染对象都有一个layout 和reflow 方法,每一个渲染对象调用须要布局的children 的layout 方法

Dirty bit 系统

为了避免由于每一个小变化都所有从新布局,浏览器使用一个dirty bit 系统,一个渲染对象发生了变化或是被添加了,就标记它及他的children 为dirty - 须要layout , 存在两个标识-dirty 及 children are dirty,
children are dirty说明即便这个渲染对象可能没问题,但它至少有一个child须要layout。

 全局和增量layout 
当layout 在整颗渲染树触发时,称为全局layout , 这可能在下面这些状况下发生
1. 一个全局的样式改变影响全部的渲染对象,好比字号的改变
2. 窗口resize 
layout 也能够是增量的,这样只有标志为dirty 的渲染对象会从新布局(也将致使一些额外的布局)。增量layout会在渲染对象dirty 时异步触发,例如,当网络接收到新的内容并添加到dom树后,新的渲染对象会添加到渲染树中。


增量layout
增量layout 的过程是异步的,firefox为整理layout 生成了reflow 队列,以及一个调度执行这些处理命令。webkit 也有一个计时器用来执行增量layout 遍历树,为dirty状态的渲染对象从新布局
另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。
全局的layout 通常都是同步触发
有些时候,layout 会被做为一个初始layout 以后的回调,好比滑动条的滑动

优化
当一个layout由于resize 或是渲染位置改版(并非大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会从新计算。
通常状况下,若是只有子树发生改变,则layout 并不从跟开始,这种状况发生在,变化发生在元素自身但并不影响其余周围元素,例如,将文本插入文本域(不然每次都将触发从根开始的重排)

layout过程

layout通常有下面这几个部分:

1. 父渲染对象决定它本身的宽度

2. 父渲染对象读取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的宽度:

<div style="width: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定义了绘制过程的顺序。这个就是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。

一个块渲染对象的堆栈顺序是:
1. 背景色
2. 背景图
3. border
4. children
5. outline

Firefox显示列表
Firefox 读取渲染树并为绘制的矩形建立一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。 用这样的方法,可使重绘时只需查找一次树,而不须要屡次查找——绘制全部的背景、全部的图片、全部的border等等。 Firefox优化了这个过程,它不添加会被隐藏的元素,好比元素彻底在其余不透明元素下面

Webkit矩形存储

重绘前,webkit将旧的矩形保存为位图,而后只绘制新旧矩形的差集。


动态变化

浏览器老是试着以最小的动做响应一个变化,因此一个元素颜色的变化将只致使该元素的重绘,元素位置的变化将大体元素的布局和重绘,添加一个Dom节点,也会大体这个元素的布局和重绘。一些主要的变化,好比增长html元素的字号,将会致使缓存失效,从而引发整数的布局和重绘。


渲染引擎的线程
渲染引擎是单线程的,除了网络操做以外,几户全部的事情都在单一的线程中处理,在firefox 和safari 中,这是浏览器的主线程,chrome 中这是tab的主线程
网络操做由几个并行线程执行,并行链接的个数是受限的(一般是2-6个)。

事件循环

浏览器主线程是一个时间循环,它被设计为无限循环比保持执行过程的可用,等待事件(例如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; width: 1in; height: 1in; "></DIV>
    <DIV style="z-index: 1;background-color:green;width: 2in; height: 2in;"> </DIV>
</p>

结果是:

虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,因此在根box的堆栈中更靠前。











相关文章
相关标签/搜索