This post describes construction of plugin for browser using NPAPI.
Disclaimer. This information contains only Linux specific details of NPAPI plugin construction.
What a plugin in general is you can read at Wikipedia. What is a browser plugin? A browser plugin is a software that extends the capabilities of browser providing it with functionality needed for displaying custom content formats. For example you can see plugins for displaying Flash SWF objects, PDF documents, embedded Java applications.
How do plugins work? It’s pretty simple. Specific content can be embedded on a web page or opened directly from a browser. When a browser encounters such content, it searches in its plugin directory for a plugin that can handle such content and if found, it then creates the plugin process and pass that content to it. The plugin then can draw itself on a webpage and receive custom events such as keyboard and mouse input.
From previous paragraph you can understand how plugins work from the user perspective. But how do they work internally? What’s the code?
Ok. First of all, a browser do almost all the work. There are two kinds of API functions: NPP and NPN. NPP functions are plugin-side functions called by the browser when special events occur (plugin creation, user input, plugin destroying). NPN_ functions are implemented on the browser side and can be called by the plugin. So, the plugin must implement NPP functions, while NPN functions are browser-side services available for the plugin.
When the browser loads plugin it calls a special function, called NP_Initialize. This function is a must for implementing. Two parameters passed by browser: nppfuncs and npnfuncs. One is a pointer to table of NPP functions, another – NPN functions. Both pointers point to the special structs which stores function pointers. The plugin must fill NPPfuncs table with pointer to functions implemented by the plugin. NPNfuncs table is needed for the plugin to be able to call that browser-implemented functions.
How the plugin can tell the browser which content types it can handle? In Unix, you must implement another special function NP_GetMIMEDescription. In Windows that information is provided with dll resources.
How many plugin processes created when I embed many objects handled by plugin? As for Firefox, it creates one plugin process for every browser instance. As already mentioned, when a browser loads plugin, NP_Initialize gets called. When a browser encounters specific content associated with plugin it calls NP_Initialize if plugin is not loaded, and then calls another special function NPP_New for every content stream associated with that plugin. That NPP_New functions creates “a plugin instance”. So, the Firefox browser creates only one plugin process. Chrome also creates one “sandboxed” process for all the plugin instances (source).
Where can I store my data? In a global namespace? But how then can I store different data for different plugin instances? Or is there another way of store my data between NPP function calls? Yes, there is. Every NPP function has a parameter “NPP” which is a pointer to the special struct associated with a specific plugin instance. You can allocate memory for your data and save a pointer to it in that struct. Don’t forget to destroy it as well. A browser calls NPP_Destroy when a plugin instance is destroyed.
To start coding you’ll need NPAPI headers that can be downloaded here, compiler and an NPAPI enabled browser (Firefox should be fine).
For UNIX, besides NP_Initialize you should additionally implement NP_GetMIMEDescription and NP_GetValue.
// Register mime types and description for UNIX
// (Windows declares it in resources)
// Plugin's mime types
#define MIME_TYPE_DESCRIPTION "application/sample-plugin:file-extension:Description"
const char* NP_GetMIMEDescription() {
return MIME_TYPE_DESCRIPTION;
}
// Plugin's name and description
NPError OSCALL NP_GetValue(void*, NPPVariable, void* out) {
return NPERR_NO_ERROR;
}
// Initializes plugin
NPError NP_Initialize(NPNetscapeFuncs* npnfuncs, NPPluginFuncs* nppfuncs) {
if(npnfuncs == NULL)
return NPERR_INVALID_FUNCTABLE_ERROR;
if(HIBYTE(npnfuncs->version) > NP_VERSION_MAJOR)
return NPERR_INCOMPATIBLE_VERSION_ERROR;
cout << "Plugin Initialized:" << npnfuncs << endl;
// TODO save npnfuncs pointer
NP_GetEntryPoints(nppfuncs);
return NPERR_NO_ERROR;
}
// Set table of functions called by browser.
NPError NP_GetEntryPoints(NPPluginFuncs* pFuncs) {
if (pFuncs == NULL)
return NPERR_INVALID_FUNCTABLE_ERROR;
pFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
pFuncs->newp = NPP_New;
pFuncs->javaClass = NULL;
return NPERR_NO_ERROR;
}
// new plugin instance
NPError NPP_New(NPMIMEType pluginType,
NPP instance, uint16_t mode,
int16_t argc, char *argn[],
char *argv[], NPSavedData *saved) {
cout << "NPP_New" << endl;
return NPERR_NO_ERROR;
}
The code should speak for itself.
Compile it with:
gcc -c plugin.cpp
gcc -shared -o plugin.o
Install it with
sudo ln -s `pwd`/simple-plugin.so /usr/lib/mozilla/plugins/simple-plugin.so
To test plugin you’ll need html file with this body:
<h1>Developing a browser plug-in</h1>
<embed type="application/simple-plugin" />
To run firefox from console use “-console” command line argument (firefox -console). When you open test html document you should see something like that:
Plugin Initialized:0xb7729ce8
NPP_New
See part two of this series of posts.
Hm, 42nd post…
2010-10-04