Building a firefox plugin – part three

Posted on August 4th, 2009 by Richard | Categories: Browser Plugin Development, NPAPI | Tags: , , , , , , , ,

Note: if you haven’t already, please read up on FireBreath, the open source cross-platform plugin framework, and consider contributing.

Previous posts

The purpose of this post is to cover the basics of providing an interface by which javascript can interface with an NPAPI plugin in a cross platform manner.  The primary focus of this post will be on npruntime, which is recommended by the Mozilla development team (or at least seems to be from the meager and confusing documentation available from Mozilla).  More importantly, of course, it is the framework that I recommend, which is all-important, since I’m the one writing this post. =]

Previous posts

If you are new to NPAPI plugins, you may want to go back and read part one and part two of this series, which cover the basic architecture and lifecycle of an NPAPI plugin.

What is npruntime?

I’m so glad you ask.  This confused the daylights out of me when I first got into building browser plugins; there are references to XPCOM and references to LiveConnect and references to NPRuntime, which I never did quite get straightened out until I ran into a Wikipedia article on NPAPI.  I recommend that you read it if you want further background information.

Beyond the simple question of “what is recommended?” is the consideration of practical needs.  For me, the primary considerations that led me to decide that NPRuntimeis the only mechanism that should be used on any new plugin are:

  • NPRuntime allows dynamic interfaces — more on this, and why it is useful, later
  • NPRuntime is the plugin scripting standard supported by Mac Plugins, Safari (even on windows), Chrome, and Opera.  My goal is always to make things as cross-platform as possible.
  • Firefox 3.6 drops support for XPCOM plugins (e.g. xpt file scripting interfaces aren’t recognized)

Getting Started

Throughout this post, I will be referring to Scriptable Objects a lot.  A Scriptable Object is simply an object that can be “scripted”, or accessed and used by javascript.  In firefox, a Scriptable Object is always a NPObject:

1
2
3
4
5
6
7
struct NPObject {
  NPClass *_class;
  uint32_t referenceCount;
  /*
   * Additional space may be allocated here by types of NPObjects
   */
};

As you can see, a NPObject is nothing more than a reference counted object with a pointer to a NPClass.  As of the latest (as of this writing) Gecko SDK, 1.9, NPClass is defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct NPClass
{
  uint32_t structVersion; // Version of the structure (to determine which features may be missing)
  NPAllocateFunctionPtr allocate; // Called to create a new instance of this object
  NPDeallocateFunctionPtr deallocate; // Called to free the instance of this object
  NPInvalidateFunctionPtr invalidate; // Called on live objects that belong to a plugin instance that is being destroyed. This call is always followed by a call to the <code>deallocate</code> function, or <code>free()</code>. Any attempt to use an invalidated object will result in undefined behavior.
  NPHasMethodFunctionPtr hasMethod; // Called to query if a method exists on the NPObject
  NPInvokeFunctionPtr invoke; // Used to invoke a method on the NPObject
  NPInvokeDefaultFunctionPtr invokeDefault; // Used to invoke the NPObject as a function
  NPHasPropertyFunctionPtr hasProperty; // Called to query if a property exists on the NPObject
  NPGetPropertyFunctionPtr getProperty; // Called to get the value of a property on the NPObject
  NPSetPropertyFunctionPtr setProperty; // Called to set the value of a property on the NPObject
  NPRemovePropertyFunctionPtr removeProperty; // Called to remove/delete a property on the NPObject
  NPEnumerationFunctionPtr enumerate; // Used to get a list of properties and methods that exist on the NPObject (new in FF 3, Gecko SDK 1.9)
  NPConstructFunctionPtr construct; // Used when the new keyword is called in javascript to create a new instance of the NPObject (new in FF 3, Gecko SDK 1.9)
};

Making it Object Oriented

The Gecko SDK is intentionally written to use structs instead of objects; this allows it to work across many platforms, with many different types of compilers, and never worry about binary compatibility.  If you haven’t figured it out yet, the NPClass structure contains a list of function pointers that should be called by the browser (or anyone else) to talk to the NPObject.  These should never be called explicitly, but rather they should be called through the NPN_ functions that I briefly mentioned towards the end of part one.  When one wishes to create a new instance of an NPObject, you call the NPN_CreateObject function and give it the class that should be used.

Fortunately, since all the browser cares about is the NPClass, its function pointers, and the reference count, any class that inherits from NPObject can easily satisfy that requirement; that means that it is not difficult to make a smart NPObject; simply create static methods for each of the pointers in the NPClass struct, dereference the NPObject* that they pass in, and call a member function.

Simple class definition and implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "npapi.h"
#include "npupp.h"
 
class MyScriptableNPObject : public NPObject
{
protected:
    // Class member functions that do the real work
    void Deallocate();
    void Invalidate();
    bool HasMethod(NPIdentifier name);
    bool Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
    bool InvokeDefault(const NPVariant *args, uint32_t argCount, NPVariant *result);
    bool HasProperty(NPIdentifier name);
    bool GetProperty(NPIdentifier name, NPVariant *result);
    bool SetProperty(NPIdentifier name, const NPVariant *value);
    bool RemoveProperty(NPIdentifier name);
    bool Enumerate(NPIdentifier **identifier, uint32_t *count);
    bool Construct(const NPVariant *args, uint32_t argCount, NPVariant *result);
public:
    // This is the method used to create the NPObject
    // This method should not be called explicitly
    // Instead, use NPN_CreateObject
    static NPObject* Allocate(NPP npp, NPClass *aClass) {
        return (NPObject *)new MyScriptableNPObject(npp);
    }
 
    /////////////////////////////
    // Static NPObject methods //
    /////////////////////////////
    static void _Deallocate(NPObject *npobj);
    static void _Invalidate(NPObject *npobj);
    static bool _HasMethod(NPObject *npobj, NPIdentifier name);
    static bool _Invoke(NPObject *npobj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
    static bool _InvokeDefault(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result);
    static bool _HasProperty(NPObject * npobj, NPIdentifier name);
    static bool _GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result);
    static bool _SetProperty(NPObject *npobj, NPIdentifier name, const NPVariant *value);
    static bool _RemoveProperty(NPObject *npobj, NPIdentifier name);
    static bool _Enumerate(NPObject *npobj, NPIdentifier **identifier, uint32_t *count);
    static bool _Construct(NPObject *npobj, const NPVariant *args, uint32_t argCount, NPVariant *result);
 
    static NPClass _npclass;
 
protected:
    NPP m_Instance;
};

Due to code ownership issues, I’m not able to give you the actual code that I’m using, but if there are any compiler errors I’m sure they’ll be simple to straighten out.  The implementation for these static members will be pretty straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// static
void MyScriptableNPObject::_Invalidate(NPObject *obj) {
    ((MyScriptableNPObject*)obj)-&gt;Invalidate();
}
void MyScriptableNPObject::Invalidate() {
    // Invalidate the control however you wish
}
 
// static
void MyScriptableNPObject::_Deallocate(NPObject *obj) {
    ((MyScriptableNPObject*)obj)-&gt;Deallocate();
    delete ((MyScriptableNPObject*)obj);
}
void MyScriptableNPObject::Deallocate() {
    // Do any cleanup needed
}

For brevity, I’m not going to include all of the methods here; I will go into more detail on them later.  For now, suffice it to say that after you have implemented the rest of these functions, all that is left is to define the NPClass itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NPClass MyScriptableNPObject::_npclass = {
    NP_CLASS_STRUCT_VERSION,
    MyScriptableNPObject::Allocate,
    MyScriptableNPObject::_Deallocate,
    MyScriptableNPObject::_Invalidate,
    MyScriptableNPObject::_HasMethod,
    MyScriptableNPObject::_Invoke,
    MyScriptableNPObject::_InvokeDefault,
    MyScriptableNPObject::_HasProperty,
    MyScriptableNPObject::_GetProperty,
    MyScriptableNPObject::_SetProperty,
    MyScriptableNPObject::_RemoveProperty,
    MyScriptableNPObject::_Enumerate,
    MyScriptableNPObject::_Construct
};

Creating the NPObject

Once you have your custom NPObject created and your NPClass defined, we can cover the basics of the NPObject lifecycle.  Creating an NPObject is pretty straightforward:

1
2
NPObject *newObj = NPN_CreateObject(m_Instance, &amp;MyScriptableNPObject::_npclass);
NPN_RetainObject(newObj);

As you can see, all that is needed is the NPP instance handler (see part two) and a pointer to the class.  Since the class is very specific to a specific NPObject, I like to create a static function to instantiate the object, like so:

1
2
3
4
static MyScriptableNPObject* NewObject(NPP npp) {
    MyScriptableNPObject* newObj = (MyScriptableNPObject*)NPN_CreateObject(npp, &amp;_npclass);
    return newObj;
}

This is completely voluntary, of course, but it also allows you some additional control of the creation process; for example, you may want to pass in additional parameters and have them set on the object as soon as it is created.

NPObject lifecycle

As you might guess, NPN_RetainObject increments the reference count on a given NPObject.  Similarly, NPN_ReleaseObject decrements the reference count.  When the reference count hits zero, the object will be destroyed by calling the specified Deallocate function, just like the Allocate function is called by NPN_CreateObject to create the object.

When code in one DLL allocates something and it is deallocated by a different DLL, heap corruption can (and often does) occur.  For this reason, all things that belong to you will ultimately get created and destroyed in your code.  All things that belong to the browser will get created and freed in browser code.  This way everyone stays happy =]  Make sure you keep track of your Retains and Releases; just like any reference counting, this can cause you a lot of hard to track problems.

Another important thing to keep in mind about the lifecycle of a NPObject is that you don’t know exactly when it will go away; the browser will release it whenever it chooses to do so.  Therefore, you need to be able to handle things gracefully even if things are destroyed in a different order than you expect.

Giving the correct NPObject to the browser

Now that you know what an NPObject is and rougly what it does, you need to know how the browser finds out about it.  If you’ll refer back to NPN_RetainObject on it.)”  Simply put, at that point in the initialization sequence (or somewhere in there; the spec is ambiguous about the order), the Browser will call GetValue on the plugin and ask for a NPObject.  If our plugin supports NPObjects, it simply needs to create one, call NPN_RetainObject on it, and return it as a void pointer.  *NOTE: Make sure that you typecast it as a NPObject* before assigning it to the void pointer; I made that mistake once and spent a week trying to figure out why the wrong functions were getting called.

Here is an example implementation of GetValue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
NPError PluginInstance::NpapiGetValue(NPPVariable variable, void *value)
{
   NPError rv = NPERR_NO_ERROR;
   switch(variable)
   {
      case NPPVpluginNameString:
          value = *((char **)value) = STRINGS_PRODUCTNAME;
          break;
      case NPPVpluginDescriptionString:    // Plugin description
          *((char **)value) = STRINGS_FILEDESCRIPTION;
          break;
      case NPPVpluginScriptableNPObject:// Scriptable plugin interface (for accessing from javascript)
          *(NPObject **)value = this-&gt;getScriptableObject();
          break;
      case NPPVpluginWindowBool:
          *((PRBool *)value) = this-&gt;isWindowed;
          break;
      default:
          rv = NPERR_GENERIC_ERROR;
  }
  return rv;
}
 
NPObject *PluginInstance::getScriptableObject()
{
   NPObject *retval;
   if (this-&gt;npobj != NULL)
      retval = (NPObject *)this-&gt;npobj;
   else
      retval = (NPObject *)MyScriptableNPObject::NewObject(this-&gt;npp);
 
   NPN_RetainObject(retval);
 
   return retval;
}

HasMethod and HasProperty

On to the meat of the article: Say you have a javascript function like so:

var objTag = document.getElementById("myObjectTag");
alert(objTag.doSomeCoolFunction);

You may think that this looks a little strange, since “doSomeCoolFunction” sounds like a function call, but it is clearly accessed like a parameter here.  In Javascript, this is legal even if that is a function, because it can be used to determine if a function exists without calling it.  Therefore, you cannot have a method and a property on the same object with the same name.

When this call is made, the browser will call two functions on the NPObject:

  1. HasMethod(NPIdentifier name);
  2. HasProperty(NPIdentifier name);

The purpose of these two calls is to ascertain whether or not a property or method exists on the NPObject (as you might have guessed).  So, what is with this NPIdentifier thing?  Well, a NPIdentifier can map to either an integer or a string.  a NPIdentifier can be converted to a string (if it is a string identifier) with NPN_UTF8FromIdentifier.  To find out if it is a string identifier, use NPN_IdentifierIsString.  If it is an integer, you can use NPN_IntFromIdentifier.

Now, the purpose of integer identifiers may not be immediately apparent to everyone; it wasn’t to me at first.  Once I figured it out, however, I was thrilled.  If you are familiar with javascript, you are doubtlessly familiar with the concept that everything in javascript is an object; arrays are actually just objects with integer keys.  So, to allow array-style access, simply handle integer identifiers!

Once you have converted the identifier into something you can deal with, decide if your object has that property or method (I’ll let you figure out which goes in which function) and return true or false.

Oh, and after you get a string wtih NPN_UTF8FromIdentifier, don’t forget to release the memory with NPN_MemFree().

NPVariant

Before I go any further, I need to spend a brief paragraph or two on the NPVariant structure.

1
2
3
4
5
6
7
8
9
10
typedef struct _NPVariant {
  NPVariantType type;
  union {
    bool boolValue;
    int32_t intValue;
    double_t doubleValue;
    NPString stringValue;
    NPObject *objectValue;
  } value;
} NPVariant;

NPVariant is the datatype used by the browser to pass variables into and out of NPObjects.  From the MDC docs, the following types are valid:

JavaScript type NPVariantType
undefined NPVariantType_Void
null NPVariantType_Null
boolean NPVariantType_Bool
number NPVariantType_Int32 or NPVariantType_Double
string NPVariantType_String
All other types NPVariantType_Object

Since it’s getting late and I’ve already put too much in this post (I’m over 2000 words), I’ll leave it as an excercise to the reader to read up on the macros and such for using this structure.

Properties

At this point, you should be getting a pretty good idea of how everything works.  Getting and setting properties is fairly straightforward:

1
2
    bool GetProperty(NPIdentifier name, NPVariant *result);
    bool SetProperty(NPIdentifier name, const NPVariant *value);

To get a property, simply check the name to see what you’re going to be doing and store the value to return in *result.  If the operation succeeds, you should return true.  If not, false.

To set a property, again check the name to see which property you’re working with and save the value of the NPVariant parameter however you wish.  Again, return true if success, false if failure.

Methods

Methods aren’t much more difficult.  The only real difference is that now you have parameters to worry about.  No problem!  Parameters are passed in as an array of NPVariants:

1
2
    bool Invoke(NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
    bool InvokeDefault(const NPVariant *args, uint32_t argCount, NPVariant *result);

The difference between Invoke and InvokeDefault is that InvokeDefault does not ever have an identifier.  If you call “objTag.funcName()” Invoke gets called; if you call “objTag()”, then InvokeDefault gets called.

As with properties, store the return value in the pointer given in the last argument and handle the other arguments however you wish.

Wrapping it up

Alright, I know I haven’t covered everything, but this is already too long.  If you have specific questions, ask them in the comments and I’ll do my best to answer; the comments may determine the direction of my next post.

Since dealing with the NPIdentifiers can get to be such a pain, I recommend using a map or hashtable to store structures with function pointers to map a string name to functions to handle it; that way you don’t end up with giant if/then or case statements, and everything ends up being much cleaner.

Another idea for the adventuresome: abstract the actual logic (properties and methods) into another object that can be wrapped with your NPObject class, and then make another wrapper class that implements IDispatch so that your scriptable objects are cross platform (IDispatch objects are scriptable on Internet Explorer).

Good luck!

Building a firefox plugin – part one

Building a firefox plugin – part two

Building a firefox plugin – part three

http://code.google.com/p/firebreath/
  1. August 21st, 2010 at 22:07
    Reply | Quote | #1

    The process you describe calling window.Object() to get an NPObject and then using SetProperty to set my_var on that, then using window.Array() to get an NPObject array and then using SetProperty to add elements to that, etc works just fine. FireBreath uses this methodology to return objects and arrays to javascript.

  2. August 21st, 2010 at 22:08
    Reply | Quote | #2

    The process you describe calling window.Object() to get an NPObject and then using SetProperty to set my_var on that, then using window.Array() to get an NPObject array and then using SetProperty to add elements to that, etc works just fine. FireBreath uses this methodology to return objects and arrays to javascript.

  3. Glauco
    August 22nd, 2010 at 16:14
    Reply | Quote | #3

    Thank you taxilian! That is going to work beautifully :-)
    Now, I just need to figure out how to do it in COM.

    - Glauco

  4. August 22nd, 2010 at 18:43
    Reply | Quote | #4

    In COM you do it the same way. Of course, you could save yourself a heck of a lot of time and just use FireBreath, where I’ve already solved that problem in a ridiculously easy to use way.

    Or, if you’re determined to do it yourself, you can look at the FireBreath code for an example: to set a property on an array (which will be an IDispatchEx object): http://code.google.com/p/firebreath/source/browse/src/ActiveXPlugin/IDispatchAPI.cpp

    look at http://code.google.com/p/firebreath/source/browse/src/ActiveXPlugin/FBControl.h for an example of getting a reference to the window / document.

  5. Glauco
    August 23rd, 2010 at 21:23
    Reply | Quote | #5

    Yes – FireBreath is pretty cool and I’ve been checking the code a lot and it’s definitely a really nice framework. But, this is my first experience with browser plug-ins and I think to fully appreciate the framework, I need to go through the pains at least once :-)

    Thanks to your help and this tutorial, I got almost everything figure out however, I have one more issue. When, in the JavaScript, I put alert(e.array[0]) I see a coma (,) – is this garbage? Only if I do alert(e.array[1]) then I actually see what I pushed from the c++ code.
    I’m sure it’s something silly but I can’t see it. I’ve pasted below the main steps in the code where I fill the array. Do you see anything wrong?

    // To create the array
    arrayObj->GetDispID(SysAllocString(CA2W(“Array”)),
    fdexNameEnsure | fdexNameCaseSensitive | 0×10000000, &dispId);

    arrayObj->InvokeEx(dispId, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &result, NULL, NULL);

    // Push elements to array
    arrayObj->GetDispID(SysAllocString(CA2W(“push”)),
    fdexNameEnsure | fdexNameCaseSensitive | 0×10000000, &dispId);

    VARIANT arg, result;
    DISPPARAMS params = {0};
    VariantInit(&arg);
    VariantInit(&result);
    arg.vt = VT_I2;
    arg.iVal = 4; // first element of the array

    params.rgvarg = &arg;
    params.rgdispidNamedArgs = 0;
    params.cArgs = 1;
    params.cNamedArgs = 0;

    hr = arrayObj->InvokeEx(dispId, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &result, NULL, NULL);

  6. August 23rd, 2010 at 21:28
    Reply | Quote | #6

    Hard to say for sure, but if your code is a direct quote, you have a problem. You could be getting the dispid from the window object, then invoking Array on the window object. You should then be getting the result and getting the dispid of push from the array object.

    Firebreath does this and it works exactly as you would expect; if you have more than 1 item on the array when only calling push once, something is wrong.

  7. Glauco
    August 23rd, 2010 at 23:20
    Reply | Quote | #7

    Yes, I’m careful to not mix these variables when calling the various methods.

    Something strange that I’ve noticed is that,when I create the array, if I also add into the variant the first element of the array (so, the variant has two arguments: arg[0].vt = VT_DISPATH, arg[0].pdispVal = NULL; arg[1].vt = VT_BSTR, arg[1].bstrValue = “test”), then e.array[0] is equal to something…..only to “t” :-(

    Does this ring any bell to you?

  8. August 23rd, 2010 at 23:31
    Reply | Quote | #8

    not particularly; I haven’t ever seen the problem you describe. I can’t reproduce it using the FireBreath code, so I’m not sure what would be going on. If you want you could email me the code and I could look at it, but it might be a few days before I could find time to look into it; I’m in the process of wrapping up one project and will be starting another one next week.

Comment pages
blog comments powered by Disqus