Building a firefox plugin – part one
March 1, 2009 104 Comments by Richard
作者:Richard
Note: For a better way to create a Browser Plugin (that will work on all browsers, not just NPAPI), check out the FireBreath project.
注:创建浏览器插件的更好方式(可用于所有浏览器,而不仅仅是NPAPI),请参阅 FireBreath 项目
Getting help: For a better place to ask NPAPI-related questions, go to StackOverflow and make sure to use the “npapi” and/or “firebreath” tags.
Introduction
获得帮助:请前往 StackOverflow 以获得有关NPAPI的更好回答,提问时务必添加 “npapi” 或 “firebreath” 标签
I have now been researching and working on a cross-platform browser plugin for several months. By far my greatest frustration throughout this process has been the significant lack of documentation on the subject. So, with the creation of this site, I wish to create a small series that will hopefully provide assistance to other poor developers who are trying to break into this strangely secretive field =]
目前,我在跨平台浏览器插件上已经研究和工作了好几个月。在这个项目中,我遇到最大的问题是缺少相关技术文档。因此,我创建了这个站点,并撰写一系列短篇文章,希望能为其他尝试进入这个秘密、奇怪领域的可怜的开发者们提供一些帮助。
I’m not going to be able to cover everything in one post (for time reasons if nothing else). How fast I get the rest of the posts done may depend in large part on whether or not anyone seems to be reading them and what requests are made in the comments =]
由于时间关系,我不会在文章中涵盖全部内容。本文的阅读量和评论中提出的需求在很大程度上决定了剩余文章的完成进度。
In these articles I plan to focus on the basic requirements for creating an NPAPI (Netscape Plugin) style plugin, which is used by most (if not all) plugin-supporting open browsers, including: Apple Internet Plugins on Mac (Firefox, Safari, probably others); Firefox (and all other gecko-based browsers), Opera, Safari, and Chrome on windows, and at least firefox on linux. I haven’t yet implemented a plugin on linux, but most of the same principles should apply.
在系列文章中,我计划重点介绍创建基于NPAPI(Netscape Plugin)技术插件的基本需求,该技术适用大部分支持插件的浏览器,包括Mac的Apple Internet Plugins(火狐、Safari等)、火狐(及其它基于gecko内核的浏览器)、Opera、Safari、Chrome。
Because of the minor differences between platforms, I will first cover the architecture as it is used in Windows, and then in a later post I will cover the differences between windows and other platforms, such as Mac and Linux.
由于各操作系统平台间的细微差异,我首先介绍Windows平台下的相关内容,再在后面的文章中介绍Windows与其它平台(Mac和Linux)之间的差异。
When I first began developing browser plugins, I failed to understand the difference between the scripting API and the plugin API. They are closely related, of course, but they serve different purposes.
一开始,我并不了解两种API的不同。它们关系密切,但有着不同的用途。
The Scripting API is used to provide methods callable from javascript, while the Browser API provides the interface for hosting the plugin itself in the browser.
脚本API用于提供可通过Javascript调用的方法,而浏览器API为运行在浏览器内的插件提供了接口。
You can find the Mozilla documentation for plugins here:
你可以通过下列地址了解插件的相关文档:
https://developer.mozilla.org/en/Gecko_Plugin_API_Reference
For history of the NPAPI (Netscape Plugin API), see the Wikipedia page:
NPAPI (Netscape Plugin API)的历史,可参阅维基百科:
http://en.wikipedia.org/wiki/NPAPI
A NPAPI browser plugin is, at it’s core, simply a DLL with a few specific entry points. Each of these entry points is only called once by the browser for all instances of your plugin on a given page. They are listed here in the order they should be called:
一个NPAPI插件,或者说其核心,仅仅是一个DLL及其内部的几个入口函数(entry points)。对于一个特定的页面内插件的所有实例,每个入口函数仅被浏览器调用一次:
It is important to note that since these entrypoints are specific to NPAPI, there is no reason you can’t have a NPAPI plugin inside a DLL that also provides other services (like, for example, an ActiveX plugin).
需要注意的是,上述入口函数是服务于NPAPI这一特定接口的,因此,一个包含其它功能(如ActiveX)的DLL,也可以同时提供NPAPI接口。
Since these three entrypoints provide the core of the NPAPI architecture, it is worth our time to look at them a little more closely.
由于NPAPI架构都基于上述三个接口函数,故值得我们多花些时间来仔细研究它们。
NPError WINAPI NP_GetEntryPoints(NPPluginFuncs* pFuncs)
This is undoubtedly the most important of the three to understand. Because of this, it shocks me that the only actual useful documentation I have found for this method so far is found in an old, but still mostly accurate web-book written who-knows-when by Zan Oliphant.
在三个入口函数中,NP_GetEntryPoints无疑是最重要的一个。正因为如此, 到目前为止,当我发现关于该方法最准确的讲解是在一个旧的,由Zan Oliphant撰写的Web图书中,而且它是真正有用的文档,震惊了。
While not as straightforward as the other two entrypoint functions, NP_GetEntryPoints is nonetheless relatively straightforward. As you can see from the above function prototype, NP_GetEntryPoints takes a pointer to the NPPluginFuncs structure:
虽然 NP_GetEntryPoints 没有其他两个入口函数那么简单, 但 NP_GetEntryPoints 也不复杂。 从上面的函数原型中可以看到, NP_GetEntryPoints 需要一个指向 NPPluginFuncs 结构体的指针:
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;
UPP in each of these type names stands for “Universal Proc Pointer”, which is essentially just a function pointer that the Gecko SDK uses in conjunction with a CallUniversalProc macro for all of its function pointer needs. For more information, grep the Gecko SDK.
每个类型名称中的 UPP 都代表 “通用 Proc 指针”, 它本质上只是 Gecko SDK 中根据需要与 CallUniversalProc 宏结合使用的函数指针。 详细信息, 请参阅 Gecko SDK。
These function pointers basically tell the browser how to interact with your plugin. Notice the naming convention here: NPP_* for all plugin functions. There are also NPN_* functions that we will see later, and these are functions that the plugin can call on the browser. They will be given to us in the NP_Initialize call.
通过这些函数指针,浏览器就基本知道了如何与您的插件交互。 请注意这里的命名约定: 所有插件函数均以 NPP_ 开头。 我们稍后还将看到 NPN_ 开头的函数, 这些都是浏览器提供给插件调用的函数。它们将在 NP_Initialize 调用中提供给我们。
So, the primary purpose of the NP_GetEntryPoints function is to give the browser pointers to all of the functions that it needs to call when creating or interacting with your plugin.
因此, NP_GetEntryPoints 函数的主要目的是为浏览器提供创建插件、与插件交互时需要调用的所有函数的指针。
Here is a quick overview of the NPP plugin functions that your plugin must provide (and give addresses to when NP_GetEntryPoints is called). This table is copied from Zan Oliphant’s book and updated to reflect the current practices and Gecko SDK 1.8:
下面是您的插件必须提供的 NPP 插件函数的简介 (并在调用 NP_GetEntryPoints 时提供地址)。 此表来自 Zan Oliphant 的书,并根据 Gecko SDK 1.8 做了最新修订:
API Name (API 名称) | Description(描述) |
---|---|
NPP_New | Creates a new instance of a plug-in. 创建插件实例 |
NPP_Destroy | Deletes an instance of a plug-in. 删除插件实例 |
NPP_SetWindow | Tells the plug-in when a window is created, moved, sized, or destroyed. 窗口创建、移动、改变大小或销毁时调用 |
NPP_NewStream | Notifies a plug-in instance of a new data stream. 通知插件实例有新的数据流 |
NPP_DestroyStream | Tells the plug-in that a stream is about to be closed or destroyed. 通知插件实例数据流将要关闭或销毁 |
NPP_StreamAsFile | Provides a local file name for the data from a stream. 为创建流数据提供本地文件名 |
NPP_WriteReady | Determines whether a plug-in is ready for data (and the maximum number of bytes it is prepared to accept). 确定插件是否准备好接收数据(以及其准备接收的最大字节数) |
NPP_Write | Called to write/deliver data to a plug-in. The docs note that this might be better named “NPP_DataArrived”. 调用以将数据读入插件,文档标注最好命名为“NPP_DataArrived” |
NPP_Print | Requests a platform-specific print operation for an embedded or full-screen plug-in. 为嵌入或全屏插件请求平台特定的打印操作 |
NPP_HandleEvent | Event handler, currently only used by Windowed plugins on Mac OS; windowless plugins on all platforms use this. 事件处理函数,对windowed的插件只在MAC操作系统上可用,对于winless的插件所有平台都可用 |
NPP_URLNotify | Notifies the completion of a URL request. 通知插件已完成URL请求 |
NPP_GetJavaClass | Deprecated / No longer used. Set to NULL 已弃用 / 设置为NULL |
NPP_GetValue | Called to query the plugin for information (also used to get an instance of a NPObject/Scriptable Plugin) 调用以查询插件信息(还用来获取NPObject/Scriptable 插件的实例) |
NPP_SetValue | This call is used to inform plugins of variable information controlled by the browser. 这是用来为浏览器提供插件变量信息的 |
We will talk more about the specifics of how each of these functions works later.
稍后我们将更多地讨论这些函数如何工作的具体细节。
NPError WINAPI NP_Initialize(NPNetscapeFuncs *aNPNFuncs) // Windows
// -or-
NPError NP_Initialize(NPNetscapeFuncs *aNPNFuncs, NPPluginFuncs *aNPPFuncs) // Linux
As noted in the documentation, NP_Initialize provides global initialization for a plug-in. The API reference is a little confusing in that it claims that NP_Initialize is actually the first function called by the browser. The reason for this is that on linux, there aparently is no NP_GetEntryPoints call; instead, the NPPluginFuncs struct is passed into the NP_Initialize function to be filled out.
如文档中所述, NP_Initialize 为插件提供全局初始化。 API 引用有点令人困惑, 因为根据声明, NP_Initialize 实际上是浏览器调用的第一个函数。 这样做的原因是, 在 Linux 上, 没有 NP_GetEntryPoints 调用;相反地, NPPluginFuncs 结构体传递传入 NP_Initialize 函数中。
Since this isn’t confusing enough, on Mac they have replaced both of these functions with a single “main” function that not only gets passed both function pointer structures (one with the browser functions and one to be filled out with plugin functions) but also is given a shutdown function pointer to be filled out with the address to the NP_Shutdown function.
这还不算是最难理解的, 在 Mac 上, 他们已经用一个 “main” 函数替代了这两个函数, 该函数不仅传递了两个函数指针结构体 (一个填充浏览器函数, 一个填充插件函数), 而且还可以传递了一个指向 NP_Shutdown 函数的指针。
Because of these discrepancies, I recommend that you write a separate function for filling out the NPPluginFuncs structure so that it can be called from any of the various init functions.
由于这些差异, 我建议您编写一个单独的函数来填充 NPPluginFuncs 结构体, 以便可以从各种初始化函数调用它。
The NPNetscapeFuncs structure that is passed in is the complement to the NPPluginFuncs structure that we have discussed previously. As might be guessed from the name, the NPNetscapeFuncs structure contains pointers to browser functions that can be called by the plugin. There are a lot more of these, and we will discuss more about how to use them next time.
NPNetscapeFuncs 结构体是对我们前面讨论过的 NPPluginFuncs 结构体的补充。 从名称中可以看出, NPNetscapeFuncs 结构体包含指向插件可以调用的浏览器函数的指针。我们下次会进一步讨论其内部细节及其用法。
For now, take a look at the structure to get a general idea of what is there. I’ve added comments on some of the more common function calls.
现在, 通过这个结构体, 就会对里面的东西有一个大致的了解。 对一些比较常见的函数调用,我添加了注释。
typedef struct _NPNetscapeFuncs {
uint16 size;
uint16 version; // Newer versions may have additional fields added to the end 较新的版本可能会在末尾添加其他字段
NPN_GetURLUPP geturl; // Make a GET request for a URL either to the window or another stream 对窗口或其他流的 URL 发出 GET 请求
NPN_PostURLUPP posturl; // Make a POST request for a URL either to the window or another stream 对窗口或其他流的 URL 发出 POST 请求
NPN_RequestReadUPP requestread;
NPN_NewStreamUPP newstream;
NPN_WriteUPP write;
NPN_DestroyStreamUPP destroystream;
NPN_StatusUPP status;
NPN_UserAgentUPP uagent;
NPN_MemAllocUPP memalloc; // Allocates memory from the browser's memory space 从浏览器的内存空间分配内存
NPN_MemFreeUPP memfree; // Frees memory from the browser's memory space 从浏览器的内存空间中释放内存
NPN_MemFlushUPP memflush;
NPN_ReloadPluginsUPP reloadplugins;
NPN_GetJavaEnvUPP getJavaEnv;
NPN_GetJavaPeerUPP getJavaPeer;
NPN_GetURLNotifyUPP geturlnotify; // Async call to get a URL 对URL的异步调用GET
NPN_PostURLNotifyUPP posturlnotify; // Async call to post a URL 对URL的异步调用POST
NPN_GetValueUPP getvalue; // Get information from the browser 从浏览器获取信息
NPN_SetValueUPP setvalue; // Set information about the plugin that the browser controls 设置有关浏览器控制的插件的信息
NPN_InvalidateRectUPP invalidaterect;
NPN_InvalidateRegionUPP invalidateregion;
NPN_ForceRedrawUPP forceredraw;
NPN_GetStringIdentifierUPP getstringidentifier; // Get a NPIdentifier for a given string 根据给定的字符串获得NPIdentifier
NPN_GetStringIdentifiersUPP getstringidentifiers;
NPN_GetIntIdentifierUPP getintidentifier;
NPN_IdentifierIsStringUPP identifierisstring;
NPN_UTF8FromIdentifierUPP utf8fromidentifier; // Get a string from a NPIdentifier 根据给定的NPIdentifier获得字符串
NPN_IntFromIdentifierUPP intfromidentifier;
NPN_CreateObjectUPP createobject; // Create an instance of a NPObject 创建 NPObject 的实例
NPN_RetainObjectUPP retainobject; // Increment the reference count of a NPObject 增加 NPObject 实例引用计数
NPN_ReleaseObjectUPP releaseobject; // Decrement the reference count of a NPObject 减少 NPObject 实例引用计数
NPN_InvokeUPP invoke; // Invoke a method on a NPObject 调用 NPObject 上的方法
NPN_InvokeDefaultUPP invokeDefault; // Invoke the default method on a NPObject 调用 NPObject 上的默认方法
NPN_EvaluateUPP evaluate; // Evaluate javascript in the scope of a NPObject 在 NPObject 作用域内执行 Javascript 脚本
NPN_GetPropertyUPP getproperty; // Get a property on a NPObject 获得 NPObject 属性
NPN_SetPropertyUPP setproperty; // Set a property on a NPObject 设置 NPObject 属性
NPN_RemovePropertyUPP removeproperty; // Remove a property from a NPObject 移除 NPObject 属性
NPN_HasPropertyUPP hasproperty; // Returns true if the given NPObject has the given property 当 NPObject 存在指定属性时返回TRUE
NPN_HasMethodUPP hasmethod; // Returns true if the given NPObject has the given Method 当 NPObject 存在指定方法时返回TRUE
NPN_ReleaseVariantValueUPP releasevariantvalue; // Release a MNVariant (free memory) 释放 MNVariant (释放内存)
NPN_SetExceptionUPP setexception;
NPN_PushPopupsEnabledStateUPP pushpopupsenabledstate;
NPN_PopPopupsEnabledStateUPP poppopupsenabledstate;
} NPNetscapeFuncs;
In addition to saving the function pointers given so that browser calls can be made, any memory that is to be shared by all instances of your browser plugin should be initialized here.
除了保存上述函数指针以便进行浏览器调用外, 浏览器插件的所有实例共享的内存都应在此处初始化。
This is the simplest of the three entrypoints. Free any shared memory and release any shared resources. This is called when the browser has already destroyed all instances of your plugin (by calling NPP_Destroy) and does not expect to create any more in the near future.
这是三个入口函数中最简单的一个。当浏览器已经销毁了插件的所有实例 (通过调用 NPP_Destroy ), 并且不再创建该实例时, 就会通过该函数释放任何共享的内存和资源。
Next time we will go into greater detail on implementing the NPP functions and also cover some of the most commonly used NPN functions.
后面,我们将更详细地介绍如何实现 NPP 函数, 并介绍一些最常用的 NPN 函数。
Building a firefox plugin – part one
Building a firefox plugin – part two
Building a firefox plugin – part three
Building a firefox plugin – part four
Update May 14, 2011: Many people have been asking questions in the comments; while I don’t mind that, it would probably be more useful if you ask your question on the FireBreath forums. There is a forum there for those just using NPAPI as well!
2011年5月14日更新 许多人一直在评论中提问;如果我没有注意到你的问题,那么在 FireBreath 论坛可能会更好,该论坛也可以讨论纯 NPAPI 的问题。