Scripting Old Style Plugins in Mozilla
April 11, 2001
(see online version for the latest updates: http://mozilla.org/docs/scripting-plugins.html)

Introduction
New in Mozilla code
New in plugin code
JavaScript code
Building and installing the plugin
What else to read
Examples

Introduction

Plugins that used to take advantage of being scriptable via LiveConnect in 4.x Netscape browsers lost this possibility in the new world. The main reason for this is that there is no guarantee of Java compatibility on a binary level due to the jri/jni switch. The newly introduced Mozilla Plugin API allows plugins be scriptable via a different mechanism called XPConnect.  Basically, this means that in order to use and take full advantage of this new API, which is interface-based,  and to be scriptable, plugins must be rewritten to become XPCOM components. Switching to the new world may not be immediately desirable by some plugin makers, since the task involves a fair amount of effort, and if the plugin mostly works fine lacking only scriptability, developers will probably just give up on this feature, which may result in unpleasant experience for the end user.

In order to make the transtion smoother, some changes have been made to the Mozilla code. The changes allow to make existing 4.x plugins scriptable with only minor modifications in their code. The present document describes the steps of what should be done to the plugin code to turn it scriptable again.

What's in the Mozilla code?

A couple of lines have been added to the DOM code asking a plugin to return a scriptable iid and a pointer to a scriptable instance object. The old Plugin API call NPP_GetValue is used to retrieve this information from the plugin. So the plugin project should be aware of two new additions to NPPVariable enumeration type which are now defined in npapi.h as

  NPPVpluginScriptableInstance = 10,
  NPPVpluginScriptableIID      = 11

and two analogous additions to nsPluginInstanceVariable type in nsplugindefs.h as

  nsPluginInstanceVariable_ScriptableInstance = 10,
  nsPluginInstanceVariable_ScriptableIID      = 11

What's in the plugin code?

1. A unique interface id should be obtained. Windows command uuidgen should be sufficient.

2. An Interface Definition (.idl) file describing the plugin scriptable interface should be added to the project (see example 1).

3. A Scriptable instance object should be implemented in the plugin. This class will contain native methods callable from JavaScript. This class should also inherit from nsISecurityCheckedComponent and implement its methods to be able to request all necessary privileges from the Mozilla security manager (see example 2).

4. Two new cases for the above mentioned new variables should be added to the plugin implementation of NPP_GetValue (see example 3).

How to call plugin native methods

The following HTML code will do the job:

<embed type="application/plugin-mimetype">
<script>
var embed = document.embeds[0];
embed.nativeMethod();
</script>

How to build and install

Having the built Mozilla tree is probably not necessary, but building the plugin with a scriptable instance interface will require Mozilla headers and the XPCOM compatible idl compiler -- xpidl.exe. MS DevStudio MIDL should not be used. (Let's assume 'TestPlugin' as a plugin name-place holder.)

1. Compile nsITestPlugin.idl with the idl compiler. This will generate nsITestPlugin.h and nsITestPlugin.xpt files.

2. Put nsITestPlugin.xpt to the Components folder.

3. Build nptestplugin.dll with nsITestPlugin.h included for compiling scriptable instance class implementaion.

4. Put nptestplugin.dll to the Plugins folder.

Related sources
 

Example 1. Sample .idl file

#include "nsISupports.idl"

[scriptable, uuid(bedb0778-2ee0-11d5-9cf8-0060b0fbd8ac)]
interface nsITestPlugin : nsISupports {
  void nativeMethod();
};

Example 2. Scriptable instance class

#include "nsITestPlugin.h"
#include "nsISecurityCheckedComponent.h"

class nsScriptablePeer : public nsITestPlugin,
                         public nsISecurityCheckedComponent
{
public:
  nsScriptablePeer();
  ~nsScriptablePeer();

  NS_DECL_ISUPPORTS
  NS_DECL_NSITESTPLUGIN
  NS_DECL_NSISECURITYCHECKEDCOMPONENT
};

nsScriptablePeer::nsScriptablePeer()
{
  NS_INIT_ISUPPORTS();
}

nsScriptablePeer::~nsScriptablePeer()
{
}

NS_IMPL_ISUPPORTS2(nsScriptablePeer, nsITestPlugin, nsISecurityCheckedComponent)

// the following method will be callable from JavaScript
NS_IMETHODIMP nsScriptablePeer::NativeMethod()
{
  return NS_OK;
}

// the purpose of the rest of the code is to get succesfully
// through the Mozilla Security Manager
static const char gAllAccess[] = "AllAccess";
NS_IMETHODIMP nsScriptablePeer::CanCreateWrapper(const nsIID * iid, char **_retval)
{
  if (!_retval)
    return NS_ERROR_NULL_POINTER;
  *_retval = (char*)NPN_MemAlloc(sizeof(gAllAccess)+1);
  if (!*_retval)
    return NS_ERROR_OUT_OF_MEMORY;
  strcpy(*_retval, gAllAccess);
  return NS_OK;
}

NS_IMETHODIMP nsScriptablePeer::CanCallMethod(const nsIID * iid, const PRUnichar *methodName, char **_retval)
{
  if (!_retval)
    return NS_ERROR_NULL_POINTER;
  *_retval = (char*)NPN_MemAlloc(sizeof(gAllAccess)+1);
  if (!*_retval)
    return NS_ERROR_OUT_OF_MEMORY;
  strcpy(*_retval, gAllAccess);
  return NS_OK;
}

NS_IMETHODIMP nsScriptablePeer::CanGetProperty(const nsIID * iid, const PRUnichar *propertyName, char **_retval)
{
  if (!_retval)
    return NS_ERROR_NULL_POINTER;
  *_retval = (char*)NPN_MemAlloc(sizeof(gAllAccess)+1);
  if (!*_retval)
    return NS_ERROR_OUT_OF_MEMORY;
  strcpy(*_retval, gAllAccess);
  return NS_OK;
}

NS_IMETHODIMP nsScriptablePeer::CanSetProperty(const nsIID * iid, const PRUnichar *propertyName, char **_retval)
{
  if (!_retval)
    return NS_ERROR_NULL_POINTER;
  *_retval = (char*)NPN_MemAlloc(sizeof(gAllAccess)+1);
  if (!*_retval)
    return NS_ERROR_OUT_OF_MEMORY;
  strcpy(*_retval, gAllAccess);
  return NS_OK;
}

Example 3. NPP_GetValue implementation

#include "nsITestPlugin.h"

NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
{
  if(instance == NULL)
    return NPERR_INVALID_INSTANCE_ERROR;

  NPError rv = NPERR_NO_ERROR;
  static nsIID scriptableIID = NS_ITESTPLUGIN_IID;

  if (variable == NPPVpluginScriptableInstance)
  {
    if (this is first time and we haven't created it yet)
    {
      nsITestPlugin * scriptablePeer = new nsScriptablePeer();
      if(scriptablePeer)
        // addref for ourself, don't forget to release on shutdown to trigger its destruction
        NS_ADDREF(scriptablePeer);
    }
    // add reference for the caller requesting the object
    NS_ADDREF(scriptablePeer);
   *(nsISupports **)value = scriptablePeer;
  }
  else if (variable == NPPVpluginScriptableIID)
  {
    nsIID* ptr = (nsIID *)NPN_MemAlloc(sizeof(nsIID));
    *ptr = scriptableIID;
    *(nsIID **)value = ptr;
  }
  return rv;
}