Chapter 12

Calling Navigator with NPN Methods


CONTENTS


Plug-in methods are divided into two sets: Those that are in the plug-in and called by Navigator, and those that are in Navigator and called by the plug-in.

The first set of methods all begin with the characters NPP (Netscape Plug-in: Plug-in defined). The second set begins with NPN (Netscape Plug-in: Navigator defined).

This chapter describes the NPN methods your plug-in uses to make requests to Navigator. Using NPN methods, you can ask Navigator to post data back to a Web server, pull down a new stream of data, or display information in Navigator's status bar.

Using NPN_GetURL()

In Chapter 11, "Step-by-Step Through the NPP Methods," you learn that Navigator and the plug-in exchange control information through the NPP and NPN methods. They exchange content through streams.

The Netscape definition of a stream includes the content (in a sequence of bytes), positioning information (showing where a current set of bytes fits in the overall stream), and, when available, an overall size. Each stream also has an associated MIME media type that you can use to invoke an additional instance of the plug-in or a new plug-in.

Note
When possible, Navigator reports the overall size of a stream so that your plug-in can estimate how long it will take to download the entire stream. Use good judgment when interpreting the stream size. But if the stream is generated by a Common Gateway Interface (CGI) script or server-side LiveWire program, the size may not be known until the stream is complete.

Tip
If you are developing your MIME media type for use with plug-ins and your content sometimes is produced on-the-fly, consider placing a size field at the beginning of the contents. In this way, your plug-in doesn't have to rely on the Netscape-supplied parameter.
In the section "Using NPN_Status()" later in this chapter, you learn how to put up a progress indicator in the status bar just like Navigator's.

If your plug-in is called as a full-page plug-in or if it's called by using an <EMBED> tag with the SRC attribute, Navigator calls the plug-in's NPP_NewStream() to associate a stream with the plug-in instance. During the life of the plug-in, Navigator sends the contents of the stream by using repeated calls to NPP_WriteReady() and NPP_Write() (if your plug-in sets the stream's stype to NP_NORMAL).

In Chapter 11, you learn that the plug-in can request that the stream be pushed by the server if the stype is NP_NORMAL, pulled by the plug-in if the stype is NP_SEEK, or read from a file if the stype is NP_ASFILEONLY.

You can ask Navigator to open more streams for you by calling NPN_GetURL(). The specification for NPN_GetURL() is as follows:

NPError NPN_GetURL(NPP instance, const char *url, const char *window);

Here, instance is the NPP described in Chapter 11. The url parameter is the uniform resource locator (URL) of the resource requested.

If url specifies a Web resource (http://), Navigator uses the GET method to retrieve the resource. The window parameter is the target to which the results of the request are sent. If window is NULL, the results of the request are sent to the plug-in using the same protocol as that used in the initial stream, NPP_NewStream(), followed by NPP_WriteReady() and NPP_Write() or other calls, depending on the stream's stype.

If you want to show the results of the request to the user rather than processing them in the plug-in, send the stream to a named target. You can require the HTML coder to give a frame a specific name or you can use one of the targets shown in Table 12.1.

These targets are based on Navigator frames, which were introduced in Navigator 2.0. Figure 12.1 illustrates Navigator frames. You can steer the results of an NPN_GetURL() request to any of a number of windows by using the window parameter.

Figure 12.1: Navigator frames form NPN_GetURL ( )'s basis for routing its results.

Table 12.1  Navigator Frames and Destinations

Target Name
Destination
_self
The same window in which the plug-in is displayed.
_current
Same as _self.
_blank
A new Navigator browser window.
_new
Same as _blank.
_parent
The window that holds the current frame.
_top
The full, top-level window that holds the current window.

Note
HTML pages with Navigator frames have the following general syntax:
<HTML>
<HEAD>
</HEAD>
<FRAMESET>
</FRAMESET>
</HTML>
<FRAMESET> tags can be nested. If you direct NPN_GetURL() results to _self or _current, the results are sent to the frame that holds the plug-in.
If you direct the results to _parent, the results are sent to the frame that is one level of <FRAMESET> tags "higher" in the hierarchy than the current window. If you send the results to _top, the new contents replace all current frames and fill the top-level window.

ON THE WEB
http://home.netscape.com/assist/net_sites/frames.html  Visit this site for the latest information on Navigator frames.

The Essence of Hyperlinking

When you first learned about the Web, you discovered that when you moved your cursor over certain text or graphics, the name of a new destination-an URL-appeared in the status bar. In a graphical browser like Netscape, you can click this hyperlink and the contents of the new page replace the old.

One of the reasons Netscape allows plug-ins is to keep the user interface as consistent as possible. Like Navigator, your plug-in can provide hyperlinks. To be consistent with Navigator, you should show the destination in the status bar and be prepared to replace the contents of your plug-in window with the contents from the new URL.

This section describes loading new URLs via hyperlinks. In the "Using NPN_Status()" section later in this chapter, you learn how to show the destination URL and a progress report in the status bar.

Loading an URL

Some data is inherently linked. Many documents are hierarchical and lend them themselves to a linked presentation. If you are using Windows 95, Windows NT 3.51, or Win32s 1.3, you have access to the tree view, one of the common controls Microsoft introduced. You'll recognize the tree view from file list displayed by the Windows 95 Explorer. The easiest way to show a tree view is with the Microsoft Foundation Classes (MFCs). You can use the tree view to present hierarchical data; the leaves provide links to HTML pages or pieces of data that could be interpreted by a plug-in.

In an intranet application, the tree might represent all material that is due in a warehouse. Each branch of the tree might represent a purchase order (PO). Each leaf under a PO might represent an item on the PO.

The MFC library has built-in messages generated by the tree view. You can arrange to get messages when an item is selected or when a branch is expanded or collapsed. There is no built-in message to tell you when the cursor is over an item.

To have your tree view behave as much like Navigator as possible, consider tracking mouse movement while the cursor is over the tree view. Whenever the mouse moves, call CTreeCtrl::HitTest() and pass in the cursor point. CTreeCtrl::HitTest() returns the HTREEITEM associated with that point or NULL if the point is not over a tree item.

After you know where the cursor is, you can determine if the tree item is a link, and you can update the status bar to show what will happen if the link is followed.

To the Current Window  You can use NPN_GetURL()'s window parameter to direct the output to the plug-in itself (window = NULL), to the current window (such as window = "_self"), or to another window (such as window = "_new" or window = "_parent"). Note that if you direct the contents to the current window, your current plug-in instance is deleted.

Using this design, you can show a tree view of the data. When the user moves the cursor over a hyperlink item, the item can be highlighted and the status bar can be updated to show the destination.

If the user clicks an item, however, this design gets messy. Does the user expect to follow the link (as in Navigator) or to open a branch (as in the MFC tree view)?

Tip
Whether you use the tree view or another control class, review the MFC documentation to see which built-in functions are available. With tree view, for example, use the CtreeCtrl class's HitTest() member to determine whether a linked item has the mouse cursor over it. Then call the CTreeCtrl class's Select() member to set the highlight.
Using the predefined methods not only saves you time, it also makes your interface more likely to have a look and feel familiar to users. This fact means the users are less likely to be confused.

You can work around this problem by having the user right-click the mouse to follow the link, or you could even have the right mouse button show a menu with other operations (see Figure 12.2). A better design, however, is to keep the tree view on the page and open a new window with the selected contents.

Figure 12.2: Trap WM_RBUTTONDOWN and use it to start a handler that loads and tracks a pop-up menu.

Tip
Macintoshes usually have only one mouse button. Rather than looking for a right-button click, look for a mouse click in an empty part of the window. Therefore, if the user presses the mouse button in a part of the window that ordinarily triggers no action, you might show a menu that shows the various operations available.

To Another Target-Navigator Style  Some designers like to keep two frames open-one with navigational information (such as a list or a tree view) and one with the contents associated with the selected item.

This design allows you to make the interface like Navigator, which is always a plus if you can keep the user from being confused. In this design, moving the mouse over a hyperlink item in the tree view highlights the item. Set the status bar to show the URL or other description of the destination.

If the user clicks the plus-minus button to the left of the item, the tree view expands or collapses the branches under the item. If the user double-clicks only the item, the tree view also expands or collapses the hierarchy.

If, however, the user clicks the selection, you can trap the TVN_SELCHANGED message, which is generated every time a new item is selected. Then you can use this message to retrieve the contents from the Internet and display them in the adjacent frame.

Sending to the Current Instance

If you set window to NULL, the results from NPN_GetURL() are sent via NPP_NewStream() and its follow-on methods. Remember that for the best performance, avoid stream types that need the data to be saved in a file; use NP_NORMAL to bring the data in as a stream with NPP_WriteReady() and NPP_Write().

Tip
When you have more than one stream open to your plug-in, you must pay attention to the NPStream() pointer that Netscape passes to your plug-in. Use this pointer to distinguish between the various streams and route each stream to the proper buffer.

Migrating Existing Applications to the Web

Many existing applications can easily be turned into plug-ins. Here are some rules of thumb to facilitate this transformation while maintaining most of the Navigator look and feel.

Subclass Your Application's Window to the Plug-In HWND  A typical application opens a window, puts up a set of menus, and then waits for user interaction. With your plug-in, Navigator already gave you a window.

On a Macintosh, Navigator gives you a portion of a window. Additionally, you get all the events that occur in the Window. You can handle the events that make sense and return the rest.

In Windows, each window is accessed through an HWND and has an attached procedure (called a WindowsProc in Windows). You can make a new window (without displaying it) to set up a new WindowsProc. Then, by subclassing to the Navigator-supplied window (through the HWND), the whole message sent to the Navigator window is passed to your WindowsProc.

If you don't have MFC or prefer not to use it, use the callback function method of subclassing shown in Chapter 11.

Move the Application's Menu to a Pop-Up Menu  Your plug-in will not have access to the application menus. Many plug-in designers capture the right-mouse button and use this mouse-down event or message to show a pop-up menu like the one shown in Figure 12.3.

Figure 12.3: Use BrowserWatch to estimate which versions of Navigator are currently popular on the Web.

Typical items to put on such a menu include the following:

Don't Do Everything in a Single Plug-In  A typical application may have a number of windows, dialog boxes, controls, and menus. Because plug-ins already have the context of Navigator, each plug-in in a network-centric application should be simpler than a full-blown application.

If you want to use Navigator to "Web-enable" a large application, break down the application by type of data. Associate a MIME media type to each data type and write a plug-in to handle each MIME media type.

Now use HTML and frames to build the "outside" of the user interface and use NPN_GetURL() to connect the components.

Thinking About Asynchronous Processing

NPN_GetURL() goes about its work asynchronously. When the plug-in calls Navigator, Navigator returns almost immediately from the function and proceeds on its own to process the request.

During this time, your plug-in doesn't know whether the request is still being processed or whether it failed. The more you depend on NPN_GetURL() to provide links between the components of your Web-enabled application, the more you need to know about the status of the NPN_GetURL() request.

Netscape provides the NPN_GetURLNotify() function to meet this need. The following line shows the syntax for NPN_GetURLNotify():

NPError NP_GetURLNotify(NPP instance, const char* url, 
const char* target, void* notifyData)

This syntax is almost identical to NPN_GetURL(). The only difference is the addition of a private parameter, notifyData.

When NPN_GetURLNotify() completes, Navigator calls the plug-in's NPP_URLNotify() method and passes back the notifyData parameter. The plug-in uses this parameter to match up requests with notifications.

The specification for NPP_URLNotify() is as follows:

void NPP_URLNotify(NPP instance, const char* url, 
NPReason reason, void* notifyData);

Note the reason parameter. Navigator returns one of the following three reason codes on completion of NPN_GetURLNotify():

Note
The NPP_URLNotify() reason codes are defined in the Netscape file npapi.h. If Netscape decides to add additional reason codes, you can find them in that file with the latest version of the plug-in SDK.

Determining the NPN_Version()

Since its earliest days, Netscape set a pace that few software developers can maintain; they released a new major release every six months or so. Netscape's goal is to maintain compati-bility between plug-in versions so that your Navigator 3.0 plug-in will run correctly on Navigator 6.0.

As Netscape adds features to their product, however, you may want to add corresponding capabilities to your plug-in. At some point, you will find your plug-in running and need to determine the version of Navigator "beneath" the plug-in.

You also may want to determine the version of the plug-in SDK with which the plug-in was compiled. With a switch statement based on the SDK version number, you can write code that uses advanced features in the SDK when they are available and writes around them otherwise.

Tip
The SDK major and minor version numbers are defined as preprocessor constants in the Netscape npapi.h file. Their names are NP_VERSION_MAJOR and NP_VERSION_MINOR. You can use this information with the preprocessor's #ifdef directive to conditionally compile the code for advanced capabilities.
Always make sure that an advanced API is present in both the SDK and Navigator before calling it. A compile-time error occurs if the prototype isn't available in the SDK, but your user gets a runtime error if the API isn't available in Navigator.

Using the Plug-In API Version

Call NPN_Version() to get the major and minor version numbers of both the plug-in SDK and Navigator. The syntax is as follows:

void NPN_Version(int *plugin_major, int *plugin_minor, 
int *netscape_major, int *netscape_minor);

Using NPN_UserAgent()

Although the Netscape plug-in is specific to Navigator, other vendors have shown an interest in duplicating Navigator's features as closely as possible. It's likely that, at some point, another browser vendor may support Netscape plug-ins.

The syntax for NPN_UserAgent is as follows:

const char* NPN_UserAgent(NPP instance);

Note
At least one version of the plug-in SDK documentation incorrectly shows the specification for NPN_Status() rather than NPN_UserAgent() (and has the NPN_Status prototype wrong!). The syntax shown here for NPN_UserAgent() is based on the prototype in npapi.h.

Caution
Some releases of the 16-bit Windows Navigator crash with a General Protection Fault (GPF) when NPN_UserAgent() is called. If you use NPN_UserAgent(), test your plug-in with the versions of Navigator you expect your users to run and use NPN_Version() to provide alternative code where necessary.

A wealth of information is available in the browser's identifying string. For example, in Windows you can find out whether you are running on Windows 95, Windows NT, or Win 32s.

On the Macintosh, you can learn if the plug-in is executing on a 680X0 machine or on a PowerPC. If your plug-in runs under UNIX, you can find out which platform and which version of UNIX.

Caution
NPN_Version() and NPN_UserAgent() allow you to tailor your plug-in's behavior. Use this capability as little as possible. When you start providing custom features for every platform and version, the plug-in begins to consume maintenance time. If you run out of time and fall behind, your plug-in gives the impression that it's out-of-date.
When possible, dynamically link to existing code and let the platform help you keep up-to-date. If you dynamically link MFC on a Windows machine, for example, you'll always get the latest version of a control. As long as the calling interface stays the same, you get the look and feel of the new version without changing a line of code.

Accessing the NPN_PostURL()

The Hypertext Transport Protocol (HTTP), the protocol of the World Wide Web, allows several request methods. The two most-commonly used methods in servers are GET and POST.

You can use NPN_GetURL() to make a request to any of several different kinds of server. If your URL begins with http, Navigator interprets the request as an http request and uses the GET method.

You can use an HTTP URL with NPN_PostURL() to send data via the POST method. The following code shows the syntax for NPN_PostURL():

NPError NPN_PostURL(NPP instance, const char *url, const char *window, 
uint32 len, const char *buf, NPBool file);

Here, url contains the URL that the POST request should be sent to, and window points to the destination for the output-just as it did when used with NPN_GetURL().

Sending a File of Data

You can use NPN_PostURL() to send a buffer of data or a file. To send a file, set the Boolean parameter file to true and put the path to the file into buf. Set len to strlen(buf) + 1. (Remember to leave room for the terminating NULL.) You may prepend file:// to the path.

Note
If you use NPN_PostURL() to send a file, specify the path with forward slashes, even if you are sending from a Windows machine.

Tip
NPN_PostURL() expects files to conform to the UNIX line-break style-each line should be terminated with a newline (\n) character. On Windows machines, the usual line termination is a carriage return-linefeed (\r\n), so you need to strip out the return (\r) before transmitting the file. Note that "newline" and "linefeed" are two names for exactly the same character.
On the Macintosh, lines usually are terminated only with a carriage return. You should read through the file and substitute linefeeds for returns before sending the file.
The exception to these rules is FTP. If you are using an URL that begins with ftp://, leave the line termination set to whatever makes sense for this transfer, which is often the native style of the machine.

Sending a Buffer

You can post a buffer of data by setting file to false. Set len to the length of the buffer and point buf to the data itself.

Some protocols, like HTTP, need an empty line between the headers and the content. If you want to leave the headers set at default values, you must still send a blank line before the content.

NPN_PostURL() doesn't accept blank lines between the headers and the content if the data is coming from a buffer. You must either send the data from a file or use NPN_PostURLNotify().

Special Considerations for Older Versions of Navigator

Because Netscape releases new versions of Navigator so often and seems always to have two or three beta releases in common use, your plug-in may be loaded into browsers that are newer or older than the ones with which you test. Be sure to check the release notes that come with your version of the SDK to find out about any known incompatibilities with older versions of Navigator.

When Netscape releases beta versions of the Navigator Plug-Ins SDK, they typically put a list of known problems on their Web site. Check this list and test with as many versions of Navigator as possible. Then use NPN_Version() and NPN_UserAgent() to steer your user around unreliable combinations.

Caution
Calling NPN_PostURL() with a non-HTTP URL can cause the Windows version of Navigator to crash with a GPF. Although the Netscape documentation says that NPN_PostURL() works with mailto, news, and ftp URLs (in addition to http), these combinations are known to fail on older versions of Navigator, such as some releases of 2.0.
If you allow the user to specify the URL, check to ensure that it begins with http before submitting the request to Navigator. If it doesn't, check the user's version of Navigator and warn the user before sending NPN_PostURL().

If you are building plug-ins for an intranet, you have a good idea of which browsers your users have. If you are writing plug-ins for an existing site, use the server logs to find out how people access your site.

If you're building a new site, you can get an idea of which browsers and which versions of Navigator are in common use by visiting the listings of BrowserWatch at http://browserwatch.iworld.com/stats/stats.html. BrowserWatch maintains statistics on the relative popularity of each browser.

Figure 12.3 shows typical figures for Mozilla (in this case, 75.7%), the internal name for Navigator . This topic also is discussed regularly in the mailing lists of the HTML Writers Guild (http://www.hwg.org/) and on the comp.infosystems.www.* newsgroups.

Tip
For more information about how to analyze your server logs, see Chapter 42, "Processing Logs and Analyzing Site Use," of Webmaster Expert Solutions (Que, 1996).

Setting Up a Distributed System

You can set up a powerful distributed application by combining one or more plug-ins with static HTML pages and scripts that produce dynamic content. Figure 12.4 revisits the system with multiple MIME media types originally introduced in Chapter 9, "Understanding Plug-Ins."

Figure 12.4: By using Navigator as a starting point, the programmer can set up a distributed warehouse application quickly.

The system starts when the warehouse manager opens a page that includes an embedded plug-in listing all the receipts that are due (sorted by date). On the newer Windows platforms, the plug-in can display this data in a tree view so that the items on a given purchase order (PO) can be shown in a hierarchical fashion.

Design Tradeoff: How to Transmit the File  One tradeoff the designer of this application must make is whether to download all the expected receipts at once or to send them on demand. In many warehouses this problem is compounded because material may arrive earlier or later than its expected arrival date, and POs with backordered material may remain open long after their expected arrival date has passed.

If the warehouse has, for example, 1,000 POs open and due within the next week and an average of 20 items on each PO, a request for all of these POs would take about 195K (assuming each PO and each line item have a 10-character identifier). On the Internet, a file of this size may take too long to download because many users have a relatively slow dial-up connection.

On a corporate intranet, however, a file of 195K may transmit in just a second or two, so it's probably all right to send the whole file to the client. Figure 12.5 illustrates an entire slice of the database being downloaded to the client.

Figure 12.5: In this design, an entire slice of the database is downloaded to the client in the warehouse. For many distributed applications, an entire file can be downloaded through an intranet in just a second or two.

Design Tradeoff: File Updates From the Server  Another consideration for the designer is update frequency. In many organizations, buyers may make changes to the file of expected receipts right up to the time the truck arrives.

If a warehouse manager pulls down the file from the server at 6 AM, the file may be missing important updates when a truck pulls in at 9 AM. To solve this problem the designer may want to set a timer in the plug-in.

Every few minutes, as long as the plug-in is in existence, the plug-in re-requests its data. One approach is to specify _self as the target so that the old plug-in is replaced with a new copy of itself. Use the NPSavedData parameter of NPP_Destroy() to keep a bookmark of the warehouse manager's current PO. When the data is restored, position the window so that the bookmark is again visible. Figure 12.6 illustrates the refresh in action.

Figure 12.6: Before the file is refreshed, the Warehouse Manager has some items expanded, others collapsed, and one selected. After the new copy is downloaded, the plug-in's NPP_URLNotify( ) method sets each item back to its original state.

If the plug-in allows the user to change the local copy of the expected receipts file, set the window parameter of NPN_GetURL() to NULL and read the data back into the plug-in. Use an associative container such as those available in the C++ standard template library (STL) or one of the map classes from MFC to match records from the old file with records from the new one. Figure 12.7 shows how this technique works.

Figure 12.7: In this design, the plug-in handles the data and deals explicitly with items that are added, deleted, or changed.

Design Tradeoff: Downloading Additional Files  As the warehouse manager reviews the expected receipts, he or she may need additional information about the items that are due. The tree view allows the manager to get summary information quickly.

For more detail, the manager can select an item (or a PO) and get a detail file. This file is handled in a different MIME media type and is sent to a different plug-in.

One design is to set up two frames: one for the tree view and one for the detail information. The second frame has a name, such as Details. This way NPN_GetURL() or, better still, NPN_GetURLNotify(), can direct the results of the request right to that frame.

If the warehouse manager needs to see a lot of information or if the screen is cramped, the designer can send the output of NPN_GetURL() to _new so that Navigator starts a new browser window. In either case, the new plug-in should be designed as a full-page plug-in.

Interacting with the User  As the warehouse workers receive material, they can check the material off of printed lists or scan bar codes by using portable terminals. Eventually, this information is returned to the desktop machine on which Navigator is running. You want to allow local editing on the PC, even if the bulk of the data is uploaded through a serial port.

Local editing can be easily handled through edit fields in a dialog box. For example, a typical function may list all the items on a PO for which the quantity received doesn't match the quantity due in. The warehouse manager can print this list and go through the receiving area looking for missing items. When found, the manager then can return to the PC, double-click the item in the list, and enter the corrected quantity in the dialog box. Figure 12.8 shows such an arrangement.

Figure 12.8: Use a dialog box to make local edits in the file.

Keeping the Data Safe  Often the user wants to edit and re-edit the data until satisfied. It makes no sense to post the data back to the server until all the edits are complete. In this case, loss of power or a crash on the client computer can result in catastrophic loss of data. The solution is to back up the data. If the data can be kept on the user's hard drive, just use file I/O or MFC serialization to save the updated version of the data.

If this approach is inconvenient (perhaps the warehouse machine is a diskless workstation), use NPN_PostURL() to periodically post the data back to the server in a temporary storage area. (You can even use FTP to save the file and then restore it if the data is lost from the PC's memory.) Figure 12.9 illustrates this part of the design.

Figure 12.9: Whether the data is on the local hard drive or on the server, be sure to save it periodically to prevent loss.

Tip
If you use this approach, make sure that you delete the temporary file after the updated file is posted to the server application.

Final Update  When the warehouse manager is satisfied that a PO is fully received, he or she can direct the plug-in to send the data back to the server. This time, rather than sending the data to a temporary file, send each record to a server application such as a CGI script or LiveWire program. The server application typically uses the data to update a database.

Tip
For more information on coupling CGI and relational databases on the server, see Chapter 18, "How to Query Databases," of Webmaster Expert Solutions (Que, 1996).

Coupling with CGI or LiveWire

There are two big advantages to developing applications by using plug-ins. First, the application can be developed quickly because much of the networking and user interface portions are supplied by Navigator. Second, the design of Web servers and browsers makes it easy to extend the application across many machines and sites.

For example, one company with a headquarters on the U.S. East Coast manages its accounts on a large mainframe. Staff in their sales districts call operators on the East Coast to check on the status of their orders. The staff have local terminals that give them access to the database on the mainframe.

The company's largest single sales district is in southern California. Due to the time difference, employees in California have to check on their orders before mid-day, before the East Coast office has closed.

This restriction, as well as the frustrations of passing detailed numerical information over the phone, causes problems for the southern California sales staff. Sales representatives might promise delivery of material that is due, only to find out that it wasn't yet received in the warehouse. When material is received, the East Coast office might be closed, causing a delay of a day or more before the material is posted as being available for sale.

The solution is to "Webify" the mainframe application and to distribute portions of the application to desktop computers in southern California. The mainframe application is integrated with a Web server, which has a firewall to protect it from intruders.

The West Coast users get a local server and a high-speed connection to the Internet. They can use forms to query their order status. The HTML forms are connected to the mainframe's relational database manager, a program that understands the Structured Query Language (SQL). A CGI script connects the database manager to the Web. When the user submits the form, the CGI script translates the request into SQL and sends it to the database manager. The same script then takes the results of the query from the database manager and formats them in HTML to build a reply page.

Tip
For more information about connecting an HTML form to a database manager through a CGI script, see Chapter 18, "How to Query Databases," in Webmaster Expert Solutions (Que, 1996).
If you use a Netscape Web server, you can use server-side JavaScript to access a database manager. Server-side JavaScript has access to a library of SQL calls, so those scripts tend to be simpler and easier to implement than CGI scripts. For more information on using server-side JavaScript to access a database, see Chapter 12, "Using the Database," in Special Edition Using Netscape LiveWire (Que, 1996).

As material is received in the warehouse, this information is posted back to the mainframe. An account representative can find out instantly that the material he or she is looking for has arrived in the warehouse and is available for sale.

NPN_RequestRead()

In Chapter 11, you learned that the programmer has the option of setting the stream's stype parameter to NP_SEEK. This change takes the stream out of server push mode and sets it to client pull. The plug-in pulls data from the stream using the NPN_RequestRead() method.

Understanding Seekable Streams

The first Web servers used the GET and POST methods already described in this chapter. If a client sent a GET request for a file, the server responded with the entire file. Some newer servers use a byte-range request: The client can request certain parts of the file, and only these parts of the file in the specified ranges are sent.

If a server uses byte-range requests, Navigator sets seekable to true in the NPP_NewStream() call. Navigator also sets seekable to true if the source of the data is a local file. If seekable is true, you can set the stream's stype to NP_SEEK with no penalty.

If seekable isn't true and you set the stream to NP_SEEK, Navigator copies the entire stream to the local hard drive. The plug-in can then do seeks on the local file. This operation, of course, can be time-consuming and undoes most of the benefits offered by seekable streams.

Seekable streams make the most sense when a well-defined structure to the data exists. Suppose that the data consists of 1,000,000 records of 80 characters each. Even over a high-speed connection, such as a T1, the file needs between 10 and 15 minutes to transfer.

Tip
Even if you are designing a plug-in for a high-speed intranet, remember that most companies have a few employees who access the intranet from dial-up connections. This category may include outside sales staff, field maintenance staff, or traveling executives. If these employees access your plug-in, you need to ensure its performance is acceptable over a 28.8Kbps or even a 14.4Kbps connection.

If the data is stored as a binary tree, the average number of reads to find a given record is as follows:

log2 n

   2

Here, n is the number of records in the file. With 1,000,000 records, the average number of reads is 10 and the worst-case number of reads is 20.

The overhead of HTTP needs around 0.5 to 0.75 seconds to set up a connection, so 10 independent reads need between 5 and 8 seconds of overhead (and a negligible amount of actual transfer time).

Reducing the time to find the data from several minutes to a few seconds is dramatic, but even better methods are available. The server can be programmed to keep an index of the data, giving an approximate starting location for each record. This technique, known as the indexed sequential access method (ISAM), is illustrated in Figure 12.10.

Figure 12.10: The indexed sequential access method provides an index to blocks of sequentially stored data.

Suppose that the key field needs 32 bits, which gives enough room for over 4 billion records, and the offset needs another 32 bits, which gives room for a 4G file. Then suppose that each cluster consists of 1,000 records.

There are 1,000 clusters (1,000,000÷1,000). Each cluster takes 64 bits in the index: 32 bits for the key and 32 bits for the offset. 64 bits can be packed into 8 characters, so the index is 8,000 bytes long (8¥1,000).

Figure 12.11 illustrates this index. This file can be downloaded over a high-speed connection in less than a second, even considering overhead. Even a dial-up user can retrieve the index in 5 to 8 seconds.

Figure 12.11: Pack the index as tight as possible to minimize download time.

When the plug-in has the index, it can keep it for multiple searches of the database. Because the index is small and is stored in memory, searches are very fast. When the byte offset of the correct cluster is identified, the plug-in can download the 80,000-byte cluster in about a second on a high-speed link or under 30 seconds on a 28.8Kbps dial-up link.

How to Use NPN_RequestRead()

The syntax for NPN_RequestRead() is as follows:

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

where NPByteRange is a structure with the following layout:

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

Note that NPByteRange includes a pointer to the "next" NPByteRange, so you can string these structures together to request multiple byte ranges in one request.

Tip
Each request needs a few hundred milliseconds of overhead. If you can design your application so that it requests more than one byte range in a single request, your plug-in incurs less overhead.
Occasionally, you get a big performance boost by taking advantage of the way people work. For example, in the warehouse application, if the warehouse manager requests one item from PO 12345, he or she is likely to request other items as well. You may want to retrieve all the items associated with a purchase order at once and store them locally in case the manager requests them.

Using NPN_NewStream()

So far, all the examples have involved processing additional streams from the Internet. Your plug-in also can generate a stream that is sent to Navigator. To open a stream to Navigator, use NPN_NewStream(). NPN_NewStream()'s specification is as follows:

NPError NPN_NewStream(NPP instance, NPMIMEType type, 
const char *target, NPStream **stream);

Here, each of these parameters has the same meaning it does in the other plug-in APIs. The target parameter is identical to the window parameter in NPN_GetURL() and NPN_PostURL().

Streaming into Navigator

The NPMIMEType type is a typedef for char*; you specify the MIME type, followed by a forward slash, followed by the subtype. For example, an HTML page has the text/html type.

Because you can specify the MIME media type with the type parameter, you can force Navigator to start additional copies of your plug-in, a different plug-in, or a helper application. If the type parameter is for a type that is native to Navigator, then, of course, Navigator handles the actual stream. For example, you could use NPN_NewStream() to write an HTML page on-the-fly and send it back to Navigator.

Caution
Think through the implications of your choice of target. Just as we saw with NPN_GetURL(), you can destroy your current plug-in instance by specifying _self or _current as the target. There's nothing wrong with doing this, as long as it is what you intended.

When NPN_NewStream() returns, the parameter stream holds a handle to an NPStream. You use this stream in your calls to NPN_Write().

NPN_Write()

Write to your new stream using NPN_Write(), which is specified by the following:

int32 NPN_Write(NPP instance, NPStream *stream, 
int32 offset, int32 len, void *buf);

There is no NPN_WriteReady() function that corresponds to NPP_WriteReady(). Instead, send as much data as you have ready to Navigator with NPN_Write(). If Navigator cannot handle all the data, it returns a number that is less than len. Just move the buf pointer to the point in the buffer where Navigator stopped, update len and offset, and call NPN_Write() again. When the return value equals len, Navigator has consumed all of your data.

If Navigator encounters an error while processing your stream, it returns a value less than zero. If this occurs, call NPN_DestroyStream() to free up the buffers associated with the stream. Of course, you should also call NPN_DestroyStream() when Navigator is finished with your data.

NPN_DestroyStream()

Your plug-in should call NPN_DestroyStream() when it is done with a stream it allocated by using NPN_NewStream(). The following line shows the syntax for NPN_DestroyStream():

NPError NPN_DestroyStream(NPP instance, NPStream *stream, NPError reason);

The reason parameter takes the same values as the reason parameter does in NPP_URLNotify(): NPPRES_NETWORK_ERR, NPPRES_USER_BREAK, and NPPRES_DONE. You usually return NPPRES_DONE.

The usual sequence for writing back to Netscape resembles the following code:

NPStream stream;
char* theData = "<HTML><B>This is a message from my plug-in!</B></HTML>";
int32 theLength = strlen(myData) + 1;
err = NPN_NewStream(instance, "text/html", "_blank", &stream);
int32 theOffset = 0;
do
{
  err = NPN_Write(instance, stream, theOffset, theLength, theData);
  theOffset = err;
  theLength -= err;
  theData += err;
} while (err >= 0 && err != theLength);
if (err >= 0)
  err = NPN_DestroyStream(instance, stream, NPRES_DONE);
else
  err = NPN_DestroyStream(instance, stream, NPRES_USER_BREAK);

If your plug-in does anything between the call to NPN_NewStream() and the call to NPN_DestroyStream() that raises an exception, be sure to include a call to NPN_DestroyStream() in the exception handler and set the reason code to NPRES_NETWORK_ERR. Failure to destroy the stream leads to a memory leak.

Using NPN_Status()

In the "Using NPN_GetURL()" section in this chapter, the NPN_Status() method was mentioned, which allows your plug-in to duplicate the Netscape look and feel by putting up its own progress indicator. This section shows how to use NPN_Status().

The specification for NPN_Status() is as follows:

void NPN_Status((NPP instance, const char *message);

You can use NPN_Status() to write any message into the Navigator status line. Remember that experienced Navigator users have certain expectations about what will appear in the status line. For example, when you drag your cursor over a hyperlink, the URL of the link appears in the status line. During a download, the percent complete, download rate, and time remaining are displayed. Your plug-in will be received more favorably if you stay close to the look and feel of Navigator.

A Progress Indicator

One good use for the status line is to mimic the progress reports that Navigator gives while it is downloading a file. Recall that Navigator calls your NPP_Write() method and passes a stream parameter.

NPStream includes an end field that gives the location of the end of the stream, in bytes. If Navigator cannot determine the length of the stream, it puts a zero into this field. If the field is nonzero, you can use NPN_Status() to report your plug-in's progress in reading the stream:

int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, 
int32 len, void *buf);
{
...
  CString theOutput;
  if (0 != stream->end)
  {
    int thePercentComplete = (int) ((float) 100 * 
      (stream->end - offset) / stream->end);
    theOutput.Format("%d%% of %dK received",
      thePercentComplete,
      stream->end / 1024);
  }
  else
    theOutput.Format("%d bytes received",
      stream->end - offset);

  NPN_Status (instance, theOutput.GetBuffer(theOutput.GetLength()));
}

Reporting Time Remaining

Netscape itself reports more than just the percentage complete. It tries to estimate the download rate and the time remaining.

ON THE WEB
Listing 12.1 shows how to provide this additional information. Note that this example uses a Windows worker thread.


Listing 12.1  -Mimic the Navigator Progress Indicator

//
// ShowProgress
//
// Note: Be sure to add a DWORD member to this class called dwLastTickCount.
// Initialize that member to zero. Reset it to zero in NPP_DestroyStream().
void CWorker::ShowProgress (NPP instance, 
                            uint32 FileSize, 
                            int32 Offset, 
                            int32 length)
{
  DWORD dwCurrentTick = GetTickCount();

  if (0 != this->dwLastTickCount)
  {
    float BytesPerSecond = (float) length / 
       ((float) (dwCurrentTick - this->dwLastTickCount) / 
       (float) (dwCurrentTick - this->dwLastTickCount) / (float) 1000.0);
    int SecondsRemaining = (int) ((FileSize - Offset) / BytesPerSecond);
    int MinutesRemaining = SecondsRemaining / 60;
    SecondsRemaining -= MinutesRemaining * 60;
    CString OutPut;
    int PercentComplete;

    // Sometimes FileSize is zero. Handle that case properly.
    if (0 != FileSize)
    {
      PercentComplete = (int)((float)(100.0*Offset) / (float)FileSize);

      OutPut.Format ("%d%% of %dK (at%8.0f Kbps, %2d:%02d remaining)",
        PercentComplete,
        FileSize / 1024,
        BytesPerSecond / 1024.0,
        MinutesRemaining,
        SecondsRemaining);
    }
    else
      OutPut.Format ("%8.0f kbps)",
        BytesPerSecond / 1024);
    NPN_Status (instance, OutPut.GetBuffer(OutPut.GetLength()));
  }

  this->dwLastTickCount = dwCurrentTick;
}

Call ShowProgress by adding a line in NPP_Write() like the following:

data->pWorker->ShowProgress(instance, stream->end, offset, len);

Note that this version of the status bar shows kilobytes per second, just like Navigator. Some designers prefer to see the speed in bits per second to allow the user to better compare his or her modem's rated speed with the transfer rate. If you want to change the status line to show bits per second, find the two lines that read

BytesPerSecond / 1024);

and change them to

BytesPerSecond * 8);

Caution
In any version of a progress indicator that you build, be sure you handle the case in which FileSize is zero. If Navigator cannot determine the file size (as happens, for example, when the stream is coming from a CGI script) it sets stream->end to zero. If you attempt to compute
PercentComplete = (int)((float)(100.0*Offset) / (float)FileSize);
when FileSize is zero, you'll get a divide-by-zero error, and your plug-in will cause Navigator to exit unexpectedly.

Understanding NPN_MemAlloc() and NPN_MemFree()

Navigator provides NPN_MemAlloc() as a substitute for malloc or the built-in new. NPN_MemAlloc() allocates memory inside Navigator.

By allowing Navigator to manage the memory, your plug-in is more likely to get the memory it requests because Navigator can free up memory it no longer needs. Navigator also preserves blocks of memory in NPSavedData as much as possible so that two instances of your plug-in for the same URL can talk to each other.

In C++, the easiest way to use NPN_MemAlloc() and its corresponding method, NPN_MemFree(), is to put these methods into the new and delete operators. You can make one top-level class with plug-in-friendly new and delete and derive all your other classes from that top-level class.

Chapter 11's section "Starting the Plug-In" shows how to perform this use. That chapter also shows how to use NPN_MemFlush() to free up as much memory as possible on the Macintosh.

In the SDK…

Much of the work of a plug-in is done by Navigator. You use Navigator to read data from the Internet, post data, and put up new Navigator windows with content generated by your plug-in. Navigator also can display status in the status line on behalf of your plug-in and report various version and user agent information.

The LiveWire/Plug-Ins SDK includes a set of HTML pages that describes each of the NPN methods. Be sure to load those pages and use them to get the latest information on these important functions.

ON THE WEB
http://home.netscape.com/comprod/development_partners/plugin_api/index.html  Start at this page to download a copy of the LiveWire/Plug-Ins SDK for your platform. For a full set of information about this technology, you should also visit http://home.netscape.com/eng/mozilla/3.0/handbook/plugins/index.html and follow the links from that page.