chorme extension plugin

乜明朗
2023-12-01
Chrome Plugin 开发
2011年10月18日 星期二 下午 4:08

从火狐转用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 * 变量。

 类似资料:

相关阅读

相关文章

相关问答