开发附带NPAPI插件的Chrome扩展
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
开发附带NPAPI插件的Chrome扩展
1NPAPI插件 (2)
1.1NPAPI简介 (2)
1.2准备工作 (2)
1.3创建插件 (2)
1.3.1创建Win32 DLL工程 (2)
1.3.2引入NPAPI库 (2)
1.3.3添加宏定义_X86_ (2)
1.3.4添加模块定义文件(.def文件) (2)
1.3.5编辑stdafx.h文件 (3)
1.3.6添加基础框架文件 (3)
1.3.7编辑sample.cpp文件 (3)
1.3.8编辑sample.h文件 (4)
1.3.9添加Version资源 (8)
1.3.10编译输出 (8)
1.4需要注意的问题 (8)
1.4.1库文件的捆绑 (8)
1.4.2谨记MIMEType (8)
1.4.3无效的NPN_CreateObject? (8)
2Chrome扩展 (9)
2.1简介 (9)
2.2开始编写 (9)
2.2.1准备一个图标文件(.png) (9)
2.2.2准备NPAPI插件(.dll) (9)
2.2.3编写manifest.json (9)
2.2.4编写background.html (10)
2.2.5编写background.js (10)
2.3安装与测试 (10)
2.4发布 (10)
3参考资料 (11)
1 NPAPI插件
1.1 NPAPI简介
NPAPI(Netscape Plugin Application Programming Interface,网景插件应用程序接口)是网景公司当年制定的开发基于网景浏览器,用于在浏览器中执行外部应用程序的通用接口。
该接口基于插件机制,制定了一系列的标准和API,因此也有NPAPI插件一说。
同期的微软,也在IE中支持ActiveX为浏览器插件,不得不承认微软在这一点上,把浏览器和OS 结合的更为紧密,这也可能是当年微软能够击溃网景的原因之一。
但网景的影响深远,除了微软特立独行之外,其他浏览器开发厂商奇迹般的都一致采用了NPAPI来对浏览器进行扩展(这包括后来从灰烬中重生的FireFox及新生的Chrome;当然,Chrome在不久前时间已经在尝试抛弃NPAPI了)。
因此,在目前来看NPAPI几乎是IE 之外的浏览器插件开发的统一标准。
1.2 准备工作
根据参考资料,从ftp:///pub//firefox/releases/4.0.1/source/中下载了firefox早期的源代码,并从中抠出了NPAPI相关的部分(恩,资料上说的不够详细)。
解压源代码,把modules\plugin\base\public和modules\plugin\sdk\samples\include两个目录中的文件复制出来放在一起(我创建了D:\npapi,把文件都放这了)。
另外,资料里提到的三个文件在modules\plugin\sdk\samples\common下(记住位置,待会会用到)。
1.3 创建插件
顺便提一下,本文以VS2003为范例。
插件实现的功能:对浏览器(贴切点说是对javascript引擎)暴露对象Sample,而Sample 又提供了一个sayHello的方法。
这样一来,我们可以在浏览器中,使用javascript通过Sample.sayHello();来调用插件所提供的功能。
1.3.1 创建Win32 DLL工程
1.3.2 引入NPAPI库
在工程属性中,添加“附加包含目录”:D:\npapi(之前抠出来的部分)。
1.3.3 添加宏定义_X86_
1.3.4 添加模块定义文件(.def文件)
创建sample.def文件,内容为:
LIBRARY "sample"
EXPORTS
NP_GetEntryPoints @1
NP_Initialize @2
NP_Shutdown @3
1.3.5 编辑stdafx.h文件
增加tchar头文件的引入:
#include <tchar.h>
增加NPAPI头文件的引入:
// Mozilla-API
#include <npfunctions.h>
#include <npruntime.h>
#include "npruntime.h"
1.3.6 添加基础框架文件
找到np_entry.cpp、npn_gate.cpp和npp_gate.cpp,复制到工程目录下,并添加到工程(恩恩,位置在modules\plugin\sdk\samples\common)。
在编辑器里分别打开着三个文件,并在文件头部加入:
#include "stdafx.h"
1.3.7 编辑sample.cpp文件
将文件代码修改为:
#include "stdafx.h"
#include "sample.h"
NPError NS_PluginInitialize()
{
return NPERR_NO_ERROR;
}
void NS_PluginShutdown()
{
}
nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct)
{
if(!aCreateDataStruct)
return NULL;
CPlugin* plugin = new CPlugin(aCreateDataStruct->instance);
return plugin;
}
void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin)
{
if(aPlugin)
delete (CPlugin *)aPlugin;
}
1.3.8 编辑sample.h文件
如果文件不存在,创建一个,并添加到工程。
将文件内容修改为:
#include "stdafx.h"
#include "npruntime.h"
#include "pluginbase.h"
bool IsStringNPIdentifier(NPIdentifier name)
{
return *(char**)name == (char*)name + 8;
}
char* CopyNPString(NPString str)
{
char* r = new char[str.UTF8Length + 1];
strncpy(r, str.UTF8Characters, str.UTF8Length);
r[str.UTF8Length] = 0;
return r;
}
class CSample : public NPObject
{
public:
CSample(NPP npp) : mNpp(npp) { }
~CSample() { }
static NPObject* _Creator(NPP npp, NPClass *aClass) { return new CSample(npp); }
static void _Deallocate(NPObject *npobj) { delete (CSample*)npobj; }
static void _Invalidate(NPObject *npobj) { ((CSample*)npobj)->Invalidate(); }
static bool _HasMethod(NPObject *npobj, NPIdentifier name) { return ((CSample*)npobj)->HasMethod(name); }
static bool _Invoke(NPObject *npobj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result) { return ((CSample*)npobj)->Invoke(name, args, argCount, result); }
static bool _InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result) { return ((CSample*)npobj)->InvokeDefault(args, argCount, result); }
static bool _HasProperty(NPObject * npobj, NPIdentifier name) { return ((CSample*)npobj)->HasProperty(name); }
static bool _GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result) { return ((CSample*)npobj)->GetProperty(name, result); }
static bool _SetProperty(NPObject *npobj, NPIdentifier name, const NPVariant *value) { return ((CSample*)npobj)->SetProperty(name, value); }
static bool _RemoveProperty(NPObject *npobj, NPIdentifier name) { return ((CSample*)npobj)->RemoveProperty(name); }
static bool _Enumerate(NPObject *npobj, NPIdentifier **identifier, uint32_t *count) { return ((CSample*)npobj)->Enumerate(identifier, count); }
static bool _Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result) { return ((CSample*)npobj)->Construct(args, argCount, result); }
virtual void Invalidate() { }
virtual bool HasMethod(NPIdentifier name)
{
if(IsStringNPIdentifier(name))
{
char* methodName = *(char**)name;
if(_tcscmp(methodName, TEXT("sayHello")) == 0)
return true;
}
return false;
}
virtual bool Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
{
if(IsStringNPIdentifier(name))
{
char* methodName = *(char**)name;
if(_tcscmp(methodName, TEXT("sayHello")) == 0)
{
MessageBox(NULL, TEXT("hello, npapi."), TEXT("plugin-sample"), MB_OK | MB_ICONINFORMATION);
return true;
}
}
return false;
}
virtual bool InvokeDefault(const NPVariant *args, uint32_t argCount, NPVariant *result) { return true; }
virtual bool HasProperty(NPIdentifier name) { return false; }
virtual bool GetProperty(NPIdentifier name, NPVariant *result) { return false; }
virtual bool SetProperty(NPIdentifier name, const NPVariant *value) { return false; }
virtual bool RemoveProperty(NPIdentifier name) { return false; }
virtual bool Enumerate(NPIdentifier **identifier, uint32_t *count) { return false; }
virtual bool Construct(const NPVariant *args, uint32_t argCount, NPVariant *result) { return false; }
private:
NPP mNpp;
};
static NPClass Sample = {
NP_CLASS_STRUCT_VERSION_CTOR,
CSample::_Creator,
CSample::_Deallocate,
CSample::_Invalidate,
CSample::_HasMethod,
CSample::_Invoke,
CSample::_InvokeDefault,
CSample::_HasProperty,
CSample::_GetProperty,
CSample::_SetProperty,
CSample::_RemoveProperty,
CSample::_Enumerate,
CSample::_Construct
};
class CPlugin : public nsPluginInstanceBase
{
public:
CPlugin(NPP pNPInstance) : nsPluginInstanceBase(), m_pNPInstance(pNPInstance), m_bInitialized(FALSE), m_sample(NULL) { }
~CPlugin() { }
NPBool init(NPWindow* pNPWindow)
{
m_bInitialized = TRUE;
return TRUE;
}
void shut()
{
if(m_sample)
{
// NPN_ReleaseObject(m_sample);
delete m_sample;
m_sample = NULL;
}
m_bInitialized = FALSE;
}
NPBool isInitialized()
{
return m_bInitialized;
}
NPError GetValue(NPPVariable variable, void *value)
{
switch(variable)
{
case NPPVpluginNameString:
*((char**)value) = "plugin-sample";
break;
case NPPVpluginDescriptionString:
*((char**)value) = "plugin-sample for Chrome";
break;
case NPPVpluginScriptableNPObject:
// if(m_sample == NULL)
// m_sample = (CSample*)NPN_CreateObject(m_pNPInstance, &Sample);
// if(m_sample != NULL)
// NPN_RetainObject(m_sample);
if(m_sample == NULL)
{
m_sample = new CSample(m_pNPInstance);
m_sample->_class = &Sample;
}
*((NPObject**)value) = m_sample;
break;
}
return nsPluginInstanceBase::GetValue(variable, value);
}
private:
NPP m_pNPInstance;
NPBool m_bInitialized;
CSample* m_sample;
};
1.3.9 添加Version资源
以文本编辑器方式打开资源文件,在版本信息BLOCK中添加:
V ALUE "MIMEType", "application/plugin-sample"
1.3.10 编译输出
自此,sample.dll已经躺在Debug目录下了。
1.4 需要注意的问题
1.4.1 库文件的捆绑
考虑到工程的独立性,我们可以把库文件与工程捆绑在一起,我的做法是在工程内创建一个inc目录,把之前提到的D:\npapi下所有文件复制过来,并把“附加包含目录”改为:inc。
1.4.2 谨记MIMEType
一定要记得添加Version资源,并添加MIMEType项。
1.4.3 无效的NPN_CreateObject?
在后续的测试过程中,NPN_CreateObject总是无法有效的创建对象。
因此,在sample.h中,我们采用了直接new CSample();的方式(具体原因有待研究)。
2 Chrome扩展
2.1 简介
不愧是Google出品,Chrome从一推出就受到了业界大量的关注和用户的青睐,几年下来,市场份额一直在膨胀。
其中原因不仅是小巧轻量和启动快速,也有其快速支持最新Web标准等多方面的缘故。
对于第三方开发商,Google也提供了Chrome扩展编程接口,用来提升浏览器本身的个性化定制。
Chrome扩展基于HTML5构建,面向javascript引擎暴露浏览器内部对象,使用javascript即可直接操作浏览器对象,从而实现功能扩展。
当然,如果我们希望实现的功能超出了Chrome本身提供的内置对象所涵盖的范围,则需要之前提到的NPAPI插件的支持了(这就类似IE浏览器中通过new ActiveXObject创建COM对象来增强浏览器功能一样)。
2.2 开始编写
哦哦,提醒一下,下面提到的所有文件,务必放到同一个目录中。
扩展实现的功能:在每个页面(空白处)的右键菜单中,添加“sayHello”菜单项,用户点击这个菜单项时,扩展程序通过调用NPAPI插件的sayHello方法,实现弹出“hello,npapi.”对话框的功能。
2.2.1 准备一个图标文件(.png)
去网上下载一个png文件吧,32x32、48x48、64x64、128x128等尺寸的都可以。
总之,这是一件彰显个性的事情。
2.2.2 准备NPAPI插件(.dll)
恩,之前编译好,已经在Debug目录躺的妥妥的sample.dll,把他复制过来吧。
2.2.3 编写manifest.json
内容如下:
{
"manifest_version" : 2,
"minimum_chrome_version" : "6.0.0.0",
"name" : "我的扩展",
"description" : "我的扩展",
"version" : "1.0.0",
"permissions" : [
"contextMenus",
"tabs",
"http://*/*",
"https://*/*"
],
"icons" : {
"128" : "sayHello.png"
},
"background" : {
"page" : "background.html"
},
"plugins" : [
{ "path" : "sample.dll", "public" : true }
]
}
2.2.4 编写background.html
内容如下:
<html>
<head></head>
<body>
<embed type="application/plugin-sample" id="Sample"></embed>
<script type="text/javascript" src="background.js"></script>
</body>
</html>
2.2.5 编写background.js
内容如下:
var bkgnd = chrome.extension.getBackgroundPage();
var sample = bkgnd.document.getElementById("Sample");
function getClickHandler(type) {
return function(info, tab) {
var url = info.pageUrl;
var title = tab.title;
if(type == "page") {
sample.sayHello();
}
}
}
chrome.contextMenus.create({ "title" : "sayHello", "type" : "normal", "contexts" : [ "page" ], "onclick" : getClickHandler("page") });
2.3 安装与测试
打开Chrome设置的“扩展程序”页面,勾选“开发者模式”,点击“加载正在开发的扩展程序”,在弹出的对话框中,选择Chrome扩展所在的目录,然后再确认“添加”即可。
2.4 发布
同上,在开发者模式下,选择“打包扩展程序”,在弹出的对话框中,选择Chrome扩展所在的目录,然后再次点击“打包扩展程序”即可(第一次打包时,Chrome会自动生成一个密钥文件;以后每次打包,都需要选择这个密钥文件)。
打包之后的Chrome扩展,是一个.crx的zip压缩文件,可以直接拖拽到Chrome的扩展程序页面,实现安装。
3 参考资料
Mozilla官方文档(英文):
https:///en-US/docs/Plugins
https:///en-US/docs/Gecko_Plugin_API_Reference/Plug-in_Basics
NPAPI开发详解(中文):
/post/21666/
/view/c4b939f59e314332396893ce.html
Chrome扩展官方文档(英文):
https:///extensions/index.html
11 / 11。