Inside the Browser 
浏览器渲染原理 
张立理 
otakustay@gmail.com
WHY? 
HTML布局无处不在已经不局限与浏览器
Summary 
 浏览器的工作过程 
 资源下载 
 HTML解析 
 CSS计算 
 布局 
 渲染 
 浏览器对细节的具体优化手段 
 Firefox 
 Webkit / Chrome
How?
下载 
一个HTTP库能够搞定? 
 DNSClient 
 WebRequest 
 NetworkStream 
 HTMLParser 
 够了吗? 
不仅仅一个文件! 
 <script src… 
 <link href… 
 <img src… 
 <iframe src… 
 何时开始下载它们?
下载 
 <head>中的<script>和<link> 
 服务器端Response.Flush() 
 <body>中的<script> 
 document.write 
 new Image().src = … 
 defer VS async
下载 
 资源优先级 
 link[rel=stylesheet] / script 
 object / img / iframe 
 link[rel=prefetch] 
 脚本依赖 
 下载阻塞VS 执行阻塞 
 并行度 
 服务器压力VS 客户端效率 
http://www.otakustay.com/browser-strategy-loading-external-resource/
下载 
 Socket重用 
 Connection: keep-alive 
 Content-Length 
 Transfer-Encoding: chucked 
 正确性保证 
 Content-MD5 
 断点续传 
 Accept-Range 
 Content-Range
下载 
 BS的精髓– 缓存 
 验证型缓存 
 Last-Modified & If-Modified-Since / If-Unmodified-Since 
 ETag & If-Match / If-None-Match 
 If-Range 
 非验证型缓存 
 Cache-Control 
 Expires 
 缓存失效 
 Vary / Via / Date / Age 
http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
下载 
 缓存年龄计算 
 age_value – Age响应头的值 
 date_value – Date响应头的值 
 request_time – 发起请求的本地时间 
 response_time – 收到响应的本地时间 
 now – 当前本地时间 
 apparent_age = max(0, response_time - date_value); 
 corrected_received_age = max(apparent_age, age_value); 
 response_delay = response_time - request_time; 
 corrected_initial_age = corrected_received_age + response_delay; 
 resident_time = now - response_time; 
 current_age = corrected_initial_age + resident_time;
下载 
 缓存过期计算 
 freshness_lifetime = 
 使用max-age时为max-age的秒数 
 使用Expires时为(Expires - Date) 
 response_is_fresh = (freshness_lifetime > current_age) 
 max-age=0 VS no-cache 
 max-age=0 – 要求浏览器向服务器验证缓存 
 no-cache – 要求浏览器向服务器请求全新内容
下载 
 输入– 资源URI 
 输出– HTML字符流 
 HTTP的超链接特性注定资源之间有关联的依赖 
 外部资源位置、类型不同影响下载时机 
 Response.Flush对下载的影响 
 缓存机制复杂但完善
解析 
• Token List 
• Normalized 
Token List 
•DOM Tree 
Demo
解析 
 只有这些? document.write
解析 
 输入– HTML字符流 
 输出– DOM Tree 
 HTML无法用自顶向下或自底向上的方法解析 
 过程– 序列化-> 转义处理-> 标签匹配 
 脚本执行会增加解析的回溯 
 DOM操作回溯至标签匹配过程 
 document.write回溯至序列化过程
CSS计算 
 元素– 匹配样式 
 div>div>div>div>div…>div { color: red; } 
 dom.parentNode istanceof HTMLDivElement && 
div.parentNode.parentNode instanceof HTMLDivElement && 
div.parentNode.parentNode.parentNode instanceof … 
 问题 
 DOM很大,对内存造成压力 
 每个元素生成一个StyleObject浪费内存 
 查找元素匹配的样式消耗时间和CPU 
 如果是div div div div … div呢?
CSS计算 
 Webkit – 特定条件下样式共享 
 鼠标状态(:hover / down / clicked)相同 
 没有id 
 标签名相同 
 class名称相同 
 attribute均相同 
 链接状态(:link / :visited)相同 
 聚焦状态(:active / :focus)相同 
 不能匹配属性选择器 
 没有内联样式 
 没有兄弟选择器(+ / :first-child / :last-child / …)
CSS计算 
 Firefox 
 Rule Tree + Style Context Tree 
Style sheets & 
rules 
Rule tree 
Style context 
tree 
DEMO 
https://developer.mozilla.org/en/Style_System_Overview
CSS计算 
 Map 
 { string: [ selector, selector, … ] } 
 以最右选择器为依据 
 匹配 
 查找id map 
 查找class map 
 查找tag map 
 确定selector完全匹配 
 遍历general map
CSS计算 
 #title { … } 
 p.error { … } 
 input[type=radio] { … } 
 #nav li~li a { … } 
 .fix-clear * { … } 
 <div class=“fix-clear”> 
<span id=“title” class=“error”>Erorr Occurred!</span> 
</div>
CSS计算 
 CSS层级(优先级) 
 来源层级 
 浏览器UA样式 
 用户样式 
 作者样式 
 作者样式+ !important 
 用户样式+ !important 
 样式层级 
 1,1,1,1算法 
 inline(0/1), count(id), count(attribute), count(tag) 
 ul#nav ol li.red
CSS计算 
DOM Tree 
document 
html 
head 
meta title 
body 
h1 p 
Render Tree 
html 
viewport 
scroll 
block 
body 
block 
h1 
block 
text 
p 
block 
text 
https://developer.mozilla.org/en/Mozilla_Style_System_Documentation
CSS计算 
 元素没有渲染对象 
 head / meta / script 
 元素有多个渲染对象 
 html / li 
 select / input[type=file] 
 通过CSS改变渲染对象 
 display: none 
 ::before / ::after
CSS计算 
 输入– DOM Tree 
 输出– Render Tree 
 目标– 内存优化、匹配效率优化 
 方法– 样式共享、选择器索引 
 DOM Tree !== Render Tree
布局 
 流布局 
 display: inline / inline-block / block 
 float: left / right 
 clear: left / right / both 
 position: static / relative / absolute / fixed 
 HTML三条流 
 文档流、浮动流、定位流 
 其它因素 
 display: list-item 
 display: run-in 
http://www.w3.org/TR/css3-box/#the-lsquo
布局 
 table布局 
 display: table / inline-table / table-row-group / table-header- 
group / table-footer-group / table-row / table-column- 
group / table-column / table-cell / table-caption 
 div VS table – 流布局VS table布局
布局 
 坐标系– 左上角为0,0点,右|下为正坐标 
 布局是递归过程 
 流布局可自左向右、自上而下进行,流中靠后的元素不会影响 
流中靠前的元素的布局(无回溯) 
 table布局需要回溯才能够完成(知道每一个单元格的大小,才 
能完成整个布局) 
 反对table布局的原因– 回溯对渲染的影响 
DEMO
布局 
 全局Reflow 
 整个Render Tree全部重新计算布局 
 全局布局样式变更– body { font-size: 12px; } / 添加新样式表 
 窗口大小变化 
 局部Reflow 
 仅标识为needLayout的渲染元素计算布局 
 Render Tree中插入新的渲染元素 
 渲染元素属性变化 
 Reflow会引起另一个Reflow – Reflow导致滚动条位置变化
布局 
 同步Reflow 
 全局Reflow通常同步进行 
 读取offsetWidth/offsetHeight等属性 
 异步Reflow 
 局部Reflow通常异步进行 
 FireFox:Reflow任务进入线程Queue,任务调度器负责执行 
 Webkit:定时器遍历Render Tree,布局所有needLayout对象 
 Reflow任务可合并,一次脚本执行过程中多个样式修改仅做一 
次Reflow,但是有阀值 
DEMO
布局 
 父元素确定自己的宽度 
 开始遍历子元素 
 指定子元素渲染器的x/y属性 
 判断子元素是否需要布局,调用layout函数 
 累计所有子元素的width/padding/border/margin,计算自己 
的宽度 
 同时考虑availWidth / sizing-box / min-width / max-width 
 将needLayout改为false
布局 
 文字布局 
 text-align: justified 
 white-space: nowrap / pre / pre-wrap 
 overflow: hidden / visible 
 换行计算 
 每行一个line-box负责渲染 
 当需要换行时,通知父元素,父元素创建新的line-box并重新布局 
 换行算法与文化相关 
 英文单词不能断开 
 中文标点不能在行首
布局 
 有流式布局和table布局2种 
 table布局需要回溯,流式布局通常不需要 
 Reflow的分类 
 触发类型上– 全局和局部 
 执行类型上– 同步和异步 
 布局过程递归进行 
 坐标系为左上角0,0点,右、下为正方向
渲染 
 transform / filter / z-index / color / visibility… 
 Reflow VS Repaint – display: none VS visibility: hidden 
 渲染顺序(CSS2): 
 background color 
 background image 
 border 
 children 
 outline 
http://www.w3.org/TR/CSS21/zindex.html
渲染 
 Firefox – display list 
 找到Render Tree中在指定Repaint区域内的渲染对象 
 Render Tree的渲染对象经Stacking Order排序后生成 
 [background(A), border(A), border(B), outline(A)] 
 避免多次遍历Render Tree 
 [background(A, B), border(A, B), outline(A, B)] 
 完全不可见的元素不可被加到display list中 
 display: none; 
 opacity: 0;
渲染 
 Webkit – rectangle storage 
 Repaint前先将原有矩形内容保存为位图 
 计算重绘后的矩形内容的位图 
 比对2个位图,仅绘制差异部分 
 Chrome的Repaint在独立进程中 
 进程间通过事件进行传递和响应 
 绘制监听器负责监听重绘事件,把事件委托给Render Tree的根 
 Repaint永远从Render Tree的根开始,遍历并找到需要重绘的节 
点,对节点调用重绘(递归至叶子节点)

Inside the browser

  • 1.
    Inside the Browser 浏览器渲染原理 张立理 otakustay@gmail.com
  • 2.
  • 3.
    Summary  浏览器的工作过程  资源下载  HTML解析  CSS计算  布局  渲染  浏览器对细节的具体优化手段  Firefox  Webkit / Chrome
  • 4.
  • 5.
    下载 一个HTTP库能够搞定? DNSClient  WebRequest  NetworkStream  HTMLParser  够了吗? 不仅仅一个文件!  <script src…  <link href…  <img src…  <iframe src…  何时开始下载它们?
  • 6.
    下载  <head>中的<script>和<link>  服务器端Response.Flush()  <body>中的<script>  document.write  new Image().src = …  defer VS async
  • 7.
    下载  资源优先级  link[rel=stylesheet] / script  object / img / iframe  link[rel=prefetch]  脚本依赖  下载阻塞VS 执行阻塞  并行度  服务器压力VS 客户端效率 http://www.otakustay.com/browser-strategy-loading-external-resource/
  • 8.
    下载  Socket重用  Connection: keep-alive  Content-Length  Transfer-Encoding: chucked  正确性保证  Content-MD5  断点续传  Accept-Range  Content-Range
  • 9.
    下载  BS的精髓–缓存  验证型缓存  Last-Modified & If-Modified-Since / If-Unmodified-Since  ETag & If-Match / If-None-Match  If-Range  非验证型缓存  Cache-Control  Expires  缓存失效  Vary / Via / Date / Age http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
  • 10.
    下载  缓存年龄计算  age_value – Age响应头的值  date_value – Date响应头的值  request_time – 发起请求的本地时间  response_time – 收到响应的本地时间  now – 当前本地时间  apparent_age = max(0, response_time - date_value);  corrected_received_age = max(apparent_age, age_value);  response_delay = response_time - request_time;  corrected_initial_age = corrected_received_age + response_delay;  resident_time = now - response_time;  current_age = corrected_initial_age + resident_time;
  • 11.
    下载  缓存过期计算  freshness_lifetime =  使用max-age时为max-age的秒数  使用Expires时为(Expires - Date)  response_is_fresh = (freshness_lifetime > current_age)  max-age=0 VS no-cache  max-age=0 – 要求浏览器向服务器验证缓存  no-cache – 要求浏览器向服务器请求全新内容
  • 12.
    下载  输入–资源URI  输出– HTML字符流  HTTP的超链接特性注定资源之间有关联的依赖  外部资源位置、类型不同影响下载时机  Response.Flush对下载的影响  缓存机制复杂但完善
  • 13.
    解析 • TokenList • Normalized Token List •DOM Tree Demo
  • 14.
  • 15.
    解析  输入–HTML字符流  输出– DOM Tree  HTML无法用自顶向下或自底向上的方法解析  过程– 序列化-> 转义处理-> 标签匹配  脚本执行会增加解析的回溯  DOM操作回溯至标签匹配过程  document.write回溯至序列化过程
  • 16.
    CSS计算  元素–匹配样式  div>div>div>div>div…>div { color: red; }  dom.parentNode istanceof HTMLDivElement && div.parentNode.parentNode instanceof HTMLDivElement && div.parentNode.parentNode.parentNode instanceof …  问题  DOM很大,对内存造成压力  每个元素生成一个StyleObject浪费内存  查找元素匹配的样式消耗时间和CPU  如果是div div div div … div呢?
  • 17.
    CSS计算  Webkit– 特定条件下样式共享  鼠标状态(:hover / down / clicked)相同  没有id  标签名相同  class名称相同  attribute均相同  链接状态(:link / :visited)相同  聚焦状态(:active / :focus)相同  不能匹配属性选择器  没有内联样式  没有兄弟选择器(+ / :first-child / :last-child / …)
  • 18.
    CSS计算  Firefox  Rule Tree + Style Context Tree Style sheets & rules Rule tree Style context tree DEMO https://developer.mozilla.org/en/Style_System_Overview
  • 19.
    CSS计算  Map  { string: [ selector, selector, … ] }  以最右选择器为依据  匹配  查找id map  查找class map  查找tag map  确定selector完全匹配  遍历general map
  • 20.
    CSS计算  #title{ … }  p.error { … }  input[type=radio] { … }  #nav li~li a { … }  .fix-clear * { … }  <div class=“fix-clear”> <span id=“title” class=“error”>Erorr Occurred!</span> </div>
  • 21.
    CSS计算  CSS层级(优先级)  来源层级  浏览器UA样式  用户样式  作者样式  作者样式+ !important  用户样式+ !important  样式层级  1,1,1,1算法  inline(0/1), count(id), count(attribute), count(tag)  ul#nav ol li.red
  • 22.
    CSS计算 DOM Tree document html head meta title body h1 p Render Tree html viewport scroll block body block h1 block text p block text https://developer.mozilla.org/en/Mozilla_Style_System_Documentation
  • 23.
    CSS计算  元素没有渲染对象  head / meta / script  元素有多个渲染对象  html / li  select / input[type=file]  通过CSS改变渲染对象  display: none  ::before / ::after
  • 24.
    CSS计算  输入–DOM Tree  输出– Render Tree  目标– 内存优化、匹配效率优化  方法– 样式共享、选择器索引  DOM Tree !== Render Tree
  • 25.
    布局  流布局  display: inline / inline-block / block  float: left / right  clear: left / right / both  position: static / relative / absolute / fixed  HTML三条流  文档流、浮动流、定位流  其它因素  display: list-item  display: run-in http://www.w3.org/TR/css3-box/#the-lsquo
  • 26.
    布局  table布局  display: table / inline-table / table-row-group / table-header- group / table-footer-group / table-row / table-column- group / table-column / table-cell / table-caption  div VS table – 流布局VS table布局
  • 27.
    布局  坐标系–左上角为0,0点,右|下为正坐标  布局是递归过程  流布局可自左向右、自上而下进行,流中靠后的元素不会影响 流中靠前的元素的布局(无回溯)  table布局需要回溯才能够完成(知道每一个单元格的大小,才 能完成整个布局)  反对table布局的原因– 回溯对渲染的影响 DEMO
  • 28.
    布局  全局Reflow  整个Render Tree全部重新计算布局  全局布局样式变更– body { font-size: 12px; } / 添加新样式表  窗口大小变化  局部Reflow  仅标识为needLayout的渲染元素计算布局  Render Tree中插入新的渲染元素  渲染元素属性变化  Reflow会引起另一个Reflow – Reflow导致滚动条位置变化
  • 29.
    布局  同步Reflow  全局Reflow通常同步进行  读取offsetWidth/offsetHeight等属性  异步Reflow  局部Reflow通常异步进行  FireFox:Reflow任务进入线程Queue,任务调度器负责执行  Webkit:定时器遍历Render Tree,布局所有needLayout对象  Reflow任务可合并,一次脚本执行过程中多个样式修改仅做一 次Reflow,但是有阀值 DEMO
  • 30.
    布局  父元素确定自己的宽度  开始遍历子元素  指定子元素渲染器的x/y属性  判断子元素是否需要布局,调用layout函数  累计所有子元素的width/padding/border/margin,计算自己 的宽度  同时考虑availWidth / sizing-box / min-width / max-width  将needLayout改为false
  • 31.
    布局  文字布局  text-align: justified  white-space: nowrap / pre / pre-wrap  overflow: hidden / visible  换行计算  每行一个line-box负责渲染  当需要换行时,通知父元素,父元素创建新的line-box并重新布局  换行算法与文化相关  英文单词不能断开  中文标点不能在行首
  • 32.
    布局  有流式布局和table布局2种  table布局需要回溯,流式布局通常不需要  Reflow的分类  触发类型上– 全局和局部  执行类型上– 同步和异步  布局过程递归进行  坐标系为左上角0,0点,右、下为正方向
  • 33.
    渲染  transform/ filter / z-index / color / visibility…  Reflow VS Repaint – display: none VS visibility: hidden  渲染顺序(CSS2):  background color  background image  border  children  outline http://www.w3.org/TR/CSS21/zindex.html
  • 34.
    渲染  Firefox– display list  找到Render Tree中在指定Repaint区域内的渲染对象  Render Tree的渲染对象经Stacking Order排序后生成  [background(A), border(A), border(B), outline(A)]  避免多次遍历Render Tree  [background(A, B), border(A, B), outline(A, B)]  完全不可见的元素不可被加到display list中  display: none;  opacity: 0;
  • 35.
    渲染  Webkit– rectangle storage  Repaint前先将原有矩形内容保存为位图  计算重绘后的矩形内容的位图  比对2个位图,仅绘制差异部分  Chrome的Repaint在独立进程中  进程间通过事件进行传递和响应  绘制监听器负责监听重绘事件,把事件委托给Render Tree的根  Repaint永远从Render Tree的根开始,遍历并找到需要重绘的节 点,对节点调用重绘(递归至叶子节点)

Editor's Notes

  • #3 因此,开发人员已经不局限与针对浏览器提供的功能进行编程,往往会涉及到对HTML / CSS / javascript容器的实现
  • #4 课程内容
  • #5 浏览器基本工作过程 Q:是不是需要等整个HTML下载完成才能够开始解析?
  • #6 下载只是一个HTTP库的问题吗?
  • #7 影响下载的因素 1、<head>中的内容会优先下载,Chrome对head中的内容串行下载,阻塞body中的内容 2、Flush提供浏览器提前解析、渲染的机会,BigPipe的核心机制,Flush的本质是Transfer-Encoding: chucked 3、IE6-7阻塞下载、解析、执行,其他浏览器并行下载、顺序执行 4、DOM树的同步,以及IE的诡异BUG 5、唯一不连接DOM树也可以发起请求的元素,响应的Content-Type非图片则只接收1个字节 6、defer保证执行时机,async不保证 defer在DOM Tree构建完成之后,DOMContentLoaded事件触发之前
  • #9 Q:Keep-alive时如何确定已经传送完成? 下载优化方面其它细节举例
  • #12 Q:max-age=0和no-cache有什么区别?
  • #13 总结
  • #14 HTML解析的过程
  • #15 各种因素的影响,可能导致解析过程中要进行回溯 Q:什么样的script会影响到Tokenizer
  • #16 总结
  • #17 正常思维下,匹配样式只需要写2层循环即可完成 但是完成不代表可用,存在着种种问题
  • #18 Q:一般来说怎么解决内存占用过大的问题
  • #19 Firefox采取了更为复杂的解决方案,在CSS计算的过程中引入了2棵树
  • #20 Q:一般来说,需要频繁查询的场合,如何优化查询的效率
  • #21 Q1:将以上选择器对应到4个Map中 Q2:红色部分的span元素的样式匹配过程 该部分为“如何优化选择器匹配”,不要当作“如何写优化的选择器”的参考,写CSS永远以语义和结构为最优先,不要在意性能
  • #22 Q:ul#nav ol li.red的CSS仅重是多少 inline – 0 id – 1 attribute – 1 tag – 3 output – 0,1,1,3
  • #23 DOM Tree和Render Tree的关系
  • #24 Q:什么情况下出现该现象
  • #25 总结
  • #26 list-item: generate a block box and a inline box for the list marker A run-in box behaves as follows: If the run-in box contains a block box, the run-in box becomes a block box. If a sibling block box (that does not float and is not absolutely positioned) follows the run-in box, the run-in box becomes the first inline box of the block box. A run-in cannot run in to a block that already starts with a run-in or that itself is a run-in. Otherwise, the run-in box becomes a block box.
  • #27 并不是用table标签才是table布局
  • #28 有人会说ol下的li前的数字标识,随着数字位数的增加,也会因为宽度增大影响布局 用示例说明,li的marker-box是设定为普通的流式布局中的box,默认样式为overflow: hidden
  • #29 Reflow在触发类型上的分类
  • #30 Reflow在执行环境上的分类 经常有人提到在遍历数组的时候要缓存length属性,个人是比较反感这种说法的,事实上缓存length属性对性能的影响微乎其微 但是有一些属性,缓存与不缓存却能产生极大的性能差距,看一下offsetWidth属性缓存对性能的影响
  • #31 布局计算流程
  • #32 元素内部文字布局过程
  • #33 总结 Q:style标签在head和body中的区别?
  • #34 影响渲染的CSS属性 渲染的顺序
  • #35 Firefox对渲染过程的优化
  • #36 Webkit对渲染过程的优化 Chrome的特殊性