浏览器内部工作原理

目录:
1.介绍
2.渲染引擎
3.解析与DOM树构建
4.渲染树构建
5.布局
6.绘制
7.多态变化
8.渲染引擎的线程
9.CSS2可视模型

1.介绍

浏览器可以被视为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到,从你在地址栏输入google.com到你看到google主页过程中都发生了什么。

浏览器的主要功能是将用户选择的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform Resource Identifier统一资源标识符)来指定所请求资源的位置,在网络一章有更多讨论。

HTML和CSS规范中规定了浏览器解释html文档的方式,由W3C组织对这些规范进行维护,W3C是负责制定web标准的组织。

浏览器的主要构成(High Level Structure)
浏览器的主要组件包括:
1.用户界面 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。
2.浏览器引擎 用来查询及操作渲染引擎的接口
3.渲染引擎 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。
4.网络 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。
5.UI后端 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
6.JS解释器 用来解释执行JS代码。
7.数据存储 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。

在这里插入图片描述

需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。

2.渲染引擎

渲染引擎的职责就是渲染,即在浏览器窗口显示所请求的内容。

默认情况下,渲染引擎可以显示html、xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读插件,可以显示PDF格式,将由专门一章讲解插件及扩展,这里只讨论渲染引擎最主要的用途——显示应用CSS之后的html及图片。

渲染引擎简介

渲染主流程(The main flow)

渲染引擎首先通过网络获得请求文档的内容,通常以8K分块的方式完成。

下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树—>构建render树—>布局render树—>绘制render树

在这里插入图片描述

渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。

Render树由一些包含颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。

Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有html都解析完成之后再去构建和布局render树。它是解析一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

在这里插入图片描述

3.解析与DOM树构建(Parsing and DOM tree construction)

解析(Parsing - general)

既然解析式渲染引擎中一个非常重要的过程,我们将稍微深入的研究它。

解析一个文档即将其转换为具有一定意义的结构——编码可以理解和使用的东西。解析的结果通常式表达文档的节点树,称为解析树或语法树。

例如,解析“2+3-1”这个表达式,可能返回这样一棵树。

在这里插入图片描述

文法(Grammars)

解析基于文档依据的语法规则——文档的语言或格式。每种可被解析的格式必须具有词汇及语法规则组成的特定的文法,称为上下文无关文法。人类语言不具有这一特性,因此不能被一般的解析技术所解析。

解析器—词法分析器(Parse—Lexer combination)

解析可以分为两个子过程——语法分析及词法分析

词法分析就是将输入分解为符号,符号式语言的词汇表——基本有效单元的集合。对于人类语言来说,它相对于我们字典中出现的所有单词。

语法分析指对语言语言语法规则。

解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符。

在这里插入图片描述

解析过程是迭代的,解析器从词法分析器处取到一个新的符号,并试着用这个符号匹配一条语法规则,如果匹配了一条规则,这个符号对应的节点将被添加到解析树上,然后解析器请求另一个符号。如果没有匹配到规则,解析器将在内部保存该符号,并从词法分析器取下一个符号,指导所有内部保存的符号能够匹配一项语法规则。如果最终没有找到匹配的规则,解析器将抛出一个异常,这意味着文档无效或是包含语法错误。

转换(Translation)

很多时候,解析树并不是最终结果。解析一般在转换中使用——将输入文档转化为另一种格式。编译就是个例子,编译在将源码编译为机器码的时候,先将源码解析为解析树,然后将该树转化为一个机器码文档。

在这里插入图片描述

DOM

输出的树,也就是解析树,是由DOM元素及属性节点组成的。DOM是文档对象模型的缩写,它是html文档的对象表示,作为html元素的外部接口供js等调用。

树的根是“document”对象。

DOM和标签基本是一一对应的关系,例如,如下的标签:

<html>
<body>
<p>
Hello DOM
</p>
<div><img src=”example.png” /></div>
</body>
</html>

将会被转换为下面的DOM树:

在这里插入图片描述

这里所谓的树包含了DOM节点是说树是由DOM接口的元素构建而成的,浏览器使用已被浏览器内部使用的其他属性的具体表现。

处理脚本及样式表的顺序(The order of processing scripts and style sheets)

脚本

web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在htmll4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行另一个线程。

预解析(Speculative parsing)

Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载

样式表(Style sheets)

样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显示这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而Chrome只在当脚本试图访问某些可能被加载的样式表所影响的特定样式属性时才阻塞这些脚本。

4.渲染树构建(Render tree construction)

当Dom树构建完成时,浏览器开始构建另一课树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。

Firefox将渲染树中的元素称为frames,WebKit则用render或渲染对象来描述这些元素。

一个渲染对象知道怎么布局及绘制自己及它的children。

渲染树和Dom树的关系(The render tree relation to the DOM tree)

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

还有一些Dom元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。

一些渲染对象和所对应的Dom节点不在树上相同的位置,例如,浮动和绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。

在这里插入图片描述

创建树的流程(The flow of constructing the tree)

Firefox中,表述为一个监听Dom更新的监听器,将frame的创建委派给Frame Constructor,这个构建器计算样式(参看样式计算)并创建一个frame。

Webkit中,计算样式并生成渲染对象的过程称为attachment,每个Dom节点有一个attach方法,attachment的过程时同步的,调用新节点的attach方法将节点插入到Dom树中。

处理html和body标签将构建渲染树的根,这个根渲染对象对应被css规范称为containing block的元素——包含了其他所有块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,Firefox称它为viewPortFrame,webkit称为RenderView,这个就是文档所指向的渲染对象,树中其他的部分都将作为一个插入的Dom节点被创建。

样式计算(Style Computation)

创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到。

样式包括各种来源的样式表,行内样式元素及html中的可视属性(例如bgcolor),可视化属性转化为css样式属性。

样式表来源于浏览器默认样式表,及页面作者和用户提供的样式表——有些样式是浏览器用户提供的(浏览器允许用户定义喜欢的样式,例如,Firefox中,可以通过在Firefox Profile目录下放置样式表实现)。

5.布局(Layout)

当渲染对象被创建并添加到树中,它们并没有位置和大小,计算这些值的过程称为layout或reflow。

Html使用基于流的布局模型,意味着大部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性,所以布局可以在文档中从右向左、自上而下的进行。也存在一些例外,比如html tables。

坐标系统相对于根frame,使用top和left坐标。

布局是一个递归的过程,由根渲染对象开始,它对应html元素,布局继续递归的通过一些或所有的frame层级,为每个需要几何信息的渲染对象进行计算。

根渲染对象的位置是0,0,它的大小是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.parent渲染对象决定它的宽度
2.parent渲染对象读取children,并
a.放置child渲染对象(设置它的x和y)。
b.在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度。
c.parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用。
d,将dirty标识设置为false。

Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。

Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。

6.绘制(Painting)

绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件,这在UI的章节有更多的介绍。

全局和增量

和布局一样,绘制也可以是全局的——绘制完整的树——或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,变现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘给对象(往往还包括它的children)。

绘制顺序

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

Firefox显示列表

Firefox读取渲染树并为绘制的矩形创建一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。

用这样的方法,可以使重绘时只需查找一次树,而不需要多次查找——绘制所有的背景、所有的图片、所有的border等等。

Firefox优化了这个过程,它不添加会被隐藏的元素,比如元素完全在其他不透明元素下面。

WebKit矩形存储

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

7.动态变化

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

8.渲染引擎的线程

渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主线程,Chrome中这是tab的主线程。

网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。

事件循环

浏览器主线程是一个事件循环,它被设计为无限循环以保持执行过程的可用,等待事件(例如layout和paint事件)并执行它们。下面是Firefox的主要事件循环代码。

while (!mExiting)
NS_ProcessNextEvent(thread);

9.CSS2可视模型(CSS2 visual module)

画布The Canvas

CSS盒模型

定位策略Position schema

Box类型

Bock box

Inline box

定位Position

Relative

Floats

Absolute和Fixed

Layered representation

更加详细介绍可以参考文章:https://kb.cnblogs.com/page/129756/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值