Building a firefox plugin – part two

May 26, 2009 53 Comments by 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.

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:

  1. NP_GetEntryPoints – Plugin fills out a function table with the addresses of NPP_NewNPP_DestroyNPP_SetWindow, etc
  2. NP_Initialize – Plugin stores a copy of a function table with the addresses of NPN_CreateObject, NPN_MemAlloc, etc
  3. NPP_New – Plugin creates a new instance of itself and initializes it
  4. *NPP_SetWindow – This is called multiple times for each instance — each time the instance’s window is created, resized, or otherwise changed
  5. NPP_GetValue (Variable = NPPVpluginScriptableNPObject) – Plugin creates a scriptable NPObject and returns a pointer to it (after calling NPN_RetainObject on it)
  6. — Standard Plugin Activity —
  7. NPP_Destroy – Plugin instance is destroyed
  8. 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_New is called once for each instance of the plugin.  Note the difference in terminology: There is one plugin, and there are multiple instances of that plugin.  NP_Initialize is only called once for the plugin – that is, the entire memory space.
From the Gecko SDK source:
/*
 *  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:

  1. Create some sort of data structure to uniquely identify this instance of the plugin
  2. 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!