关于FrameLoader、DocumentLoader以及DocLoader的关系[1],在新的Chrome版本中,DocLoader已经被remove掉了,因此哪个图也就应该更新了,我猜想现在的资源下载应该都是通过DocumentLoader完成了
1)FrameLoad在init函数中创建了PolicyDocumentLoader
2)DocumentLoader在DocumentLoader::startLoadingMainResource中创建了MainResourceLoader,在startLoadingMainResource函数中直接调用MainResourceLoader::load函数,在MainResourceLoader::load函数中,执行loadNow函数,创建ResourceHandler,开始网络数据的下载
3)增加了MainResourceLoader一层,可以隔离DocumentLoader和ResourceHandler的callback函数。
4)FrameLoader和DocumentLoader主要负责HTML页面的加载,
5)DocLoader负责HTML页面中图片、js、css文件等的下载工作,新版中,DocLoader已经不存在了。
6)网络中的数据是如何给Document以及HTMLDocumentParser的呢?DocumentLoader拥有一个DocumentWriter对象,DocumentWriter类中有DocumentParser的指针,DocumentWrite类通过DocumentWriter::createDocument完成创建Document对象,创建的Document对象类型包括PluginDocument,PlaceholderDocument以及类似HTML的document,其中类似HTML类型的Document通过DOMImplementation::createDocuement来创建,根据不同的类型创建不同的Document对象,DocumentWriter::addData负责将接收到的数据传递给DocumentParser,但DocumentWriter::createDocument是一个private函数,它被DocumentWriter::begin函数调用,而DocumentWriter::begin函数分别在FrameLoader::init和FrameLoader::receivedFirstData调用,FrameLoader::receivedFirstData会被FrameLoader::willSetEncoding函数调用,困惑的是FrameLoader::willSetEncoding会被DocumentWriter::setEncoding调用,DocumentWriter::setEncoding有两处被调用,一处是FrameLoader::finishedLoadingDocument,另外一处是:DocumentLoader::commitData,不过这个路线好像不太对,再仔细看看就没问题了,尤其是参考了[5]以后,当访问一个URL时,文档是没有被创建的,当收到第一批数据时,在DocumentWriter::setEncoding中调用FrameLoader::willSetEncoding中调用DocuemntWriter::begin创建文档,并将FrameLoader.m_hasReceivedFirstData置为true,以后再有数据到来时就不会再创建文档了。
正如5、所说的loadURL应该是URL的加载入口,一会仔细看看
5、FrameLoader::loadURL可能是加载资源的一个入口,需要仔细看一下
FrameLoader::loadURL调用FrameLoader::loadWithNavigationAction,在这个函数中通过FrameLoaderClient::createDocumentLoader创建了DocumentLoader,执行DocumentLoader::setTriggerAction,将NavigationAction传递给DocumentLoader,然后,执行FrameLoader::loadWithDocumentLoader,在FrameLoader::loadWithDocumentLoader中,可能会检查是否需要输入Form之类的(需要仔细看),然后执行FrameLoader::continueLoadAfterNavigationPolicy,在FrameLoader::continueLoadAfterNavigationPolicy中,根据policy可能会sumbmit Form,然后调用FrameLoader::continueLoadAfterwillSubmitForm,在FrameLoader::continueLoadAfterWillSubmitForm中,使用m_provisionalDocumentLoader执行DocumentLoader::startLoadingMainResource,开始加载真正HTML资源,FrameLoader中有3个DocumentLoader,这些DocumentLoader是为了满足frame loading的三个阶段,需要注意的是,当一个新的request被load的时候,old document loader可能仍然被引用,举例来说,当一个request处在policy state,old document loader可以用来设置新的docuemnt loader。在load的过程中,在FrameLoader::loadWithDocumentLoader中,FrameLoader::loadWithNavigationAction中创建的DocumentLoader被设置为m_policyDocumentLoader;在FrameLoader::continueLoadAfternavigationPolicy中,FrameLoader的状态被设定为FrameStateProvisional状态,同时,FrameLoader::m_provisionalDocumentLoader也指向m_policyDocumentLoader,而原来的m_policyDocumentLoader指向空。下面由m_provisionalDocumentLoader完成startLoadingMainResource的任务。DocuemntLoader::startLoadingMainResource中首先创建MainResourceLoader,然后调用MainResourceLoader::load,可以参考问题11中加载的过程。
5.1 根据5的过程,一个疑问是Document是在哪里创建的呢?
目前所看到的是,在FrameLoader::init中,调用了DocumentWriter::begin,DocumentWriter::begin函数会调用DocumentWriter::createDocument创建一个Document对象。在DocumentWriter::begin中会将创建的对象设置到Frame当中,因此Frame拥有Document对象。
5.2 FrameLoader::init什么时候被调用呢?FrameLoader::init在Frame::init中调用,那么Frame::init什么时候被调用呢?Frame::init是接口的起点了,不同的接口实现都会调用Frame::init函数
5.3 DocumentWrite什么时候被创建的呢?DocumentWrite和DocumentLoader是绑定在一起的,每一个DocumentLoader都有一个DocumentWrite对象。FrameLoader通过拥有DocumentLoader而间接拥有DocumentWriter对象。
5.4 Page和Frame的关系,一个Page包含多个Frame,这些Frame放在Page.m_mainFrame的FrameTree中,那么FrameTree有什么作用,为什么要有一个FrameTree呢?这主要是因为HTML页面中可能会使用iFrame,产生子Frame,这些产生的子Frame会添加到m_mainFrame中。SubframeLoader::loadSubframe会调用FrameLoaderClient::createFrame创建subFrame,SubframeLoader::loadSubframe会被SubframeLoader::loadOrRedirectSubframe调用,这一系列的过程中,subframe没有被加入到FrameTree中,但用户自己实现createFrame的参数中传入了父Element的指针,可以在用户自己实现的createFrame中,完成这个操作。
6、FrameLoaderClient的作用是什么?
7、所谓WebKit的Port其实就是提供给用户的回调函数接口,例如FrameLoaderClient类,其中提供了大量的回调函数,当webkit内部处理到某个阶段时,会调用回调函数,用户可以得到处理的数据或者进展的状况。
8、ResourceLoader和ResourceHandle是什么关系呢?
ResourceLoader内部有ResourceHandle的对象,ResourceLoader通过ResourceHandle来获取网络数据,FrameLoader拥有ResouceLoadNotifier的实例,ResourceLoader可以通过FrameLoader获得ResourceLoadNotifier,通过这个notifier可以通知订阅了资源加载需求的订阅这,也就是调用回调函数,其中就包括FrameLoaderClient中的回调函数,notifier的主要将消息发给Inspector和FrameLoaderClient
9、MainResourceLoader和ResourceLoad又有什么关系呢?
关于MainResourceLoader与ResourceLoader的关系,参考文献[4]给出了很好的解释,MainResourceLoader负责HTML页面的下载,而ResourceLoader则负责HTML解析过程中,子资源的下载,之所以要区分进行,因为这两种资源下载的回调函数处理有很大的不同,如果HTML资源下载失败,应该向用户报错,而一个图片或者css下载出错,则肯能选择不显示图片,或者不应用css来处理。参考文献[4]给了非常多清楚的加载过错分析,值得仔细阅读。
10、InspectorInstrumentation是自己再建立一遍Dom tree吗?还是使用现实网页时的Dom Tree,应该是使用网页时的DomTree,每必要付出同步和重建的代价呀!需要仔细看看InspectorInstrumentation是如何构建的
11、加载的过程如下:
.DocumentLoader::startLoadingMainResource调用MainResourceLoader::load
.MainResource::load中调用MainResourceLoader::loadNow
.MainResourceLoader::loadNow中调用MainResourceLoader::willSendRequest
.MainResouceLoader::willSendRequest调用ResourceLoader::willSendRequest
.ResouceLoader::willSendRequest通过ResourceLoadNotifier通知FrameLoaderClient,将要发送用户请求,用户可以通过重写FrameLoadClient更改发送Request等。ResourceHandle的实现分成两个部分,一部份是与平台无关的,实现在ResourceHandle.cpp当中,还有一部份是与平台相关的,比如如果网络处理使用curl,那么另一部分实现就在curl平台下的ResourceHandleCurl.cpp当中。另外ResourceHandle实现的过程中,通过ResourceHandleInternal完成了部分功能,但没有找到ResourceHandleInternal的实现,后来发现在WebKit/WebKit/chromium/src/ResourceHandle.cpp中有ResourceHandle和ResourceInternal的实现。在ResourceHandleCurl.cpp中只有一个ResourceHandleInternal::~ResourceHandleInternal()的析构函数的实现。但WebKit/WebKit/chromium/src/ResourceHandle.cpp实现与webkit中定义的ResourceHandle、ResourceHandleInternal有很大的不同,不知道为什么,或许这个是google重新定义的内核接口?
.接下来的数据处理流程应该就是数据驱动的了,因为当有数据到达时,就会回调数据的处理函数,完成对数据的处理,无论MainResourceLoader还是ResourceLoader都是ResourceHandleClient的子类,都有didReceiveResponse函数会被调用到;
.在MainResourceLoader::didReceiveResponse中调用了会做一些检查,然后调用MainResourceLoader::callContinueAfterContentPolicy,进一步调用MainResourceLoader::continueAfterContentPolicy
.在ResourceLoader::didReceiveResponse中经过notifier,会将接收到reponse的情况告诉给FrameLoaderClient
.由于ResourceHandler传入的client是MainResouceLoader,因此当接收到数据时,MainResourceLoader::didReceiveData将被回调,同时在MainResourceLoader::didReceiveData中调用ResourceLoader::didReceiveData来通知FrameLoaderClient,同时ResourceLoader::didReceiveData会调用addData函数,addData函数会将数据加入到sharedBuffer中
.非常困惑MainResourceLoader::addData是谁在什么时候调用的呢?
.如果MainResourceLoader::addData被调用,那么他会调用它父类ResourceLoader的addData,然后,调用DocumentLoader::receiveData
.DocumentLoader::receiveData调用DocumentLoader::commitLoad
.DocumentLoader::commitLoad回调FrameLoaderClient::commitedLoad
.非常困惑的是数据到此好像断了?文献[4]的16、17步有一些解释,但没有找到代码依据
.不知到通过什么途径DocuemntLoader::commitData被调用,进而调用DocumentWriter::addData,在DocumentWriter::addData中,数据编码被设置,设置编码过程中,数据被添加到DocumentWriter当中
.在DocumentWriter::addData中,DocumentParser终于现身,DocumentParser::appendBytes被调用
.接下来,就是比较熟悉的DecodedDataDocumentParser::appendBytes,以及HTMLDocuementParser::append,进行HTML解析了
.不知什么时候,MainResourceLoader::didFinishLoading被调用,它会调用FrameLoader::finishLoading,在FrameLoader::finishLoading中会回调FrameLoaderClient::dispatchDidLoadMainResource,同时在MainResourceLoader::didFinishLoading中还会调用ResourceLoader::didFinishLoading,ResourceLoader::didFinishLoading会调用其自身的didFinishLoadingOnePart,通过notifier通知FrameLoaderClient::dispatchDidFinishLoading
.基本上差不多就是这样,还有一些过程可以参考[4]
11.1下面再看看,当解析HTML的过程中,如果过发现了需要下载的子资源如何处理?
.Document拥有一个CachedResourceLoader对象,在Document的构造函数中创建了CacheResourceLoader对象,Document::cachedResourceLoader提供了Document.m_cachedResourceLoader实例的访问接口,很多类通过这个接口访问CachedResouceLoader对象(包括:CSSImportRule、CSSPreloadScanner、PreloadTask等等)
.MemoryCache是一个全局使用的类,通过一个全局函数,创建一个静态的MemoryCache类,用于保存,key-资源的映射,其中key是resource的url
.当Document需要加载subresource时,通过调用CachedResourceLoader::requestResource,CachedResourceLoader::requestResource根据RevalidationPolicy来决定是Load/ReLoad/Revalidate/Use等
.如果需要Load资源时,会调用CachedResourceLoader::loadResource,它会使用createResource创建CachedResource,createResource是一个工厂方法,根据资源的类型创建匹配的CachedResource,比如script、css等。
.当CachedResource创建好以后,会对资源下载设定优先级,然后会调用资源本身的CachedResource::load函数;
.在CachedResource::load函数中会通过ResourceLoadScheduler::scheduleSubresourceLoad将需要下载的资源加入到ResourceLoadScheduler中,ResouceLoadScheduler也是一个静态的实例,通过全局函数resourceLoadSchedular来获得;
.ResourceLoadScheduler::scheduleSubresourceLoad函数中会创建一个SubresourceLoader的实例,并加入调度器,创建的SubresourceLoader负责资源的加载
.ResourceLoadScheduler使用一个计时器来调度,当开始调度时关闭计时器,然后开始调度,资源的下载调度是按照主机进行组织的,同时又区分为nonHTTPProtocol和HTTPProtocol,每个主机取一个优先级最高的request进行下载,下载方法是调用ResourceLoader::start
.以后的处理流程就与MainResource的下载非常相似了,至此,下载的过程比较清楚了,但还有一个问题是,Document什么时候开始subresource的下载工作呢?这些subresource请求的发出,是由具体的Element来发出的,比如CachedResourceLoader::requestScript是由dom/ScriptElement::requestScript发出的,而CachedResourceLoader::requestCSSStyleSheet则是由css/CSSImportRule::requestStyleSheet发出的,而CachedResourceLoader::requestImage是由loader/ImageLoader::updateFromElement发出的。
12、Page类主要包括了一些settings和RenderTheme等,它有一个Frame对象:m_mainFrame
13、Frame是众多部件的一个集合体,集中了包括Document、FrameLoader、ScriptController等对象,Frame中的Document对象通过Frame::setDocument被设置,而Frame::setDocument会在FrameLoader::open时被调用
14、PreScan的过程是什么样的?
15、DomTree建立完成和RenderTree建立完成后,如何工作的?
16、在Render、Layout和Paint部分,涉及到那些port?
17、css如何完成解析和css规则应用的?
参考文献[8]有一定介绍
.CSSStyleSelector是一个重要的类,在CSSStyleSelector的构建中会缺省载入一些style,这些style是以代码的形式存在UserAgentStyleSheets.h和UserAgentStyleSheetsData.cpp中的,按数据是以整数的形式保存的,不太容易看出数据的具体含义。
.CSSRule的pase过程:CSSStyleSheetSelector.cpp中有一个全局函数parseUASheet->CSSStyleSheet::parseString->CSSParser::parseSheet,CSSStyleSheet会创建一个CSSParser对象,并将自身的指针传如Parser对象,parse的结果应该保存在传入的CSSStyleSheet对象中
.CSSParser::parseSheet使用yark来对csssheet进行parse
18、DOM树中HTML的各个节点如何创建的?创建过程
.HTMLConstructionSite::insertHTMLElement->HTMLConstructionSite::createHTMLElement->HTMLElementFactory::createHTMLElement
.HTMLElementFactory.cpp和HTMLElementFactory.h文件在源代码中是找不到的,它是在编译的过程中自动生成的,HTMLElementFactory的实现方法是,通过一个map可以找到tagname对应的构造函数的回调函数接口,每个HTMLElement都有一个静态的create函数,在createHTMLElement时,会调用对应的创建函数,如果遇到Unknow的tag,会创建一个unknow的tag,有些tag的创建是调用HTMLElement::create,有些tag的创建是调用某个具体的Element的create函数,这些在HTMLElementFactory中都有实现
.CSSStyleSelector::styleForElement是为Element设置style
.RuleSet位于CSSStyleSelector.cpp文件中,它保存了3个用于match的Hashtable,分别是m_idRules,m_classRules, m_tagRules,其实还有另外两个CSSRuleDataList,直接保存在list当中的,m_universialRules和m_pageRules
.在应用styles时首先调用CSSStyleSelector::styleForElement,在CSSStyleSelector::styleForElement中会根据不同的RuleSet,来调用CSSStyleSelector::matchRules,CSSStyleSelector::matchRules输入参数包括一个RuleSet,matchRules会根据当期元素的id、class、tagname、以及universalrule,去从RuleSet的hash表中,取出对应的CSSRuleDataList,CSSRuleDataList是一个CSSRuleData的链表,CSSStyleSelector::matchRules会进一步通过CSSStyleSelector::matchRuleForList来对CSSRuleDataList进行匹配,在CSSStyleSelctor::matchRulesForList中关键的一个函数就是CSSStyleSelector::checkSelector,也就是判断某个rule的selector和当期的element是否匹配,如果selector匹配则将rule加入到m_matchedRules当中。从上面的分析可以看出,整个匹配过程是分几个过程的,首先是选择适当的RuleSet(m_userStyle,m_authorStyle),然后根据element的情况,看需要对RuleSet中的哪些CSSRuleDataList进行匹配(这一步通过id、class、tagname等通过hash筛选),下一步是对选出的CSSRuleDataList进行顺序链表匹配,这一步需要对每个RuleData进行selector的准确匹配,匹配成功的rule加入到m_matchedRules中,待进一步处理。
.selector的匹配逻辑是比较复杂的,通过内部类CSSStyleSelector::SelectorChecker完成,主要通过CSSStyleSelector::SelectorChecker::checkSelector完成,匹配的过程是一个递归的过程,首先处理当前元素的匹配情况,通过调用checkOneSelector来实现,在根据当前匹配的情况,递归调用该函数进行匹配处理。比较负责的匹配逻辑都集中在checkOneSelector中。
.在matchRules函数中会对匹配的rule进行排序
.styleForElement最后是按照一定的顺序调用应用规则的Declarations也就是调用applyDeclarations,在applyDeclaration会调用CSSStyleSelector::applyProperty