当前位置: 首页 > news >正文

网站建设开发报价义乌广告设计与制作

网站建设开发报价,义乌广告设计与制作,win10系统之家官网,网站设计配色怎么做该文章整理自 网易博客 http://blog.163.com/net_worm/blog/static/12770241920101831312381/ 转载请注明出处 WebKit是QT4新整合的第三方构件。按照惯例动手分析之前#xff0c;先了解大概 WebKit由三个模块组成#xff1a;JavaScriptCore、WebCore 和 WebKit 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 C2 QtWebKitd4.dll!WebCore::MainResourceLoader::load(const WebCore::ResourceRequest r{...}, const WebCore::SubstituteData substituteData{...}) 行494 0x12 字节 C3 QtWebKitd4.dll!WebCore::DocumentLoader::startLoadingMainResource(unsigned long identifier0x00000004) 行807 0x32 字节 C4 QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterWillSubmitForm(WebCore::PolicyAction __formalPolicyUse) 行3274 0x16 字节 C5 QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterNavigationPolicy(const WebCore::ResourceRequest __formal{...}, WTF::PassRefPtrWebCore::FormState formState{...}, bool shouldContinuetrue) 行3968 C6 QtWebKitd4.dll!WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy(void * argument0x01d424e8, const WebCore::ResourceRequest request{...}, WTF::PassRefPtrWebCore::FormState formState{...}, bool shouldContinuetrue) 行3906 C7 QtWebKitd4.dll!WebCore::PolicyCheck::call(bool shouldContinuetrue) 行4963 0x3b 字节 C8 QtWebKitd4.dll!WebCore::FrameLoader::continueAfterNavigationPolicy(WebCore::PolicyAction policyPolicyUse) 行3899 C9 QtWebKitd4.dll!WebCore::FrameLoaderClientQt::slotCallPolicyFunction(int action0x00000000) 行194 C 10 QtWebKitd4.dll!WebCore::FrameLoaderClientQt::dispatchDecidePolicyForNavigationAction(void (WebCore::PolicyAction)* function0x10018f0c, const WebCore::NavigationAction action{...}, const WebCore::ResourceRequest request{...}, WTF::PassRefPtrWebCore::FormState __formal{...}) 行938 C 11 QtWebKitd4.dll!WebCore::FrameLoader::checkNavigationPolicy(const WebCore::ResourceRequest request{...}, WebCore::DocumentLoader * loader0x00f63ff8, WTF::PassRefPtrWebCore::FormState formState{...}, void (void *, const WebCore::ResourceRequest , WTF::PassRefPtrWebCore::FormState, bool)* function0x1004e661, void * argument0x01d424e8) 行3868 C 12 QtWebKitd4.dll!WebCore::FrameLoader::loadWithDocumentLoader(WebCore::DocumentLoader * loader0x00f63ff8, WebCore::FrameLoadType typeFrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtrWebCore::FormState prpFormState{...}) 行2291 C 13 QtWebKitd4.dll!WebCore::FrameLoader::loadWithNavigationAction(const WebCore::ResourceRequest request{...}, const WebCore::NavigationAction action{...}, WebCore::FrameLoadType typeFrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtrWebCore::FormState formState{...}) 行2226 C 14 QtWebKitd4.dll!WebCore::FrameLoader::loadURL(const WebCore::KURL newURL{...}, const WebCore::String referrer{...}, const WebCore::String frameName{...}, WebCore::FrameLoadType newLoadTypeFrameLoadTypeRedirectWithLockedHistory, WebCore::Event * event0x00000000, WTF::PassRefPtrWebCore::FormState prpFormState{...}) 行2174 C 15 QtWebKitd4.dll!WebCore::FrameLoaderClientQt::createFrame(const WebCore::KURL url{...}, const WebCore::String name{...}, WebCore::HTMLFrameOwnerElement * ownerElement0x00f681a0, const WebCore::String referrer{...}, bool allowsScrollingfalse, int marginWidth0xffffffff, int marginHeight0xffffffff) 行981 0x70 字节 C 16 QtWebKitd4.dll!WebCore::FrameLoader::loadSubframe(WebCore::HTMLFrameOwnerElement * ownerElement0x00f681a0, const WebCore::KURL url{...}, const WebCore::String name{...}, const WebCore::String referrer{...}) 行472 0x74 字节 C 17 QtWebKitd4.dll!WebCore::FrameLoader::requestFrame(WebCore::HTMLFrameOwnerElement * ownerElement0x00f681a0, 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 * n0x00f681a0) 行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 * n0x00f681a0, bool flatfalse) 行351 C 27 QtWebKitd4.dll!WebCore::HTMLParser::parseToken(WebCore::Token * t0x00f65fd0) 行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 appendDatatrue) 行1730 0x23 字节 C 31 QtWebKitd4.dll!WebCore::FrameLoader::write(const char * str0x01d3f5c0, int len0x000001df, bool flushfalse) 行1039 0x23 字节 C 32 QtWebKitd4.dll!WebCore::FrameLoader::addData(const char * bytes0x01d3f5c0, int length0x000001df) 行1891 C 33 QtWebKitd4.dll!WebCore::FrameLoaderClientQt::committedLoad(WebCore::DocumentLoader * loader0x00f881e0, const char * data0x01d3f5c0, int length0x000001df) 行680 C 34 QtWebKitd4.dll!WebCore::FrameLoader::committedLoad(WebCore::DocumentLoader * loader0x00f881e0, const char * data0x01d3f5c0, int length0x000001df) 行3513 C 35 QtWebKitd4.dll!WebCore::DocumentLoader::commitLoad(const char * data0x01d3f5c0, int length0x000001df) 行356 C 36 QtWebKitd4.dll!WebCore::DocumentLoader::receivedData(const char * data0x01d3f5c0, int length0x000001df) 行368 C 37 QtWebKitd4.dll!WebCore::FrameLoader::receivedData(const char * data0x01d3f5c0, int length0x000001df) 行2342 C 38 QtWebKitd4.dll!WebCore::MainResourceLoader::addData(const char * data0x01d3f5c0, int length0x000001df, bool allAtOncefalse) 行147 C 39 QtWebKitd4.dll!WebCore::ResourceLoader::didReceiveData(const char * data0x01d3f5c0, int length0x000001df, __int64 lengthReceived0x00000000000001df, bool allAtOncefalse) 行267 C 40 QtWebKitd4.dll!WebCore::MainResourceLoader::didReceiveData(const char * data0x01d3f5c0, int length0x000001df, __int64 lengthReceived0x00000000000001df, bool allAtOncefalse) 行342 C 41 QtWebKitd4.dll!WebCore::ResourceLoader::didReceiveData(WebCore::ResourceHandle * __formal0x00fb9aa0, const char * data0x01d3f5c0, int length0x000001df, int lengthReceived0x000001df) 行418 C 42 QtWebKitd4.dll!WebCore::QNetworkReplyHandler::forwardData() 行341 C 43 QtWebKitd4.dll!WebCore::QNetworkReplyHandler::qt_metacall(QMetaObject::Call _cInvokeMetaMethod, int _id0x00000002, void * * _a0x00fba378) 行74 C 44 QtCored4.dll!QMetaCallEvent::placeMetaCall(QObject * object0x00f810d0) 行478 C 45 QtCored4.dll!QObject::event(QEvent * e0x01d3ee18) 行1102 0x14 字节 C 46 QtGuid4.dll!QApplicationPrivate::notify_helper(QObject * receiver0x00f810d0, QEvent * e0x01d3ee18) 行4065 0x11 字节 C 47 QtGuid4.dll!QApplication::notify(QObject * receiver0x00f810d0, QEvent * e0x01d3ee18) 行3605 0x10 字节 C 48 QtCored4.dll!QCoreApplication::notifyInternal(QObject * receiver0x00f810d0, QEvent * event0x01d3ee18) 行610 0x15 字节 C 49 QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver0x00f810d0, QEvent * event0x01d3ee18) 行213 0x39 字节 C 50 QtCored4.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver0x00000000, int event_type0x00000000, QThreadData * data0x00e78f60) 行1247 0xd 字节 C 51 QtCored4.dll!QEventDispatcherWin32::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags{...}) 行679 0x10 字节 C 52 QtGuid4.dll!QGuiEventDispatcherWin32::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags{...}) 行1182 0x15 字节 C 53 QtCored4.dll!QEventLoop::processEvents(QFlagsenum QEventLoop::ProcessEventsFlag flags{...}) 行150 C 54 QtCored4.dll!QEventLoop::exec(QFlagsenum 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 argc0x00000001, char * * argv0x00e78e20) 行51 0x6 字节 C 58 previewer.exe!WinMain(HINSTANCE__ * instance0x00400000, HINSTANCE__ * prevInstance0x00000000, char * __formal0x001520d9, int cmdShow0x00000001) 行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 RefPtrSecurityOrigin 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 RefPtrDocument 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/xhtmlxml创建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/svgxml 对应 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?id108547 // The frames last ref may be removed and it can be deleted by checkCompleted(), 8 // so well add a protective refcount9 RefPtrFrame protector(m_frame); 10 11 // make sure nothings 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 docnil, 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 QListQByteArray 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 were 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 // shouldnt 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, PassRefPtrFormState prpFormState)2 {3 ASSERT(m_client-hasWebView());4 5 // Unfortunately the view must be non-nil, this is ultimately due6 // to parser requiring a FrameView. We should fix this dependency.7 8 ASSERT(m_frame-view());9 10 m_policyLoadType type; 11 RefPtrFormState formState prpFormState; 12 bool isFormSubmission formState; 13 14 const KURL newURL loader-request().url(); 15 16 if (shouldScrollToAnchor(isFormSubmission, m_policyLoadType, newURL)) { 17 RefPtrDocumentLoader 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 PassRefPtrFormState 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 // Dont 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, PassRefPtrWebCore::FormState)2 {3 Q_ASSERT(!m_policyFunction);4 Q_ASSERT(m_webFrame);5 m_policyFunction function;6 #if QT_VERSION 0x0404007 QWebNetworkRequest r(request);8 #else9 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, PassRefPtrFormState formState, bool shouldContinue)3 {4 FrameLoader* loader static_castFrameLoader*(argument);5 loader-continueLoadAfterNavigationPolicy(request, formState, shouldContinue);6 }7 8 void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest, PassRefPtrFormState formState, bool shouldContinue)9 { 10 // If we loaded an alternate page to replace an unreachableURL, well 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 cant continue: 18 // 1) Navigation policy delegate said we cant 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 doesnt 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 cache9 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, and9 // 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 the4 // fact that this callback is sent when starting every load, and the state of callback5 // 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 RefPtrMainResourceLoader 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 doesnt change when we redirect. 15 if (frameLoader()-isLoadingMainFrame()) 16 newRequest.setMainDocumentURL(newRequest.url()); 17 18 // If were 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 // Dont set this on the first request. It is set when the main load was started. 28 m_documentLoader-setRequest(newRequest); 29 30 // FIXME: Ideally wed stop the I/O until we hear back from the navigation policy delegate 31 // listener. But theres 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 do4 // anything including possibly derefing this; one example of this is Radar 3266216.5 RefPtrResourceLoader 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 PassRefPtrResourceHandle ResourceHandle::create(const ResourceRequest request, ResourceHandleClient* client,2 Frame* frame, bool defersLoading, bool shouldContentSniff, bool mightDownloadFromHandle)3 {4 RefPtrResourceHandle 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 an8 // onUnload handler, so lets just block it.9 if (!page) 10 return false; 11 12 getInternal()-m_frame static_castFrameLoaderClientQt*(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 dont 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 we5 // can send the response as early as possible6 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 // dont 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 RefPtrMainResourceLoader 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 do4 // anything including possibly derefing this; one example of this is Radar 3266216.5 RefPtrResourceLoader protector(this);6 7 addData(data, length, allAtOnce);8 // FIXME: If we get a resource with more than 2B bytes, this code wont do the right thing.9 // However, with todays computers and networking speeds, this wont 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_castint(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 datasource4 // by starting a new load, so retain temporarily.5 RefPtrDocumentLoader 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 appendDatatrue) 行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 // dont parse; we will do this later15 if (m_currentPrependingSrc)16 m_currentPrependingSrc-append(source);17 else {18 m_pendingSrc.append(source);19 #if PRELOAD_SCANNER_ENABLED20 if (m_preloadScanner m_preloadScanner-inProgress() appendData)21 m_preloadScanner-write(source);22 #endif23 }24 return false;25 }26 27 #if PRELOAD_SCANNER_ENABLED28 if (m_preloadScanner m_preloadScanner-inProgress() appendData)29 m_preloadScanner-end();30 #endif31 32 if (!m_src.isEmpty())33 m_src.append(source);34 else35 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_SCHEDULING45 if (!m_doc-ownerElement())46 printf(Beginning write at time %d , m_doc-elapsedTime());47 #endif48 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 // its important to keep needsSpecialWriteHandling with the flags this block tests73 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.jpg1. DOM Tree 与 Rendering Tree 的结构Figure 1中左上是一个简单的HTML文本文件右上是Webkit rendering engine绘制出来的页面。页面的内容包括一个标题“AI”一行正 “Apes Intelligence”以及一幅照片。整个页面分成前后两个层面标题和正文绘制在前一个层面照片处于后一个层面。L君和我亦步亦趋地跟踪了从解析这个HTML文本文件到生成DOM Tree和Rendering Tree的整个流程目的是为了了解DOM Tree和Rendering Tree的具体成份以及构造的各个步骤。先说Figure 1中左下角的DOM Tree。基本上HTML文本文件中每个tag在webkit/webcore/html中都有一个class与之对应。譬如HTML tag 对应HTMLHtmlElementHEAD tag 对应HTMLHeadElementSTYLE 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阅读和管理器制作数据库管理工具甚至制作游戏界面。稍微让人有点吃惊的是对于 HTMLHtmlElementHTMLBodyElementHTMLHeadingElement和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.jpg2. DOM Tree 与 Rendering Tree 的根节点前一节中我们提到HTMLDocument是一个比较特殊的class它是整个HTML DOM Tree的根节点但是不对应任何HTML tag。JavaScript中经常出现的document指的就是这个根。例如    “document.getElementByIdx(x).style.backgroundyellow;”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 15th 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.jpgFigure  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)去完成。常用的第三方图形工具库包括QTGTKWxSkiaCairo等等。打个比方图形工具库相当于活字以及绘制图像的石板(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 eventCourtesy http://farm4.static.flickr.com/3302/3640149734_3268bf297f_o.jpg先看一段简单的HTML文件。在浏览器里打开这个文件将看到两张照片。把鼠标移动到第一张照片点击鼠标左键将自动弹出一个窗口上书“World”。但是当鼠标移动到第二张照片或者其它任何区域点击鼠标却没有反应。关闭“World”窗口自动弹出第二个窗口上书“Hello”。 1 html2 script typetext/javascript3 function myfunction(v)4 {5 alert(v)6 }7 /script8 9 body onclickmyfunction(Hello) 10 p 11 img onclickmyfunction(World) height250 width290 srchttp://www.dirjournal.com/info/wp-content/uploads/2009/02/antarctica_mountain_mirrored.jpg 12 p 13 img height206 width275 srchttp://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 Engine4. Alert() 是HTML定义的方法还是JavaScript提供的函数谁负责生成那两个弹出的窗口是Webkit还是JavaScript Engine5. 注意到有两个οnclickmyfunction(...)当用户在第一张照片里点击鼠标的时候为什么是先后弹出而不是同时弹出6. 除了PC上的浏览器以外手机是否也可以完成同样的事件及其响应假如手机上没有鼠标但是有触摸屏如何把onclick定义成用手指点击屏幕7. 为什么需要深入了解这些问题 除了满足好奇心以外还有没有其它目的   Figure 2. Event callback stacksCourtesy 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对MFCGTK和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 οnclickmyfunction(World) height250 width290  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 method2. 间接调用外设的Script。οnclickalert(Hello)是第一种方式。alert()是W3C制订的标准的 HTML DOM methods之一。除此以外也有稍微复杂一点的methods譬如可以把这一句改成img οnclickdocument.write(Hello)。本文的例子οnclickmyfunction(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是TraceMonkeyTraceMonkey的前身是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 WebkitCourtesy http://farm4.static.flickr.com/3659/3640149732_e55446f6b3_b.jpgFigure 3 详细描绘了Webkit 设置JavaScript Engine 的全过程。在Webkit 解析HTML文件生成DOM Tree 和Render Tree 的过程中当解析到 img οnclick... 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 WebkitCourtesy 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 οnclickmyfunction(World)和body οnclickmyfunction(Hello)这意味着有两个DOM Tree Nodes 对onclick事件有响应它们的事件处理逻辑分别是myfunction(World) 和myfunction(Hello)这两个字符串。   当JavaScript Engine 获得事件处理逻辑的字符串后它把这个字符串根据JavaScript的语法规则解析为一棵树状结构称作Parse Tree。有了这棵Parse TreeJavaScript 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这一节比较枯燥因为涉及了太多的源代码细节。之所以这么不厌其烦地说明细节是为了解决如何更有效率地处理事件以及提供更丰富的手段去处理事件。待续。
http://wiki.neutronadmin.com/news/46223/

相关文章:

  • 三门峡市建设项目备案网站wordpress欢迎页
  • 做网站的属于什么工作类型全功能电子商务网站建设
  • 简述企业建设网站的必要性别人盗用我的网站备案号怎么办
  • 网站怎么上传代码吗网站文章不收录
  • 企业网站源码 非织梦施工企业会计的内涵
  • 苏州建站方法附近找工作招聘信息
  • 成都市建设招标网站淄博网站制作形象
  • 在线生成手机网站wordpress get_category_parents
  • wordpress建站案例学做网站从零开始
  • wordpress嵌入百度地图关闭站长工具seo综合查询
  • 网站开发公司特点网站建设中企动力最佳a5
  • 邢台企业网站制作建设wordpress 图片对其
  • 做导航网站把别人的网址链接过来要经过允许吗网站核检单
  • 免费个人网站服务器推荐wordpress 在线教育 模板
  • 松岗专业做网站公司wordpress正在执行例行维护
  • 教育类的网站案例东莞智通人才招聘网
  • 网站微信公众号链接怎么做优秀的定制网站建设
  • 广东省建设信息网站成绩查询广州网站建设 广州亦客网络
  • h5响应式网站技术阿里巴巴网站的功能
  • 长沙网站外包哪个网站做相册好
  • 如何海外网站建设wordpress采集电影
  • 沭阳做网站好的阿里网站建设视频教程
  • 购物型网站模板网站怎么做区域性优化
  • 网站内的地图导航怎么做长治推广型网站建设
  • 企业营销微网站建设百度site app网站添加到网站首页源文件中的代码是哪些?
  • 医院网站建设步骤代销网站源码
  • 淘宝网站建设方案模板下载做电影采集网站用什么vps
  • 网站安全维护网站备案号信息查询
  • python3 网站开发入门怎么查网站备案接入商
  • 网站的内连接如何做做网站员培训