Chapter 11

Step-by-Step Through the NPP Methods


CONTENTS


This chapter provides a detailed description of the elements of the Navigator plug-in Software Development Kit (SDK), emphasizing the hooks to these elements (NPP methods) that need to be written by you, the programmer.

This chapter begins at the point where Navigator installs and inventories its plug-ins. It then proceeds through the point where Navigator loads the plug-in and makes a series of calls to it. This chapter then demonstrates how the plug-in displays its data and interacts with the user. The chapter concludes by showing the sequence the plug-in should use to destroy its instance and shut itself down.

Starting the Plug-In

When Navigator launches, it looks in the plug-ins directory-the exact name and location depends upon the platform, but in general the directory is named "plug-ins" and is in the same directory as Navigator itself. In the case of the Windows version of Navigator, plug-ins must also begin with the letters np in their file name. When Navigator starts up, it examines each file in the plug-ins directory. If the file is a plug-in, it checks the file to see what MIME types the plug-in handles. (The exact mechanism for associating MIME types with plug-ins depends upon the platform.) During this process, Navigator puts a message on the screen that says it is Loading plug-ins. As programmers, we understand that it is only registering, or installing, the plug-ins. Later, when the plug-in is needed, Navigator will actually load the plug-in into memory.

Launching the Plug-In

When Navigator encounters a request to handle a particular MIME media type, it looks at the list of plug-ins and selects the plug-in that handles this type. If the plug-in is already loaded (by a prior instance of this type), Navigator makes another instance of the plug-in. If Navigator is seeing this media type for the first time, it loads the plug-in code, and then makes the first instance.

This section describes this start-up process in detail.

Advertising a MIME Media Type  Every Navigator plug-in must handle at least one MIME media type and subtype. Netscape's documentation usually abbreviates "MIME media type and subtype" to "MIME type." This section shows how to put the MIME type identifiers into the plug-in. The method is different for each supported platform.

The MIME media type is specified in a Windows plug-in in the Version Information Resource. The specific resource to change is named VS_VERSION_INFO. You should add three lines to the "StringFileInfo" section of the resource. These new lines resemble this example:

VALUE "MIMEType", "application/x-chapter11\0"
VALUE "FileExtents", "c11\0"
VALUE "FileOpenName", "Chapter 11 Plug-In (*.c11)\0"

To see a specific example of how to set the Version Information Resource, see the section "Modifying the Version Information Resource" in Chapter 13, "Analysis of a Simple Plug-In."

To specify the MIME media type and file extensions in your OS/2 Warp plug-in, modify the RCDATA lines in the makefile. To add the unregistered MIME media type used as this chapter's example, the makefile might include:

#include "npapi.h"
       RCDATA NP_INFO_ProductVersion { 1,0,0,1}
       RCDATA NP_INFO_MIMEType       {"application/x-chapter11\0"}
       RCDATA NP_INFO_FileExtents    {"c11\0"}
       RCDATA NP_INFO_FileOpenName   {"Chapter 11 Sample Plugin\0"}

On a Macintosh, everyfile has a "resource fork" and a "data fork." These "forks" are simply two sections of the file. On some files, the resource fork is empty. In an application, however, the code, the windows templates, the strings, and all the other resources are present in the resource fork.

Before the application is compiled, the resources are stored in a file with the extension .rsrc. Use ResEdit (a resource editor developed by Apple and supplied with most compilers) to open one of the .rsrc files that comes with the Macintosh version of the SDK-choose File, Open from the menu, and use the dialog box to select the file. Figure 11.1 shows the two resources present in ViewText.rsrc.

Figure 11.1: The resource files in the SDK contain a string resource and a version resource.

The resource used by Navigator to identify the MIME media type of a plug-in is the STR# resource, specifically STR# 128. Figure 11.2 shows STR# 128 open in ResEdit.

Figure 11.2: Navigator reads STR# 128 to determine which MIME media types a plug-in handles.

As far as ResEdit is concerned, STR# resources just contain lists of strings. Navigator, however, interprets the strings in STR# 128 as holding the MIME media type and file extensions associated with this plug-in. To specify a MIME media type and set of file extensions for a plug-in, set string 1 of STR# 128 to the MIME media type and string 2 to the file extension.

The X Window System does not have the concept of a "resource" in the same way as Windows, OS/2, and the Macintosh do, so if you are preparing a plug-in for use under UNIX, you need to build the reference to the MIME media type into the program itself. Navigator will call your UNIX program with two new calls: NPP_GetMIMEDescription() and NPP_GetValue(). Use the following code as a model for implementing these two functions:

#ifdef XP_UNIX
char* NPP_GetMIMEDescription()
{
  return ("application/x-chapter11:c11:Chapter 11 Sample Plug-in");
}
#define PLUGIN_NAME "Chapter 11 Sample Plug-in"
#define PLUGIN_DESCRIPTION "Demonstrates how to pass information

to Navigator from a UNIX plug-in"
NPError NPP_GetValue(void* future, NPPVariable theVariable, void* value)
{
  NPError err = NPERR_NO_ERROR;
  if (variable == NPPVpluginNameString)
    *((char**)value) = PLUGIN_NAME;
  else if (variable == NPPVpluginDescriptionString)
    *((char**)value) = PLUGIN_DESCRIPTION;
  else
    err = NPERR_GENERIC_ERROR;
  return err;
}
#endif

Be sure to use the file examples/UnixTemplate/source/UnixShell.c (part of the UNIX SDK) as the model for your UNIX plug-in. Also examine the files in examples/Simple/Source from the same SDK; in particular, look for conditionals based on #ifdef XP_UNIX to see how UNIX plug-ins differ from those built for other operating systems.

Loading the Plug-In  When Navigator is loaded, it goes through the available plug-ins, reading MIME media types. If a user has two or more plug-ins loaded that both handle the same MIME type, Navigator registers the last type it finds.

Navigator finds media types in one of three ways:

If the media type is one that Navigator handles itself (such as text/html or image/gif) Navigator calls its own internal routines. If the media type is not native to Navigator, but there is no plug-in or helper application to handle the type, Navigator displays a dialog box similar to the one shown in Figure 11.3.

Figure 11.3: If Navigator cannot find a plug-in or helper application to handle the media type, it announces that fact to the user.

If Navigator finds a matching plug-in on the list, it next checks to see if the plug-in has already been loaded. If the plug-in has not been loaded, Navigator makes a series of calls to the plug-in. The first of these calls is to methods provided by the SDK. Finally, the standard (cross-platform) initialize function, which is written by the plug-in programmer, is called. This function is named NPP_Initialize().

The Programmer's Start-up Code: NPP_Initialize  What you do in NPP_Initialize() is up to you and is largely determined by the design of your plug-in. The general rule is that all static data members of your classes should be initialized here. If you need to allocate disk files, memory, or other resources that will be shared by all instances of the plug-in, do this work here. If you want to check the platform to ensure that certain minimum resources are present, do it now.

If, for example, your plug-in expects to find an ODBC-compliant database on the platform and all instances will share one connection to the database, NPP_Initialize() is the appropriate place both to ensure that the database exists and to establish the connection.

Tip
Prototypes for all of the NPP functions are found in npapi.h. Make sure that you put #include "npapi.h" in any file that defines one of the NPP_ functions, such as NPP_Initialize().

The specification of NPP_Initialize() is as follows:

NPError NPP_Initialize(void)

The function takes no parameters and returns an error code. If NPP_Initialize() is successful, it should return NPERR_NOERR. If a failure occurs, it should return the appropriate error code (listed in npapi.h). If the plug-in is unsuccessful in allocating memory, for example, it can return NPERR_OUT_OF_MEMORY_ERROR.

After you exit NPP_Initialize(), the next event is the instantiation of your plug-in-the class constructors are called to make an instance of your plug-in. From this point, copies of this plug-in will be instantiated with no further calls to NPP_Initialize().

Instantiating a Copy of the Plug-In  At last, the executable image of the plug-in is loaded into memory, and Navigator and the plug-in know how to talk to each other. Any shared resources, such as class variables or database connections, are set up. The plug-in is ready to open its doors for business.

Recall that more than one copy of a plug-in may be present in memory at the same time. All copies will share the same code and the same class variables. Each instance will have its own instance variable. If the operating system supports preemptive multitasking, each copy may have its own thread of execution.

Tip
Although processes are protected from one another by virtue of having discrete address spaces, two or more threads can share an address space and can potentially interfere with each other. If you are writing plug-ins for a multitasking operating system, bear this fact in mind and ensure that you think through issues of reentrant code.

NPP_New()  To instantiate your plug-in, Navigator calls the plug-in's NPP_New() function. NPP_New()'s specification is as follows:

NPError NPP_New(NPMIMEType *pluginType, NPP instance, uint16 mode, int16 argc, 

char *argn[], char *argv[], NPSavedData *saved);

Recall that a plug-in can "sign up" for more than one MIME media type. The parameter pluginType is a pointer to the type actually specified by the stream or the <EMBED> tag. NPMIMEType is a typecast of a char pointer.

Tip
Note that, in the NPP_New() specification, the parameter instance is of type NPP.NPP, a C/C++ structure that has void pointer members named pData and nData. Navigator uses nData to store its private structures concerning this instance. You can use pData for your purposes. Just remember that it is a void pointer. You are responsible for allocating and deallocating any memory associated with pData. You also are on your own with respect to type safety. You may want to write a custom instance class and store a pointer to it in pData in order to get back some type safety.

The mode tells the plug-in whether it was called as an embedded or full-page plug-in. Valid values include NP_EMBED and NP_FULL.

Caution
Although it is tempting to think of mode as an enumerated type (enum), note that it is implemented as an unsigned 16-bit integer. If you write a switch statement to handle the various modes, make sure that you include a default case.

The three parameters argc, argn[], and argv[] are used to pass parameters in from the <EMBED> tag. Suppose the HTML writer writes the following:

<EMBED SRC="/path/to/data" HEIGHT=50 WIDTH=150 Zee=True Par=False Loop>

When Navigator calls NPP_New(), it will pass:

Tip
If you're coming to C and C++ from another language, remember that C and C++ use zero as the starting index for arrays. Therefore, in the preceding example, argn[0] is "SRC" and argv[1] is "50."

The type NPSavedData is a cast of a two-member structure, as follows:

typedef struct _NPSavedData
{
    int32  len;
    void*  buf;
} NPSavedData;

If you store data in NPSavedData when the plug-in is destroyed, Navigator associates this data with the current URL. If the same URL is requested later, Navigator will attempt to restore your saved data here in NPP_New().

Caution
The attempt to restore the NPSavedData associated with an URL will fail if Navigator was shut down and restarted. The attempt also will fail if Navigator needed to purge memory. Do not use NPSavedData to store any information your plug-in cannot afford to lose.

A typical use of NPSavedData is to remember user selections. Another use is to remember how much of a data stream the plug-in displayed. If, for example, the plug-in presents an audio stream, NPSavedData can be used to store an index into the stream. If the user stops the play before the recording is complete, and later loads a page that points to the same audio stream, the plug-in may resume at the point where the previous playback stopped.

Your plug-in should check instance to make sure that it is not null. If Navigator passes a null pointer to your plug-in, the plug-in should return code NPERR_INVALID_INSTANCE_ERROR.

As part of NPP_New(), you probably will want to allocate some memory and build initial instances of your plug-in's classes. Calls to conventional new() or malloc() may fail, even if memory is available, because this memory may be controlled by Navigator and not the plug-in. Use NPN_MemAlloc() to allocate memory on the heap.

Tip
You should use NPN_MemAlloc(), particularly when you write Macintosh plug-ins, because the Macintosh version of Navigator uses a memory-based cache (in addition to the conventional disk-based cache). If you allocate memory by using NPN_MemAlloc(), Navigator flushes the cache as required in an attempt to satisfy the request.

One approach to ensure that objects are allocated by using NPN_MemAlloc() is illustrated in the Plug-Ins SDK. Classes that require instance variables are derived from class CNetscapeMemObject. The Netscape programmers define new() and delete() operators as follows:

void* CNetscapeMemObject::operator new(size_t size)
{ 
  return NPN_MemAlloc( size ); 
}
void CNetscapeMemObject::operator delete( void* theThing, size_t size )
{ 
  NPN_MemFree( theThing ); 
}

Tip
In Netscape's example, they implement the new and delete operators as inline functions. You are advised to do the same. Implementing a function inline saves the overhead of a function call. This savings can add up if the function is called often (as new and delete tend to be).

NPP_NewStream()

After the instance and any standing classes are built, Navigator is ready to deliver the data to the plug-in. To determine how the plug-in prefers to receive the data, Navigator starts by calling NPP_NewStream().

Tip
Not all plug-ins have an associated data stream. If the plug-in doesn't need to read data, the HTML writer should call it as using the TYPE attribute of the <EMBED> tag. If your plug-in doesn't expect data, trap the call to NPP_NewStream and return a usage dialog if the plug-in is called incorrectly.

The specification for NPP_NewStream() is as follows:

NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream, 

NPBool seekable, uint16* stype);

The parameters instance and type have the same meaning they have when used in NPP_New(). Remember that, if you choose, you can read or change any data in instance->pdata.

The type NPStream is a cast of a structure, as follows:

typedef struct _NPStream
{
 void*   pdata;     /* plug-in private data */
 void*   ndata;     /* netscape private data */
 const char*  url;
 uint32  end;
 uint32  lastmodified;
 void*   notifyData;
} NPStream;

The members pdata and ndata have the same meaning they have in type NPP. You may store stream-specific data in pdata; you should leave ndata alone because it is private to Navigator.

The member url has the obvious meaning. The member end is the length of the stream, in bytes. If Navigator cannot determine the length of the stream, end is set to zero. This condition occurs if the server doesn't send a Content-length header, as, for example, if the output is being generated on-the-fly from a LiveWire application or CGI script.

The lastModified parameter should be interpreted as the date-time stamp of the stream, expressed in the number of seconds after midnight, January 1, 1970 (a date and time known as the UNIX epoch).

The function parameter stype is set, by default, to NP_NORMAL. If your plug-in leaves this parameter at NP_NORMAL, Navigator sends the data to the plug-in by using a series of NPP_WriteReady() and NPP_Write() calls, as described in the next section, "Reading the Data as a Stream." You can change stype to NP_ASFILEONLY, which tells Navigator to save the entire stream into a file in its disk cache, then return the path name of this file (in NPP_StreamAsFile()). This technique is described later in this chapter in "Reading the Data as a File."

Note
Navigator also supports a stype of NP_ASFILE, for compatibility with older versions of the SDK. NP_ASFILEONLY is more efficient than NP_ASFILE, and should be used in all new plug-ins if file I/O is desired.

If the parameter seekable is true, the plug-in can set stype to NP_SEEK. The parameter seekable is true if the stream is a local disk file, or is coming from a server that supports byterange requests. If seekable is false, the plug-in can still use NP_SEEK, but Navigator is forced to read the entire file from the server to a local file in the cache.

You will find how to transfer data by using NP_SEEK mode in a later section, "Calling for the Data in a Seekable Stream."

Reading the Data as a Stream  If the plug-in sets stype to NP_NORMAL in NPP_NewStream(), Navigator next calls NPP_WriteReady() to determine how many bytes to send to the plug-in. Typically, you would allocate a buffer in NPP_NewStream() and store a pointer to that buffer somewhere in the structure maintained under stream->pdata. On each call to NPP_WriteReady(), the plug-in would just return the capacity remaining in the buffer.

The specification for NPP_WriteReady() is as follows:

int32 NPP_WriteReady(NPP instance, NPStream *stream);

After calling NPP_WriteReady(), Navigator calls NPP_Write(), in which it sends a buffer full of data. The buffer may be larger than the amount specified in the plug-in's return to NPP_WriteReady(). If so, the plug-in is obligated only to accept the amount it advertised in NPP_WriteReady(). It should copy that much data out of Netscape's buffer and into its own (or simply consume the data directly from Netscape's buffer), and then return the amount actually consumed as the return value of NPP_Write(). The specification for NPP_Write() is as follows:

int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, 

int32 len, void *buf);

The parameter offset is used to show where in the stream the current buffer fits. Offset zero is at the beginning of the stream. The parameter len indicates the length of the actual buffer.

If an error occurs while processing the stream, return a negative value from NPP_Write(). Navigator interprets this value as a request to destroy the stream, and calls the plug-in's own NPP_DestroyStream().

Caution
The buffer delivered by Navigator as part of NPP_Write() isn't persistent from one call to the next. If your plug-in doesn't copy the data into its own buffer before NPP_Write() returns, the data is irretrievably lost.

Reading the Data as a File  If the plug-in set NPP_NewStream()'s stype parameter to NP_ASFILEONLY, Navigator calls the plug-in's NPP_StreamAsFile() function. NPP_StreamAsFile()'s specification is:

void NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname);

Parameters instance and stream have the same meaning they have in other NPP_ functions. The parameter fname is the path to the file, either in the cache or elsewhere on the local disk. Use this path name to perform I/O on the file by using the functions of the native operating system.

Caution
At the start of NPP_StreamAsFile(), check for the possibility that fname may be null. If fname is null, an error may have occurred while Navigator was reading the data stream from the Internet and writing the data to the disk. If the data comes from a local file rather than over the network, Navigator may have encountered an error because the local file did not exist. In either case, error code NPERR_FILE_NOT_FOUND is a good choice for a return code.

Calling for the Data in a Seekable Stream  If you specify NP_SEEK as the stype mode in NPP_NewStream(), Navigator waits for your plug-in to request data. Call for the data by using the NPN_RequestRead() function:

NPError NPN_RequestRead((NPStream *stream, NPByteRange *rangeList);

An NPByteRange is a structure for accessing specific bytes in the stream. The structure is given by the following:

typedef struct _NPByteRange
{
    int32       offset;         /* negative offset means from the end */
    uint32      length;
    struct _NPByteRange* next;
} NPByteRange;

Note that the structure contains a pointer to other such structures. You can use this field to make an NPByteRange into a linked list, stringing NPByteRange structures together to request a series of non-contiguous byte ranges.

NPN_RequestRead() is the first NPN_ function we have seen. Recall that NPN_ functions are calls made by the plug-in back to Navigator. You find more information on NPN_RequestRead() and also the other NPN_ functions in Chapter 12, "Calling Navigator with NPN Methods."

Presenting the Data

Unless the plug-in is called as a background plug-in, Navigator will call NPP_SetWindow() to pass the plug-in a pointer to the plug-in's window. NPP_SetWindow() is specified by the following, where the exact nature of an NPWindow depends upon the platform:

NPError NPP_SetWindow(NPP instance, NPWindow *window);

On a Windows or UNIX machine (running the X-Window System), windows form a hier-archy. As shown in Figure 11.4, the window "owned" by Navigator can have subwindows. NPP_SetWindow() passes a handle to one of these subwindows to the plug-in. On Windows and UNIX platforms, these subwindows are native windows objects.

Figure 11.4: On Windows and X-Window UNIX machines, windows form a parent-child hierarchy.

On a Macintosh, Navigator runs in a native window, but doesn't spawn a child window to pass to the plug-in. Rather, Navigator constructs a native Macintosh drawing area, known as a GrafPort, and passes a pointer to the GrafPort in a structure Navigator calls NPPort.

An NPWindow is defined by the cross-platform structure, as follows:

typedef struct _NPWindow
{
    void*       window;     /* Platform specific window handle */
    uint32      x;          /* Position of top left corner relative */
    uint32      y;          /*   to a netscape page. */
    uint32      width;      /* Maximum window size */
    uint32      height;
    NPRect      clipRect;   /* Clipping rectangle in port coordinates */
                            /* Used by Mac only. */
#ifdef XP_UNIX
    void *      ws_info;        /* Platform-dependent additional data */
#endif /* XP_UNIX */
} NPWindow;

Using Macintosh GrafPorts  On the Macintosh, window points to an NPPort. The NPPort is defined by the following:

typedef struct NP_Port
{
    CGrafPtr    port;           /* GrafPort */
    int32       portx;          /* position inside the topmost window */
    int32       porty;
} NP_Port;

When operating on a Macintosh, use the coordinates of the clipRect to limit drawing. In this way, if the user scrolls an embedded plug-in window to the edge of the screen, the plug-in will not overwrite the scroll bar or the menus. The Macintosh Toolbox call ClipRect() changes the clipping rectangle of the current GrafPort. This function takes a pointer to a rect as its parameter, and interprets the rect as a clipping rectangle in local coordinates.

Caution
Macintosh plug-ins share the GrafPort with other plug-ins and with Navigator itself. Make sure that you restore any port settings you change. For example, before calling ClipRect() to change the clipping rectangle of the port, call GetClip() to get a copy of the existing clipping rectangle. Upon exiting, restore that clipping rectangle with SetClip(), like this:
// make an empty region
rgnHandle theSavedClipRegion = NewRgn();
// copy the old clipping rectangle
GetClip(theSavedClipRegion);
ClipRect(&clipRect);
...
// restore the original clipping rectangle
SetClip(theSavedClipRegion);
// and free the region used to store the old clipRect
DisposeRgn(theSavedClipRegion);

Programming on UNIX in the X Window System  Note that the UNIX version of NPWindow contains an additional member: ws_info, which points to an NPSetWindowCallbackStruct allocated by Navigator. The structure's members, given in the following structure, are the standard pieces of information any program needs to know about a window in the X Window System.

typedef struct
{
    int32               type;
    Display*            display;
    Visual*             visual;
    Colormap            colormap;
    unsigned int        depth;
} NPSetWindowCallbackStruct;

What to Do in NPP_SetWindow()  Whether you are building a plug-in for a Windows computer, a Macintosh, or a UNIX machine, the most important step to take each time NPP_SetWindows() is called is to draw a representation of the data. Experiment with an instrumented version of the plug-in, such as npZero.dll (introduced in Chapter 13, "Analysis of a Simple Plug-In"), to see when Navigator calls NPP_SetWindow(). You will find that NPP_SetWindow() is called when the window is resized, and may be called when the window is moved. You also may find that occasionally NPP_SetWindow() seems to be called for no reason. (Netscape is aware of this problem, but this behavior really doesn't hurt anything.) Your copy of the NPWindow pointer remains valid until the next time NPP_SetWindow() is called-you may want to store it in the instance so that you can compare the old window with the new one to see if NPP_SetWindow() was called unnecessarily.

Note, too, that if the HTML programmer specified your plug-in as hidden, you will never get an NPP_SetWindow() call. In this case, your plug-in should be prepared to initiate its task of presenting the data as soon as the data becomes available, and not wait for a window.

Handling the Interaction

In many cases, the programmer wants to interact with the user as long as the plug-in is active. The programmer may put up buttons, sliders, or pop-up menus in the plug-in's window. This section shows how to capture user events and make them available in the plug-in.

Once You Have the Keyboard Focus

The HTML page has loaded. Navigator, seeing an <EMBED> tag, is retrieving data from the Web server and has loaded your plug-in. Your plug-in is receiving data and has displayed that data in its assigned window. For many plug-ins, the day is over-their work is done.

For more sophisticated plug-ins, however, tasks still await. You have included user controls in your window, and you must wait while the user clicks your buttons or types in your text fields. In short, you are waiting for an event.

Getting Events  The major platforms supported by Navigator (OS/2 Warp, Windows, Macintosh, and UNIX) each have different mechanisms for notifying a program of a user action.

Each operating system provides a way for the programmer to register an interest in certain external events so that the application is notified by the operating system when these events occur.

Note
The discussion in this chapter about Microsoft Windows generally is applicable to all versions of this product. Where the versions differ, this chapter assumes that the Win32 model is used in Windows NT, Windows 95, and the Win32's simulator. (Strictly speaking, Windows 3.1 isn't an operating system but, rather, a sophisticated application that runs on top of MS-DOS.)

All Windows messages have four parameters, regardless of where they come from or what they are telling your program:

The most common message types are Windows messages. In your documentation, all of these messages begin with the characters, WM_.

Remember that Netscape's call to NPP_SetWindow() passes a native window handle to the Windows plug-in. The operating system is prepared to dispatch events associated with this window to the plug-in. The plug-in just has to ask for them.

On the Web
Here's a typical piece of code a plug-in can use to handle messages it receives.

LONG theResult = -1;
switch(Message)
{
  case WM_PALLETTECHANGED:
  {
    ...
    break;
  }
  case WM_PAINT:
  {
    ...
    break;
  }
  default:
  {
    // this message was not for us; call old Window process
    ...
    break;
  }
}
return theResult;

If the WM_PALLETTECHANGED message or the WM_PAINT message ever reaches the plug-in, the plug-in now knows how to handle it. The question is, "How does the plug-in tell Windows that it wants these messages?"

The answer is a callback function. Every window in the Windows operating system has a list of functions known as WindowProc functions attached to it. A typical callback function contains the message-handling code just described.

Note that in the message-handling code fragment, the handler for default case is missing. Windows expects a WindowProc to maintain a pointer to any WindowProc it replaces so that if the new WindowProc cannot handle a message, it can forward it to its predecessor.

Windows makes it easy to reserve space for these pointers in their function, RegisterClass. Unfortunately, Navigator doesn't request this extra space, so plug-ins for Navigator 3.0 on Windows also must maintain a linked list. The code to implement this list is given in the nspi30 SDK (which is available online at http://home.netscape.com/eng/mozilla/3.0/handbook/plugins/index.html).

Figure 11.5 illustrates the process by which Windows dispatches messages.

Figure 11.5: Windows sends the message to the window's most recently attached callback function and expects the callback function to call its predecessor.

To install a new callback function, use the SetWindowLong() function, which you can use to change many different parameters of the window. For example, if This is a pointer to the current data structure that you stored in pData, you can put the following code in your implementation of NPP_SetWindow():

This->hWnd = (HWND)(DWORD)This->fWindow->window;
This->lpfnOldWndProc = 
  (FARPROC) SetWindowLong(This->hWnd,
                          GWL_WNDPROC,
                          (DWORD)myCallbackFunction);
AssociateInstance(This->hWnd, This);

When NPP_SetWindow() is called, this code sets the WindowProc of the window identified by hWnd to the callback function myCallbackFunction. Then it links the old WindowProc, carefully saved in This-> lpfnOldWndProc, by calling AssociateInstance().

Note
AssociateInstance() is part of the system that maintains a linked list of data structures associated with each of the plug-in's open windows. The code that implements this list is omitted here but is available in the Netscape Plug-In 3.0 SDK (nspi30).

When a message is sent to the window identified by hWnd, the callback function is invoked. If the message is on the list of messages handled by the switch statement, the appropriate action is taken and the function returns zero. Otherwise, the default case is taken and the old WindowProc tries to handle the message.

on the web
Here is a sample of the callback function, using the message handling code shown previously.

LONG_NP_LOADDS WINAPI myCallbackFunction ( HWND hWnd,
                                           WORD Message,
                                           WORD wParam,
                                           LONG lParam)
{
  PluginInstance* This = GetInstance(hWnd);
  LONG theResult = -1;
  switch(Message)
  {
    case WM_PALLETTECHANGED:
    {
      ...
      break;
    }
    case WM_PAINT:
    {
      ...
      break;
    }
    default:
    {
      // this message was not for us; call old Window process
      theResult = CallWindowProc(This->lpfnOldWndProc,
                                 hWnd,
                                 Message,
                                 wParam,
                                 lParam);
      break;
    }
  }
return theResult;
}

Look in the Microsoft-supplied file winuser.h to see the list of windows messages (WM_s) available. Which messages should your plug-in handle? You should certainly handle WM_PAINT. This message is sent whenever the window needs to be redrawn. It is sent once as the plug-in is starting, and then again whenever one of the following events occurs:

Some of these events, like resizing, trigger NPP_SetWindow(), but play it safe and handle WM_PAINT directly.

You also may want to handle WM_PALETTECHANGED. This message is sent by Windows when a new window gets the keyboard focus. When the focus changes, the window with the focus sets its logical palette, which changes the system palette. The operating system sends WM_PALETTECHANGED to all visible windows, which gives them a chance to set their own logical palette.

You should recall from the section "Starting the Plug-In," earlier in the chapter, Windows and UNIX plug-ins get a native window, but Macintosh plug-ins get a GrafPort. One consequence of this implementation is that the plug-in sees all events, although it may handle very few. Otherwise, the practical effect of this difference is small.

When an event occurs, Netscape calls NPP_HandleEvent(). This function has the specification:

int16 NPP_HandleEvent(NPP instance, void *event);

The second parameter is a pointer to a standard Macintosh EventRecord. Macintosh events are like Windows messages. Event types include the following:

In addition to the usual Macintosh events, Navigator adds three custom events:

If your plug-in does something special with the cursor when the mouse enters its rectangle, return TRUE in response to this event. Otherwise, return FALSE.

You can read the event type from the event->what field. Generally, your implementation of NPP_HandleEvent() should return TRUE if your plug-in handles the event and FALSE if it does not.

Tip
The definitive guide for Macintosh programming is Inside Macintosh (Addison-Wesley, 1994). Be sure to use the second edition-it is organized topically and is much easier to use than the first edition. For day-to-day use, you want the volumes Overview, Macintosh Toolbox Essentials, and More Macintosh Toolbox. Then you want selected volumes, depending on what your plug-in does. For example, you may need the volume on QuickTime to play video clips in this standard.
Most of the C/C++ compilers for the Macintosh come with CD-ROMs that contain much of the information in Inside Macintosh. If you prefer a paper copy, be sure to get the appropriate volumes of Inside Macintosh-otherwise, you may decide that you are perfectly satisfied with the CD-ROM documentation.

Typical code to implement NPP_HandleEvent() might include the following lines:

Bool theResult = FALSE;
switch (theEvent.what)
{
  case mouseDown:
  {
    Handle_Mouse_Down( &theEvent );
    theResult = TRUE;
    break;
  }
  case updateEvt:
  {
    BeginUpdate( window );
    EraseRgn( window->visRgn );
    Update( window );
    EndUpdate( window );
    theResult = TRUE;
    break;
  }
}
return theResult;

Which Macintosh events should your application handle? The updateEvt is analogous to the Windows message WM_PAINT, and should be handled in a similar fashion. Handle other events as needed by your plug-in.

The event model in the X Window System is similar to the one used by Windows and the Macintosh. You can still think in terms of the server sending events to the client to tell the client that its window needs to be updated. (X calls this event Expose.)

Your plug-in should certainly handle Expose, just as the Windows plug-in handles WM_PAINT and the Macintosh plug-in handles updateEvt. Recall that X widgets are implemented as windows in their own right-don't forget to forward events received by your plug-in to any off-the-Net or commercial widgets your plug-in uses.

Reading the Keyboard Events in Windows  Microsoft Windows, the X Window System, and the Macintosh OS each generate a variety of events or messages associated with the keyboard. This section shows how to capture simple key-down messages in a Windows plug-in.

Recall that Windows messages are handled by a WindowProc function. Your window's WindowProc gets no keyboard messages, however, unless you assign it the keyboard focus.

To give your plug-in window the keyboard focus, call SetFocus(), passing it the HWND to your window. A good place to add this line is at the bottom of your NPP_SetWindow() implementation so that your window is given the focus when it is first instantiated, as well as after any resizing or repainting.

Here's a fragment of the revised NPP_SetWindow():

NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
{
  ...
  This->hWnd = (HWND)(DWORD)This->fWindow->window;
  ...
  // give our plug-in keyboard focus
  SetFocus (This->hWnd);
  return NPERR_NO_ERROR;
}

Next, add code to the window's callback function to handle the keyboard messages. Here's a portion of a WindowProc function to handle the Windows message WM_KEYDOWN:

LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
                                    WORD Message,
                                    WORD wParam,
                                    LONG lParam)
{
  PluginInstance* This = GetInstance(hWnd);
  int nVirtKey;
  LONG lKeyData; // not used in this example
  char buffer[2];
  switch (Message)
  {
  ...
  case WM_KEYDOWN:
  {
    // Windows uses "virtual keys" to accommodate non-Roman character sets.
    // Unicode is at the heart of Window's future.
    // When the message is WM_KEYDOWN, wParam holds the virtual key
    // and lParam holds extension information about the keypress
    nvirtKey = (int) wParam;
    lKeyData = lParam;

    // put the incoming character into a string
    buffer[0] = (char) nvirtKey;
    buffer[1] = NULL;

    // append the string into the instance's persistent buffer
    strcat(This->fBuffer, buffer);

    // trigger a repainting of the window
    InvalidateRect(hWnd, NULL, FALSE);
    UpdateWindow(hWnd);
    break;
  }
  ...
  case WM_PAINT
  {
    PAINTSTRUCT paint;
    HDC hDC = BeginPaint(hWnd, &paint);
    TextOut(hDC, 0, 0, This->fBuffer, strlen(This->fBuffer));
    EndPaint(hWnd, &paint);
    break;
  }
...
}

Note that this code collects the keystrokes in a structure named fBuffer, which is a member of This. This is a pointer to an instance of class PluginInstance. Add fBuffer to class PluginInstance by adding the line shown in bold.

typedef struct _PluginInstance
{
  NPWindow*     fWindow;
  HWND          hWnd;
  uint16        fMode;
#ifdef STRICT
  WNDPROC       lpfnOldWndProc;
#else
  FARPROC       lpfnOldWndProc;
#endif
  NPSavedData*  pSavedInstanceData;
  char          fBuffer[80];
  PluginInstance* pNext;
} PluginInstance;

Finally, initialize the fBuffer member to the empty string. Be careful where you put this initialization. The callback function will be called on every keystroke, so this clearly is not the right place. Likewise, NPP_SetWindow() gets called when the window is resized or redrawn, as well as when the window is initially allocated.

You can put initialization code for instance variables in NPP_New(), or in NPP_SetWindow(), inside the if statement that verifies that the window is new:

NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window)
{
  if (NULL == instance)
    return NPERR_INVALID_INSTANCE_ERROR;
  PluginInstance* This = (PluginInstance*) instance->pdata;
  if ((window->window != NULL) && (This->hWnd == NULL))
  {
    This->fWindow = window;
    This->hWnd = (HWND)(DWORD)This->fWindow->window;
    ...
    // empty the string
    This->fBuffer[0] = NULL;
  }
  ...
}

Figure 11.6 shows the resulting plug-in in action.

Figure 11.6: This simple plug-in translates every keystroke into a character in the plug-in window.

Several things are wrong with this plug-in, not the least of which is that fBuffer has a fixed length that will quickly overflow. If you want a quick fix to this problem, you might replace the char array with a dynamically sized container from the Standard Template Library (STL), which comes with most C++ compilers.

The more immediate problem is that the program translates every keystroke to the screen-shift keys, delete keys, and also character keys. A better design adds a switch statement inside the WM_KEYDOWN case to handle non-character keys.

With this design, the program shows uppercase characters when the shift key is down. It deletes characters from the buffer when the backspace key is pressed.

This kind of keyboard behavior is so basic, however, that we shouldn't have to build it from scratch. Someone has written a generic keyboard and text buffer manager that we can incorporate into our program.

Microsoft has written classes to handle thousands of common Windows tasks and provides it as Microsoft Foundation Classes (MFC).

Tip
To implement a more sophisticated text editor in your plug-in, use the CEdit class from MFC. You can get even more functionality by using CEditView, which adds printing and find-and-replace capability. If you need to display text in more than one font or if you need special character formatting, use class CRichEditView.

Watching the Mouse  Handling mouse clicks is just as simple as handling key presses, but the amount of code is greater because users expect to be able to do more with the mouse.

You don't have to do anything special to have mouse messages sent to your window. Depending on the features you want to implement, however, you may need to handle more than one mouse message. This section shows how to implement a simple "scribbler" that allows the user to draw with the mouse.

The design of the scribbler is based on three mouse messages:

When Windows sends WM_LBUTTONDOWN, the plug-in records the mouse location and records that it is now in the "drawing" state. As the mouse is moved, the plug-in records the new mouse location, and calls InvalidateRect() and UpdateWindow() to force a WM_PAINT message.

The WM_PAINT handler draws a line from the old mouse location to the new mouse location if the plug-in is in the drawing state. When, finally, the user releases the left mouse button, the plug-in records the fact that it no longer is in the drawing state.

Here are the additions to the callback function to handle these three new messages, and the additional code for WM_PAINT:

LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
                                    WORD Message,
                                    WORD wParam,
                                    LONG lParam)
{
  PluginInstance* This = GetInstance(hWnd);
  switch (message)
  {
    ...
    case WM_LBUTTONDOWN:
    {
      // Windows encodes the mouse position in the lParam parameter.
      This->newMouseLocation.x = LOWORD(lParam);
      This->newMouseLocation.y = HIWORD(lParam);
      This->oldMouseLocation = This->newMouseLocation;
      SetCapture(hWnd);
      This->bDrawTrail = TRUE;
      break;
    }
    case WM_MOUSEMOVE:
    {
      if (This->bDrawTrail)
      {
        This->oldMouseLocation = This->newMouseLocation;
        This->newMouseLocation.x = LOWORD(lParam);
        This->newMouseLocation.y = HIWORD(lParam);
        InvalidateRect(hWnd, NULL, FALSE);
        UpdateWindow(hWnd);
      }
      break;
    }
    case WM_LBUTTONUP:
    {
      ReleaseCapture();
      This->bDrawTrail = FALSE;
      break;
    }
    ...
    case WM_PAINT:
    {
      HPEN hOldPen;
      HPEN hPen;
      long colorShade = 0x000001L;
      int  penWidth = 2;

      PAINTSTRUCT paint;
      HDC hDC = BeginPaint(hWnd, &paint);
      hPen = CreatePen(PS_SOLID, penWidth, colorShade);
      hOldPen = (HPEN) SelectObject(hDC, hPen);
      MoveToEx(hDC,
               This->oldMouseLocation.x,
               This->oldMouseLocation.y,
               NULL);
      LineTo ( hDC,
               newMouseLocation.x,
               newMouseLocation.y);
      SelectObject(hDC, hOldPen);
      DeleteObject(hPen);
      EndPaint(hWnd, &paint);
      break;
    }
    ...
  } // end switch
  ...
  return 0L;
}

In this code fragment, three pieces of data must be preserved between invocations of the plug-in: oldMouseLocation, newMouseLocation, and bDrawTrail. Add these definitions to pluginInstance:

typedef struct _PluginInstance
{
  NPWindow*     fWindow;
  HWND          hWnd;
  uint16        fMode;
#ifdef STRICT
  WNDPROC       lpfnOldWndProc;
#else
  FARPROC       lpfnOldWndProc;
#endif
  NPSavedData*  pSavedInstanceData;
  POINT         oldMouseLocation;
  POINT         newMouseLocation;
  BOOL          bDrawTrail;
  PluginInstance* pNext;
} PluginInstance;

Note that this plug-in uses SetCapture() and ReleaseCapture() to ensure that all mouse movement is sent to this plug-in while the left mouse button is down, even if the mouse moves beyond the bounds of the plug-in's window.

Figure 11.7 shows the scribbler in action.

Figure 11.7: This plug-in captures mouse movement while the left mouse button is down and uses that movement to draw lines into the plug-in window.

A Better Way of Managing Message Mapping

As the plug-in becomes more sophisticated, the callback function becomes larger and more complex. You'll quickly want to pull the more elaborate handlers (such as WM_PAINT) into their own functions (such as onPaint).

To handle a new message, you need to be sure to make two changes: Add the new message to the switch statement in the callback function, where you call the handler, and implement the handler function itself.

Whenever you have to keep two things synchronized in software, you have a potential defect. Sometimes you struggle over a program in which you implemented a handler function but forgot to add the message in the callback function. At other times, the linker complains that you didn't supply a handler, even though you called one.

If you are using Microsoft Visual C++, or a similar compiler that supports Microsoft Foundation Classes, you can get the development environment to help you map messages onto their handlers by using a construct known as a message map.

Most MFC applications include the macro DECLARE_MESSAGE_MAP(); in their window definition. This macro works much like the virtual keyword in C++ because it tells the compiler that the class overrides the handling of certain messages.

To complete the hookup of the message map, add the BEGIN_MESSAGE_MAP() macro to the body of the application, listing the messages your application is prepared to handle. To handle the WM_PAINT message, for example, your message map would include the following line:

ON_WM_PAINT()

The default name of the handler function for WM_PAINT is OnPaint(). When a plug-in with ON_WM_PAINT() in its message map receives the message WM_PAINT, OnPaint() is invoked. From here, processing goes on just as it did when you handled WM_PAINT explicitly in your WindowProc.

If you use Microsoft's Visual C++, the fastest way to set up a plug-in that is based on MFC is to use the MFC AppWizard. Open the Microsoft Developer Studio and choose File, New. In the New dialog box, choose Project Workspace, and in the New Project Workspace dialog box, choose MFC AppWizard (dll). Fill in a name and click the Create button.

Caution
Remember that under Windows, plug-ins need a name that begins with the letters "np." You will find it's difficult to change a project's name after it is set up. To keep your project internally consistent, put the "np" characters in the name right from the beginning-in the Name field of the New Project Workspace dialog.

The MFC AppWizard for Dynamic Link Libraries (DLLs) has only one step, and the default values give reasonable behavior for plug-ins. Click the Finish button, then click OK to approve the New Project Information.

Tip
The Professional edition of Microsoft Visual C++ gives you the option of statically linking MFC into your DLL. This option is worth serious consideration.
If you statically link MFC in, your plug-in will be bigger but you won't have to worry that the end-user doesn't have the MFC DLL. If you dynamically link MFC, you save space but, undoubtedly, some users will not have the MFC bDLL on their computer. They won't be able to run your plug-in without it.

Examine the application class produced by the AppWizard. If you named your plug-in npTest, this class is named CNpTestApp. At the bottom of the class declaration, you find the following macro:

DECLARE_MESSAGE_MAP()
In the .cpp
file where the class methods are implemented, you find the following lines:
BEGIN_MESSAGE_MAP(CNpTestApp, CWinApp)
  //{{AFX_MSG_MAP(CNpTestApp)
       //NOTE - the ClassWizard will add and remove mapping macros here
       //   DO NOT EDIT what you see in these blocks of generated code!
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

When AppWizard first builds your class, it reserves a message map but doesn't put messages into it. The comments have a special format-they are maintained by tools in the Developer Studio.

The macro BEGIN_MESSAGE_MAP() takes two parameters. The first (CNpTestApp in our example) is the target of the messages associated with this map. Any class derived from MFC's CCmdTarget can have a message map.

Note that CNpTestApp is derived from CWinApp. In MFC, the class CWinApp (or its descendent) provides the main event loop for the programmer. When the class's member function Run is invoked, the main event loop begins to spin, dispatching messages (or calling the idle function when there is nothing else to do).

Run dispatches messages to the application's windows. Each window has a WindowProc defined at the class level. A message map in that window class dispatches messages to their handlers.

Figure 11.8 shows the message-handling hierarchy of MFC. If your derived class gets a message it doesn't understand, it forwards the message up the hierarchy. The second parameter of the BEGIN_MESSAGE_MAP() macro in a given class implementation contains the name of the class directly "above" the current class. This class is the point at which your code interacts with MFC code to handle messages.

Figure 11.8: The message map for this example is BEGIN-MESSAGE-MAP (CNpTestApp, CWinApp).

Each message macro (such as ON_WM_PAINT) is associated with a handler, such as OnPaint(). Note that the macros don't take arguments. The framework passes arguments to the handler if it needs them. Examine the MFC declaration of class CWnd. You will find the handler for ON_WM_PAINT declared as follows:

afx_msg void OnPaint();

The afx_msg keyword is a flag to the preprocessor, telling it that this function is associated with a message map. Its presence reminds the programmer that this function is special, in much the same way as the virtual keyword reminds a programmer about the special handling of virtual functions. (Message maps are not implemented using the virtual mechanism, although the effect is similar.)

Tip
Can't find OnPaint() in the example MFC-based plug-ins?
Sophisticated applications benefit by having the data separated from the presentation of that data. In MFC, the data is stored in a document and presented by one or more views. Views are ultimately derived from the MFC class CView, which handles WM_PAINT by calling the view's OnDraw() member.
As the programmer, you are expected to implement OnDraw(). So, when your MFC-based plug-in gets the WM_PAINT message, your window's message map forwards it up the hierarchy until it reaches CView. CView calls the OnDraw() member back in your own derived class.

Processing the Stream

So far, theseexamples have ignored the major reason for building a plug-in-a server is sending you a stream of data that Netscape doesn't know how to handle. Recall from the earlier section in this chapter, "Starting the Plug-In," that the preferred way to read your data is as a stream (stype is NP_NORMAL).

If you accept the stream in NP_NORMAL stype, Navigator makes a series of NPP_WriteReady()-NPP_Write() calls until the stream is completely sent. If you're developing your own data type, consider placing information about the size of the file at the head of the file so that you can receive it as a stream.

Handling Streams with the Windows API  Here's a procedure for handling many types of streaming data:

  1. In NPP_New(), allocate a buffer and store it in the data structure you associate with pdata. Consider using a container class from the STL or another class library so you don't have to worry about the details of buffer management.
  2. In NPP_NewStream(), set the stype of the stream to NP_NORMAL.
  3. In NPP_WriteReady(), report out the capacity of the buffer.
  4. In NPP_Write(), copy the data into your local buffer. Record how much of the buffer capacity is left so the plug-in can return that information on the next invocation of NPP_WriteReady().
  5. In NPP_SetWindow(), subclass the window and set up a WindowProc that handles WM_PAINT by painting a representation of the data in the buffer.

An Example of Streaming Data  Suppose that you define a data type that consists of positive numbers separated by newlines. Following the procedure given in the previous section, you might write the following code.

Add the following members to PluginInstance:

short fBuffer[kBufferSize];
short fTopOfWindow;
short fBottomOfWindow;

Set kBufferSize to a reasonable size, with a line like the following:

const short kBufferSize = 32767;

At the bottom of NPP_NewStream(), before the function returns, add the following:

*stype = NP_NORMAL

Provide an NPP_WriteReady() function:

int32 NP_LOADDS NPP_WriteReady(NPP instance, NPStream* stream)
{
  int32 theResult = 0L;
  if (instance != NULL)
  {
    PluginInstance* This = (PluginInstance*) instance->pdata;
    theResult = (kfBufferSize - (This->fBottomOfWindow) - 1);
  }
  return theResult;
}

Provide an NPP_Write()function, as follows:

int32 NP_LOADDS NPP_Write(NPP instance, NPStream* stream,
                          int32 offset, int32 len, void* buffer)
{
  if (instance != NULL)
  {
    PluginInstance* This = (PluginInstance*) instance->pdata;
    int i;

    // Start stripping off ASCII numbers
    char* theString = strtok((char*) buffer, "\n");
    if (NULL != theString)
    {
      This->fBuffer[offset] = atoi(theString);
      for (i=offset+1; i<kBufferSize && (i-offset < len); I++)
      {
        theString = strtok(NULL, "\n");

        // keep reading until we run out of room or data
        // don't trust len; it can overreport the data
        if (NULL == theString)
          break;
        This->fBuffer[i] = atoi(theString);
      }
      This->fBottomOfWindow = i;
      return (len);
    }
    else
       return 0L;
  }
  else
    return 0L;
}

Finally, every time the window needs redrawing, plot out the data. This display routine is quite simple-a more sophisticated routine can include scroll bars, autoscaling, grid lines and axes, or even simple animation.

LONG NP_LOADDS WINAPI SubClassFunc( HWND hWnd,
                                    WORD Message,
                                    WORD wParam,
                                    LONG lParam)
{
  PluginInstance* This = GetInstance(hWnd);
  LONG theResult = 0L;
  switch (Message)
  {
    ...
    case WM_PAINT:
    {
       HPEN hOldPen;
       HPEN hPen;
       long colorShade = 0x000001L;
       int penWidth = 2;

       PAINTSTRUCT paint;
       HDC hDC = BeginPaint(hWnd, &paint);
       hPen = CreatePen(PS_SOLID, penWidth, colorShade);
       hOldPen = (HPEN) SelectObject(hDC, hPen);
       MoveToEx(hDC, This->fBuffer(This->fTopOfWindow),
                     This->fTopOfWindow,
                     NULL);
       for (int i=This->fTopOfWindow+1;
                i<This->fBottomOfWindow;
                i++)
         LineTo(hDC, This->fBuffer[i], i); // autoscale here if desired
        SelectObject(hDC, hOldPen);
        DeleteObject(hPen);

 EndPaint(hWnd, &paint);
        theResult = 0L;
        break;
      } // end case
      ...
    } // end switch
    fTopofWindow=0;
    fBottomofWindow=0;    return theResult;
}

Figure 11.9 shows the result plotted by this plug-in.

Figure 11.9: The simplest version of this plug-in plots data from the stream, from the top of the window to the bottom of the window, without scrolling or animating.

Handling Streams in MFC  If you are implementing in MFC, consider deriving a plug-in document that is instantiated in NPP_New(). Attach a view to this document in NPP_SetWindow() and implement that view's OnDraw() member so that it puts a representation of the document into the window.

The default memory map take cares of sending WM_PAINT to CView. CView, in turn, calls its implementation of OnPaint(), which calls your derived view's OnDraw().

Starting Additional Streams

During its interaction, your plug-in may produce data that should be shown to the user. Use the Navigator method NPN_NewStream() to send this data to a target in Navigator. Chapter 12, "Calling Navigator with NPN Methods," describes the various targets you can choose.

As a result of the interaction, your plug-in can identify data that is still on the Web that the user should see. For example, you can include buttons in your plug-in that function like links. If the user clicks one of these links, you want to call for the data from the associated URL.

Understanding NPP_Print()

Recall that you, as the programmer, write all NPP functions. These NPP functions are called by Navigator. NPP_Print() is unique in that, under the right circumstances, Navigator calls it twice. NPP_Print() is Navigator's request that your plug-in print itself.

Embedded Mode  An HTML author can include a plug-in by using an <EMBED> statement. Alternatively, the HTML author can put a link in his or her Web page that points to a whole new document.

If your plug-in is called through <EMBED>, it is an embedded plug-in. If your plug-in is invoked on a document, it is a full-page plug-in.

If your plug-in is embedded, Netscape calls the plug-in's NPP_Print() function once. If your plug-in is full-page, Netscape calls your plug-in's NPP_Print() once to find out whether the plug-in handles the whole printing process. If it does not, Netscape calls your plug-in again, also through NPP_Print(), to have it handle the printing of the content.

Confused? NPP_Print() is a busy place, particularly for a full-page plug-in. This section shows an example of how to handle NPP_Print() in an embedded plug-in. NPP_Print() is defined by the following line:

void NPP_Print(NPP instance, NPPrint *platformPrint);

Here, instance is the usual instance that is passed from Netscape to nearly all NPP functions and platformPrint is a structure whose mode member can take on the NP_EMBED and NP_FULL values. When the user elects to print a page that has an embedded plug-in, Netscape calls NPP_Print() for the plug-in with platformPrint->mode set to NP_EMBED.

The full declaration of platformPrint is as follows:

typedef struct _NPPrint
{
    uint16      mode;           /* NP_FULL or NP_EMBED */
    union
    {
        NPFullPrint fullPrint;  /* if mode is NP_FULL */
        NPEmbedPrint embedPrint;/* if mode is NP_EMBED */
    } print;
} NPPrint;

Note that the union statement says that the two data members fullPrint and embedPrint occupy the same space-only one member can be present in an instance at a time. If mode is set to NP_EMBED, the union print has embedPrint.

The structure of embedPrint is as follows:

typedef struct _NPEmbedPrint
{
    NPWindow    window;
    void*       platformPrint;  /* Platform-specific printing info */
} NPEmbedPrint;

The NPWindow in this structure is the window the plug-in should draw into for printing. Recall from the earlier section in this chapter, "Starting the Plug-In," that an NPWindow is defined as shown in the following lines:

typedef struct _NPWindow 
{
    void*       window;         /* Platform specific window handle */
    uint32      x;              /* Position of top left corner relative */
    uint32      y;              /*   to a netscape page. */
    uint32      width;          /* Maximum window size */
    uint32      height;
    NPRect      clipRect;       /* Clipping rectangle in port coordinates */
                                /* Used by Mac only. */
#ifdef XP_UNIX
    void *      ws_info;        /* Platform-dependent additional data */
#endif /* XP_UNIX */
} NPWindow;

Your plug-in should draw into the window member to print its contents.

Caution
On a Windows machine, the coordinates of the window rectangle are in twips. In the MM_TWIPS mapping mode, the logical unit, twip, is 1/20 of a point or 1/1440 of an inch, and the base unit is in physical inches. This design is never used with a display (because displays come in all sizes and the physical inch is meaningless), but it is perfectly appropriate when drawing to a printer.
Make sure that you call DPtoLP() to convert these points when you print text into the window. DPtoLP() converts device coordinates to logical coordinates using the current mapping mode.

Full-Page Mode  When Navigator is ready to print your full-page plug-in, it first gives you an opportunity to take control of the printing process. Some documents benefit from this opportunity. For example, Figures 11.10 and 11.11 contrast the standard Print dialog box with the Print dialog provided by Microsoft Word.

Figure 11.10: Unless you take control of the printing process, Navigator provides a standard Print dialog box for printing your full-page plug-in.

Figure 11.11: Microsoft added several items to the Print dialog box to supplement the standard Print dialog box.

To allow you to take over the printing process, Navigator calls NPP_Print() with platformPrint->mode set to NP_FULL and union member print filled with an NPFullPrint. Navigator defines an NPFullPrint as follows:

typedef struct _NPFullPrint
{
    NPBool      pluginPrinted;  /* Set TRUE if plugin handled fullscreen */
                                /*      printing */
    NPBool      printOne;       /* TRUE if plugin should print one copy  */
                                /*      to default printer */
    void*       platformPrint;  /* Platform-specific printing info */
} NPFullPrint;

The platformPrint member has the same information it has in NPEmbedded. Using the NPBool, printOne is self-explanatory and tells the plug-in that the user just wants a quick, no-dialog print.

The NPBool pluginPrinted is the member that distinguishes full-page printing from embedded plug-in printing. If you commandeer the printing process, providing your own dialog boxes and driving the printer yourself, set pluginPrinted to true. If Navigator sees this flag set to true, it bypasses its own Print dialog.

Tip
Although Netscape allows your full-page plug-in to take over the printing process, you need to use this option only if your plug-in has special needs. Most full-page plug-ins can set pluginPrinted to false, allowing Navigator to handle the Print dialog.

If you set pluginPrinted to false, Navigator initiates the printing process. When it is ready for your content, Navigator calls your plug-in again-through NPP_Print()-with platformPrint->mode set to NP_EMBED.

Printing is supported by the Microsoft Foundations Classes (MFCs) CView class. If your full-page plug-in handles multipage documents, override CView's OnPrint() member in your own derived class.

In your view's version of OnPrint(), look up the size of the printer's page and use this information to adjust the clipping region of the device context that is associated with the print window. After the "page" is defined, call the view's OnDraw() member to output the content.

When you are ready to print, you will want to put up a Cancel dialog box, in case the user decides to stop the print job before it completes. Putting up this kind of dialog box is a four-step process:

  1. Set a Boolean flag that the abort procedure and the plug-in can share. The abort procedure reads this flag to see if it should allow the printing to continue.
  2. Register an AbortProc function with the operating system.
  3. Display the modeless Cancel dialog box.
  4. Disable the application window.

Here's the code for these four steps:

BOOL bContinuePrinting = TRUE;
SetAbortProc(hDC, AbortProc);
hdlgCancel = CreateDialog(hinst, 
  (LPTSTR) "AbortDlg", hWnd, (DLGPROC) AbortPrintJob);
EnableWindow(hWnd, FALSE);

The abort procedure can be very simple, as follows:

BOOL CALLBACK AbortProc(HDC hDC, int nCode)
{
  MSG msg;
  while (PeekMessage((LPMSG) &msg, (HWND) NULL, 0, 0, PM_REMOVE))
  {
    // if the message isn't for us, send it on
    if (!IsDialogMessage(hdlgCancel, (LPMSG) &arg))
    {
      TranslateMessage((LPMSG) &msg);
      DispatchMessage((LPMSG) &msg);
    }
  }
  return bContinuePrinting; // set to FALSE by Cancel button
}

Use your development environment to construct a suitable Cancel dialog box. Write a dialog procedure that includes the following code:

switch (message)
  ...
  case WM_COMMAND: // the Cancel button is the only control
    bContinuePrinting = FALSE;
    return TRUE:
    break;
  ...
}

Now when Navigator tells you to print, you can put up the common Print dialog box (possibly with customizations). Verify that the printer you selected can print your document. Put the Cancel dialog box on-screen.

If the user clicks the Cancel button, the WM_COMMAND message is sent to the AbortPrintJob function, which sets bContinuePrinting to FALSE. When the plug-in sees bContinuePrinting change to FALSE, it stops drawing pages and cleans up from printing.

After the Cancel dialog box is up, the plug-in calls StartDoc(), and then StartPage(). These Windows functions alert Windows that the print data is coming.

Then, based on the data returned from the Print dialog, you find the page(s) the user wants to print and draw those pages' contents to the print window. Call EndPage() after each page is printed.

When all the data has been sent (or bContinuePrinting becomes false), call EndDoc(). Finally, call EnableWindow() to return control to the plug-in window, call DestroyWindow() on the cancel dialog, and call DeleteDC() on the device context that is allocated by StartDoc().

For most plug-ins, this is all that's needed. But if your plug-in draws text, you may want to think about fonts and text metrics. Microsoft Windows comes with various fonts for the screen-Courier, Helvetica, and Times Roman, to name a few.

Most printers include most of these common fonts. But not all printers use all the standard fonts, and users can add fonts to their system. Therefore, a user may have text on the screen in a font that is unavailable in the printer.

Note
Windows supports many different font technologies. A Postscript printer uses, of course, a PostScript font. That font may be downloaded from the computer or stored in the printer's read-only memory (ROM).
Hewlett-Packard's PCL printers use HP's Printer Control Language (PCL). Both Microsoft and HP provide drivers for these printers which offer good font quality.
On the PC's screen, Windows can use bit-mapped fonts (called raster fonts). If a user prints a document that is being viewed with raster fonts, Windows must try to match a printer font (for example, PostScript or PCL) to the raster font.
TrueType technology is a compromise between raster fonts and printer fonts. TrueType fonts are built from mathematical models (like PostScript fonts) but can be displayed on the screen as well as on the printed page.
For more information on Windows fonts, see Chapter 8, "Working with Fonts," in Platinum Edition Using Windows 95 (Que, 1996).

Windows includes an elaborate penalty weighting algorithm based on 10 font characteristics, including character sets, pitch, family, face name, height, width, and various styles. Windows uses this algorithm to choose a printer font that should be a close match to the screen font.

You, the programmer, should control which font the plug-in uses and match it with the available printer font. Otherwise, the printing may be done in a font that is quite unlike the font used on-screen.

Tip
If your plug-in chooses the screen font, choose a TrueType font whenever possible. One big advantage of TrueType is that the printer version and the screen version match exactly.

Using NPP_Destroy()

It's said that "All good things must come to an end…." So it is with plug-ins. When the user leaves the page on which your plug-in content is displayed, Navigator calls your plug-in's NPP_Destroy() function, and then deletes the plug-in instance.

Freeing Dynamic Memory  In C++, every class has one or more constructors and one destructor. The constructors get called in response to instantiations. The destructor gets called when a stack-based instance goes out of scope or a dynamically allocated instance is deleted.

If a C++ class allocates memory in its constructor, it should deallocate that memory in the destructor. If you forget to deallocate the memory, the pointer to the allocated memory is lost, but the memory remains unavailable for use by other applications.

If the application runs long enough, it eventually uses up all memory and crashes the Netscape client. Depending upon the operating system, the user may lose work in other open applications as well. The phenomenon of allocating memory and then failing to deallocate it is known as a memory leak, which can lead to subtle defects in your software.

Figure 11.12 illustrates a memory leak.

Figure 11.12: If you forget to free memory that was allocated in the constructor, Navigator eventually runs out of memory and crashes.

Recall that you should allocate dynamic memory for a plug-in by calling NPN_MemAlloc(). The corresponding routine to deallocate that memory is NPN_MemFree(). In C++, implement new and delete using NPN_MemAlloc() and NPN_MemFree() to ensure proper behavior.

Besides deallocating dynamic memory allocated in constructors, you should make sure that you deallocate all memory you have reserved in the structure pointed to by instance->pdata. The rule of thumb to remember is "For every new, there must be a delete."

Saving Instance Data

Often, you want to associate some record of the user's activity with the URL they visited. For example, the user may have opened a video clip and played the first 30 frames. If the user leaves this URL and comes back later, perhaps play should resume at frame 31. Perhaps the user has printed the current document, specifying parameters such as page range and orientation. If he or she returns to this URL and prints the document again, you may want your plug-in to remember the parameters the user specified before, and use them as the default settings.

You can store data in Navigator's memory and associate it with the current URL by saving that data in the save parameter of NPP_Destroy().

NPP_Destroy() is specified as follows:

NPError NPP_Destroy(NPP instance, NPSavedData **save);
Here, NPSavedData
is as follows:
typedef struct _NPSavedData
{
    int32       len;
    void*       buf;
} NPSavedData;

If you allocate saved data in NPP_Destroy(), Navigator keeps this data in case the user returns to the same URL. You might want to save data if the following instances are true:

To use saved data, follow these five steps in NPP_Destroy():

  1. Design and use a C++ class or structure that has all the information you want to store. For this example, the class is named TStoredData.
  2. Allocate a new instance of TStoredData (here named theStoredData) and fill its fields.
  3. Allocate a new NPSavedData, with a line such as the following:
    *save = (NPSavedData*) NPN_MemAlloc(sizeof(NPSavedData));
  4. Assign sizeof(theStoredData) to (*save)->len.
  5. Assign the address of theStoredData to (*save)->buf.

Now complete NPP_Destroy(). Navigator deletes your plug-in instance. Later, if the user visits the same URL as the earlier instance, Navigator passes theStoredData in the saved parameter of NPP_New().

Tip
Make sure that whatever structure you put into (*save)->buf (in this example, TStoredData) is flat-that is, none of its members are themselves dynamically allocated. If Navigator runs short on memory, it begins deallocating saved data to free space.
Because it doesn't know the internal structure of your storage class, it cannot deallocate internal dynamic memory. Figures 11.13 and 11.14 illustrate the wrong and right ways to design TStoredData.

Figure 11.13: If any members of your storage class are dynamically allocated, that memory is lost when Navigator deletes the saved data, resulting in a memory leak.

Figure 11.14: Flatten your stored data before saving it so that Navigator can get every field with a single call to delete.

Caution
Remember that Navigator deletes saved data if it needs to free up memory. Remember also that all saved data is stored in RAM and is lost if the user exits Navigator. If your plug-in produces critical data that you don't want to lose, either save it on the user's hard disk, or use NPN_PostURL() or NPN_PostURLNotify() to send the data back to the server.
Note that some users are uncomfortable with the idea that a program they download from the Net may write to their hard disk. Be courteous-ask the user at runtime if it's okay, or at least put a conspicuous notice in your documentation that the file will be written to the user's hard drive.

Destroying the Window  If you are an experienced programmer, you are used to explicitly closing and deallocating all windows when the program exits. You don't need to do this in a plug-in. By the time NPP_Destroy() is called, the window is gone.

Caution
Do not do any graphics operations in NPP_Destroy(). Your window is no longer valid-it is gone!

Using NPP_Shutdown()

Recall from the earlier section "Starting the Plug-In," that Navigator calls NPP_Initialize() when the plug-in is first loaded, before calling NPP_New() to make the first instance. NPP_Initialize() is the place to allocate any data that is used by all instances of your plug-in.

NPP_Destroy() corresponds to NPP_New() in that NPP_Destroy() is called whenever the instance is deleted. Similarly, NPP_Shutdown() corresponds to NPP_Initialize().

When the last instance of a plug-in is deleted, Navigator calls NPP_Shutdown() so that it can delete any dynamic data allocated by NPP_Initialize(). Just as in NPP_Destroy(), failure to free all the memory allocated in NPP_Initialize() leads to a memory leak.

In the SDK…

When your plug-in is started, Navigator initiates a series of calls that ensures both Navigator and the plug-in know how to call each other. Then, Navigator makes a series of calls to load the plug-in and make a new instance of it. Finally, Navigator offers data and a window, so that the plug-in can do what it was designed to do: transform an unknown data type into a representation that is visible from inside Navigator.

The LiveWire/Plug-ins xSDK includes a set of HTML pages that describe each of the NPP methods. Be sure to load and use those pages to get the latest information on these important functions.