Introduction

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.

Writing a Netscape Plugin

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:

  1. Create a new empty Visual C++ DLL project.
  2. Add the plugin.cpp, plugin.h, npp_gate.cpp, npn_gate.cpp and np_entry.cpp source files (they're in the Netscape plug-in SDK or the modules\plugin\tools\sdk\sample\common directory of your Mozilla build).
  3. Create a DEF file with the same contents as the basic sample plug-in DEF file. This file tells Visual C++ which C methods to export in your plug-in DLL - you have to export the three methods Netscape is looking for (NP_GetEntryPoints, NP_Initialize, NP_Shutdown).
  4. Create a new version resource ("Insert...Resource"), save the resource and close the resource file.
  5. Open the resource file (.rc) in a text editor and add the lines in the basic plug-in resource file that aren't in the normal version resource (MIMEType, FileExtents, etc.).
  6. Open the resource in Visual C++. Double-click on "Block Header" and change "Code Page" to "Windows, Multilingual."
  7. Add the Netscape plug-in SDK or Mozilla include directories to the include path for the project.
  8. Copy the C++ preprocessor definitions from the basic plug-in project.
  9. Try building your new plug-in!

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.

Starting Out

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;
}