博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
10、QT分析之WebKit
阅读量:6314 次
发布时间:2019-06-22

本文共 77587 字,大约阅读时间需要 258 分钟。

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

转载请注明出处

 

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

WebKit由三个模块组成:JavaScriptCore、WebCore 和 WebKit

WebKit作为了整个项目的名称。其目录结构:(未校准)

WebCore:

  ¨Page与外框相关的内容(Frame,Page,History,Focus,Window)

  ¨Loader加载资源及Cache

  ¨HTML-DOM HTML内容及解析

  ¨DOM- DOM CORE内容

  ¨XML- XML内容及解析

  ¨Render-排版功能

  ¨CSS-DOM CSS内容

  ¨Binding-DOM与JavascriptCore绑定的功能

  ¨Editing-所有与编辑相关的功能

  JavascriptCore-javascript引擎

  ¨API-基本javascript功能

  ¨Binding与其它功能绑定的功能,如:DOM,C,JNI

  ¨DerviedSource自动产生的代码

  ¨ForwordHeads头文件,无实际意义

  ¨PCRE-Perl-Compatible Regular Expressions 

  ¨KJS-Javascript Kernel

  ¨WTF-KDE的C++模板库

  Unicode unicode 库

  Tools tools库

  CURL-url 客户端传输库

  PlatForm- 与平台相关的功能,如图形图像,字体,Unicode, IO,输入法等.

在QT自带的例子中,有WebKit相关的例子。我选中previewer作为分析的项目。

 
previewer是QT自带的例子,运行之后的样子:
 
 
我是通过输入URL,进行跟踪分析的。下面是断点保存的调用堆栈,暂存资料

 

1      QtWebKitd4.dll!WebCore::MainResourceLoader::loadNow(WebCore::ResourceRequest & r={...})  行458    C++ 2      QtWebKitd4.dll!WebCore::MainResourceLoader::load(const WebCore::ResourceRequest & r={...}, const WebCore::SubstituteData & substituteData={...})  行494 + 0x12 字节    C++ 3      QtWebKitd4.dll!WebCore::DocumentLoader::startLoadingMainResource(unsigned long identifier=0x00000004)  行807 + 0x32 字节    C++ 4      QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterWillSubmitForm(WebCore::PolicyAction __formal=PolicyUse)  行3274 + 0x16 字节    C++ 5      QtWebKitd4.dll!WebCore::FrameLoader::continueLoadAfterNavigationPolicy(const WebCore::ResourceRequest & __formal={...}, WTF::PassRefPtr
formState={...}, bool shouldContinue=true) 行3968 C++ 6 QtWebKitd4.dll!WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy(void * argument=0x01d424e8, const WebCore::ResourceRequest & request={...}, WTF::PassRefPtr
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
__formal={...}) 行938 C++11 QtWebKitd4.dll!WebCore::FrameLoader::checkNavigationPolicy(const WebCore::ResourceRequest & request={...}, WebCore::DocumentLoader * loader=0x00f63ff8, WTF::PassRefPtr
formState={...}, void (void *, const WebCore::ResourceRequest &, WTF::PassRefPtr
, bool)* function=0x1004e661, void * argument=0x01d424e8) 行3868 C++12 QtWebKitd4.dll!WebCore::FrameLoader::loadWithDocumentLoader(WebCore::DocumentLoader * loader=0x00f63ff8, WebCore::FrameLoadType type=FrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtr
prpFormState={...}) 行2291 C++13 QtWebKitd4.dll!WebCore::FrameLoader::loadWithNavigationAction(const WebCore::ResourceRequest & request={...}, const WebCore::NavigationAction & action={...}, WebCore::FrameLoadType type=FrameLoadTypeRedirectWithLockedHistory, WTF::PassRefPtr
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
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
flags={...}) 行679 + 0x10 字节 C++52 QtGuid4.dll!QGuiEventDispatcherWin32::processEvents(QFlags
flags={...}) 行1182 + 0x15 字节 C++53 QtCored4.dll!QEventLoop::processEvents(QFlags
flags={...}) 行150 C++54 QtCored4.dll!QEventLoop::exec(QFlags
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 字节 C60 previewer.exe!WinMainCRTStartup() 行399 C61 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
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;28 29 if (!m_isDisplayingInitialEmptyDocument && m_client->shouldUsePluginDocument(m_responseMIMEType))30 document = PluginDocument::create(m_frame);31 else32 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(); // 更新排布策略的基础URL57 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_STYLESHEET68 KURL userStyleSheet = settings ? settings->userStyleSheetLocation() : KURL();69 if (!userStyleSheet.isEmpty())70 m_frame->setUserStyleSheetLocation(userStyleSheet);71 #endif72 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 resources82 // used to give an instant visual feedback. We currently only enable it for83 // 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 #endif91 }

看其中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 protector(m_frame);10 11     // make sure nothing's left in there12     if (m_frame->document()) {13         write(0, 0, true);14         m_frame->document()->finishParsing();15    } else16         // WebKit partially uses WebCore when loading non-HTML docs.  In these cases doc==nil, but17         // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to18         // become true.  An example is when a subframe is a pure text doc, and that subframe is the19         // 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
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     } else11         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're17     // 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
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 = prpFormState;12 bool isFormSubmission = formState;13 14 const KURL& newURL = loader->request().url();15 16 if (shouldScrollToAnchor(isFormSubmission, m_policyLoadType, newURL)) {17 RefPtr
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, 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
) 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 #endif11 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, bool shouldContinue) 3 { 4 FrameLoader* loader = static_cast
(argument); 5 loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue); 6 } 7 8 void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr
formState, bool shouldContinue) 9 {10 // If we loaded an alternate page to replace an unreachableURL, we'll get in here with a11 // nil policyDataSource because loading the alternate page will have passed12 // 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 //
- 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 else67 continueLoadAfterWillSubmitForm();68 }69 70 void FrameLoader::continueLoadAfterWillSubmitForm(PolicyAction)71 {72 if (!m_provisionalDocumentLoader)73 return;74 75 // DocumentLoader calls back to our prepareForLoadStart76 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 #endif23 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 for10     // initial requests.11     willSendRequest(r, ResourceResponse());12 13     // 
14 // willSendRequest() is liable to make the call to frameLoader() return NULL, so we need to check that here15 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 else29 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 last10     // reference to this object; one example of this is 3266216.11     RefPtr
protect(this);12 13 // Update cookie policy base URL as URL changes, except for subframes, which use the14 // 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, since19 // this is a common site technique to return to a page viewing some data that the POST20 // 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 delegate31 // listener. But there's no way to do that in practice. So instead we cancel later if the32 // listener tells us to. In practice that means the navigation policy needs to be decided33 // synchronously for these redirect cases.34 35 ref(); // balanced by deref in continueAfterNavigationPolicy36 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
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         else21             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::create(const ResourceRequest& request, ResourceHandleClient* client, 2 Frame* frame, bool defersLoading, bool shouldContentSniff, bool mightDownloadFromHandle) 3 { 4 RefPtr
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
(frame->loader()->client())->webFrame();13 #if QT_VERSION < 0x04040014 return QWebNetworkManager::self()->add(this, getInternal()->m_frame->page()->d->networkInterface);15 #else16 ResourceHandleInternal *d = getInternal();17 d->m_job = new QNetworkReplyHandler(this, QNetworkReplyHandler::LoadMode(d->m_defersLoading));18 return true;19 #endif20 }

新创建了一个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 for12     // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html13     // we still need to retrieve the file/data, which means we map it to a Get instead.14     if (m_method == QNetworkAccessManager::PostOperation15         && (!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 we58     // can send the response as early as possible59     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 HTML10     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 
for more details. 8 #if !PLATFORM(CF) 9 ASSERT(!defersLoading());10 #endif11 12 // The additional processing can do anything including possibly removing the last13 // reference to this object; one example of this is 3266216.14 RefPtr
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
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
(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
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 created15     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 in19             // setting up this stream can cause the main document load to be cancelled, setting m_pluginView20             // to null21             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 #endif40 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 XML13         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 instruction102                     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 through118                 default: {119                     if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z'))) {120                         // Start of a Start-Tag121                     } else {122                         // Invalid tag123                         // Add as is124                         *m_dest = '<';125                         m_dest++;126                         continue;127                     }128                 }129                 }; // end case130 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 LF148                 state.setDiscardLF(false); // We have discarded 1 LF149             else {150                 // Process this LF151                 *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_SCHEDULING168     if (!m_doc->ownerElement())169         printf("Ending write at time %d ", m_doc->elapsedTime());170 #endif171     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 deleted178         return true;179     }180     return false;181 }

在调用的时候,因为调用参数decoded是String类型的,所以先隐含转化成SegmentedString。SegmentedString可以附带行号,也可以不带行号(可以设定)。上面程序中的while循环主体,就是一个分析程序主体。

 

WebKit的结构与解构

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

从指定一个HTML文本文件,到绘制出一幅布局复杂,字体多样,内含图片音频视频等等多媒体内容的网页,这是一个复杂的过程。在这个过程中Webkit所做的一切,都是围绕DOM Tree和Rendering Tree这两个核心。上一章我们谈到这两棵树各自的功用,这一章,我们借一个简单的HTML文件,展示一下DOM Tree和Rendering Tree的具体构成,同时解剖一下Webkit是如何构造这两棵树的。

 

 

Figure 1. From HTML to webpage, and the underlying DOM tree and rendering tree. Courtesy http://farm4.static.flickr.com/3351/3556972420_23a30366c2_o.jpg

1. DOM Tree 与 Rendering Tree 的结构
Figure 1中左上是一个简单的HTML文本文件,右上是Webkit rendering engine绘制出来的页面。页面的内容包括一个标题,“AI”,一行正 ,“Ape's Intelligence”,以及一幅照片。整个页面分成前后两个层面,标题和正文绘制在前一个层面,照片处于后一个层面。L君和我亦步亦趋地跟踪了,从解析这个HTML文本文件,到生成DOM Tree和Rendering Tree的整个流程,目的是为了了解DOM Tree和Rendering Tree的具体成份,以及构造的各个步骤。
先说Figure 1中左下角的DOM Tree。基本上HTML文本文件中每个tag,在webkit/webcore/html中都有一个class与之对应。譬如<HTML> tag 对应HTMLHtmlElement,<HEAD> tag 对应HTMLHeadElement,<STYLE> tag 对应HTMLStyleElement 等等。比较特别的是DOM Tree的根节点,HTMLDocument,在HTML文本文件中没有哪个tag与之对应。关于HTMLDocument的作用,我们稍后介绍。整个 DOM Tree的结构,与HTML文本文件中各个tags的嵌套关系也一一对应。一言以蔽之,DOM Tree就是把HTML文本文件翻译成object树状结构。
需要强调的是,DOM Tree是一个通用数据结构,任何XML文本文件都可以翻译成DOM Tree,而不仅仅限于HTML文本文件。webkit/webcore/html 中林林总总html classes,基本上都是webkit/webcore/dom 中的某个class的子类,也就是说,/html 是 /dom的一个特例。这样的设计,为将来把Webkit拓展到HTML格式以外的页面的布局和渲染,埋下了伏笔。所以严格地讲,Figure 1中左下的DOM Tree,实际上是一个HTML DOM Tree。
再看Rendering Tree,显著的特点在于,
a. 整个Rendering Tree树状结构,与HTML DOM Tree树状结构一一对应。也就是说,几乎每个HTML DOM Tree中的节点,在Rendering Tree中都有对应的节点。节点与节点之间的父子或兄弟关系也一一对应。
例外的是,在HTML DOM Tree有HTMLStyleElement叶子节点,而在Rendering Tree中,没有相应的叶子节点。原因是,Rendering Tree各个节点,都涉及页面中某块区域的布局和渲染。而HTMLStyleElement,并不直接涉及某块区域的布局和渲染,HTML DOM Tree中HTMLStyleElement叶子节点包含的内容,已经融入Rendering Tree中RenderImage叶子节点的属性中去了。另外,因为Rendering Tree中不存在与HTMLStyleElement相应的叶子节点,所以,与HTMLHeadElement对应的节点也没有必要存在。
b. webkit/webcore/rendering中各个class与HTML tags并没有一一对应的关系。
Rendering Tree是一个通用的规划页面布局和渲染的机制,这个通用机制可以服务于HTML页面,但是并不仅仅限于为HTML页面服务,我们可以用 Rendering Tree来规划其它格式的页面的布局和渲染。以DOM Tree和Rendering Tree为核心的Webkit渲染机,是一个功能强大,扩展性良好的通用渲染机。它不仅可以用来绘制HTML页面,也可以用来渲染其它格式的页面,譬如可以用它来制作email阅读和管理器,制作数据库管理工具,甚至制作游戏界面。
稍微让人有点吃惊的是,对于 HTMLHtmlElement,HTMLBodyElement,HTMLHeadingElement和HTMLParagraphElement,在Rendering Tree中通通以RenderBlock呼应。如果说HTMLHeadingElement和HTMLParagraphElement的区别不大,仅仅是字体和对齐方式有些微小的差别,所以Rendering Tree可以用RenderBlock来统一应对。那么问题是,HTMLHtmlElement和HTMLBodyElement是两种容器,总是出现在 DOM Tree的中部,而从来不会作为叶子节点出现,对应于这样的容器节点,为什么Rendering Tree不另设一种class,与RenderBlock有所区别呢?不过话又说回来,这不是个大问题,最多是个美感的问题。

 

Figure 2. The construction sequence of the root of the DOM tree.

Courtesy http://farm4.static.flickr.com/3010/3554310018_e34d271344_o.jpg
2. DOM Tree 与 Rendering Tree 的根节点
前一节中我们提到HTMLDocument是一个比较特殊的class,它是整个HTML DOM Tree的根节点,但是不对应任何HTML tag。JavaScript中经常出现的document,指的就是这个根。例如,
    “document.getElementByIdx(x).style.background="yellow";”
HTML文本文件,通常是以<HTML>开头,以</HTML>结尾。但是<HTML> tag并不对应DOM Tree的根节点,而是根以下的第一个子节点,即HTMLHtmlElement节点。
初看Figure 2 觉得有点意外,当用户在浏览器里打开一个空白页面的时候,立刻生成了DOM Tree的根节点HTMLDocument,与Rendering Tree的根节点RenderView。而这个时候,用户并没有给定URL,也就是说,对于浏览器来讲,这时候具体的HTML文本文件并不存在。根节点与具体HTML内容相脱节,或许暗示了Webkit的两个设计思路,
a. DOM Tree的根节点HTMLDocument,与Rendering Tree的根节点RenderView,可以重复利用。
当用户在同一个浏览器页面中,先后打开两个不同的URLs,也就是两个不同的HTML文本文时,HTMLDocument和RenderView两个根节点并没有发生改变,改变的是HTMLHtmlElement以下的子树,以及对应的Rendering Tree的子树。
为什么这样设计?原因是HTMLDocument和RenderView服从于浏览器页面的设置,譬如页面的大小和在整个屏幕中的位置等等。这些设置与页面中要显示什么的内容无关。同时HTMLDocument绑定HTMLTokenizer和HTMLParser,这两个构件也与某一个具体的HTML内容无关。
b. 同一个DOM Tree的根节点可以悬挂多个HTML子树,同一个Rendering Tree的根节点可以悬挂多个RenderBlock子树。
在我们目前所见到的浏览器中,每一个页面通常只显示一个HTML文件。虽然一个HTML文件可以分割成多个frames,每个frame承载一个独立的 HTML文件,但是从DOM Tree结构来讲,HTMLDocument根节点以下,只有一个子节点,这个子节点是HTMLHtmlElement,它领衔某个HTML文本文件对应的子树。Rendering Tree也一样,目前我们见到的网页中,一个RenderView根节点以下,也只有一个RenderBlock子节点。
但是Webkit的设计,却允许同一个根以下,悬挂多个HTML子树。虽然我们目前没有看到一个页面中,并存多个HTML文件,并存多个布局和渲染风格的情景,但是Webkit为将来的拓展留下了空间。前文中所设想的个性化,多皮肤,多视角的浏览器页面绘制,用Webkit实现起来难度不大。

 Figure 3. The construction sequence of the DOM Tree and the Rendering Tree.

Courtesy http://farm4.static.flickr.com/3627/3554182242_b0bec88534_b.jpg
 
3. DOM Tree 与 Rendering Tree 的构筑
HTMLDocument 根节点包含的最重要的构件是HTMLTokenizer,而HTMLTokenizer又包含HTMLParser这个构件。HTMLTokenizer 从前到后读取HTML文本文件中每一个字符,并从中提取出各个HTML tags以及它们的内容。而HTMLParser不仅负责HTML DOM Tree的构筑,而且也同时负责Rendering Tree的构筑。
在Figure 3中,从第8步到第11步,HTMLParser根据一个HTML Tag生成一个HTML DOM Tree节点。从第12步到第17步,生成相应的Rendering Tree的节点,并把它和HTML DOM Tree的节点勾连在一起。这张图的细节过多,读解不容易。Figure 4把第8步到第17步演示了一下。

 

Figure 4. An illustration of the construction of a DOM tree node and its corresponding Rendering tree node.

Courtesy http://farm4.static.flickr.com/3306/3554259140_3deb9736ea_o.jpg
值得注意的是,每当HTMLParser生成一个DOM Tree的节点的时候,相应地,也同时生成一个Rendering Tree节点。然后把它们两个新节点勾连在一起。换而言之,Rendering Tree与DOM Tree同步生长。
Webkit 值得赞赏的地方非常多,但是HTMLParser让DOM Tree和Rendering Tree同步生长的做法,却值得商榷。如果同步生长,那么Rendering Tree必然平铺直叙地刻板地忠实于DOM Tree。假设先生成DOM Tree,再生成Rendering Tree,把两者割裂开,就有机会让Webkit发挥更加奇妙的布局和渲染。平铺直叙固然符合大多数人在大多数时间里的阅读习惯,但是离经叛道的设计,也会有市场。一个例子就是上一章末尾处那张多视点的地图。如果让DOM Tree与Rendering Tree同步生长,这样的布局和渲染是难以想像的。

 

 

WebKit的显示,继续转邓侃博士的blog

WebKit,为了布局,忙并美丽着

如果没有1440年以后活字印刷术的大规模普及,或许就不会有文艺复兴运动,更不会有后来的启蒙运动。如果没有这两个运动的开展,或许就不会有世界范围的工业化。

在活字印刷术出现以前,每出版一本书,都必须先刻制一套模版,称为雕版,每套雕版上的每一个字,都是手工雕刻的。不仅制作雕版费时费力,而且有了错误不容易更改。活字印刷术的进步在于,可以预先批量生产各种样式和大小的字体,称为活字。需要出版某一本书籍时,先制作该书的页面模版,模版做好以后,只需要把这些活字摆放在模版上即可。如果出现错误,只需要调换某些活字,既省时又省力。如果某本书的模版不需要长期保存,还可以把模版中摆放的活字拆解下来,在印刷其它图书时用,节约成本。
活字印刷术没有解决的问题,1. 
图像的印刷。起初不能印刷笔触丰富,层次复杂的图像,一直到1796年,石板印刷术(lithography)出现以后,才能印刷表现手段丰富的图像。 2. 
灵活的布局排版。纸张大小不同,布局排版也不同,布局变了,需要重新摆放活字,而且有时候还需要改变字体和大小。
灵活的布局排版对于纸质书籍来说,或许并不太重要,但是对于电脑浏览器来说,却必须实现完全的自动化。否则,每当用户改变浏览器窗口的大小的时候,页面内容就不能正确显示。对于手机浏览器来说,布局排版的自动化尤其重要,因为不同手机的屏幕不一致,而且屏幕分辨率也不同。
但是即便是浏览器,也没有摆脱传统的排版方式。所谓传统的排版方式,基本是横平竖直的,单一的鸟瞰视角。

 

Figure 1. Incunabulum, the end of 15'th century Courtesy 

http://www.citrinitas.com/history_of_viscom/images/printing/venice-1505.jpg

Figure 2. City of Words, by Vito Acconi, 1999 Courtesy 

http://upload.wikimedia.org/wikipedia/en/6/63/%27City_of_Words%27%2C_lithograph_by_Vito_Acconci%2C_1999.jpg
Figure  1 中显示的是1490年代的书籍,不难看出,现代书报中广泛使用的双列,边注,页码,首字母大写等等,都是继承了500多年以前的做法。而CSS规范,囊括了所有这些页面设计的要素。
在当今信息爆炸的形势下,如何安排页面的布局排版,在有限的页面面积内,承载更多内容,突出读者关注的内容,增强页面设计的视觉美感,成为不可回避的问题。例如,手机购物的UI设计,既要包含商品简介,又要包含用户意见反馈,还要包含实物照片,以及各个不同商场的标价等等。完美的页面设计,不仅要求简练而清晰,而且也不能遗漏相关内容,实在是一件困难的事情。可以说,手机购物之所以不普及,与手机购物的UI设计笨拙而丑陋是相关的。
要巧妙地 设计手机应用的UI设计,终极而言,需要突破传统的单一鸟瞰视角的方式,Figure 2 就是这方面的尝试。Webkit能不能做到这一点?原理上是可以做到的,但是必须修改源代码。但是在改造以前,我们还是先踏踏实实研究一下,Webkit 的布局排版的内部机制是什么。只有充分了解对方之长,才有可能改进对方之短。
读解Webkit排版布局与绘制的具体实现以前,首先需要明确的是,Webkit把排版布局(layout),与绘制(paint),分开处理。
Layout负责确定Render Tree中,每个叶子和中间节点的位置。每个节点在屏幕上的显示,都呈长方形格局。所谓位置,指的是这个长方形左上角起始坐标(X,Y),以及长方形的宽度和高度。每个中间节点的长方形,里面嵌套着若干小长方形,对应这个中间节点的后代节点等等。
在Layout过程结束以后,Webkit启动 Paint过程,负责把Render Tree中各个叶子节点,在相应的位置绘制出来。Webkit 把具体绘制的工作,交给第三方图形工具库(Graphics Library)去完成。常用的第三方图形工具库包括QT,GTK+,Wx,Skia,Cairo等等。
打个比方,图形工具库相当于活字,以及绘制图像的石板(lithography),它们负责paint。而从严格意义上来说,Webkit的主要工作是layout,也就是排版布局,相当于版面模版。
关于图形库,台湾的开源高手,黄敬群(Jim Huang / jserv),写过一篇介绍Google Skia 图形库的文章(http://blog.linux.org.tw/~jserv/archives/002095.html)。文中谈到,
Google 为了搭建Android平台,于2005年8月并购了Android公司。同年11月份,Google还收购了Skia公司。2007年11 月,Google发布Android,并公开部分源代码。当人们热衷于探究Android Dalvik VM的奥秘的时候,忽略了Skia的意义。
2008年9月,Google发布了以改良的Webkit为核心的Chrome PC浏览器。当人们热衷于探究V8 JavaScript引擎等等功能模块时,再次忽略了Skia的意义。
Skia是一个2D图形工具库,该产品的特色在于,能够在手机等等移动设备中,以较低的内存和CPU消耗,呈现高品质的2D图形。
Skia 的创办人,Mike Reed,是图形技术方面的顶尖人物。Mike早年任职于Apple,参与QuickDraw GX项目,处理字型和图像显示。后来他跳槽到OpenWave,开发手机浏览器。在OpenWave工作期间,与Benoit Schillings合作,在50-300KB的内存空间内,提供图层之间alpha 
blended方式的预览,以及全功能向量矩阵转换等等,真可谓螺丝壳里做道场。后来Benoit Schillings离开OpenWave,去Trolltech任职CTO。Trolltech的主打产品是大名鼎鼎的QT。再后来Trolltech 被Nokia并购,Benoit随之加入Nokia。Benoit Schillings离开OpenWave不久,Mike 
Reed也离开了OpenWave,去创建Skia公司。

Figure 3. Layout implementation in Webkit Courtesy 

http://www.flickr.com/photos/87209438%40N00/3609632247/sizes/l/

Figure 4. Paint implementation in Webkit Courtesy http://www.flickr.com/photos/87209438%40N00/3609632249/sizes/l/

Figure 3 和 Figure 4,分别显示了Webkit执行排版布局(layout),以及绘制(paint)的两个过程。仔细查看这两张sequence diagrams,会发现以下特点,
1. Layout 和 Paint 这两个过程完全分开。开始执行Paint过程以前,必然预先执行过Layout,否则图形库就不知道在哪里写字以及显示图像。但是这并不意味着,Layout执行结束后,随即就立刻执行Paint。实际上,Layout执行结束后,触发一个事件,这个事件启动Paint过程。但是Paint过程也可以被其它事件触发,譬如屏幕内容的切换,以及把隐藏的浏览器窗口复原等等。
2. Layout 涵盖了所有CSS规定的布局要素。包括页面边缘与内容之间的空白,文字对插入图像的避让(floating),单列与多列,上下层覆盖(z-index)等等。
3. 图像,视频播放器插件,Applet等等,在 Layout 被称作 Replaced Render Object。这些 Replaced 元素的宽度和高度可以由CSS规定。如果CSS没有规定,就解析这些元素的数据流,譬如一个JPG照片的metadata里,规定了这幅照片原件的宽度和高度。如果元素自己也没有规定宽度高度,就使用Webkit提供的缺省值。
4. 文字的宽度根据页面的排版来确定。譬如一页中包含多列文字,则每列文字宽度相等。每列文字的宽度,乘以列数,加上列与列之间的夹缝,加上页面边缘空白等等,应当等于页面总的宽度。假设页面总的宽度已知,边缘空白,和列与列之间的夹缝的宽度也已知,就可以反推文字的宽度。
5. Render Tree中每个节点在屏幕上的显示,都呈长方形格局。前面第3点和第4点,描述了宽度的确定。而高度的确定,取决于这个中间节点的所有后代节点的高度的总和。对于 Replaced 元素来说,它的高度相对比较容易确定,而文字段落的高度,需要根据字数,字型,以及字体大小计算得出。
6. 在 Layout 过程中,反复出现以 Repaint 为开头的子过程,例如 repaintAfterLayoutIfNeed

ed()。这些子过程的意义在于,当确定了某个节点的高度和宽度以后,需要对其前辈节点,和左右兄弟节点的位置,做适当调整。严格意义上来讲,这不是repaint,而是relayout。
7. 相对于 Layout 过程,Paint 过程的逻辑要简单得多。Paint的过程,大致按照深度优先的顺序,遍历整棵RenderTree。也就是说,从最左边的叶子节点开始,从左向右逐个绘制 RenderTree所有可以显示的叶子节点。所谓“可以显示的叶子节点”,是因为CSS中可以规定,不显示某些叶子。
反复研究以上Layout和Paint的过程,我们有以下看法。
1. Layout 是一个计算量很繁重的过程。之所以繁重,主要体现在估算完每个RenderTree节点的宽度尤其是高度以后,需要相应调整这个节点的前辈节点以及左邻右舍兄弟节点的位置。对于文字段落而言,它的高度有赖于字数,字体和大小,所以估算不容易准确。
有没有可能把Layout 过程,与第一遍 Paint 过程合二为一?只要遍历一次RenderTree的所有叶子节点,绘制图像并码字。Paint过程结束后,各个叶子节点对应的长方形的起始位置的(X,Y)坐标,以及宽度和高度都自然迎刃而解。然后再由叶子节点开始,逐步确定RenderTree中,各个中间节点的起始位置和宽度高度。这样做的好处是,可以大大降低 Layout 过程的成本。
2. Layout 过程假设每个RenderTree 的节点都对应一个长方形屏幕区域。受限于这个规定,类似于Figure 2的效果,就显示不出来。有没有可能取消这个限制?SVG不仅提供了强大的绘图能力,而且也提供了强大的排版布局能力。能不能把CSS当着SVG格式的一个子集来看待?
 
WebKit,鼠标引发的故事
 
Figure 1. JavaScript onclick event
Courtesy 
先看一段简单的HTML文件。在浏览器里打开这个文件,将看到两张照片。把鼠标移动到第一张照片,点击鼠标左键,将自动弹出一个窗口,上书“World”。但是当鼠标移动到第二张照片,或者其它任何区域,点击鼠标,却没有反应。关闭“World”窗口,自动弹出第二个窗口,上书“Hello”。
1  2    8  9   10     

11 12

13 14 15

这段HTML文件没有什么特别之处,所有略知一点HTML的人,估计都会写。但是耳熟能详,未必等于深入了解。不妨反问自己几个问题,

1. 浏览器如何知道,是否鼠标的位置,在第一个照片的范围内?
2. 假如修改一下HTML文件,把第一张照片替换成另一张照片,前后两张照片的尺寸不同。在浏览器里打开修改后的文件,我们会发现,能够触发弹出窗口事件的区域面积,随着照片的改变而自动改变。浏览器内部,是通过什么样的机制,自动识别事件触发区域的?
3. Onclick 是HTML的元素属性(Element attribute),还是JavaScript的事件侦听器(EventListener)?换而言之,当用户点击鼠标以后,负责处理onclick事件的,是Webkit 还是JavaScript Engine?
4. Alert() 是HTML定义的方法,还是JavaScript提供的函数?谁负责生成那两个弹出的窗口,是Webkit还是JavaScript Engine?
5. 注意到有两个οnclick="myfunction(...)",当用户在第一张照片里点击鼠标的时候,为什么是先后弹出,而不是同时弹出?
6. 除了PC上的浏览器以外,手机是否也可以完成同样的事件及其响应?假如手机上没有鼠标,但是有触摸屏,如何把onclick定义成用手指点击屏幕?
7. 为什么需要深入了解这些问题? 除了满足好奇心以外,还有没有其它目的?

 

Figure 2. Event callback stacks

Courtesy 
当用户点击鼠标,在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 οnclick="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。οnclick="alert('Hello')",是第一种方式。alert()是W3C制订的标准的 HTML DOM methods之一。除此以外,也有稍微复杂一点的methods,譬如可以把这一句改成,<img οnclick="document.write('Hello')">。本文的例子,οnclick="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 
Figure 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 Webkit

Courtesy 
当解析完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 οnclick="myfunction('World')">,和<body οnclick="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
这一节比较枯燥,因为涉及了太多的源代码细节。之所以这么不厌其烦地说明细节,是为了解决如何更有效率地处理事件,以及提供更丰富的手段去处理事件。待续。

转自:http://www.cnblogs.com/lfsblack/p/5278777.html
你可能感兴趣的文章
java 基于QRCode、zxing 的二维码生成与解析
查看>>
关于职业规划的一些思考
查看>>
img垂直水平居中与div
查看>>
Fabrik – 在浏览器中协作构建,可视化,设计神经网络
查看>>
防恶意注册的思考
查看>>
http2-head compression
查看>>
C# 命名空间
查看>>
订餐系统之同步美团商家订单
查看>>
使用ArrayList时设置初始容量的重要性
查看>>
Java Web-----JSP与Servlet(一)
查看>>
Maven搭建SpringMVC+Mybatis项目详解
查看>>
关于量子理论:最初无意的简化,和一些人有意的强化和放大
查看>>
CentOS 6.9通过RPM安装EPEL源(http://dl.fedoraproject.org)
查看>>
“区块链”并没有什么特别之处
查看>>
没有功能需求设计文档?对不起,拒绝开发!
查看>>
4星|《先发影响力》:影响与反影响相关的有趣的心理学研究综述
查看>>
IE8调用window.open导出EXCEL文件题目
查看>>
python之 列表常用方法
查看>>
vue-cli脚手架的搭建
查看>>
在网页中加入百度搜索框实例代码
查看>>