站在老罗的肩膀上:https://blog.csdn.net/luoshengyang/article/details/50450100
Chromium在加载一个网页之前,需要在Browser进程创建一个Frame Tree。Browser进程为网页创建了Frame Tree之后,再请求Render进程加载其内容。Frame Tree将网页抽象为Render Frame。Render Frame是为实现Out-of-Process iframes设计的。本文接下来就分析Frame Tree的创建过程,为后面分析网页加载过程打基础。
关于Chromium的Out-of-Process iframes的详细介绍,可以参考官方文档:Out-of-Process iframes (OOPIFs),或者前面Chromium网页加载过程简要介绍和学习计划一文。我们通过图1所示的例子简单介绍,如下所示:
图1 Out-of-Process iframes
在图1中,网页A在一个Tab显示,并且它通过iframe标签包含了网页B和网页C。网页C通过window.open在另外一个Tab中打开了另外一个网页C实例。新打开的网页C通过iframe标签包含了网页D,网页D又通过iframe标签包含了网页A的另外一个实例。
这时候Browser进程会分别为图1的两个Tab创建一个WebContents对象,如图2所示:
图2 RenderFrameHost/RenderFrameProxyHost
在Browser进程中,一个网页使用一个WebContents对象描述。每一个WebContents对象都又关联有一个Frame Tree。Frame Tree的每一个Node代表一个网页。其中,Frame Tree的根Node描述的是主网页,子Node描述的是通过iframe标签嵌入的子网页。例如,第一个Tab的Frame Tree包含有三个Node,分别代表网页A、B和C,第二个Tab的Frame Tree也包含有三个Node,分别代表网页C、D和A。
网页A、B、C和D在Chromium中形成了一个Browsing Instance。关于Chromium的Browsing Instance,可以参考官方文档:Process Models,它对应于HTML5规范中的Browsing Context。
我们观察代表网页B的子Node,它关联有一个RenderFrameHost对象和三个RenderFrameProxyHost对象。其中,RenderFrameHost对象描述的是网页B本身,另外三个RenderFrameProxyHosts对象描述的是网页A、C和D。表示网页B可以通过Javascript接口window.postMessage向网页A、C和D发送消息。
假设图1所示的网页A、B、C和D分别在不同的Render进程中渲染,如图3所示:
图3 RenderFrame/RenderFrameProxy
在负责加载和渲染网页B的Render进程中,有一个RenderFrame对象,代表图1所示的网页B。负责加载和渲染网页B的Render进程还包含有五个RenderFrameProxy对象,分别代表图1所示的两个网页A实例、两个网页C实例和一个网页D实例。在负责渲染网页A、C和D的Render进程中,也有类似的RenderFrame对象和RenderFrameProxy对象。
Browser进程的RenderFrameHost对象和Render进程的RenderFrame对象是一一对应的。同样,Browser进程的RenderFrameHostProxy对象和Render进程的RenderFrameProxy对象也是一一对应的。RenderFrame对象和RenderFrameProxy对象的存在,使得在同一个Browsing Instance或者Browsing Context中的网页可以通过Javascript接口window.postMessage相互发送消息。
例如,当网页B要向网页A发送消息时,负责加载和渲染网页B的Render进程就会在当前进程中找到与网页B对应的RenderFrame对象,以及与网页A对应的RenderFrameProxy对象,然后将要发送的消息从找到的RenderFrame对象传递给找到的RenderFrameProxy对象即可。与网页A对应RenderFrameProxy对象接收到消息之后,会进一步将该消息发送给负责加载和渲染网页A的Render进程处理,从而完成消息的发送和处理过程。
接下来,我们就结合源码分析Chromium为网页创建Frame Tree的过程,这个过程涉及到WebContents、RenderFrameHost和RenderFrame等重要对象的创建。
前面提到,Frame Tree是在Browser进程中创建的,并且与一个WebContents对象关联。从前面Chromium网页加载过程简要介绍和学习计划一文可以知道,WebContents是Chromium的Content模块提供的一个高级接口,通过这个接口可以将一个网页渲染在一个可视区域中。我们可以认为,一个WebContents对象就是用来实现一个网页的加载、解析、渲染和导航等功能的。
Frame Tree是在与其关联的WebContents对象的创建过程中创建的,因此接下来我们就从WebContents对象的创建过程开始分析Frame Tree的创建过程。以Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文提到的Shell APK为例,它会为每一个要加载的网页在Native层创建一个Shell。在创建这个Shell的过程中,就会同时创建一个WebContents对象。
从前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文可以知道,Shell APK是通过调用Native层的Shell类的静态成员函数CreateNewWindow为要加载的网页创建一个Shell的,它的实现如下所示:
Shell* Shell::CreateNewWindowWithSessionStorageNamespace(
BrowserContext* browser_context,
const GURL& url,
const scoped_refptr<SiteInstance>& site_instance,
const gfx::Size& initial_size,
scoped_refptr<SessionStorageNamespace> session_storage_namespace) {
WebContents::CreateParams create_params(browser_context, site_instance);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForcePresentationReceiverForTesting)) {
create_params.starting_sandbox_flags =
blink::kPresentationReceiverSandboxFlags;
}
create_params.initial_size = AdjustWindowSize(initial_size);
std::map<std::string, scoped_refptr<SessionStorageNamespace>>
session_storages;
session_storages[""] = session_storage_namespace;
std::unique_ptr<WebContents> web_contents =
WebContents::CreateWithSessionStorage(create_params, session_storages);
Shell* shell =
CreateShell(std::move(web_contents), create_params.initial_size,
true /* should_set_delegate */);
if (!url.is_empty())
shell->LoadURL(url);
return shell;
}
src/content/shell/browser/shell.cc
WebContents类的静态成员函数CreateWithSessionStorage的实现如下所示:
std::unique_ptr<WebContents> WebContents::CreateWithSessionStorage(
const WebContents::CreateParams& params,
const SessionStorageNamespaceMap& session_storage_namespace_map) {
std::unique_ptr<WebContentsImpl> new_contents(
new WebContentsImpl(params.browser_context));
...
new_contents->Init(params);
return new_contents;
}
src/content/browser/web_contents/web_contents_impl.cc
WebContents类的静态成员函数CreateWithOpener首先是创建一个WebContentsImpl对象。在将这个WebContentsImpl对象返回给调用者之前,会先调用它的成员函数Init对它进行初始化。
接下来,我们先分析WebContentsImpl对象的创建过程,即WebContentsImpl类的构造函数的实现,接下来再分析WebContentsImpl对象的初始化过程,即WebContentsImpl类的成员函数Init的实现。
WebContentsImpl类的构造函数的实现如下所示:
WebContentsImpl::WebContentsImpl(BrowserContext* browser_context)
: delegate_(nullptr),
controller_(this, browser_context),
render_view_host_delegate_view_(nullptr),
created_with_opener_(false),
frame_tree_(new NavigatorImpl(&controller_, this),
this,
this,
this,
this),
...
}
src/content/browser/web_contents/web_contents_impl.cc
WebContentsImpl类的成员变量controller_描述的是一个NavigationControllerImpl对象。后面我们可以看到,这个NavigationControllerImpl对象负责执行加载URL的操作。现在我们分析它的创建过程,如下所示:
NavigationControllerImpl::NavigationControllerImpl(
NavigationControllerDelegate* delegate,
BrowserContext* browser_context)
: browser_context_(browser_context),
...
delegate_(delegate),
...
}
src/content/browser/frame_host/navigation_controller_impl.cc
从前面的调用过程可以知道,参数delegate指向的是一个WebContentsImpl对象。这个WebContentsImpl对象保存在NavigationControllerImpl类的成员变量delegate_中。
回到WebContentsImpl类的构造函数中,它构造了一个NavigationControllerImpl对象之后,接下来又根据该NavigationControllerImpl对象创建一个NavigatorImpl对象。这个NavigatorImpl对象也是在加载URL时使用到的,它的创建过程如下所示:
NavigatorImpl::NavigatorImpl(NavigationControllerImpl* navigation_controller,
NavigatorDelegate* delegate)
: controller_(navigation_controller), delegate_(delegate) {}
src/content/browser/frame_host/navigator_impl.cc
再回到WebContentsImpl类的构造函数中,它创建了一个NavigatorImpl对象之后,又根据该NavigatorImpl对象创建一个FrameTree对象,并且保存在成员变量frame_tree_中。这个FrameTree对象描述的就是一个Frame Tree。
接下来我们继续分析FrameTree对象的创建过程,也就是FrameTree类的构造函数的实现,如下所示:
FrameTree::FrameTree(Navigator* navigator,
RenderFrameHostDelegate* render_frame_delegate,
RenderViewHostDelegate* render_view_delegate,
RenderWidgetHostDelegate* render_widget_delegate,
RenderFrameHostManager::Delegate* manager_delegate)
: render_frame_delegate_(render_frame_delegate),
render_view_delegate_(render_view_delegate),
render_widget_delegate_(render_widget_delegate),
manager_delegate_(manager_delegate),
root_(new FrameTreeNode(this,
navigator,
render_frame_delegate,
render_widget_delegate,
manager_delegate,
nullptr,
// The top-level frame must always be in a
// document scope.
blink::WebTreeScopeType::kDocument,
std::string(),
std::string(),
false,
base::UnguessableToken::Create(),
FrameOwnerProperties())),
focused_frame_tree_node_id_(FrameTreeNode::kFrameTreeNodeInvalidId),
load_progress_(0.0) {}
src/content/browser/frame_host/frame_tree.cc
从前面的调用过程可以知道,参数navigator指向的是一个NavigatorImpl对象,其余四个参数指向的是同一个WebContentsImpl对象,分别被保存在FrameTree类的成员变量render_frame_delegate_、render_view_delegate_、render_widget_delegate_和manager_delegate_中。
FrameTree类的构造函数接下来做的一件事情是创建一个FrameTreeNode对象,并且保存在成员变量root_中,作为当前正在创建的Frame Tree的根节点。这个根节点描述的就是后面要加载的网页。
FrameTreeNode对象的创建过程,即FrameTreeNode类的构造函数的实现,如下所示:
FrameTreeNode::FrameTreeNode(FrameTree* frame_tree,
Navigator* navigator,
RenderFrameHostDelegate* render_frame_delegate,
RenderWidgetHostDelegate* render_widget_delegate,
RenderFrameHostManager::Delegate* manager_delegate,
FrameTreeNode* parent,
blink::WebTreeScopeType scope,
const std::string& name,
const std::string& unique_name,
bool is_created_by_script,
const base::UnguessableToken& devtools_frame_token,
const FrameOwnerProperties& frame_owner_properties)
: frame_tree_(frame_tree),
navigator_(navigator),
render_manager_(this,
render_frame_delegate,
render_widget_delegate,
manager_delegate),
frame_tree_node_id_(next_frame_tree_node_id_++),
...
}
src/content/browser/frame_host/frame_tree_node.cc
从前面的调用过程可以知道,参数frame_tree和navigator指向的分别是一个FrameTree对象和一个NavigatorImpl对象,它们分别被保存在FrameTreeNode类的成员变量frame_tree_和navigator_中。
FrameTreeNode类的构造函数接下来做的一件事情是构造一个RenderFrameHostManager对象,并且保存在成员变量render_manager_中。这个RenderFrameHostManager对象负责管理与当前正在创建的FrameTreeNode对象关联的一个RenderFrameHost对象,它的构造过程如下所示:
RenderFrameHostManager::RenderFrameHostManager(
FrameTreeNode* frame_tree_node,
RenderFrameHostDelegate* render_frame_delegate,
RenderWidgetHostDelegate* render_widget_delegate,
Delegate* delegate)
: frame_tree_node_(frame_tree_node),
delegate_(delegate),
render_frame_delegate_(render_frame_delegate),
render_widget_delegate_(render_widget_delegate),
weak_factory_(this) {
DCHECK(frame_tree_node_);
}
src/content/browser/frame_host/render_frame_host_manager.cc
从前面的调用过程可以知道,参数frame_tree_node指向的是一个FrameTreeNode对象,它被保存在RenderFrameHostManager类的成员变量frame_tree_node_中,其余四个参数指向的是同一个WebContentsImpl对象,分别被保存在RenderFrameHostManager类的成员变量render_frame_delegate_和render_widget_delegate_中。
这一步执行完成之后,一个Frame Tree就创建出来了。这是一个只有根节点的Frame Tree。根节点描述的网页就是接下来要进行加载的。根节点描述的网页加载完成之后,就会进行解析。在解析的过程中,如果碰到iframe标签,那么就会创建另外一个子节点,并且添加到当前正在创建的Frame Tree中去。
返回到WebContents类的静态成员函数CreateWithOpener中,它创建了一个WebContentsImpl对象之后,接下来会调用这个WebContentsImpl对象的成员函数Init执行初始化工作,如下所示:
void WebContentsImpl::Init(const WebContents::CreateParams& params) {
...
GetRenderManager()->Init(
site_instance.get(), view_routing_id, params.main_frame_routing_id,
main_frame_widget_routing_id, params.renderer_initiated_creation);
...
if (GuestMode::IsCrossProcessFrameGuest(this)) {
view_.reset(new WebContentsViewChildFrame(
this, delegate, &render_view_host_delegate_view_));
} else {
view_.reset(CreateWebContentsView(this, delegate,
&render_view_host_delegate_view_));
if (browser_plugin_guest_) {
...
}
}
...
}
src/content/browser/web_contents/web_contents_impl.cc
WebContentsImpl类的成员函数GetRenderManager的实现如下所示, 指向一个RenderFrameHostManager对象:
RenderFrameHostManager* WebContentsImpl::GetRenderManager() const {
return frame_tree_.root()->render_manager();
}
src/content/browser/web_contents/web_contents_impl.cc
回到WebContentsImpl类的成员函数Init中,它获得了一个RenderFrameHostManager对象之后,就调用它的成员函数Init对它进行初始化,如下所示:
void RenderFrameHostManager::Init(SiteInstance* site_instance,
int32_t view_routing_id,
int32_t frame_routing_id,
int32_t widget_routing_id,
bool renderer_initiated_creation) {
DCHECK(site_instance);
SetRenderFrameHost(CreateRenderFrameHost(site_instance, view_routing_id,
frame_routing_id, widget_routing_id,
delegate_->IsHidden(),
renderer_initiated_creation));
...
}
src/content/browser/frame_host/render_frame_host_manager.cc
RenderFrameHostManager类的成员函数Init主要做了两件事情。第一件事情是调用成员函数CreateRenderFrameHost创建了一个RenderFrameHostImpl对象。第二件事情是调用成员函数SetRenderFrameHost将该RenderFrameHostImpl对象保存在内部,如下所示:
std::unique_ptr<RenderFrameHostImpl> RenderFrameHostManager::SetRenderFrameHost(
std::unique_ptr<RenderFrameHostImpl> render_frame_host) {
// Swap the two.
std::unique_ptr<RenderFrameHostImpl> old_render_frame_host =
std::move(render_frame_host_);
render_frame_host_ = std::move(render_frame_host);
...
return old_render_frame_host;
}
src/content/browser/frame_host/render_frame_host_manager.cc
RenderFrameHostManager类的成员函数SetRenderFrameHost主要是将参数render_frame_host描述的一个RenderFrameHostImpl对象保存在成员变量render_frame_host_中。
回到RenderFrameHostManager类的成员函数Init中,接下来我们主要分析它调用成员函数CreateRenderFrameHost创建RenderFrameHostImpl对象的过程,如下所示:
std::unique_ptr<RenderFrameHostImpl>
RenderFrameHostManager::CreateRenderFrameHost(
SiteInstance* site_instance,
int32_t view_routing_id,
int32_t frame_routing_id,
int32_t widget_routing_id,
bool hidden,
bool renderer_initiated_creation) {
...
// Create a RVH for main frames, or find the existing one for subframes.
FrameTree* frame_tree = frame_tree_node_->frame_tree();
RenderViewHostImpl* render_view_host = nullptr;
if (frame_tree_node_->IsMainFrame()) {
render_view_host = frame_tree->CreateRenderViewHost(
site_instance, view_routing_id, frame_routing_id, widget_routing_id,
false, hidden);
...
} else {
render_view_host = frame_tree->GetRenderViewHost(site_instance);
CHECK(render_view_host);
}
return RenderFrameHostFactory::Create(
site_instance, render_view_host, render_frame_delegate_,
render_widget_delegate_, frame_tree, frame_tree_node_, frame_routing_id,
widget_routing_id, hidden, renderer_initiated_creation);
}
src/content/browser/frame_host/render_frame_host_manager.cc
从前面的分析可以知道,当前正在处理的RenderFrameHostManager对象的成员变量frame_tree_node_描述的是一个Frame Tree的根节点。一个Frame Tree的根节点描述的网页是一个Main Frame(网页中的iframe标签描述的网页称为Sub Frame),因此RenderFrameHostManager类的成员函数CreateRenderFrameHost是调用FrameTree类的成员函数FrameTree::CreateRenderViewHost创建一个RenderViewHostImpl对象,并且保存在本地变量render_view_host中。
FrameTree类的成员函数CreateRenderViewHost的实现如下所示:
RenderViewHostImpl* FrameTree::CreateRenderViewHost(
SiteInstance* site_instance,
int32_t routing_id,
int32_t main_frame_routing_id,
int32_t widget_routing_id,
bool swapped_out,
bool hidden) {
RenderViewHostMap::iterator iter =
render_view_host_map_.find(site_instance->GetId());
if (iter != render_view_host_map_.end())
return iter->second;
RenderViewHostImpl* rvh =
static_cast<RenderViewHostImpl*>(RenderViewHostFactory::Create(
site_instance, render_view_delegate_, render_widget_delegate_,
routing_id, main_frame_routing_id, widget_routing_id, swapped_out,
hidden));
render_view_host_map_[site_instance->GetId()] = rvh;
return rvh;
}
src/content/browser/frame_host/frame_tree.cc
FrameTree类的成员函数CreateRenderViewHost调用RenderViewHostFactory类的静态成员函数Create创建了一个RenderViewHostImpl对象。在将这个RenderViewHostImpl对象返回给调用者之前,会以参数site_instance描述的一个Site Instance的ID为键值,将该RenderViewHostImpl对象保存在成员变量render_view_host_map_描述的一个Hash Map中,以便以后可以进行查找。
RenderViewHostFactory类的静态成员函数Create的实现如下所示:
RenderViewHost* RenderViewHostFactory::Create(
SiteInstance* instance,
RenderViewHostDelegate* delegate,
RenderWidgetHostDelegate* widget_delegate,
int32_t routing_id,
int32_t main_frame_routing_id,
int32_t widget_routing_id,
bool swapped_out,
bool hidden) {
...
return new RenderViewHostImpl(
instance,
base::WrapUnique(RenderWidgetHostFactory::Create(
widget_delegate, instance->GetProcess(), widget_routing_id, nullptr,
hidden)),
delegate, routing_id, main_frame_routing_id, swapped_out,
true /* has_initialized_audio_host */);
}
src/content/browser/renderer_host/render_view_host_factory.cc
RenderViewHostFactory类的静态成员函数Create首先是创建了一个RenderViewHostImpl对象,然后再将这个RenderViewHostImpl对象返回给调用者。从前面Chromium的Render进程启动过程分析一文可以知道,在创建RenderViewHostImpl对象的过程中,将会启动一个Render进程。这个Render进程接下来将会负责加载指定的URL。
这一步执行完成之后,回到RenderFrameHostManager类的成员函数CreateRenderFrameHost中,它接下来调用RenderFrameHostFactory类的静态成员函数Create创建了一个RenderFrameHostImpl对象。这个RenderFrameHostImpl对象返回给RenderFrameHostManager类的成员函数Init后,将会被保存在成员变量render_frame_host_中。
RenderFrameHostFactory类的静态成员函数Create的实现如下所示:
// static
std::unique_ptr<RenderFrameHostImpl> RenderFrameHostFactory::Create(
SiteInstance* site_instance,
RenderViewHostImpl* render_view_host,
RenderFrameHostDelegate* delegate,
RenderWidgetHostDelegate* rwh_delegate,
FrameTree* frame_tree,
FrameTreeNode* frame_tree_node,
int32_t routing_id,
int32_t widget_routing_id,
bool hidden,
bool renderer_initiated_creation) {
...
return base::WrapUnique(new RenderFrameHostImpl(
site_instance, render_view_host, delegate, rwh_delegate, frame_tree,
frame_tree_node, routing_id, widget_routing_id, hidden,
renderer_initiated_creation));
}
src/content/browser/frame_host/render_frame_host_factory.cc
从这里可以看到,RenderFrameHostFactory类的静态成员函数Create创建的是一个RenderFrameHostImpl对象,并且将这个RenderFrameHostImpl对象返回给调用者。
RenderFrameHostImpl对象的创建过程,即RenderFrameHostImpl类的构造函数的实现,如下所示:
RenderFrameHostImpl::RenderFrameHostImpl(SiteInstance* site_instance,
RenderViewHostImpl* render_view_host,
RenderFrameHostDelegate* delegate,
RenderWidgetHostDelegate* rwh_delegate,
FrameTree* frame_tree,
FrameTreeNode* frame_tree_node,
int32_t routing_id,
int32_t widget_routing_id,
bool hidden,
bool renderer_initiated_creation)
: render_view_host_(render_view_host),
delegate_(delegate),
...
frame_tree_(frame_tree),
frame_tree_node_(frame_tree_node),
...
}
src/content/browser/frame_host/render_frame_host_impl.cc
RenderFrameHostImpl类的构造函数将参数render_view_host描述的一个RenderViewHostImpl对象保存在成员变量render_view_host_中,这个RenderViewHostImpl对象就是前面调用RenderViewHostFactory类的静态成员函数Create创建的。
这一步执行完成之后,回到WebContentsImpl类的成员函数Init中,它接下来还做有另外一个初始化工作,如下所示:
void WebContentsImpl::Init(const WebContents::CreateParams& params) {
...
GetRenderManager()->Init(
site_instance.get(), view_routing_id, params.main_frame_routing_id,
main_frame_widget_routing_id, params.renderer_initiated_creation);
...
if (GuestMode::IsCrossProcessFrameGuest(this)) {
view_.reset(new WebContentsViewChildFrame(
this, delegate, &render_view_host_delegate_view_));
} else {
view_.reset(CreateWebContentsView(this, delegate,
&render_view_host_delegate_view_));
if (browser_plugin_guest_) {
...
}
}
...
}
src/content/browser/web_contents/web_contents_impl.cc
当WebContentsImpl类的成员变量browser_plugin_guest_指向了一个BrowserPluginGuest对象时,表示当前正在处理的WebContentsImpl对象用来为一个Browser Plugin加载网页。我们不考虑这种情况。这时候WebContentsImpl类的成员函数Init就会调用函数CreateWebContentsView创建一个WebContentsViewAndroid对象,并且保存在成员变量view_中。
函数CreateWebContentsView的实现如下所示:
WebContentsView* CreateWebContentsView(
WebContentsImpl* web_contents,
WebContentsViewDelegate* delegate,
RenderViewHostDelegateView** render_view_host_delegate_view) {
WebContentsViewAura* rv = new WebContentsViewAura(web_contents, delegate);
*render_view_host_delegate_view = rv;
return rv;
}
src/content/browser/web_contents/web_contents_view_aura.cc
从这里可以看到,在Android平台上,函数CreateWebContentsView返回给调用者的是一个WebContentsViewAndroid对象。
这一步执行完成之后,回到Shell类的静态成员函数CreateNewWindow,这时候它就创建了一个WebContentsImpl对象,接下来这个WebContentsImpl对象会用来创建一个Shell对象,这是通过调用Shell类的静态成员函数CreateShell实现的,如下所示:
Shell* Shell::CreateShell(std::unique_ptr<WebContents> web_contents,
const gfx::Size& initial_size,
bool should_set_delegate) {
WebContents* raw_web_contents = web_contents.get();
Shell* shell = new Shell(std::move(web_contents), should_set_delegate);
shell->PlatformCreateWindow(initial_size.width(), initial_size.height());
shell->PlatformSetContents();
shell->PlatformResizeSubViews();
// Note: Do not make RenderFrameHost or RenderViewHost specific state changes
// here, because they will be forgotten after a cross-process navigation. Use
// RenderFrameCreated or RenderViewCreated instead.
if (switches::IsRunWebTestsSwitchPresent()) {
raw_web_contents->GetMutableRendererPrefs()->use_custom_colors = false;
raw_web_contents->GetRenderViewHost()->SyncRendererPrefs();
}
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kForceWebRtcIPHandlingPolicy)) {
raw_web_contents->GetMutableRendererPrefs()->webrtc_ip_handling_policy =
command_line->GetSwitchValueASCII(
switches::kForceWebRtcIPHandlingPolicy);
}
return shell;
}
src/content/shell/browser/shell.cc
从前面的调用过程可以知道,参数web_contents指向的是一个WebContentsImpl对象。
Shell类的静态成员函数CreateShell首先创建了一个Shell对象,接下来将参数web_contents指向的WebContentsImpl对象保存在该Shell对象的成员变量web_contents_中,最后又调用该Shell对象的成员函数PlatformSetContents执行一些平台相关的初始化操作。
Shell类的成员函数PlatformSetContents的实现如下所示:
void Shell::PlatformSetContents() {
JNIEnv* env = AttachCurrentThread();
Java_Shell_initFromNativeTabContents(env, java_object_,
web_contents()->GetJavaWebContents());
}
src/content/shell/browser/shell_android.cc
Shell类的成员函数PlatformSetContents调用函数Java_Shell_initFromNativeTabContents调用成员变量java_object_描述的一个Java层Shell对象的成员函数initFromNativeTabContents执行平台相关的初始化操作。同时传递给该Java层Shell对象的成员函数initFromNativeTabContents一个WebContentsImpl对象。这个WebContentsImpl对象是通过调用Native层Shell类的成员函数web_contents获得的,如下所示:
class Shell : public WebContentsDelegate,
public WebContentsObserver {
public:
......
WebContents* web_contents() const { return web_contents_.get(); }
......
private:
......
scoped_ptr<WebContents> web_contents_;
......
}
src/content/shell/browser/shell.h
从前面的分析可以知道,Shell类的成员变量web_contents_指向的一个WebContentsImpl对象,Shell类的成员函数web_contents将这个WebContentsImpl对象返回给调用者。
接下来我们继续分析Java层Shell类的成员函数initFromNativeTabContents的实现,如下所示:
public class Shell extends LinearLayout {
......
private ContentViewCore mContentViewCore;
......
@CalledByNative
private void initFromNativeTabContents(WebContents webContents) {
Context context = getContext();
ContentView cv = ContentView.createContentView(context, webContents);
mViewAndroidDelegate = new ShellViewAndroidDelegate(cv);
webContents.initialize(
"", mViewAndroidDelegate, cv, mWindow, WebContents.createDefaultInternalsHolder());
...
}
}
src/content/shell/android/java/src/org/chromium/content_shell/Shell.java
ContentViewCore类的成员函数initialize执行了一个操作,就是调用另外一个成员函数nativeInit创建了一个Native层的ContentViewCoreImpl对象,并且将它的地址保存在成员变量mNativeContentViewCore中。
....MIssing sth about java functions....
回到ContentViewCoreImpl类的成员函数LoadUrl,它获得了一个NavigationControllerImpl对象之后,再调用该NavigationControllerImpl对象的成员函数LoadURLWithParams加载参数params描述的URL,如下所示:
void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
...
// The user initiated a load, we don't need to reload anymore.
needs_reload_ = false;
NavigateWithoutEntry(params);
}
src/content/browser/frame_host/navigation_controller_impl.cc
NavigationControllerImpl类的成员函数LoadURLWithParams首先是调用静态成员函数NavigateWithoutEntry创建一个NavigationEntryImpl对象,如下所示:
void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
...
// The user initiated a load, we don't need to reload anymore.
needs_reload_ = false;
NavigateWithoutEntry(params);
}
void NavigationControllerImpl::NavigateWithoutEntry(
const LoadURLParams& params) {
...
// Javascript URLs should not create NavigationEntries. All other navigations
// do, including navigations to chrome renderer debug URLs.
std::unique_ptr<NavigationEntryImpl> entry;
if (!params.url.SchemeIs(url::kJavaScriptScheme)) {
entry = CreateNavigationEntryFromLoadParams(node, params);
DiscardPendingEntry(false);
SetPendingEntry(std::move(entry));
}
// Renderer-debug URLs are sent to the renderer process immediately for
// processing and don't need to create a NavigationRequest.
// Note: this includes navigations to JavaScript URLs, which are considered
// renderer-debug URLs.
// Note: we intentionally leave the pending entry in place for renderer debug
// URLs, unlike the cases below where we clear it if the navigation doesn't
// proceed.
if (IsRendererDebugURL(params.url)) {
HandleRendererDebugURL(node, params.url);
return;
}
// Convert navigations to the current URL to a reload.
// TODO(clamy): We should be using FrameTreeNode::IsMainFrame here instead of
// relying on the frame tree node id from LoadURLParams. Unfortunately,
// DevTools sometimes issues navigations to main frames that they do not
// expect to see treated as reload, and it only works because they pass a
// FrameTreeNode id in their LoadURLParams. Change this once they no longer do
// that. See https://crbug.com/850926.
ReloadType reload_type = ReloadType::NONE;
if (ShouldTreatNavigationAsReload(
params.url, pending_entry_->GetVirtualURL(),
params.base_url_for_data_url, params.transition_type,
params.frame_tree_node_id == RenderFrameHost::kNoFrameTreeNodeId,
params.load_type ==
NavigationController::LOAD_TYPE_HTTP_POST /* is_post */,
false /* is_reload */, false /* is_navigation_to_existing_entry */,
transient_entry_index_ != -1 /* has_interstitial */,
GetLastCommittedEntry())) {
reload_type = ReloadType::NORMAL;
}
// navigation_ui_data should only be present for main frame navigations.
DCHECK(node->IsMainFrame() || !params.navigation_ui_data);
// TODO(clamy): Create the NavigationRequest directly from the LoadURLParams
// instead of relying on the NavigationEntry.
DCHECK(pending_entry_);
std::unique_ptr<NavigationRequest> request = CreateNavigationRequest(
node, *pending_entry_, pending_entry_->GetFrameEntry(node), reload_type,
false /* is_same_document_history_load */,
false /* is_history_navigation_in_new_child */, nullptr,
params.navigation_ui_data ? params.navigation_ui_data->Clone() : nullptr);
// If the navigation couldn't start, return immediately and discard the
// pending NavigationEntry.
if (!request) {
DiscardPendingEntry(false);
return;
}
// If an interstitial page is showing, the previous renderer is blocked and
// cannot make new requests. Unblock (and disable) it to allow this
// navigation to succeed. The interstitial will stay visible until the
// resulting DidNavigate.
if (delegate_->GetInterstitialPage()) {
static_cast<InterstitialPageImpl*>(delegate_->GetInterstitialPage())
->CancelForNavigation();
}
// This call does not support re-entrancy. See http://crbug.com/347742.
CHECK(!in_navigate_to_pending_entry_);
in_navigate_to_pending_entry_ = true;
node->navigator()->Navigate(std::move(request), reload_type,
RestoreType::NONE);
in_navigate_to_pending_entry_ = false;
}
创建request:
std::unique_ptr<NavigationRequest>
NavigationControllerImpl::CreateNavigationRequest(
FrameTreeNode* frame_tree_node,
const NavigationEntryImpl& entry,
FrameNavigationEntry* frame_entry,
ReloadType reload_type,
bool is_same_document_history_load,
bool is_history_navigation_in_new_child,
const scoped_refptr<network::ResourceRequestBody>& post_body,
std::unique_ptr<NavigationUIData> navigation_ui_data) {
GURL dest_url = frame_entry->url();
Referrer dest_referrer = frame_entry->referrer();
if (reload_type == ReloadType::ORIGINAL_REQUEST_URL &&
entry.GetOriginalRequestURL().is_valid() && !entry.GetHasPostData()) {
// We may have been redirected when navigating to the current URL.
// Use the URL the user originally intended to visit as signaled by the
// ReloadType, if it's valid and if a POST wasn't involved; the latter
// case avoids issues with sending data to the wrong page.
dest_url = entry.GetOriginalRequestURL();
dest_referrer = Referrer();
}
// Don't attempt to navigate if the virtual URL is non-empty and invalid.
if (frame_tree_node->IsMainFrame()) {
const GURL& virtual_url = entry.GetVirtualURL();
if (!virtual_url.is_valid() && !virtual_url.is_empty()) {
LOG(WARNING) << "Refusing to load for invalid virtual URL: "
<< virtual_url.possibly_invalid_spec();
return nullptr;
}
}
// Don't attempt to navigate to non-empty invalid URLs.
if (!dest_url.is_valid() && !dest_url.is_empty()) {
LOG(WARNING) << "Refusing to load invalid URL: "
<< dest_url.possibly_invalid_spec();
return nullptr;
}
// The renderer will reject IPC messages with URLs longer than
// this limit, so don't attempt to navigate with a longer URL.
if (dest_url.spec().size() > url::kMaxURLChars) {
LOG(WARNING) << "Refusing to load URL as it exceeds " << url::kMaxURLChars
<< " characters.";
return nullptr;
}
// Determine if Previews should be used for the navigation.
PreviewsState previews_state = PREVIEWS_UNSPECIFIED;
if (!frame_tree_node->IsMainFrame()) {
// For subframes, use the state of the top-level frame.
previews_state = frame_tree_node->frame_tree()
->root()
->current_frame_host()
->last_navigation_previews_state();
}
// Give the delegate an opportunity to adjust the previews state.
if (delegate_)
delegate_->AdjustPreviewsStateForNavigation(&previews_state);
// This will be used to set the Navigation Timing API navigationStart
// parameter for browser navigations in new tabs (intents, tabs opened through
// "Open link in new tab"). If the navigation must wait on the current
// RenderFrameHost to execute its BeforeUnload event, the navigation start
// will be updated when the BeforeUnload ack is received.
base::TimeTicks navigation_start = base::TimeTicks::Now();
TRACE_EVENT_INSTANT_WITH_TIMESTAMP0(
"navigation,rail", "NavigationTiming navigationStart",
TRACE_EVENT_SCOPE_GLOBAL, navigation_start);
FrameMsg_Navigate_Type::Value navigation_type = GetNavigationType(
frame_tree_node->current_url(), // old_url
dest_url, // new_url
reload_type, // reload_type
entry, // entry
*frame_entry, // frame_entry
is_same_document_history_load); // is_same_document_history_load
return NavigationRequest::CreateBrowserInitiated(
frame_tree_node, dest_url, dest_referrer, *frame_entry, entry,
navigation_type, previews_state, is_same_document_history_load,
is_history_navigation_in_new_child, post_body, navigation_start, this,
std::move(navigation_ui_data));
}
src/content/browser/frame_host/navigation_controller_impl.cc
加载URL:
void NavigatorImpl::Navigate(std::unique_ptr<NavigationRequest> request,
ReloadType reload_type,
RestoreType restore_type) {
TRACE_EVENT0("browser,navigation", "NavigatorImpl::Navigate");
const GURL& dest_url = request->common_params().url;
FrameTreeNode* frame_tree_node = request->frame_tree_node();
navigation_data_.reset(new NavigationMetricsData(
request->common_params().navigation_start, dest_url, restore_type));
// Check if the BeforeUnload event needs to execute before assigning the
// NavigationRequest to the FrameTreeNode. Assigning it to the FrameTreeNode
// has the side effect of initializing the current RenderFrameHost, which will
// return that it should execute the BeforeUnload event (even though we don't
// need to wait for it in the case of a brand new RenderFrameHost).
//
// We don't want to dispatch a beforeunload handler if
// is_history_navigation_in_new_child is true. This indicates a newly created
// child frame which does not have a beforeunload handler.
bool should_dispatch_beforeunload =
!FrameMsg_Navigate_Type::IsSameDocument(
request->common_params().navigation_type) &&
!request->request_params().is_history_navigation_in_new_child &&
frame_tree_node->current_frame_host()->ShouldDispatchBeforeUnload(
false /* check_subframes_only */);
int nav_entry_id = request->nav_entry_id();
bool is_pending_entry =
controller_->GetPendingEntry() &&
(nav_entry_id == controller_->GetPendingEntry()->GetUniqueID());
frame_tree_node->CreatedNavigationRequest(std::move(request));
DCHECK(frame_tree_node->navigation_request());
// Have the current renderer execute its beforeunload event if needed. If it
// is not needed then NavigationRequest::BeginNavigation should be directly
// called instead.
if (should_dispatch_beforeunload) {
frame_tree_node->navigation_request()->SetWaitingForRendererResponse();
frame_tree_node->current_frame_host()->DispatchBeforeUnload(
RenderFrameHostImpl::BeforeUnloadType::BROWSER_INITIATED_NAVIGATION,
reload_type != ReloadType::NONE);
} else {
frame_tree_node->navigation_request()->BeginNavigation();
// WARNING: The NavigationRequest might have been destroyed in
// BeginNavigation(). Do not use |frame_tree_node->navigation_request()|
// after this point without null checking it first.
}
if (frame_tree_node->IsMainFrame() && frame_tree_node->navigation_request()) {
// For the trace below we're using the navigation handle as the async
// trace id, |navigation_start| as the timestamp and reporting the
// FrameTreeNode id as a parameter. For navigations where no network
// request is made (data URLs, JavaScript URLs, etc) there is no handle
// and so no tracing is done.
TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1(
"navigation", "Navigation timeToNetworkStack",
frame_tree_node->navigation_request()->navigation_handle(),
frame_tree_node->navigation_request()->common_params().navigation_start,
"FrameTreeNode id", frame_tree_node->frame_tree_node_id());
}
// Make sure no code called via RFH::Navigate clears the pending entry.
if (is_pending_entry)
CHECK_EQ(nav_entry_id, controller_->GetPendingEntry()->GetUniqueID());
// Notify observers about navigation.
if (delegate_ && is_pending_entry)
delegate_->DidStartNavigationToPendingEntry(dest_url, reload_type);
}
src/content/browser/frame_host/navigation_controller_impl.cc
...Missing sth....
回到RenderFrameHostManager类的成员函数Navigate中,它通过调用成员函数UpdateStateForNavigate获得的RenderFrameHostImpl对象还没有关联一个Render View控件,这时候它就会调用另外一个成员函数InitRenderView为这个RenderFrameHostImpl关联一个Render View控件。
RenderFrameHostManager类的成员函数InitRenderView的实现如下所示:
bool RenderFrameHostManager::InitRenderView(
RenderViewHostImpl* render_view_host,
RenderFrameProxyHost* proxy) {
...
bool created = delegate_->CreateRenderViewForRenderManager(
render_view_host, opener_frame_routing_id,
proxy ? proxy->GetRoutingID() : MSG_ROUTING_NONE,
frame_tree_node_->devtools_frame_token(),
frame_tree_node_->current_replication_state());
if (created && proxy)
proxy->set_render_frame_proxy_created(true);
return created;
}
src/content/browser/frame_host/render_frame_host_manager.cc
从前面的分析可以知道,RenderFrameHostManager类的成员变量delegate_描述的是一个WebContentsImpl对象。RenderFrameHostManager类的成员函数InitRenderView调用这个WebContentsImpl对象的成员函数CreateRenderViewForRenderManager为参数render_view_host描述的一个RenderViewHostImpl对象创建一个Render View控件。这个RenderViewHostImpl对象是与RenderFrameHostManager类的成员函数UpdateStateForNavigate返回的RenderFrameHostImpl对象关联的,因此,这里调用成员变量delegate_描述的WebContentsImpl对象的成员函数CreateRenderViewForRenderManager实际上是为该RenderFrameHostImpl对象创建一个Render View控件。
WebContentsImpl类的成员函数CreateRenderViewForRenderManager的实现如下所示:
bool WebContentsImpl::CreateRenderViewForRenderManager(
RenderViewHost* render_view_host,
int opener_frame_routing_id,
int proxy_routing_id,
const base::UnguessableToken& devtools_frame_token,
const FrameReplicationState& replicated_frame_state) {
TRACE_EVENT0("browser,navigation",
"WebContentsImpl::CreateRenderViewForRenderManager");
if (proxy_routing_id == MSG_ROUTING_NONE)
CreateRenderWidgetHostViewForRenderManager(render_view_host);
if (!static_cast<RenderViewHostImpl*>(render_view_host)
->CreateRenderView(opener_frame_routing_id, proxy_routing_id,
devtools_frame_token, replicated_frame_state,
created_with_opener_)) {
return false;
}
...
return true;
}
src/content/browser/web_contents/web_contents_impl.cc
为参数render_view_host描述的RenderViewHostImpl对象创建了一个Render View控件之后,WebContentsImpl类的成员函数CreateRenderViewForRenderManager接下来还会调用这个RenderViewHostImpl对象的成员函数CreateRenderView请求在对应的Render进程中创建一个RenderFrameImpl对象。这个RenderFrameImpl对象与前面通过调用成员函数UpdateStateForNavigate获得的RenderFrameHostImpl对象相对应,也就是这两个对象以后可以进行Render Frame相关的进程间通信操作。
bool RenderViewHostImpl::CreateRenderView(
int opener_frame_route_id,
int proxy_route_id,
const base::UnguessableToken& devtools_frame_token,
const FrameReplicationState& replicated_frame_state,
bool window_was_created_with_opener) {
...
GetWidget()->set_renderer_initialized(true);
mojom::CreateViewParamsPtr params = mojom::CreateViewParams::New();
...
// Let our delegate know that we created a RenderView.
DispatchRenderViewCreated();
...
return true;
}
src/content/browser/renderer_host/render_view_host_impl.cc
使用DispatchRenderViewCreated()通知delegate已经创建了Render View控件.
在Render进程中,类型为ViewMsg_New的IPC消息由RenderThreadImpl类的成员函数OnControlMessageReceived进行接收,如下所示:
bool RenderThreadImpl::OnControlMessageReceived(const IPC::Message& msg) {
for (auto& observer : observers_) {
if (observer.OnControlMessageReceived(msg))
return true;
}
// Some messages are handled by delegates.
if (dom_storage_dispatcher_->OnMessageReceived(msg)) {
return true;
}
return false;
}
src/content/renderer/render_thread_impl.cc
从这里可以看到,RenderThreadImpl类的成员函数OnControlMessageReceived将类型为ViewMsg_New的IPC消息分发给成员函数OnCreateNewView处理。
...Missing who calls CreateView...
RenderThreadImpl类的成员函数CreateView的实现如下所示:
void RenderThreadImpl::CreateView(mojom::CreateViewParamsPtr params) {
CompositorDependencies* compositor_deps = this;
is_scroll_animator_enabled_ = params->web_preferences.enable_scroll_animator;
// When bringing in render_view, also bring in webkit's glue and jsbindings.
RenderViewImpl::Create(compositor_deps, std::move(params),
RenderWidget::ShowCallback(),
GetWebMainThreadScheduler()->DefaultTaskRunner());
}
/*static*/
RenderViewImpl* RenderViewImpl::Create(
CompositorDependencies* compositor_deps,
mojom::CreateViewParamsPtr params,
RenderWidget::ShowCallback show_callback,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(params->view_id != MSG_ROUTING_NONE);
DCHECK(params->main_frame_widget_routing_id != MSG_ROUTING_NONE);
RenderViewImpl* render_view;
if (g_create_render_view_impl)
render_view = g_create_render_view_impl(compositor_deps, *params);
else
render_view = new RenderViewImpl(compositor_deps, *params, task_runner);
render_view->Initialize(std::move(params), std::move(show_callback),
std::move(task_runner));
return render_view;
}
src/content/renderer/render_thread_impl.cc
void RenderViewImpl::Initialize(
mojom::CreateViewParamsPtr params,
RenderWidget::ShowCallback show_callback,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
...
WebFrame* opener_frame =
RenderFrameImpl::ResolveOpener(params->opener_frame_route_id);
webview_ =
WebView::Create(this, WidgetClient(),
is_hidden() ? blink::mojom::PageVisibilityState::kHidden
: blink::mojom::PageVisibilityState::kVisible,
opener_frame ? opener_frame->View() : nullptr);
RenderWidget::Init(std::move(show_callback), webview_->GetWidget());
...
main_render_frame_ = RenderFrameImpl::CreateMainFrame(
this, params->main_frame_routing_id,
std::move(main_frame_interface_provider),
params->main_frame_widget_routing_id, params->hidden,
GetWidget()->GetWebScreenInfo(), GetWidget()->compositor_deps(),
opener_frame, params->devtools_main_frame_token,
params->replicated_frame_state, params->has_committed_real_load);
}
...
if (main_render_frame_)
main_render_frame_->Initialize();
...
GetContentClient()->renderer()->RenderViewCreated(this);
page_zoom_level_ = 0;
nav_state_sync_timer_.SetTaskRunner(task_runner);
}
src/content/renderer/render_view_impl.cc
WebView类的静态成员函数create调用WebViewImpl类的静态成员函数create创建了一个WebViewImpl对象,后者的实现如下所示:
WebView* WebView::Create(WebViewClient* client,
WebWidgetClient* widget_client,
mojom::PageVisibilityState visibility_state,
WebView* opener) {
return WebViewImpl::Create(client, widget_client, visibility_state,
static_cast<WebViewImpl*>(opener));
}
src/third_party/blink/renderer/core/exported/web_view_impl.cc
if (main_render_frame_)
main_render_frame_->Initialize();
src/content/renderer/render_view_impl.cc
在WebKit中,Web Frame有Local和Remote之分。Local类型的Web Frame描述的是网页的Main Frame,它是在当前Render进程中加载和渲染的。Remote类型的Web Frame描述的是网页的Sub Frame,它是在其他Render进程中加载和渲染的。Sub Frame描述的网页是通过iframe标签嵌入在Main Frame描述的网页中的。这一点可以参考前面的图3。
// static
LocalFrame* LocalFrame::Create(LocalFrameClient* client,
Page& page,
FrameOwner* owner,
InterfaceRegistry* interface_registry) {
LocalFrame* frame = new LocalFrame(
client, page, owner,
interface_registry ? interface_registry
: InterfaceRegistry::GetEmptyInterfaceRegistry());
PageScheduler* page_scheduler = page.GetPageScheduler();
if (frame->IsMainFrame() && page_scheduler)
page_scheduler->SetIsMainFrameLocal(true);
probe::frameAttachedToParent(frame);
return frame;
}
初始化FrameLoader创建document_loader, 用于关联之后的DOM,
void FrameLoader::Init() {
...
provisional_document_loader_ = Client()->CreateDocumentLoader(
frame_, initial_request, SubstituteData(),
ClientRedirectPolicy::kNotClientRedirect,
base::UnguessableToken::Create(), nullptr /* extra_data */,
WebNavigationTimings());
...
}
Render进程就处理完毕从Browser进程发送过来的类型为ViewMsg_New的IPC消息。Render进程在处理IPC消息的过程中,主要就是创建了一个RenderViewImpl对象、一个RenderFrameImpl对象、一个WebLocalFrameImpl对象、一个LocalFrame对象。这些对象的关系和作用可以参考前面Chromium网页加载过程简要介绍和学习计划一文的介绍。创建这些对象是为后面加载网页内容作准备的。
Frame Tree是在网页内容加载过程之前在Browser进程中创建的。要加载的网页对应于这个Frame Tree的一个Node。这个Node又关联有一个RenderFrameHostImpl对象。这个RenderFrameHostImpl对象在Render进程又对应有一个RenderFrameImpl对象。RenderFrameHostImpl对象和RenderFrameImpl对象负责在Browser进程和Render进程之间处理网页导航相关的操作。
在接下来的一篇文章分析网页内容的加载过程中,我们就可以看到,Render进程中的RenderFrameImpl对象处理其在Browser进程中的对应的RenderFrameHostImpl对象发送过来的类型为FrameMsg_Navigate的IPC消息的过程,也就是Render进程加载网页内容的过程