QWebkit 源码走读笔记(1)

本文基于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层调用

  1. 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;
}
  1. frame->loader()->load(WebCore::FrameLoadRequest(frame, request));
    关键代码 WebCore::FrameLoadRequest() 调用的是 -> FrameLoadRequest(Frame*, const ResourceRequest&, const SubstituteData& = SubstituteData());
    这里WebCore::FrameLoadRequest().frameName() 返回的应该是空的string。

  2. 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 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.
//
// 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();
}
  1. prepareForLoadStart(); 通知进度条进行加载准备,向client 分发消息通知进行加载
1
2
3
4
5
6
void FrameLoader::prepareForLoadStart()
{
m_progressTracker->progressStarted();
m_client->dispatchDidStartProvisionalLoad();
... ...
}
  1. m_provisionalDocumentLoader->startLoadingMainResource(); 开始将首页面作为主资源进行加载,
    这里实际调用的是DocumentLoader::startLoadingMainResource;