WebKit介紹及總結(二)
五 . 調用過程
知道了 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 。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/spacetiller/archive/2010/08/03/5784475.aspx