本文基于Qt5.5.1版本中的QWebkit
userAgent “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) cordova Safari/538.1”
页面加载的过程
FrameLoader
FrameLoader-状态定义
1 2 3 4 5 6 7
| enum FrameState { FrameStateProvisional, // This state indicates we are ready to commit to a page, // which means the view will transition to use the new data source. FrameStateCommittedPage, FrameStateComplete };
|
资源类型定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| enum Type { MainResource, // HTML ImageResource, // 图片 CSSStyleSheet, // CSS文件 Script, // Javascript文件 FontResource, // 字体 RawResource // 其他包括多媒体文件等二进制文件 #if ENABLE(SVG) , SVGDocumentResource #endif #if ENABLE(XSLT) , XSLStyleSheet #endif #if ENABLE(LINK_PREFETCH) , LinkPrefetch , LinkSubresource #endif #if ENABLE(VIDEO_TRACK) , TextTrackResource #endif #if ENABLE(CSS_SHADERS) , ShaderResource #endif };
|
在webkit中一般将HTML页面定义为主资源。
FrameLoader::FrameState::FrameStateProvisional
Provisional 是Frame第一个定义状态,命名定义该状态是一个临时,且不确定状态
TODO: 后续需要添加说明,该状态的命名原因;
WebPage->WebFrame->FrameLoader的加载过程
Adapter层调用
load -> WebCore::Frame()->loader()
获得了 WebCore::FrameLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void QWebFrameAdapter::load(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& body) { if (frame->tree()->parent()) pageAdapter->insideOpenCall = true; QUrl url = ensureAbsoluteUrl(req.url()); WebCore::ResourceRequest request(url); ... ... frame->loader()->load(WebCore::FrameLoadRequest(frame, request)); if (frame->tree()->parent()) pageAdapter->insideOpenCall = false; }
|
frame->loader()->load(WebCore::FrameLoadRequest(frame, request));
关键代码 WebCore::FrameLoadRequest() 调用的是 -> FrameLoadRequest(Frame*, const ResourceRequest&, const SubstituteData& = SubstituteData())
;
这里WebCore::FrameLoadRequest().frameName() 返回的应该是空的string。
FrameLoadRequest(Frame*, const ResourceRequest&, const SubstituteData& = SubstituteData())
;
FrameLoader用空的SubstituteData去创建DocumentLoader,并使用DocumentLoader来完成MainResource的加载,SubstituteData用于在所请求的资源不可到达的时候,提供重定向指导。
通过FrameLoader 执行加载过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void FrameLoader::load(const FrameLoadRequest& passedRequest) { FrameLoadRequest request(passedRequest); if (m_inStopAllLoaders) return; if (!request.frameName().isEmpty()) { Frame* frame = findFrameForNavigation(request.frameName()); if (frame) { request.setShouldCheckNewWindowPolicy(false); if (frame->loader() != this) { frame->loader()->load(request); return; } } } if (request.shouldCheckNewWindowPolicy()) { policyChecker()->checkNewWindowPolicy(NavigationAction(request.resourceRequest(), NavigationTypeOther), FrameLoader::callContinueLoadAfterNewWindowPolicy, request.resourceRequest(), 0, request.frameName(), this); return; } if (!request.hasSubstituteData()) request.setSubstituteData(defaultSubstituteDataForURL(request.resourceRequest().url())); RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request.resourceRequest(), request.substituteData()); if (request.lockHistory() && m_documentLoader) loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory()); load(loader.get()); }
|
建立一个documentloader来加载资源:RefPtr loader = m_client->createDocumentLoader(request.resourceRequest(), request.substituteData());
注意这里的request.subtitueData是一个空对象。
然后调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void FrameLoader::load(DocumentLoader* newDocumentLoader) { …… 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)) { // shouldReloadToHandleUnreachableURL() returns true only when the original load type is back-forward. // In this case we should save the document state now. Otherwise the state can be lost because load type is // changed and updateForBackForwardNavigation() will not be called when loading is committed. history()->saveDocumentAndScrollState(); ASSERT(type == FrameLoadTypeStandard); type = FrameLoadTypeReload; } loadWithDocumentLoader(newDocumentLoader, type, 0); }
|
注意这里的问题:
if (m_documentLoader)
newDocumentLoader->setOverrideEncoding(m_documentLoader->overrideEncoding());
1 2 3 4 5 6
| // 这里是m_documentLoader在FrameLoader中的定义: // Document loaders for the three phases of frame loading. Note that while // a new request is being loaded, the old document loader may still be referenced. // E.g. while a new request is in the "policy" state, the old document loader may // be consulted in particular as it makes sense to imply certain settings on the new loader. RefPtr<DocumentLoader> m_documentLoader;
|
在frame 加载的过程中的三个不同阶段,都将创建并调用到document loader这个类型。请注意一个新的请求正在被加载的时候,先前的document loader可能依然会被其他对象引用到。
比如,一个新的请求还处于”policy”这个状态时,新的loader依然会关联到先前的document loader中一些具体配置参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState) { // Retain because dispatchBeforeLoadEvent may release the last reference to it. RefPtr<Frame> protect(m_frame); ASSERT(m_client->hasWebView()); // 所以页面在加载前会判断当前的client中是否包含了View的实现。 …… if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker()->loadType(), newURL)) { RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader; NavigationAction action(loader->request(), policyChecker()->loadType(), isFormSubmission); oldDocumentLoader->setTriggeringAction(action); policyChecker()->stopCheck(); policyChecker()->checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState, callContinueFragmentScrollAfterNavigationPolicy, this); } else { if (Frame* parent = m_frame->tree()->parent()) loader->setOverrideEncoding(parent->loader()->documentLoader()->overrideEncoding()); policyChecker()->stopCheck(); // 将新创建的documentloader设置给m_policyDocumentLoader setPolicyDocumentLoader(loader); if (loader->triggeringAction().isEmpty()) // 将本次加载的请求记录在loader的m_triggeringAction中 loader->setTriggeringAction(NavigationAction(loader->request(), policyChecker()->loadType(), isFormSubmission)); if (Element* ownerElement = m_frame->ownerElement()) { // We skip dispatching the beforeload event if we've already // committed a real document load because the event would leak // subsequent activity by the frame which the parent frame isn't // supposed to learn. For example, if the child frame navigated to // a new URL, the parent frame shouldn't learn the URL. if (!m_stateMachine.committedFirstRealDocumentLoad() && !ownerElement->dispatchBeforeLoadEvent(loader->request().url().string())) { continueLoadAfterNavigationPolicy(loader->request(), formState, false); return; } } // 使用前面记录的loader.m_triggeringAction做校验,处理空白,重复,不可到达的请求, // 该校验还要包括FrameLoaderClient实现的一些检查,以决定如何处理本次请求。 policyChecker()->checkNavigationPolicy(loader->request(), loader, formState, callContinueLoadAfterNavigationPolicy, this); } }
|
设置完成以后,通过policyChecker()
校验请求后,调用callContinueLoadAfterNavigationPolicy()
。
在callContinueLoadAfterNavigationPolicy()
中将设置FrameLoader的状态转换为Provisional。参考FrameLoader的状态定义
这是一个简单 包裹方法,简单地做了一个转换。实际调用的是continueLoadAfterNavigationPolicy。
1 2 3 4 5 6
| void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument, const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) { FrameLoader* loader = static_cast<FrameLoader*>(argument); loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue); }
|
关于 continueLoadAfterNavigationPolicy()
说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue) { // If we loaded an alternate page to replace an unreachableURL, we'll get in here with a // nil policyDataSource because loading the alternate page will have passed // through this method already, nested; otherwise, policyDataSource should still be set. ASSERT(m_policyDocumentLoader || !m_provisionalDocumentLoader->unreachableURL().isEmpty()); bool isTargetItem = history()->provisionalItem() ? history()->provisionalItem()->isTargetItem() : false; // Two reasons we can't continue: // 1) Navigation policy delegate said we can't so request is nil. A primary case of this // is the user responding Cancel to the form repost nag sheet. // 2) User responded Cancel to an alert popped up by the before unload event handler. bool canContinue = shouldContinue && shouldClose(); if (!canContinue) { // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we // need to report that the client redirect was cancelled. if (m_quickRedirectComing) clientRedirectCancelledOrFinished(false); setPolicyDocumentLoader(0); // If the navigation request came from the back/forward menu, and we punt on it, we have the // problem that we have optimistically moved the b/f cursor already, so move it back. For sanity, // we only do this when punting a navigation for the target frame or top-level frame. if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(policyChecker()->loadType())) { if (Page* page = m_frame->page()) { Frame* mainFrame = page->mainFrame(); if (HistoryItem* resetItem = mainFrame->loader()->history()->currentItem()) { page->backForward()->setCurrentItem(resetItem); m_frame->loader()->client()->updateGlobalHistoryItemForPage(); } } } return; } FrameLoadType type = policyChecker()->loadType(); // A new navigation is in progress, so don't clear the history's provisional item. stopAllLoaders(ShouldNotClearProvisionalItem); // <rdar://problem/6250856> - In certain circumstances on pages with multiple frames, stopAllLoaders() // might detach the current FrameLoader, in which case we should bail on this newly defunct load. if (!m_frame->page()) return; #if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR) if (Page* page = m_frame->page()) { if (page->mainFrame() == m_frame) m_frame->page()->inspectorController()->resume(); } #endif setProvisionalDocumentLoader(m_policyDocumentLoader.get()); m_loadType = type; setState(FrameStateProvisional); setPolicyDocumentLoader(0); if (isBackForwardLoadType(type) && history()->provisionalItem()->isInPageCache()) { loadProvisionalItemFromCachedPage(); return; } /// if (formState) m_client->dispatchWillSubmitForm(&PolicyChecker::continueLoadAfterWillSubmitForm, formState); else continueLoadAfterWillSubmitForm(); }
|
在继续执行加载的策略中判断,用户是否取消加载,如果取消加载则将已加载的内容替换成缓存中的历史记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // Two reasons we can't continue: // 1) Navigation policy delegate said we can't so request is nil. A primary case of this // is the user responding Cancel to the form repost nag sheet. // 2) User responded Cancel to an alert popped up by the before unload event handler. bool canContinue = shouldContinue && shouldClose(); if (!canContinue) { // If we were waiting for a quick redirect, but the policy delegate decided to ignore it, then we // need to report that the client redirect was cancelled. if (m_quickRedirectComing) clientRedirectCancelledOrFinished(false); setPolicyDocumentLoader(0); // If the navigation request came from the back/forward menu, and we punt on it, we have the // problem that we have optimistically moved the b/f cursor already, so move it back. For sanity, // we only do this when punting a navigation for the target frame or top-level frame. if ((isTargetItem || isLoadingMainFrame()) && isBackForwardLoadType(policyChecker()->loadType())) { if (Page* page = m_frame->page()) { Frame* mainFrame = page->mainFrame(); if (HistoryItem* resetItem = mainFrame->loader()->history()->currentItem()) { page->backForward()->setCurrentItem(resetItem); m_frame->loader()->client()->updateGlobalHistoryItemForPage(); } } } return; }
|
如果用户未取消加载操作,则根据表单状态,执行后续流程。一般都是走else
这个分支。
1 2 3 4
| if (formState) m_client->dispatchWillSubmitForm(&PolicyChecker::continueLoadAfterWillSubmitForm, formState); else continueLoadAfterWillSubmitForm();
|
接着开始进入页面的真正加载流程。
开始加载页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void FrameLoader::continueLoadAfterWillSubmitForm() { if (!m_provisionalDocumentLoader) return; prepareForLoadStart(); // The load might be cancelled inside of prepareForLoadStart(), nulling out the m_provisionalDocumentLoader, // so we need to null check it again. if (!m_provisionalDocumentLoader) return; DocumentLoader* activeDocLoader = activeDocumentLoader(); if (activeDocLoader && activeDocLoader->isLoadingMainResource()) return; m_loadingFromCachedPage = false; m_provisionalDocumentLoader->startLoadingMainResource(); }
|
prepareForLoadStart();
通知进度条进行加载准备,向client 分发消息通知进行加载
1 2 3 4 5 6
| void FrameLoader::prepareForLoadStart() { m_progressTracker->progressStarted(); m_client->dispatchDidStartProvisionalLoad(); ... ... }
|
m_provisionalDocumentLoader->startLoadingMainResource();
开始将首页面作为主资源进行加载,
这里实际调用的是DocumentLoader::startLoadingMainResource;
DocumentLoader
所有资源的加载,都是通过DocumentLoader 以及子类实现的;
m_provisionalDocumentLoader 就是一个DocumentLoader;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void DocumentLoader::startLoadingMainResource() { ... ... ResourceRequest request(m_request); DEFINE_STATIC_LOCAL(ResourceLoaderOptions, mainResourceLoadOptions, (SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, SkipSecurityCheck, UseDefaultOriginRestrictionsForType)); CachedResourceRequest cachedResourceRequest(request, mainResourceLoadOptions); m_mainResource = m_cachedResourceLoader->requestMainResource(cachedResourceRequest); if (!m_mainResource) { setRequest(ResourceRequest()); // If the load was aborted by clearing m_request, it's possible the ApplicationCacheHost // is now in a state where starting an empty load will be inconsistent. Replace it with // a new ApplicationCacheHost. m_applicationCacheHost = adoptPtr(new ApplicationCacheHost(this)); maybeLoadEmpty(); return; } if (!mainResourceLoader()) { m_identifierForLoadWithoutResourceLoader = m_frame->page()->progress()->createUniqueIdentifier(); frameLoader()->notifier()->assignIdentifierToInitialRequest(m_identifierForLoadWithoutResourceLoader, this, request); frameLoader()->notifier()->dispatchWillSendRequest(this, m_identifierForLoadWithoutResourceLoader, request, ResourceResponse()); } m_mainResource->addClient(this); // A bunch of headers are set when the underlying ResourceLoader is created, and m_request needs to include those. if (mainResourceLoader()) request = mainResourceLoader()->originalRequest(); // If there was a fragment identifier on m_request, the cache will have stripped it. m_request should include // the fragment identifier, so add that back in. if (equalIgnoringFragmentIdentifier(m_request.url(), request.url())) request.setURL(m_request.url()); setRequest(request); }
|
关于ResourceRequest request(m_request);
这里仅仅是将指针值赋值给新的request 还是将整个m_request 的内容都拷贝给了request?
如果是整个内容都拷贝给了request,那么下面执行的语句:If there was a fragment identifier on m_request, the cache will have stripped it. m_request should include1 2 3
| // the fragment identifier, so add that back in. if (equalIgnoringFragmentIdentifier(m_request.url(), request.url())) request.setURL(m_request.url());
|
不是太理解这边的执行内容。
在这里通过创建mainResource,然后将自己注册给mainResource作为回调
1 2
| line 1382: m_mainResource = m_cachedResourceLoader->requestMainResource(cachedResourceRequest);
|
1 2
| line 1398: m_mainResource->addClient(this);
|
接着就将请求转向了CachedResourceLoader
。
CachedResourceLoader
1 2 3 4 5 6 7 8 9
| // 注释说明: // The CachedResourceLoader provides a per-context interface to the MemoryCache // and enforces a bunch of security checks and rules for resource revalidation. // Its lifetime is roughly per-DocumentLoader, in that it is generally created // in the DocumentLoader constructor and loses its ability to generate network // requests when the DocumentLoader is destroyed. Documents also hold a // RefPtr<CachedResourceLoader> for their lifetime (and will create one if they // are initialized without a Frame), so a Document can keep a CachedResourceLoader // alive past detach if scripts still reference the Document.
|
1 2 3 4
| CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest& request) { return static_cast<CachedRawResource*>(requestResource(CachedResource::MainResource, request).get()); }
|
通过调用requestResource 来向服务端请求主资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request) { // 判断是否由缓存中预取得到相应的资源对象 ... ... const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer()); switch (policy) { case Reload: memoryCache()->remove(resource.get()); // Fall through case Load: resource = loadResource(type, request, request.charset()); break; case Revalidate: resource = revalidateResource(request, resource.get()); break; case Use: if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(resource.get())) return 0; memoryCache()->resourceAccessed(resource.get()); break; } ... ... if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) { resource->load(this, request.options()); // We don't support immediate loads, but we do support immediate failure. if (resource->errorOccurred()) { if (resource->inCache()) memoryCache()->remove(resource.get()); return 0; } } ... ... return resource; }
|
const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer());
这里通过封装的加载策略,来判断加载方式。
加载的策略方式有 reLoad, Revalidate, Use, Load。
这里仅分析在Load的情况,其他情况待分解。
resource = loadResource(type, request, request.charset());
该函数调用了static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset)
来创建指定的资源类型,并将其返回。
然后程序执行到这句:
if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) {
resource->load(this, request.options());
新加载的资源对应策略为load,并且主资源未设置Defer,resource->stillNeedsLoad(),默认情况下都返回False。
所以policy != Use
与 CachedResourceRequest::NoDefer == request.defer()
同时命中,执行resource->load方法。
执行CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options)
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) { // 此处忽略若干行 ... ... m_loader = platformStrategies()->loaderStrategy()->resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->frame(), this, request, request.priority(), options); if (!m_loader) { failBeforeStarting(); return; } m_status = Pending; }
|
由于webkit需要支持不同的平台,所以将不同平台下资源加载的方式使用策略模式进行了封装。然后将资源调用通过resourceLoadScheduler来统一请求和调度。
1 2 3 4 5 6 7
| PassRefPtr<SubresourceLoader> ResourceLoadScheduler::scheduleSubresourceLoad(Frame* frame, CachedResource* resource, const ResourceRequest& request, ResourceLoadPriority priority, const ResourceLoaderOptions& options) { RefPtr<SubresourceLoader> loader = SubresourceLoader::create(frame, resource, request, options); if (loader) scheduleLoad(loader.get(), priority); return loader.release(); }
|
这里创建了一个资源调度器,进行加载调用。
从CachedResource::load -> ResourceLoadScheduler::scheduleSubresourceLoad 这里的调用流程与其他派生资源(如图片、CSS文件、JS文件等)的加载是同一个流程。
Document
Document 对象的三个状态具体转换过程
在Javascript中页面的DOM加载经历三个状态, loading, interactive, complete。
三个状态定义在Document中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| // 三个状态通过readyState 接口返回 String Document::readyState() const { DEFINE_STATIC_LOCAL(const String, loading, (ASCIILiteral("loading"))); DEFINE_STATIC_LOCAL(const String, interactive, (ASCIILiteral("interactive"))); DEFINE_STATIC_LOCAL(const String, complete, (ASCIILiteral("complete"))); switch (m_readyState) { case Loading: return loading; case Interactive: return interactive; case Complete: return complete; } ASSERT_NOT_REACHED(); return String(); } // 三个状态通过setReadyState接口进行更改 void Document::setReadyState(ReadyState readyState) { if (readyState == m_readyState) return; switch (readyState) { case Loading: if (!m_documentTiming.domLoading) m_documentTiming.domLoading = monotonicallyIncreasingTime(); break; case Interactive: if (!m_documentTiming.domInteractive) m_documentTiming.domInteractive = monotonicallyIncreasingTime(); break; case Complete: if (!m_documentTiming.domComplete) m_documentTiming.domComplete = monotonicallyIncreasingTime(); break; } m_readyState = readyState; /// COMMENT 通知页面上的监听者(JS) dispatchEvent(Event::create(eventNames().readystatechangeEvent, false, false)); if (settings() && settings()->suppressesIncrementalRendering()) setVisualUpdatesAllowed(readyState); }
|