As a developer, you may choose one of several strategies for packaging and distributing your Mozilla extensions:
There are several downsides to this approach:
This approach would have the advantage of getting your changes distributed widely, and may be best if your primary added value is not in the client, but in a server product that these changes interact with. But you have to be convincing that your client code has wide appeal, and that these changes should become part of all future releases. This may be a hard sell if your application satisfies a particular niche market, but is not something needed by all users.
This approach has numerous advantages:
"Hey, wait a minute! I started reading this to find out about how to extend Mozilla, not write some stupid plugin!" you calmly point out. "What are you trying to pull...." Well, although this paper focuses primarily on the Plugin API, you have to see the big picture. The Plugin API is a model for how all extensions to Mozilla should be designed and implemented. Although plugins to date have been concerned with extensions controlled by the OBJECT tag, as you'll see, the techniques described here are generally applicable. Over time, plugins can take on an increasing range of functionality. One can easily imagine extending them to include networking protocol plugins, html extensions, or even replacements entire browser subsystems like bookmarks, or mail/news handling. So when we say plugins here, think big.
It's also possible to develop a plugin that uses the new Plugin APIs but also will run in older versions of Communicator (and Navigator). A new version of the old plugin glue files will be provided that maps from the old plugin entry points to their new C++ equivalents, so you can take advantage of the new C++ architecture for any plugins you develop.
The new plugin architecture breaks up the old monolithic interface into several abstract base classes. All "NPP" functions (those implemented by the plugin developer) become methods on the NPIPlugin, NPIPluginInstance and NPIPluginStream classes. All "NPN" functions (those provided by Mozilla) become methods on the NPIPluginManager, NPIPluginManagerStream, NPIPluginInstancePeer and NPIPluginStreamPeer classes. (The "NP" prefix stands for "Netscape Plugin" and the "I" denotes that this class is an abstract interface.) Functions pertaining to LiveConnect functionality are provided as subclasses of these basic classes and will be discussed later in the document.
When a plugin is loaded by the browser, an instance of NPIPluginManager is passed to the plugin's initial entry point, NP_CreatePlugin (this corresponds to the old Plugin API's NPP_Initialize function). This function is responsible for instantiating a subclass of NPIPlugin and returning it to the caller, i.e. Mozilla. Mozilla hangs on to this plugin object and uses it to obtain general information about the plugin, e.g. the MIME type that the plugin handles, or its name and description strings to be displayed in the "About Plugins" page (note that on some platform these strings may come from from resources associated with the plugin DLL).
When an EMBED or OBJECT tag appears on the page, the NewInstance method is called on the plugin object. It is the responsibility of the NewInstance method to instantiate a subclass of NPIPluginInstance. The NewInstance method is passed an instance of NPIPluginInstancePeer which the newly created plugin instance may use to query particular aspects of the plugin's relationship to the page -- the HTML tag attributes, or the size and location on the page. Typically the plugin instance stores the plugin instance peer in an instance variable for use by other methods later.
After the plugin instance is created, a data stream may be provided to it by calling the NewStream method on the plugin instance. This will happen if the OBJECT tag has a DATA attribute (or the EMBED tag has a SRC attribute), or if the plugin instance explicitly requests a stream by calling GetURL or PostURL on the plugin instance peer. When NewStream is called it is passed an instance of NPIPluginStreamPeer which is used to obtain subsequent information about the stream, or to control it. NewStream constructs an appropriate instance of NPIPluginStream and passes it back to the browser. The browser then calls the WriteReady and Write operations on the plugin stream to pass data to the plugin.
With the new C++ Plugin API, a plugin instance will be sent a Stop message which informs it that it is being suspended. The plugin can either do a lot of work to suspend itself as before, or can simply stop operation and wait for a corresponding Start method to indicate that the plugin's page has again become active. Both Stop and Start will be followed by a SetWindow call indicating that the plugin's parent window has been changed. Finally, when a plugin's page leaves the history, the Release method will be called on the plugin instance, indicating that it should free up its state and destroy itself. This new Start/Stop/Release mechanism greatly simplifies the amount of effort needed to write a plugin.
As you're sitting down with the morass of Mozilla code thinking about how to expose that feature you really need, consider making the effort to pull subsystems into reusable DLLs. Talk with mozilla.org to hook you up with the original Netscape developers of a piece of code to determine whether a piece of code is in the process of being pulled out into a DLL, or whether doing so would benefit not only yourself, but the community at large. Packaging things as DLLs is by far the most preferable way to go, providing a nice package that can be used by not only your plugin and the browser, but by future applications as well.
Take the time to talk to the community before just hacking something together. You may get some good advice, or find out that someone has already done what you're trying to do. Check out the mozilla.org newsgroups.
Since interface IDs are strongly tied to C++ classes, it's important to change the interface ID whenever the class changes. If a class gets changed in a backward compatible way, it can continue to support both interface IDs (or any number of them), and savvy clients can use the newer interface IDs to know its safe to call the newer methods.
Talk to us, let us know what you want, what your plans are, and a gross schedule and we promise to work with you. The last thing we want is someone going away, angry, saying "mozilla.org screwed my product over in the 11th hour." Remember that we've got a hard job trying to balance the needs of hundreds of developers while trying to evolve the Mozilla source base in a coherent direction. Cut us some slack, and be patient, and we'll do our best to make you look good!
First you should be sure to read Will's excellent document, Modularization Techniques, on using XP-COM and writing modules. Then you should continue reading to learn the gory details of extending Mozilla.
First, congratulations on spending enough time looking at the Mozilla source code to see that what you need is there beneath the covers, but not exposed through the Plugin API. Good engineering starts with understanding the terrain.
Second, since the objective here is simple -- to simply add a method and not totally rearchitect the world -- we'll be sure add the new method(s) at the end of the class declaration. That way, the extended interface won't become incompatible with the old vtable layout, and cause plugins that expected the old NPIPluginStreamPeer interface to be forced to recompile:
| from nsplugin.h |
|---|
class NPIPluginStreamPeer : public nsISupports {
public:
...all the old NPIPluginStreamPeer methods...
//////////////////////////////////////////////
// Extensions to NPIPluginStreamPeer for version 2:
// GetServerStatus: This methods returns the server
// status code for the requested URL.
virtual NS_IMETHOD_(PRInt32)
GetServerStatus(void) = 0;
};
#define NP_IPLUGINSTREAMPEER1_IID \
{ /* 38278eb0-a1bd-11d1-85b1-00805f0e4dfe */ \
0x38278eb0, \
0xa1bd, \
0x11d1, \
{ 0x85, 0xb1, 0x00, 0x80, 0x5f, 0x0e, 0x4d,0xfe } \
}
#define NP_IPLUGINSTREAMPEER2_IID \
{ /* f87f0420-c149-11d1-85c1-00805f034dfe */ \
0xf87f0420, \
0xc149, \
0x11d1, \
{ 0x85, 0xc1, 0x00, 0x80, 0x5f, 0x03, 0x4d,0xfe } \
}
#define NP_IPLUGINSTREAMPEER_IID NP_IPLUGINSTREAMPEER2_IID
|
Some things to observe here:
Next we need to implement this addition to the browser. First note that we've simply extended the interface, NPIPluginStreamPeer. So we have to add a corresponding declaration to the implementation header file. Hunting around in the source you'll find that this lives in npglue.h:
| from npglue.h |
|---|
class nsPluginStreamPeer : public NPISeekablePluginStreamPeer {
public:
...all the other nsPluginStreamPeer methods...
// GetServerStatus: This methods returns the
// server status code for the requested URL.
virtual NS_IMETHOD_(PRInt32)
GetServerStatus(void);
protected:
...
};
|
Here we've copied the declaration of the method from NPIPluginStreamPeer, but made it not be a pure abstract method. It doesn't matter where this method gets inserted in the class because the compiler will sort it out based on the interface being inherited, but will put it near the end for clarity anyway. Finally we can implement our extensions as follows:
| from nsplugin.cpp |
|---|
NS_IMETHOD_(PRInt32)
nsPluginStreamPeer::GetServerStatus(void)
{
return urls->server_status;
}
NS_DEFINE_IID(kSeekablePluginStreamPeerIID,
NP_ISEEKABLEPLUGINSTREAMPEER_IID);
NS_DEFINE_IID(kPluginStreamPeer1IID,
NP_IPLUGINSTREAMPEER1_IID); // changed
NS_DEFINE_IID(kPluginStreamPeer2IID,
NP_IPLUGINSTREAMPEER2_IID); // NEW!
NS_IMETHOD_(nsresult)
nsPluginStreamPeer::QueryInterface(const nsIID& aIID,
void**aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if ((stream->seekable &&
aIID.Equals(kSeekablePluginStreamPeerIID)) ||
aIID.Equals(kPluginStreamPeer1IID)||
aIID.Equals(kPluginStreamPeer2IID)|| // NEW!
aIID.Equals(kISupportsIID)) {
*aInstancePtr = (void*)((nsISupports*)this);
AddRef();
return NS_OK;
}
return NS_NOINTERFACE;
}
|
Here we see our humble method definition (most methods will probably be more complex than this!), and then a change to QueryInterface to say that we not only support the old interface ID (with the new name, NP_IPLUGINSTREAMPEER1_IID), but also the new interface ID with that denotes the current version of the class NPIPluginStreamPeer with the GetServerStatus extension, NP_IPLUGINSTREAMPEER2_IID.
That's it -- with one small exception -- we blew it! In this particular case we can add a method to NPIPluginStreamPeer and maintain backward compatibility because there is already a public subclass of NPIPluginStreamPeer defined, namely, NPISeekablePluginStreamPeer! If we added a method to this superclass (interface), we'd affect existing plugins that depend on the previous version of the NPISeekablePluginStreamPeer class -- its RequestRead method would get moved down one notch because of the addition of the GetServerStatus method to its superclass. Back to the drawing board!
(Sorry to take you through this big detour, but the above technique might be useful for classes that don't otherwise have subclasses. However, the following technique -- although a bit more complex -- will work in most all circumstances while allowing backward compatibility to be maintained.)
Here's a new interface that implements the GetServerStatus method:
class NPIServerStatus : public nsISupports {
public:
// use QueryInterface on an instance of
// NPIPluginStreamPeer to obtain this interface
// GetServerStatus: This methods returns the
// server status code for the requested URL.
virtual NS_IMETHOD_(PRInt32)
GetServerStatus(void);
};
#define NP_ISERVERSTATUS_IID \
{ /* f87f0420-c149-11d1-85c1-00805f034dfe */ \
0xf87f0420, \
0xc149, \
0x11d1, \
{ 0x85, 0xc1, 0x00, 0x80, 0x5f, 0x03, 0x4d,0xfe } \
}
|
Then to obtain an instance of this interface, the plugin code would do something like this:
NS_DEFINE_IID(kServerStatusIID, NP_ISERVERSTATUS_IID);
void somePluginOperation(NPIPluginStreamPeer* peer) {
NPIServerStatus* ssIntf;
if (peer->QueryInterface(kServerStatusIID,
(void**)&ssIntf)== NS_OK) {
// at this point we've successfully obtained
// an instance supporting NPIServerStatus so
// we can call GetServerStatus:
PRInt32 status = ssIntf->GetServerStatus();
...now use status...
// don't forget to release the thing we
// obtained by QueryInterface
ssIntf->Release();
}
else {
// otherwise, the plugin stream peer
// we were passed doesn't support the
// NPIServerStatus interface-- probably
// because it's an older version of the
// browser.
...do something else...
}
...
}
|
Of course this example is rather simple. Your interfaces will usually provide more than one accessor routine. But for illustrative purposes, let's continue and look at how to add the NPIServerStatus interface to the browser's plugin stream peer implementation.
| from npglue.h |
|---|
class nsPluginStreamPeer : public NPISeekablePluginStreamPeer,
public NPIServerStatus {
public:
...all the other nsPluginStreamPeer methods...
// GetServerStatus: This methods returns the
// server status code for the requested URL.
virtual NS_IMETHOD_(PRInt32)
GetServerStatus(void);
protected:
...
};
|
Then we implement the GetServerStatus method as we did above. The QueryInterface method, however, gets an extra 'if' statement:
| from nsplugin.cpp |
|---|
NS_IMETHOD_(PRInt32)
nsPluginStreamPeer::GetServerStatus(void)
{
return urls->server_status;
}
NS_DEFINE_IID(kSeekablePluginStreamPeerIID,
NP_ISEEKABLEPLUGINSTREAMPEER_IID);
NS_DEFINE_IID(kPluginStreamPeerIID,
NP_IPLUGINSTREAMPEER_IID);
NS_DEFINE_IID(kServerStatusIID,
NP_ISERVERSTATUS_IID);
NS_IMETHOD_(nsresult)
nsPluginStreamPeer::QueryInterface(const nsIID& aIID,
void**aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if ((stream->seekable &&
aIID.Equals(kSeekablePluginStreamPeerIID)) ||
aIID.Equals(kPluginStreamPeerIID)||
aIID.Equals(kISupportsIID)) {
*aInstancePtr = (void*)
((NPISeekablePluginStreamPeer*)this);
AddRef();
return NS_OK;
}
if (aIID.Equals(kServerStatusIID)) { // NEW!
*aInstancePtr = (void*)((NPIServerStatus*)this);
AddRef();
return NS_OK;
}
return NS_NOINTERFACE;
}
|
Here, it's important to have separate interface ID tests for those interfaces being inherited through the NPISeekablePluginStreamPeer superclass, and for those interfaces being inherited through the NPIServerStatus superclass (i.e. NPIServerStatus itself). This is because different vtables are obtained at the point where the 'this' pointer is cast to one superclass or the other. Note that we don't use the 'static_cast' operator available in newer versions of C++ because not all compilers that we use to compile Mozilla support it. Also, there's a nsISupports interface that could also be obtained through the NPIServerStatus superclass, but since it ends up being operationally identical to the one from the NPISeekablePluginStreamPeer superclass, we don't need to do anything special to allow it to be accessed.
We've provided the header file nsAgg.h (ns/xpcom/src/nsAgg.h) to make aggregation a relatively straightforward thing to do. For our example, we'll make NPIServerStatus an aggregated implementation whose "outer" object is the plugin stream peer. Again, you probably wouldn't want to build such an elaborate mechanism for such a simple method (and in this particular case, the GetServerStatus method probably rightfully belongs in the NPIPluginStreamPeer interface), but we'll continue with the example for illustrative purposes. (Note that the JVM Plug-in API for adding Java virtual machine plugins was implemented this way, as an aggregateable interface that extends the basic nsPluginManager class in Mozilla.)
class nsServerStatus : public NPIServerStatus {
public:
NS_DECL_AGGREGATED
nsServerStatus(nsISupports* outer, URL_Struct*url);
virtual ~nsServerStatus(void);
// GetServerStatus: This methods returns the
// server status code for the requested URL.
virtual NS_IMETHOD_(PRInt32)
GetServerStatus(void);
protected:
URL_Struct* urls;
};
|
nsServerStatus::nsServerStatus(nsISupports* outer,
URL_Struct*url)
: urls(url)
{
NS_INIT_AGGREGATED(outer);
}
nsServerStatus::~nsServerStatus(void)
{
}
|
The first thing you'll notice here is that nsServerStatus, our new implementation, only inherits from the NPIServerStatus interface that defines the abstract GetServerStatus method. This class will work hand in hand with the nsPluginStreamPeer class already defined in nsplugin.cpp.
The second thing is that we use the NS_DECL_AGGREGATED macro to declare all the mechanism needed to make this class aggregateable. We'll see more later as we start implementing this class how this works.
Third, the constructor takes an instance that supports the nsISupports interface as an "outer" object. The mechanism set up by NS_DECL_AGGREGATED will test whether the outer object is non-NULL, and if so, will forward nsISupports operations to the outer which will get first crack at QueryInterface, and will manage the reference counting for the aggregated objects. Also the URL structure is passed in the constructor in order to initialize the instance variable needed by the GetServerStatus method. Here's what the constructor and destructor look like:
nsServerStatus::nsServerStatus(nsISupports* outer,
URL_Struct* url)
: urls(url)
{
NS_INIT_AGGREGATED(outer);
}
nsServerStatus::~nsServerStatus(void)
{
}
|
Here the constructor initializes the urls instance variable from the url argument, and then uses the NS_INIT_AGGREGATED macro to initialize outer. NS_INIT_AGGREGATED also does the work that NS_INIT_ISUPPORTS usually does when you're just implementing a subclass of nsISupports. The destructor does nothing special. We're just careful not to declare it in the header file (inline), because it's virtual, and some compilers can't deal with that. Also we're careful to use 'void' in the argument list for poor g++.
The only other change we have to make to finish our aggregated implementation is we have to declare the nsISupports operations by using the macro NS_IMPL_AGGREGATED, and then write an AggregatedQueryInterface method instead of the usual QueryInterface. It will be called from the QueryInterface implementation defined by the NS_IMPL_AGGREGATED macro after it does its aggregation magic:
NS_IMPL_AGGREGATED(nsServerStatus);
NS_IMETHOD_(nsresult)
nsServerStatus::AggregatedQueryInterface(const nsIID& aIID,
void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kServerStatusIID)) {
*aInstancePtr = (void*) this;
AddRef();
return NS_OK;
}
if (aIID.Equals(kISupportsIID)) {
// special case for when we're called
// from the Create operation
*aInstancePtr = &fAggregated;
return NS_OK;
}
return NS_NOINTERFACE;
}
|
The GetServerStatus method looks just like it did before, so we won't bother to show you again. So that's it! We've defined an aggregated class with relatively little pain.
Now, how do we hook it up to our nsPluginStreamPeer class? The first trick is that we must define an instance variable in nsPluginStreamPeer that points to the instance of the nsServerStatus class -- but we must declare it of type nsISupports. That's because we're going to use QueryInterface on it whenever we need a more specific type:
| from npglue.h |
|---|
class nsPluginStreamPeer : public NPISeekablePluginStreamPeer {
public:
...all the other nsPluginStreamPeer methods...
(don't declare GetServerStatus here)
protected:
nsISupports* fServerStatus;
...
};
|
Then in the QueryInterface method of nsPluginStreamPeer, we'll instantiate our nsServerStatus class, possibly loading it dynamically if we want. Usually that's done by using the nsFactory mechanism (which we strongly encourage you to use), but we won't go into it here. See the document Modularization Techniques for more details.
| from nsplugin.cpp |
|---|
NS_IMETHOD_(nsresult)
nsPluginStreamPeer::QueryInterface(const nsIID& aIID,
void**aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if ((stream->seekable &&
aIID.Equals(kSeekablePluginStreamPeerIID)) ||
aIID.Equals(kPluginStreamPeerIID) ||
aIID.Equals(kISupportsIID)) {
*aInstancePtr = (void*)
((NPISeekablePluginStreamPeer*)this);
AddRef();
return NS_OK;
}
if (fServerStatus == NULL) { // try to create/load it
nsServerStatus* ss = ...create or load it...
if (ss) {
nsresult ok = ss->QueryInterface(kISupportsIID,
(void**)&fServerStatus);
if (ok != NS_OK) {
...release or unload ss...
}
}
}
if (fServerStatus)
return fServerStatus->QueryInterface(aIID,aInstancePtr);
else
return NS_NOINTERFACE;
}
|
What's happening here is that we're doing the usual QueryInterface thing for the plugin stream peer, but if that fails, we check whether fServerStatus has been set yet to see if we should load it. Note that this implementation could suffer from continually trying to load or create the server status object (for instance if the DLL simply isn't available), but we'll let you fix that. If we are able to load or create it, we use QueryInterface to set the fServerStatus instance variable. This call to QueryInterface is very special. The aggregation boiler-plate code that we used to define QueryInterface for nsServerStatus knows that when kISupportsIID is passed to it it must return a special internal version of nsISupports that makes the aggregation work. Fortunately you don't have to think about this too much -- just copy the example (ok, if you want, you can stare at nsAgg.h for a while and figure out how it works).
So, that was only a little complicated, but now that you've done it, you'll be the proud owner of a completely dynamically loadable and modular interface that can be aggregated with just about any other class in Mozilla. We think that's a big plus, and we'll worship you if you go to the trouble to do it right.