这一系列将从源码角度,分析WebView加载页面的全过程。在摸透缓存机制的基础上,实现自己的WebView缓存控制项目Vindow。
从0到1很难,但是克服了之后,从1到100就容易了许多。


WebView.java

这里采用android-23的源码示例。

WebView的类说明注释非常长,建议耐心地读完每一行,随后,你就会对WebView的主要功能有一个轮廓上的认识。

1
2
3
4
5
6
7
/**
* A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
* It uses the WebKit rendering engine to display
* web pages and includes methods to navigate forward and backward
* through a history, zoom in and out, perform text searches and more.
* /

这段是说,WebView主要用来展示网页信息,它使用WebKit内核(这里很重要,后续分析大部分源码都是来自WebKit的),包含了控制网页前进后退的导航功能、缩放功能、文本搜索功能以及其他。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* By default, a WebView provides no browser-like widgets, does not
* enable JavaScript and web page errors are ignored. If your goal is only
* to display some HTML as a part of your UI, this is probably fine;
* the user won't need to interact with the web page beyond reading
* it, and the web page won't need to interact with the user. If you
* actually want a full-blown web browser, then you probably want to
* invoke the Browser application with a URL Intent rather than show it
* with a WebView.
* Uri uri = Uri.parse("http://www.example.com");
* Intent intent = new Intent(Intent.ACTION_VIEW, uri);
* startActivity(intent);
* /

这里强调了,WebView应当仅仅提供展示,默认情况下是禁用JavaScript,并且隐藏网页错误信息的。换句话说,Google本意是不提倡在WebView中引导用户进行过多的操作,如果有这种需求,就通过intent打开浏览器页面进行操作。然而当下这条规则在很多应用场景下是被无视的,想想微信在H5页面里,可以做多少事。

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
/**
* <p>A WebView has several customization points where you can add your
* own behavior. These are:</p>
*
* <ul>
* <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
* This class is called when something that might impact a
* browser UI happens, for instance, progress updates and
* JavaScript alerts are sent here (see <a
* href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
* </li>
* <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
* It will be called when things happen that impact the
* rendering of the content, eg, errors or form submissions. You
* can also intercept URL loading here (via {@link
* android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
* shouldOverrideUrlLoading()}).</li>
* <li>Modifying the {@link android.webkit.WebSettings}, such as
* enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
* setJavaScriptEnabled()}. </li>
* <li>Injecting Java objects into the WebView using the
* {@link android.webkit.WebView#addJavascriptInterface} method. This
* method allows you to inject Java objects into a page's JavaScript
* context, so that they can be accessed by JavaScript in the page.</li>
* </ul>
* /

默认WebView的很多功能是关闭的,需要我们手动打开。这里列出了如何处理JS Alert与错误、如何开启JS、如何使native代码与页面JS进行交互。

1
2
3
4
5
6
// Implementation notes.
// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
// Methods are delegated to the provider implementation: all public API methods introduced in this
// file are fully delegated, whereas public and protected methods from the View base classes are
// only delegated where a specific need exists for them to do so.

上面这段说明了WebView主要的实现机理————WebView本身只是一个代理(delegate),提供了公共的API供客户端调用,而这些API的实现,都是代理到了一个叫WebViewProvider的对象上面。既然这样,我们就大致浏览下WebView有哪些公有API,把更多的精力保留下来,集中分析WebViewProvider的实现。

WebView API

这部分不罗列了,只做一个简单的归类,API文档见 https://developer.android.com/reference/android/webkit/WebView.html

加载页面
这是最主要的功能,全部由代理Provider完成

  • loadUrl
  • postUrl
  • loadData
  • loadDataWithBaseURL
  • getUrl,getOriginalUrl
  • getFavicon,getTouchIconUrl
  • 保存页面:saveWebArchive
  • 控制加载:stopLoading,reload,getProgress
  • 页面尺寸:getContentHeight,getContentWidth

计时器

  • JS中的计时器,在onPause时可以暂停,onResume时恢复:pauseTimers,resumeTimers

导航

  • canGoBack,canGoForward,canGoBackOrForward
  • goBack,goForward,goBackOrForward
  • pageUp,pageDown
  • copyBackForwardList

JavaScript

  • evaluateJavascript
  • addJavascriptInterface,removeJavascriptInterface

WebViewClient/WebChromeClient

  • setWebViewClient:WebView本身是控制页面整体框架的前进、后退、缩放、加载等功能,而具体页面内容的变化,则要交给WebViewClient来管理。
  • setWebChromeClient:WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等。如果页面只是简单地展示HTML,并没有JS操作,那么用WebViewClient就足够了。

下载

  • 完成下载监听器:setDownloadListener

查找

  • 通过FindListener回调接口实现:setFindListener,findNext,findAll……

缩放

  • 展示缩放控件:invokeZoomPicker
  • 控制缩放:zoomIn,zoomOut……

安全认证

  • 证书操作:getCertificate,setCertificate(deprecated),
  • 用户名密码:setHttpAuthUsernamePassword,getHttpAuthUsernamePassword
  • 私密模式:isPrivateBrowsingEnabled

设置网络

  • 网络可用性:setNetworkAvailable

保存状态

  • 代理给Provider进行:saveState,restoreState

生命周期与回调

  • postVisualStateCallback

页面内容标签

  • HitTestResult系列:getHitTestResult

Cache与访问历史
本系列文章重点了解的内容,代理给Provider实现

  • clearCache
  • clearFormData
  • clearHistory
  • clearSslPreferences

被废弃的方法

  • 设置滚动条样式:setHorizontalScrollbarOverlay,setVerticalScrollbarOverlay,overlayHorizontalScrollbar,overlayVerticalScrollbar……
  • 获取Title高度:getVisibleTitleHeight
  • 平台通知:enablePlatformNotifications,disablePlatformNotifications
  • 图片操作:savePicture,restorePicture……
  • 内存:freeMemory
  • Plugins:getPluginList,refreshPlugins……

WebViewProvider

前面说了,WebView的功能几乎全部都是代理给WebViewProvider来实现的,android.webkit.WebViewProvider是一个接口,其实现要追溯源码,据版本不同有所区分。

  • Android4.4之前的版本,由WebViewClassic实现。
  • Android4.4以及之后的版本,由WebViewChromium实现。

随SDK下载的源码里不包含这部分代码,在这个页面查看:WebViewChromium.java

WebViewChromium类实现了WebView中被代理的全部方法,篇幅所限,我们不逐一进行分析,只追踪我们关注的加载页面/cache相关,也就是loadUrlclearCache两个方法。

loadUrl

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void loadUrl(String url) {
loadUrl(url, null);
}

@Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
// TODO: We may actually want to do some sanity checks here (like filter about://chrome).
LoadUrlParams params = new LoadUrlParams(url);
if (additionalHttpHeaders != null) params.setExtraHeaders(additionalHttpHeaders);
mAwContents.loadUrl(params);
}

我们通常所用的loadUrl("http://www.foo.com"),会走到loadUrl(String url, Map<String, String> additionalHttpHeaders)这个方法,可以看到首先把url拼装成了一个LoadUrlParams,那么这个LoadUrlParams是用来做什么的呢?

LoadUrlParams.java

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
/**
* Holds parameters for ContentViewCore.LoadUrl. Parameters should match
* counterparts in NavigationController::LoadURLParams, including default
* values.
*/
@JNINamespace("content")
public class LoadUrlParams {
// Should match NavigationController::LoadUrlType exactly. See comments
// there for proper usage. Values are initialized in initializeConstants.
public static int LOAD_TYPE_DEFAULT;
public static int LOAD_TYPE_BROWSER_INITIATED_HTTP_POST;
public static int LOAD_TYPE_DATA;
// Should match NavigationController::UserAgentOverrideOption exactly.
// See comments there for proper usage. Values are initialized in
// initializeConstants.
public static int UA_OVERRIDE_INHERIT;
public static int UA_OVERRIDE_FALSE;
public static int UA_OVERRIDE_TRUE;
// Fields with counterparts in NavigationController::LoadURLParams.
// Package private so that ContentViewCore.loadUrl can pass them down to
// native code. Should not be accessed directly anywhere else outside of
// this class.
final String mUrl;
int mLoadUrlType;
int mTransitionType;
int mUaOverrideOption;
private Map<String, String> mExtraHeaders;
byte[] mPostData;
String mBaseUrlForDataUrl;
String mVirtualUrlForDataUrl;
boolean mCanLoadLocalResources;
public LoadUrlParams(String url) {
// Check initializeConstants was called.
assert LOAD_TYPE_DEFAULT != LOAD_TYPE_BROWSER_INITIATED_HTTP_POST;
mUrl = url;
mLoadUrlType = LOAD_TYPE_DEFAULT;
mTransitionType = PageTransitionTypes.PAGE_TRANSITION_LINK;
mUaOverrideOption = UA_OVERRIDE_INHERIT;
mPostData = null;
mBaseUrlForDataUrl = null;
mVirtualUrlForDataUrl = null;
}
// 以下略
}

一个URL竟然可以解析出这么多东西来,逐个看看这些变量的含义:(参考navigation_controller.h

  • mUrl:最原始的URL
  • mLoadUrlType:加载类型,有如下三种
    1. LOAD_TYPE_DEFAULT:默认类型,以下两种以外的任意类型。
    2. LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:POST请求需要设置此类型。
    3. LOAD_TYPE_DATA:使用Base64编码的图片类型,通过Base64编码图片能够减少一次网络资源加载。(可以使用以下这两个工具,查看如何在图片与Base64编码之间进行转换:http://dataurl.net/#dataurlmaker http://codebeautify.org/base64-to-image-converter)
  • mTransitionType:页面变化的类型(实在找不出合适的词语来描述),默认为,详见page_transition_types.h,举例说明:
    1. PAGE_TRANSITION_LINK:默认值,点击link
    2. PAGE_TRANSITION_TYPED:在地址栏输入link
    3. PAGE_TRANSITION_RELOAD:刷新页面
  • mUaOverrideOption:控制http中的UserAgent,有三个可选值
    1. UA_OVERRIDE_INHERIT:默认值,Use the override value from the previous NavigationEntry in the NavigationController.
    2. UA_OVERRIDE_FALSE:Use the default user agent
    3. UA_OVERRIDE_TRUE:Use the user agent override, if it’s available.
  • mPostData:POST请求的data
  • mBaseUrlForDataUrl:仅对LOAD_TYPE_DATA有效,用于URL的相对路径以及JavaScript跨域检验。
  • mVirtualUrlForDataUrl:仅对LOAD_TYPE_DATA有效,显示在外给用户看的地址。

分析完了LoadUrlParams,继续追溯WebViewChromium中的loadUrl方法。

1
if (additionalHttpHeaders != null) params.setExtraHeaders(additionalHttpHeaders);

这里设置的ExtraHeaders是一个Map,具体参见http协议的header部分。

1
mAwContents.loadUrl(params);

最终交给AwContent对象处理,完整源码见AwContents.java。”Aw”是”Android WebView”的缩写。

1
2
3
4
5
6
7
8
/**
* Exposes the native AwContents class, and together these classes wrap the ContentViewCore
* and Browser components that are required to implement Android WebView API. This is the
* primary entry point for the WebViewProvider implementation; it holds a 1:1 object
* relationship with application WebView instances.
* (We define this class independent of the hidden WebViewProvider interfaces, to allow
* continuous build & test in the open source SDK-based tree).
*/

上述注释中最重要的是This is the primary entry point for the WebViewProvider implementation; it holds a 1:1 object relationship with application WebView instances.这一句,说明每一个WebView示例,都有一个AwContent对象和它对应。同样,我们只关注loadUrl方法,这里通过我们上一步分析的LoadUrlParams参数进行加载。

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
/**
* Load url without fixing up the url string. Consumers of ContentView are responsible for
* ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
* off during user input).
*
* @param params Parameters for this load.
*/
public void loadUrl(LoadUrlParams params) {
if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA &&
!params.isBaseUrlDataScheme()) {
// This allows data URLs with a non-data base URL access to file:///android_asset/ and
// file:///android_res/ URLs. If AwSettings.getAllowFileAccess permits, it will also
// allow access to file:// URLs (subject to OS level permission checks).
params.setCanLoadLocalResources(true);
}
// If we are reloading the same url, then set transition type as reload.
if (params.getUrl() != null &&
params.getUrl().equals(mContentViewCore.getUrl()) &&
params.getTransitionType() == PageTransitionTypes.PAGE_TRANSITION_LINK) {
params.setTransitionType(PageTransitionTypes.PAGE_TRANSITION_RELOAD);
}
params.setTransitionType(
params.getTransitionType() | PageTransitionTypes.PAGE_TRANSITION_FROM_API);
// For WebView, always use the user agent override, which is set
// every time the user agent in AwSettings is modified.
params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
// We don't pass extra headers to the content layer, as WebViewClassic
// was adding them in a very narrow set of conditions. See http://crbug.com/306873
// However, if the embedder is attempting to inject a Referer header for their
// loadUrl call, then we set that separately and remove it from the extra headers map/
final String REFERER = "referer";
Map<String, String> extraHeaders = params.getExtraHeaders();
if (extraHeaders != null) {
for (String header : extraHeaders.keySet()) {
if (REFERER.equals(header.toLowerCase(Locale.US))) {
params.setReferrer(new Referrer(extraHeaders.remove(header), 1));
params.setExtraHeaders(extraHeaders);
break;
}
}
}
if (mNativeAwContents != 0) {
nativeSetExtraHeadersForUrl(
mNativeAwContents, params.getUrl(), params.getExtraHttpRequestHeadersString());
}
params.setExtraHeaders(new HashMap<String, String>());
mContentViewCore.loadUrl(params);
// The behavior of WebViewClassic uses the populateVisitedLinks callback in WebKit.
// Chromium does not use this use code path and the best emulation of this behavior to call
// request visited links once on the first URL load of the WebView.
if (!mHasRequestedVisitedHistoryFromClient) {
mHasRequestedVisitedHistoryFromClient = true;
requestVisitedHistoryFromClient();
}
if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA &&
params.getBaseUrl() != null) {
// Data loads with a base url will be resolved in Blink, and not cause an onPageStarted
// event to be sent. Sending the callback directly from here.
mContentsClient.getCallbackHelper().postOnPageStarted(params.getBaseUrl());
}
}

设置加载本地文件

1
2
3
4
5
6
7
if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA &&
!params.isBaseUrlDataScheme()) {
// This allows data URLs with a non-data base URL access to file:///android_asset/ and
// file:///android_res/ URLs. If AwSettings.getAllowFileAccess permits, it will also
// allow access to file:// URLs (subject to OS level permission checks).
params.setCanLoadLocalResources(true);
}

重设transitionType

1
2
3
4
5
6
7
8
// If we are reloading the same url, then set transition type as reload.
if (params.getUrl() != null &&
params.getUrl().equals(mContentViewCore.getUrl()) &&
params.getTransitionType() == PageTransitionTypes.PAGE_TRANSITION_LINK) {
params.setTransitionType(PageTransitionTypes.PAGE_TRANSITION_RELOAD);
}
params.setTransitionType(
params.getTransitionType() | PageTransitionTypes.PAGE_TRANSITION_FROM_API);

设置UA为override

1
2
3
// For WebView, always use the user agent override, which is set
// every time the user agent in AwSettings is modified.
params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);

把ExtraHeaders中的referer属性提取出来单独设置,并将其从ExtraHeaders中删除,referer属性用于声明当前页面是从哪个页面跳转来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// We don't pass extra headers to the content layer, as WebViewClassic
// was adding them in a very narrow set of conditions. See http://crbug.com/306873
// However, if the embedder is attempting to inject a Referer header for their
// loadUrl call, then we set that separately and remove it from the extra headers map/
final String REFERER = "referer";
Map<String, String> extraHeaders = params.getExtraHeaders();
if (extraHeaders != null) {
for (String header : extraHeaders.keySet()) {
if (REFERER.equals(header.toLowerCase(Locale.US))) {
params.setReferrer(new Referrer(extraHeaders.remove(header), 1));
params.setExtraHeaders(extraHeaders);
break;
}
}
}

然后对于剩下的属性,单独设置,最终清楚ExtraHeaders

1
2
3
4
5
if (mNativeAwContents != 0) {
nativeSetExtraHeadersForUrl(
mNativeAwContents, params.getUrl(), params.getExtraHttpRequestHeadersString());
}
params.setExtraHeaders(new HashMap<String, String>());

对params进行过上述二次加工后,调用mContentViewCore的loadUrl方法

1
mContentViewCore.loadUrl(params);

继续追溯至ContentViewCore.java,位于org.chromium.content.browser包中。

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
/**
* Load url without fixing up the url string. Consumers of ContentView are responsible for
* ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
* off during user input).
*
* @param pararms Parameters for this load.
*/
public void loadUrl(LoadUrlParams params) {
if (mNativeContentViewCore == 0) return;
if (isPersonalityView()) {
// For WebView, always use the user agent override, which is set
// every time the user agent in ContentSettings is modified.
params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
}
nativeLoadUrl(mNativeContentViewCore,
params.mUrl,
params.mLoadUrlType,
params.mTransitionType,
params.mUaOverrideOption,
params.getExtraHeadersString(),
params.mPostData,
params.mBaseUrlForDataUrl,
params.mVirtualUrlForDataUrl,
params.mCanLoadLocalResources);
}

这里调用了nativeLoadUrl方法,传入的参数我们之前都已经分析过其含义,除了第一个参数mNativeContentViewCore,它是一个指向ContentViewCoreImpl的指针地址。

1
2
// Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
private int mNativeContentViewCore = 0;

接下来就要深入到cpp文件中了,content_view_core_impl.cc。注意!可能是版本的原因,这里的LoadUrl方法多出了第一个参数JNIEnv* env,通过对比可以发现,其它的参数是完全一致的。

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
void ContentViewCoreImpl::LoadUrl(
JNIEnv* env, jobject obj,
jstring url,
jint load_url_type,
jint transition_type,
jint ua_override_option,
jstring extra_headers,
jbyteArray post_data,
jstring base_url_for_data_url,
jstring virtual_url_for_data_url,
jboolean can_load_local_resources) {
DCHECK(url);
NavigationController::LoadURLParams params(
GURL(ConvertJavaStringToUTF8(env, url)));
params.load_type = static_cast<NavigationController::LoadURLType>(
load_url_type);
params.transition_type = PageTransitionFromInt(transition_type);
params.override_user_agent =
static_cast<NavigationController::UserAgentOverrideOption>(
ua_override_option);
if (extra_headers)
params.extra_headers = ConvertJavaStringToUTF8(env, extra_headers);
if (post_data) {
std::vector<uint8> http_body_vector;
base::android::JavaByteArrayToByteVector(env, post_data, &http_body_vector);
params.browser_initiated_post_data =
base::RefCountedBytes::TakeVector(&http_body_vector);
}
if (base_url_for_data_url) {
params.base_url_for_data_url =
GURL(ConvertJavaStringToUTF8(env, base_url_for_data_url));
}
if (virtual_url_for_data_url) {
params.virtual_url_for_data_url =
GURL(ConvertJavaStringToUTF8(env, virtual_url_for_data_url));
}
params.can_load_local_resources = can_load_local_resources;
LoadUrl(params);
}

GURL是一个宏,虽然不了解它具体做了什么,但是根据上下文可以猜测这里生成了一个NavigationController::LoadURLParams对象

1
2
NavigationController::LoadURLParams params(
GURL(ConvertJavaStringToUTF8(env, url)));

随后对几个参数进行类型转换,把它们拼入params中,最后调用LoadUrl(params)方法。

1
2
3
4
5
void ContentViewCoreImpl::LoadUrl(
NavigationController::LoadURLParams& params) {
GetWebContents()->GetController().LoadURLWithParams(params);
UpdateTabCrashedFlag();
}

虽然自己对C语言不是很熟悉,但在这里也可以发现,是把上一步拼装成的params参数集合传递给了某个Controller对象。那么到底是哪一个Controller呢?跟着代码走~

1
2
3
WebContents* ContentViewCoreImpl::GetWebContents() const {
return web_contents_;
}

这里返回成员变量web_contents_,而Controller就在它内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void ContentViewCoreImpl::InitWebContents() {
DCHECK(web_contents_);
notification_registrar_.Add(
this, NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
Source<NavigationController>(&web_contents_->GetController()));
notification_registrar_.Add(
this, NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
notification_registrar_.Add(
this, NOTIFICATION_WEB_CONTENTS_CONNECTED,
Source<WebContents>(web_contents_));
notification_registrar_.Add(
this, NOTIFICATION_WEB_CONTENTS_SWAPPED,
Source<WebContents>(web_contents_));
static_cast<WebContentsViewAndroid*>(web_contents_->GetView())->
SetContentViewCore(this);
DCHECK(!web_contents_->GetUserData(kContentViewUserDataKey));
web_contents_->SetUserData(kContentViewUserDataKey,
new ContentViewUserData(this));
}

找到了,是NavigationController类型,源码见navigation_controller_impl.cc

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
71
72
73
74
75
76
void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
TRACE_EVENT0("browser", "NavigationControllerImpl::LoadURLWithParams");
if (HandleDebugURL(params.url, params.transition_type))
return;
// Checks based on params.load_type.
switch (params.load_type) {
case LOAD_TYPE_DEFAULT:
break;
case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
if (!params.url.SchemeIs(kHttpScheme) &&
!params.url.SchemeIs(kHttpsScheme)) {
NOTREACHED() << "Http post load must use http(s) scheme.";
return;
}
break;
case LOAD_TYPE_DATA:
if (!params.url.SchemeIs(chrome::kDataScheme)) {
NOTREACHED() << "Data load must use data scheme.";
return;
}
break;
default:
NOTREACHED();
break;
};
// The user initiated a load, we don't need to reload anymore.
needs_reload_ = false;
bool override = false;
switch (params.override_user_agent) {
case UA_OVERRIDE_INHERIT:
override = ShouldKeepOverride(GetLastCommittedEntry());
break;
case UA_OVERRIDE_TRUE:
override = true;
break;
case UA_OVERRIDE_FALSE:
override = false;
break;
default:
NOTREACHED();
break;
}
NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
CreateNavigationEntry(
params.url,
params.referrer,
params.transition_type,
params.is_renderer_initiated,
params.extra_headers,
browser_context_));
if (params.should_replace_current_entry)
entry->set_should_replace_entry(true);
entry->set_should_clear_history_list(params.should_clear_history_list);
entry->SetIsOverridingUserAgent(override);
entry->set_transferred_global_request_id(
params.transferred_global_request_id);
entry->SetFrameToNavigate(params.frame_name);
switch (params.load_type) {
case LOAD_TYPE_DEFAULT:
break;
case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
entry->SetHasPostData(true);
entry->SetBrowserInitiatedPostData(
params.browser_initiated_post_data.get());
break;
case LOAD_TYPE_DATA:
entry->SetBaseURLForDataURL(params.base_url_for_data_url);
entry->SetVirtualURL(params.virtual_url_for_data_url);
entry->SetCanLoadLocalResources(params.can_load_local_resources);
break;
default:
NOTREACHED();
break;
};
LoadEntry(entry);
}

这里做的事情与前面类似,取出参数再次拼装成NavigationEntryImpl* entry,然后调用LoadEntry。LoadEntry这里的注释讲解的很清楚,当我们进入新页面时,我们并不清楚是不是要终止上一个页面,因为新页面有可能只是一个下载或者邮件。由于我们的url等信息都保存在entry中,继续追溯 SetPendingEntry(entry) 方法。

1
2
3
4
5
6
7
void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) {
// When navigating to a new page, we don't know for sure if we will actually
// end up leaving the current page. The new page load could for example
// result in a download or a 'no content' response (e.g., a mailto: URL).
SetPendingEntry(entry);
NavigateToPendingEntry(NO_RELOAD);
}
1
2
3
4
5
6
7
8
void NavigationControllerImpl::SetPendingEntry(NavigationEntryImpl* entry) {
DiscardNonCommittedEntriesInternal();
pending_entry_ = entry;
NotificationService::current()->Notify(
NOTIFICATION_NAV_ENTRY_PENDING,
Source<NavigationController>(this),
Details<NavigationEntry>(entry));
}

上述代码中,先是终止了尚未提交处理的Entry,然后将欲访问的entry保存在pending_entry_变量,最后通过NotificationService::current()->Notify,把Entry插入一个消息队列,可以在notification_service_impl.cc的源码中看到,收到这个消息后,会通知所有的Observer

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
void NotificationServiceImpl::Notify(int type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK_GT(type, NOTIFICATION_ALL) <<
"Allowed for observing, but not posting.";
// There's no particular reason for the order in which the different
// classes of observers get notified here.
// Notify observers of all types and all sources
if (HasKey(observers_[NOTIFICATION_ALL], AllSources()) &&
source != AllSources()) {
FOR_EACH_OBSERVER(NotificationObserver,
*observers_[NOTIFICATION_ALL][AllSources().map_key()],
Observe(type, source, details));
}
// Notify observers of all types and the given source
if (HasKey(observers_[NOTIFICATION_ALL], source)) {
FOR_EACH_OBSERVER(NotificationObserver,
*observers_[NOTIFICATION_ALL][source.map_key()],
Observe(type, source, details));
}
// Notify observers of the given type and all sources
if (HasKey(observers_[type], AllSources()) &&
source != AllSources()) {
FOR_EACH_OBSERVER(NotificationObserver,
*observers_[type][AllSources().map_key()],
Observe(type, source, details));
}
// Notify observers of the given type and the given source
if (HasKey(observers_[type], source)) {
FOR_EACH_OBSERVER(NotificationObserver,
*observers_[type][source.map_key()],
Observe(type, source, details));
}
}

我们必须找到是哪个Observer处理了加载Entry的消息,可是到这里,线索似乎断了,怎么才能找到对应的Observer呢?

内事不决问百度,外事不决问谷歌。

在Google的帮助下,找到了这篇文档Getting Around the Chromium Source Code Directory Structure。这里介绍了整个Chromium的架构,重点关注“Navigating from the URL bar”一节。

  1. When the user types into or accepts an entry in the URL bar, the autocomplete edit box determines the final target URL and passes that to AutocompleteEdit::OpenURL. (This may not be exactly what the user typed - for example, an URL is generated in the case of a search query.)
  2. The navigation controller is instructed to navigate to the URL in NavigationController::LoadURL.
  3. The NavigationController calls TabContents::Navigate with the NavigationEntry it created to represent this particular page transition. It will create a new RenderViewHost if necessary, which will cause creation of a RenderView in the renderer process. A RenderView won’t exist if this is the first navigation, or if the renderer has crashed, so this will also recover from crashes.
  4. Navigate forwards to RenderViewHost::NavigateToEntry. The NavigationControllerstores this navigation entry, but it is marked as “pending” because it doesn’t know for sure if the transition will take place (maybe the host can not be resolved).
  5. RenderViewHost::NavigateToEntry sends a ViewMsg_Navigate to the new RenderView in the renderer process.
  6. When told to navigate, RenderView may navigate, it may fail, or it may navigate somewhere else instead (for example, if the user clicks a link). RenderViewHost waits for a ViewHostMsg_FrameNavigate from the RenderView.
  7. When the load is “committed” by WebKit (the server responded and is sending us data), the RenderView sends this message, which is handled in RenderViewHost::OnMsgNavigate.
  8. The NavigationEntry is updated with the information on the load. In the case of a link click, the browser has never seen this URL before. If the navigation was browser-initiated, as in the startup case, there may have been redirects that have changed the URL.
  9. The NavigationController updates its list of navigations to account for this new information.

步骤3中看到,NavigationController调用了TabContents::Navigate来处理Entry,随后就进入了渲染(Render)过程。而我们想要追踪的缓存文件管理的疑问,还是要深入到渲染阶段才能有个答案。

更多分析,将在后续文章中一一道出。