从火狐转用Google Chrome已经N年了,前几天偶然想用一下IRC,因为mIRC一直收费,想起来装个免费的Chrome 插件,但结果没找到。其实主要原因是因为Chrome的地址栏集成了地址输入和搜索的功能,不能实现像 irc://... 之类的自定义协议。很多开发者都在询问有没有可能实现,出于好奇,进一步研究了一下Chrome的扩展开发。在谷歌的开发网站上有详尽的API文档和示例,不过关于 NPAPI 的部分就几行文字,其实这也确实超出了其API的范畴。Extention API 主要以 javascript 实现对UI和浏览器的设置和管理,地址栏由omnibox扩展管理,主要还是适用于实现搜索功能,虽然不能有效实现自定义协议,但也可以变通一下,比如由 omnibox 注册一个 "keyword": "irc" ,当在地址栏中输入 "irc+空格" 后,地址栏呈现出自定义的搜索状态,并将输入内容管理权交给了你的扩展程序。接下来是Socket通讯,虽然说HTML5支持 socket 了,但大体了解了一下,发现这个 socket 还是基于 http 的,出于安全考虑,还增加了许多限制,以纯 js 脚本方式实现 irc telnet 等通讯协议是根本不可能的。因此,想实现通讯的话就不的不使用插件开发(Plugins)。 Chrome 的插件使用 ,想找点示例比较困难,这也许是其很少招惹木马、病毒的原因之一吧。进入正式话题: 在扩展的 manifest.json 中加入 "plugins": [{ "path": "myplugin.dll", "public": false } ] ,说明该扩展将使用文件名为myplugin.dll的插件,插件为标准windows动态库,使用 NPAPI 来开发,因为只做 js 的扩展,仅用到其中的 npapi.h nptypes.h npruntime.h npfunctions.h 四个头文件,不需要连接库,所以编译器也不限制,之要能生成 .dll 就可以。public参数说明该插件是否可被所有页面使用,默认为false,说明之能被扩展内部的页面使用。 nptypes.h : NPAPI 的变量类型定义 npapi.h : NPAPI 定义插件的接口函数说明和常用结构说明 npruntime.h : 插件与JavaScript交互的结构说明 npfunctions.h : 调用浏览器的接口函数和结构 动态库必须实现下面三个接口函数: NPError WINAPI NP_GetEntryPoints(NPPluginFuncs* pluginFuncs); 插件加载后第一个被调用,参数 pluginFuncs 为一个结构指针,插件代码需要实现相关调用内容,浏览器则通过该结构,调用插件的功能。 NPError WINAPI NP_Initialize(NPNetscapeFuncs* pBrowserFuncs); 插件加载后第二个被调用,参数 pBrowserFuncs 需要被插件保存为全局变量,通过此结构的函数,调用浏览器提供的功能。 NPError WINAPI NP_Shutdown(void); 插件释放前被调用。 插件通过MIME被扩展加载,在插件的 .html 中增加 <embed type="application/x-my-extension" id="myplugin"> ,浏览器会通过该 MIME 去搜索所有插件是否被支持,如果支持则加载该插件,在windows环境下MIME表述在插件的库文件的版本资源中(Unix,Linux环境通过NP_GetMIMEDescription()返回,即在.rc文件的版本信息中加入 VALUE "MIMEType", "application/x-my-extension" 。 实现插件功能: NPError WINAPI NP_GetEntryPoints(NPPluginFuncs* pluginFuncs) { pluginFuncs->version = 1; pluginFuncs->newp = PluginNewp; // <embed type="(MIME)"> 创建插件时被调用 pluginFuncs->destroy = PluginDestroy; // 释放插件时调用 pluginFuncs->getvalue = PluginGetValue; // js 中赋值时被调用 pluginFuncs->setwindow = PluginSetWindow; .... return NPERR_NO_ERROR; }
NPError WINAPI NP_Initialize(NPNetscapeFuncs* pBrowserFuncs) { BROWSER = pBrowserFuncs; // 记录为全局变量 .... return NPERR_NO_ERROR; }
NPError WINAPI NP_Shutdown(void) { .... return NPERR_NO_ERROR; }
NPError PluginNewp(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved) { CMyPlugin *plugin = BROWSER->createobject(instance, &CMyPlugin::m_npClass); // 创建插件实体, 将浏览器调用 m_npClass->allocate(...); instance->pdata = plugin; // 用 pdata 记录插件实体地址,也可在 CMyPlugin 创建时实现,释放时会用到 ... return NPERR_NO_ERROR; }
NPError PluginDestroy(NPP instance, NPSavedData** save) { BROWSER->releaseobject((CMyPlugin *) instance->pdata); // 释放,浏览器调用 m_npClass->deallocate(...); return NPERR_NO_ERROR; }
NPError PluginGetValue(NPP instance, NPPVariable variable, void *value) { if (variable==NPPVpluginScriptableNPObject) { // javascript: var v = document.getElementById("myplugin"); 时被浏览器调用,必须 retainobject(),浏览器处理 NPObject 的内容和引用计数器 CMyPlugin *plugin = (CMyPlugin *) instance->pdata; if (plugin) *value = BROWSE->retainobject(plugin); } return NPERR_NO_ERROR; }
struct NPClass CMyPlugin::m_npClass = { NP_CLASS_STRUCT_VERSION, CMyPlugin::Allocate, CMyPlugin::Deallocate, NULL, CMyPlugin::HasMethod, CMyPlugin::InvokeMethod, NULL, CMyPlugin::HasProperty, CMyPlugin::GetProperty, CMyPlugin::SetProperty, NULL, NULL, NULL, };
class CMyPlugin : public NPObject { public: NPP m_npp; CMyPlugin(NPP instance) : m_npp(instance) { m_npp->pdata = this; };
public: static NPClass m_npClass;
static NPObject *Allocate(NPP npp, NPClass *aClass) { // PluginNewp 的 BROWSER->createobject(); 调用此过程 return new CMyPlugin(npp); };
static void Deallocate(NPObject *npobj) { // PluginDestroy 的 BROWSER->releaseobject(); 调用此过程 delete (CMyPlugin *)npobj; };
static bool HasMethod(NPObject *obj, NPIdentifier methodName) { // javascript: myplugin.method1(...); 时调用,返回本插件是否有此函数调用 // *obj 即 CMyPlugin 的实体指针 NPUTF8 *name = BROWSER->utf8fromidentifier(methodName); bool rc = %是否有此函数%; // 可以用 strcmp(methodName, "?????") 你的函数列表 BROWSER->memfree(name); return rc; };
static bool InvokeMethod(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) { // 如果 HasMethod() 返回 true,此过程会被立即调用 // 根据 methodName 调用 obj 实现对应的功能 .... return true; };
static bool HasProperty(NPObject *obj, NPIdentifier propertyName) { // javascript: myplugin.property1 = xxx; 时调用,返回本插件是否有此函数调用 .... };
static bool GetProperty(NPObject *obj, NPIdentifier propertyName, NPVariant *result) { .... };
static bool SetProperty(NPObject *obj, NPIdentifier propertyName, const NPVariant *result) { .... };
....
// 脚本调用首先判断 HasProperty() 如果返回 true 则调用 GetProperty() 或 SetProperty() // 否则判断 HasMethod(), 如果返回 true 则调用 InvokeMethod() // 否则调用 Contructor()
};
至此,插件的主体就已经实现了,剩下的就是根据需要实现对脚本的解释执行了。需要注意的是与js脚本的交互都是NPVariant类型,需要调用浏览器的功能来实现,举个例子实现 js 脚本的回调处理 : javascript: var rc = plugin.setCallback("someevent", function(msg) { alert(msg); } ); 脚本向插件注册了一个回调函数,浏览器将调用插件的 InvokeMethod(); static bool InvokeMethod(NPObject *obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) { // *obj 为插件类实体指针 // methodName 为 "setCallback" // args[0] 为 "someevent" // args[1] 为 NPObject ,即回调函数的类 // argCount 为 2 // result 返回给脚本的函数调用结果
// 记录回调函数,由于脚本调用的参数是retain出来的,调用完成将被释放,因此此处必须 retainobject 后再记录。否则将出现异常 !!!! ((CMyPlugin *)obj)->m_someevent_callback = BROWSER->retainobject( args[1].value.objectValue ); BOOLEAN_TO_NPVARIANT(true, *result); // 使用 npruntime.h 提供的宏,设置返回值为 bool 类型的 true。 return true; };
插件中需要执行脚本的回调函数时: void invoke_script_callback(char *str) { NPVariant msg, res; STRINGZ_TO_NPVARIANT(str, &msg); BROWSER->invokeDefault(m_npp, m_someevent_callback, &msg, 1, &res); ... 此处处理脚本返回值 res BROWSER->releasevariantvalue(&res); }
注意: 使用字符串时应特别注意,STRINGZ_TO_NPVARIANT 转换仅适用于全局静态 char* 变量。最好使用 BROWSER->malloc 分配空间来创建PNVariant 字符串,特别是传给脚本的非 const PNVariant * 变量。 |