WEBKIT内核源码分析系列

原帖地址:http://www.cnblogs.com/qq499194341/articles/2891954.html     作者空间有很多webkit基础的文章,有时间看看

目录:

1.WebKit内核源码分析(一)

摘要:在分析内核的时候,Frame是首当其冲的一个类,本文将分析Frame类的代码。

2.WebKit内核源码分析(二)

摘要:FrameLoader类负责一个Frame的加载,在Frame的流程中起到非常重要的重要,同很多组件都有交互,本文将分析FrameLoader类的代码。

3.WebKit内核源码分析(三)

摘要:Page类就是用来对应这样的页面请求。Page类是WebKit中非常重要的一个类,它就像内核对外的一个聚合器。

4.WebKit内核源码分析(四)

摘要:本文介绍 WebCore 中 Loader 模块是如何加载资源的,分主资源和派生资源分析 loader 模块的类关系。

5.WebKit内核源码分析(五)

摘要:本文分析WebKithtml的解析过程,DOM节点树的建立。


 

WebKit内核源码分析(一)

摘要:本系列通过分析WebKit的源代码,试图分析WebKit的内核设计架构,模块之间的关系,分析的时候以Qt的移植为参考,涉及移植的东西不多,主要还是以内核为主。在分析内核的时候,Frame是首当其冲的一个类,本文将分析Frame类的代码。

1. 描述

Frame类是WebCore内核同应用之间联系的一个重要的类。它有点像设计模式中的Façade,将内核的各个不同的零配件组装在了一起,但又不是Façade,因为用户很多时候还是要直接去操作里面的组件。除了设计上的考虑,Frame还有语法上的意义,它对应于Page里面的帧。

2. 类结构

 

1) FrameTree对象用来协助管理父帧和子帧的关系,常见的比如main frame之中有iframe元素,就会调用FrameLoaderClientQt::createFrame来产生子帧,产生的子帧会通过appendChild添加到主帧的树状结构中。Frame通过FrameTree对象,可以方便地访问它的父帧,子帧,兄弟帧。

2) 维护FrameLoader对象用来完成frame的加载,FrameLoader是一个非常重要的类,后续进行进一步的分析。

3) 维护NavigationScheduler对象用来管理页面跳转调度(比如重定向,meta refresh等)。

4) DOMWindow用来管理同DOM相关的事件、属性和消息。

5) FrameViwe类用于Frame的排版。

6) Frame文档解析后,对每一个tag或者attr,会有对应的dom节点关联,Document类用来管理这些dom节点。不同的文档类型继承出不同的子类,比如HTML文档对应子类HTMLDocument,XML文档对应于XMLDocument。

7) SciptController对象,脚本控制器,用来管理脚本的执行和操作。

8) Editor对象用来处理页面的编辑相关的操作,比如拷贝,粘贴,输入等,Editor对象,它同Page类的EditorClient对象紧密合作。和EditorClient的关系就如同Page同Frame的关系。

9) SelectionController用来管理Frame中的选取操作。

10) AnimationControlle,动画控制,控制动画的播放,暂停,继续(同HTML video标签是否有关系?)

11) EventHandler,事件处理对象,这里的对象主要是同上层应用也就是用户参与的事件,比如鼠标事件,按键事件(快捷键等),滚动事件,resize事件等。这是一个浏览器外壳经常需要打交道的类。

3. 主要接口

3.1 Create

static PassRefPtr<Frame> create(Page*,HTMLFrameOwnerElement*,FrameLoaderClient*)

 

描述: 调用Frame构造函数,创建出Frame对象。有两个地方会创建Frame对象,一是要加载一个新的页面请求,这个时候会创建main frame,一是在加载子帧的时候,通过FrameLoaderClientQt的createFrame接口,创建子帧对应的Frame对象,在第一种情况中,HTMLFrameOwnerElement参数为NULL,第二种情况传子帧的父元素。在一个tab页内,main frame会重用。

调用系列:

->QwebPage::setView
->QwebPage::setViewportSize
->QwebPage::mainFrame
->QwebPagePrivate::createMainFrame
->QwebFrameData::QwebFrameData
->Frame::create

->FrameLoader::finishedLoading
->……
->HTMLDocumentParser::append
->……
->HTMLTreeBuilder::processToken
->……
->HTMLElementBase::openURL
->SubFrameLoader::requestFrame
->……
->FrameLoaderClientQt::creatFrame
->QwebFrameData::QwebFrameData
->Frame::create

3.2 createView

void createView(const IntSize&, const Color&, bool, const IntSize&, bool,ScrollbarMode = ScrollbarAuto, bool horizontalLock = false,ScrollbarMode = ScrollbarAuto, bool verticalLock = false)

 

描述:创建出FrameView对象,以用于之后的排版。应用调用这个函数的时候需要传入同排版有关的一些信息,如初始视窗大小,背景色,滚动条模式等。创建出FrameView以后,即调用Frame::setView设置成当前的FrameView。

函数调用系列:

->FrameLoader::commitProvisionalLoad
->FrameLoader::transitionToCommitted
->FrameLoaderClientQt::transitionToCommittedForNewPage
->Frame::createView

3.3 setDocument

void setDocument(PassRefPtr<Document>)

 

描述:设置同Frame关联的Document对象(一般是DocumentWriter创建的)。

函数调用系列:

->QWebFrame::QwebFrame
->QwebFramePrivate::init
->Frame::init
->FrameLoader::init
->DocumentWriter::begin
->Frame::setDocument

->DocumentLoader::receivedData
->DocumentLoader::commitLoad
->FrameLoaderClientQt::committedLoad
->DocumentLoader::commitData
->DocumentWriter::setEncoding
->DocumentWriter::willSetEncoding
->FrameLoader::receivedFirstData
->DocumentWriter::begin
->FrameLoader::clear
->Frame::setDocument

3.4 init

void Frame::init()

 

描述:Frame对象初始化,会调用FrameLoader::init初始化FrameLoader对象。
调用系列:
->QWebFrame::QWebFrame
->QwebFramePrivate::init
->Frame::init

3.5 setPageAndTextZoomFactors

void setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)

 

描述:设置页面放大因子和文字放大因子。在网页缩放或者改变网页字体大小的时候调用。


 

WebKit内核源代码分析(二)

摘要:本系列通过分析WebKit的源代码,试图分析WebKit的内核设计架构,模块之间的关系,分析的时候以Qt的移植为参考,涉及移植的东西不多,主要还是以内核为主。FrameLoader类负责一个Frame的加载,在Frame的流程中起到非常重要的重要,同很多组件都有交互,本文将分析FrameLoader类的代码。

1. 概述

顾名思义,FrameLoader是一个Frame的loader,它的作用就是为客户提供一个下载一个Frame的一系列接口。这里的客户指的是类的客户,比如Frame类,间接客户是上层应用,比如qwebframe。
从它的定义看,最容易想到的是一个load接口,用来将一个frame load下来。任何一个页面至少都需要一个mainframe,因此一个页面的下载一般就是从load一个mainframe开始。
在load frame的过程中,通过FrameLoaderClient接口将load过程的不同阶段告知客户。
FrameLoader通过setDocumentLoader相当于把load的工作委托给了DocumentLoader类。
FrameLoader同DocumentLoader是has-a的关系。一般在load的时候创建DocumentLoader。Frame调用DocumentLoader的startLoadingMainResource开始load frame。

2. 类关系

 

1)Frame和FrameLoader是contain-a的关系,在Frame的构造函数中调用FrameLoader的构造函数,调用时传入参数Frame指针和FrameLoaderClient指针。
2)Frame有可能有子Frame,所以维护SubFrameLoader对象m_subframeLoader来管理子Frame的load。Frame可以对应xml document,也可对应html document,等等。跟Document相关的子resource的load不在FrameLoader的职责范围内。
3)包含一个DocumentWriter类对象m_writer,当Frame的数据load finish的时候,将数据传给DocumentWriter类,进行下一步的处理(比如解码)
4)FrameLoader维护了三个DocumentLoader对象,分别对应于不同的阶段,m_policyDocumentLoader对应于收到用户load调用,进行policy check阶段,m_provisionalDocumentLoader对应于policy check通过以后,Frame数据还没有到来之前,它会负责startLoadingMainResource的调用。m_documentLoader则是Frame第一个数据到来以后使用的DocumentLoader,这个时候,前一个主Frame的DocumentLoader已经不能再用(user agent开始白屏,刷掉前一个页面的显示)。
5)包含一个HistoryController对象,用于操作历史记录相关的接口,保存或者恢复Document和View相关的状态,维护前进后退队列,以实现前进后退功能,前进后退本质上是同Page对象关联的,FrameLoader通过HistoryController操作m_backFowardController对象
6)包含一个ResourceLoadNotifier对象,主要用于同ResourceLoader及FrameLoaderClient打交道,可以理解为ResourceLoader有事件变化或者发生的时候,通知FrameLoader的一个手段
7)包含一个SubframeLoader对象,当FrameLoader下载的Document有子帧需要请求的时候(比如HTMLDocument中解析到iframe 元素),用来处理子帧请求
8)将FrameLoader的状态封装到FrameLoaderStateMachine中,这个状态同FrameState不同,FrameState倾向于判断Frame涉及的Document的下载状态,是出于发起状态(Provisional),还是出于已经收到响应但不全(CommittedPage),还是响应收全的状态,倾向于同http相关。而FramLoaderStateMachine倾向于同DocumentLoader相关,用来描述FrameLoader处理DocumentLoader的节点,是处于已经创建,还是显示的状态。
9)PolicyChecker主要用来对FrameLoader进行一些校验。包括三种校验:NewWindow,Navigation和Content。NewWindow对应于浏览器需要新开一个tab页或窗口的时候,Navigation对应于一个页面请求发起的时候,Content校验对应于收到数据以后(判断Mime type等),PolicyChecker通过提供对应的接口,由FrameLoaderClient来对这些请求进行校验,以确定是否允许继续,或者需要其它的动作。

3. 主要接口

Frame::init()

 

功能:FrameLoader的初始化
函数调用系列
->QWebFrame::QWebFrame(QwebPage* parent,QWebFrameData *frameData)
->QWebFramePrivate::init(QWebFrame* qwebframe,QWebFrameData* frameData)
->Frame::init()
->FrameLoader::init()
说明:主要做一些自身的初始化工作,比如初始化状态机,Sandbox Flags,创建DocumentLoader被设置为Policy DocumentLoader和Provisional DocumentLoader,调用DocumentLoader和documentWriter等的接口进行初始化操作

FrameLoader::commitProvisionalLoad()

 

功能:提交Provisional阶段下载的数据
函数调用系列:
->DocumentLoader::finishLoading
->DocumentLoader::commitIfReady
->FrameLoader::commitProvisionalLoad

或者
->ResourceLoader::didReceiveData
->MainResourceLoader::addData
->DocumentLoader::receiveData
->DocumentLoader::commitLoad
->DocumentLoader::commitIfReady
->DocumentLoader::commitProvisionalLoad
说明:这个接口主要的操作是将Provisional DocumentLoader设置成DocumentLoader,因为已经收到数据,所以FrameState也会跃迁到FrameStateCommittedPage。还有历史记录,PageCache相关的操作。另外,这个接口会间接调用FrameLoaderClientQt::transitionToCommittedForNewPage,通过Frame::createView创建出FrameView来。

Frame::finishedLoading()

 

功能:frame请求网络加载完成的时候调用此接口
函数调用系列
->ResourceLoader::didFinishLoading
->MainResourceLoader::didFinishLoading
->FrameLoader::finishedLoading
->FrameLoader::init()
说明:检查是否有网络错误,告诉DocumentLoader和DocumentWriter下载完成,以便进行后续操作(提交数据,解析)。

FrameLoader::finishedParsing()

 

功能:解析完成调用此接口
函数调用系列
->DocumentWritter::end
->….
->Document::finishParsing
->….
->Document::finishedParsing
->FrameLoader::finishedParsing
FrameLoader::load(const ResourceRequest& request,bool lockHistory)
功能:加载一个frame请求,Frame请求相关的数据,封装成ResourceRequest传入。
函数调用系列:一般由应用触发调用
说明:这个接口调用FrameLoaderClientQt::createDocumentLoader创建出DocumentLoader,并以此DocumentLoader为Policy Document Loader,进入Policy check流程。


 

WebKit内核源代码分析(三)

摘要:浏览器的请求一般是以页面请求为单位,当用户通过网址栏输入一个url,浏览器就开始一个页面请求。而一个页面请求可能包含有一到多个页面子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。Page类是WebKit中非常重要的一个类,它就像内核对外的一个聚合器。
关键词:WebKit内核源代码,WebCore,Page,Frame,WebKit架构

1. 概述

浏览器的请求一般是以页面请求为单位,当用户通过网址栏输入一个url,浏览器就开始一个页面请求。而一个页面请求可能包含有一到多个页面子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。前进后退,导航,编辑,右键菜单,设置,Inspector等这些用户参与的动作,大部分是同Page相关的。而标记语言解析、排版、加载则更多地同Frame相关。
我们通过几个图来看下Qt移植中Page类同应用之间的关系。

QWebPage通过QWebPagePrivate维护Page类的指针,并在QWebPagePrivate的构造函数中实例化Page对象。QWebPage类通过之后的createMainFrame调用实例化QwebFrame,而在QwebFrameData的构造函数中,以Page指针为参数调用了Frame::create 创建出Frame对象。

Page类通过组合其它类的方式,实现了很多功能,Page类本身并没有多少代码。

2. 类关系

2.1 PageGroup

PageGroup并不是用来对Page进行管理的,而是设计用来将一些具有共同的属性或者设置的Page编成组的,以方便对这些属性进行管理。目前这样的属性包括localStorage的属性,IndexDB,User Script,User StyleSheet等。最常见的同PageGroup相关的操作是维护已访问链接(如addVisitedLink等接口)。根据地瓜的理解,假设WebKit内核之上架设多个应用(浏览器是一个应用),比较可能的是,一个应用独立一个PageGroup。这里同多tab页没有关系,多tab页属于同一个PageGroup。地瓜曾在mailing group上就这个问题咨询过,一位RIM的同学给我举了一个例子,比如一个基于WebKit的邮件程序,一方面他可能调用基于webkit的browser来显示网页,另外他本身也基于webkit来显示一些邮件,这两个之间的setting有很大可能不一样,他们就使用不同的PageGroup。

PageGroup中有这个Group已经安装并且使用的User Script和User StyleSheet的集合,一般在网页解析完毕后,这些User Script和User StyleSheet会插入到Document中。
PageGroup中还维护了Local Storage和Index DB相关的设置,比如它们的Path,上限等,通过GroupSettings类实现。
PageGroup创建以后,每次创建一个新的Page对象,会通过addPage接口加入到这个PageGroup的m_pages中。

每次有导航行为发生的时候,会调用addVisitedLink来将url加入到已访问链接中。如果浏览器要跟踪已访问的接口,则在初始化的时候必须调用PageGroup::setShouldTrackVisitedLinks,且参数为true。此处shouldTrackVisitedLinks是一个静态的全局变量,也就是说,所有应用维护一样的行为(一个应用将其设置为false会影响到其它同样基于此核的应用)?
Page类中维护了PageGroup的指针,并提供了group接口,这是个lazy接口,如果m_group不存在,会调用InitGroup来创建一个。对于Page类来说,如果没有设置GroupName,则在初始化的时候会生成一个空GroupName的PageGroup,由m_singlePageGroup维护,并把指针赋给m_group,如果以非空的名字调用了setGroupName,则会重新创建PageGroup,此时这个PageGroup由m_group来维护。

2.2 Setting

WebCore中的设置相关的类,浏览器应用的不少配置、选项同该类相关,Qt移植中,应用在创建Page对象后,会根据Page::settings来实例化QwebSetting。

2.3 Chrome

原生窗口接口类,参考地瓜写的”WebKit中的Chrome和ChromeClient”。

2.4 其它

SelectionController:负责管理Page中的选取操作,绝大部分选取操作是基于Frame的,只在Frame的Selection为空的时候,对焦点游标的绘制工作才会使用到Page类的SelectionController。
SharedGraphicsContext3D:共享3D图形上下文,为了优化2D显示而加入。在加速的2D canvas中,引入的DrawingBuffer的概念,SharedGraphicsContext3D提供了createDrawingBuffer来创建DrawingBuffer。
DragController:拖拽控制器。Chrome的超级拖拽功能同这个有关?地瓜会在以后对此进行求证。
FocusController:焦点控制器。考虑到焦点会在各个frame之间切换,所以由Page类维护焦点控制器最合适不过。
ContextMenuController:右键下拉菜单控制器。
InspectorController:Inspector控制器,浏览器中的很多开发工具都同这个类相关。
GeolocationController:定位定位服务控制器。
DeviceMotionController:设备移动控制器
DeviceOrientationController:设备方向控制器
SpeechInputClient:语音输入Client。
ProgressTracker:进度跟踪。
BackForwardController:前进后退操作控制。
Frame:一个Page由至少一个主帧和若干个其它子帧构成。
HistoryItem:历史记录。
PluginData:插件相关,未来可能同PluginDatabase类合并。主要是初始化Plugin的信息。
PluginHalter:用来控制Plugin的停止和重新开始。
RenderTheme:这个类提供了控件的渲染和绘制接口。Qt移植中,RenderThemeQt是RenderTheme接口的具体实现。
EditorClient:同编辑功能相关,比如拷贝、剪切、删除等操作。


 

WebKit 内核源代码分析 ( 四 )

摘要:本文介绍 WebCore 中 Loader 模块是如何加载资源的,分主资源和派生资源分析 loader 模块的类关系。
关键词: WebKit,Loader,Network,ResouceLoader,SubresourceLoader

一、类结构及接口

Loader 模块是 Network 模块的客户。 Network 模块提供指定资源的获取和上传功能,获取的资源可能来自网络、本地文件或者缓存。对不同 HTTP 实现的适配会在 Network 层完成,所以 Loader 接触到的基本上是同 OS 和 HTTP 实现无关的 Network 层接口。

如上是 Loader 和 NetWork 之间的类关系图, ResourceHandleClient 是 ResourceHandle 的客户,它定义一系列虚函数,这些虚函数是 ResouceHandle 的回调,继承类实现这些接口。
ResouceHandleClient 的接口同网络传输过程息息相关,一般为某一个网络事件对应的回调。下面是其中的一些接口。

复制代码
// 一般情况下,在发起网络请求前调用,可以设置特定的 Http头部,比如 user agent 等,在重定向请求的时候,也会自动调用

void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&)

// 上传数据的时候,在 TCP wrtie 事件的时候,向对方发送数据的时候调用, loader 可以根据这个回调显示上传进度。

void didSendData(ResourceHandle*, unsigned long long /*bytesSent*/, unsigned long long /*totalBytesToBeSent*/)

// 收到第一个响应包,此时至少 http 的部分头部已经解析(如status code ), loader 根据响应的头部信息判断请求是否成功等。

void didReceiveResponse(ResourceHandle*,const ResourceResponse&)

// 收到 HTTP 响应数据,类似 tcp 的 read 事件,来 http 响应数据了, Network 的设计机制是来一段数据上传一段数据。

void didReceiveData(ResourceHandle*, const char*, int,int /*lengthReceived*/)

// 加载完成,数据来齐。

void didFinishLoading(ResourceHandle*, double /*finishTime*/)

// 加载失败

void didFail(ResourceHandle*, const ResourceError&)

// 要求用户鉴权

void didReceiveAuthenticationChallenge(ResourceHandle*,const AuthenticationChallenge&)
复制代码

 

WebCore 把要加载的资源分成两类,一类是主资源,比如 HTML 页面,或者下载项,一类是派生资源,比如 HTML 页面中内嵌的图片或者脚本链接。这两类资源对于回调的处理有很大的不同,比如,同样是下载失败,主资源可能需要向用户报错,派生资源比如页面中的一张图下载失败,可能就是图不显示或者显示代替说明文字而已,不向用户报错。因此有了 MainResourceLoader 和 SubresourceLoader 之分。它们的公共基类 ResourceLoader 则完成一些两种资源下载都需要完成的操作,比如通过回调将加载进程告知上层应用。

ResourceLoader 通过 ResourceNotifier 类将回调传导到 FrameLoaderClient 类。

主资源的加载是立刻发起的,而派生资源则可能会为了优化网络,在队列中等待 ( 这里的立刻发起是 loader 层面的,不是 Network 层面的 ) 。 ResourceScheduler 这个类就是用来管理资源加载的调度。主要调度对象就是派生资源,会根据 host 来影响资源加载的先后顺序。

主资源和派生资源的加载还有一个区别,主资源目前是没有缓存的,而派生资源是有缓存机制的。这里的缓存指的是 Resouce Cache ,用于保存原始数据(比如 CSS , JS 等),以及解码过的图片数据,通过 Resource Cache 可以节省网络请求和图片解码的时候。不同于 Page Cache , Page Cache 存的是 DOM 树和 Render 树的数据结构,用来在前进后退的时候快速显示页面。

二、加载流程

下图是加载 html 页面时,一个正常的加载流程。

三、主资源加载过程

1. DocumentLoader 调用 MainResourceLoader::load 向 loader 发起请求
2. 调用 MainResourceLoader::loadNow
3. 调用 MainResourceLoader::willSendRequest
4. 调用 ResourceLoader::willSendRequest, 将 callback 通过 ResourceNotifier 传导给 FrameLoaderClient 。 Client 可以在回调中操作 ResourceRequest ,比如设置请求头部。
5. 调用 PolicyChecker::checkNavigationPolicy 过滤掉重复请求等
6. loader 调用 ResourceHandle::create 向 Network 发起加载请求
7. 收到第一个 HTTP 响应数据包 ,Network 回调 MainResourceLoader::didReceiveResponse ,主要处理 HTTP 头部。
8. 调用 PolicyChecker:: checkContentPolicy, 并最终通过 FrameLoaderClient 的 dispatchDecidePolicyForMIMEType 判断是否为下载请求(存在 "Content-Disposition"http 头部)
9. 调用 MainResourceLoader::continueAfterContentPolicy ,根据 ResourceResponse 检测是否发生错误。
10. 调用 ResourceLoader::didReceiveResponse ,将 callback 通过 ResourceNotifier 传导给 FrameLoaderClient 。
11. 收到 HTTP 体部数据,调用 MainResourceLoader::didReceiveData
12. 调用 ResourceLoader::didReceiveData ,将 callback 通过 ResourceNotifier 传导给 FrameLoaderClient
13. 调用 MainResourceLoader::addData
14. 调用 DocumentLoader::receivedData
15. 调用 DocumentLoader::commitLoad
16. 调用 FrameLoader::commitProvisionalLoad , FrameLoader 从 provisional 状态跃迁到 Committed 状态
17. 调用 FrameLoaderClientQt::committedLoad
18. 调用 DocumentLoader::commitData ,启动 Writer 对象来处理数据( DocumentWriter::setEncoding , DocumentWriter::addData )
19. 调用 DocumentWriter::addData
20. 调用 DocumentParser::appendByte
21. 调用 DecodedDataDocumentParser::appendBytes 对文本编码进行解码
22. 调用 HTMLDocumentParser::append ,进行 HTML 解析
23. 数据来齐,调用 MainResourceLoader::didFinishLoading
24. 调用 FrameLoader::finishedLoading
25. 调用 DocumentLoader::finishedLoading
26. 调用 FrameLoader::finishedLoadingDocument ,启动 writer 对象接收剩余数据,重复 19-22 进行解析
27. 调用 DocumentWriter::end 结束接收数据(调用 Document::finishParsing )
28. 调用 HTMLDocumentParser::finish

四、派生资源加载流程

在派生资源的加载中, SubresourceLoader 更多起到的是一个转发的作用,通过它的 client(SubresourceLoaderClient 类)来完成操作。



各个加载阶段的处理在 SubresourceLoaderClient 的派生类 CachedResourceRequest,Loader,IconLoader 中完成。 Client 会创建 SubresourceLoader 。请求发起阶段, ResourceLoadScheduler 负责对 SubresourceLoader 进行调度。

Document 类会创建 CachedResourceLoader 类的对象 m_cachedResourceLoader, 这个类 ( 对象 ) 提供了对 Document 的派生资源的访问接口 requestImage , requestCSSStyleSheet , requestUserCSSStyleSheet,requestScript,requestFont,requestXSLStyleSheet,requestLinkPrefetch。为了实现这些接口,CachedResourceLoader 需要创建 CachedResourceRequest 对象来发起请求。

一般情况下,一个 Document 拥有一个 CachedResourceLoader 类实例。MemoryCache 类则对提供缓存条目的管理,可以方便地进行 add , remove ,缓存淘汰等。具体的缓存条目则是通过 CachedResource 类存储, MemoryCache 类维护了一个 HashMap 存储所有缓存条目。

HashMap <String,CachedResource> m_resources;

 

CachedResourceRequest 依赖于 CachedResource, 在 CacheResourceRequest 的构造函数中,会传入 CachedResource 对象作为参数。 CachedResource 既存储响应体部,也存储同 cache 相关的头部。在发起请求前,会检查是否有 cache 的 validator ,在收到响应的时候,则需要更新对应的头部。 CachedResource 类实现了 RFC2616 中的缓存一节。实际上 CachedResource 类真正完成了同网络的通信。 CachedResource 类根据申请的资源类型派生出不同的子类。

CachedResource 类的使用者必须是 CachedResourceClient, 在这个类中维护了 CachedResourceClient 类的集合 m_clients 。每一个 Client 通过 addClient 和 removeClient 将自己加入到该类的 Client 集合中。CachedResourceClientWalker则提供了CachedResouceClient的一个遍历接口。当数据来齐的时候,CachedResource 类会通过 CachedResouceClient::notifyFinished 接口通知使用者。

下图是 Image 元素对应的几个类关系。

下面以 image 为例分析其加载过程

1. 解析 html 页面的时候,解析到 img 标签,调用 HTMLImageElement::create 创建 HTMLImageElement 对象,该对象包含 HTMLImageLoader 对象 m_imageLoader
2. 解析到 img 的 href 属性,调用 ImageLoader::updateFromElementIgnoringPreviousError
3. 调用 ImageLoader::updateFromElement
4. 调用 CachedResourceLoader::requestImage
5. 调用 CachedResourceLoader::requestResource( 根据缓存的情况确定是否可以从缓存获取,或者需要 revalidate ,或者需要直接从网络获取 )
6. 调用 CachedResourceLoader::loadResource
7. 根据 Resource 的类型调用 createResource 创建对应的 CachedResource
8. 调用 MemoryCache::add 在 cache 中查找是否有对应的 cache 条目,如果没有创建之
9. 调用 CachedImage::load
10. 调用 CachedResource::load
11. 调用 CachedResourceLoader::load
12. 调用 CachedResourceRequest::load
13. 创建 CachedResourceRequest 对象,它将作为 SubresourceLoader 的 client
14. 调用 ResourceLoaderScheduler::scheduleSubresourceLoad
15. 调用 SubresourceLoader::create
16. ResourceLoadScheduler::requestTimerFired
17. 调用 ResourceLoader::start
18. 调用 ResourceHandle::create 发起请求
19. 收到 HTTP 响应头部,调用 ResourceLoader::didReceiveResponse
20. 调用 SubresourceLoader::didiReceiveResponse
21. 调用 CachedResourceRequest::didReceiveResponse 处理响应头部,特别是同缓存相关的头部,比如 304 的 status code
22. 调用 ResourceLoader::didReceiveResponse
23. 收到体部数据,调用 ResourceLoader::didReceiveData
24. 调用 SubresourceLoader::didReceiveData
25. 调用 ResourceLoader::didReceiveData
26. 调用 ResourceLoader::addData 将数据存储到 SharedBuffer 里面
27. 调用 CachedResourceRequest::didReceiveData
28. 数据来齐 , 调用 ResourceLoader::didFinishLoading
29. 调用 SubresourceLoader::didFinishLoading
30. 调用 CachedResourceRequest::didFinishLoading
31. 调用 CachedResource::finish
32. 调用 CachedResourceLoader::loadDone
33. 调用 CachedImage::data ,创建对应的 Image 对象,解码


 

WebKit内核源代码分析(五)

摘要:本文分析WebKit中html的解析过程,DOM节点树的建立。
关键词:WebKit,html解析,html tree construction,WebCore,DOM节点树

1. HTML解析模型

图1 HTML解析模型图

上图是HTML解析模型图,HTML解析分成Tokeniser和Tree Construction两个步骤,在”WebKit中的html词法分析”一文中,我们已经对Tokeniser这一步进行了分析,本文的目标是Tree Construction这一步。

Tree Construction输入是token流,输出是DOM节点树。

2. DOM树

HTML DOM定义了一套标准来将html文档结构化,它定义了表示和修改文档所需的对象、这些对象的行为和属性以及对象之间的关系,可以把它理解为页面上数据和结构的一个树形表示。
Node是DOM模型中的基础类,它可以分成13类(见NodeType),在HTML解析中,最常见的是Document,Element,Text三类。

  • Document是文档树的根节点,在HTML文档中,他派生为HTMLDocument。
  • 在文档中,所有的标签转化为Element类,一般它有标签名,并根据标签名继承为特定的子类。
  • Element之间的原始文本转化成Text类。

以一个简单的html页面为例:

复制代码
<html>
<head>
<title>test</title>
</head>
<body>
<h1>hl1</h1>
<h2>hl2</h2>
<h3>hl3</h3>
</body>
</html>
复制代码

 经过解析后的节点树如下(忽略换行符):

图2 HTML DOM节点树示例

如果没有忽略换行符,则每个换行符就是一个Value为”\n”的Text节点。

3. Tree Construction原理

将图二中的节点树以WebKit中的类具体化(同样忽略换行符)。

图3 Webkit HTML DOM节点树示例

看到这里,你是不是觉得仿佛看到了一个呼之欲出的Tree Construction轮廓?是的,最简化的情况就是这样,根据输入的token,创建出相应的Element派生类,然后添加到DOM树中合适的位置,这就是Tree Construction干的事情。当然,添加到合适的位置,这个需要一系列复杂的规则,另外,WebKit将Render树的创建也放到了Tree Construction阶段中来,再加上CSS,Javascript,所以,这就是你看到的复杂的代码。

放出两个函数原型,热热身,培养培养感情。

PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& token);

void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token);

Tree Construction流程由一个状态“Insertion Mode”进行控制,它影响token的处理以及是否支持CDATA部分,HTML5中给出了详细的规则。它也控制了在特定状态下能够处理的token,比如在head里面,再出现head标签,显然是不应该处理的。

4. 开放元素堆栈

为了维护即将解析的标签同已解析的标签之间的关系(此时即将解析的标签还没有加入到DOM树中),引入了开放元素堆栈m_openElements,初始状态下,这个堆栈是空的,它是向下增长的,所以最上面的节点是最早加入到堆栈中的,在html文档中,最上面的节点就是html元素,最底部的节点就是最新加入到堆栈中的。Tree Builder的时候,每碰到一个StartTag的token,就会往m_opnElements中压栈,碰到EndTag的token,则出栈。像Character这样的token,则不需要进行压栈出栈的动作,只有可以包含子节点的tag,才做压栈出栈的动作。Html5的文档中对开放元素堆栈也有说明

对于正在解析的token,除了根节点html,它必然是堆栈底部元素(m_openElements.top())的子节点,所以在形成DOM树的时候,就可以通过ContainerNode::parserAddChild这样的接口加入到DOM节点树中。除了正常的堆栈和压栈,对于html,head,body元素,栈结构(HTMLElementStack)中有专门的成员m_htmlElement,m_headElement,m_bodyElement记录,主要是用于检错纠错处理。

在本文的html范例中,当解析到<h2>hl2</h2>的hl2这个character的token的时候,它的开放元素堆栈如下,HTMLHeadingElement是堆栈的top,所以它是hl2这个Text节点的parent。

图4 开放元素堆栈示例

此时的DOM节点树如下:

图5 Webkit DOM节点数示例

5. 元素的创建

HTMLElementFactory类提供了元素的创建方法createHTMLElement。传入为对应的标签名,所属的document,所属的form(如果属于form),在parser的时候,最后一个参数为true。

PassRefPtr<HTMLElement> HTMLElementFactory::createHTMLElement(const QualifiedName& qName, Document* document, HTMLFormElement* formElement, bool createdByParser);

 

在HTMLElementFactory中,通过一个Hash Map将tag name和对应的元素构造函数对应起来(gFunctionMap)。tag一般对应一个派生于HTMLElement的类。如下是HTMLHeadingElement的类层次结构图。

图6 HTMLHeadingElement类层次图

6. 其它

HTMLConstructionSite::attach中的attach一词,主要是attach到DOM节点数上,当然,它同时调用了Element::attach,Element类的attach主要是attach到Render树上,它会创建对应该Element的RendrObject。

除了m_openElements,HTMLConstructionSite同时维护了Format元素列表m_activeFormattingElements,Formating元素就是那些格式化标签,包括a,b,big,code,em,font,I,fot,I,nobr,s,small,strike,strong,tt,u。为了处理这些Formatting元素的嵌套关系(此时它们可能不是父子关系,而是平级,不加入到m_openElements),HTML5引入了这个列表

使用gdb调试的童子,可以运行Tools/gdb/webkit.py脚本,在print结构体的时候得到易于理解的表示,还可以打印出节点树,具体参考http://trac.webkit.org/wiki/GDB

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值