插件机制(NPAPI Plugin)

优质
小牛编辑
144浏览
2023-12-01

Chromium中的NPAPI插件(plugin)来源于mozilla的插件机制。因为它被广泛的应用,很多插件厂商或者开发者基于它编写了数以万计的插件,因而chromium对它也提供了支持,不过chromium有自己独特的插件架构,后面我们会详细介绍。

NPAPI提供两组接口,一类以NPP打头,由插件来实现,被浏览器调用,主要包括一些插件创建,初始化,关闭,销毁,信息查询及事件处理,数据流,窗口设置,URL等;另一类以NPN打头,由浏览器来实现,被插件所调用,主要包括图形绘制,数据流处理,浏览器信息查询,内存分配和释放,浏览器的插件设置,URL等。

原始的NPAPI的接口使用起来不是很方便,因而有贡献者对其进行了封装以利于其使用。一个比较著名的开源项目是Firebreath。它将原始的C风格的NPAPI进行封装成C++风格的接口,非常方便用户使用,而且有针对Windows和X window的移植,用户无需对底层特别了解。特别的是,Firebreath也有对ActiveX的封装,因而对于现在主流的两种插件接口,你都可以基于Firebreath的接口进行编程,极大地方便了开发者。详情请参考Firebreath主页http://www.firebreath.org/display/documentation/FireBreath+Home

架构

因为chromium的安全模型,renderer进程没有访问除I/O读写等之外的权限,因而插件需要有自己的进程,这就是插件的out-of-process模型。下图给出chromium中插件的架构和进程模型。

enter image description here

每一种类型的plugin只有一个进程,这就是说,如果有两个或者多个renderer进程同时使用同一个插件,那么该插件会共享同一个进程。因为多个renderer进程共享同一种的plugin进程,那么plugin进程如何为它们服务呢?答案是为每个插件使用点在plugin进程中创建一个插件实例(PluginInstance).

值得注意的是,plugin进程是由browser进程来负责创建和销毁, 而不是renderer进程。 原因在于renderer进程没有创建的权限,而且plugin进程由browser进程来统一管理也更方便。 当plugin进程创建成功时,browser进程会返回IPCchannel handle用于创建和plugin进程通讯的PluginChannelHost. 那它什么时候被销毁呢?当没有任何插件实例并且空闲一段事件后,它才会被销毁,这样做的好处是避免频繁的创建和销毁plugin进程。

下图描述了browser和plugin进程间的通讯机制及其所涉及的相关的模块(类)。Browser进程通过PluginProcessHost发送消息调用Plugin进程的函数,响应动作由PluginThread完成。而Plugin进程则是通过WebPluginProxy发送消息调用browser的函数,响应动作有PluginProcessHost完成。

enter image description here

Browser和Plugin仅有较少的消息传递,用于创建等管理工作。主要的部分在renderer进程和plugin进程之间,机制也相对更复杂一些。HTMLPluginElement会包含一个WebPluginContainerImpl,而它包含一个WebPluginImpl,对plugin的调用有WebPluginDelegateProxy负责中转。在Plugin进程中,由WebPluginDelegateStub处理所有renderer过来的请求,并由WebPluginDelegateImpl调用创建好的PluginInstance对象。PluginInstance最终调用PluginLib读取的插件的函数入口地址,最终完成对插件实现的调用。而对插件实现中对NPN开头函数的调用,则是通过PluginHost来完成。

PluginHost主要负责实现NPN开头的函数,如前面所描述,这些函数被plugin进程所调用。可以在plugin和renderer进程被调用。当在plugin进程调用这些函数时,chromium会覆盖PluginHost的部分函数,而这些新的callback函数会调用NPObjectProxy来通过IPC发送请求到renderer进程。

PluginInstance实现了NPP开头的函数,被render所调用(webpluginImpl通过webplugindelegateImpl来调用),PluginInstance通过PluginLib获得了插件库的函数地址,从而把实际的调用桥接到具体的插件中。

具体的见下图所示,主要结构来源于chromium的官网,略有修改。

enter image description here

对于NPObject相关的函数调用, 有专门的类来处理。NPObject的调用或者访问是双向的(renderer进程<->plugin进程),他们的具体实现是通过NPObjectProxy和NPObjectStub来完成。 NPObjectProxy接受来自对方的访问请求,转发给NPObjectStub,最后NPObjectStub调用真正的NPObject并返回结果。见下图所示。

enter image description here

下面示例给出一个插件如何被renderer进程触发创建的过程。

enter image description here

当页面中包含一个“embed”或者“object”元素,renderer进程会创建一个HTMLEmbedElement元素,当该元素被访问是,会触发创建相应的插件。HTMLEmbedElement会请求创建自己对应的RenderWidget(WebPluginContainerImpl),进而创建WebPluginImpl和WebPluginDelegateProxy。WebPluginDelegateProxy会发送消息来创建Plugin进程。Plugin进程被browser进程创建后,会响应renderer的请求来创建PluginInstance并初始化它。这样它们之间的联系就建立好了。

Window和windowless插件

可以通过设置”embed”或者”object”元素的属性。两者的区别主要在于绘图的方式不同。Window插件由renderer进程提供一个窗口(window), 插件直接在该窗口上进行绘制;而windowless插件则不同,插件将绘制的结构(Pixmap),通过共享内存方式(Transport DIB)传递给renderer进程,renderer绘制该内容到自己内部的存储结构(backing store)上。好处是,可以把插件绘制的结构和网页上的其他内容做各种形式的合成。但是,从上面不能看出,一般来讲,window模式的性能是要高于windowless的。

相关目录和文件

third_party/WebKit/Source/WebKit/chromium/public/webplugin.h:
         定义Webkit::WebPlugin接口,用于创建和销毁插件,及传送事件给插件
content/common/plugin_messages.h:
         定义plugin进程与renderer进程和browser进程间的消息结构体,包括五类:
PluginProcessMsg开头的消息- browser进程发给plugin进程
PluginProcessHostMsg开头的消息- plugin进程发给browser进程
PluginMsg开头的消息- renderer进程发给plugin进程
PluginHostMsg开头的消息- plugin进程发给renderer进程
NPObjectMsg开头的消息- plugin进程与render进程相互编码和解码NPObject
content/common/npobject_stub.(h&cc):
                   类NPObjectStub的定义和实现
content/common/npobject_Proxy.(h&cc):
                   类NPObjectProxy的定义和实现
content/plugin:
         该目录用于存放Plugin进程使用的IPC和WebPlugin相关的函数和类
content/plugin/webplugin_proxy.(h&cc):
                   实现WebKit::npapi::WebPlugin接口,把插件的调用通过IPC发送给renderer进程
content/plugin/webplugin_delegate_stub.(h&cc):
                   把WebPluginDelegateProxy的消息转换为对WebPluginDelegateImpl的调用
content/plugin/plugin_channel.(h&cc):
                   定义Plugin进程与renderer进程通信的通道
webkit/plugins/npapi/:
         该目录用于存放Plugin进程使用的对WebKit接口实现和插件库处理的相关的函数和类
webkit/plugins/npapi/webplugin.h:
                   定义WebPlugin接口,用于plugin端同web frame和webcore对象的交互
webkit/plugins/npapi/webplugin_impl.(h&cc):
                   实现WebKit::WebPlugin和WebPlugin接口,通过WebPluginPageDelegate来创建WebPluginDelegate
webkit/plugins/npapi/plugin_instance.(h&cc)
                   PluginInstance类的接口和实现,代表一个Plugin实例,对应于renderer进程的一个请求创建的plugin实例
webkit/plugins/npapi/webplugin_delegate.h:
                   定义WebPlugin代理,用来分离具体的插件的实现方式,例如可以使来实现进程内或者跨进程的插件架构,对WebPlugin来说,具体架构是透明的
webkit/plugins/npapi/webplugin_delegate_impl.(h&cc):
                   响应renderer进程实现对PluginInstance的调用请求,有gtk,win和aura三种不同的实现
webkit/plugins/npapi/plugin_host.(h&cc):
                   实现NPN开头的函数,在plugin进程和renderer进程有不同的实现
webkit/plugins/npapi/plugin_lib.(h&cc):
                   PluginLib用来管理实际插件库的生命周期
content/renderer/webplugin_delegate_proxy.(h&cc)
                   定义和实现WebPluginDelegateProxy类,桥接所有来自于WebPlugin的请求到WebPluginDelegateImpl
content/browser/plugin_process_host.(h&cc):
                   类PluginProcessHost的定义和实现,用于Browser进程与plugin进程的交互
content/browser/plugin_service_impl.(h&cc):
                   类PluginServiceImpl的定义和实现,用于响应Renderer进程创建插件请求及其他一些插件管理工作

参考文档

  1. http://www.chromium.org/developers/design-documents/plugin-architecture