五 . 调用过程
知道了 WebKit 的大体结构,我们就可以深究下去,看看这个浏览器引擎具体是怎么工作的。首先介绍几个基本且重要的类:
-
Page :打开 page.h 头文件,我们似乎看不到我们概念中的“页面”相关的东西,没错,这里的 Page 并非就是我们印象中的简单网页,在头文件中我们发现很多关于 history 的东西, goBack(),goForward(), 等等,关于主题的设定,关于Frame 的描述等等,因此,这里的 Page 更像是我们见到的浏览器,抽象起来,应该算是我们访问网站的一次浏览会话;
在 page.cpp 文件里,还有个重要的全局指针变量: static HashSet<Page*>* allPages; 这个变量包含了所有的page 实例,没错!就像 FireFox 一样,我们可以启动几个浏览器,而且就是在一个进程里;
allPages 在 Page 的构造函数里将每次新产生的 Page 对象加入;每次启动新的 window ,才会新建一个 Page 对象,并触发 PageGroup::addPage() ; -
Frame :与 Page 相比, Frame 更像我们印象中的一个网页,它关注的是页面的显示 (FrameView) 、页面数据的加载(FrameLoader) 、页面内的各种控制器 (Editor, EventHandler, ScriptController, etc.) 等等,可以说,这个结构表示浏览器开始从外部控制转向关注一个页面的具体描述了;
-
Document :这个类的爷爷类是 Node ,它是 DOM 树各元素的基类; Document 有个子类是 HTMLDocument ,它是整个文档 DOM 树的根结点,这样就明白了:原来 Document 就是描述具体文档的代码,看一下它的头文件,就更明白了,它的属性与方法就是围绕着各种各样的结点: Text , Comment , CDATASection , Element……
当然, Document 不止描述了 HTML 相关的结点,还有 XHTML 、 SVG 、 WML 等等其他类型的页面;
另外, Node 的父类之一是 EventTarget ,也就是所有元素都有事件响应的能力,由于 Document 类作为 DOM 根结点的位置,因此在窗口事件发生时,它第一个接收到事件,并寻找到事件发生的目标元素,然后从目标元素开始以树的路径向上依次处理事件。 -
RenderObject :在形成 DOM 树结点的时候, Node 会根据需要调用 RenderObject 的方法,生成 Render 结点并最终形成 Render 树,因此此类是 Render 树各种结点类的基类,它的一个孙子类—— RenderView ,就是整个 Render 树的根结点。
对于调用过程,这里有一段话,我认为比较明确地描述了一个场景:
“ 浏览器的一个经典应用场景是用户给出一个 URL (直接输入或者点击链接或者 JavaScript 解析等方式)。然后浏览器外壳调用 FrameLoader 来装载页面。 FrameLoader 首先检查一些条件 (policyCheck()) ,如 URL 是否非空、 URL 是否可达,用户是否取消等等。然后通过 DocumentLoader 启动一个 MainResourceLoader 来装载页面。MainResourceLoader 调用 network 模块中的接口来下载页面内容( ResourceHandle ),实际上这里的Resourcehandle 已 经是平台相关的内容了,比如在 Qt 里面,会有 ResourceHandleQt 来控制,然后调用QtNetworkReplyHandler 来处理 HTTP 请求( GET , POST 等)。接收到数据以后,会有回调函数,告诉MainResourceLoader 数据已经接收到了。然后一路返回到 FrameLoader 开始调用 HTMLTokenizer 解析 HTML 文本。解析过程中,如果遇到 Javascript 脚本的话,也会调用 Javascript 引擎( Webkit 中的 JavascriptCore , chrome 中的 V8 )来解析。数据被解析完了以后,成了一个一个的 node ,生成 DOM 树和 Render 树,然后通过 FrameLoaderClient 调用外部的壳把内容显示出来。”
下面我们以 Qt 平台为例,看看这个场景下的具体函数调用关系:
首先是 整理并向服务器发送客户请求 :
WebCore:: FrameLoader::load()
→ WebCore:: FrameLoader::loadWithDocumentLoader() →WebCore:: FrameLoader::continueLoadAfterNavigationPolicy() →WebCore:: FrameLoader::continueLoadAfterWillSubmitForm() →WebCore:: DocumentLoader::startLoadingMainResource()
→ WebCore:: MainResourceLoader::load()
→ WebCore:: MainResourceLoader::loadNow()
这里,注意到本函数在调用 ResourceHandle::create() 时, MainResourceLoader 把自己作为 create() 的第二个参数传入,这个参数是 MainResourceLoader 的祖父类 ResourceHandleClient ,这样便于下面当调用祖父类的虚函数 didReceiveData() 时,实际调用的是 MainResourceLoader 的 didReceiveData() 方法。
继续:
→ WebCore:: ResourceHandle::create()
→ WebCore:: ResourceHandleQt::start()
→ WebCore:: QnetworkReplyHandler::start()
截至本函数,用户请求才会被最终发送出去 ,然后用 connect() 方法挂载了几个信号回调函数,比如针对 finished() 信号的 finish() 函数,针对 readyRead() 信号的 forwardData() 函数,针对 processQueuedItems() 信号的sendQueuedItems() 函数,其中最重要的就是 forwardData() 函数,此函数就是 对服务器返回数据的接收与处理 。
让我们来看看 返回数据的处理过程 :
WebCore:: QnetworkReplyHandler::forwardData()
→ WebCore:: (QNetworkReply)QIODevice::read(),ResourceHandleClient:: didReceiveData()
可见,首先 forwardData() 函数会利用 QIODevice 的 read() 方法从网络数据缓冲区中读取接收数据,然后调用didReceiveData() 方法,在类 ResourceHandleClient 中,这个方法实际上是个虚函数,因此实际调用的是其子类ResourceLoader 的同名函数:
→ WebCore:: ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length, int lengthReceived)
→ WebCore:: MainResourceLoader::didReceiveData()
→ WebCore:: ResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce)
在这个函数中,有个直接调用 addData(data, length, allAtOnce); 虽然这个语句在 ResourceLoader 中,但是实际上调用的并非 ResourceLoader::addData(), 而是 MainResourceLoader::addData() ,又一个虚函数覆盖的例子:
→ WebCore:: MainResourceLoader::addData()
→ WebCore:: ResourceLoader::addData(), FrameLoader::receivedData() →WebCore:: DocumentLoader::receivedData()
→ WebCore:: DocumentLoader::commitLoad()
→ WebCore:: FrameLoader::committedLoad()
→ WebCore:: FrameLoaderClient::committedLoad()
→ WebCore:: FrameLoaderClientQt::committedLoad()
→ WebCore:: FrameLoader::addData()
→ WebCore:: DocumentWriter::addData()
至此,一次 URL 请求就完成了初始化设置,请求发送,以及数据接收,接下来就是 HTML / JS 分析 。
→ WebCore:: Tokenizer::write()
→ WebCore:: HTMLTokenizer::write()
在此函数中有个循环,针对每个 Tag 进行分析,下面是对某个 Tag 的分析过程:
→ WebCore:: HTMLTokenizer::advance()
→ WebCore:: HTMLTokenizer::parseTag(),HTMLTokenizer::processToken()
→ WebCore:: HTMLParser::parseToken()
→ WebCore:: HTMLParser::insertNodeAfterLimitDepth()
→ WebCore:: HTMLParser::insertNode()
→ WebCore:: Element::attach()
当分析了一个 Tag ,如果不是 HTML 的 Tag ,则调用相关的 parse 函数解析(如 parseNonHTMLText );如果它是HTML 的 Tag ,就将其加入 Dom 树的一个节点,接下来根据这个节点 生成 Render 树节点 :
→ WebCore:: Node::createRendererIfNeeded()
→ WebCore::Text::createRenderer()
→ WebCore::RenderText::RenderText()
另外,在 HTMLTokenizer::parseTag() 中,也会调用 HTMLTokenizer::parseNonHtmlText, 之后调用:
→ WebCore::HTMLTokenizer::scriptHandler()
→ WebCore::HTMLTokenizer::scriptExecution()
→ WebCore::ScriptController::executeScript()
→ WebCore::ScriptController::evaluate()
→ WebCore::ScriptController::evaluateInWorld()
→ WebCore::JSMainThreadExecState::evaluate()
→ JSC::evaluate()
→ JSC::Interpreter::execute()
→ JSC::JITCode::execute()
→ JSC::JITThunks::tryCacheGetByID()
→ cti_op_put_by_id()
→ JSC::JSValue::put()
→ WebCore::JSHTMLInputElement::put()
→ JSC::lookupPut<WebCore::JSHTMLInputElement, WebCore::JSHTMLElement> ()
→ JSC::lookupPut<WebCore::JSHTMLInputElement>()
→ WebCore::setJSHTMLInputElementSelectionStart()
→ WebCore::JSHTMLInputElement::setSelectionStart()
→ WebCore::HTMLTextFormControlElement::setSelectionStart()
→ WebCore::HTMLTextFormControlElement::textRendererAfterUpdateLayout()
→ WebCore::Document::updateLayoutIgnorePendingStylesheets()
→ WebCore::Document::updateLayout()
→ WebCore::FrameView::layout ()
之后有可能会调用 FrameView::adjustViewSize(), FrameView::setContentsSize(), ScrollView::updateScrollbars(), FrameView::visibleContentsResized(), FrameView::endDeferredRepaints(), FrameView::doDeferredRepaints() 等函数, 然后调用:
→ WebCore::ScrollView::repaintContentRectangle()
→ WebCore::Chrome::invalidateContentsAndWindow()
在此函数中,有关键的一句: emit m_webPage->repaintRequested(windowRect) ,意思是 将 paint 的信号最终发送出去 。
在 qt 中,函数 QEventLoop::exec() 负责对事件的检测,当检测到事件发生(信号),会调用以下函数进行处理:
→ QeventLoop::processEvents()
→ ?
→ QEventDispatcherGlib::processEvents
→ g_main_context_iteration()
→ ?
→ g_main_context_dispatch()
→ ?
→ QCoreApplication::sendPostedEvents()
→ QCoreApplicationPrivate::sendPostedEvents()
→ QCoreApplication::notifyInternal()
→ QApplication::notify()
→ QApplicationPrivate::notify_helper()
→ QMainWindow::event(QEvent*)
→ QWidget::event(QEvent*)
→ QWidgetPrivate::syncBackingStore()
→ ?
→ QWidgetPrivate::drawWidget()
→ QCoreApplication::notifyInternal()
→ QApplication::notify()
→ QApplicationPrivate::notify_helper()
→ QWebView::event()
→ Qwidget::event()
以上是 Qt 事件处理的通用过程,从下面的函数开始, Qt 识别出此信号是 paint 信号:
→ QWebView::paintEvent()
→ QWebFrame::render()
→ QWebFramePrivate::renderRelativeCoords()
→ WebCore::FrameView::paintContents()
→ WebCore::RenderLayer::paint()
→ WebCore::RenderLayer::paintLayer()
→ WebCore::RenderLayer::paintList()
→ WebCore::RenderLayer::paintLayer()
→ WebCore::RenderLayer::paintList()
→ WebCore::RenderLayer::paintLayer()
→ WebCore::RenderBlock::paint()
→ WebCore::RenderBlock::paintObject()
→ WebCore::RenderBlock::paintContents()
→ WebCore::RenderBlock::paintChildren()
→ WebCore::RenderBlock::paint()
→ WebCore::RenderBlock::paintObject()
→ WebCore::RenderBlock::paintContents()
→ WebCore::RenderBlock::paintChildren()
→ WebCore::RenderBlock::paint()
→ WebCore::RenderBlock::paintObject()
→ WebCore::RenderBlock::paintContents()
→ WebCore::RenderLineBoxList::paint()
→ WebCore::RootInlineBox::paint()
→ WebCore::InlineFlowBox::paint()
→ WebCore::InlineFlowBox::paint()
→ WebCore::InlineTextBox::paint()
→ paintTextWithShadows()
→ WebCore::GraphicsContext::drawText()
→ WebCore::Font::drawText()
→ WebCore::Font::drawComplexText()
→ QPainter::drawText()
可以看到,以上有的函数会重复调用,因为现在所展示的只是一个执行流程,于 WebKit 全体只是冰山一角。到此, WebKit最终调用 Qt 的 QPainter 画出文字。
这样,从最初的数据载入到将一个文字最终画出,基本上完成了,其他如图片的过程类似。
之后,在 resize 、 mouse-click 等事件的驱动下, WebKit 仍然会不停地进行 relayout 和 repaint 。