最近需要写一个Mac平台上的简单的跨浏览器的插件,需要在js中调用本地方法,而npapi满足此要求。
NPAPI就是Netscape Plugin Application Programming Interface的缩写了,虽然Netscape已经去了,但是这个却被沿用下来,在各大浏览器中都得以实现。还是纪念下曾经的浏览器的鼻祖啊。
在网络上搜索了很长时间,一直没有找到合适的满足自己需求的代码例子。且这方面的文档也少的可怜。
还是先提一下,有两个系列的文章还是不错,虽然或许可能也不完全正确,但是帮助理解npapi的编程模型是非常有帮助的:
http://colonelpanic.net/category/plugindev/npapi-plugindev/
http://rintarou.dyndns.org/category/browser/plugin
下面是我寻找解决方法的过程
一开始找到mozilla的一篇官方文档:Writing a plugin for Mac OS X,这里面提到了了官方的代码库中有demo,于是就跑到公司checkout下mozilla的分支。并看了下里面的一个mac的例子.
不过这个例子没有使用npruntime的东西,加上一开始的时候对npapi不熟悉,就继续搜索.
在google, stackoverflow,github, google codes 中不停的搜索,结果甚少,最后找到一个npruntime的例子,Call Native API from Google Chrome Extension on Mac OS X只可惜编译后只能在mac的chrome下运行。
后来又找到了Firebreath这个跨平台的浏览器NPAPI插件开发框架,可以通过python和cmake的配合,生成适合不同操作系统的浏览器插件的工程,于是就测试了,果真生成xcode项目后,编译,然后测试了下js调用插件的方法,还真能在各个浏览器(测试了三个比较常用的safari,chrome,firefox)下运行。但是无奈由于包含一些高级的程序代码其最小编译后的大小都2m+对于一个功能单一的插件来讲,无疑是让人无法接受的,所以继续找demo.
后来找到了npsimple-win32 这个windows下的vs的项目,我将其移植到mac上,编译运行后,唯独在safari下无法运行,悲剧的一塌糊涂,对于一个npapi不同的浏览器在实现的具体细节上竟然有细微的差别,真是太变态鸟。
最后无奈之下,将Weikit开源项目中的例子弄出来,然后和npruntime的相关代码进行整合,发现在safari,chrome可以运行在firefox中没法运行,好吧,至少现在两个例子的并集是完整的,我就将两个代码进行查看,最终找出了问题的所在,在safari下需要启动CoreGraphics,而在firefox下scriptable的NPObject的hasProperty和getProperty必须设置,可能在firefox下调用某函数,先去scriptable NPObject中找有没有这个名字的属性,然后在找方法吧。
最后终于写出了一个简单的例子
#import <WebKit/npapi.h>
#import <WebKit/npfunctions.h>
#import <WebKit/npruntime.h>
// Browser function table,可以通过它来得到浏览器提供的功能
static NPNetscapeFuncs * browser ;
static const char * plugin_method_name_open = "open" ;
////////////////////////////////////
/*******各种接口的声明*********/
//在NPAPI编程的接口中你会发现有NP_打头的,有NPP_打头的,有NPN_打头的
//NP是npapi的插件库提供给浏览器的最上层的接口
//NPP即NP Plugin是插件本身提供给浏览器调用的接口,主要被用来填充NPPluginFuncs的结构体
//NPN即NP Netscape ,是浏览器提供给插件使用的接口,这些接口一般都在NPNetscapeFuncs结构体中
//Mach-o entry points,浏览器和创建交流的最上层的接口
NPError NP_Initialize ( NPNetscapeFuncs * browserFuncs );
NPError NP_GetEntryPoints ( NPPluginFuncs * pluginFuncs );
void NP_Shutdown ( void );
//NPP Functions
NPError NPP_New ( NPMIMEType pluginType , NPP instance , uint16_t mode , int16_t argc , char * argn [], char * argv [], NPSavedData * saved );
NPError NPP_Destroy ( NPP instance , NPSavedData ** save );
NPError NPP_SetWindow ( NPP instance , NPWindow * window );
NPError NPP_NewStream ( NPP instance , NPMIMEType type , NPStream * stream , NPBool seekable , uint16_t * stype );
NPError NPP_DestroyStream ( NPP instance , NPStream * stream , NPReason reason );
int32 NPP_WriteReady ( NPP instance , NPStream * stream );
int32 NPP_Write ( NPP instance , NPStream * stream , int32 offset , int32 len , void * buffer );
void NPP_StreamAsFile ( NPP instance , NPStream * stream , const char * fname );
void NPP_Print ( NPP instance , NPPrint * platformPrint );
int16_t NPP_HandleEvent ( NPP instance , void * event );
void NPP_URLNotify ( NPP instance , const char * URL , NPReason reason , void * notifyData );
NPError NPP_GetValue ( NPP instance , NPPVariable variable , void * value );
NPError NPP_SetValue ( NPP instance , NPNVariable variable , void * value );
//Functions for scriptablePluginClass
bool plugin_has_method ( NPObject * obj , NPIdentifier methodName );
bool plugin_invoke ( NPObject * obj , NPIdentifier methodName , const NPVariant * args , uint32_t argCount , NPVariant * result );
bool hasProperty ( NPObject * obj , NPIdentifier propertyName );
bool getProperty ( NPObject * obj , NPIdentifier propertyName , NPVariant * result );
////////////////////////////////////
static struct NPClass scriptablePluginClass = {
NP_CLASS_STRUCT_VERSION ,
NULL ,
NULL ,
NULL ,
plugin_has_method ,
plugin_invoke ,
NULL ,
hasProperty ,
getProperty ,
NULL ,
NULL ,
};
//接口的实现
NPError NP_Initialize ( NPNetscapeFuncs * browserFuncs )
{
browser = browserFuncs ;
return NPERR_NO_ERROR ;
}
NPError NP_GetEntryPoints ( NPPluginFuncs * pluginFuncs )
{
pluginFuncs -> version = 11 ;
pluginFuncs -> size = sizeof ( pluginFuncs );
pluginFuncs -> newp = NPP_New ;
pluginFuncs -> destroy = NPP_Destroy ;
pluginFuncs -> setwindow = NPP_SetWindow ;
pluginFuncs -> newstream = NPP_NewStream ;
pluginFuncs -> destroystream = NPP_DestroyStream ;
pluginFuncs -> asfile = NPP_StreamAsFile ;
pluginFuncs -> writeready = NPP_WriteReady ;
pluginFuncs -> write = ( NPP_WriteProcPtr ) NPP_Write ;
pluginFuncs -> print = NPP_Print ;
pluginFuncs -> event = NPP_HandleEvent ;
pluginFuncs -> urlnotify = NPP_URLNotify ;
pluginFuncs -> getvalue = NPP_GetValue ;
pluginFuncs -> setvalue = NPP_SetValue ;
return NPERR_NO_ERROR ;
}
void NP_Shutdown ( void )
{
}
bool plugin_has_method ( NPObject * obj , NPIdentifier methodName ) {
// This function will be called when we invoke method on this plugin elements.
NPUTF8 * name = browser -> utf8fromidentifier ( methodName );
bool result = strcmp ( name , plugin_method_name_open ) == 0 ;
browser -> memfree ( name );
return result ;
}
bool plugin_invoke ( NPObject * obj , NPIdentifier methodName , const NPVariant * args , uint32_t argCount , NPVariant * result ) {
// Make sure the method called is "open".
NPUTF8 * name = browser -> utf8fromidentifier ( methodName );
if ( strcmp ( name , plugin_method_name_open ) == 0 ) {
browser -> memfree ( name );
BOOLEAN_TO_NPVARIANT ( false , * result );
// Meke sure the arugment has at least one String parameter.
if ( argCount > 0 && NPVARIANT_IS_STRING ( args [ 0 ])) {
// Build CFURL object from the arugment.
NPString str = NPVARIANT_TO_STRING ( args [ 0 ]);
CFURLRef url = CFURLCreateWithBytes ( NULL , ( const UInt8 * ) str . UTF8Characters , str . UTF8Length , kCFStringEncodingUTF8 , NULL );
if ( url ) {
// Open URL with the default application by Launch Service.
OSStatus res = LSOpenCFURLRef ( url , NULL );
CFRelease ( url );
BOOLEAN_TO_NPVARIANT ( res == noErr , * result );
}
}
return true ;
}
browser -> memfree ( name );
return false ;
}
bool hasProperty ( NPObject * obj , NPIdentifier propertyName ) {
return false ;
}
bool getProperty ( NPObject * obj , NPIdentifier propertyName , NPVariant * result ) {
return false ;
}
//NPP Functions Implements
NPError NPP_New ( NPMIMEType pluginType , NPP instance , uint16_t mode , int16_t argc , char * argn [], char * argv [], NPSavedData * saved )
{
// Create per-instance storage
//obj = (PluginObject *)malloc(sizeof(PluginObject));
//bzero(obj, sizeof(PluginObject));
//obj->npp = instance;
//instance->pdata = obj;
if ( ! instance -> pdata ) {
instance -> pdata = browser -> createobject ( instance , & scriptablePluginClass );
}
// Ask the browser if it supports the CoreGraphics drawing model
NPBool supportsCoreGraphics ;
if ( browser -> getvalue ( instance , NPNVsupportsCoreGraphicsBool , & supportsCoreGraphics ) != NPERR_NO_ERROR )
supportsCoreGraphics = FALSE ;
if ( ! supportsCoreGraphics )
return NPERR_INCOMPATIBLE_VERSION_ERROR ;
// If the browser supports the CoreGraphics drawing model, enable it.
browser -> setvalue ( instance , NPPVpluginDrawingModel , ( void * ) NPDrawingModelCoreGraphics );
return NPERR_NO_ERROR ;
}
NPError NPP_Destroy ( NPP instance , NPSavedData ** save )
{
// If we created a plugin instance, we'll destroy and clean it up.
NPObject * pluginInstance = instance -> pdata ;
if ( ! pluginInstance ) {
browser -> releaseobject ( pluginInstance );
pluginInstance = NULL ;
}
return NPERR_NO_ERROR ;
}
NPError NPP_SetWindow ( NPP instance , NPWindow * window )
{
return NPERR_NO_ERROR ;
}
NPError NPP_NewStream ( NPP instance , NPMIMEType type , NPStream * stream , NPBool seekable , uint16_t * stype )
{
* stype = NP_ASFILEONLY ;
return NPERR_NO_ERROR ;
}
NPError NPP_DestroyStream ( NPP instance , NPStream * stream , NPReason reason )
{
return NPERR_NO_ERROR ;
}
int32 NPP_WriteReady ( NPP instance , NPStream * stream )
{
return 0 ;
}
int32 NPP_Write ( NPP instance , NPStream * stream , int32 offset , int32 len , void * buffer )
{
return 0 ;
}
void NPP_StreamAsFile ( NPP instance , NPStream * stream , const char * fname )
{
}
void NPP_Print ( NPP instance , NPPrint * platformPrint )
{
}
int16_t NPP_HandleEvent ( NPP instance , void * event )
{
return 0 ;
}
void NPP_URLNotify ( NPP instance , const char * url , NPReason reason , void * notifyData )
{
}
NPError NPP_GetValue ( NPP instance , NPPVariable variable , void * value )
{
NPObject * pluginInstance = NULL ;
switch ( variable ) {
case NPPVpluginScriptableNPObject :
// If we didn't create any plugin instance, we create it.
pluginInstance = instance -> pdata ;
if ( pluginInstance ) {
browser -> retainobject ( pluginInstance );
}
* ( NPObject ** ) value = pluginInstance ;
break ;
default:
return NPERR_GENERIC_ERROR ;
}
return NPERR_NO_ERROR ;
}
NPError NPP_SetValue ( NPP instance , NPNVariable variable , void * value )
{
return NPERR_GENERIC_ERROR ;
}