浏览器工作原理

1.基础

1.1 浏览器高层结构

 

注意:现代浏览器例如chrome Rendering Engine 上运行了很多程序,每个tab是不同的进程

1.2 Rendering Engine

不同的浏览器使用不同的RE

1.3 the main flow

渲染引擎会在从网络层获取到请求文件(数据),这通常在8kB

  1. 解析html文件并将元素转变成DOM节点到‘content tree’(dom tree)
  2. 从elements和cc文件中解析css信息,综合css rules+dom tree,创建render tree,render tree包含一个举行上有可视信息,等待被罗列在界面上
  3. 通过‘layout’进程,给每一个节点明确的位置信息,来罗列在屏幕上
  4. 开始绘制render tree

这是一个渐进的过程,对于用户来说,绘制rendertree应该越快越好,他不会等到所有html被解析时才构建和布局渲染树,部分content会被解析展示,当进程正在等待网络信息的时候

1.3.1 main flow example

 2.流程步骤

2.1解析

1.解析一个文件指,把结构解析成代码可以使用的,解析结果通常是一个象征着文件结构的树,他叫解析树或者是句法树。

2.解析可以分成两个过程,词法解析和句法解析,词法解析是把句子打散,句法解析是指把打散的token根据规则组合

3.最后符合句法的token会被加到解析树中

4.解析树通过转化会最后变成机器码文件

2.2HTML 解析

1.html解析器的作用就是将html文件最后解析成解析树,将HTML字节流转换成DOM结构

2.html解析树所遵守的词法句法是由w3c来规定的
解析流程:

  1. 通过分词器将字节流转换为Token;
  2. 将Token解析为DOM节点,并将DOM节点添加到DOM树中。

2.3CSS解析

2.3.1处理脚本和样式表的顺序

1.脚本:web模型是同步的,用户们希望脚本在当解析器到达script标签的时候就尽快执行,当脚本是外部资源,我们就需要等待脚本获取完再执行解析,所以解析进程会被阻塞,人们可以通过在脚本标签中加入defer 关键字来防止parse被阻塞,使脚本异步加载,这样解析和执行就是使用不同的进程。

2.

 很多浏览器做了这样的优化,当在执行脚本的时候,其他空闲的解析进程会找其他需要被加载到资源,并网络加载,这个时候资源会被平行的链接并且速度提升很快,但是只能应像脚本,样式表,和图片,不可以应用dom树。

3.样式表

当样式表没有被阻塞,脚本在执行时有些需要依赖于格式表的数据,这样会出现问题,所以有一些浏览器在样式表没有加载完全时,会阻塞脚本的加载

4.render tree 结构

回流和重绘不是在一个地方的,他们有先后顺序

每一个renderer(切片)都代表了一个长方形区域,通常对应一个节点的cssbox,它包含一些几何信息,例如,宽高和位置

box类型受display这个属性值影响

可以看到代码里的switch中是按display类型来决定render何种类型的box

2.5.渲染树与dom树关系

renderers是和dom元素相关的,但不是一对一的关系,不可见dom元素就不会被插入到render树中,比如head元素,display值被赋为none也是同样的。

dom元素可以与可见对象联系起来,有一些元素又复杂的结构以至于不能被描述在一个简单的句型中,例如select里面可以下拉的菜单就是会被加尔u到额外的renderers中

一些render对象和dom元素对象但是并不在一棵树相同的位置,例如float和absolutely的元素就是out of flow,在render流之外的,在树的不同部分,映射到一个真实的帧

2.4风格计算(css部分性能)

构建rendertree需要计算每一个render对象的可见属。

css结构比较复杂还有一些语法,会在解析时带来一些问题

  1. 样式表是一个巨大的结构,有很多的风格属性,这会造成存储问题
  2. 在不做优化的情况下,为每一个元素寻找适配规则会造成性能问题
  3. 规则中有一些复杂的级联规则,还有规则之前优先级(等级制度)

如何解决问题?

1.共享样式数据:在一些特殊情况,这些node时sibling或cousins关系及

  1. 元素必须在一个形同mouse state
  2. 另一个元素应该有一个id
  3. tag名字应该匹配
  4. class 属性应该匹配
  5. 一组匹配属性应该相同

2.firefox rule tree

firebox有两个额外的树来使央视计算更简单,规则树和样式内容树,

样式内容树会包含最终的数值,这些数值是应用所有正确的匹配规则指令并执行操作他们从逻辑到具体数值,例如%会变成绝对单位

这些树不会在起初就进行计算,但是计算路径会被加入到树中

3.看看树是如何我保存我们的工作的?

  1. 划分成结构:就是继承,你不自己定义就继承
  2. 使用规则书计算上下文样式:

2.5渐进式过程

webkit会用一个flag来标记是否所有top level样式表都被加载完了,如果attach时没有加载完全,会使用占位,并且标记在文件中,然后他们会被重新计算再加载

 2.4布局

当renderer被创建成功并加入到renderer tree,他还没有位置和大小,计算这些值被叫做布局或回流

html使用基于流的布局模型,意味着大多数情况下只要一次遍历就能计算出几何信息,‘’in the flow‘’之后的元素通常不会影响之前的元素(处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,),所以layout可以从左到右,从上到下,遍历文档,有一个例外:html tables可能需要多余一个pass

坐标系基于root frame,使用的是上坐标和左坐标

(布局是一个递归的过程,它开始于根renderer,所谓<html>元素,布局便利所有层次结构,为每一个renderer计算他需要的几何信息。)

布局是一个 递归 的过程。它从根渲染器(对应于 HTML 文档的 <html> 元素)开始,然后递归遍历部分或所有的渲染器层次结构,每一个渲染器都会通过调用其需要进行布局的子代的 layout 方法,为每一个需要计算的渲染器计算几何信息。任何有可能改变元素位置或大小的样式都会触发这个 Layout 事件。

根renderer的位置是0,0,他的平面(dimention)是基于视口的(viewport),浏览器可见的窗口

所有renderer都有布局或者回流的方法,每一个renderer在需要布局时调用相关接口

2.4.1Dirty bit system(脏位系统)

为了不因为每一个微小的改变去做全局回流,浏览器使用脏位系统,A renderer that is changed or added marks itself and its children as "dirty": needing layout.

There are two flags: "dirty", and "children are dirty" which means that although the renderer itself may be OK, it has at least one child that needs a layout.

为避免对所有细小更改都进行整体布局,浏览器采用了一种 Dirty 位系统。如果某个渲染器发生了更改,或者将自身及其子代标注为 dirty,则需要进行布局。类似于脏检测。

有 dirty 和 children are dirty 两种标记方法。children are dirty 表示尽管渲染器自身没有变化,但它至少有一个子代需要布局。

2.4.2Global and incremental layout

  • 全局布局:指触发了整个渲染树范围的布局,渲染器的 全局样式更改 或者 屏幕大小调整 都会触发全局布局

布局可以被在全部render tree上出发,这是全局布局,之所以发生这个的原因有:

  1. 一个样式改变影响所有的renderer,就像字体大小改变
  2. 屏幕被重置大小
  • 增量布局:采用增量方式,也就是只对 dirty 渲染器进行布局(这样可能存在需要进行额外布局的弊端)

布局可以是增量的,只有dirty renderer会被 布置(this can cause some damage which will require extra layouts)

当renderer是 dirty时会触发增量布局(异步的),例如当来自网络的额外内容添加到 DOM 树之后,新的渲染器附加到了渲染器中

2.4.3 异步与同步布局

  • 增量布局是异步布局,firefox将增量布局的‘reflow’命令加入队列,调度程序会触发这些命令的批量执行
  • webkit 也有一个计时器用来执行增量布局,树被便利并且dirty renderers 会触发布局
  • 脚本请求样式信息,像“offsetHeight”能触发异步增量布局
  • 全局布局通常被同步触发
  • 有时布局作为回调函数在初始化布局之后被触发因为一些属性,比如滑动位置变化

2.4.4 优化

当布局因为resize或在renderer里面的position变化(or not size)被触发,render size会被从内存中获取,不用重新计算

在一些例子里,只有子树被修改,布局不从根renderer开始,这会发生在一些本地改变并且不会影响周围的情况下,就像在text field中插入text(否则每次按键都会触发从根节点开始的布局)

2.4.5布局过程

  1. 父renderer 决定他的宽度
  2. 父renderer依次处理child 并且
    1. 放置子renderer(x,y)
    2. 如果有必要,调用子渲染器的布局(如果子渲染器是 dirty 的,或者这是全局布局,或者出于其他某些原因),这会计算子渲染器的高度
  3. 父渲染器根据子渲染器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父渲染器的父渲染器使用
  4. 将其父 dirty 位设置为 false

2.4.6宽度测量

renderer的宽度是用容器的宽度,renderer的宽度,margin和broder算出来的

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

wbekit:将由 Webkit 计算如下(BenderBox 类,calcWidth 方法):

  • 容器的宽度取容器的 availableWidth 和 0 中的较大值。availableWidth 在本例中相当于 contentWidth,计算公式如下:
  • clientWidth() - paddingLeft() - paddingRight();

    clientWidth 和 clientHeight 表示一个对象的内部(除去边框和滚动条)。

  • 元素的宽度是 width 样式属性。它会根据容器宽度的百分比计算得出一个绝对值。

  • 然后加上水平方向的边框和补白。

2.4.7:换行

如果渲染器在布局过程中需要换行,会立即暂停布局,并告知其父代需要换行。父代会创建额外的渲染器,并对其调用布局。

2.5绘制

在绘制阶段,render tree 被遍历,并且renderer的paint方法被调用来在屏幕上展示内容,绘制使用ui基础组件

2.5.1全局和增量

  • 全局:所有树都被绘制
  • 增量:一些renderer通过一些不会影响全部tree的方式,这些改变了的renderer使在屏幕上的矩形无效,这造成os把他们看作“dirty”区域并且产生绘制事件,os把这些区域聚集起来,chrome中更复杂因为renderer比起main进程在不同的进程里,chrome对某些内容模拟os行为。

2.5.2绘制顺序

CSS2 规范 定义了绘制流程的顺序。绘制的顺序其实就是元素进入 堆栈样式上下文 的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。块呈现器的堆栈顺序如下:

  1. 背景颜色(background-color
  2. 背景图片(background-image
  3. 边框(border
  4. 子代
  5. 轮廓(outline

2.5.3

2.5.4动态改变

 在样式发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘;元素的位置改变后,会对该元素及其子元素(可能还有同级元素)进行布局和重绘;添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大 <html> 元素的字体)会导致缓存无效,使得整个渲染树都会进行重新布局和绘制。

2.5.5渲染引擎进程

 

3.css2 视觉模型

3.1canvas

3.2 盒模型

display决定和盒子的类型

block:生成block 盒子

inline:生成一个或者多个inline盒子

none:没有盒子生成

3.3position scheme

  1. nromal:这个对象会根据他在文档中的位置定位,这意味着在render tree的位置和他在dom树中的位置一样并且根据box类型和维度定位
  2. float:对象一开始和nromal一样被放置,然后他们可以移动
  3. absolute:对象被放在rendertree中的不同地方比起在dom树

位置方案在position和float里面设置

  • 静态且相对=> 就是nromal flow
  • 绝对固定就是 absolute定位

在静态定位中,没有定义位置,而是使用默认定位。在其他方案中,指定位置:top,bottom,left,right。

box被放置的方式取决于:

  • box 类型
  • box (平面)
  • 定位方案
  • 额外的信息(eg:图像大小,屏幕大小)

3.3.1box 类型

  • block:形成一个block,在浏览器窗口上他有他自己的矩形区域
  • inline box:没有自己的block,但是包含块内
  • 块 一个接一个地垂直格式化。内联(inline)是水平格式化的。

内联框放在行(line)或“行框”(lineblock)内。 这些线至少与最高的盒子一样高,但可以更高,当盒子对齐“基线”时 - 意味着元素的底部与底部以外的另一个盒子的点对齐。 如果容器宽度不够,内联会放在几行上。 这通常发生在段落中。

3.3.2:定位方案

  • relative:像往常一样定位,然后移动所需的增量(delta)。
  • float:浮动框移动到一行的左侧或右侧。有趣的特点是其他盒子围绕它流动
  • Absolute and fixed:无论正常流程如何,都可以准确定义布局。该元素不参与正常流程。尺寸是相对于容器的。在fixed中,容器就是视口。即使文档滚动,fixed框也不会移动!

 3.4层级表示

层级结构由z-index CSS属性指定,他表示的box的第三个维度,这个位置沿着z轴变化

这些boxes被分成栈(堆叠上下文),在每一个栈最后一个元素会被第一个绘制,离user更近,

如果重叠,最前面的元素将隐藏前一个元素。

堆栈根据 z-index 属性进行排序。具有“z-index”属性的框形成一个本地堆栈。视口具有外部堆栈。

 z-index 属性更高,因此在根框持有的堆栈中更靠前。

 

 参考:How browsers work

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值