webkit源码解读系列-普及篇

webkit源码解读系列-普及篇前言我们在做移动端的 web 开发的时候 无时无刻不在和浏览器打交道 而目前我们使用大多数手机浏览器都是基于 webkit 进行包装完成的

欢迎大家来到IT世界,在知识的湖畔探索吧!

前言

我们在做移动端的web开发的时候,无时无刻不在和浏览器打交道,而目前我们使用大多数手机浏览器都是基于webkit进行包装完成的。在这种背景下,对我们开发人员来说,如果能更多的熟悉webkit的内部机制,了解基优缺点,那么就能更好解决当前我们遇到的各种问题,包括,标准支持,兼容性,性能解决方案等。本文的目的就在于将通过对webkit的结构及相关解析流程上进行简述与分解来帮助我们更好的理解这一开源框架。

Webkit是什么?都有谁在用?

首先,引用webkit官方地话说就是“webkit并不是浏览器,而是一套针对web内容的渲染引擎”。Webkit所包含的WebCore排版引擎和JsCore引擎,均是从KDE的KHTML及KJS引擎衍生而来,但是webkitr 的优势在于它的高效稳定,兼容性好,且源码清晰,易于维护。

当前基于手机的浏览器中,大多数浏览器都是使用webkit做为渲染引擎,包括safari,, uc ,safari,chrome(js采用v8引擎)。特别是中国大多数的手机浏览器均是将webkit进行包装山寨而成。除此之外,由于手机的本身内存及cpu等硬件的限制,导致基于手机的web实现,需要更多的考虑的兼容及性能问题。所以熟悉webkit对我们来说,也是一个门必选的功课。

Webkit的结构

webkit源码解读系列-普及篇
欢迎大家来到IT世界,在知识的湖畔探索吧!

通过上图我们可以看到,Webkit主要包括

  1. Webkit(向外部开放的API)
  2. Wekit2 为webkit提供的支持单独进程模型的api层,将web内容的处理与应用程序分离,分别运行在不同的进程当中.
  3. Webcore(html,css,dom等元素的渲染引擎)
  4. JavaScriptCore(javascript解释及执行引擎)
  5. Platform 主要是一些第三方的开源的类库,包括网络解析libcurl,图片处理的libjpeg,libpng,xml处理libxml,libxslt,小型数据库sqlite,除此之外还有一个二维的图像处理cairo等.
  6. WTF主要是与操作系统本身的交互,调用操作系统接口为上层应用服务

Webkit的加载流程

webkit源码解读系列-普及篇

在页面的加载过程中,首先,需要通过网络请求加载页面及图片,脚本等附属资源。然后由解释器对页面资源进行语法分析,最终生成一棵dom树,在生成dom树的同时,会对生成的dom节点通过render生成相应的render树,遇到script时,也会通过javascriptcore对script进行解析执行.最终,再通过图像处理模块,生成可视的页面. 在此节,我们将主要来着重介绍webcore中的渲染流程.

Webkit主要包含两个加载管道,一个是用来加载documents到frames,另一个是用来加载附属资源(如image,脚本).

  • FrameLoader 主要负责将documents加载到frames中,每当点击一个超链接的时候,FrameLoader都会产生一个DocumentLoader,并将其状态设置为”policy”,然后FrameLoader会等待webkit 客户端做出一个如何来对待当前加载的决定。通常情况下,客户端都会告诉FrameLoader,将当前的加载视为一个”navigation”. 一旦客户告诉Frameloader,当前加载是一个“navigation”,FrameLoader就会将Document Loader的状态更新为”provisional”,从而触发网络请求,并且等待一个是把当前的网络请求视为下载还是新的document加载的决定.

Document Loader会创建一个叫做MainResourceLoader的对象.MainResourceLoader的主要工作就是与platform的网络库通信。之所以将MainResourceLoader从DcoumentLoader中分离出来主要有两个原因:一是MainResourceLoader将Document Loader从复杂,繁琐ResourceHandler返回的信息处理中隔离开来。二是将MainResourceLoader的生命周期从Document Loader的生命周期中脱离出来.

一旦加载系统从网络中接收到了足够的信息来做出判断,认为当前的加载的资源就是一个document的呈现,FrameLoader就会将DocumentLoader状态更新为“commited”。然后Frame就会将新的document展现出来.

  • DocLoader主要是用来加载图片,脚本等附属信息.我们下面以加载图片为例来说明一下DocLoader的流程。

DocLoader首先会去查看cache里是否有对当前请求图片的缓存.如果有,那么会立即从加载图片(而且,为了加快效率,缓存的图片一般都是已经经过解码的)。反之,DocLoader会创建一个新的CacheImage对象来呈现图片.CacheImage对象会要求Loader去发出网络请求,同DocumentLoader,DocLoader会生成一个SubResourceLoader。这个对象与前文所提到的MainResourceLoader有相同的作用.

Dom元素渲染流程

Domrender

在webkit中,dom元素会被解析成Dom Tree.每一个元素(包括文字)都会有一个树上的节点对应.根节点就是document.

Dom tree中的每个结点都对应一个叫做Renderobject 的类,这个类的作用就是用来描述当前dom节点的样式,如高宽border等。而Renderobject同样会组成棵树被称为RenderTree。每当DomTree结点中的一个节点创建后,RenderTree中就会有一个相应的结节被创建。因此DomTree与RenderTree从始至终会保持相同的结构.

当前有一个问题,RenderObject只能决定单个Dom元素的样式,但是没办法决定Dom元素的位

webkit源码解读系列-普及篇

置及排版.

因此每一个RenderObject 又分别直接或者通过其父节点间接对应着一个RenderLayer.

在同一个坐标空间中RenderObject通常属于同一个RenderLayer. RenderLayer节点和Render 节点不是一一对应的,而是一对多的关系。那么在什么情况下RenderObject节点需要建立新的RenderLayer节点呢?

(1)当前节点是DOM树的根节点,也就是document

(2)显式设置css位置

(3)有透明效果的对象

(4)节点有overflow, alpha或者反射等效果

(5)使用了css filter

(6)Canvas 2D和3D(WebGL)

(7)Video节点对应的RenderObject对象

一个RenderLayer建立后,其所在的RenderObject对象的所有后代均包含在该RenderLayer,除非这些后代需要建立自己的RenderLayer.而后代的RenderLayer的父亲就是自己最近的祖先所在的不同的RenderLayer.

每个RenderLayer对应的Render节点内容均会绘制在该RenderLayer所对应的层次上(或者内部存储结构上)。不同的RenderLayer可以共享同一个内部存储结构,也可以有各自的后端存储,这取决于不同的移植。在软件渲染下,通常各个RenderLayer的内容都绘制在同一块后端存储上。在GPU硬件加速下,某些RenderLayer可能有自己独立的后端存储,而后通过合成器来把这些不同的后端合成在一起,最终形成网页的可视化内容.

RenderLayer在创建RenderObject对象的时候,如果需要,也会同时被创建,当然也有可能在执行Javascript代码时,更新页面的样式从而需要新创建一个RenderLayer.

RenderLayer的子节点会以降序的方式在两个列表中存放,一个是存放zindex小于0或者低于当前节点的zindex的子节点,一个存放高于当前节点zindex的子节点.

Dom元素的显示

Dom元素的显示有两种方式,一种是基于软件策略,一种是基于硬件加速。默认情况下是使用软件策略。我们将在下面介绍软件策略(硬件策略较复杂,在此略去)

dom元素的rending是按照从后到前的顺序进行的。首先,会判断当前的renderlayer是否存在相互交叉的情况,如果有,那么,会先找到zindex低于当前layer的列表,然后将其rending出来,再rending当前的layer,最后rending zindex大于当前layer的列表。

webkit源码解读系列-普及篇

Renderobject会根自己的所具有的信息通过GraphicContext绘制成bitmap存放到共享内存中.然后browser process会通过系统的windows api将bitmap绘制到指定的窗口或者tab上去.

JavaScriptCore(Javascript执行过程)

JavaScriptCore主要模块

Assembler

对各种开发环境的集成,如arm,Macro.x86(区别)

BytecodeCompiler

主要说明如何生成字节码,以及字节码的编译情况

Bytecode

详细说明了字节码的具体内容,比如字节码类型以及具体的计算过程

Interpreter

解析当前执行的脚本文件

JIT

即时编译(just-in-time compilation),又称动态转译,是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。即时编译前期的两个运行时理论是字节码编译和动态编译。该目录提供的功能是对脚本文件进行及时编译。

Parser

对脚本文件进行词法和语法分析

Runtime

运行时

JavaScript的解释

webkit源码解读系列-普及篇

JavaScript底层是通过c++来实现的。Js本身的面向对象性并不强,主要是依靠原型链来实现对象和继承。

事实上每一个Javascript对象都对应着一个c++的对象。c++中对Javascript实现却充分表现出来了对象及继承的特点。c++有一个基类叫做JsObject,c++中所有对Javascript的实现对象,都继承了JsObject.

通过上图,我们可以看到有两个很重要的属性proto_和prop_. proto_用来指向一个JSObject对象,即指明,一个Javascript对象的原型是谁。而prop_则是一个键值对的列表,主要说明当前的Javascript对象有哪一些属性。

针对prop_ 和proto_我们来举个简单的例子来说明它的作用。我们写了如下一段js代码来获取testObject这个对象下的value属性

testObject.value

那么在runtime中,就会查找testObject对应的prop_中是否存在value这样一个健值对,有的话返回。如果没有那么会根据testObject的proto_去找到它所对应的原型对象的prop_中是存在,直到查找完所有的proto_及对应的prop_.

事实上属性表的实现比较复杂,除了对象具有一般的属性外,还可以通过 ClassInfo来提供一些初始化信息及类型信息。解释器会区分对象一般的属性及ClassInfo提供的属性。如上例,如果value是一般的javascript的属性,那么会直接去键值对列表中查找,如果是由ClassInfo提供,那么会调用callRuntimeMethod作为统一入口去查找.

webkit源码解读系列-普及篇

JavaScriptDOM元素的交互

Webkit是通过绑定(binding)机制来实现Javascrpt和dom的互操作的.binding主要涉及到三方面:一是javascript对dom的操作,二是dom触发javascript的执行,三是javascript的执行环境,即在创建语法树之前,如何初始化。(代码Webcore/bindings/下)

上边我们说到javascript在c++中都有一个继承自JsObject的对象.为了能让javascript操作dom,那么我们就要实现一个针对dom元素的并且继承自Jsobject的对象,这个对象,不仅仅包括javascript的一般的属性和方法,它仍然需要支持对dom元素api的调用从而来实现对dom 的操作(创建,访问期属性,调用成员函数)。那么我们就称这一类对象为”影子对象”,而且在其构造函数执行之时,我们需要初始化一些信息,包括对dom的document类暴露出来的接口.

实际上,在运行时,并不是每一个dom元素都会有一个影子对象,影子对象只是在需要时才会建立。比如,我们要用js操作一个input组件获取其值,那么只有在调用document.getElementById(“”)之时,影子对象才会被建立。而且影子对象,并没有做较多的事情,它的主要作用是对dom 的document类的接口做了一层包装和调用。所以影子对象并不会对性能产生较大影响,主要的还是在控制dom元素对象的数量上.

Javascript响应dom事件

Dom触发javascript是通过事件监听(eventlistener)来完成的.dom对象会给js的影子对象提供add或者 remove Eventlistener的接口,这个应该是我们都比较了解的。前面提到,在解释时会对javascript建立一棵语法树,其中监听器就对应着其中的一棵子树,然后只需要将此子树的根节点注册到对应的dom节点上就行了。

DOM中在执行javascript语句之前,创建JSDOMWindow对象,它和DOM中的DOMWindow对象相对应。从 DoM进入javascript解释器的入口。创建过程做两件事:一是初始化大量的基本类型的原型(prototype),前面提到的原型链机制,二是创建了一个全局javascript对象JSDOmWindow,从而可以使得解释器能从javascript进入到dom

JavaScript的垃圾回收

当一旦有新的对象被new出来时,webkit都会做一次回收,来保证有足够的内存分配给新的对象。

webkit的垃圾回收目前主要是使用引用计数器。即判断当前对象有没有被引用,如果没有那么清理此对象。但是webkit的垃圾回收基于引用计数做了一定的优化,主要包括:protect和mark。

有一些全局对象,有可能当前并没有被引用,但是将来可能会,所以这部分对象不应该被回收,那么我就要在回收前放到protect集合中,还有一种是正处于当前局部区域中的栈对象集合,他们同样不应该回收。那么我们会在回收前针对protect及栈集合中的对象做mark,将其标识为不可回收对象。

除此之外,javascript针对dom元素生成的一些对象,在binding 中提供了另一种机制来帮助mark的顺利执行.binding中维护了一张dom元素与jsdom的映射表。只要表中的dom存在,那么在回收时,都应该将相应的jsdom做mark.

总结

从上述的各点,我们可以看到webkit具有较清晰的接口及结构,并且具有较强的跨平台性。而且根据上面的分析,我们也能针对Javascript及dom展示的性能进行一定优化。主要是减少资源的请求,减少dom节点的创建,充分利用缓存机制.

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/110619.html

(0)
上一篇 2025年 2月 22日 下午12:00
下一篇 2025年 2月 22日 下午3:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信