Before Mozilla and Netscape 6, there was Netscape 4 and LiveConnect. To use LiveConnect, you'd write a Java layer that would allow your browser plugin to be "scriptable" - you could call it from JavaScript. The LiveConnect Java layer was a proxy for the plugin and, in most cases, a pain - you have to write another interface in another language to get your plugin to communicate via JavaScript!
Now that we have Mozilla, we also have the Sun Java Plugin. LiveConnect used the Java Runtime Interface (JRI) to communicate with the native plugin. The JRI was replaced by the Java Native Interface (JNI). Mozilla needed another way to communicate with native plug-ins, so the team came up with XPConnect.
Before we get all fancy, it's important to understand the old plugin architecture. Build the sample basic plugin in mozilla\modules\plugin\tools\sdk\sample\basic\windows. Copy the resulting debug DLL to your Netscape plugins directory (this can either be the plugins directory of a production Netscape install or the dist\bin\plugins directory of a Mozilla build).
Try making your own plugin. This involves a few steps:
See if your plugin is listed when you navigate to "about:plugins" or do "Help...About Plug-ins."
If it doesn't show up, don't panic. Make sure:
You now have a plug-in that will run in Netscape or Mozilla. Way to go! However, it's on its own - no communication with the web page. To do that, we have to implement the XPConnect bridge.
The Mozilla Tools distribution includes the XPCOM IDL compiler (xpidl). Unfortunately, it doesn't include the IDL includes, so the best thing to do is download a recent source tree. This is not small - you'll want to have a relatively fast internet connection and computer, along with a decent amount of RAM and disk space. If you have time and resources, compile the browser. Having a debug version of Mozilla around is really handy for debugging.
There are some helpful pages around on XPCOM and scriptable plug-ins (check out the XPConnect FAQ). You might want to read them before proceeding.
IDL Files
The first step is to create the IDL file for your plugin. It'll look something like this:
#include "nsISupports.idl"
[scriptable, uuid(20D67C6F-B9A2-40ff-8C70-A9A18D9595F9)]
interface nsIMyPlugin : nsISupports
{
void writeValue(in string aPrefix);
};
To generate the guid, just use uuidgen or guidgen.
Use xpidl (from Mozilla tools or mozilla\dist\bin in your Mozilla build) to generate the C++ header and type library (XPT) file:
xpidl -I c:\dev\mozilla\dist\idl -m header nsIMyPlugin.idl xpidl -I c:\dev\mozilla\dist\idl -m typelib nsIMyPlugin.idl
The easiest thing to do if you're using Visual C++ is create a utility project for the IDL file. Add only the IDL file, and check "Always use custom build" in the project settings for the IDL file. Add the above lines (correct the include path to your Mozilla source tree) to the custom build tab that appears, and add "nsIMyPlugin.h" and "nsIMyPlugin.xpt" to the "Files" text field in the custom build step.
Compiling the IDL file creates a C++ abstract base class and a XPCOM type library for your plugin. You need to implement the C++ interface.
Implementing the Plugin Interface
The header necessary to implement the generated interface doesn't have to be very big:
#include "nsIMyPlugin.h"
class nsMyPlugin : public nsIMyPlugin
{
public:
nsMyPlugin();
virtual ~nsMyPlugin();
// Define the standard methods necessary
// to implement nsISupports.
NS_DECL_ISUPPORTS
// Define the methods you defined in your
// plugin IDL file.
NS_DECL_NSIMYPLUGIN
private:
char *mValue;
};
The second macro (NS_DECL_NSIMYPLUGIN) is optional. It's better software development practice to explicitly define the plugin methods you're implementing. But for brevity's sake, here we are.
Here's the implementation:
#include <stdio.h>
#include <windows.h>
#include "nsMyPlugin.h"
#include "plstr.h"
#include "nsMemory.h"
nsMyPlugin::nsMyPlugin() : mValue(0)
{
NS_INIT_ISUPPORTS();
mValue = 0;
}
nsMyPlugin::~nsMyPlugin()
{
if (mValue)
PL_strfree(mValue);
}
// Include standard implementation of AddRef, Release
// and QueryInterface.
NS_IMPL_ISUPPORTS1(nsMyPlugin, nsIMyPlugin)
/**
* Notice that in the protoype for this function, the NS_IMETHOD macro was
* used to declare the return type. For the implementation, the return
* type is declared by NS_IMETHODIMP
*/
NS_IMETHODIMP nsMyPlugin::GetValue(char** aValue)
{
NS_PRECONDITION(aValue != nsnull, "null ptr");
if (! aValue)
return NS_ERROR_NULL_POINTER;
if (mValue) {
/**
* GetValue's job is to return data known by an instance of
* nsSampleImpl to the outside world. If we were to simply return
* a pointer to data owned by this instance, and the client were to
* free it, bad things would surely follow.
* On the other hand, if we create a new copy of the data for our
* client, and it turns out that client is implemented in JavaScript,
* there would be no way to free the buffer. The solution to the
* buffer ownership problem is the nsMemory singleton. Any buffer
* returned by an XPCOM method should be allocated by the nsMemory.
* This convention lets things like JavaScript reflection do their
* job, and simplifies the way C++ clients deal with returned buffers.
*/
*aValue = (char*) nsMemory::Clone(mValue, strlen(mValue) + 1);
if (! *aValue)
return NS_ERROR_NULL_POINTER;
}
else {
*aValue = nsnull;
}
return NS_OK;
}
NS_IMETHODIMP nsMyPlugin::SetValue(const char* aValue)
{
NS_PRECONDITION(aValue != nsnull, "null ptr");
if (! aValue)
return NS_ERROR_NULL_POINTER;
if (mValue) {
nsMemory::Free(mValue);
}
/**
* Another buffer passing convention is that buffers passed INTO your
* object ARE NOT YOURS. Keep your hands off them, unless they are
* declared "inout". If you want to keep the value for posterity,
* you will have to make a copy of it.
*/
mValue = (char*) nsMemory::Clone(aValue, strlen(aValue) + 1);
return NS_OK;
}
NS_IMETHODIMP nsMyPlugin::WriteValue(char const *s)
{
MessageBox(NULL, s, "MyPlugin", MB_OK);
return NS_OK;
}