1、Chromium的Plugin进程启动过程分析Chromium的Plugin进程启动过程分析前面我们分析了Chromium的Render进程和GPU进程的启动过程,它们都是由Browser进程启动的。在Chromium中,还有一类进程是由Browser进程启动的,它们就是Plugin进程。顾名思义,Plugin进程是用来运行浏览器插件的。浏览器插件的作用是扩展网页功能,它们由第三方开发,安全性和稳定性都无法得到保证,因此运行在独立的进程中。本文接下来就详细分析Plugin进程的启动过程。在Chromium中,还有一种组件比较容易和浏览器插件混淆,它们称为浏览器扩展(Extension)。浏览器
2、扩展工作在浏览器层面上,用来扩展浏览器的功能,例如可以在浏览器的工具栏上增加一个按钮,并且通过浏览器提供的API实现某种功能,例如翻墙。浏览器插件工作在网页层面上,用来扩展网页的功能,例如在网页上显示一个PDF文档或者播放Flash视频。 本文要分析的是浏览器插件。在Chromium中,浏览器插件又分为两类,一类称为NPAPI插件,另一类称为PPAPI插件。NPAPI的全称是Netscape Plugin API,就是运行在Netscape Navigator浏览器上的一种插件,也就是由Netscape定义一套API接口,插件开发商通过调用这些API接口来扩展网页的功能。Netscape Na
3、vigator浏览器虽然早已离我们远去,但是NPAPI插件还顽强地活着,并且成为了一种几乎所有浏览器都支持的跨平台插件标准。因此,Chromium也对NPAPI插件进行了支持,目的是为了可以运行数以万计已经存在的NPAPI插件。 但是由于NPAPI插件定义的API接口太古老了,也太难用了,崩溃问题和安全问题也很多,无法适应现代浏览器的发展,因此Chromium搞了另外一套API接口,称为PPAPI接口,全称是Pepper Plugin API。基于PPAPI实现的插件就称为PPAPI插件。Chromium从2014年1月,逐渐放弃对NPAPI插件的支持,因此本文接下来要分析的是PPAPI插件。
4、 PPAPI插件使用了一种称为Native Client(NaCl)的技术来解决安全性问题。在Native Client技术中,我们可以使用熟悉的Native语言C/C+开发插件核心模块,以满足效率要求。这些C/C+模块通过IPC可以与网页中的JS进行通信,同时它们还可以通过PPAPI使用浏览器提供的功能,例如可以通过PPB_OpenGLES2接口使用浏览器提供的OpenGL渲染能力。为了保证安全性,Native Client在工具链层面上限制C/C+模块能够调用的本地OS提供的API接口。也就是说,PPAPI插件的C/C+模块要通过Native Client提供的编译工具进行编译,而这些编译
5、工具会检查PPAPI插件的C/C+模块调用的API,并且禁止非法API调用。这样实际上就给PPAPI插件加上了两层安全保护。第一层安全保护体现在运行时,将PPAPI插件运行在一个受限进程中,即运行在一个沙箱中。第二层安全保护体现在编译时,禁止Native代码调用非法API。 PPAPI插件有三种运行模式。第一种模式是运行在一个受限的独立进程中,即运行在沙箱中。第二种模式运行在一个非受限的独立进程中。第三种模式直接运行在Render进程中。一个PPAPI插件运行在哪一种模式取决于浏览器内部实现和浏览器命令行参数。浏览器内部实现会对某些内部PPAPI的运行模式进行Hard Code,例如PDF插件
6、。这一点可以参考ChromeContentClient类的成员函数AddPepperPlugins(定义在文件external/chromium_org/chrome/common/chrome_content_client.cc中)。此外,如果浏览器命令行参数指定了switches:kPpapiInProcess选项,则其余安装的PPAPI插件将运行在第三种模式中,否则的话,就运行在第一种模式中。这一点可以参考函数ComputePluginsFromCommandLine(定义在文件external/chromium_org/content/common/pepper_plugin_list
7、.cc中)。 本文主要关注的是PPAPI插件进程的启动过程。通过这个启动过程,我们可以了解PPAPI插件进程与Browser进程和Render进程的关系,如下所示: Render进程在解析网页时,如果遇到一个embed标签,那么就会创建一个HTMLPlugInElement对象来描述该标签,并且调用该HTMLPlugInElement对象的成员函数loadPlugin加载对应的PPAPI插件。但是Render进程没有启动PPAPI插件进程的权限,因此它就会通过之前与Browser进程建立的IPC通道向Browser进程发出一个启动PPAPI插件进程的请求。 Browser进程接收到启动PPAP
8、I插件进程的请求之后,就会启动一个PPAPI插件进程,并且创建一个PpapiPluginProcessHost对象描述该PPAPI插件进程。PPAPI插件进程启动起来之后,会在内部创建一个ChildProcess对象,用来与Browser进程中的PpapiPluginProcessHost对象建立一个IPC通道。有了这个IPC通道之后,Browser进程请求PPAPI插件进程创建另外一个UINX Socket。该UNIX Socket的Server端文件描述符保留在PPAPI进程中,Client端文件描述符则返回给Render进程。基于这个UNIX Socket的Server端和Client端
9、文件描述符,PPAPI插件进程和Render进程将分别创建一个HostDispatcher对象和一个PluginDispatcher对象,构成一个Plugin通道。以后PPAPI插件进程和Render进程就通过该Plugin通道进行通信。 接下来,我们就从HTMLPlugInElement类的成员函数loadPlugin开始,分析PPAPI插件进程的启动过程,如下所示:cpp view plain copybool HTMLPlugInElement:loadPlugin(const KURL& url, const String& mimeType, const Vector& paramN
10、ames, const Vector& paramValues, bool useFallback, bool requireRenderer) LocalFrame* frame = document().frame(); . RefPtr widget = m_persistedPluginWidget; if (!widget) . widget = frame-loader().client()-createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy); . return true;
11、 这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp中。 HTMLPlugInElement类的成员变量m_persistedPluginWidget的值等于NULL的时候,说明当前正在处理的HTMLPlugInElement对象描述的插件实例还没有创建,或者需要重新创建。在这种情况下,HTMLPlugInElement类的成员函数loadPlugin就会首先获得当前网页的Document对象,然后通过该Document对象获得一个LocalFrame对象,这个Loca
12、lFrame对象描述的是当前网页的页框。每一个LocalFrame对象都有一个关联的FrameLoader对象,这个FrameLoader对象用来加载当前网页的页框。每一个FrameLoader对象又关联有一个FrameLoaderClientImpl对象,通过这个FrameLoaderClientImpl对象,WebKit可以用来请求执行平台相关的操作,例如,HTMLPlugInElement类的成员函数loadPlugin就通过调用它的成员函数createPlugin创建一个插件实例。 FrameLoaderClientImpl类的成员函数createPlugin的实现如下所示:cpp v
13、iew plain copyPassRefPtr FrameLoaderClientImpl:createPlugin( HTMLPlugInElement* element, const KURL& url, const Vector& paramNames, const Vector& paramValues, const String& mimeType, bool loadManually, DetachedPluginPolicy policy) . WebPlugin* webPlugin = m_webFrame-client()-createPlugin(m_webFrame,
14、 params); . / The container takes ownership of the WebPlugin. RefPtr container = WebPluginContainerImpl:create(element, webPlugin); if (!webPlugin-initialize(container.get() return nullptr; . return container; 这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp中。 F
15、rameLoaderClientImpl类的成员变量m_webFrame指向的是一个WebLocalFrameImpl对象,该WebLocalFrameImpl对象用来描述当前处理的网页内容是在当前进程进行渲染的,并且通过该WebLocalFrameImpl对象的成员函数client可以获得一个WebFrameClient对象,该WebFrameClient对象是用WebKit用来与上层使用者,即Chromium进行交互的,例如WebKit就通过调用它的成员函数createPlugin创建一个插件实例。创建出来的插件实例在WebKit中用一个WebPlugin对象描述,这个WebPlugin对
16、象最后封装在一个WebPluginContainerImpl对象中返回给调用者。 创建出来的WebPlugin对象在返回给调用者之前,会先调用其成员函数initailize进行初始化。对于NPAPI插件来说,初始化工作包括请求Browser进程启动一个插件进程,但是对于PPAPI插件来说,插件进程是在上述的插件实例创建过程中请求Browser进程启动的,接下来我们就会到这一点。由于NPAPI插件进程和PPAPI插件进程的启动过程类似,因此我们只关注PPAPI插件进程的启动过程。 Chromium将上述WebFrameClient对象指定为一个RenderFrameImpl对象,这个Render
17、FrameImpl对象是Chromium用来描述当前正在处理的一个网页的,它的成员函数createPlugin的实现如下所示:cpp view plain copyblink:WebPlugin* RenderFrameImpl:createPlugin( blink:WebLocalFrame* frame, const blink:WebPluginParams& params) DCHECK_EQ(frame_, frame); blink:WebPlugin* plugin = NULL; if (GetContentClient()-renderer()-OverrideCreate
18、Plugin( this, frame, params, &plugin) return plugin; if (base:UTF16ToASCII(params.mimeType) = kBrowserPluginMimeType) return render_view_-GetBrowserPluginManager()-CreateBrowserPlugin( render_view_.get(), frame, false); #if defined(ENABLE_PLUGINS) WebPluginInfo info; std:string mime_type; bool found
19、 = false; Send(new FrameHostMsg_GetPluginInfo( routing_id_, params.url, frame-top()-document().url(), params.mimeType.utf8(), &found, &info, &mime_type); if (!found) return NULL; if (info.type = content:WebPluginInfo:PLUGIN_TYPE_BROWSER_PLUGIN) return render_view_-GetBrowserPluginManager()-CreateBro
20、wserPlugin( render_view_.get(), frame, true); WebPluginParams params_to_use = params; params_to_use.mimeType = WebString:fromUTF8(mime_type); return CreatePlugin(frame, info, params_to_use); #else return NULL; #endif / defined(ENABLE_PLUGINS) 这个函数定义在文件external/chromium_org/content/renderer/render_fr
21、ame_impl.cc中。 Chromium是多层架构的,从上到下大概分为浏览器层、Content层和WebKit层。其中,WebKit层用来解析网页,Content层位于WebKit层之上,并且提供多进程架构。这样我们就可以基于Content层,实现一个浏览器。例如,Chrome就是基于Chromium的Content层提供的API实现的。这意味着我们也可以基于Chromium的Content层实现一个与Chrome类似的浏览器。 Chromium为了能够让浏览层定制一些Content层的行为,允许浏览器层设置一个Content Client到Content层来。Chromium在执行某些操
22、作时,就会首先通过函数GetContentClient获得上述Content Client,然后再通过它询问浏览层是否需要接管该操作。例如,在我们这个场景中,RenderFrameImpl类的成员函数createPlugin会先询问浏览器层是否需要接管创建插件实例的操作。如果浏览器层需要接管,那么它就负责根据指定的参数创建一个相应的插件实现,这时候RenderFrameImpl类的成员函数createPlugin就什么也不用做。 浏览器层设置到Content层的Content Client包含有两个部分,一部分称为Browser端,另一部分称为Renderer端。其中,Browser端运行在C
23、hromium的Browser进程中,负责接管Chromium的Browser进程的某些操作,而Renderer端运行在Chromium的Render进程中,负责接管Chromium的Render进程的某些操作。从这里我们可以看到,RenderFrameImpl类的成员函数createPlugin是通过调用浏览器层设置到Content层的Content Client的Renderer端的成员函数OverrideCreatePlugin来询问浏览层是否需要接管创建插件实例的操作的。 假设浏览层不接管创建插件实例的操作,那么RenderFrameImpl类的成员函数createPlugin接下来判
24、断embed标签的type属性值是否等于kBrowserPluginMimeType,即“application/browser-plugin”。如果等于的话,那么就意味着要创建一个Browser Plugin,这是通过调用一个Browser Plugin Manager的成员函数CreateBrowserPlugin创建的。Browser Plugin的作用类似于HTML中的iframe,是用来在当前网页中内嵌另外一个网页的,更详细的信息可以参考这里:。注意,Browser Plugin是由Chromium提供实现支持的,而不是某一个Plugin。 假设embed标签的type属性值不等于k
25、BrowserPluginMimeType,那么RenderFrameImpl类的成员函数createPlugin接下来向Browser进程发送一个类型为FrameHostMsg_GetPluginInfo的IPC消息,用来获取要创建的插件实例的更详细的信息,实际上就是通过embed标签的type属性值在已安装的插件列表找到一个对应的插件。 如果没有找到与embed标签的type属性值对应的插件,那么RenderFrameImpl类的成员函数createPlugin就什么也不做就返回了。另一方面,如果找到的插件的类型为content:WebPluginInfo:PLUGIN_TYPE_BROW
26、SER_PLUGIN,那么同样意味要创建一个Browser Plugin,因此这时候也会调用Browser Plugin Manager的成员函数CreateBrowserPlugin创建一个Browser Plugin。 假设找到了一个对应的插件,并且该插件的类型不是content:WebPluginInfo:PLUGIN_TYPE_BROWSER_PLUGIN,那么RenderFrameImpl类的成员函数createPlugin接下来就会调用另外一个成员函数CreatePlugin创建一个插件实现,如下所示:cpp view plain copyblink:WebPlugin* Rend
27、erFrameImpl:CreatePlugin( blink:WebFrame* frame, const WebPluginInfo& info, const blink:WebPluginParams& params) DCHECK_EQ(frame_, frame); #if defined(ENABLE_PLUGINS) bool pepper_plugin_was_registered = false; scoped_refptr pepper_module(PluginModule:Create( this, info, &pepper_plugin_was_registered
28、); if (pepper_plugin_was_registered) if (pepper_module.get() return new PepperWebPluginImpl(pepper_module.get(), params, this); . / TODO(jam): change to take RenderFrame. return new WebPluginImpl(frame, params, info.path, render_view_, this); #endif #else return NULL; #endif 这个函数定义在文件external/chromi
29、um_org/content/renderer/render_frame_impl.cc中。 RenderFrameImpl类的成员函数CreatePlugin首先是调用PluginModule类的成员函数Create查询要创建的插件是否是一个PPAPI插件。如果是的话,那么PluginModule类的成员函数Create会将输出参数pepper_plugin_was_registered的值设置为true,并且会返回一个PluginModule对象,用来描述PPAPI插件所在的模块,最后这个PluginModule对象将会被封装在一个PepperWebPluginImpl对象,并且返回给调用
30、者。这意味着PPAPI插件是通过PepperWebPluginImpl类来描述的。 另一方面,如果PluginModule类的成员函数Create将输出参数pepper_plugin_was_registered的值设置为false,那么就意味着要创建的是一个NPAPI插件。NPAPI插件通过WebPluginImpl类来描述,因此在这种情况下,RenderFrameImpl类的成员函数CreatePlugin就创建一个WebPluginImpl对象返回给调用者。 接下来我们继续分析PluginModule类的成员函数Create的实现,如下所示:cpp view plain copyscop
31、ed_refptr PluginModule:Create( RenderFrameImpl* render_frame, const WebPluginInfo& webplugin_info, bool* pepper_plugin_was_registered) *pepper_plugin_was_registered = true; / See if a module has already been loaded for this plugin. base:FilePath path(webplugin_info.path); scoped_refptr module = PepperPluginRegistry:GetInstance()-GetLiveModule(path);
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1