QT分析之WebKit

 

 

該文章整理自 網易博客 http://blog.163.com/net_worm/blog/static/12770241920101831312381/

轉載請註明出處

 

WebKit是QT4新整合的第三方構件。按照慣例動手分析之前,先了解大概

WebKit由三個模塊組成:JavaScriptCore、WebCore 和 WebKit

WebKit作爲了整個項目的名稱。其目錄結構:(未校準)

WebCore:

  ¨Page與外框相關的內容(Frame,Page,History,Focus,Window)

  ¨Loader加載資源及Cache

  ¨HTML-DOM HTML內容及解析

  ¨DOM- DOM CORE內容

  ¨XML- XML內容及解析

  ¨Render-排版功能

  ¨CSS-DOM CSS內容

  ¨Binding-DOM與JavascriptCore綁定的功能

  ¨Editing-所有與編輯相關的功能

  JavascriptCore-javascript引擎

  ¨API-基本javascript功能

  ¨Binding與其它功能綁定的功能,如:DOM,C,JNI

  ¨DerviedSource自動產生的代碼

  ¨ForwordHeads頭文件,無實際意義

  ¨PCRE-Perl-Compatible Regular Expressions 

  ¨KJS-Javascript Kernel

  ¨WTF-KDE的C++模板庫

  Unicode unicode 庫

  Tools tools庫

  CURL-url 客戶端傳輸庫

  PlatForm- 與平臺相關的功能,如圖形圖像,字體,Unicode, IO,輸入法等.

在QT自帶的例子中,有WebKit相關的例子。我選中previewer作爲分析的項目。

 
previewer是QT自帶的例子,運行之後的樣子:
 
 
我是通過輸入URL,進行跟蹤分析的。下面是斷點保存的調用堆棧,暫存資料

 

複製代碼
 1      QtWebKitd4.dll!WebCore::MainResourceLoader::loadNow(WebCore::ResourceRequest & r={...})  行458    C++
 2      QtWebKitd4.dll!WebCore::MainResourceLoader::load(const WebCore::ResourceRequest & r={...}, const WebCore::SubstituteData & substituteData={...})  行494 + 0x12 字節    C++
 3      QtWebKitd4.dll!WebCore::DocumentLoader::startLoadingMainResource(unsigned long identifier=0x00000004)  行807 + 0x32 字節    C++
 4      QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterWillSubmitForm(WebCore::PolicyAction __formal=PolicyUse)  行3274 + 0x16 字節    C++
 5      QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterNavigationPolicy(const WebCore::ResourceRequest & __formal={...}, WTF::PassRefPtr<WebCore::FormState> formState={...}, bool shouldContinue=true)  行3968    C++
 6      QtWebKitd4.dll!WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy(void * argument=0x01d424e8, const WebCore::ResourceRequest & request={...}, WTF::PassRefPtr<WebCore::FormState> formState={...}, bool shouldContinue=true)  行3906    C++
 7      QtWebKitd4.dll!WebCore::PolicyCheck::call(bool shouldContinue=true)  行4963 + 0x3b 字節    C++
 8      QtWebKitd4.dll!WebCore::FrameLoader::continueAfterNavigationPolicy(WebCore::PolicyAction policy=PolicyUse)  行3899    C++
 9      QtWebKitd4.dll!WebCore::FrameLoaderClientQt::slotCallPolicyFunction(int action=0x00000000)  行194    C++
10      QtWebKitd4.dll!WebCore::FrameLoaderClientQt::dispatchDecidePolicyForNavigationAction(void (WebCore::PolicyAction)* function=0x10018f0c, const WebCore::NavigationAction & action={...}, const WebCore::ResourceRequest & request={...}, WTF::PassRefPtr<WebCore::FormState> __formal={...})  行938    C++
11      QtWebKitd4.dll!WebCore::FrameLoader::checkNavigationPolicy(const WebCore::ResourceRequest & request={...}, WebCore::DocumentLoader * loader=0x00f63ff8, WTF::PassRefPtr<WebCore::FormState> formState={...}, void (void *, const WebCore::ResourceRequest &, WTF::PassRefPtr<WebCore::FormState>, bool)* function=0x1004e661, void * argument=0x01d424e8)  行3868    C++
12      QtWebKitd4.dll!WebCore::FrameLoader::loadWithDocumentLoader(WebCore::DocumentLoader * loader=0x00f63ff8, WebCore::FrameLoadType type=FrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtr<WebCore::FormState> prpFormState={...})  行2291    C++
13      QtWebKitd4.dll!WebCore::FrameLoader::loadWithNavigationAction(const WebCore::ResourceRequest & request={...}, const WebCore::NavigationAction & action={...}, WebCore::FrameLoadType type=FrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtr<WebCore::FormState> formState={...})  行2226    C++
14      QtWebKitd4.dll!WebCore::FrameLoader::loadURL(const WebCore::KURL & newURL={...}, const WebCore::String & referrer={...}, const WebCore::String & frameName={...}, WebCore::FrameLoadType newLoadType=FrameLoadTypeRedirectWithLockedHistory, WebCore::Event * event=0x00000000, WTF::PassRefPtr<WebCore::FormState> prpFormState={...})  行2174    C++
15      QtWebKitd4.dll!WebCore::FrameLoaderClientQt::createFrame(const WebCore::KURL & url={...}, const WebCore::String & name={...}, WebCore::HTMLFrameOwnerElement * ownerElement=0x00f681a0, const WebCore::String & referrer={...}, bool allowsScrolling=false, int marginWidth=0xffffffff, int marginHeight=0xffffffff)  行981 + 0x70 字節    C++
16      QtWebKitd4.dll!WebCore::FrameLoader::loadSubframe(WebCore::HTMLFrameOwnerElement * ownerElement=0x00f681a0, const WebCore::KURL & url={...}, const WebCore::String & name={...}, const WebCore::String & referrer={...})  行472 + 0x74 字節    C++
17      QtWebKitd4.dll!WebCore::FrameLoader::requestFrame(WebCore::HTMLFrameOwnerElement * ownerElement=0x00f681a0, const WebCore::String & urlString={...}, const WebCore::AtomicString & frameName={...})  行442 + 0x29 字節    C++
18      QtWebKitd4.dll!WebCore::HTMLFrameElementBase::openURL()  行105    C++
19      QtWebKitd4.dll!WebCore::HTMLFrameElementBase::setNameAndOpenURL()  行161    C++
20      QtWebKitd4.dll!WebCore::HTMLFrameElementBase::setNameAndOpenURLCallback(WebCore::Node * n=0x00f681a0)  行166    C++
21      QtWebKitd4.dll!WebCore::ContainerNode::dispatchPostAttachCallbacks()  行572 + 0x7 字節    C++
22      QtWebKitd4.dll!WebCore::ContainerNode::attach()  行587    C++
23      QtWebKitd4.dll!WebCore::Element::attach()  行648    C++
24      QtWebKitd4.dll!WebCore::HTMLFrameElementBase::attach()  行194    C++
25      QtWebKitd4.dll!WebCore::HTMLFrameElement::attach()  行67    C++
26      QtWebKitd4.dll!WebCore::HTMLParser::insertNode(WebCore::Node * n=0x00f681a0, bool flat=false)  行351    C++
27      QtWebKitd4.dll!WebCore::HTMLParser::parseToken(WebCore::Token * t=0x00f65fd0)  行256 + 0x19 字節    C++
28 >    QtWebKitd4.dll!WebCore::HTMLTokenizer::processToken()  行1902 + 0x20 字節    C++
29      QtWebKitd4.dll!WebCore::HTMLTokenizer::parseTag(WebCore::SegmentedString & src={...}, WebCore::HTMLTokenizer::State state={...})  行1484 + 0x12 字節    C++
30      QtWebKitd4.dll!WebCore::HTMLTokenizer::write(const WebCore::SegmentedString & str={...}, bool appendData=true)  行1730 + 0x23 字節    C++
31      QtWebKitd4.dll!WebCore::FrameLoader::write(const char * str=0x01d3f5c0, int len=0x000001df, bool flush=false)  行1039 + 0x23 字節    C++
32      QtWebKitd4.dll!WebCore::FrameLoader::addData(const char * bytes=0x01d3f5c0, int length=0x000001df)  行1891    C++
33      QtWebKitd4.dll!WebCore::FrameLoaderClientQt::committedLoad(WebCore::DocumentLoader * loader=0x00f881e0, const char * data=0x01d3f5c0, int length=0x000001df)  行680    C++
34      QtWebKitd4.dll!WebCore::FrameLoader::committedLoad(WebCore::DocumentLoader * loader=0x00f881e0, const char * data=0x01d3f5c0, int length=0x000001df)  行3513    C++
35      QtWebKitd4.dll!WebCore::DocumentLoader::commitLoad(const char * data=0x01d3f5c0, int length=0x000001df)  行356    C++
36      QtWebKitd4.dll!WebCore::DocumentLoader::receivedData(const char * data=0x01d3f5c0, int length=0x000001df)  行368    C++
37      QtWebKitd4.dll!WebCore::FrameLoader::receivedData(const char * data=0x01d3f5c0, int length=0x000001df)  行2342    C++
38      QtWebKitd4.dll!WebCore::MainResourceLoader::addData(const char * data=0x01d3f5c0, int length=0x000001df, bool allAtOnce=false)  行147    C++
39      QtWebKitd4.dll!WebCore::ResourceLoader::didReceiveData(const char * data=0x01d3f5c0, int length=0x000001df, __int64 lengthReceived=0x00000000000001df, bool allAtOnce=false)  行267    C++
40      QtWebKitd4.dll!WebCore::MainResourceLoader::didReceiveData(const char * data=0x01d3f5c0, int length=0x000001df, __int64 lengthReceived=0x00000000000001df, bool allAtOnce=false)  行342    C++
41      QtWebKitd4.dll!WebCore::ResourceLoader::didReceiveData(WebCore::ResourceHandle * __formal=0x00fb9aa0, const char * data=0x01d3f5c0, int length=0x000001df, int lengthReceived=0x000001df)  行418    C++
42      QtWebKitd4.dll!WebCore::QNetworkReplyHandler::forwardData()  行341    C++
43      QtWebKitd4.dll!WebCore::QNetworkReplyHandler::qt_metacall(QMetaObject::Call _c=InvokeMetaMethod, int _id=0x00000002, void * * _a=0x00fba378)  行74    C++
44      QtCored4.dll!QMetaCallEvent::placeMetaCall(QObject * object=0x00f810d0)  行478    C++
45      QtCored4.dll!QObject::event(QEvent * e=0x01d3ee18)  行1102 + 0x14 字節    C++
46      QtGuid4.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x00f810d0, QEvent * e=0x01d3ee18)  行4065 + 0x11 字節    C++
47      QtGuid4.dll!QApplication::notify(QObject * receiver=0x00f810d0, QEvent * e=0x01d3ee18)  行3605 + 0x10 字節    C++
48      QtCored4.dll!QCoreApplication::notifyInternal(QObject * receiver=0x00f810d0, QEvent * event=0x01d3ee18)  行610 + 0x15 字節    C++
49      QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver=0x00f810d0, QEvent * event=0x01d3ee18)  行213 + 0x39 字節    C++
50      QtCored4.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000, QThreadData * data=0x00e78f60)  行1247 + 0xd 字節    C++
51      QtCored4.dll!QEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...})  行679 + 0x10 字節    C++
52      QtGuid4.dll!QGuiEventDispatcherWin32::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...})  行1182 + 0x15 字節    C++
53      QtCored4.dll!QEventLoop::processEvents(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...})  行150    C++
54      QtCored4.dll!QEventLoop::exec(QFlags<enum QEventLoop::ProcessEventsFlag> flags={...})  行201 + 0x2d 字節    C++
55      QtCored4.dll!QCoreApplication::exec()  行888 + 0x15 字節    C++
56      QtGuid4.dll!QApplication::exec()  行3526    C++
57      previewer.exe!main(int argc=0x00000001, char * * argv=0x00e78e20)  行51 + 0x6 字節    C++
58      previewer.exe!WinMain(HINSTANCE__ * instance=0x00400000, HINSTANCE__ * prevInstance=0x00000000, char * __formal=0x001520d9, int cmdShow=0x00000001)  行137 + 0x12 字節    C++
59      previewer.exe!__tmainCRTStartup()  行574 + 0x35 字節    C
60      previewer.exe!WinMainCRTStartup()  行399    C
61      kernel32.dll!7c82f23b()     
複製代碼

 

 

 

[下面的框架可能不正確和/或缺失,沒有爲 kernel32.dll 加載符號]   

 

分三個階段對QWebView進行分析:初始化(獲取數據)、HTML解析、頁面顯示。從QT自帶的文檔中可以知道

1 QWebView -> QWebPage => QWebFrame(一個QWebPage含多個QWebFrame)

在界面中選擇了Open URL,輸入URL之後,調用的是:void MainWindow::openUrl()

複製代碼
 1 void MainWindow::openUrl()
 2 {
 3     bool ok;
 4     QString url = QInputDialog::getText(this, tr("Enter a URL"),
 5                   tr("URL:"), QLineEdit::Normal, "http://", &ok);
 6 
 7     if (ok && !url.isEmpty()) {
 8         centralWidget->webView->setUrl(url);
 9     }
10 }
複製代碼

調用的是QWebView::setUrl()

1 void QWebView::setUrl(const QUrl &url)
2 {
3     page()->mainFrame()->setUrl(url);
4 }

其中page()是獲取QWebPage指針,QWebPage::mainFrame()獲取的是QWebFrame指針

所以調用的是:QWebFrame::setUrl()

1 void QWebFrame::setUrl(const QUrl &url)
2 {
3     d->frame->loader()->begin(ensureAbsoluteUrl(url));
4     d->frame->loader()->end();
5     load(ensureAbsoluteUrl(url));
6 }

ensureAbsoluteUrl()函數作用是,確保URL是絕對URL(完整URL)。所謂相對URL是指沒有輸入http://或者https://等前綴的web地址。先看第一句的調用。其中隱含了從QUrl到KURL的變換。

複製代碼
 1 void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin)
 2 {
 3     // We need to take a reference to the security origin because |clear|
 4     // might destroy the document that owns it.
 5     RefPtr<SecurityOrigin> forcedSecurityOrigin = origin;
 6 
 7     bool resetScripting = !(m_isDisplayingInitialEmptyDocument && m_frame->document() && m_frame->document()->securityOrigin()->isSecureTransitionTo(url));
 8     clear(resetScripting, resetScripting);      // 清除上一次的數據,爲本次裝載準備
 9     if (resetScripting)   
10         m_frame->script()->updatePlatformScriptObjects();    // 在Windows平臺下,這是空函數
11     if (dispatch)
12         dispatchWindowObjectAvailable();
13 
14     m_needsClear = true;
15     m_isComplete = false;
16     m_didCallImplicitClose = false;
17     m_isLoadingMainResource = true;
18     m_isDisplayingInitialEmptyDocument = m_creatingInitialEmptyDocument;
19 
20     KURL ref(url);
21     ref.setUser(String());
22     ref.setPass(String());
23     ref.setRef(String());
24     m_outgoingReferrer = ref.string();
25     m_URL = url;
26 
27     RefPtr<Document> document;
28     
29     if (!m_isDisplayingInitialEmptyDocument && m_client->shouldUsePluginDocument(m_responseMIMEType))
30         document = PluginDocument::create(m_frame);
31     else
32         document = DOMImplementation::createDocument(m_responseMIMEType, m_frame, m_frame->inViewSourceMode());    // 創建DOM文件,m_responseMIMEType不同實體不同。
33 
34 // 如果是"text/html"創建HTMLDocument實體;"application/xhtml+xml"創建Document實體
35 
36 // 如果是"application/x-ftp-directory"則是FTPDirectoryDocument實體
37 
38 // text/vnd.wap.wml 對應 WMLDocument 實體(無線)
39 
40 // "application/pdf" /"text/plain" 對應 PluginDocument實體
41 
42 // 如果是MediaPlayer::supportsType(type),創建的是MediaDocument實體
43 
44 // "image/svg+xml" 對應 SVGDocument實體
45     m_frame->setDocument(document);
46 
47     document->setURL(m_URL);
48     if (m_decoder)
49         document->setDecoder(m_decoder.get());
50     if (forcedSecurityOrigin)
51         document->setSecurityOrigin(forcedSecurityOrigin.get());
52 
53     m_frame->domWindow()->setURL(document->url());
54     m_frame->domWindow()->setSecurityOrigin(document->securityOrigin());
55 
56     updatePolicyBaseURL();   // 更新排布策略的基礎URL
57 
58     Settings* settings = document->settings();
59     document->docLoader()->setAutoLoadImages(settings && settings->loadsImagesAutomatically());
60 
61     if (m_documentLoader) {
62         String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control");
63         if (!dnsPrefetchControl.isEmpty())
64             document->parseDNSPrefetchControlHeader(dnsPrefetchControl);
65     }
66 
67 #if FRAME_LOADS_USER_STYLESHEET
68     KURL userStyleSheet = settings ? settings->userStyleSheetLocation() : KURL();
69     if (!userStyleSheet.isEmpty())
70         m_frame->setUserStyleSheetLocation(userStyleSheet);
71 #endif
72 
73     restoreDocumentState();
74 
75     document->implicitOpen();
76 
77     if (m_frame->view())
78         m_frame->view()->setContentsSize(IntSize());
79 
80 #if USE(LOW_BANDWIDTH_DISPLAY)
81     // Low bandwidth display is a first pass display without external resources
82     // used to give an instant visual feedback. We currently only enable it for
83     // HTML documents in the top frame.
84     if (document->isHTMLDocument() && !m_frame->tree()->parent() && m_useLowBandwidthDisplay) {
85         m_pendingSourceInLowBandwidthDisplay = String();
86         m_finishedParsingDuringLowBandwidthDisplay = false;
87         m_needToSwitchOutLowBandwidthDisplay = false;
88         document->setLowBandwidthDisplay(true);
89     }
90 #endif
91 }
複製代碼

看其中document->implicitOpen()的代碼:

複製代碼
 1 void Document::implicitOpen()
 2 {
 3     cancelParsing();
 4 
 5     clear();
 6     m_tokenizer = createTokenizer();
 7     setParsing(true);
 8 }
 9 
10 Tokenizer *HTMLDocument::createTokenizer()
11 {
12     bool reportErrors = false;
13     if (frame())
14         if (Page* page = frame()->page())
15             reportErrors = page->inspectorController()->windowVisible();
16 
17     return new HTMLTokenizer(this, reportErrors);
18 }
複製代碼

新創建的HTMLTokenizer對象,就是HTML的解析器。

回到QWebFrame::setUrl()的第二句:d->frame->loader()->end();

只是把上次未完的解析停止:

複製代碼
 1 void FrameLoader::endIfNotLoadingMainResource()
 2 {
 3     if (m_isLoadingMainResource || !m_frame->page())
 4         return;
 5 
 6     // http://bugs.webkit.org/show_bug.cgi?id=10854
 7     // The frame's last ref may be removed and it can be deleted by checkCompleted(), 
 8     // so we'll add a protective refcount
 9     RefPtr<Frame> protector(m_frame);
10 
11     // make sure nothing's left in there
12     if (m_frame->document()) {
13         write(0, 0, true);
14         m_frame->document()->finishParsing();
15    } else
16         // WebKit partially uses WebCore when loading non-HTML docs.  In these cases doc==nil, but
17         // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to
18         // become true.  An example is when a subframe is a pure text doc, and that subframe is the
19         // last one to complete.
20         checkCompleted();
21 }
複製代碼

再來看QWebFrame::setUrl()的第三句:load(ensureAbsoluteUrl(url));

1 void QWebFrame::load(const QUrl &url)
2 {
3    load(QNetworkRequest(ensureAbsoluteUrl(url)));
4 }

新建一個QNetworkRequest對象,然後調用

1 void load(const QNetworkRequest &request,
2               QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
3               const QByteArray &body = QByteArray());

看其代碼:

複製代碼
 1 void QWebFrame::load(const QNetworkRequest &req,
 2                      QNetworkAccessManager::Operation operation,
 3                      const QByteArray &body)
 4 {
 5     if (d->parentFrame())
 6         d->page->d->insideOpenCall = true;
 7 
 8     QUrl url = ensureAbsoluteUrl(req.url());
 9 
10     WebCore::ResourceRequest request(url);
11 
12     switch (operation) {
13         case QNetworkAccessManager::HeadOperation:
14             request.setHTTPMethod("HEAD");
15             break;
16         case QNetworkAccessManager::GetOperation:
17             request.setHTTPMethod("GET");
18             break;
19         case QNetworkAccessManager::PutOperation:
20             request.setHTTPMethod("PUT");
21             break;
22         case QNetworkAccessManager::PostOperation:
23             request.setHTTPMethod("POST");
24             break;
25         case QNetworkAccessManager::UnknownOperation:
26             // eh?
27             break;
28     }
29 
30     QList<QByteArray> httpHeaders = req.rawHeaderList();
31     for (int i = 0; i < httpHeaders.size(); ++i) {
32         const QByteArray &headerName = httpHeaders.at(i);
33         request.addHTTPHeaderField(QString::fromLatin1(headerName), QString::fromLatin1(req.rawHeader(headerName)));
34     }
35 
36     if (!body.isEmpty())
37         request.setHTTPBody(WebCore::FormData::create(body.constData(), body.size()));
38 
39     d->frame->loader()->load(request);
40 
41     if (d->parentFrame())
42         d->page->d->insideOpenCall = false;
43 }
複製代碼

看關鍵的FrameLoader::load()

複製代碼
 1 void FrameLoader::load(const ResourceRequest& request)
 2 {
 3     load(request, SubstituteData());
 4 }
 5 
 6 void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData)
 7 {
 8     if (m_inStopAllLoaders)
 9         return;
10         
11     // FIXME: is this the right place to reset loadType? Perhaps this should be done after loading is finished or aborted.
12     m_loadType = FrameLoadTypeStandard;
13     load(m_client->createDocumentLoader(request, substituteData).get());
14 }
複製代碼

 

上面m_client對應的是FrameLoaderClientQt實體,m_client->createDocumentLoader()創建的是DocumentLoader對象。進一步看FrameLoader::load(DocumentLoader *)的代碼:

複製代碼
 1 void FrameLoader::load(DocumentLoader* newDocumentLoader)
 2 {
 3     ResourceRequest& r = newDocumentLoader->request();
 4     addExtraFieldsToMainResourceRequest(r);
 5     FrameLoadType type;
 6 
 7     if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->originalRequest().url())) {
 8         r.setCachePolicy(ReloadIgnoringCacheData);
 9         type = FrameLoadTypeSame;
10     } else
11         type = FrameLoadTypeStandard;
12 
13     if (m_documentLoader)
14         newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding());
15     
16     // When we loading alternate content for an unreachable URL that we're
17     // visiting in the history list, we treat it as a reload so the history list 
18     // is appropriately maintained.
19     //
20     // FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadTypeReload" ...
21     // shouldn't a more explicit type of reload be defined, that means roughly 
22     // "load without affecting history" ? 
23     if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) {
24         ASSERT(type == FrameLoadTypeStandard);
25         type = FrameLoadTypeReload;
26     }
27 
28     loadWithDocumentLoader(newDocumentLoader, type, 0);
29 }
複製代碼

看FrameLoader::loadWithDocumentLoader()的代碼:

複製代碼
 1 void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState)
 2 {
 3     ASSERT(m_client->hasWebView());
 4 
 5     // Unfortunately the view must be non-nil, this is ultimately due
 6     // to parser requiring a FrameView.  We should fix this dependency.
 7 
 8     ASSERT(m_frame->view());
 9 
10     m_policyLoadType = type;
11     RefPtr<FormState> formState = prpFormState;
12     bool isFormSubmission = formState;
13 
14     const KURL& newURL = loader->request().url();
15 
16     if (shouldScrollToAnchor(isFormSubmission, m_policyLoadType, newURL)) {
17         RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader;
18         NavigationAction action(newURL, m_policyLoadType, isFormSubmission);
19 
20         oldDocumentLoader->setTriggeringAction(action);
21         stopPolicyCheck();
22         checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState,
23             callContinueFragmentScrollAfterNavigationPolicy, this);
24     } else {
25         if (Frame* parent = m_frame->tree()->parent())
26             loader->setOverrideEncoding(parent->loader()->documentLoader()->overrideEncoding());
27 
28         stopPolicyCheck();
29         setPolicyDocumentLoader(loader);
30 
31         checkNavigationPolicy(loader->request(), loader, formState,
32             callContinueLoadAfterNavigationPolicy, this);
33     }
34 }
複製代碼

上面調用checkNavigationPolicy()是關鍵,看其實現:

複製代碼
 1 void FrameLoader::checkNavigationPolicy(const ResourceRequest& request, DocumentLoader* loader,
 2     PassRefPtr<FormState> formState, NavigationPolicyDecisionFunction function, void* argument)
 3 {
 4     NavigationAction action = loader->triggeringAction();
 5     if (action.isEmpty()) {
 6         action = NavigationAction(request.url(), NavigationTypeOther);
 7         loader->setTriggeringAction(action);
 8     }
 9         
10     // Don't ask more than once for the same request or if we are loading an empty URL.
11     // This avoids confusion on the part of the client.
12     if (equalIgnoringHeaderFields(request, loader->lastCheckedRequest()) || (!request.isNull() && request.url().isEmpty())) {
13         function(argument, request, 0, true);
14         loader->setLastCheckedRequest(request);
15         return;
16     }
17     
18     // We are always willing to show alternate content for unreachable URLs;
19     // treat it like a reload so it maintains the right state for b/f list.
20     if (loader->substituteData().isValid() && !loader->substituteData().failingURL().isEmpty()) {
21         if (isBackForwardLoadType(m_policyLoadType))
22             m_policyLoadType = FrameLoadTypeReload;
23         function(argument, request, 0, true);
24         return;
25     }
26     
27     loader->setLastCheckedRequest(request);
28 
29     m_policyCheck.set(request, formState.get(), function, argument);
30 
31     m_delegateIsDecidingNavigationPolicy = true;
32     m_client->dispatchDecidePolicyForNavigationAction(&FrameLoader::continueAfterNavigationPolicy,
33         action, request, formState);
34     m_delegateIsDecidingNavigationPolicy = false;
35 }
複製代碼

其中m_client是FrameLoaderClientQt實體指針

複製代碼
 1 void FrameLoaderClientQt::dispatchDecidePolicyForNavigationAction(FramePolicyFunction function, const WebCore::NavigationAction& action, const WebCore::ResourceRequest& request, PassRefPtr<WebCore::FormState>)
 2 {
 3     Q_ASSERT(!m_policyFunction);
 4     Q_ASSERT(m_webFrame);
 5     m_policyFunction = function;
 6 #if QT_VERSION < 0x040400
 7     QWebNetworkRequest r(request);
 8 #else
 9     QNetworkRequest r(request.toNetworkRequest());
10 #endif
11     QWebPage*page = m_webFrame->page();
12 
13     if (!page->d->acceptNavigationRequest(m_webFrame, r, QWebPage::NavigationType(action.type()))) {
14         if (action.type() == NavigationTypeFormSubmitted || action.type() == NavigationTypeFormResubmitted)
15             m_frame->loader()->resetMultipleFormSubmissionProtection();
16 
17         if (action.type() == NavigationTypeLinkClicked && r.url().hasFragment()) {
18             ResourceRequest emptyRequest;
19             m_frame->loader()->activeDocumentLoader()->setLastCheckedRequest(emptyRequest);
20         }
21 
22         slotCallPolicyFunction(PolicyIgnore);
23         return;
24     }
25     slotCallPolicyFunction(PolicyUse);
26 }
27 void FrameLoaderClientQt::slotCallPolicyFunction(int action)
28 {
29     if (!m_frame || !m_policyFunction)
30         return;
31     FramePolicyFunction function = m_policyFunction;
32     m_policyFunction = 0;
33     (m_frame->loader()->*function)(WebCore::PolicyAction(action));
34 }
複製代碼

用函數指針回調,FrameLoader::continueAfterNavigationPolicy(PolicyAction policy),參數爲PolicyUse

 

複製代碼
 1 void FrameLoader::continueAfterNavigationPolicy(PolicyAction policy)
 2 {
 3     PolicyCheck check = m_policyCheck;
 4     m_policyCheck.clear();
 5 
 6     bool shouldContinue = policy == PolicyUse;
 7     
 8     switch (policy) {
 9         case PolicyIgnore:
10             check.clearRequest();
11             break;
12         case PolicyDownload:
13             m_client->startDownload(check.request());
14             check.clearRequest();
15             break;
16         case PolicyUse: {
17             ResourceRequest request(check.request());
18             
19             if (!m_client->canHandleRequest(request)) {
20                 handleUnimplementablePolicy(m_client->cannotShowURLError(check.request()));
21                 check.clearRequest();
22                 shouldContinue = false;
23             }
24             break;
25         }
26     }
27 
28     check.call(shouldContinue);
29 }
複製代碼

 

上面調用的是PolicyCheck::call(),參數爲true

複製代碼
1 void PolicyCheck::call(bool shouldContinue)
2 {
3     if (m_navigationFunction)
4         m_navigationFunction(m_argument, m_request, m_formState.get(), shouldContinue);
5     if (m_newWindowFunction)
6         m_newWindowFunction(m_argument, m_request, m_formState.get(), m_frameName, shouldContinue);
7     ASSERT(!m_contentFunction);
8 }
複製代碼

m_navigationFunction又是一個函數指針,指向的是FrameLoader::callContinueLoadAfterNavigationPolicy()

複製代碼
 1 void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument,
 2     const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue)
 3 {
 4     FrameLoader* loader = static_cast<FrameLoader*>(argument);
 5     loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue);
 6 }
 7 
 8 void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue)
 9 {
10     // If we loaded an alternate page to replace an unreachableURL, we'll get in here with a
11     // nil policyDataSource because loading the alternate page will have passed
12     // through this method already, nested; otherwise, policyDataSource should still be set.
13     ASSERT(m_policyDocumentLoader || !m_provisionalDocumentLoader->unreachableURL().isEmpty());
14 
15     bool isTargetItem = m_provisionalHistoryItem ? m_provisionalHistoryItem->isTargetItem() : false;
16 
17     // Two reasons we can't continue:
18     //    1) Navigation policy delegate said we can't so request is nil. A primary case of this 
19     //       is the user responding Cancel to the form repost nag sheet.
20     //    2) User responded Cancel to an alert popped up by the before unload event handler.
21     // The "before unload" event handler runs only for the main frame.
22     bool canContinue = shouldContinue && (!isLoadingMainFrame() || m_frame->shouldClose());
23 
24     if (!canContinue) {
25         // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we 
26         // need to report that the client redirect was cancelled.
27         if (m_quickRedirectComing)
28             clientRedirectCancelledOrFinished(false);
29 
30         setPolicyDocumentLoader(0);
31 
32         // If the navigation request came from the back/forward menu, and we punt on it, we have the 
33         // problem that we have optimistically moved the b/f cursor already, so move it back.  For sanity, 
34         // we only do this when punting a navigation for the target frame or top-level frame.  
35         if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(m_policyLoadType))
36             if (Page* page = m_frame->page()) {
37                 Frame* mainFrame = page->mainFrame();
38                 if (HistoryItem* resetItem = mainFrame->loader()->m_currentHistoryItem.get()) {
39                     page->backForwardList()->goToItem(resetItem);
40                     Settings* settings = m_frame->settings();
41                     page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : resetItem);
42                 }
43             }
44         return;
45     }
46 
47     FrameLoadType type = m_policyLoadType;
48     stopAllLoaders();
49     
50     // <rdar://problem/6250856> - In certain circumstances on pages with multiple frames, stopAllLoaders()
51     // might detach the current FrameLoader, in which case we should bail on this newly defunct load. 
52     if (!m_frame->page())
53         return;
54         
55     setProvisionalDocumentLoader(m_policyDocumentLoader.get());
56     m_loadType = type;
57     setState(FrameStateProvisional);
58 
59     setPolicyDocumentLoader(0);
60 
61     if (isBackForwardLoadType(type) && loadProvisionalItemFromCachedPage())
62         return;
63 
64     if (formState)
65         m_client->dispatchWillSubmitForm(&FrameLoader::continueLoadAfterWillSubmitForm, formState);
66     else
67         continueLoadAfterWillSubmitForm();
68 }
69 
70 void FrameLoader::continueLoadAfterWillSubmitForm(PolicyAction)
71 {
72     if (!m_provisionalDocumentLoader)
73         return;
74 
75     // DocumentLoader calls back to our prepareForLoadStart
76     m_provisionalDocumentLoader->prepareForLoadStart();
77     
78     // The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader, 
79     // so we need to null check it again.
80     if (!m_provisionalDocumentLoader)
81         return;
82     // 先看活動的DocumentLoader能否裝載
83     DocumentLoader* activeDocLoader = activeDocumentLoader();
84     if (activeDocLoader && activeDocLoader->isLoadingMainResource())
85         return;
86     // 看Cache中能否裝載
87     m_provisionalDocumentLoader->setLoadingFromCachedPage(false);
88 
89     unsigned long identifier = 0;
90 
91     if (Page* page = m_frame->page()) {
92         identifier = page->progress()->createUniqueIdentifier();
93         dispatchAssignIdentifierToInitialRequest(identifier, m_provisionalDocumentLoader.get(), m_provisionalDocumentLoader->originalRequest());
94     }
95 
96     if (!m_provisionalDocumentLoader->startLoadingMainResource(identifier))
97         m_provisionalDocumentLoader->updateLoading();
98 }
複製代碼

上面的裝載過程,如果是第一次並且只有m_provisionalDocumentLoader的話,只會執行最後一中裝載。

複製代碼
 1 bool DocumentLoader::startLoadingMainResource(unsigned long identifier)
 2 {
 3     ASSERT(!m_mainResourceLoader);
 4     m_mainResourceLoader = MainResourceLoader::create(m_frame);
 5     m_mainResourceLoader->setIdentifier(identifier);
 6 
 7     // FIXME: Is there any way the extra fields could have not been added by now?
 8     // If not, it would be great to remove this line of code.
 9     frameLoader()->addExtraFieldsToMainResourceRequest(m_request);
10 
11     if (!m_mainResourceLoader->load(m_request, m_substituteData)) {
12         // FIXME: If this should really be caught, we should just ASSERT this doesn't happen;
13         // should it be caught by other parts of WebKit or other parts of the app?
14         LOG_ERROR("could not create WebResourceHandle for URL %s -- should be caught by policy handler level", m_request.url().string().ascii().data());
15         m_mainResourceLoader = 0;
16         return false;
17     }
18 
19     return true;
20 }
複製代碼

創建MainResourceLoader對象,並調用load()

複製代碼
 1 bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData)
 2 {
 3     ASSERT(!m_handle);
 4 
 5     m_substituteData = substituteData;
 6 
 7 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
 8     // Check if this request should be loaded from the application cache
 9     if (!m_substituteData.isValid() && frameLoader()->frame()->settings() && frameLoader()->frame()->settings()->offlineWebApplicationCacheEnabled()) {
10         ASSERT(!m_applicationCache);
11 
12         m_applicationCache = ApplicationCacheGroup::cacheForMainRequest(r, m_documentLoader.get());
13 
14         if (m_applicationCache) {
15             // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource.
16             ApplicationCacheResource* resource = m_applicationCache->resourceForRequest(r);
17             m_substituteData = SubstituteData(resource->data(), 
18                                               resource->response().mimeType(),
19                                               resource->response().textEncodingName(), KURL());
20         }
21     }
22 #endif
23 
24     ResourceRequest request(r);
25     bool defer = defersLoading();
26     if (defer) {
27         bool shouldLoadEmpty = shouldLoadAsEmptyDocument(r.url());
28         if (shouldLoadEmpty)
29             defer = false;
30     }
31     if (!defer) {
32         if (loadNow(request)) {
33             // Started as an empty document, but was redirected to something non-empty.
34             ASSERT(defersLoading());
35             defer = true;
36         }
37     }
38     if (defer)
39         m_initialRequest = request;
40 
41     return true;
42 }
複製代碼

繼續深入看MainResourceLoader::loadNow()

複製代碼
 1 bool MainResourceLoader::loadNow(ResourceRequest& r)
 2 {
 3     bool shouldLoadEmptyBeforeRedirect = shouldLoadAsEmptyDocument(r.url());
 4 
 5     ASSERT(!m_handle);
 6     ASSERT(shouldLoadEmptyBeforeRedirect || !defersLoading());
 7 
 8     // Send this synthetic delegate callback since clients expect it, and
 9     // we no longer send the callback from within NSURLConnection for
10     // initial requests.
11     willSendRequest(r, ResourceResponse());
12 
13     // <rdar://problem/4801066>
14     // willSendRequest() is liable to make the call to frameLoader() return NULL, so we need to check that here
15     if (!frameLoader())
16         return false;
17     
18     const KURL& url = r.url();
19     bool shouldLoadEmpty = shouldLoadAsEmptyDocument(url) && !m_substituteData.isValid();
20 
21     if (shouldLoadEmptyBeforeRedirect && !shouldLoadEmpty && defersLoading())
22         return true;
23 
24     if (m_substituteData.isValid()) 
25         handleDataLoadSoon(r);
26     else if (shouldLoadEmpty || frameLoader()->representationExistsForURLScheme(url.protocol()))
27         handleEmptyLoad(url, !shouldLoadEmpty);
28     else
29         m_handle = ResourceHandle::create(r, this, m_frame.get(), false, true, true);
30 
31     return false;
32 }
複製代碼

主要兩個調用:willSendRequest()和ResourceHandle::create(),前面一個估計是發送請求前的相關設定;後一個就是請求發送了。先看前一個:

複製代碼
 1 void MainResourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
 2 {
 3     // Note that there are no asserts here as there are for the other callbacks. This is due to the
 4     // fact that this "callback" is sent when starting every load, and the state of callback
 5     // deferrals plays less of a part in this function in preventing the bad behavior deferring 
 6     // callbacks is meant to prevent.
 7     ASSERT(!newRequest.isNull());
 8     
 9     // The additional processing can do anything including possibly removing the last
10     // reference to this object; one example of this is 3266216.
11     RefPtr<MainResourceLoader> protect(this);
12     
13     // Update cookie policy base URL as URL changes, except for subframes, which use the
14     // URL of the main frame which doesn't change when we redirect.
15     if (frameLoader()->isLoadingMainFrame())
16         newRequest.setMainDocumentURL(newRequest.url());
17     
18     // If we're fielding a redirect in response to a POST, force a load from origin, since
19     // this is a common site technique to return to a page viewing some data that the POST
20     // just modified.
21     // Also, POST requests always load from origin, but this does not affect subresources.
22     if (newRequest.cachePolicy() == UseProtocolCachePolicy && isPostOrRedirectAfterPost(newRequest, redirectResponse))
23         newRequest.setCachePolicy(ReloadIgnoringCacheData);
24 
25     ResourceLoader::willSendRequest(newRequest, redirectResponse);
26     
27     // Don't set this on the first request. It is set when the main load was started.
28     m_documentLoader->setRequest(newRequest);
29 
30     // FIXME: Ideally we'd stop the I/O until we hear back from the navigation policy delegate
31     // listener. But there's no way to do that in practice. So instead we cancel later if the
32     // listener tells us to. In practice that means the navigation policy needs to be decided
33     // synchronously for these redirect cases.
34 
35     ref(); // balanced by deref in continueAfterNavigationPolicy
36     frameLoader()->checkNavigationPolicy(newRequest, callContinueAfterNavigationPolicy, this);
37 }
複製代碼

主要是調用ResourceLoader::willSendRequest()函數:

複製代碼
 1 void ResourceLoader::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
 2 {
 3     // Protect this in this delegate method since the additional processing can do
 4     // anything including possibly derefing this; one example of this is Radar 3266216.
 5     RefPtr<ResourceLoader> protector(this);
 6         
 7     ASSERT(!m_reachedTerminalState);
 8 
 9     if (m_sendResourceLoadCallbacks) {
10         if (!m_identifier) {
11             m_identifier = m_frame->page()->progress()->createUniqueIdentifier();
12             frameLoader()->assignIdentifierToInitialRequest(m_identifier, request);
13         }
14 
15         frameLoader()->willSendRequest(this, request, redirectResponse);
16     }
17     
18     m_request = request;
19 }
複製代碼

進一步調用FrameLoader::willSendRequest()

1 void FrameLoader::willSendRequest(ResourceLoader* loader, ResourceRequest& clientRequest, const ResourceResponse& redirectResponse)
2 {
3     applyUserAgent(clientRequest);
4     dispatchWillSendRequest(loader->documentLoader(), loader->identifier(), clientRequest, redirectResponse);
5 }

更多的調用:

複製代碼
 1 void FrameLoader::dispatchWillSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse)
 2 {
 3     StringImpl* oldRequestURL = request.url().string().impl();
 4     m_documentLoader->didTellClientAboutLoad(request.url());
 5 
 6     m_client->dispatchWillSendRequest(loader, identifier, request, redirectResponse);
 7 
 8     // If the URL changed, then we want to put that new URL in the "did tell client" set too.
 9     if (oldRequestURL != request.url().string().impl())
10         m_documentLoader->didTellClientAboutLoad(request.url());
11 
12     if (Page* page = m_frame->page())
13         page->inspectorController()->willSendRequest(loader, identifier, request, redirectResponse);
14 }
複製代碼

囧~~還有下一步嗎??
m_client->dispatchWillSendRequest()實際調用的是FrameLoaderClientQt::dispatchWillSendRequest(),目前是一個空函數(僅在dump的時候打印信息)。

複製代碼
 1 void InspectorController::willSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse)
 2 {
 3     if (!enabled())
 4         return;
 5 
 6     InspectorResource* resource = m_resources.get(identifier).get();
 7     if (!resource)
 8         return;
 9 
10     resource->startTime = currentTime();
11 
12     if (!redirectResponse.isNull()) {
13         updateResourceRequest(resource, request);
14         updateResourceResponse(resource, redirectResponse);
15     }
16 
17     if (resource != m_mainResource && windowVisible()) {
18         if (!resource->scriptObject)
19             addScriptResource(resource);
20         else
21             updateScriptResourceRequest(resource);
22 
23         updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime);
24 
25         if (!redirectResponse.isNull())
26             updateScriptResourceResponse(resource);
27     }
28 }
複製代碼

在這裏設定了開始時間,猜測是供請求超時判斷用的,請求超時的定時器在何處設定有待進一步分析。
看都是一些Resource的更新,感覺意義不大,不再進一步追蹤。回到MainResourceLoader::loadNow(),看下一步ResourceHandle::create()

複製代碼
 1 PassRefPtr<ResourceHandle> ResourceHandle::create(const ResourceRequest& request, ResourceHandleClient* client,
 2     Frame* frame, bool defersLoading, bool shouldContentSniff, bool mightDownloadFromHandle)
 3 {
 4     RefPtr<ResourceHandle> newHandle(adoptRef(new ResourceHandle(request, client, defersLoading, shouldContentSniff, mightDownloadFromHandle)));
 5 
 6     if (!request.url().isValid()) {
 7         newHandle->scheduleFailure(InvalidURLFailure);
 8         return newHandle.release();
 9     }
10     // 檢查端口號(port)是否合法
11     if (!portAllowed(request)) {
12         newHandle->scheduleFailure(BlockedFailure);
13         return newHandle.release();
14     }
15         
16     if (newHandle->start(frame))
17         return newHandle.release();
18 
19     return 0;
20 }
複製代碼

看關鍵的ResourceHandle::start調用:

複製代碼
 1 bool ResourceHandle::start(Frame* frame)
 2 {
 3     if (!frame)
 4         return false;
 5 
 6     Page *page = frame->page();
 7     // If we are no longer attached to a Page, this must be an attempted load from an
 8     // onUnload handler, so let's just block it.
 9     if (!page)
10         return false;
11 
12     getInternal()->m_frame = static_cast<FrameLoaderClientQt*>(frame->loader()->client())->webFrame();
13 #if QT_VERSION < 0x040400
14     return QWebNetworkManager::self()->add(this, getInternal()->m_frame->page()->d->networkInterface);
15 #else
16     ResourceHandleInternal *d = getInternal();
17     d->m_job = new QNetworkReplyHandler(this, QNetworkReplyHandler::LoadMode(d->m_defersLoading));
18     return true;
19 #endif
20 }
複製代碼

新創建了一個QNetworkReplyHandler對象,QNetworkReplyHandler在構造的時候會調用QNetworkReplyHandler::start()

複製代碼
 1 void QNetworkReplyHandler::start()
 2 {
 3     m_shouldStart = false;
 4 
 5     ResourceHandleInternal* d = m_resourceHandle->getInternal();
 6 
 7     QNetworkAccessManager* manager = d->m_frame->page()->networkAccessManager();
 8 
 9     const QUrl url = m_request.url();
10     const QString scheme = url.scheme();
11     // Post requests on files and data don't really make sense, but for
12     // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
13     // we still need to retrieve the file/data, which means we map it to a Get instead.
14     if (m_method == QNetworkAccessManager::PostOperation
15         && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
16         m_method = QNetworkAccessManager::GetOperation;
17 
18     m_startTime = QDateTime::currentDateTime().toTime_t();
19 
20     switch (m_method) {
21         case QNetworkAccessManager::GetOperation:
22             m_reply = manager->get(m_request);
23             break;
24         case QNetworkAccessManager::PostOperation: {
25             FormDataIODevice* postDevice = new FormDataIODevice(d->m_request.httpBody()); 
26             m_reply = manager->post(m_request, postDevice);
27             postDevice->setParent(m_reply);
28             break;
29         }
30         case QNetworkAccessManager::HeadOperation:
31             m_reply = manager->head(m_request);
32             break;
33         case QNetworkAccessManager::PutOperation: {
34             FormDataIODevice* putDevice = new FormDataIODevice(d->m_request.httpBody()); 
35             m_reply = manager->put(m_request, putDevice);
36             putDevice->setParent(m_reply);
37             break;
38         }
39         case QNetworkAccessManager::UnknownOperation: {
40             m_reply = 0;
41             ResourceHandleClient* client = m_resourceHandle->client();
42             if (client) {
43                 ResourceError error(url.host(), 400 /*bad request*/,
44                                     url.toString(),
45                                     QCoreApplication::translate("QWebPage", "Bad HTTP request"));
46                 client->didFail(m_resourceHandle, error);
47             }
48             return;
49         }
50     }
51 
52     m_reply->setParent(this);
53 
54     connect(m_reply, SIGNAL(finished()),
55             this, SLOT(finish()), Qt::QueuedConnection);
56 
57     // For http(s) we know that the headers are complete upon metaDataChanged() emission, so we
58     // can send the response as early as possible
59     if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
60         connect(m_reply, SIGNAL(metaDataChanged()),
61                 this, SLOT(sendResponseIfNeeded()), Qt::QueuedConnection);
62 
63     connect(m_reply, SIGNAL(readyRead()),
64             this, SLOT(forwardData()), Qt::QueuedConnection);
65 }
複製代碼

看到了熟悉的QNetworkAccessManager、QNetworkReply。跟蹤至此,初始化和URL請求發送基本完成。

 前面分析WebView初始化的時候,在QNetworkReplyHandler::start()裏有設定讀取數據的處理函數:

複製代碼
 1     connect(m_reply, SIGNAL(finished()),
 2             this, SLOT(finish()), Qt::QueuedConnection);
 3 
 4     // For http(s) we know that the headers are complete upon metaDataChanged() emission, so we
 5     // can send the response as early as possible
 6     if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
 7         connect(m_reply, SIGNAL(metaDataChanged()),
 8                 this, SLOT(sendResponseIfNeeded()), Qt::QueuedConnection);
 9 
10     connect(m_reply, SIGNAL(readyRead()),
11             this, SLOT(forwardData()), Qt::QueuedConnection);
複製代碼

先看QNetworkReplyHandler::forwardData()

複製代碼
 1 void QNetworkReplyHandler::forwardData()
 2 {
 3     m_shouldForwardData = (m_loadMode == LoadDeferred);
 4     if (m_loadMode == LoadDeferred)
 5         return;
 6 
 7     sendResponseIfNeeded();
 8 
 9     // don't emit the "Document has moved here" type of HTML
10     if (m_redirected)
11         return;
12 
13     if (!m_resourceHandle)
14         return;
15 
16     QByteArray data = m_reply->read(m_reply->bytesAvailable());
17 
18     ResourceHandleClient* client = m_resourceHandle->client();
19     if (!client)
20         return;
21 
22     if (!data.isEmpty())
23         client->didReceiveData(m_resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
24 }
複製代碼

實際就是兩個調用:read()和didReceiveData()。其中QNetworkReply::read()前面分析過不再重複;

ResourceHandleClient* client->didReceiveData()實際調用的是MainResourceLoader::didReceiveData()

複製代碼
 1 void MainResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce)
 2 {
 3     ASSERT(data);
 4     ASSERT(length != 0);
 5 
 6     // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred.
 7     // See <rdar://problem/6304600> for more details.
 8 #if !PLATFORM(CF)
 9     ASSERT(!defersLoading());
10 #endif
11  
12     // The additional processing can do anything including possibly removing the last
13     // reference to this object; one example of this is 3266216.
14     RefPtr<MainResourceLoader> protect(this);
15 
16     ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce);
17 }
複製代碼

進一步看其調用:

複製代碼
 1 void ResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce)
 2 {
 3     // Protect this in this delegate method since the additional processing can do
 4     // anything including possibly derefing this; one example of this is Radar 3266216.
 5     RefPtr<ResourceLoader> protector(this);
 6 
 7     addData(data, length, allAtOnce);
 8     // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
 9     // However, with today's computers and networking speeds, this won't happen in practice.
10     // Could be an issue with a giant local file.
11     if (m_sendResourceLoadCallbacks && m_frame)
12         frameLoader()->didReceiveData(this, data, length, static_cast<int>(lengthReceived));
13 }
複製代碼

在ResourceLoader類中addData()是虛函數,client->didReceiveData()中client指針實際的實體爲MainResourceLoader對象,所以addData()先調用 MainResourceLoader::addData()

 

1 void MainResourceLoader::addData(const char* data, int length, bool allAtOnce)
2 {
3     ResourceLoader::addData(data, length, allAtOnce);
4     frameLoader()->receivedData(data, length);
5 }

這裏只有兩個調用,前一個是將接收到的數據保存到一個buffer中,供後續語法掃描使用(猜測的),暫不深入分析。看frameLoader->receivedData()

 

複製代碼
 1 void FrameLoader::receivedData(const char* data, int length)
 2 {
 3     activeDocumentLoader()->receivedData(data, length);
 4 }
 5 
 6 void DocumentLoader::receivedData(const char* data, int length)
 7 {    
 8     m_gotFirstByte = true;
 9     if (doesProgressiveLoad(m_response.mimeType()))
10         commitLoad(data, length);
11 }
複製代碼

 

其中doesProgressiveLoad()會測試MIME的類型,重點是commitLoad()

複製代碼
 1 void DocumentLoader::commitLoad(const char* data, int length)
 2 {
 3     // Both unloading the old page and parsing the new page may execute JavaScript which destroys the datasource
 4     // by starting a new load, so retain temporarily.
 5     RefPtr<DocumentLoader> protect(this);
 6 
 7     commitIfReady();
 8     if (FrameLoader* frameLoader = DocumentLoader::frameLoader())
 9         frameLoader->committedLoad(this, data, length);
10 }
複製代碼

前面一個調用:commitIfReady()是清理前一次頁面掃描的中間數據;committedLoad()纔是正題。

1 void FrameLoader::committedLoad(DocumentLoader* loader, const char* data, int length)
2 {
3     if (ArchiveFactory::isArchiveMimeType(loader->response().mimeType()))
4         return;
5     m_client->committedLoad(loader, data, length);
6 }

其中m_client指向的是FrameLoaderClientQT對象實體。

複製代碼
 1 void FrameLoaderClientQt::committedLoad(WebCore::DocumentLoader* loader, const char* data, int length)
 2 {
 3     if (!m_pluginView) {
 4         if (!m_frame)
 5             return;
 6         FrameLoader *fl = loader->frameLoader();
 7         if (m_firstData) {
 8             fl->setEncoding(m_response.textEncodingName(), false);
 9             m_firstData = false;
10         }
11         fl->addData(data, length);
12     }
13     
14     // We re-check here as the plugin can have been created
15     if (m_pluginView) {
16         if (!m_hasSentResponseToPlugin) {
17             m_pluginView->didReceiveResponse(loader->response());
18             // didReceiveResponse sets up a new stream to the plug-in. on a full-page plug-in, a failure in
19             // setting up this stream can cause the main document load to be cancelled, setting m_pluginView
20             // to null
21             if (!m_pluginView)
22                 return;
23             m_hasSentResponseToPlugin = true;
24         }
25         m_pluginView->didReceiveData(data, length);
26     }
27 }
複製代碼

其中fl->setEncoding()是根據服務器返回的HTML數據流設定編碼格式(例如:中文gb2312),另外處理了其他一些事情,例如Redirect等。fl->addData()是關鍵:

複製代碼
1 void FrameLoader::addData(const char* bytes, int length)
2 {
3     ASSERT(m_workingURL.isEmpty());
4     ASSERT(m_frame->document());
5     ASSERT(m_frame->document()->parsing());
6     write(bytes, length);
7 }
複製代碼

上面的FrameLoader::write()調用,啓動了HTML/JS分析掃描

 

在繼續分析FrameLoader::write()之前,先回到前面,那裏曾經保存了一個完整的調用堆棧,

……
QtWebKitd4.dll!WebCore::HTMLTokenizer::write(const WebCore::SegmentedString & str={...}, bool appendData=true)  行1730 + 0x23 字節    C++
QtWebKitd4.dll!WebCore::FrameLoader::write(const char *

可知調用的次序爲:FrameLoader::write()調用了HTMLTokenizer::write()。
下面是FrameLoader::write()的定義:

1 void write(const char* str, int len = -1, bool flush = false);

這裏包含了兩個缺省值調用定義,在前一篇,調用的形式是:write(bytes, length);
實際傳遞的的是:write(bytes, length,
false);
接着看write()的實現

複製代碼
 1 void FrameLoader::write(const char* str, int len, bool flush)
 2 {
 3     if (len == 0 && !flush)
 4         return;
 5     
 6     if (len == -1)
 7         len = strlen(str);
 8 
 9     Tokenizer* tokenizer = m_frame->document()->tokenizer();
10     if (tokenizer && tokenizer->wantsRawData()) {
11         if (len > 0)
12             tokenizer->writeRawData(str, len);
13         return;
14     }
15     
16     if (!m_decoder) {
17         Settings* settings = m_frame->settings();
18         m_decoder = TextResourceDecoder::create(m_responseMIMEType, settings ? settings->defaultTextEncodingName() : String());
19         if (m_encoding.isEmpty()) {
20             Frame* parentFrame = m_frame->tree()->parent();
21             if (parentFrame && parentFrame->document()->securityOrigin()->canAccess(m_frame->document()->securityOrigin()))
22                 m_decoder->setEncoding(parentFrame->document()->inputEncoding(), TextResourceDecoder::DefaultEncoding);
23         } else {
24             m_decoder->setEncoding(m_encoding,
25                 m_encodingWasChosenByUser ? TextResourceDecoder::UserChosenEncoding : TextResourceDecoder::EncodingFromHTTPHeader);
26         }
27         m_frame->document()->setDecoder(m_decoder.get());
28     }
29 
30     String decoded = m_decoder->decode(str, len);
31     if (flush)
32         decoded += m_decoder->flush();
33     if (decoded.isEmpty())
34         return;
35 
36 #if USE(LOW_BANDWIDTH_DISPLAY)
37     if (m_frame->document()->inLowBandwidthDisplay())
38         m_pendingSourceInLowBandwidthDisplay.append(decoded);   
39 #endif
40 
41     if (!m_receivedData) {
42         m_receivedData = true;
43         if (m_decoder->encoding().usesVisualOrdering())
44             m_frame->document()->setVisuallyOrdered();
45         m_frame->document()->recalcStyle(Node::Force);
46     }
47 
48     if (tokenizer) {
49         ASSERT(!tokenizer->wantsRawData());
50         tokenizer->write(decoded, true);
51     }
52 }
複製代碼

怎麼和HTMLTokenizer關聯的呢?就是在《QT分析之WebKit(三)》初始化Document對象的時候關聯上的。

1 DOMImplementation::createDocument()

上面程序做了一些邊緣的工作,例如設定編碼(因爲可以在HTTP協議、HTML的TITLE部分或者瀏覽器特別指定編碼),主要是新建一個decoder另外一個是調用tokenizer->write()

接着前面的分析,先看m_decoder->decode(str, len);

複製代碼
 1 String TextResourceDecoder::decode(const char* data, size_t len)
 2 {
 3     if (!m_checkedForBOM)
 4         checkForBOM(data, len);  // 檢查是否爲Unicode編碼
 5 
 6     bool movedDataToBuffer = false;
 7 
 8     if (m_contentType == CSS && !m_checkedForCSSCharset)
 9         if (!checkForCSSCharset(data, len, movedDataToBuffer))  // 如果是CSS,則檢查CSS的字符集
10             return "";
11 
12     if ((m_contentType == HTML || m_contentType == XML) && !m_checkedForHeadCharset) // HTML and XML
13         if (!checkForHeadCharset(data, len, movedDataToBuffer))  // 檢查HTML/XML的字符集
14             return "";
15 
16     // Do the auto-detect if our default encoding is one of the Japanese ones.
17     // FIXME: It seems wrong to change our encoding downstream after we have already done some decoding.
18     if (m_source != UserChosenEncoding && m_source != AutoDetectedEncoding && encoding().isJapanese())
19         detectJapaneseEncoding(data, len);  // 檢查日文編碼(爲什麼沒有檢查中文編碼的啊?)
20 
21     ASSERT(encoding().isValid());
22 
23     if (m_buffer.isEmpty())
24         return m_decoder.decode(data, len, false, m_contentType == XML, m_sawError);
25 
26     if (!movedDataToBuffer) {
27         size_t oldSize = m_buffer.size();
28         m_buffer.grow(oldSize + len);
29         memcpy(m_buffer.data() + oldSize, data, len);
30     }
31 
32     String result = m_decoder.decode(m_buffer.data(), m_buffer.size(), false, m_contentType == XML, m_sawError);
33     m_buffer.clear();
34     return result;
35 }
複製代碼

再回到tokenizer->write(decoded, true);看其具體實現:

複製代碼
  1 bool HTMLTokenizer::write(const SegmentedString& str, bool appendData)
  2 {
  3     if (!m_buffer)
  4         return false;
  5     
  6     if (m_parserStopped)
  7         return false;
  8 
  9     SegmentedString source(str);
 10     if (m_executingScript)
 11         source.setExcludeLineNumbers();
 12 
 13     if ((m_executingScript && appendData) || !m_pendingScripts.isEmpty()) {
 14         // don't parse; we will do this later
 15         if (m_currentPrependingSrc)
 16             m_currentPrependingSrc->append(source);
 17         else {
 18             m_pendingSrc.append(source);
 19 #if PRELOAD_SCANNER_ENABLED
 20             if (m_preloadScanner && m_preloadScanner->inProgress() && appendData)
 21                 m_preloadScanner->write(source);
 22 #endif
 23         }
 24         return false;
 25     }
 26     
 27 #if PRELOAD_SCANNER_ENABLED
 28     if (m_preloadScanner && m_preloadScanner->inProgress() && appendData)
 29         m_preloadScanner->end();
 30 #endif
 31 
 32     if (!m_src.isEmpty())
 33         m_src.append(source);
 34     else
 35         setSrc(source);
 36 
 37     // Once a timer is set, it has control of when the tokenizer continues.
 38     if (m_timer.isActive())
 39         return false;
 40 
 41     bool wasInWrite = m_inWrite;
 42     m_inWrite = true;
 43     
 44 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
 45     if (!m_doc->ownerElement())
 46         printf("Beginning write at time %d ", m_doc->elapsedTime());
 47 #endif
 48     
 49     int processedCount = 0;
 50     double startTime = currentTime();
 51 
 52     Frame* frame = m_doc->frame();
 53 
 54     State state = m_state;
 55 
 56     while (!m_src.isEmpty() && (!frame || !frame->loader()->isScheduledLocationChangePending())) {
 57         if (!continueProcessing(processedCount, startTime, state))
 58             break;
 59 
 60         // do we need to enlarge the buffer?
 61         checkBuffer();
 62 
 63         UChar cc = *m_src;
 64 
 65         bool wasSkipLF = state.skipLF();
 66         if (wasSkipLF)
 67             state.setSkipLF(false);
 68 
 69         if (wasSkipLF && (cc == ' '))
 70             m_src.advance();
 71         else if (state.needsSpecialWriteHandling()) {
 72             // it's important to keep needsSpecialWriteHandling with the flags this block tests
 73             if (state.hasEntityState())
 74                 state = parseEntity(m_src, m_dest, state, m_cBufferPos, false, state.hasTagState());
 75             else if (state.inPlainText())
 76                 state = parseText(m_src, state);
 77             else if (state.inAnySpecial())
 78                 state = parseSpecial(m_src, state);
 79             else if (state.inComment())
 80                 state = parseComment(m_src, state);
 81             else if (state.inDoctype())
 82                 state = parseDoctype(m_src, state);
 83             else if (state.inServer())
 84                 state = parseServer(m_src, state);
 85             else if (state.inProcessingInstruction())
 86                 state = parseProcessingInstruction(m_src, state);
 87             else if (state.hasTagState())
 88                 state = parseTag(m_src, state);
 89             else if (state.startTag()) {
 90                 state.setStartTag(false);
 91                 
 92                 switch(cc) {
 93                 case '/':
 94                     break;
 95                 case '!': {
 96                     // or 
 97                     searchCount = 1; // Look for '                    m_doctypeSearchCount = 1;
 98                     break;
 99                 }
100                 case '?': {
101                     // xml processing instruction
102                     state.setInProcessingInstruction(true);
103                     tquote = NoQuote;
104                     state = parseProcessingInstruction(m_src, state);
105                     continue;
106 
107                     break;
108                 }
109                 case '%':
110                     if (!m_brokenServer) {
111                         // <% server stuff, handle as comment %>
112                         state.setInServer(true);
113                         tquote = NoQuote;
114                         state = parseServer(m_src, state);
115                         continue;
116                     }
117                     // else fall through
118                 default: {
119                     if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z'))) {
120                         // Start of a Start-Tag
121                     } else {
122                         // Invalid tag
123                         // Add as is
124                         *m_dest = '<';
125                         m_dest++;
126                         continue;
127                     }
128                 }
129                 }; // end case
130 
131                 processToken();
132 
133                 m_cBufferPos = 0;
134                 state.setTagState(TagName);
135                 state = parseTag(m_src, state);
136             }
137         } else if (cc == '&' && !m_src.escaped()) {
138             m_src.advancePastNonNewline();
139             state = parseEntity(m_src, m_dest, state, m_cBufferPos, true, state.hasTagState());
140         } else if (cc == '<' && !m_src.escaped()) {
141             m_currentTagStartLineNumber = m_lineNumber;
142             m_src.advancePastNonNewline();
143             state.setStartTag(true);
144             state.setDiscardLF(false);
145         } else if (cc == ' ' || cc == ' ') {
146             if (state.discardLF())
147                 // Ignore this LF
148                 state.setDiscardLF(false); // We have discarded 1 LF
149             else {
150                 // Process this LF
151                 *m_dest++ = ' ';
152                 if (cc == ' ' && !m_src.excludeLineNumbers())
153                     m_lineNumber++;
154             }
155 
156             /* Check for MS-DOS CRLF sequence */
157             if (cc == ' ')
158                 state.setSkipLF(true);
159             m_src.advance(m_lineNumber);
160         } else {
161             state.setDiscardLF(false);
162             *m_dest++ = cc;
163             m_src.advancePastNonNewline();
164         }
165     }
166     
167 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
168     if (!m_doc->ownerElement())
169         printf("Ending write at time %d ", m_doc->elapsedTime());
170 #endif
171     
172     m_inWrite = wasInWrite;
173 
174     m_state = state;
175 
176     if (m_noMoreData && !m_inWrite && !state.loadingExtScript() && !m_executingScript && !m_timer.isActive()) {
177         end(); // this actually causes us to be deleted
178         return true;
179     }
180     return false;
181 }
複製代碼

在調用的時候,因爲調用參數decoded是String類型的,所以先隱含轉化成SegmentedString。SegmentedString可以附帶行號,也可以不帶行號(可以設定)。上面程序中的while循環主體,就是一個分析程序主體。

 

WebKit的結構與解構

原文地址:http://blog.sina.com.cn/s/blog_46d0a3930100d5pt.html

從指定一個HTML文本文件,到繪製出一幅佈局複雜,字體多樣,內含圖片音頻視頻等等多媒體內容的網頁,這是一個複雜的過程。在這個過程中Webkit所做的一切,都是圍繞DOM Tree和Rendering Tree這兩個核心。上一章我們談到這兩棵樹各自的功用,這一章,我們借一個簡單的HTML文件,展示一下DOM Tree和Rendering Tree的具體構成,同時解剖一下Webkit是如何構造這兩棵樹的。

 

 

Figure 1. From HTML to webpage, and the underlying DOM tree and rendering tree. Courtesy http://farm4.static.flickr.com/3351/3556972420_23a30366c2_o.jpg
1. DOM Tree 與 Rendering Tree 的結構
Figure 1中左上是一個簡單的HTML文本文件,右上是Webkit rendering engine繪製出來的頁面。頁面的內容包括一個標題,“AI”,一行正 ,“Ape's Intelligence”,以及一幅照片。整個頁面分成前後兩個層面,標題和正文繪製在前一個層面,照片處於後一個層面。L君和我亦步亦趨地跟蹤了,從解析這個HTML文本文件,到生成DOM Tree和Rendering Tree的整個流程,目的是爲了瞭解DOM Tree和Rendering Tree的具體成份,以及構造的各個步驟。
先說Figure 1中左下角的DOM Tree。基本上HTML文本文件中每個tag,在webkit/webcore/html中都有一個class與之對應。譬如<HTML> tag 對應HTMLHtmlElement,<HEAD> tag 對應HTMLHeadElement,<STYLE> tag 對應HTMLStyleElement 等等。比較特別的是DOM Tree的根節點,HTMLDocument,在HTML文本文件中沒有哪個tag與之對應。關於HTMLDocument的作用,我們稍後介紹。整個 DOM Tree的結構,與HTML文本文件中各個tags的嵌套關係也一一對應。一言以蔽之,DOM Tree就是把HTML文本文件翻譯成object樹狀結構。
需要強調的是,DOM Tree是一個通用數據結構,任何XML文本文件都可以翻譯成DOM Tree,而不僅僅限於HTML文本文件。webkit/webcore/html 中林林總總html classes,基本上都是webkit/webcore/dom 中的某個class的子類,也就是說,/html 是 /dom的一個特例。這樣的設計,爲將來把Webkit拓展到HTML格式以外的頁面的佈局和渲染,埋下了伏筆。所以嚴格地講,Figure 1中左下的DOM Tree,實際上是一個HTML DOM Tree。
再看Rendering Tree,顯著的特點在於,
a. 整個Rendering Tree樹狀結構,與HTML DOM Tree樹狀結構一一對應。也就是說,幾乎每個HTML DOM Tree中的節點,在Rendering Tree中都有對應的節點。節點與節點之間的父子或兄弟關係也一一對應。
例外的是,在HTML DOM Tree有HTMLStyleElement葉子節點,而在Rendering Tree中,沒有相應的葉子節點。原因是,Rendering Tree各個節點,都涉及頁面中某塊區域的佈局和渲染。而HTMLStyleElement,並不直接涉及某塊區域的佈局和渲染,HTML DOM Tree中HTMLStyleElement葉子節點包含的內容,已經融入Rendering Tree中RenderImage葉子節點的屬性中去了。另外,因爲Rendering Tree中不存在與HTMLStyleElement相應的葉子節點,所以,與HTMLHeadElement對應的節點也沒有必要存在。
b. webkit/webcore/rendering中各個class與HTML tags並沒有一一對應的關係。
Rendering Tree是一個通用的規劃頁面佈局和渲染的機制,這個通用機制可以服務於HTML頁面,但是並不僅僅限於爲HTML頁面服務,我們可以用 Rendering Tree來規劃其它格式的頁面的佈局和渲染。以DOM Tree和Rendering Tree爲核心的Webkit渲染機,是一個功能強大,擴展性良好的通用渲染機。它不僅可以用來繪製HTML頁面,也可以用來渲染其它格式的頁面,譬如可以用它來製作email閱讀和管理器,製作數據庫管理工具,甚至製作遊戲界面。
稍微讓人有點喫驚的是,對於 HTMLHtmlElement,HTMLBodyElement,HTMLHeadingElement和HTMLParagraphElement,在Rendering Tree中通通以RenderBlock呼應。如果說HTMLHeadingElement和HTMLParagraphElement的區別不大,僅僅是字體和對齊方式有些微小的差別,所以Rendering Tree可以用RenderBlock來統一應對。那麼問題是,HTMLHtmlElement和HTMLBodyElement是兩種容器,總是出現在 DOM Tree的中部,而從來不會作爲葉子節點出現,對應於這樣的容器節點,爲什麼Rendering Tree不另設一種class,與RenderBlock有所區別呢?不過話又說回來,這不是個大問題,最多是個美感的問題。

 

Figure 2. The construction sequence of the root of the DOM tree.
Courtesy http://farm4.static.flickr.com/3010/3554310018_e34d271344_o.jpg

2. DOM Tree 與 Rendering Tree 的根節點

前一節中我們提到HTMLDocument是一個比較特殊的class,它是整個HTML DOM Tree的根節點,但是不對應任何HTML tag。JavaScript中經常出現的document,指的就是這個根。例如,
    “document.getElementByIdx(x).style.background="yellow";”

HTML文本文件,通常是以<HTML>開頭,以</HTML>結尾。但是<HTML> tag並不對應DOM Tree的根節點,而是根以下的第一個子節點,即HTMLHtmlElement節點。

初看Figure 2 覺得有點意外,當用戶在瀏覽器裏打開一個空白頁面的時候,立刻生成了DOM Tree的根節點HTMLDocument,與Rendering Tree的根節點RenderView。而這個時候,用戶並沒有給定URL,也就是說,對於瀏覽器來講,這時候具體的HTML文本文件並不存在。根節點與具體HTML內容相脫節,或許暗示了Webkit的兩個設計思路,

a. DOM Tree的根節點HTMLDocument,與Rendering Tree的根節點RenderView,可以重複利用。

當用戶在同一個瀏覽器頁面中,先後打開兩個不同的URLs,也就是兩個不同的HTML文本文時,HTMLDocument和RenderView兩個根節點並沒有發生改變,改變的是HTMLHtmlElement以下的子樹,以及對應的Rendering Tree的子樹。

爲什麼這樣設計?原因是HTMLDocument和RenderView服從於瀏覽器頁面的設置,譬如頁面的大小和在整個屏幕中的位置等等。這些設置與頁面中要顯示什麼的內容無關。同時HTMLDocument綁定HTMLTokenizer和HTMLParser,這兩個構件也與某一個具體的HTML內容無關。

b. 同一個DOM Tree的根節點可以懸掛多個HTML子樹,同一個Rendering Tree的根節點可以懸掛多個RenderBlock子樹。

在我們目前所見到的瀏覽器中,每一個頁面通常只顯示一個HTML文件。雖然一個HTML文件可以分割成多個frames,每個frame承載一個獨立的 HTML文件,但是從DOM Tree結構來講,HTMLDocument根節點以下,只有一個子節點,這個子節點是HTMLHtmlElement,它領銜某個HTML文本文件對應的子樹。Rendering Tree也一樣,目前我們見到的網頁中,一個RenderView根節點以下,也只有一個RenderBlock子節點。

但是Webkit的設計,卻允許同一個根以下,懸掛多個HTML子樹。雖然我們目前沒有看到一個頁面中,並存多個HTML文件,並存多個佈局和渲染風格的情景,但是Webkit爲將來的拓展留下了空間。前文中所設想的個性化,多皮膚,多視角的瀏覽器頁面繪製,用Webkit實現起來難度不大。

 Figure 3. The construction sequence of the DOM Tree and the Rendering Tree.
Courtesy http://farm4.static.flickr.com/3627/3554182242_b0bec88534_b.jpg

 
3. DOM Tree 與 Rendering Tree 的構築

HTMLDocument 根節點包含的最重要的構件是HTMLTokenizer,而HTMLTokenizer又包含HTMLParser這個構件。HTMLTokenizer 從前到後讀取HTML文本文件中每一個字符,並從中提取出各個HTML tags以及它們的內容。而HTMLParser不僅負責HTML DOM Tree的構築,而且也同時負責Rendering Tree的構築。

在Figure 3中,從第8步到第11步,HTMLParser根據一個HTML Tag生成一個HTML DOM Tree節點。從第12步到第17步,生成相應的Rendering Tree的節點,並把它和HTML DOM Tree的節點勾連在一起。這張圖的細節過多,讀解不容易。Figure 4把第8步到第17步演示了一下。

 

Figure 4. An illustration of the construction of a DOM tree node and its corresponding Rendering tree node.
Courtesy http://farm4.static.flickr.com/3306/3554259140_3deb9736ea_o.jpg

值得注意的是,每當HTMLParser生成一個DOM Tree的節點的時候,相應地,也同時生成一個Rendering Tree節點。然後把它們兩個新節點勾連在一起。換而言之,Rendering Tree與DOM Tree同步生長。

Webkit 值得讚賞的地方非常多,但是HTMLParser讓DOM Tree和Rendering Tree同步生長的做法,卻值得商榷。如果同步生長,那麼Rendering Tree必然平鋪直敘地刻板地忠實於DOM Tree。假設先生成DOM Tree,再生成Rendering Tree,把兩者割裂開,就有機會讓Webkit發揮更加奇妙的佈局和渲染。平鋪直敘固然符合大多數人在大多數時間裏的閱讀習慣,但是離經叛道的設計,也會有市場。一個例子就是上一章末尾處那張多視點的地圖。如果讓DOM Tree與Rendering Tree同步生長,這樣的佈局和渲染是難以想像的。

 

 

WebKit的顯示,繼續轉鄧侃博士的blog

WebKit,爲了佈局,忙併美麗着

如果沒有1440年以後活字印刷術的大規模普及,或許就不會有文藝復興運動,更不會有後來的啓蒙運動。如果沒有這兩個運動的開展,或許就不會有世界範圍的工業化。

在活字印刷術出現以前,每出版一本書,都必須先刻制一套模版,稱爲雕版,每套雕版上的每一個字,都是手工雕刻的。不僅製作雕版費時費力,而且有了錯誤不容易更改。活字印刷術的進步在於,可以預先批量生產各種樣式和大小的字體,稱爲活字。需要出版某一本書籍時,先製作該書的頁面模版,模版做好以後,只需要把這些活字擺放在模版上即可。如果出現錯誤,只需要調換某些活字,既省時又省力。如果某本書的模版不需要長期保存,還可以把模版中擺放的活字拆解下來,在印刷其它圖書時用,節約成本。

活字印刷術沒有解決的問題,1.
圖像的印刷。起初不能印刷筆觸豐富,層次複雜的圖像,一直到1796年,石板印刷術(lithography)出現以後,才能印刷表現手段豐富的圖像。 2.
靈活的佈局排版。紙張大小不同,佈局排版也不同,佈局變了,需要重新擺放活字,而且有時候還需要改變字體和大小。

靈活的佈局排版對於紙質書籍來說,或許並不太重要,但是對於電腦瀏覽器來說,卻必須實現完全的自動化。否則,每當用戶改變瀏覽器窗口的大小的時候,頁面內容就不能正確顯示。對於手機瀏覽器來說,佈局排版的自動化尤其重要,因爲不同手機的屏幕不一致,而且屏幕分辨率也不同。

但是即便是瀏覽器,也沒有擺脫傳統的排版方式。所謂傳統的排版方式,基本是橫平豎直的,單一的鳥瞰視角。

 

Figure 1. Incunabulum, the end of 15'th century Courtesy
http://www.citrinitas.com/history_of_viscom/images/printing/venice-1505.jpg

Figure 2. City of Words, by Vito Acconi, 1999 Courtesy
http://upload.wikimedia.org/wikipedia/en/6/63/%27City_of_Words%27%2C_lithograph_by_Vito_Acconci%2C_1999.jpg

Figure  1 中顯示的是1490年代的書籍,不難看出,現代書報中廣泛使用的雙列,邊注,頁碼,首字母大寫等等,都是繼承了500多年以前的做法。而CSS規範,囊括了所有這些頁面設計的要素。

在當今信息爆炸的形勢下,如何安排頁面的佈局排版,在有限的頁面面積內,承載更多內容,突出讀者關注的內容,增強頁面設計的視覺美感,成爲不可迴避的問題。例如,手機購物的UI設計,既要包含商品簡介,又要包含用戶意見反饋,還要包含實物照片,以及各個不同商場的標價等等。完美的頁面設計,不僅要求簡練而清晰,而且也不能遺漏相關內容,實在是一件困難的事情。可以說,手機購物之所以不普及,與手機購物的UI設計笨拙而醜陋是相關的。

要巧妙地 設計手機應用的UI設計,終極而言,需要突破傳統的單一鳥瞰視角的方式,Figure 2 就是這方面的嘗試。Webkit能不能做到這一點?原理上是可以做到的,但是必須修改源代碼。但是在改造以前,我們還是先踏踏實實研究一下,Webkit 的佈局排版的內部機制是什麼。只有充分了解對方之長,纔有可能改進對方之短。

讀解Webkit排版佈局與繪製的具體實現以前,首先需要明確的是,Webkit把排版佈局(layout),與繪製(paint),分開處理。

Layout負責確定Render Tree中,每個葉子和中間節點的位置。每個節點在屏幕上的顯示,都呈長方形格局。所謂位置,指的是這個長方形左上角起始座標(X,Y),以及長方形的寬度和高度。每個中間節點的長方形,裏面嵌套着若干小長方形,對應這個中間節點的後代節點等等。

在Layout過程結束以後,Webkit啓動 Paint過程,負責把Render Tree中各個葉子節點,在相應的位置繪製出來。Webkit 把具體繪製的工作,交給第三方圖形工具庫(Graphics Library)去完成。常用的第三方圖形工具庫包括QT,GTK+,Wx,Skia,Cairo等等。

打個比方,圖形工具庫相當於活字,以及繪製圖像的石板(lithography),它們負責paint。而從嚴格意義上來說,Webkit的主要工作是layout,也就是排版佈局,相當於版面模版。

關於圖形庫,臺灣的開源高手,黃敬羣(Jim Huang / jserv),寫過一篇介紹Google Skia 圖形庫的文章(http://blog.linux.org.tw/~jserv/archives/002095.html)。文中談到,

Google 爲了搭建Android平臺,於2005年8月併購了Android公司。同年11月份,Google還收購了Skia公司。2007年11 月,Google發佈Android,並公開部分源代碼。當人們熱衷於探究Android Dalvik VM的奧祕的時候,忽略了Skia的意義。

2008年9月,Google發佈了以改良的Webkit爲核心的Chrome PC瀏覽器。當人們熱衷於探究V8 JavaScript引擎等等功能模塊時,再次忽略了Skia的意義。

Skia是一個2D圖形工具庫,該產品的特色在於,能夠在手機等等移動設備中,以較低的內存和CPU消耗,呈現高品質的2D圖形。

Skia 的創辦人,Mike Reed,是圖形技術方面的頂尖人物。Mike早年任職於Apple,參與QuickDraw GX項目,處理字型和圖像顯示。後來他跳槽到OpenWave,開發手機瀏覽器。在OpenWave工作期間,與Benoit Schillings合作,在50-300KB的內存空間內,提供圖層之間alpha
blended方式的預覽,以及全功能向量矩陣轉換等等,真可謂螺絲殼裏做道場。後來Benoit Schillings離開OpenWave,去Trolltech任職CTO。Trolltech的主打產品是大名鼎鼎的QT。再後來Trolltech 被Nokia併購,Benoit隨之加入Nokia。Benoit Schillings離開OpenWave不久,Mike
Reed也離開了OpenWave,去創建Skia公司。

Figure 3. Layout implementation in Webkit Courtesy
http://www.flickr.com/photos/87209438%40N00/3609632247/sizes/l/

Figure 4. Paint implementation in Webkit Courtesy http://www.flickr.com/photos/87209438%40N00/3609632249/sizes/l/
Figure 3 和 Figure 4,分別顯示了Webkit執行排版佈局(layout),以及繪製(paint)的兩個過程。仔細查看這兩張sequence diagrams,會發現以下特點,
1. Layout 和 Paint 這兩個過程完全分開。開始執行Paint過程以前,必然預先執行過Layout,否則圖形庫就不知道在哪裏寫字以及顯示圖像。但是這並不意味着,Layout執行結束後,隨即就立刻執行Paint。實際上,Layout執行結束後,觸發一個事件,這個事件啓動Paint過程。但是Paint過程也可以被其它事件觸發,譬如屏幕內容的切換,以及把隱藏的瀏覽器窗口復原等等。
2. Layout 涵蓋了所有CSS規定的佈局要素。包括頁面邊緣與內容之間的空白,文字對插入圖像的避讓(floating),單列與多列,上下層覆蓋(z-index)等等。
3. 圖像,視頻播放器插件,Applet等等,在 Layout 被稱作 Replaced Render Object。這些 Replaced 元素的寬度和高度可以由CSS規定。如果CSS沒有規定,就解析這些元素的數據流,譬如一個JPG照片的metadata裏,規定了這幅照片原件的寬度和高度。如果元素自己也沒有規定寬度高度,就使用Webkit提供的缺省值。
4. 文字的寬度根據頁面的排版來確定。譬如一頁中包含多列文字,則每列文字寬度相等。每列文字的寬度,乘以列數,加上列與列之間的夾縫,加上頁面邊緣空白等等,應當等於頁面總的寬度。假設頁面總的寬度已知,邊緣空白,和列與列之間的夾縫的寬度也已知,就可以反推文字的寬度。
5. Render Tree中每個節點在屏幕上的顯示,都呈長方形格局。前面第3點和第4點,描述了寬度的確定。而高度的確定,取決於這個中間節點的所有後代節點的高度的總和。對於 Replaced 元素來說,它的高度相對比較容易確定,而文字段落的高度,需要根據字數,字型,以及字體大小計算得出。
6. 在 Layout 過程中,反覆出現以 Repaint 爲開頭的子過程,例如 repaintAfterLayoutIfNeed

ed()。這些子過程的意義在於,當確定了某個節點的高度和寬度以後,需要對其前輩節點,和左右兄弟節點的位置,做適當調整。嚴格意義上來講,這不是repaint,而是relayout。
7. 相對於 Layout 過程,Paint 過程的邏輯要簡單得多。Paint的過程,大致按照深度優先的順序,遍歷整棵RenderTree。也就是說,從最左邊的葉子節點開始,從左向右逐個繪製 RenderTree所有可以顯示的葉子節點。所謂“可以顯示的葉子節點”,是因爲CSS中可以規定,不顯示某些葉子。
反覆研究以上Layout和Paint的過程,我們有以下看法。
1. Layout 是一個計算量很繁重的過程。之所以繁重,主要體現在估算完每個RenderTree節點的寬度尤其是高度以後,需要相應調整這個節點的前輩節點以及左鄰右舍兄弟節點的位置。對於文字段落而言,它的高度有賴於字數,字體和大小,所以估算不容易準確。
有沒有可能把Layout 過程,與第一遍 Paint 過程合二爲一?只要遍歷一次RenderTree的所有葉子節點,繪製圖像並碼字。Paint過程結束後,各個葉子節點對應的長方形的起始位置的(X,Y)座標,以及寬度和高度都自然迎刃而解。然後再由葉子節點開始,逐步確定RenderTree中,各個中間節點的起始位置和寬度高度。這樣做的好處是,可以大大降低 Layout 過程的成本。
2. Layout 過程假設每個RenderTree 的節點都對應一個長方形屏幕區域。受限於這個規定,類似於Figure 2的效果,就顯示不出來。有沒有可能取消這個限制?SVG不僅提供了強大的繪圖能力,而且也提供了強大的排版佈局能力。能不能把CSS當着SVG格式的一個子集來看待?
 
WebKit,鼠標引發的故事
 
Figure 1. JavaScript onclick event
Courtesy http://farm4.static.flickr.com/3302/3640149734_3268bf297f_o.jpg

先看一段簡單的HTML文件。在瀏覽器裏打開這個文件,將看到兩張照片。把鼠標移動到第一張照片,點擊鼠標左鍵,將自動彈出一個窗口,上書“World”。但是當鼠標移動到第二張照片,或者其它任何區域,點擊鼠標,卻沒有反應。關閉“World”窗口,自動彈出第二個窗口,上書“Hello”。
複製代碼
 1 <html>
 2   <script type="text/javascript">
 3     function myfunction(v)
 4     {
 5       alert(v)
 6     }
 7   </script>
 8 
 9   <body onclick="myfunction('Hello')">
10     <p>
11     <img onclick="myfunction('World')" height="250" width="290" src="http://www.dirjournal.com/info/wp-content/uploads/2009/02/antarctica_mountain_mirrored.jpg">
12     <p>
13     <img height="206" width="275" src="http://media-cdn.tripadvisor.com/media/photo-s/01/26/f4/eb/hua-shan-hua-mountain.jpg">
14   </body>
15 </html>
複製代碼

這段HTML文件沒有什麼特別之處,所有略知一點HTML的人,估計都會寫。但是耳熟能詳,未必等於深入瞭解。不妨反問自己幾個問題,

1. 瀏覽器如何知道,是否鼠標的位置,在第一個照片的範圍內?

2. 假如修改一下HTML文件,把第一張照片替換成另一張照片,前後兩張照片的尺寸不同。在瀏覽器裏打開修改後的文件,我們會發現,能夠觸發彈出窗口事件的區域面積,隨着照片的改變而自動改變。瀏覽器內部,是通過什麼樣的機制,自動識別事件觸發區域的?

3. Onclick 是HTML的元素屬性(Element attribute),還是JavaScript的事件偵聽器(EventListener)?換而言之,當用戶點擊鼠標以後,負責處理onclick事件的,是Webkit 還是JavaScript Engine?

4. Alert() 是HTML定義的方法,還是JavaScript提供的函數?誰負責生成那兩個彈出的窗口,是Webkit還是JavaScript Engine?

5. 注意到有兩個onclick="myfunction(...)",當用戶在第一張照片裏點擊鼠標的時候,爲什麼是先後彈出,而不是同時彈出?

6. 除了PC上的瀏覽器以外,手機是否也可以完成同樣的事件及其響應?假如手機上沒有鼠標,但是有觸摸屏,如何把onclick定義成用手指點擊屏幕?

7. 爲什麼需要深入瞭解這些問題? 除了滿足好奇心以外,還有沒有其它目的?

 

Figure 2. Event callback stacks
Courtesy http://farm4.static.flickr.com/3611/3640149728_bc64397f60_o.gif

當用戶點擊鼠標,在OS語彙裏,這叫發生了一次中斷(interrupt)。系統內核(kernel) 如何偵聽以及處理interrupt,不妨參閱“Programming Embedded Systems” 一書,Chapter 8. Interrupts。這裏不展開介紹,有兩個原因,1. 這些內容很龐雜,而且與本文主題不太相關。2. 從Webkit角度看,它不必關心interrupt 以及interrupt handling 的具體實現,因爲Webkit建築在GUI Toolkit之上,而GUI Toolkit已經把底層的interrupt handling,嚴密地封裝起來。Webkit只需要調用GUI Toolkit 的相關APIs,就可以截獲鼠標的點擊和移動,鍵盤的輸入等等諸多事件。所以,本文着重討論Figure 2 中,位於頂部的Webkit和JavaScript兩層。

不同的操作系統,有相應的GUI Toolkit。GUI Toolkit提供一系列APIs,方便應用程序去管理各色窗口和控件,以及鼠標和鍵盤等等UI事件的截獲和響應。

1. 微軟的Windows操作系統之上的GUI Toolkit,是MFC(Microsoft Fundation Classes)。

2. Linux操作系統GNOME環境的GUI Toolkit,是GTK+.

3. Linux KDE環境的,是QT。

4. Java的GUI Toolkit有兩個,一個是Sun Microsystem的Java Swing,另一個是IBM Eclipse的SWT。
  
   Swing對native的依賴較小,它依靠Java 2D來繪製窗口以及控件,而Java 2D對於native的依賴基本上只限於用native library畫點畫線着色。 SWT對native的依賴較大,很多人把SWT理解爲Java通過JNI,對MFC,GTK+和QT進行的封裝。這種理解雖然不是百分之百準確,但是大體上也沒錯。

有了GUI Toolkit,應用程序處理鼠標和鍵盤等等UI事件的方式,就簡化了許多,只需要做兩件事情。1. 把事件來源(event source),與事件處理邏輯(event listener) 綁定。2. 解析並執行事件處理邏輯。

Figure 3 顯示的是Webkit如何綁定event source和event listener。Figure 4 顯示的是Webkit如何調用JavaScript Engine,解析並執行事件處理邏輯。首先看看event source,注意到在HTML文件裏有這麼一句,
   <img onclick="myfunction('World')" height="250" width="290"  src=".../antarctica_mountain_mirrored.jpg">

這句話裏“<img>”標識告訴Webkit,需要在瀏覽器頁面裏擺放一張照片,“src”屬性明確了照片的來源,“height, width”明確了照片的尺寸。“onclick”屬性提醒Webkit,當用戶把鼠標移動到照片顯示的區域,並點擊鼠標時(onclick),需要有所響應。響應的方式定義在“onclick”屬性的值裏面,也就是“myfunction('World')”。

當Webkit解析這個HTML文件時,它依據這個HTML文件生成一棵DOM Tree,和一棵Render Tree。對應於這一句<img>語句,在DOM Tree裏有一個HTMLElement節點,相應地,在Render Tree裏有一個RenderImage節點。在layout() 過程結束後,根據<img>語句中規定的height和width,確定了RenderImage的大小和位置。由於 Render Tree的RenderImage節點,與DOM Tree的HTMLElement節點一一對應,所以HTMLElement節點所處的位置和大小也相應確定。

因爲onclick事件與這個HTMLElement節點相關聯,所以這個HTMLElement節點的位置和大小確定了以後,點擊事件的觸發區域也就自動確定。假如修改了HTML文件,替換了照片,經過layout() 過程以後,新照片對應的HTMLElement節點,它的位置和大小也自動相應變化,所以,點擊事件的觸發區域也就相應地自動變化。

在onclick屬性的值裏,定義瞭如何處理這個事件的邏輯。有兩種處理事件的方式,1. 直接調用HTML DOM method,2. 間接調用外設的Script。onclick="alert('Hello')",是第一種方式。alert()是W3C制訂的標準的 HTML DOM methods之一。除此以外,也有稍微複雜一點的methods,譬如可以把這一句改成,<img onclick="document.write('Hello')">。本文的例子,onclick="myfunction('world')",是第二種方式,間接調用外設的Script。

外設的script有多種,最常見的是JavaScript,另外,微軟的VBScript和Adobe的ActionScript,在一些瀏覽器裏也能用。即便是JavaScript,也有多種版本,各個版本之間,語法上存在一些差別。爲了消弭這些差別,降低JavaScript使用者,以及 JavaScript Engine開發者的負擔,ECMA(歐洲電腦產聯)試圖制訂一套標準的JavaScript規範,稱爲ECMAScript。

各個瀏覽器使用的JavaScript Engine不同。

1. 微軟的IE瀏覽器,使用的JavaScript Engine是JScript Engine,渲染機是Trident。

2. Firefox瀏覽器,使用的JavaScript Engine是TraceMonkey,TraceMonkey的前身是SpiderMonkey,渲染機是Gecko。TraceMonkey JavaScript Engine借用了Adobe的Tamarin的部分代碼,尤其是Just-In-Time即時編譯機的代碼。而Tamarin也被用在Adobe Flash的Action Engine中。

3. Opera瀏覽器,使用的JavaScript Engine是Futhark,它的前身是Linear_b,渲染機是Presto。

4. Apple的Safari瀏覽器,使用的JavaScript Engine是SquirrelFish,渲染機是Webkit。

5. Google的Chrome瀏覽器,使用的JavaScript Engine是V8,渲染機也是Webkit。

6. Linux的KDE和GNOME環境中可以使用Konqueror瀏覽器,這個瀏覽器使用的JavaScript Engine是JavaScriptCore,前身是KJS,渲染機也是Webkit。

同樣是Webkit渲染機,可以調用不同的JavaScript Engine。之所以能做到這一點,是因爲Webkit的架構設計,在設置JavaScript Engine的時候,利用代理器,採取了鬆散的調用方式。

Figure 3. The listener binding of Webkit
Courtesy http://farm4.static.flickr.com/3659/3640149732_e55446f6b3_b.jpg

Figure 3 詳細描繪了Webkit 設置JavaScript Engine 的全過程。在Webkit 解析HTML文件,生成DOM Tree 和Render Tree 的過程中,當解析到 <img onclick="..." src="..."> 這一句的時候,生成DOM Tree中的 HTMLElement 節點,以及Render Tree 中 RenderImage 節點。如前文所述。在生成HTMLElement 節點的過程中,因爲注意到有onclick屬性,Webkit決定需要給 HTMLElement 節點綁定一個 EventListener,參見Figure 3 中第7步。

Webkit 把所有EventListener 的創建工作,交給Document 統一處理,類似於 Design Patterns中,Singleton 的用法。也就是說,DOM Tree的根節點 Document,掌握着這個網頁涉及的所有EventListeners。 有趣的是,當Document 接獲請求後,不管針對的是哪一類事件,一律讓代理器 (kjsProxy) 生成一個JSLazyEventListener。之所以說這個實現方式有趣,是因爲有幾個問題需要特別留意,

1. 一個HTMLElement節點,如果有多個類似於onclick的事件屬性,那麼就需要多個相應的EventListener object instances與之綁定。

2. 每個節點的每個事件屬性,都對應一個獨立的EventListener object instance。不同節點不共享同一個 EventListener object instance。即便同一個節點中,不同的事件屬性,對應的也是不同的EventListener object instances。

   這是一個值得商榷的地方。不同節點不同事件對應彼此獨立的EventListener object instances,這種做法給不同節點之間的信息傳遞,造成了很大障礙。反過來設想一下,如果能夠有一種機制,讓同一個object instance,穿梭於多個HTMLElement Nodes之間,那麼瀏覽器的表現能力將會大大增強,屆時,將會出現大量的前所未有的匪夷所思的應用。

3. DOM Tree的根節點,Document,統一規定了用什麼工具,去解析事件屬性的值,以及執行這個屬性值所定義的事件處理邏輯。如前文所述,事件屬性的值,分成HTML DOM methods 和JavaScript 兩類。但是不管某個HTMLElement節點的某個事件屬性的值屬於哪一類,Document一律讓 kjsProxy代理器,生成一個 EventListener。

   看看這個代理器的名字就知道,kjsProxy生成的 EventListener,一定是依託JavaScriptCore Engine,也就是以前的KJS JavaScript Engine,來執行事件處理邏輯的。覈實一下源代碼,這個猜想果然正確。

4. 如果想把JavaScriptCore 替換成其它JavaScript Engine,例如Google的V8,不能簡單地更改configuration file,而需要修改一部分源代碼。所幸的是,Webkit的架構設計相當清晰,所以需要改動部分不多,關鍵部位是把Document.{h,cpp} 以及其它少數源代碼中,涉及kjsProxy 的部分,改成其它Proxy即可。

5. kjsProxy 生成的EventListener,是JSLazyEventListener。解釋一下JSLazyEventListener 命名的寓意,JS容易理解,意思是把事件處理邏輯,交給JavaScript engine 負責。所謂 lazy 指的是,除非用戶在照片顯示區域點擊了鼠標,否則,JavaScript Engine 不主動處理事件屬性的值所規定的事件處理邏輯。

   與 lazy做法相對應的是JIT即時編譯,譬如有一些JavaScript Engine,在用戶尚沒有觸發任何事件以前,預先編譯了所有與該網頁相關的JavaScript,這樣,當用戶觸發了一個特定事件,需要調用某些 JavaScript functions時,運行速度就會加快。當然,預先編譯會有代價,可能會有一些JavaScript functions,雖然編譯過了,但是從來沒有被真正執行過。

 

Figure 4. The event handling of Webkit
Courtesy http://farm4.static.flickr.com/3390/3640149730_0c98f0218d_b.jpg

當解析完HTML文件,生成了完整的DOM Tree 和Render Tree 以後,Webkit就準備好去響應和處理用戶觸發的事件了。響應和處理事件的整個流程,如Figure 4所描述。整個流程分成兩個階段,

1. 尋找 EventTargetNode。

   當用戶觸發某個事件,例如點擊鼠標,根據鼠標所在位置,從Render Tree的根節點開始,一路搜索到鼠標所在位置對應的葉子節點。Render Tree根節點對應的是整個瀏覽器頁面,而葉子節點對應的區域面積最小。

   從Render Tree根節點,到葉子節點,沿途每個Render Tree Node,都對應一個DOM Tree Node。這一串DOM Tree Nodes中,有些節點響應用戶觸發的事件,另一些不響應。例如在本文的例子中,<body> tag 對應的DOM Tree Node,和第一張照片的<img> tag 對應的DOM Tree Node,都對onclick事件有響應。

   第一階段結束時,Webkit得到一個EventTargetNode,這個節點是一個DOM Tree Node,而且是對事件有響應的DOM Tree Node。如果存在多個DOM Tree Nodes對事件有響應,EventTargetNode是那個最靠近葉子的中間節點。

2. 執行事件處理邏輯。

   如果對於同一個事件,有多個響應節點,那麼JavaScript Engine 依次處理這一串節點中,每一個節點定義的事件處理邏輯。事件處理邏輯,以字符串的形式定義在事件屬性的值中。在本文的例子中,HTML文件包含<img onclick="myfunction('World')">,和<body onclick="myfunction('Hello')">,這意味着,有兩個DOM Tree Nodes 對onclick事件有響應,它們的事件處理邏輯分別是myfunction('World') 和myfunction('Hello'),這兩個字符串。

   當JavaScript Engine 獲得事件處理邏輯的字符串後,它把這個字符串,根據JavaScript的語法規則,解析爲一棵樹狀結構,稱作Parse Tree。有了這棵Parse Tree,JavaScript Engine就可以理解這個字符串中,哪些是函數名,哪些是變量,哪些是變量值。理解清楚以後,JavaScript Engine 就可以執行事件處理邏輯了。本文例子的事件處理過程,如Figure 4中第16步,到第35步所示。

   本文的例子中,“myfunction('World')" 這個字符串本身並沒有定義事件處理邏輯,而只是提供了一個JavaScript函數的函數名,以及函數的參數的值。當JavaScript Engine 得到這個字符串以後,解析,執行。執行的結果是得到函數實體的代碼。函數實體的代碼中,最重要的是alert(v) 這一句。JavaScript Engine 把這一句解析成Parse Tree,然後執行。

   注意到本文例子中,對於同一個事件onclick,有兩個不同的DOM Tree Nodes 有響應。處理這兩個節點的先後順序要麼由capture path,要麼由bubbling path決定,如Figure 5所示。(Figure 5中對應的HTML文件,不是本文所引的例子)。在HTML文件中,可以規定event.bubbles屬性。如果沒有規定,那就按照bubbling的順序進行,所以本文的例子,是先執行<img>,彈出“World” 的窗口,關掉“World”窗口後,接着執行<body>,彈出“Hello” 的窗口。

Figure 5. The capture and bubbling of event by the DOM tree.
Courtesy http://www.w3.org/TR/DOM-Level-3-Events/images/eventflow.png


這一節比較枯燥,因爲涉及了太多的源代碼細節。之所以這麼不厭其煩地說明細節,是爲了解決如何更有效率地處理事件,以及提供更豐富的手段去處理事件。待續。

 

 
 
 
有一種落差是,你配不上自己的野心,也辜負了所受的苦難
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章