首先文笔不好, 其次精力关系,理解错误肯定很多,欢迎指正,随时修改.
WebKit代码非常的多,现在的WebKit源代码包括jscore部分大概有90万代码,去除js部分有70万左右(WebKit组成部分加移植部分很多,各版本差异可能大概在几万左右.这里的代码统计是我自己写代码读取的行数).大家都称WebKit相比FireFox代码上要清晰很多,但里面还是有着臃肿混乱的部分,相信除了WebKit的代码维护者,一般人对着源代码去了解其运行逻辑还是痛苦的,外加代码庞大,"物理意义"上仅仅是代码行数乘以1秒换算成时间,也是让人望而却步的(感觉就像你不吃不喝拿自己的工资乘以时间去参照北京房价一样),所以一个好的,熟悉的调试工具,代码阅读工具是很重要的.
Qt是一个很完善的开源平台(很多代码看上去比WebKit友好清晰,更关键是Qt在各个平台上的编译及初期工作都很温柔及人性化.),Qt对WebKit有一个很完善的移植,借助Qt这个外壳,能让我们跳过无意义的时间耗费,更有效和快速接近WebKit的内部.所以我主要是通过Qt中对WebKit部分对WebKit进行阅读的. 首先你的CPU必须是双核的,以便编译过程太卡时,你还能干点代码阅读的事情,此外CPU速度和硬盘速度也不能太古董,否则说不定光按下F5调试就足以你泡杯咖啡并喝完它.软件工具: VisualStudio,Source Insigh,其中VS需要搭配VS assistant, Source Insigh需要搭配标签页工具(我使用的是TabSiPlus),以更人性化的使用VS和source insigh. a.按照网络上的教程使用qmake命令生成Qt的Visual studio工程文件,手工编辑sln文件,删除其它的Qt工程,仅保留Browser,jscore,QtWebKit 编译. b.新建Source Insigh的工程,添加Qt/thirdpartys/Webkit中所有文件到工程中.然后使用vs和source Insigh进行代码的调试运行和阅读. VS打开: 找到browsermainwindow.cpp文件,里面的void BrowserMainWindow::slotHome()函数,修改 QString home = settings.value(QLatin1String("home"), QLatin1String("file:///C:/Users/moon/Desktop/save/17.html")).toString(); 这行代码 指向自己想加载的主页. A. 找到RenderObject::addChild函数添加断点.VS调试运行工程,WebKit在加载网页的开始加载了一个空白页(参看FrameLoader::Init的第一行注释.),所以可能有前1,2次进入RenderObject::addChild是无意义的. 命中断点时,打开调用堆栈界面, 这是在收到网络数据后,从网络移植部分调用到RenderObject::addChild函数的过程. WebCore::RenderObject::addChild WebCore::RenderBlock::addChild WebCore::Node::createRendererIfNeeded WebCore::HTMLParser::insertNode WebCore::HTMLParser::insertNodeAfterLimitBlockDepth WebCore::HTMLParser::parseToken WebCore::HTMLTokenizer::processToken WebCore::HTMLTokenizer::parseTag WebCore::HTMLTokenizer::write WebCore::FrameLoader::write WebCore::FrameLoader::addData WebCore::FrameLoaderClientQt::committedLoad WebCore::FrameLoader::committedLoad WebCore::DocumentLoader::commitLoad WebCore::DocumentLoader::receivedData WebCore::FrameLoader::receivedData WebCore::MainResourceLoader::addData WebCore::ResourceLoader::didReceiveData WebCore::MainResourceLoader::didReceiveData WebCore::ResourceLoader::didReceiveData WebCore::QNetworkReplyHandler::forwardData B.给ResourceHandleQt.cpp文件中的ResourceHandle::start加上断点,加载网页: WebCore::ResourceHandle::start(WebCore::Frame * frame=0x035c5810) WebCore::ResourceHandle::create(const WebCore::ResourceRequest & request={...}, WebCore::ResourceHandleClient * client=0x035feb40, WebCore::Frame * frame=0x035c5810, bool defersLoading=false, bool shouldContentSniff=false) WebCore::MainResourceLoader::loadNow(WebCore::ResourceRequest & r={...}) WebCore::MainResourceLoader::load(const WebCore::ResourceRequest & r={...}, const WebCore::SubstituteData & substituteData={...}) WebCore::DocumentLoader::startLoadingMainResource(unsigned long identifier=1) WebCore::FrameLoader::continueLoadAfterWillSubmitForm() WebCore::FrameLoader::continueLoadAfterNavigationPolicy(const WebCore::ResourceRequest & __formal={...}, WTF::PassRefPtr<WebCore::FormState> formState={...}, bool shouldContinue=true) WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy(void * argument=0x035c5840, const WebCore::ResourceRequest & request={...}, WTF::PassRefPtr<WebCore::FormState> formState={...}, bool shouldContinue=true) WebCore::PolicyCallback::call(bool shouldContinue=true) WebCore::PolicyChecker::continueAfterNavigationPolicy(WebCore::PolicyAction policy=PolicyUse) WebCore::FrameLoaderClientQt::callPolicyFunction(void (WebCore::PolicyAction)* function=0x1076f340, WebCore::PolicyAction action=PolicyUse) WebCore::FrameLoaderClientQt::dispatchDecidePolicyForNavigationAction(void (WebCore::PolicyAction)* function=0x1076f340, const WebCore::NavigationAction & action={...}, const WebCore::ResourceRequest & request={...}, WTF::PassRefPtr<WebCore::FormState> __formal={...}) WebCore::PolicyChecker::checkNavigationPolicy(const WebCore::ResourceRequest & request={...}, WebCore::DocumentLoader * loader=0x035fe080, WTF::PassRefPtr<WebCore::FormState> formState={...}, void (void *, const WebCore::ResourceRequest &, WTF::PassRefPtr<WebCore::FormState>, bool)* function=0x102f0540, void * argument=0x035c5840) WebCore::FrameLoader::loadWithDocumentLoader(WebCore::DocumentLoader * loader=0x035fe080, WebCore::FrameLoadType type=FrameLoadTypeStandard, WTF::PassRefPtr<WebCore::FormState> prpFormState={...}) WebCore::FrameLoader::load(WebCore::DocumentLoader * newDocumentLoader=0x035fe080) WebCore::FrameLoader::load(const WebCore::ResourceRequest & request={...}, const WebCore::SubstituteData & substituteData={...}, bool lockHistory=false) WebCore::FrameLoader::load(const WebCore::ResourceRequest & request={...}, bool lockHistory=false) QWebFrame::load(const QNetworkRequest & req={...}, QNetworkAccessManager::Operation operation=GetOperation, const QByteArray & body={...}) QWebFrame::load(const QUrl & url={...}) QWebView::load(const QUrl & url={...}) WebView::loadUrl(const QUrl & url={...}) TabWidget::loadUrlInCurrentTab(const QUrl & url={...}) BrowserMainWindow::loadUrl(const QUrl & url={...}) BrowserMainWindow::loadPage(const QString & page={...}) BrowserMainWindow::slotHome() BrowserApplication::postLaunch() 这是程序启动到发起一次网络下载操作的流程,ResourceHandle包含了一个QNetworkReplyHandler对象,并在有data可read的时候执行QNetworkReplyHandler::forwardData. A和B在这里联系起来. C. 给drawTextCommon加上断点,加载一个网页: WebCore::drawTextCommon() WebCore::Font::drawComplexText() WebCore::Font::drawText() WebCore::GraphicsContext::drawText() WebCore::paintTextWithShadows() WebCore::InlineTextBox::paint() WebCore::InlineFlowBox::paint() WebCore::RootInlineBox::paint() WebCore::RenderLineBoxList::paint() WebCore::RenderBlock::paintContents() WebCore::RenderBlock::paintObject() WebCore::RenderBlock::paint() WebCore::RenderLayer::paintLayer() WebCore::RenderLayer::paint() WebCore::FrameView::paintContents() QWebFramePrivate::renderRelativeCoords() QWebFrame::render(QPainter * painter=0x0012cb40, const QRegion & clip={...}) QWebView::paintEvent(QPaintEvent * ev=0x0012d364) 这是一次绘画的流程,从重画事件一直到读取保存在RenderObject,RenderStyle(它们是webkit中对CSS部分的实现)中的信息进行paint. D.给JsObject.cpp里的jsobject::put加上断点,给jscore里的parser.cpp的didfinishParsing加上断点, 加载一个有js代码的网页. (1) jsobject::put JSC::JSObject::put(JSC::ExecState * exec=0x03d00048, const JSC::Identifier & propertyName={...}, JSC::JSValue value={...}, JSC::PutPropertySlot & slot={...}) JSC::JSValue::put(JSC::ExecState * exec=0x03d00048, const JSC::Identifier & propertyName={...}, JSC::JSValue value={...}, JSC::PutPropertySlot & slot={...}) cti_op_put_by_id(void * * args=0x0012cf60) cti_op_convert_this@4() JSC::JITCode::execute(JSC::RegisterFile * registerFile=0x03501cac, JSC::ExecState * callFrame=0x03d00048, JSC::JSGlobalData * globalData=0x034dd0e0, JSC::JSValue * exception=0x0012d094) JSC::Interpreter::execute(JSC::ProgramExecutable * program=0x0356b508, JSC::ExecState * callFrame=0x0354b6f8, JSC::ScopeChainNode * JSC::JSObject * thisObj=0x03bc0000, JSC::JSValue * exception=0x0012d094) (2) parser::didfinishparsing JSC::Parser::didFinishParsing(JSC::SourceElements * sourceElements=0x03545c58, JSC::ParserArenaData<WTF::Vector<std::pair<JSC::Identifier const *,unsigned int>,0> > * varStack=0x03545ac8, JSC::ParserArenaData<WTF::Vector<JSC::FunctionBodyNode *,0> > * funcStack=0x00000000, unsigned int features=6, int lastLine=36, int numConstants=0) jscyyparse(void * globalPtr=0x034bd150) JSC::Parser::parse(JSC::JSGlobalData * globalData=0x034bd150, int * errLine=0x0012d034, JSC::UString * errMsg=0x0012d03c) JSC::Parser::parse<JSC::ProgramNode>(JSC::JSGlobalData * globalData=0x034bd150, JSC::Debugger * debugger=0x00000000, JSC::ExecState * debuggerExecState=0x03542c80, const JSC::SourceCode & source={...}, int * errLine=0x0012d034, JSC::UString * errMsg=0x0012d03c) JSC::ProgramExecutable::compile(JSC::ExecState * exec=0x03542c80, JSC::ScopeChainNode * scopeChainNode=0x03542de0) (3) 2者共同部分 JSC::evaluate(JSC::ExecState * exec=0x0354b6f8, JSC::ScopeChain & scopeChain={...}, const JSC::SourceCode & source={...}, JSC::JSValue thisValue={...}) WebCore::ScriptController::evaluateInWorld(const WebCore::ScriptSourceCode & sourceCode={...}, WebCore::DOMWrapperWorld * world=0x03501f98) WebCore::ScriptController::evaluate(const WebCore::ScriptSourceCode & sourceCode={...}) WebCore::ScriptController::executeScript(const WebCore::ScriptSourceCode & sourceCode={...}) WebCore::HTMLTokenizer::scriptExecution(const WebCore::ScriptSourceCode & sourceCode={...}, WebCore::HTMLTokenizer::State state={...}) WebCore::HTMLTokenizer::executeExternalScriptsIfReady() WebCore::HTMLTokenizer::notifyFinished(WebCore::CachedResource * __formal=0x0353fa08) WebCore::CachedScript::checkNotify() WebCore::CachedScript::data(WTF::PassRefPtr<WebCore::SharedBuffer> data={...}, bool allDataReceived=true) WebCore::Loader::Host::didFinishLoading(WebCore::SubresourceLoader * loader=0x03540640) WebCore::SubresourceLoader::didFinishLoading() WebCore::ResourceLoader::didFinishLoading(WebCore::ResourceHandle * __formal=0x0353ffd8) WebCore::QNetworkReplyHandler::finish() 这是一个网页加载完毕到javascript解析逻辑,以及在js核心里的流程. 我们可以参考一下A,B,C,D这样的总体流程,以更直观的理解逻辑,当然我这里的断点选择仅仅是示意,并不是最佳的展示. 我第一次看到WebKit代码中did,will前缀有点困惑,看多了才熟悉了其含义.will/did相当于某些项目里的before/after,也就是在某件事情发生之间和发生之后要进行的处理(有些地方并不正确,但大体如此),did类似callback的含义,像win平台上常见的OnKeyDown里的On. will,did,callback,after,before在webkit的函数命名中都有使用. 其命名惯例还有client和private, AAAClient表示的是需要关注AAA处理的过程中一些逻辑点的类,***client一般是个接口类,继承它,实现自己的逻辑,这样***在进行一个操作时会调用client类的逻辑,实现通知or处理or拦截. private就是pimpl手法,多用于外围逻辑和移植的部分,AAA类会有一个AAAprivate成员,AAA的方法就是简单的调用AAAprivate的同名方法.在WebKit里有些地方也用impl这个标缀,但不多. 阅读过程可能很累很疲倦,涉及方方面面细节很多,最好找至少30分钟的块状时间来进行阅读.看5分钟就被打断了,回过头很可能就忘记了,也就白浪费了5分钟. 个人觉得多注意几点: 0. 选择性阅读,注意每个类实现了什么功能,这个功能的关键程度. 我们的时间只够看有意义的类,不可能犄角旮旯的代码也去仔细阅读. 1. 对于 类,可以通过查看类名的引用关注它是在何处何时构造的,以清晰它的重要程度. 2. 阅读一个类的方法时,多使用"查看所有引用"功能. 以: a.防止自己在阅读无太大意义的代码. b.清晰这个方法调用时的运行环境/逻辑环境,以更好的体会方法中的代码要注意的地方. 3. VS与Source Insigh交换着使用,2者各有各的优缺点.阅读时应该注意自己此时使用的是哪样工具从事哪样事情,争取提高每分每秒的效率. 我使用的是VS2005,其代码上下文窗口响应极其缓慢,查看定义和声明时常有一些bug,"智能感应"功能耗CPU很高却耗时很久,而source insigh在这方面相当的完美. VS内置标签页功能,而且可以建立垂直选项卡组,在宽屏上可以很方便进行并排阅读.安装visual assistant后,有更多快捷的功能. 总而言之,我们要灵活使用2者来最高效率的完成夸张的工作量. 4. 阅读时可以加些断点,加载一个网页,执行一些操作,跟踪下流程,加深理解. 5. 多做记录,因为逻辑繁杂,很容易看一些忘一些(我去年就看过一小部分webkit代码,今年几乎就想不起来了,幸亏留了些笔记.), 做些记录,以便查看时能把以前的理解很快的捡起来. 6. 对一些有趣优秀的地方,一些臃肿的地方进行总结反思学习,多总结才能更好的帮助自己代码设计的更好,写的更好. 7. 对自己要严酷,要有规划,若仅仅是非常肤浅的读webkit一部分代码,个人强烈建议节约时间看一些网络上的webkit代码阅读总结大体了解好了.毕竟不深刻的记忆总是很容易忘记的,而人的时间是最昂贵的,做一件不能成为回忆的事情是对生命极大的浪费.