2008年7月28日星期一

浅谈Gecko关键部分之九Plugin

Plugin作为一种对浏览器内核扩充的方式,为丰富浏览器应用提供了更加广阔的空间,如大家熟知的flash、java applet、window media player、vlc、adobe pdf等插件已经在人们的不知不觉中走到人们的生活当中,那么究竟什么是plugin?为firefox写的plugin能在其他浏览器中使用吗?如何为浏览器写plugin?浏览器实现plugin的机制及其基本原理如何?等等这些问题一直困扰在心头,现就这几方面来了解了解Gecko内核中的plugin实现等。

一、什么是plugin?
大家以前应该接触过一些关于plugin插件的概念,如eclipse就提供了很好的插件机制,以扩展ide的功能,apache也提供了丰富的插件机制,以丰富http server的处理请求。他们都有一个共同的特点就是以独立动态库的形式存在,动态库提供的输出接口应该满足主程序所规定的一些要求,以向主程序描述自身的特性及主要功能;而主程序也提供一组公共函数接口以供动态库调用,这样协同作业以完成任务。

在Gecko内核中plugin通过扩展html标签embed、object的MIMEtype类型的方式来拓展显示在一个页面中指定区域的内容。如大家熟知的application/x-shockwave-flash、application/x-java-applet、application/x-vlc-plugin等等,每一个plugin须说明自身所能支持的mime类型。一旦页面中遇到指定标签embed或object的mime类型,gecko内核就会搜索它目前所能支持的mime类型及对应的plugin,然后调用plugin中输出的接口由plugin来完成指定区域内的内容显示。

在Gecko内核中,就扩展的方向及方式的不同,plugin与xpcom拥有本质上的差别,plugin以扩展页面embed/object标签 mime类型的方式存在,往往与页面布局渲染相关,而xpcom是以扩展不同的组件种类,以实现不同内部功能的方式存在,它往往有更大的扩展空间包如字符串管理、线程管理、p2p传输管理等。他们的应用场景有本质上的差别,一般说来plugin相对内核而言更加独立如可以有独立的线程管理、内存管理、渲染方式等,而xpcom往往是内核中的一个组成部分。

Gecko内核plugin机制的实现接口Netscape Plugin Application Programming Interface
简称为NPAPI,它提供了相当完备的跨平台的接口框架,其中包括对Stream、Security、Scripting等方面的支持,目前包括Firefox、Opera、Safari、部分IE等浏览器都支持符合NPAPI接口标准的插件来扩展浏览器的功能。

二、如何编写plugin?
为了能编写plugin,首先需要了解其主要API,下面四个函数接口往往需要由plugin实现并export出去,可由外部即浏览器内核调用。
NPError WINAPI NP_Initialize(NPNetscapeFuncs* pFuncs);
当该plugin动态库第一次加载后浏览器内核会调用该方法,其中参数为当前内核提供给plugin调用的函数接口,plugin往往会将这些接口保存下来,以待以后使用;

NPError WINAPI NP_GetEntryPoints(NPPluginFuncs* pFuncs);
浏览器内核初始plugin动态库后,会调用该方法以获取该plugin实现的接口函数,内核会根据具体情况的不同来调用该plugin提供的不同接口函数;

NPError WINAPI NP_Shutdown();
当浏览器内核需要释放该plugin动态库时会调用该方法以告诉plugin其将会被释放;

char* NP_GetMIMEDescription(void);
浏览器内核通过该方法可获得该plugin支持的MIME类型,以便在解析页面embed/object标签时匹配其Type是否由哪个plugins来支持;

typedef struct _NPPluginFuncs {
uint16 size;
uint16 version;
NPP_NewUPP newp;
NPP_DestroyUPP destroy;
NPP_SetWindowUPP setwindow;
NPP_NewStreamUPP newstream;
NPP_DestroyStreamUPP destroystream;
NPP_StreamAsFileUPP asfile;
NPP_WriteReadyUPP writeready;
NPP_WriteUPP write;
NPP_PrintUPP print;
NPP_HandleEventUPP event;
NPP_URLNotifyUPP urlnotify;
JRIGlobalRef javaClass;
NPP_GetValueUPP getvalue;
NPP_SetValueUPP setvalue;
} NPPluginFuncs;

typedef struct _NPNetscapeFuncs {
uint16 size;
uint16 version;
NPN_GetURLUPP geturl;
NPN_PostURLUPP posturl;
NPN_RequestReadUPP requestread;
NPN_NewStreamUPP newstream;
NPN_WriteUPP write;
NPN_DestroyStreamUPP destroystream;
NPN_StatusUPP status;
NPN_UserAgentUPP uagent;
NPN_MemAllocUPP memalloc;
NPN_MemFreeUPP memfree;
NPN_MemFlushUPP memflush;
NPN_ReloadPluginsUPP reloadplugins;
NPN_GetJavaEnvUPP getJavaEnv;
NPN_GetJavaPeerUPP getJavaPeer;
NPN_GetURLNotifyUPP geturlnotify;
NPN_PostURLNotifyUPP posturlnotify;
NPN_GetValueUPP getvalue;
NPN_SetValueUPP setvalue;
NPN_InvalidateRectUPP invalidaterect;
NPN_InvalidateRegionUPP invalidateregion;
NPN_ForceRedrawUPP forceredraw;
} NPNetscapeFuncs;
NPPluginFuncs、 NPNetscapeFuncs结构中的函数接口分别由plugin和浏览器内核提供实现,而让对方调用。每个接口的具体含义及什么时候调用可参考下面提到的相关资源。

三、Gecko内核的plugin实现机制及基本原理
Gecko内核作为外部调用plugin的主程序,它不仅提供一组NPNetscapeFuncs函数接口给plugin调用,同时需要管理所有的plugin动态库、针对不同embed/object创建不同的plugininstance以及相关安全、创建窗口句柄、释放资源等。

在Gecko内核中提供了如下几个主要头文件、接口及类来完成相关任务。
\mozilla\modules\plugin\base\public\Npapi.h
\mozilla\modules\plugin\base\public\Npupp.h
\mozilla\modules\plugin\base\public\Npruntime.h
这几个头文件包含实现plugin所需要的函数接口、常量定义、数据结构定义等;

nsIPluginTag.idl
主要代表一个plugin动态库基本信息包括文件名、描述等;
nsIPluginTagInfo.idl/nsIPluginTagInfo2.idl
主要代表一个embed/object标签所描述的内容如MIME类型、参数、宽高等;
nsIPlugin.idl
主要描述一个plugin动态库所对应的initialize、shutdown、getMIMEDescription、getValue及创建plugin实例方法createPluginInstance,相当于plugin动态库加载后该plugin所提供的函数入口等;
nsIPluginInstance.idl
主要描述一个plugin动态库所实现的一个plugin实例所对应的函数接口,如initialize、start、stop、destroy、setWindow、newStream、getValue、handleEvent等;
nsIPluginHost.idl
主要描述如何装载plugin动态库、实例化EmbededPlugin、FullPagePlugin、检查指定MIME类型是否有对应plugin来实现等;

由类nsPluginHostImpl来代表"@mozilla.org/plugin/manager;1"、"@mozilla.org/plugin/host;1"来管理plugin等;它主要实现了接口nsIPluginHost、nsIPluginManager2等,提供了
void loadPlugins();
nsIPlugin getPluginFactory(in string aMimeType);
void instantiateEmbeddedPlugin(in string aMimeType, in nsIURI aURL, in nsIPluginInstanceOwner aOwner);
void setUpPluginInstance(in string aMimeType, in nsIURI aURL, in nsIPluginInstanceOwner aOwner);
void stopPluginInstance(in nsIPluginInstance aInstance);
等主要方法;

由类ns4xPlugini来实现nsIPlugin接口,其中维护了上面提到的函数接口
NPPluginFuncs fCallbacks;
PRLibrary* fLibrary;
NP_PLUGINSHUTDOWN fShutdownEntry;
/**
* The browser-side callbacks that a 4.x-style plugin calls.
*/
static NPNetscapeFuncs CALLBACKS;

在nsPluginHostImpl.cpp中通过static nsActivePluginList *gActivePluginList;来维护一组当前活动的plugin
class nsActivePluginList
{
public:
nsActivePlugin * mFirst;
nsActivePlugin * mLast;
PRInt32 mCount;
................................
};

struct nsActivePlugin
{
nsActivePlugin* mNext;
char* mURL;
nsIPluginInstancePeer* mPeer;
nsRefPtr mPluginTag;
nsIPluginInstance* mInstance;
PRTime mllStopTime;
PRPackedBool mStopped;
PRPackedBool mDefaultPlugin;
PRPackedBool mXPConnected;
//Array holding all opened stream listeners for this entry
nsCOMPtr mStreams;
.........................................
};

由nsPluginHostImpl、ns4xPlugin及nsActivePluginList等类能够维护好plugin动态库的加载、创建一个新plugin实例等,哪究竟是由谁来加载动态库、创建新的plugin实例呢?

作为一个html embed/object标签,其对应的元素为nsHTMLObjectElement,其继承自nsGenericHTMLFormElement、nsObjectLoadingContent、nsIDOMHTMLObjectElement,其中nsObjectLoadingContent提供了下面主要的方法
nsObjectLoadingContent::LoadObject(const nsAString& aURI,
PRBool aNotify, const nsCString& aTypeHint, PRBool aForceLoad)

nsresult nsObjectLoadingContent::TryInstantiate(const nsACString& aMIMEType,
nsIURI* aURI)

而nsHTMLObjectElement往往对应有nsObjectFrame;其中包含有 nsRefPtr mInstanceOwner;

而nsPluginInstanceOwner中含有如下元素:
nsPluginNativeWindow *mPluginWindow;
nsCOMPtr mInstance;
nsObjectFrame *mOwner;
nsCOMPtr mContent;
nsCString mDocumentBase;
char *mTagText;
nsCOMPtr mWidget;
nsCOMPtr mPluginTimer;
nsCOMPtr mPluginHost;

有了对基本的数据结构理解之后这样在parser html内容时,会根据embed/object标签创建nsHTMLObjectElement、nsObjectFrame、nsPluginInstanceOwner等实例,进而在nsHTMLObjectElement的LoadObject方法驱动下逐步的调用TryInstantiate、Instantiate、InstantiateEmbeddedPlugin、TrySetUpPluginInstance、Create4xPlugin等等,从而完成一个plugin实例的生成等。

至于其Scripting、Stream、security方面的支持须进一步参考nsIDOMClassInfo、nsGenericHtmlObjectSH及nsIPluginStreamListener等;

四、总结
通过初步了解plugin的实现机制后,然后自己动手参照Mozilla提供的plugin examples及相关API,完成一个基本的Plugin难度其实并不大,但是如想利用plugin技术实现如3D、Stream Media、叠加等方面的效果,须更进一步的了解3D、Stream Media等方面的内容。其实支持Gecko内核的Plugin相当多,如主流的播放器、flash、java applet、silverlight等等都有浏览器插件,再加上支持Gecko内核的plugin一般也支持Opera、Safari,这样如果我们掌握了NPAPI Plugin技术,那么我们就可以充分运用已有的plugin或自己动手创建一个好用的plugin,这样会大大的扩展浏览器的应用,让我们拭目以待吧。。。。。

五、参考资源
WIKI NPAPI
Mozilla projects -plugins
Mozilla developer center -plugins
Netscape Gecko Plugin API Reference
Mozilla Plugin Support on Microsoft Windows

没有评论: