QT分析之WebKit(三)

QT分析之WebKit(三)

分三个阶段对QWebView进行分析:初始化(获取数据)、HTML解析、页面显示。从QT自带的文档中可以知道:

QWebView -> QWebPage => QWebFrame(一个QWebPage含多个QWebFrame)

在界面中选择了Open URL,输入URL之后,调用的是:void MainWindow::openUrl()

void MainWindow::openUrl()
{
    bool ok;
    QString url = QInputDialog::getText(this, tr("Enter a URL"),
                  tr("URL:"), QLineEdit::Normal, "http://", &ok);

    if (ok && !url.isEmpty()) {
        centralWidget->webView->setUrl(url);
    }
}

调用的是QWebView::setUrl()

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

其中page()是获取QWebPage指针,QWebPage::mainFrame()获取的是QWebFrame指针。

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

void QWebFrame::setUrl(const QUrl &url)
{
    d->frame->loader()->begin(ensureAbsoluteUrl(url));
    d->frame->loader()->end();
    load(ensureAbsoluteUrl(url));
}

ensureAbsoluteUrl()函数作用是,确保URL是绝对URL(完整URL)。所谓相对URL是指没有输入http://或者https://等前缀的web地址。先看第一句的调用。其中隐含了从QUrl到KURL的变换。

void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin)
{
    // We need to take a reference to the security origin because |clear|
    // might destroy the document that owns it.
    RefPtr<SecurityOrigin> forcedSecurityOrigin = origin;

    bool resetScripting = !(m_isDisplayingInitialEmptyDocument && m_frame->document() && m_frame->document()->securityOrigin()->isSecureTransitionTo(url));
    clear(resetScripting, resetScripting);      // 清除上一次的数据,为本次装载准备
    if (resetScripting)   
        m_frame->script()->updatePlatformScriptObjects();    // 在Windows平台下,这是空函数
    if (dispatch)
        dispatchWindowObjectAvailable();

    m_needsClear = true;
    m_isComplete = false;
    m_didCallImplicitClose = false;
    m_isLoadingMainResource = true;
    m_isDisplayingInitialEmptyDocument = m_creatingInitialEmptyDocument;

    KURL ref(url);
    ref.setUser(String());
    ref.setPass(String());
    ref.setRef(String());
    m_outgoingReferrer = ref.string();
    m_URL = url;

    RefPtr<Document> document;
    
    if (!m_isDisplayingInitialEmptyDocument && m_client->shouldUsePluginDocument(m_responseMIMEType))
        document = PluginDocument::create(m_frame);
    else
        document = DOMImplementation::createDocument(m_responseMIMEType, m_frame, m_frame->inViewSourceMode());    // 创建DOM文件,m_responseMIMEType不同实体不同。

// 如果是"text/html"创建HTMLDocument实体;"application/xhtml+xml"创建Document实体

// 如果是"application/x-ftp-directory"则是FTPDirectoryDocument实体

// text/vnd.wap.wml 对应 WMLDocument 实体(无线)

// "application/pdf" /"text/plain" 对应 PluginDocument实体

// 如果是MediaPlayer::supportsType(type),创建的是MediaDocument实体

// "image/svg+xml" 对应 SVGDocument实体
    m_frame->setDocument(document);

    document->setURL(m_URL);
    if (m_decoder)
        document->setDecoder(m_decoder.get());
    if (forcedSecurityOrigin)
        document->setSecurityOrigin(forcedSecurityOrigin.get());

    m_frame->domWindow()->setURL(document->url());
    m_frame->domWindow()->setSecurityOrigin(document->securityOrigin());

    updatePolicyBaseURL();   // 更新排布策略的基础URL

    Settings* settings = document->settings();
    document->docLoader()->setAutoLoadImages(settings && settings->loadsImagesAutomatically());

    if (m_documentLoader) {
        String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control");
        if (!dnsPrefetchControl.isEmpty())
            document->parseDNSPrefetchControlHeader(dnsPrefetchControl);
    }

#if FRAME_LOADS_USER_STYLESHEET
    KURL userStyleSheet = settings ? settings->userStyleSheetLocation() : KURL();
    if (!userStyleSheet.isEmpty())
        m_frame->setUserStyleSheetLocation(userStyleSheet);
#endif

    restoreDocumentState();

    document->implicitOpen();

    if (m_frame->view())
        m_frame->view()->setContentsSize(IntSize());

#if USE(LOW_BANDWIDTH_DISPLAY)
    // Low bandwidth display is a first pass display without external resources
    // used to give an instant visual feedback. We currently only enable it for
    // HTML documents in the top frame.
    if (document->isHTMLDocument() && !m_frame->tree()->parent() && m_useLowBandwidthDisplay) {
        m_pendingSourceInLowBandwidthDisplay = String();
        m_finishedParsingDuringLowBandwidthDisplay = false;
        m_needToSwitchOutLowBandwidthDisplay = false;
        document->setLowBandwidthDisplay(true);
    }
#endif
}

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

void Document::implicitOpen()
{
    cancelParsing();

    clear();
    m_tokenizer = createTokenizer();
    setParsing(true);
}

Tokenizer *HTMLDocument::createTokenizer()
{
    bool reportErrors = false;
    if (frame())
        if (Page* page = frame()->page())
            reportErrors = page->inspectorController()->windowVisible();

    return new HTMLTokenizer(this, reportErrors);
}

新创建的HTMLTokenizer对象,就是HTML的解析器。

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

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

void FrameLoader::endIfNotLoadingMainResource()
{
    if (m_isLoadingMainResource || !m_frame->page())
        return;

    // http://bugs.webkit.org/show_bug.cgi?id=10854
    // The frame's last ref may be removed and it can be deleted by checkCompleted(), 
    // so we'll add a protective refcount
    RefPtr<Frame> protector(m_frame);

    // make sure nothing's left in there
    if (m_frame->document()) {
        write(0, 0, true);
        m_frame->document()->finishParsing();

   } else
        // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but
        // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to
        // become true. An example is when a subframe is a pure text doc, and that subframe is the
        // last one to complete.
        checkCompleted();
}

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

void QWebFrame::load(const QUrl &url)
{
   load(QNetworkRequest(ensureAbsoluteUrl(url)));
}

新建一个QNetworkRequest对象,然后调用

    void load(const QNetworkRequest &request,
              QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
              const QByteArray &body = QByteArray());
看其代码:

void QWebFrame::load(const QNetworkRequest &req,
                     QNetworkAccessManager::Operation operation,
                     const QByteArray &body)
{
    if (d->parentFrame())
        d->page->d->insideOpenCall = true;

    QUrl url = ensureAbsoluteUrl(req.url());

    WebCore::ResourceRequest request(url);

    switch (operation) {
        case QNetworkAccessManager::HeadOperation:
            request.setHTTPMethod("HEAD");
            break;
        case QNetworkAccessManager::GetOperation:
            request.setHTTPMethod("GET");
            break;
        case QNetworkAccessManager::PutOperation:
            request.setHTTPMethod("PUT");
            break;
        case QNetworkAccessManager::PostOperation:
            request.setHTTPMethod("POST");
            break;
        case QNetworkAccessManager::UnknownOperation:
            // eh?
            break;
    }

    QList<QByteArray> httpHeaders = req.rawHeaderList();
    for (int i = 0; i < httpHeaders.size(); ++i) {
        const QByteArray &headerName = httpHeaders.at(i);
        request.addHTTPHeaderField(QString::fromLatin1(headerName), QString::fromLatin1(req.rawHeader(headerName)));
    }

    if (!body.isEmpty())
        request.setHTTPBody(WebCore::FormData::create(body.constData(), body.size()));

    d->frame->loader()->load(request);

    if (d->parentFrame())
        d->page->d->insideOpenCall = false;
}

看关键的FrameLoader::load()

void FrameLoader::load(const ResourceRequest& request)
{
    load(request, SubstituteData());
}

void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData)
{
    if (m_inStopAllLoaders)
        return;
        
    // FIXME: is this the right place to reset loadType? Perhaps this should be done after loading is finished or aborted.
    m_loadType = FrameLoadTypeStandard;
    load(m_client->createDocumentLoader(request, substituteData).get());
}

上面m_client对应的是FrameLoaderClientQt实体,m_client->createDocumentLoader()创建的是DocumentLoader对象。进一步看FrameLoader::load(DocumentLoader *)的代码:

void FrameLoader::load(DocumentLoader* newDocumentLoader)
{
    ResourceRequest& r = newDocumentLoader->request();
    addExtraFieldsToMainResourceRequest(r);
    FrameLoadType type;

    if (shouldTreatURLAsSameAsCurrent(newDocumentLoader->originalRequest().url())) {
        r.setCachePolicy(ReloadIgnoringCacheData);
        type = FrameLoadTypeSame;
    } else
        type = FrameLoadTypeStandard;

    if (m_documentLoader)
        newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding());
    
    // When we loading alternate content for an unreachable URL that we're
    // visiting in the history list, we treat it as a reload so the history list 
    // is appropriately maintained.
    //
    // FIXME: This seems like a dangerous overloading of the meaning of "FrameLoadTypeReload" ...
    // shouldn't a more explicit type of reload be defined, that means roughly 
    // "load without affecting history" ? 
    if (shouldReloadToHandleUnreachableURL(newDocumentLoader)) {
        ASSERT(type == FrameLoadTypeStandard);
        type = FrameLoadTypeReload;
    }

   loadWithDocumentLoader(newDocumentLoader, type, 0);
}

你可能感兴趣的:(webkit)