How browsers work -Behind the scenes of modern web browsers 浏览器到底是怎么工作的、浏览器的工作原理(完整中文翻译)

How browsers work -Behind the scenes of modern web browsers
有空翻译一下这篇必读的文章 —浏览器到底是怎么工作的、浏览器的底层原理是啥、浏览器的工作原理
网上有很多译文,谁知道他们怎么搞出来的,我觉得他们肯定连幼儿园都没毕业 ,我不看,我自己翻一遍
会结合资料去翻译,反正怎么翻的你自己脑补就完事了,关于缩写,只在第一次出现的时候标一下
一些东西知道的就补上别的详细的,不知道的咱也不能瞎扯

有些意译的会放上原文参考,有错的地方留言指正,及时更改, 感谢
如果想转请评论留个言并注明原博 @Sclifftop https://blog.csdn.net/S_clifftop/article/details/95994512

复制转载的记得常回来看看,因为我可能会增加东西便于进一步理解,包括有些地方会加些原文没有的概括,有些错的我可能会改正,请别误导别人,臭弟弟
(2020.3.12)之前太骚了,让人感觉有点乱,删了点骚话,改了一点描述,但意思相差不大


之前node译成了结点,但我发现节点更适合,改蒙逼了,早知道就不译node了
有漏改或替换错的你知道就行了:结点(节点)== Node
结点:本身不代表任何质点, 它们为只具几何意义的几何点
节点:没找到


目录

1、基本介绍

网页浏览器无疑是使用最广泛的软件。接下来我将解释一下它是怎么工作的,下面会讲:当你输入googld.com(就不改)后浏览器会怎么运行才能让你看到google的页面


1.1、我们将要谈的浏览器

目前大家使用最广泛的最主要有五款浏览器 —Internet Explorer(微软的浏览器)、Firefox(全称也可以说是Mozilla Firefox,火狐)、Safari(苹果浏览器,知道怎么读吗?别再“撒夫瑞”了,是“色” “发” “瑞”哟)、Chrome(谷歌浏览器)、Opera(欧朋)。
后面我将通过几款部分有开源的浏览器并结合一些例子来说明。
通过W3C(World Wide Web Consortium,为了规范某些东西而设立的组织,规范什么俺也不用多说)的数据统计,我们知道火狐,Safari和谷歌浏览器的市场占有率已经达到了近六成(旧的占有率,现在是多少自己去查),所以开源浏览器已是市场不可缺的一部分。


1.2、浏览器最主要的功能

浏览器最主要的功能说白了就是显示出你想要的资源,也就里向服务器请求然后显示在浏览器窗口中。请求的资源最常使用的格式是HTML(Hyper Text Markup Language 超文本标记语言),其他也包括PDF(Portable Document Format便携的文档格式),image(图片)等等。怎么获取它们呢?就是使用URL(Uniform resource Identifier统一资源标志符)。

浏览器解析出HTML文件的规则在HTML和CSS(Cascading Style Sheets,层叠样式表)中有规范说明。这些规范说明是W3C来统一的,最新的HTML版本是5,最新的CSS标准是CSS3。

之前的浏览器都想当老大,都想赚钱,部分规则统一,其他规则都乱七八糟,都有自己的标准。所以这个事很严重,对吧,但W3C不让你乱搞,所以他们相对之前老实了一点,大部分是一致的。

浏览器的界面大致都相同(反正都抄来抄去,也不能反人类,毕竟要恰饭),一般都有下面的几点:

  • 地址栏
  • 回退和前进(帅得不谈)
  • 书签栏(帅得不谈+1)
  • 刷新、暂停刷新、停止载入(帅得不谈+2)
  • 主页(帅得不谈+n)

重点来了,浏览器界面没有规范说要怎么怎么样,但设计出来都一致,“这是经过多年,浏览器相互模仿而形成的良好实践”(看见没有,抄袭用优美的话就这样说),HTML5没有规定浏览器界面必须有什么元素,所以你想加什么加什么(加个奶子)


1.3、浏览器的高级组成结构(注意,开始有内涵了)

浏览器主要的组成:

  • 用户界面(就是上面提到的那些乱七八糟的,就是打开浏览器看到的)
  • 浏览器引擎 — 用于查数据和呈现内容
  • 渲染引擎 — 结果是给人看的,解析并显示请求的内容呈现到你美丽的辗庞前(解析呈现就完事)
  • 网络 — 就是网络请求用的,很多协议,不懂就去看计算机网络原理
  • UI的后端(前面这个UI的后端我为了保命用工具人翻的,所以翻译好坏都不关我滴事) — 绘制基本的部件,你看一个页面是整体,其实是一个又一个小窗口拼起来的,小窗口内填满自己的内容,底层是一些操作用户界面的接口
  • js解释器(javascript不是java,如果你还不太清楚区别请合法上网搜) — 解析执行js代码
  • 数据存储(这个帅得不谈,就是效率有点低) — 持久层,就是把数据存到本地,也是个数据库,只不过存储方式和容量不同(就有限制呗就小呗,害能咋低)

看下图,不用再翻一遍了吧,还是再来一遍吧
浏览器
注意了,Chrome每一个选项卡都是一个单独的进程,就像你一边浏览黄色网站,一边防着父母就打开了一个学习的网页,你以为只是表面上的一个浏览器,但他有俩,打开任务管理器就知道了(自己查任务管理器怎么打开的)。

1.4、组件之间是怎么关联的呢(你的器官是怎么联系的呢)

Firefox和Chrome都有一个特殊的实现方法,下面会谈。


2、渲染引擎

渲染引擎,渲染嘛,就是把请求的内容显示在浏览器上。

一般渲染引擎可以显示HTML,XML(Extensible Markup Language,可扩展标记语言),和图片。如果你有安装各种其他插件的话,浏览器也可以显示其他的东西。如果你想显示PDF,那你可以安装PDF查看工具插件,其他什么看片插件啦,文明和谐插件啦自己去找。


2.1渲染引擎

我们说的这几个浏览器(Firefox、Chrome、Safari)底层其实用了不同的渲染引擎。

Firefox使用的是Gecko(网页排版引擎),“自制”的引擎,为什么加引号,因为这是它老大网景做的,它只是属于网景的一个组织,原文是"home made",你懂我意思吧。

Safari和Chrome使用的是Webkit,之前苹果寻思了半天,是抄Gecko(由网景开发)还是抄KHTML(由KDE开发),最后也不知道谁决定的就抄了后面这个然后加以改进,后面给的理由挺多:KHTML渲染速度快,结构清晰等等

但是后来KDE和苹果不和谐,具体发生了什么咱也不在现场,你问我我也不知道谁先动的手,(苹果开源Webkit之前是一直在修改KHTML的,但他改动完提交的代码是一次性提交,而且量很巨大,KDE整理起来就很烦,岔个话题,你像那种变量名同时用拼音和英文缩写,英文缩写还写错,又不加注释,最基本的都乱七八糟,那运行效率可想而知,最后还得重构,我头都能给他削肚子里),再后来苹果开源了Webkit(苹果终于开源了东西,之前只开源抄来的,这个是站在巨人肩膀上的抄)

KHTML最初用于Linux操作系统,后来也可以用在Mac和Windows上,想了解可以看下后面这个网站: Webkit


2.2主流程

渲染引擎先从网络层来获取所请求的内容,这些内容是8K以内,使用chunk传输编码,这个是一种数据传输的机制,不懂的去查一下,然后就是渲染出来,看下面这个图:

获取完内容先解析,解析后把CSS和DOM组合形成一个渲染树,然后步局计算每一个对象的准确的位置及大小,最后画就完事了(不是拿笔画)
渲就完事了
再给你解释一遍:

  • 渲染引擎先解析HTML,变为DOM节点,再解析CSS规则(还有三种引入方式,还记得吧,你不知道的话影响也不大,文章后面会说),然后组在一起就是渲染树
  • 渲染树包含了很多矩形,这个矩形有各种各样的属性,也就是颜色,大小之类的。这些矩形最后会在屏幕上按各自的位置显示出来的,像那些display是none的元素是不在里面的,但是注意了:visibility是hidden的会在里面
  • 然后就是布局的过程,意思就是计算出每一个节点的精确位置,有布局的元素会发生偏移
  • 接下来就是根据前一步使用UI后端来“画”出来

理解每一步挺重要的,浏览器为了更好的用户体验,渲染引擎会尽快的把它们都显示出来,它不是在所有内容接收后再布局计算显示,有一部分是在从获取数据的同时就开始渲染了


2.3主要使用的两个渲染引擎工作方式示意图

Webkit
Webkit
Gecko
Gecko

从上面两张图的对比,可以看出来虽然有些词不同,但方式基本相同的
下面是两者之间的对比:

  • 首先从“树”来说
    • Gecko把格式化的元素树称为框架树(Frame tree),其中每一个元素都是一个框架树
    • Webkit使用的是渲染树(Render tree)这个词,你也可以叫呈现树嘛,是由一个个的渲染对象组成
  • 然后是布局这方面
    • Gecko使用重排(Reflow)这个词
    • Webkit使用的是布局(Layout)
  • DOM树和样式的联系
    • Gecko在HTML和DOM树间多了一层内容池(Content Sink),你想翻成别的意思,什么内容下沉层乱七八糟的都没关系,只要知道他是创建DOM元素的一个工厂
    • Webkit在DOM树和样式间使用附加(Attachment)这个词

2.4解析

我先用一句话给你概括一下

怎么硕呢,所有的编译过程都是从解析源文件开始,词法分析就是分析源文件,转换成token(这个我不译了,你把他当专有名词,原词比译过来表达方式更好一些),所以词法分析器也称为tokenizer也是可以理解的

而语法分析呢,就把把词法分析后的token拿来,然后根据定义好的文法规则,进行归约(不懂这个词就往下看,我只是概括)

2.4.1 先从基本的解析来说

解析是渲染引擎非常重要的过程,下面我来慢慢说明

先不管原文,解析说白了就是翻译成机器可以理解的,这样它才能运行,原文写的是:解析表示的是把那一堆东西翻译成一种可以更好的结构,它可以被理解然后运行。解析出来的东西和原来的一样,只不过表现形式不同,可以叫那个东西为解析树,或者是语法树。

下面是举栗子— 如果你想解析“2+3-1”,看下图
解析树
看下应该就明白了,从左下方子节点开始,然后从左到右合并运算,如果你没学过算法设计(大致是这个名),可以去找一下,和二叉树差不多的规则。解析基于语法(Grammar)的规则,你可以解析的每一种格式都是由词汇表和语法规则组成。人类的语言和这种相差很大,所以不能用传统的解析方式来解析。

2.4.1.1 解析

解析可以分成两部分:词汇解析和语法解析

  • 词汇解析(Lexical)是一个把一句分解的过程。从人类的语言方面举例就是把一句话拆成每一个字或词
  • 语法解析(Syntax)是根据语法规则对分解的东西再进行分析

解析器把工作分为两个部分 :词汇分析器(Lexer,某些时候也称为Tokenizer)(也可以叫做分词器超级无敌把输入字符分开然后再超级无敌解析的器)主要负责把一句话拆分,分出来的东西还得有实际意义(像那些空格,换行什么的就去掉)、解析器(Parser)负责分析文档结构。看下图嘛:
解析

解析过程是一个重复的过程

  • 解析器先会从词汇分析器(Lexer)中取出一个,然后和解析规则相匹配
    • 如果成功配对,就~会加到解析树中
    • 如果没有成功配对,就先存起来,一直查询匹配,直到找到。如果找完都没找到,那就是报个异常(exception),表示这一块东西无效,里面有错,像语法方面什么的
  • 然后就是重复上面的过程,解完取,取完解,循环+10086,直到没得了
2.4.1.2 翻译(Translation)

通常情况下,解析树只是半成品,它还要进一步操作,再对其进行“翻译”,转成另一种格式,那就是机器码

解析过程

2.4.1.3 解析器的种类

解析器有两种:从上到下和从下到上

  • 自上而下的解析器会从高层结构开始,并匹配
  • 自下而上的解析器就是上面那种语法规则,从低级开始,直到高级

看例子吧,直接说也看不懂(还是“2 + 3 -1”):

  • 从上而下的解析器会先识别“2 + 3”做为一块(表达式),接下来会进一步判断“2 + 3 -1”做为一块,这个过程就是与其他规则相匹配,但起点是最高级的规则
  • 上自下而上的解析器将分析语句,直到规则匹配,然后用规则替换匹配的输入,这将持续到输入结束,部分匹配的表达式放在解析器堆栈上,也称作移位归约(Shift Reduce)解析器,因为输入是逐渐右移,逐渐合并减少
输入
2 + 3 - 1
+ 3 - 1
块 操作符 3 - 1
表达式 - 1
表达式 操作符 1
表达式 1
2.4.1.4 自动生成解析器

有些工具是可以生成你需要的解析器,也可以叫它们解析器的爹 解析器生成器,你可以定义自己的规则,他根据你的规则来生成一个解析器(根据你规则来解析其他东西)

Webkit使用两种解析器生成器 Flex(生成词汇解析器,需要输入包含你自定义的正则表达式的文件)、Bision(生成词法解析器,需要输入BNF <Backus-Naur Form巴科斯范式> 格式的语句规则)

2.4.2 HTML解析器

HTML解析器就是把HTML解析成解析树

2.4.2.1 HTML语法定义

这个是由W3C组织来规定的,最新的是HTML5,去找吧少年

2.4.2.2 解析HTML的语法

在上面解析的介绍中,可以知道,语法规则可以使用BNF格式来自定义,但是对HTML来说不适用,定义出解析HTML的解析规则不太容易,所以不能使用这种方法,那怎么办,就有了DTD(Document Type Definition文档格式定义)

HTML和XML(可扩展标记语言)非常接近,对于XML,有很多解析XML的工具,然后XML还有一个变种XHTML,这个结合了XML还有HTML的一些其他的特性,HTML的规则相对宽松,你可以省略一些标记,可以不需要头和尾一定匹配(就是不必封闭起来),而XML就非常严格了

虽然只有这一点差别,但是这就使HTML使用非常广泛,对于开发者来说更加容易,不易出错,你可以漏掉东西但能运行,这也导致HTML解析起来不太容易,传统的解析工具根本不行,你看了头不能匹配尾,就没办法确定结束,你看了尾就可能找不到头,所以XML这类解析器不适用于HTML

2.4.2.3 解析HTML的DTD

HTML定义是DTD格式,这种格式曾用于定义SGML(Standard Generalized Markup language标准通用语言标准),这种格式包括元素所有的定义,像属性及其层级结构,所以HTML DTD不能制作上下文结合的语法

DTD也经过一了些修改,严格模式的只是单一遵循定义,而其他模式也能对旧浏览器的标记进行支持,目的是向后兼容,目前严格的DTD参考后面这个网站:http://www.w3.org/TR/html4/strict.dtd(能打开,莫慌)

2.4.2.4 DOM

输出树(也就是渲染树),包括了DOM元素和其属性,它是HTML文档和HTML元素对外部而做的对象表现,这个外部就是那些js什么的,树的根节点就是文档对象

DOM相对解析前的文档几乎是一对一的关系

又举栗子,下面这段代码解析后就是下面那个图:

<html>
	<body>
		<p>
			Hello World
		</p>
		<div> <img src="example.png"/></div>
	</body>
</html>

DOM
和HTML一样,W3C组织对DOM也有明确规定,看后面这个网站:http://www.w3.org/DOM/DOMTR ,网站有对操作文档的规定,HTML定义看这个网站:http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html

当我说“树包含DOM节点”,我的意思是由树是由继承DOM接口的元素组成的,浏览器内部使用拥有其他属性的接口

2.4.2.5 解析算法

由之前所说,HTML是不能被那些常规的解析器(自上而下或自下而上)解析,因为以下几点:

  • 语言相对宽松
  • 浏览器只有基本的容错能力,只能检测我们知道的HTML错误
  • 解析过程是反复调用的,一般来说被解析的资源是不变的,是固定的那些,但是在HTML中是有脚本的,脚本会包括一些document.write之类的方法,它是动态变化的,所以那些常规的解析方式严格来说只能解析刚开始输入的那些内容,后面的变化那些就完蛋辽

因为常规的解析器不能解析HTML,只能自己定制适合自己的解析器,“私人定制,天下无敌”,解析算法在HTML5中有详细说明

算法分为两个阶段:

  • 解析词(Tokenization,我把这个名词译成一段过程,主要是让你明白),“解析词”的过程是词汇分析,解析输入的字符,HTML的字符包括:开始的标签(tag知道吧,像<html>)、结束的标签(像</html>)、属性名还有属性值
  • 这个宇宙超级无敌分词完事了再超级无敌解析词的工具(tokenizer,看名字就知道上一个是干嘛的了)就是识别字符,然后交给“树先生”(Tree Construction),“树先生”获取后生成DOM

这也是个重复的过程,直到输入的字符全都搞完辽就停止辽

看下图(还用解释不,想的话评论留言我加上):
解析过程

2.4.2.6 解析的第一个过程解析词的算法

这个算法输出的是HTML Token,算法做为状态机(开发的应该知道状态机吧,就是根据每个状态封装当前状态下要做的事,尤其是开发游戏的很了解)来进行,每个状态识别一个或多个字符,然后根据字符的结果更新下一个状态,每一步都会受到当前识别字符的状态和“树先生”的影响 ,所以呢,输入同一个字符根据当前的状态可能产生不同的结果,非为算法太复杂

所以举个简单的西瓜给你看:

<html>
	<body>
		Hello world
	</body>
</html>

结合上面的西瓜我来说一哈:
这个算法里有几个状态,先说一部分:

  • Data state,初始状态
  • Tag open state,标记开启状态,我也称之为“六老师两开花状态”,或者“标记两开花状态”
  • Tag name state,获取标记名的状态,我也称为“ ‘我特么要开始获取这个标记名了’ 状态”

然后就是解析的过程,与下面的图一起看:

  • 初始状态是“数据状态”(Data state,这个是默认的状态),当它读到“<”,就知道自己得开,就切换到“标记两开花状态”(Tag open state),然后接着读取,如果是“a-z”这39个英文字母,它就切换到“ ‘我特么要开始获取这个标记名了’ 状态”(Tag name state),当再读到“>”,它就闭辽,上面的西瓜中<html>就是个标记
  • 它闭辽之后先提交,然后就切回“数据状态”,下面的<body>也是同样的操作
  • 然后就开始读“H”,然后就是“e”,然后就是“l”……,直到读到“<”之前,它会先提交,然后切到“标记两开花状态”(Tag open state),再往下读,读到“/”,它就会切换到“我要开始获取这个标记的名状态”(Tag name state),然后读到“>”就闭辽,接下来就切回“数据状态”,下面的</html>和前面的一样

看下面的图,不懂英语看上面的字脑补我在你耳边缩话讲解:

字符算法

2.4.2.7 解析的第二个过程“树先生”(tree construction)的算法

当有“树先生”的时候,就开始创建DOM对象(Document object),接下就“树先生”不断接收上一过程输出的,然后不断往DOM添加元素,除了往DOM里面添加,它也会把一些元素存到栈中,这个栈你可以当成临时的仓库,这里面的东西就是用来匹配一些没有关闭的标记等东西,很巧,“树先生”算法也有一个状态机,你可以叫他“插入模式”(insertion modes)

还是看那个栗子:

<html>
	<body>
		Hello world
	</body>
</html>

先说一点,这个插入模式是包括很多状态(“before html”,“before head”,“after head”,“before body”等等,这些我不译,看也能看懂),别晕了,可以与下面的图结合着看

  • “树先生”的输入就是上一过程的输出,他的第一个状态是“初始状态”(initial mode),接收HTML字符将会转为“before html”模式,然后处理这些字符,这个状态会在DOM根节点创建“HTMLHtmlElement”(没有写错,中间的Html换成Head或Body意会一下)
  • 当收到<body>后就转为“before head”模式,,如果你不写<head>,它也会创建“HTMLHeadElement”,接下来会转为“in head”状态,然后的然后就是“after head”模式,(然后的同义词除了接下来还有哪个?)然后它创建“HTMLBodyElement”并转为“in body”模式
  • 下一步,它不断收到“Hello world”的字符,当收到“H”它就会把“H”加到“Text”节点,后面的都会加进去
    然后它收到</body>,就会转到“after body”模式,最后的最后它接收完了就会转到"after after body”模式,收到最后的字符它就会休息了,他就不干了,他就,嗯

看下面的图,不懂英语看上面的字脑补我在你耳边缩话讲解:
“树先生”工作中

2.4.2.8 解析完了之后

在这个阶段,浏览器标记DOM为“开放”状态,然后转译处于阻塞模式的脚本(js之类的,这些脚本需要在前面那些东西解析后执行,所以先排队等待),前面的“开放”状态会激活“complete”和“load”事件

想看上面两步解析的算法,可以看下这个网站嘛: http://www.w3.org/TR/html5/syntax.html#html-parser

2.4.2.9 看下浏览器怎么处理错误

你在浏览器中不会看到一些报错,是因为浏览器已经帮你“修复”了
看下这个桃子:

<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
  	Really lousy HTML
  </p>
</html>

上面的栗子有很多错,要是我我可能受不了,但是浏览器是我哥,它不仅能忍,它还能给你改过来

浏览器有对错误处理的能力,但是这个并没有在HTML中明确规定,就像它本来也没有去规范浏览器必须要书签、前进、后退这些按钮,但开发者都一致的加上了

你可以没说要有什么功能,但是我可以让它有,很强,就像你规定了人类菊花只能拉屎(好像也没人规定只能出不能进),但是……嗯,你一定想不到此洞还能包容万物,那句话怎么说“天腐成都,无1无靠,遍地飘0,看那一朵朵菊花爆满山,盛开在Gay们相爱的季节”,爱他就多买润滑油

HTML5也规定了一些必要的东西,Webkit也在HTML解析器类的开头总结写出来,不译了,自己看,求我也不译(我译):

The parser parses tokenized input into the document, building up the document tree. If the document is well-formed, parsing it is straightforward.
 解析器解析内容输入到文档,构建出文档树。如果文档符合语法规则,就会很容易的解析出来
 
Unfortunately, we have to handle many HTML documents that are not well-formed, so the parser has to be tolerant about errors.
 事实上(如果你和我纠结不幸的为什么译成事实上,那你直接走吧,别看了),我们处理的HTML文档一定程度上不符合语法规则,所以转译的同时还得去处理错误(容错)
 
We have to take care of at least the following error conditions:
 我们得小心下面这些错误的情况:
 
 - The element being added is explicitly forbidden inside some outer tag.In this case we should close all tags up to the one, which forbids the element, and add it afterwards.
 一些元素是不能被加到外部的tag中的,所以在一种情况下我们需要关闭所有的tag,那种情况就是禁止元素再被加进去
 
 - We are not allowed to add the element directly. It could be that the person writing the document forgot some tag in between (or that the tag in between is optional).This could be the case with the following tags: HTML HEAD BODY TBODY TR TD LI (did I forget any?).
 我们不被允许直接添加元素,可能开发者写文档时在中间漏写了一些tag(或许tag写不写都行),例如:HTML HEAD BODY TBODY TR TD LI(后面是作者开玩笑问有没有漏举列)
 
 - We want to add a block element inside to an inline element. Close all inline elements up to the next higher block element.
 还有一种是我们想添加一个块元素到行内元素中。
 关闭所有行内元素直到下一个更高级别的块元素

 - If this doesn't help, close elements until we are allowed to add the element or ignore the tag.
(这句你自己看,给你个机会)

下面看下Webkit的一些容错的栗子:

  • <br>写成</br>,为了适配IE和Firefox,Webkit使用下面的代码改错,原文后面有一句:这些错误是静默处理的,不会展示给使用者

     纠错代码:
     if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
          reportError(MalformedBRError);
          t->beginTag = true;
     }
    
  • 错误的表格,这种就是你瞎写,表套表,瞎弄,表1是你写的,表2是人家帮你改的,他会把里面的表弄到外面的表外面,让它们成为同级关系

    错表格式

     <table>
     	<table>
     		<tr><td>inner table</td></tr>
        </table>
     	<tr><td>outer table</td></tr>
     </table>
    

    下面是帮你改后的表


     <table>
     		<tr><td>outer table</td></tr>
     </table>
     <table>
     		<tr><td>inner table</td></tr>
     </table>
    

     纠错代码:
     if (m_inStrayTableContent && localName == tableTag)
     	popBlock(tableTag);
    
  • 嵌套形式的元素,如果一个套在另一个外面,那第二个就会无视

     纠错代码:
     if (!m_currentFormElement) {
     	m_currentFormElement = new HTMLFormElement(formTag,    m_document);
     }
    
  • 过深的标签层次

      www.liceo.edu.mx is an example of a site that achieves a level of nesting of about 1500 tags, all from a bunch of <b>s.
      We will only allow at most 20 nested tags of the same type before just ignoring them all together.
      上面那个网站就是一个例子,实现了大约有1500个嵌套标签,这些标签是一堆<b>,如果标签是相同的,那最多允许嵌套20个
      
      纠错代码:
      bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName){
      unsigned i = 0;
      for (HTMLStackElem* curr = m_blockStack;
               i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
           curr = curr->next, i++) { }
      return i != cMaxRedundantTagDepth;
      }
    
  • 错误的结束标签

      Support for really broken html.
      We never close the body tag, since some stupid web pages close it before the actual end of the doc.
      Let's rely on the end() call to close things.
      为了能够运行错误的html,我们不会让关闭body的标签起作用
      因为一些臭傻逼瞎jb写,在不该结束之前就乱加结束标签,所以当我们想结束的时候,我们会调用end()方法
      
      纠错代码:(直接给return了有没有,你就别想着瞎加乱搞事)
      if (t->tagName == htmlTag || t->tagName == bodyTag )
      return;
    

2.4.3 CSS解析器

了解了HTML解析,那这个就好理解了,不同于HTML解析,CSS是上下文解析,可以使用上面说的那些解析器解析
看几个栗子,一些词汇的语法由正则表达式规范,不懂正则表达的先去看下,如下:

这部分不用看,只要会CSS就知道这什么意思了,不会的话你不结合其他的示例也看不懂,所以不会就去搞两个小Demo看下

comment		\/\*[^*]*\*+([^/*][^*]*\*+)*\/   
num		[0-9]+|[0-9]*"."[0-9]+					
nonascii	[\200-\377]							
nmstart		[_a-z]|{nonascii}|{escape} 			
nmchar		[_a-z0-9-]|{nonascii}|{escape}
name		{nmchar}+
ident		{nmstart}{nmchar}*	 这个是标志符(identifier)的缩写,和类名(class name)差不多,使用的方法是:#id

句法使用BFN格式定义:

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator selector ] ]
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;
Explanation: A ruleset is this structure: (这个是使用的语法结构)
div.error , a.error {
	color:red;
	font-weight:bold;
}
div.error and a.error are selectors. The part inside the curly braces contains the rules that are applied by this ruleset. This structure is defined formally in this definition:
ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
This means a ruleset is a selector or optionally number of selectors separated by a coma and spaces (S stands for white space). A ruleset contains curly braces and inside them a declaration or optionally a number of declarations separated by a semicolon. "declaration" and "selector" will be defined in the following BNF definitions.
2.4.3.1 Webkit的CSS解析器

Webkit使用Flex和Bison解析器生成器来生成CSS解析器,之前的章节有说到解析器介绍,Bison使用从下到上移位归约解析器,而火狐使用的是自上而下解析器。每个CSS文件会被解析成样式表对象,每个对象都包含了CSS规则,而CSS规则又包含了各个组件,还有组件的样式,看下图:
在这里插入图片描述

2.4.4 脚本和样式表的顺序

2.4.4.1 脚本

浏览器是同步的,程序员希望解析器读到<script>标签时就立刻解析脚本并运行,当运行脚本时会停止解析文档,当脚本没有包含在内部,会先从网络获取,这个过程也是同步的,获取的时候也会停止解析文档,这个规则一直都在,去看HTML4和5

  • 当你使用“defer”,也就是<script defer src=""></script>,它在获取脚本的时候不会停止解析文档,当从网络获取完之后,它不会马上解析运行脚本,会在解析文档完之后再开始
  • 还有一个关键字“async”,写法<script async src=""></script>,它是从网络获取的时候,不会停止解析文档,获取完就马上解析运行脚本
2.4.4.2 根据后面的内容解析并加载

Webkit和Firefox都有这方面的优化,当执行脚本的时候,会分出另一个线程去解析文档剩余的部分,然后查找出需要从网络获取的内容,获取然后加载。获取的时候会使用平行的网络(parallel connection),所以速度会更快。

注意一下,推测解析只是解析所需要的东西并提前加载,并不会修改整个DOM树,DOM树是它老大解析器负责的(它不是“解析器”,它只是解析一部分,并不负责真正的DOM生成部分)

2.4.4.3 样式表

理论上来说,样式表并不会改变DOM树,所以不需要停止总进度来等待这个样式表的生成

但是有一个小问题,在文档解析的时候,脚本也在运行,它会去查询样式信息,如果样式还没有解析完成或者没有加载完成,那脚本就不会得到正确的结果,或者得到一个没有解析完的样式,这个问题产生的概率非常非常小,但它总归要发生,所以Firefox定了个规则:加载或者解析样式表的时候,停止脚本的运行,而Webkit做的是:如果脚本想获取的样式与未被加载的那些内容有关联,那就停止运行脚本


2.5 渲染树

当DOM树解析生成完之后,浏览器就会去生成另一个树 渲染树。

渲染树是为了更好的渲染显示出来,它是文档解析成一个视觉方面的效果(举个不恰当的例子,剧本拍成电影,导演脑子里先有的就是电影之前的效果,他这个根据剧本想出来的效果可能拍不出来,所以不太恰当),渲染树主要目的就是为了浏览器把各元素正确的渲染在该在的位置

Firefox把渲染树的元素称为“frames”(上面有说,看之前那个图),Wekit使用渲染器或渲染对象

渲染器知道如何布局并画出各个渲染节点中的内容

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中有定义,自己去看)它包括很多属性,像长、宽、位置等等,就是那些图形该有的属性,这个区域与样式中的属性相关联

下面是Webkit代码,根据显示的属性选择相应的渲染器:

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;
}

元素的类型也不是瞎定义的,像表单控件和表这种就比较特殊

拿Webkit来说,如果想要渲染一个渲染树的节点就会重写上面的“createRenderer”方法,渲染器指向样式对象(样式对象包含各种非几何信息)

2.5.1 渲染树与DOM树的关系

渲染树的每个节点渲染器与DOM树的每个节点对应,但不是完全对应,一些不会在视觉上表现出来的DOM元素就不会添加到渲染树中,栗如,“none”属性的元素不会添加进去,但如果元素属性是“hidden”则会加进去

一部分DOM元素与可视对象一一对应。也有一些有复杂结构的元素不能被一个单独的渲染器渲染出来(Thease are usually elements with complex structure that cannot be described by a single rectangle),举个栗子,为了渲染“select”(选择列表),就会用到三个渲染器,一个是描绘显示的区域,一个是选择列表,还有一个是按钮。如果一行文本过长而换行,那多余的那些行也会有额外的渲染器来渲染

通过CSS的规定可以知道,一个内联元素必须包括块元素或内联元素。而且为了混合元素,将会创建一些匿名块渲染器来包装行内元素(anonymous block renders will be created to wrap the inline elements)

还有一些渲染对象与DOM树不同位置的DOM节点对应。有布局的元素(“float”、“absolutely”)会浮动出来,最终渲染在正确的位置,在没有真正渲染前,那个位置会先“规划”出来,先“占个位”(Floats and absolutely positioned elements are out of flow,placed in a diefferent place in the tree,and mapped to the real frame.A placeholder frame is where they should have been)
渲染对应DOM树

2.5.2 构造树的流

Firefox会注册一个“东西”(presentation,特么的这些专有名词译出来尬尬的,不译了)来监听DOM树的变化,如果DOM树变化,那这个“东西”会重新排列样式,并让“FrameConstructor”来创建一个“盒子”(frame,就是一个盒子一样的东西)

而在Webkit中,重排样式并且创建渲染器的称为“附加”(attachment),每一个DOM节点都会有个附加(attach)方法,附加的过程是同步过程,每个节点会调用attach方法来插入DOM树

<html><body>标签会加到渲染树的根节点,根节点渲染在CSS规定里面有说,也被称为包裹块(containing block)—包含别的块的最高级别块,它的尺寸是Viewport(这个不译,看上面那张图就知道了)的尺寸,也就是浏览器窗口的显示区域尺寸。Firefox称之为ViewPortFrame,Webkit称之为RenderView(这些名词不译),剩余的渲染树节点将会插入其中

2.5.3 样式计算过程

构建渲染树需要对每个要渲染对象的可见属性进行计算,这个过程在计算样式属性的时候就一块搞了

渲染对象的样式包括各种原始的样式表,内联样式以及HMTL可见的属性(像“bgcolor”这些),内联样式会转换并与CSS样式属性进行匹配

原始的样式表就是浏览器默认的样式,之后你也可以自定义(自定义喜欢的样式,Firefox的自定义会放在“Firefox Profile”文件夹内)

样式的计算会带来某些困难:

  • 首先是存储方面:样式有非常多的属性,所以数据量有点大

  • 表现方面:渲染树中每个元素都需要进行匹配规则,如果没有进行优化,那表现上就会出现问题,因为规则太多,一个还好,那所有的元素都匹配一遍就会gg,拿下拉列表来说,这玩意结构复杂所以不可行。看后面这个栗子 div div div div { },这个套了四层,假设想要检查规则是否适用于你想选择的那个<div>,然后也选择了该节点的路径,但你第一次找的可能只有两个div(因为你的网页里面不可能只有四层div的结构),所以不得不继续再遍历去找其他符合的

  • 规则涉及太多、层级太复杂,导致难以应用

下面是浏览器的解决办法:

2.5.3.1 共享样式数据

Webkit节点会引用样式的对象,在某些条件下这些对象可以共享,下面是可以共享需要的条件:

  • 这些节点是“兄弟姐妹”关系(就是同级)
  • 这些节点必须在同一鼠标状态下(如果一个是hover,另一个必须也是hover
  • 都没指定id
  • 标签名要一样
  • 类也要一样
  • 属性要完全一样
  • 链接的状态也要一样(参考上面的鼠标状态)
  • focus状态也要一样
  • 不能被属性选择器影响,属性选择器就是一次性匹配一堆节点然后进行属性设置
  • 没有写内联样式
  • 没有同级选择器被使用,当有的话,WebCore会设置一个全局的开关来禁止样式的共享(包括相邻兄弟选择器,还有:first-child:last-child选择器)
2.5.3.2 Firefox规则树

为了简单样式的计算,Firefox有另外两个树 —规则树(rule tree)和样式内容树(style context tree),Webkit也有样式对象,但不是像Firefox这样存储在类似树的结构中,使用的是DOM节点指向关联的样式

下面是Firefox的style context tree:
style context tree

样式内容包括最终值(end values,别说结束值呦,打你)。这些值是结合规则把所有逻辑值变为具体的值后计算出来的,举个樱桃,如果有一个逻辑值是节点某个属性,而且这个属性值是百分比,它会计算出这个节点属性绝对的值,规则树非常“聪明”,它会在节点内共享这些值,这样就不用重复计算了,也节省了空间

所有的匹配规则都存在树中,底部的节点有更高的优先级,如果有节点匹配规则它就会把其加进去,刚开始它不会计算匹配所有的节点,只有节点的样式需要被计算时,它才会把计算的路径加进树里,可以说相当懒

The idea is to see the tree paths as words in a lexicon(这句你自己去理解)
下面是已经计算好的路径:
在这里插入图片描述

结合上图说一下,如果我们想找B-E-I这个规则,就可以直接在A-B-E-I-L中找到,不用重新再去寻找,不用浪费不必要的“精力”

2.5.3.2.1 分解为各种结构(structs)

样式树分解成各种结构,这些结构包括某一类别的样式,像border、color等,结构中的属性包括继承的(如果节点的属性没有被定义,将会继承它们的父节点),非继承的(非继承的属性也可以叫做“reset”属性,指那些如果没有被定义,则会使用默认的值)

树缓存了全部的结构(包括计算的最终值),如果有一个节点不满足结构中的定义,那就会在高一级节点使用缓存的结构

2.5.3.2.2 使用规则树得到样式内容

当想要计算元素的样式内容,会首先算出所在的路径(或使用已有的路径),然后就会结合路径中的规则去完善(fill,你也可以说“填满”)样式内容的结构。

  • 计算会从最高级节点开始,也就是最底部节点,然后向上遍历树,直到遍历完,如果某个节点A没有具体的结构说明,会继续向上找,直到找到最符合的,然后指向这个节点A(共享结构,这是最佳的方法),这样不用重新计算最终值,也节省了空间
  • 如果没有找到,那就会使用上面说的继承,继承父节点,如果是reset结构,那就会使用非继承,也就是使用默认值

如果一个节点有计算值,那就会进一步操作,计算出最终值,然后缓存在父节点以便子节点使用

假如一个元素有相邻节点或者它的“兄弟”节点指向同一个父节点,那这个样式内容可以在它们之间共享(下面的<span>就是)

看下面的栗子,上面是THML,下面是CSS:

<html>
	<body>
		<div class="err" id="div1">
			<p>
                this is a <span class="big"> big error </span>
                this is also a
                <span class="big"> very  big  error</span> error
        	</p>
		</div>
		<div class="err" id="div2">another error</div>
    	</body>
</html>
div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div 2 {color:green}

根据CSS可以看出,我们改的只有两个值,外边距(改了整个边距margin、上边margin-top、下边margin-bottom)和颜色(也就是颜色)

下面是规则树(rule tree):
规则树
下面是结构树(context tree):
结构树
下面结合上面的规则树和结构树说下解析的过程:

  • 先解析HTML,当解析到第二个<div>创建样式环境并完善样式结构
  • <div>匹配规则时发现影响到的是1、2、6,有这么多就意味着已经有匹配的路径,就得再为规则6添加另一个节点(就是上面规则树的节点F)
  • 创建完结构树的F就将新的样式环境指向规则树中的F(注意,两个F不是同一个)
  • 因为节点F规则没有写外边框结构,因为我们向上找,如果有的话就继承使用(就是节点Bmargin:5px)该节点的样式(会用CSS就很好了解,就是父节点的样式会影响到子节点,子节点单独有定义规则就用自己的规则,如果节点是reset属性且没有定义规则就使用默认值)
  • 该节点有写颜色样式,所以就不能使用父节点的样式,然后计算出该节点的RGB值,接下来缓存到该节点
  • 对于第一个<span>,我们将会匹配规则然后指向规则树的G,因为有兄弟节点有同一父节点(可以共享父节点的样式内容),第二个<span>就更简单了,就可以直接指向前一个<span>

结构(就是那一条条路径,里面包括节点)的样式可以继承父节点的(color属性也可以继承,但Firefox把它当成reset然后缓存在规则树中)

如果我们修改<p>的规则为p {font-family:Verdana;font size:10px;font-weight:bold},那它的子节点就得使用同样的字体

Webkit中没有rule tree,匹配声明要经过4次判断,顺序如下:

  • 高优先级的、不重要的(需要首先被声明的是那种被依赖的,像display)
  • 高优先级的、重要的
  • 普通优先级的、不重要的
  • 普通优先级的、重要的

根据前面的优先顺序进行一次一次替代(The last wins,就是有规则的节点赛高)

2.5.3.3 更容易的匹配规则方法

几种CSS方法:

  • 外部样式
    p {color:bule}
  • 内联样式
    <p style="color:blue" />
  • 还有一种是直接用控件的属性名定义
    <p bgcolor="blue" />

后两种直接写进去了,不用再去查找节点进行匹配规则,所以比较快

但是因为一些缺点(上上上面有说),匹配CSS规则的过程非常麻烦,所以就做了如下的操作:

就是创建一堆表(map),解析完样式,根据选择器的不同,规则会被加进不同的哈希表(hash map)中,这个表有几类:id表、class表、tag表、还有一个表是如果不能匹配前面那几种就加进来。 如果选择器有id,规则会被加进id表中,如果有class名,将会加进class表中

这样就不需要查看声明了,可以直接在相关的表中取规则,这些表包含了超过95%的规则(This optimization eliminates 95+% of the rules,这种优化可以剔除至少95%的规则),所以在匹配的过程中就不需要再做很费时的操作,就很快

看下面这个栗子:

p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}

CSS样式的第一行会被加进class表中,第二行会加进id表中,第三行加入tag表中

再看下一段HTML:

<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>

首先为<p>寻找规则,会进入class表,找到p.error,然后下面的<div>会在id表或tag表中找到,剩下的那些无非就是找出最最符合的然后提取出来

如果多加了一行下面规则:

table div {margin:5px}

那就会在tag表中找,但是找不到,因为没有<table>

很巧,Webkit和Firefox会做同样的这些步骤

2.5.3.4 规则的优先级

样式对象的属性(property)与每个可见的属性(attribute)对应(基本上的都是这样),如果属性没有被匹配的规则定义,就会从父节点继承,不能继承的有默认值

如果一个还好,当有多个那就会冲突,所以用优先级解决这个问题

2.5.3.4.1 样式表的优先级

一个控件(称节点什么的其实都“一样”,都对应同一个东西)的属性可能在好几个样式表中定义,也能在一个样式表中有多处定义,根据CSS2的定义,层级顺序(从低到高),如下:

  • 浏览器默认值 Browser declarations(declaration我暂译成值,你知道它表示什么就行了)
  • 用户默认值 User normal declarations
  • 开发者默认值 Author normal declarations
  • 开发者优先值 Author important declarations
  • 用户优先值 User important declarations
2.5.3.4.2 权重

原文写了很多,我找张图看下就好了,如下:
在这里插入图片描述
说明一下:

  • 如果有!important表示强制使用,是最高级,然后就是上面的顺序,权值越大级别越高
  • 如果权重值加起来一样,那么后声明的比先声明的级别高
  • id是高于class的,所以再多的class权重值相加即使比id的权重值大也不会比id优先
  • emmmmm……

具体的还是要你们去深入学,其实规则也就那些,我写太多怕影响你们强大的学习能力,我就不写了

2.5.3.4.3 对规则进行排序

规则匹配后,根据层级规则排序,Webkit使用冒泡排序(bubble sort)小列表,使用归并排序(merge sort)大列表,Webkit会重写“>”操作符实现规则排序:

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2; 
}
2.5.3.5 渐进过程(Gradual process)

Webkit使用一个标志来标记最高级的样式表(包括@import)被加载完,当附加(attach,Webkit专有的,开始有说)时样式没有完全被加载,会在那个地方加个占位,当样式加载完会重新计算一次


2.6 布局

当渲染器创建并加入树中,它还没有声明所在的位置和大小,计算这些值的过程称为layout(Webkit的叫法,刚开始有说,就是那张图)或reflow(Gecko的叫法)

HTML使用基于流的布局模型,意思就是每一次过程都在计算,而且后面的布局不会影响前面的,所以可以从左到右、从上到下进行布局

坐标系统是相对于根框架(最大的那个框架),会使用到TopLeft

布局是一个递归的过程,从根渲染器(对应HTML根)开始布局,然后在各框架中循环并计算几何信息(就是位置、大小等这些值)

根渲染器是从(0,0)开始,大小就是窗口的尺寸(不是整个浏览器,是可显示的区域的尺寸)

所有的渲染器都有layoutreflow方法,每个渲染器布局时都可能调用到

2.6.1 “Dirty bit”系统(Dirty bit system)

一些小的改变就没必要重新布局,浏览器会使用到“Dirty bit”系统,如果有部分改动,渲染器会标记自身或子节点为“dirty”,就表示需要重新布局

有两种标记 — “嘤嘤嘤,我身体不干净了”(dirty)、“我孩子脏了,嘤嘤嘤”(children are dirty)
子节点脏表示尽管自己没变,但至少有一个子节点需要重新布局

2.6.2 整体布局和递增布局

有些情况会造成整体重新布局,这种称为整体布局(global layout),产生的原因如下:

  • 整体风格的改变,像字体大小的改变
  • 窗口的缩放

Layout can be incremental, only the dirty renderers will be layed out (this can cause some damage which will require extra layouts),布局不仅有整体,也是可以递增的,只需要布局脏的那部分就行了(整体布局会有额外的布局,带来不需要的开销,所以不妥)

当渲染器“脏了“会触发递增布局(incremental layout),举个栗子,有额外的内容从网络获取被加进DOM树中,此时就会触发,这种是异步的

在这里插入图片描述

2.6.3 异步布局和同步布局

上面的递增布局是异步的。Firefox有一个队列叫“reflow commands”来放置递增布局并设置一个调度器来批处理队列中的命令。Webkit也有一个计时器来执行递增布局(遍历树并对脏渲染器重新布局)

脚本请求某些样式信息会同步触发递增布局,整体布局通常也是同步触发

有时候一些属性的改变会也重新布局,像列表滚动

2.6.4 优化

当窗口发生“变化”(他这边用的“resize”)或者节点发生变化(只是位置,没有尺寸变化),就不重新计算,会在缓存里去取值

这种优化在只修改子树且不发生整体布局变化,只是局部变化不影响周围的变化的情况下生效(例如:往输入框里输入字符),如果没有这种优化,那每输入一个字符就需要重新布局一次,那就太扯了
(In some cases - only a sub tree is modified and layout does not start from the root. This can happen in cases where the change is local and does not affect its surroundings - like text inserted into text fields [otherwise every keystroke would have triggered a layout starting from the root]. )

2.6.5 布局过程

布局有以下几个步骤:

  1. 父渲染器先决定宽度
  2. 然后父节点遍历子节点
    • 放置子渲染器位置
    • 计算子节点的高度,如果子节点是dirty 或者 整体布局变化 或 其他原因也会调用子节点的布局得到最终高度
  3. 得到全部子节点的高度算出自己整个的高度
  4. 将自己的dirty bit设为false,表示算完了,如果有改变再设true,由父的父节点调用

这个自己理解一下,是相对的,你是爸爸的同时也是儿子,你对儿子做的,你爸爸也会对你做同样的事 乖,叫爸爸

Firefox使用“state”对象(nsHTMLReflowState)作为布局参数,这个包括了父节点的宽度
Firefox布局输出是“metrics”对象(nsHTMLReflowMetrics),这个包括节点计算后的高度

2.6.6 宽度计算

渲染器宽度是根据容器宽度、渲染器样式宽度属性、外边距及边框

举个栗子:

<div style="width:30%"/>

在Webkit中,上面这段计算方式(使用的是RenderBox类的calWidth方法)如下:

  • 容器的宽度是容器的可用宽度的最大值,这里说的可用宽度指的是内容的宽度,由下面的过程计算出clientWidth() - paddingLeft() - paddingRight() ,clientWidth(或者是clientHeight,后面就改成高度)指的是对象除开边框和滚动条之外剩下的内部宽度
  • 元素的宽度属性“witdh”,将会根据容器宽度的百分比算出实际的值
  • 然后计算横向的边框和内边距并加进去

上面就是计算过程,最终得到最小和最大宽度,如果实际宽度大于最大宽度,就取最大宽度,如果小于最小,那就取最小宽度

这个宽度值是缓存起来的,如果宽度没有变化可以随时取用

2.6.7 换行

当渲染器要换行时,将会停止并告诉父节点“爸爸,俺要换行”,父节点将会创建新的渲染器然后对换行部分进行布局


2.7 绘制(Painting)

在绘制阶段,渲染树会遍历然后调用paint方法画出内容,使用UI基础控件绘制

2.7.1 整体方式和递增方式

和前面说的布局差不多,绘制也有整体绘制(就是绘制整个树)和递增绘制。

在递增绘制中,一些渲染器以不影响整个树的方式进行修改,这些渲染器会使它自己的区域无效,操作系统(OS)把那些区域当作脏区域(dirty region)然后触发“paint”事件,OS贼鸡儿聪明,鸡儿贼聪明,它会将几个区域合并成一个。

在Chrome中,这个比较复杂,因为渲染器处在不同进程中(除了主进程还有其他进程),在某种程度上Chrome模仿了OS的行为,监听事件然后发送消息给根节点渲染器 (render root,这是大总管,可以比作奥特之王,M78星云的王者,说到奥特之王那我就来说一说嗷,这个奥特之王年龄超过20万岁,他头部的球体可以接收宇宙中发生的所有的事(这要是给扣下来,得特么卖多少钱?),而且奥特之王有个很牛逼的技能:王者再生光线,这可是复活雷欧的光线,说到雷欧,我不得不多说几句,雷欧同志以前是咱L77星云的炎之战士,经过一系列事件后加入了党,尽职尽责,没事就欺负一下小怪兽,小日子过得美滋滋,他的变身器是戒指狮子之瞳,只要双手交叉然后上举,接下来大喊一声我党万岁 “雷特么欧”,就可以变身,与巴啦啦小魔仙变身方式比稍微不那么华丽(巴啦啦魔仙变身方式多帅多华丽啊,宇宙最高但雨女无瓜),他的腿技非常厉害,什么腿切,背踢,最强必杀技就是雷欧飞踢,吊的一批,接着再说奥特之王,他的另一个技能是bug般的存在:王者变革,可以与宇宙融合,靠这一招拯救宇宙你敢信?反正也是吊的一批) 同志们回来回来,接着上面的render root,它将会重绘自己(不过经常是子节点)

2.7.2 绘制顺序

CSS2定义了绘制过程的顺序,看后面这个 http://www.w3.org/TR/CSS21/zindex.html 元素会先放在stacking contexts,放置的顺序会影响到绘制

一个块渲染器的放置顺序如下(这个不译,都懂):

  1. background color
  2. background image
  3. border
  4. children
  5. outline

2.7.3 Firefox的显示列表

Firefox遍历渲染树然后会为每一个绘制的区域(rectangular)创建一个显示列表,这个列表里面包括此区域绘制的顺序

通过这种方式重绘就不需要再次遍历渲染树来获取绘制顺序

Firefox不会把那些要隐藏的元素加进去(例如在其他不透明元素下的元素)

2.7.4 Webkit区域存储

绘制前,Webkit会将旧的绘制区域存为位图,然后只绘制新的与旧的之间的交叉的区域(It then paints only the delta between the new and old rectangles)


2.8 动态修改

浏览器会做尽可能小的修改来改变,所以如果改变元素的颜色不会重绘元素,改变元素的位置将会重新布局子节点然后重新绘制,添加一个DOM节点将会布局并重绘该节点

那些影响大的修改,例如修改整体字体大小,将会使之前的缓存失效,重新布局整个树并绘制

2.9 渲染引擎的线程

渲染引擎是串行的,除了网络外,都是单线程的。

  • 在Firefox和Safari浏览器中,这个线程就指的是主线程
  • 而在Chorme浏览器中,是选项卡进程的主线程(因为每一个选项卡就是一个进程,最最最最开始有说)

网络方面的是通过并行的线程进行操作,而且并行的连接是有限制的(通常是2-6个,Firefox 3使用的6个)

2.9.1 事件循环

浏览器主线程是一个事件循环,它无限循环以确保进程保持工作状态,事件是依次执行的(处理过之前的才能处理后面的)

下面是Firefox的主事件循环代码:

while ( !mExiting)
	NS_ProcessNextEvent(thread);

2.10 CSS2视图模型

注意:我译的这篇是很早之前的了,虽然CSS3与CSS2相比添加新的功能或者完善部分定义,修改的挺多的,但是下面说的是基本的,还是要看,其他新增或修改的你自己去找

2.10.1 The canvas

根据CSS2的定义,canvas描述为“渲染格式化的结构的容器”(the space where the formatting structure is rendered)—也就是浏览器显示内容的区域

理论上canvas可以是无限大,但浏览器取初始的长宽值来设置canvas的大小,你家浏览器很大的话那你很牛批(交个朋友

根据 http://www.w3.org/TR/CSS2/zindex.html ,如果一个canvas嵌在另一个canvas中,就会把前者设为透明,否则就会设置一个默认的颜色

2.10.2 CSS盒子模型

所谓的CSS盒子模型(CSS box model)就是用来放置元素的长方形盒子,把抽象的事物具体化表述

每个盒子都有一个放置内容的区域(栗如:text、image等等),这个内容区域会被padding、margin、border等样式环绕(如果有设置的话),看下面的图就了解了,不用多说

下面就是盒子模型(CSS2):
在这里插入图片描述
デスノート

2.10.3 元素位置方案(Positioning scheme)

下面说的对象、元素、DOM树结点你可以当成一个东西来理解

有三种方案:

  1. Normal - 根据本身的位置来放置对象,意思就是对象在渲染树中的位置与DOM树中的位置相同
  2. Float - 对象先按Normal方式布局,然后根据设置的位置进行浮动
  3. Absolute - 对象在渲染树中的位置与DOM树中的位置不同

元素的位置会结合position和float属性得出

  • static和relative会在默认的文档流中
  • absolute和fixed会有一个绝对的位置

如果没有定义位置信息,那默认的就是static,剩下那几种需要结合位置(top、bottom、left、right)信息使用
盒子的布局会根据下面得出:

  • 盒子类型
  • 盒子的尺寸
  • 上面的位置方案
  • 外部的信息(例如图的大小、屏幕的大小)

2.10.4 盒子的类型

我直接在下面两句总结,后面说的那些就不写了,都是一个意思
块盒子(block box) - 这种是竖向排列的,它会独占一行,没有设置的话宽度默认占满父元素,设置宽度也是独占一行(一些刚学CSS的同学在想设置宽度怎么还是独占一行呢,有这种类似的骚想法很正常,吃一堑长一智)
在这里插入图片描述
内联盒子(inline box )- 这种是模向排行,直到一行放不下才会换行,它的宽度与其内的元素有关,设置宽高是没用的,而且边框的话只有left,right有效,像padding-top,padding-bottom是没用的
在这里插入图片描述

2.10.5 位置

2.10.5.1 相对(relative)

相对位置 - 很好理解,相对哪个元素,距离那个元素多远

看下面的栗子:
在这里插入图片描述

2.10.5.2 浮动 (float)

浮动的盒子就是在一行内向左或向右浮动

看下面的栗子(这个是图片向右浮动,看起来有一种inline-box的效果,具体的自己去查):

<p>
<img style="float:right" src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer...
</p>

在这里插入图片描述

2.10.5.3 绝对和定位 (absolute、fixed)

这两种已经飞升了,不在文档流中

  • absolute相对的是非static的父类元素,没找到就一直找爸爸的爸爸的爸爸,直到body
  • fixed是相对窗口,如果有滚动条,滚动的时候,这个元素是不跟着动的

2.10.6 层级

CSS的z-index属性决定层级

盒子是叠在一起的(称为堆叠环境),先画的在下面,后画的在上面,上面的会盖住下面的

如果z-index没有指定,那后画的元素在上面,如果指定了,z-index值越大的越在上面

看下面的栗子:

<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>

在这里插入图片描述

2.11 相关资料

  1. Browser architecture

    1. Grosskurth, Alan. A Reference Architecture for Web Browsers.
  2. Parsing

    1. Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools (aka the “Dragon book”), Addison-Wesley, 1986
    2. Rick Jelliffe. The Bold and the Beautiful: two new drafts for HTML 5
  3. Firefox

    1. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers.
    2. L. David Baron, Faster HTML and CSS: Layout Engine Internals for Web Developers(Google tech talk video).
    3. L. David Baron, Mozilla’s Layout Engine.
    4. L. David Baron, Mozilla Style System Documentation.
    5. Chris Waterson, Notes on HTML Reflow.
    6. Chris Waterson, Gecko Overview.
    7. Alexander Larsson, The life of an HTML HTTP request.
  4. Webkit

    1. David Hyatt, Implementing CSS(part 1).
    2. David Hyatt, An Overview of WebCore.
    3. David Hyatt, WebCore Rendering.
    4. David Hyatt, The FOUC Problem.
  5. W3C Specifications

    1. HTML 4.01 Specification.
    2. HTML5 Specification.
    3. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification.
  6. Browsers build instructions

    1. Firefox.
    2. Webkit.


译完了,我透。


维尼聚合工具


猜你喜欢

转载自blog.csdn.net/S_clifftop/article/details/95994512
今日推荐