Building a firefox plugin – part two
Note: For a better way to create a Browser Plugin (that will work on all browsers, not just NPAPI), check out the FireBreath project.
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.
Recap
Last time, I talked about the fundamentals of implementing a NPAPI plugin. Today, I’m going to go into a little more detail on how to use the strange callback architecture that firefox exposes.
The NPAPI uses a rather unique (in my experience) methodology for providing an interface between the plugin and browser, but it does have some rather significant advantages. Instead of providing binaries to be linked against, the browser simply expects you to implement the entry point NP_Initialize in your dll that the browser will call and tell you where the methods that you will be using are located. In order for the browser to interface with you, it calls NP_GetEntryPoints with a pointer to a structure for your plugin to fill out with function pointers that the browser will be calling on your plugin.
This removes the need for a large number of include and library files in your project. In fact, in practice I found that I only actually needed 12 include files from the Gecko SDK:
- jni.h
- jni_md.h
- jri.h
- jritypes.h
- jri_md.h
- npapi.h
- npruntime.h
- nptypes.h
- npupp.h
- obsolete/protypes.h
- prcpucfg.h
- prtypes.h
Edit (Sep 16, 2010): Now over a year later there is a better way to do this. There is a Google Code project called npapi-headers that has just the header files you need — and they actually work on all browsers, all platforms (unlike the ones from the gecko SDK sometimes).
Basic Lifecycle of a plugin:
Jean-Lou Dupont has an excelent visual diagram of this on his blog, which you can find here: http://jldupont.blogspot.com/2009/11/notes-on-npapi-based-plugins.html
The lifecycle of a plugin is actually pretty simple, once you figure it out. The initial entrypoints NP_Initialize and NP_GetEntryPoints could, according to the documentation, get called in any order; however, in practice, NP_GetEntryPoints seems to get called first on Windows. With that in mind, here is the order of calls for basic windowed plugin initialization on Windows:
- NP_GetEntryPoints – Plugin fills out a function table with the addresses of NPP_New, NPP_Destroy, NPP_SetWindow, etc
- NP_Initialize – Plugin stores a copy of a function table with the addresses of NPN_CreateObject, NPN_MemAlloc, etc
- NPP_New – Plugin creates a new instance of itself and initializes it
- *NPP_SetWindow – This is called multiple times for each instance — each time the instance’s window is created, resized, or otherwise changed
- NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – Plugin creates a scriptable NPObject and returns a pointer to it (after calling NPN_RetainObject on it)
- — Standard Plugin Activity —
- NPP_Destroy – Plugin instance is destroyed
- NP_Shutdown – All remaining plugin resources are destroyed
It is worth our time to investigate a few of these calls in more detail.
NPError NPP_New(NPMIMEType pluginType, NPP npp, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)
/*
* NPP is a plug-in's opaque instance handle
*/
typedef struct _NPP
{
void*pdata; /* plug-in private data */
void*ndata; /* netscape private data */
} NPP_t;
typedef NPP_t* NPP;
This structure is normally seen as NPP and serves as the handle of a plugin instance. As you can see, there is private data for the plugin and private data for the browser. When NPP_New is called, you should:
- Create some sort of data structure to uniquely identify this instance of the plugin
- Assign a pointer to that data structure to npp->pdata
What I like to do is to create a PluginInstance class and use that as my “pdata”. Then my NPP_New function looks like this:
// Called by the browser to create a new instance of the plugin
NPError NPP_New(NPMIMEType pluginType,
NPP npp,
uint16 mode,
int16 argc,
char* argn[],
char* argv[],
NPSavedData* saved)
{
if (npp == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
NPError rv = NPERR_NO_ERROR;
PluginInstance* pPluginObj = new PluginInstance(npp);
if (pPluginObj == NULL)
return NPERR_OUT_OF_MEMORY_ERROR;
npp->pdata = pPluginObj;
return pPluginObj->NpapiNew(pluginType, mode, argc, argn, argv, saved);
}
In this way I can have a PluginInstance class that handles the entire lifecycle of a given instance of a plugin, and I simply create stub static functions to dereference the pdata pointer and call the correct function.
NPP_SetWindow (NPP npp, NPWindow* pNPWindow)
For many, this will be where the real fun starts — this function is called to tell the plugin which window they are in. From the Gecko SDK (npapi.h):
typedef struct _NPWindow
{
void* window; /* Platform specific window handle */
/* OS/2: x - Position of bottom left corner */
/* OS/2: y - relative to visible netscape window */
int32 x; /* Position of top left corner relative */
int32 y; /* to a netscape page. */
uint32 width; /* Maximum window size */
uint32 height;
NPRect clipRect; /* Clipping rectangle in port coordinates */
/* Used by MAC only. */
void * ws_info; /* Platform-dependent additonal data, linux specific */
NPWindowType type; /* Is this a window or a drawable? */
} NPWindow;
A pointer to this structure is passed in with each call. On windows, the “void* window” will dereference to an HWND. On other platforms, it will likewise be dereferenced as an appropriate type.
Notice that again NPP npp is the first parameter. This will be the case on all NPP functions except NPP_New, where the mimetype is also passed in. Since we created a PluginInstance object and assigned it to npp->pdata in NPP_New, we need to create a small stub function to forward our NPP_New to a method on that object, like so:
// Called by browser whenever the window is changed, including to set up or destroy
NPErrorNPP_SetWindow (NPP npp, NPWindow* pNPWindow)
{
if (npp == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
else if (npp->pdata == NULL)
return NPERR_GENERIC_ERROR;
PluginInstance *inst = (PluginInstance *)npp->pdata;
return inst->NpapiSetWindow(pNPWindow);
}
On windows, when SetWindow is called we need to save the HWND and subclass the window so that we can get our own window event proc.
NPError PluginInstance::NpapiSetWindow (NPWindow* pNPWindow)
{
NPError rv = NPERR_NO_ERROR;
if(pNPWindow == NULL)
return NPERR_GENERIC_ERROR;
// window just created; in initWindow, set initialized to true
if(!this->initialized) {
if(!this->initWindow(pNPWindow)) {
return NPERR_MODULE_LOAD_FAILED_ERROR;
}
}
// Window was already created; just pass on the updates
this->updateWindow(pNPWindow);
return rv;
}
With these functions, we get notified in one function when the window is first set, and another is called each time an update is made.
NPP_Destroy (NPP npp, NPSavedData** save)
When the Browser is ready to shut down a given plugin instance (the user leaves the page or the object tag is removed from the DOM), the browser calls NPP_Destroy. This call is responsible to free any memory being used by that plugin instance.
// Called by browser to destroy an instance of the plugin
NPError NPP_Destroy (NPP npp, NPSavedData** save)
{
if (npp == NULL)
return NPERR_INVALID_INSTANCE_ERROR;
else if (npp->pdata == NULL)
return NPERR_GENERIC_ERROR;
PluginInstance *inst = (PluginInstance *)npp->pdata;
NPError rv = inst->NpapiDestroy(save);
delete getPluginObject(npp);
npp->pdata = NULL;
return rv;
}
Next Time
That’s all the time I have today. Next time I will discuss NPObjects and how they are used to provide an interface by which Javascript can interact with your plugin.
Building a firefox plugin – part one
Building a firefox plugin – part two
Building a firefox plugin – part three
Building a firefox plugin – part four
Getting more help
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!
jasta ko
12 years ago
Hi Taxilian,
Thank you for your prompt response. I’m already awared of FB development, but I want also to know how to develop purely native FF plugins using npruntime/NPAPI, without FB layer. The one made by me (FB) works fine, but still not sure about Mozilla SDK. Yes I know, it is highly recommended to use FB instead of NPAPI, but we’re running new project, and have to perform brief training session on web browsers plugins development, where want to cover known approaches of doing so, like ActiveX, FireBreath and NPAPI. The first two already works fine, in contrary to the last one, which i didn’t figure out yet.
I’d appreciate help for this,
Kuba
taxilian
12 years ago
You seem to be misunderstanding what FireBreath is. FireBreath is not an alternate method for creating browser plugins; FireBreath plugins *are* NPAPI plugins. They are also ActiveX controls. Thus even if you don’t want to use FireBreath for your plugin, it is still the best example I can give you of a working NPAPI plugin.