Chromium的Plugin进程启动过程分析.docx
《Chromium的Plugin进程启动过程分析.docx》由会员分享,可在线阅读,更多相关《Chromium的Plugin进程启动过程分析.docx(41页珍藏版)》请在冰豆网上搜索。
Chromium的Plugin进程启动过程分析
Chromium的Plugin进程启动过程分析
前面我们分析了Chromium的Render进程和GPU进程的启动过程,它们都是由Browser进程启动的。
在Chromium中,还有一类进程是由Browser进程启动的,它们就是Plugin进程。
顾名思义,Plugin进程是用来运行浏览器插件的。
浏览器插件的作用是扩展网页功能,它们由第三方开发,安全性和稳定性都无法得到保证,因此运行在独立的进程中。
本文接下来就详细分析Plugin进程的启动过程。
在Chromium中,还有一种组件比较容易和浏览器插件混淆,它们称为浏览器扩展(Extension)。
浏览器扩展工作在浏览器层面上,用来扩展浏览器的功能,例如可以在浏览器的工具栏上增加一个按钮,并且通过浏览器提供的API实现某种功能,例如翻墙。
浏览器插件工作在网页层面上,用来扩展网页的功能,例如在网页上显示一个PDF文档或者播放Flash视频。
本文要分析的是浏览器插件。
在Chromium中,浏览器插件又分为两类,一类称为NPAPI插件,另一类称为PPAPI插件。
NPAPI的全称是NetscapePluginAPI,就是运行在NetscapeNavigator浏览器上的一种插件,也就是由Netscape定义一套API接口,插件开发商通过调用这些API接口来扩展网页的功能。
NetscapeNavigator浏览器虽然早已离我们远去,但是NPAPI插件还顽强地活着,并且成为了一种几乎所有浏览器都支持的跨平台插件标准。
因此,Chromium也对NPAPI插件进行了支持,目的是为了可以运行数以万计已经存在的NPAPI插件。
但是由于NPAPI插件定义的API接口太古老了,也太难用了,崩溃问题和安全问题也很多,无法适应现代浏览器的发展,因此Chromium搞了另外一套API接口,称为PPAPI接口,全称是PepperPluginAPI。
基于PPAPI实现的插件就称为PPAPI插件。
Chromium从2014年1月,逐渐放弃对NPAPI插件的支持,因此本文接下来要分析的是PPAPI插件。
PPAPI插件使用了一种称为NativeClient(NaCl)的技术来解决安全性问题。
在NativeClient技术中,我们可以使用熟悉的Native语言C/C++开发插件核心模块,以满足效率要求。
这些C/C++模块通过IPC可以与网页中的JS进行通信,同时它们还可以通过PPAPI使用浏览器提供的功能,例如可以通过PPB_OpenGLES2接口使用浏览器提供的OpenGL渲染能力。
为了保证安全性,NativeClient在工具链层面上限制C/C++模块能够调用的本地OS提供的API接口。
也就是说,PPAPI插件的C/C++模块要通过NativeClient提供的编译工具进行编译,而这些编译工具会检查PPAPI插件的C/C++模块调用的API,并且禁止非法API调用。
这样实际上就给PPAPI插件加上了两层安全保护。
第一层安全保护体现在运行时,将PPAPI插件运行在一个受限进程中,即运行在一个沙箱中。
第二层安全保护体现在编译时,禁止Native代码调用非法API。
PPAPI插件有三种运行模式。
第一种模式是运行在一个受限的独立进程中,即运行在沙箱中。
第二种模式运行在一个非受限的独立进程中。
第三种模式直接运行在Render进程中。
一个PPAPI插件运行在哪一种模式取决于浏览器内部实现和浏览器命令行参数。
浏览器内部实现会对某些内部PPAPI的运行模式进行HardCode,例如PDF插件。
这一点可以参考ChromeContentClient类的成员函数AddPepperPlugins(定义在文件external/chromium_org/chrome/common/chrome_content_client.cc中)。
此外,如果浏览器命令行参数指定了switches:
:
kPpapiInProcess选项,则其余安装的PPAPI插件将运行在第三种模式中,否则的话,就运行在第一种模式中。
这一点可以参考函数ComputePluginsFromCommandLine(定义在文件external/chromium_org/content/common/pepper_plugin_list.cc中)。
本文主要关注的是PPAPI插件进程的启动过程。
通过这个启动过程,我们可以了解PPAPI插件进程与Browser进程和Render进程的关系,如下所示:
Render进程在解析网页时,如果遇到一个embed标签,那么就会创建一个HTMLPlugInElement对象来描述该标签,并且调用该HTMLPlugInElement对象的成员函数loadPlugin加载对应的PPAPI插件。
但是Render进程没有启动PPAPI插件进程的权限,因此它就会通过之前与Browser进程建立的IPC通道向Browser进程发出一个启动PPAPI插件进程的请求。
Browser进程接收到启动PPAPI插件进程的请求之后,就会启动一个PPAPI插件进程,并且创建一个PpapiPluginProcessHost对象描述该PPAPI插件进程。
PPAPI插件进程启动起来之后,会在内部创建一个ChildProcess对象,用来与Browser进程中的PpapiPluginProcessHost对象建立一个IPC通道。
有了这个IPC通道之后,Browser进程请求PPAPI插件进程创建另外一个UINXSocket。
该UNIXSocket的Server端文件描述符保留在PPAPI进程中,Client端文件描述符则返回给Render进程。
基于这个UNIXSocket的Server端和Client端文件描述符,PPAPI插件进程和Render进程将分别创建一个HostDispatcher对象和一个PluginDispatcher对象,构成一个Plugin通道。
以后PPAPI插件进程和Render进程就通过该Plugin通道进行通信。
接下来,我们就从HTMLPlugInElement类的成员函数loadPlugin开始,分析PPAPI插件进程的启动过程,如下所示:
[cpp]viewplaincopy
boolHTMLPlugInElement:
:
loadPlugin(constKURL&url,constString&mimeType,constVector¶mNames,constVector¶mValues,booluseFallback,boolrequireRenderer)
{
LocalFrame*frame=document().frame();
......
RefPtrwidget=m_persistedPluginWidget;
if(!
widget){
......
widget=frame->loader().client()->createPlugin(this,url,paramNames,paramValues,mimeType,loadManually,policy);
}
......
returntrue;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp中。
HTMLPlugInElement类的成员变量m_persistedPluginWidget的值等于NULL的时候,说明当前正在处理的HTMLPlugInElement对象描述的插件实例还没有创建,或者需要重新创建。
在这种情况下,HTMLPlugInElement类的成员函数loadPlugin就会首先获得当前网页的Document对象,然后通过该Document对象获得一个LocalFrame对象,这个LocalFrame对象描述的是当前网页的页框。
每一个LocalFrame对象都有一个关联的FrameLoader对象,这个FrameLoader对象用来加载当前网页的页框。
每一个FrameLoader对象又关联有一个FrameLoaderClientImpl对象,通过这个FrameLoaderClientImpl对象,WebKit可以用来请求执行平台相关的操作,例如,HTMLPlugInElement类的成员函数loadPlugin就通过调用它的成员函数createPlugin创建一个插件实例。
FrameLoaderClientImpl类的成员函数createPlugin的实现如下所示:
[cpp]viewplaincopy
PassRefPtrFrameLoaderClientImpl:
:
createPlugin(
HTMLPlugInElement*element,
constKURL&url,
constVector¶mNames,
constVector¶mValues,
constString&mimeType,
boolloadManually,
DetachedPluginPolicypolicy)
{
......
WebPlugin*webPlugin=m_webFrame->client()->createPlugin(m_webFrame,params);
......
//ThecontainertakesownershipoftheWebPlugin.
RefPtrcontainer=
WebPluginContainerImpl:
:
create(element,webPlugin);
if(!
webPlugin->initialize(container.get()))
returnnullptr;
......
returncontainer;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp中。
FrameLoaderClientImpl类的成员变量m_webFrame指向的是一个WebLocalFrameImpl对象,该WebLocalFrameImpl对象用来描述当前处理的网页内容是在当前进程进行渲染的,并且通过该WebLocalFrameImpl对象的成员函数client可以获得一个WebFrameClient对象,该WebFrameClient对象是用WebKit用来与上层使用者,即Chromium进行交互的,例如WebKit就通过调用它的成员函数createPlugin创建一个插件实例。
创建出来的插件实例在WebKit中用一个WebPlugin对象描述,这个WebPlugin对象最后封装在一个WebPluginContainerImpl对象中返回给调用者。
创建出来的WebPlugin对象在返回给调用者之前,会先调用其成员函数initailize进行初始化。
对于NPAPI插件来说,初始化工作包括请求Browser进程启动一个插件进程,但是对于PPAPI插件来说,插件进程是在上述的插件实例创建过程中请求Browser进程启动的,接下来我们就会到这一点。
由于NPAPI插件进程和PPAPI插件进程的启动过程类似,因此我们只关注PPAPI插件进程的启动过程。
Chromium将上述WebFrameClient对象指定为一个RenderFrameImpl对象,这个RenderFrameImpl对象是Chromium用来描述当前正在处理的一个网页的,它的成员函数createPlugin的实现如下所示:
[cpp]viewplaincopy
blink:
:
WebPlugin*RenderFrameImpl:
:
createPlugin(
blink:
:
WebLocalFrame*frame,
constblink:
:
WebPluginParams¶ms){
DCHECK_EQ(frame_,frame);
blink:
:
WebPlugin*plugin=NULL;
if(GetContentClient()->renderer()->OverrideCreatePlugin(
this,frame,params,&plugin)){
returnplugin;
}
if(base:
:
UTF16ToASCII(params.mimeType)==kBrowserPluginMimeType){
returnrender_view_->GetBrowserPluginManager()->CreateBrowserPlugin(
render_view_.get(),frame,false);
}
#ifdefined(ENABLE_PLUGINS)
WebPluginInfoinfo;
std:
:
stringmime_type;
boolfound=false;
Send(newFrameHostMsg_GetPluginInfo(
routing_id_,params.url,frame->top()->document().url(),
params.mimeType.utf8(),&found,&info,&mime_type));
if(!
found)
returnNULL;
if(info.type==content:
:
WebPluginInfo:
:
PLUGIN_TYPE_BROWSER_PLUGIN){
returnrender_view_->GetBrowserPluginManager()->CreateBrowserPlugin(
render_view_.get(),frame,true);
}
WebPluginParamsparams_to_use=params;
params_to_use.mimeType=WebString:
:
fromUTF8(mime_type);
returnCreatePlugin(frame,info,params_to_use);
#else
returnNULL;
#endif//defined(ENABLE_PLUGINS)
}
这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
Chromium是多层架构的,从上到下大概分为浏览器层、Content层和WebKit层。
其中,WebKit层用来解析网页,Content层位于WebKit层之上,并且提供多进程架构。
这样我们就可以基于Content层,实现一个浏览器。
例如,Chrome就是基于Chromium的Content层提供的API实现的。
这意味着我们也可以基于Chromium的Content层实现一个与Chrome类似的浏览器。
Chromium为了能够让浏览层定制一些Content层的行为,允许浏览器层设置一个ContentClient到Content层来。
Chromium在执行某些操作时,就会首先通过函数GetContentClient获得上述ContentClient,然后再通过它询问浏览层是否需要接管该操作。
例如,在我们这个场景中,RenderFrameImpl类的成员函数createPlugin会先询问浏览器层是否需要接管创建插件实例的操作。
如果浏览器层需要接管,那么它就负责根据指定的参数创建一个相应的插件实现,这时候RenderFrameImpl类的成员函数createPlugin就什么也不用做。
浏览器层设置到Content层的ContentClient包含有两个部分,一部分称为Browser端,另一部分称为Renderer端。
其中,Browser端运行在Chromium的Browser进程中,负责接管Chromium的Browser进程的某些操作,而Renderer端运行在Chromium的Render进程中,负责接管Chromium的Render进程的某些操作。
从这里我们可以看到,RenderFrameImpl类的成员函数createPlugin是通过调用浏览器层设置到Content层的ContentClient的Renderer端的成员函数OverrideCreatePlugin来询问浏览层是否需要接管创建插件实例的操作的。
假设浏览层不接管创建插件实例的操作,那么RenderFrameImpl类的成员函数createPlugin接下来判断embed标签的type属性值是否等于kBrowserPluginMimeType,即“application/browser-plugin”。
如果等于的话,那么就意味着要创建一个BrowserPlugin,这是通过调用一个BrowserPluginManager的成员函数CreateBrowserPlugin创建的。
BrowserPlugin的作用类似于HTML中的iframe,是用来在当前网页中内嵌另外一个网页的,更详细的信息可以参考这里:
。
注意,BrowserPlugin是由Chromium提供实现支持的,而不是某一个Plugin。
假设embed标签的type属性值不等于kBrowserPluginMimeType,那么RenderFrameImpl类的成员函数createPlugin接下来向Browser进程发送一个类型为FrameHostMsg_GetPluginInfo的IPC消息,用来获取要创建的插件实例的更详细的信息,实际上就是通过embed标签的type属性值在已安装的插件列表找到一个对应的插件。
如果没有找到与embed标签的type属性值对应的插件,那么RenderFrameImpl类的成员函数createPlugin就什么也不做就返回了。
另一方面,如果找到的插件的类型为content:
:
WebPluginInfo:
:
PLUGIN_TYPE_BROWSER_PLUGIN,那么同样意味要创建一个BrowserPlugin,因此这时候也会调用BrowserPluginManager的成员函数CreateBrowserPlugin创建一个BrowserPlugin。
假设找到了一个对应的插件,并且该插件的类型不是content:
:
WebPluginInfo:
:
PLUGIN_TYPE_BROWSER_PLUGIN,那么RenderFrameImpl类的成员函数createPlugin接下来就会调用另外一个成员函数CreatePlugin创建一个插件实现,如下所示:
[cpp]viewplaincopy
blink:
:
WebPlugin*RenderFrameImpl:
:
CreatePlugin(
blink:
:
WebFrame*frame,
constWebPluginInfo&info,
constblink:
:
WebPluginParams¶ms){
DCHECK_EQ(frame_,frame);
#ifdefined(ENABLE_PLUGINS)
boolpepper_plugin_was_registered=false;
scoped_refptrpepper_module(PluginModule:
:
Create(
this,info,&pepper_plugin_was_registered));
if(pepper_plugin_was_registered){
if(pepper_module.get()){
returnnewPepperWebPluginImpl(pepper_module.get(),params,this);
}
}
......
//TODO(jam):
changetotakeRenderFrame.
returnnewWebPluginImpl(frame,params,info.path,render_view_,this);
#endif
#else
returnNULL;
#endif
}
这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
RenderFrameImpl类的成员函数CreatePlugin首先是调用PluginModule类的成员函数Create查询要创建的插件是否是一个PPAPI插件。
如果是的话,那么PluginModule类的成员函数Create会将输出参数pepper_plugin_was_registered的值设置为true,并且会返回一个PluginModule对象,用来描述PPAPI插件所在的模块,最后这个PluginModule对象将会被封装在一个PepperWebPluginImpl对象,并且返回给调用者。
这意味着PPAPI插件是通过PepperWebPluginImpl类来描述的。
另一方面,如果PluginModule类的成员函数Create将输出参数pepper_plugin_was_registered的值设置为false,那么就意味着要创建的是一个NPAPI插件。
NPAPI插件通过WebPluginImpl类来描述,因此在这种情况下,RenderFrameImpl类的成员函数CreatePlugin就创建一个WebPluginImpl对象返回给调用者。
接下来我们继续分析PluginModule类的成员函数Create的实现,如下所示:
[cpp]viewplaincopy
scoped_refptrPluginModule:
:
Create(
RenderFrameImpl*render_frame,
constWebPluginInfo&webplugin_info,
bool*pepper_plugin_was_registered){
*pepper_plugin_was_registered=true;
//Seeifamodulehasalreadybeenloadedforthisplugin.
base:
:
FilePathpath(webplugin_info.path);
scoped_refptrmodule=
PepperPluginRegistry:
:
GetInstance()->GetLiveModule(path);