該文章整理自 網易博客 http://blog.163.com/net_worm/blog/static/12770241920101831312381/javascript
轉載請註明出處html
WebKit是QT4新整合的第三方構件。按照慣例動手分析以前,先了解大概java
WebKit由三個模塊組成:JavaScriptCore、WebCore 和 WebKitnode
WebKit做爲了整個項目的名稱。其目錄結構:(未校準)linux
WebCore:web
¨Page與外框相關的內容(Frame,Page,History,Focus,Window)數據庫
¨Loader加載資源及Cache瀏覽器
¨HTML-DOM HTML內容及解析服務器
¨DOM- DOM CORE內容cookie
¨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做爲分析的項目。
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
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這一節比較枯燥,由於涉及了太多的源代碼細節。之因此這麼不厭其煩地說明細節,是爲了解決如何更有效率地處理事件,以及提供更豐富的手段去處理事件。待續。