文章目录
  1. 1. WebKit加载Web page
    1. 1.1. FrameLoader
    2. 1.2. 总结

WebKit加载Web page

我们在阅读一个页面之前,webKit要做一些加载的工作,从network加载page和子资源,整个加载中伴随着很多复杂的结构。学习一波,WebCore在WebKit中扮演的角色是什么样的。

WebKit的加载过程分类两类:

  • 加载documents
  • 加载images和scripts

看下面的图片:

FrameLoader

从图片中可以看到,FrameLoader充当的就是加载documents到Frams的一个类。最开始就是FrameLoader启动,创建DocumentLoader对象,进入临时状态,这个时候就等待客户端如何决定这次的加载方式,通常会进行一次正常的加载,或者也可能阻塞这次加载。

看一具体实现(因为代码量太大了,跳去主要的分析):

1
2
3
RefPtr<DocumentLoader> m_documentLoader;
RefPtr<DocumentLoader> m_provisionalDocumentLoader;
RefPtr<DocumentLoader> m_policyDocumentLoader;

FrameLoader维护着三种DocumentLoader,对应不同的阶段,我们使用不同的DocumentLoader
首先就是初始化三个DocumentLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

void FrameLoader::init()
{
// This somewhat odd set of steps gives the frame an initial empty document.
setPolicyDocumentLoader(m_client.createDocumentLoader(ResourceRequest(URL(ParsedURLString, emptyString())), SubstituteData()).ptr());
setProvisionalDocumentLoader(m_policyDocumentLoader.get());
m_provisionalDocumentLoader->startLoadingMainResource();

Ref<Frame> protect(m_frame);
m_frame.document()->cancelParsing();
m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocument);

m_networkingContext = m_client.createNetworkingContext();
m_progressTracker = std::make_unique<FrameProgressTracker>(m_frame);
}

正如上面说的,FrameLoader启动,创建DocumentLoader对象,进入临时状态,所以这个时候实用的是m_policyDocumentLoader。

检测这此的加载该如何处理:

1
2
3
4
5
6
7
8
9
10
11
12
void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState, AllowNavigationToInvalidURL allowNavigationToInvalidURL)
{
.
.
.
.
.

policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, loader, formState, [this, allowNavigationToInvalidURL](const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) {
continueLoadAfterNavigationPolicy(request, formState, shouldContinue, allowNavigationToInvalidURL);
});
}

这里就是调用checkNavigationPolicy来检测这次浏览的处理方式。

checkNavigationPolicy

1
2
3
4
5
6
m_callback.set(request, formState.get(), WTFMove(function));


m_frame.loader().client().dispatchDecidePolicyForNavigationAction(action, request, formState, [this](PolicyAction action) {
continueAfterNavigationPolicy(action);
});

构建PolicyCallback回调,之后这段,我猜测是就是客户端来决定这次的浏览事件该如何处理。continueAfterNavigationPolicy(action);继续执行接下来的的浏览策略。正常的加载继续进行;

1
2
3
4
5
6
7
8
9
void PolicyChecker::continueAfterNavigationPolicy(PolicyAction policy)
{
.
.
.
.
.
callback.call(shouldContinue);
}

回调消息给FrameLoader,FrameLoader收到导航后继续执行continueLoadAfterNavigationPolicy

1
2
3
policyChecker().checkNavigationPolicy(loader->request(), false /* didReceiveRedirectResponse */, loader, formState, [this, allowNavigationToInvalidURL](const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) {
continueLoadAfterNavigationPolicy(request, formState, shouldContinue, allowNavigationToInvalidURL);
});

FrameLoadercontinueLoadAfterNavigationPolicy方法中

1
2
3
4
setProvisionalDocumentLoader(m_policyDocumentLoader.get());
m_loadType = type;
setState(FrameStateProvisional);
setPolicyDocumentLoader(nullptr);

标志着进入了一个新的阶段。将DocumentLoader变为Provisional状态

FrameLoader此时发出网络请求,进入等待状态,等待这次网络请求是否会进行数据的拉去。

一步一步查看

continueLoadAfterNavigationPolicy:

1
2
3
4
5

if (!formState) {
continueLoadAfterWillSubmitForm();
return;
}

进入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就是准备开始加载,这里面发送了网络请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void FrameLoader::prepareForLoadStart()
{
RELEASE_LOG_IF_ALLOWED("Starting frame load, frame = %p, main = %d", &m_frame, m_frame.isMainFrame());

m_progressTracker->progressStarted();
m_client.dispatchDidStartProvisionalLoad();

if (AXObjectCache::accessibilityEnabled()) {
if (AXObjectCache* cache = m_frame.document()->existingAXObjectCache()) {
AXObjectCache::AXLoadingEvent loadingEvent = loadType() == FrameLoadType::Reload ? AXObjectCache::AXLoadingReloaded : AXObjectCache::AXLoadingStarted;
cache->frameLoadingEventNotification(&m_frame, loadingEvent);
}
}
}

continueLoadAfterWillSubmitForm中可以看到,经过一系列的处理,终于进入资源加载

1
m_provisionalDocumentLoader->startLoadingMainResource();

探索到了这里。

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();
}

原来会有很多人提起说通过MainResourceLoader老进行数据的加载,但是现在的话WebKit代码主资源已经加入了缓存机制。所以,资源的加载统一由CachedResourceLoader进行。

1
2
3
4
5
void DocumentLoader::startLoadingMainResource()
{
m_mainResource = m_cachedResourceLoader->requestMainResource(cachedResourceRequest);

}

接下来就是请求加载数据源requestResource

requestResource中,

1
resource = loadResource(type, request);

顺着往下查找会看到

1
CachedResourceHandle<CachedResource> resource = createResource(type, request.mutableResourceRequest(), request.charset(), sessionID());

这里就是根据resource的类型来创建对应的CachedResource,上面提到过的缓存机制.

再往下就是开启请求的操作。

1
resource->load(*this, request.options());

断点顺着查找,最终会发现到这里

1
2
3
4
if (resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->request().url())) {
resourceLoader->start();
return;
}

start()函数在ResourceLoader中,这里贴出完整的代码:

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
void ResourceLoader::start()
{
ASSERT(!m_handle);
ASSERT(!m_request.isNull());
ASSERT(m_deferredRequest.isNull());
ASSERT(frameLoader());

#if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML)
if (m_documentLoader->scheduleArchiveLoad(*this, m_request))
return;
#endif

if (m_documentLoader->applicationCacheHost()->maybeLoadResource(*this, m_request, m_request.url()))
return;

if (m_defersLoading) {
m_deferredRequest = m_request;
return;
}

if (m_reachedTerminalState)
return;

if (m_request.url().protocolIsData()) {
loadDataURL();
return;
}

m_handle = ResourceHandle::create(frameLoader()->networkingContext(), m_request, this, m_defersLoading, m_options.sniffContent == SniffContent);
}

经过了一系列复杂的事情,ResourceLoader对象的成员函数start开始加载要加载的URL的主资源。

CachedResourceLoader的资源获取,跟DocumentLoader是分开的,一旦加载系统通过网络获得足够多信息,以便把文档进行呈现,FrameLoader将DocumentLoader置于“committed”状态,这时Frame对象就要显示这个新加载的文档了。

上面也提到过,资源分为两类,文档和子资源(图片,Javascript脚本,CSS样式等),这些的加载是在解析的DOM树的过程中做的。发现资源标签就进行加载,这里就不做代码流程的分析了。

简单的描述下,跟我们做客户端开发的一些缓存方案还是差不多的。

所有的子资源都是有自己对应的类

  • CacheImage
  • CacheScript

等等,所有的子资源都有一个共同的父类:CachedResource

DocLoader也是先去查看缓存中是否有,没有的话,Cache会创建一个CachedImage对象来表示这个图片。CachedImage对象让Loader对象来发起网络请求,Loader会创建SubresourceLoader来做这个事情。

有一个很详细的时序图:

通过这个可以详细分析下整个流程。

总结

  • DocumentLoader分三个阶段,本文涉及两个
  • m_policyDocumentLoader用于策略检测
  • m_provisionalDocumentLoader负责startLoadingMainResource的调用
  • 整个过程中伴随着各种load
  • 先加载文档资源,在加载子资源,子资源的加载是在解析成DOM树的过程中进行的。
  • DocumentLoader为什么分为三个阶段:我个人认为也是我了不同阶段处理不同的业务,加载资源的过程中,毕竟伴随着太多的状态。

我也是慢慢的摸索着,主要还是对资源流程做了主要的摸索,更多细节还希望大家帮补充。这里的东西也只是冰山一角,还有很多东西,很多细节值得学习,官网也有很多的知识值得学习。

如果有错的地方,希望帮我指正出来。

文章目录
  1. 1. WebKit加载Web page
    1. 1.1. FrameLoader
    2. 1.2. 总结