Building a firefox plugin – part one
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.
Introduction
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 =]
Plugin architecture
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.
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.
Plugin API vs Scripting API
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.
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.
NPAPI
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: http://en.wikipedia.org/wiki/NPAPI
What makes up an NPAPI browser plugin?
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:
- NP_GetEntryPoints – Called immediately after the plugin is loaded and is used by the browser (no longer just netscape) to get pointers to all of the API functions that the browser might need to call.
- NP_Initialize – Provides global initialization of your plugin.
- NP_Shutdown – Provides global deinitialization of your plug-in.
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).
Since these three entrypoints provide the core of the NPAPI architecture, it is worth our time to look at them a little more closely.
NP_GetEntryPoints
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.
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:
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.
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.
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.
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:
API Name | 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_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. |
NPP_URLNotify | Notifies the completion of a URL request. |
NPP_GetJavaClass | Deprecated / No longer used. Set to NULL |
NPP_GetValue | Called to query the plugin for information (also used to get an instance of a NPObject/Scriptable Plugin) |
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.
NP_Initialize
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.
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.
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.
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.
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
NPN_PostURLUPP posturl; // Make a POST request for a URL either to the window or another stream
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
NPN_PostURLNotifyUPP posturlnotify; // Async call to post a URL
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
NPN_GetStringIdentifiersUPP getstringidentifiers;
NPN_GetIntIdentifierUPP getintidentifier;
NPN_IdentifierIsStringUPP identifierisstring;
NPN_UTF8FromIdentifierUPP utf8fromidentifier; // Get a string from a NPIdentifier
NPN_IntFromIdentifierUPP intfromidentifier;
NPN_CreateObjectUPP createobject; // Create an instance of a NPObject
NPN_RetainObjectUPP retainobject; // Increment the reference count of a NPObject
NPN_ReleaseObjectUPP releaseobject; // Decrement the reference count of a NPObject
NPN_InvokeUPP invoke; // Invoke a method on a NPObject
NPN_InvokeDefaultUPP invokeDefault; // Invoke the default method on a NPObject
NPN_EvaluateUPP evaluate; // Evaluate javascript in the scope of a NPObject
NPN_GetPropertyUPP getproperty; // Get a property on a NPObject
NPN_SetPropertyUPP setproperty; // Set a property on a NPObject
NPN_RemovePropertyUPP removeproperty; // Remove a property from a NPObject
NPN_HasPropertyUPP hasproperty; // Returns true if the given NPObject has the given property
NPN_HasMethodUPP hasmethod; // Returns true if the given NPObject has the given Method
NPN_ReleaseVariantValueUPP releasevariantvalue; // Release a MNVariant (free memory)
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.
NP_Shutdown
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.
Next Time
Next time we will go into greater detail on implementing the NPP functions and also cover some of the most commonly used NPN functions.
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!
taxilian
12 years ago
This is a ridiculous place to ask that question; ask on stackoverflow.com
Roimer García
12 years ago
Hi Taxilian, I’m developing a NPAPI plugin witn fireBreath, but It looks like the Firebreath’s forums are down. I really would like to read and ask some questions there…
taxilian
12 years ago
I have updated the post; the forums were somewhat of a failed experiment. Few besides me monitored it and I didn’t have time to do everything. Use Stackoverflow.com and add the npapi and firebreath tags; that’s the best way to ask a question. Be a good citizen and mark answers as answered and upvote when you can; that’ll make people want to help you more. I monitor those two tags, though.
Murasoli Selvan
12 years ago
Can you help me to make it work in chrome, mine is working in firefox but not in chrome, i am following npruntime example, can you help me