Browser plug-ins are executable components that extend the browser’s capabilities in a particular way. When the browser encounters an embedded object of a type that it is not prepared to handle (e.g., something that isn’t HTML or other Web file type), the browser might hand the content off to an appropriate plug-in. If no appropriate plug-in is installed, the user is given the option to install one (assuming the page is properly written). Plug-ins consist of executable code for displaying or otherwise processing a particular type of data. In this way, the browser is able to hand special types of data, for example multimedia files, to plug-ins for processing.
Plug-ins are persistent in the browser in the sense that once installed, they remain there unless manually removed by the user. Most browsers come with many plug-ins already installed, so you may have used them without even knowing. Plug-ins were introduced in Netscape 2 but are supported, at least HTML–syntax-wise, by most major browsers, including Opera and Internet Explorer 3 and later. However, the actual component in the case of Internet Explorer is not a plug-in but instead an ActiveX control discussed later in the chapter. Plug-ins are a Netscape-introduced technology supported by many other browsers.
Although never officially a part of any HTML specification, the <<embed>> tag is most often used to include embedded objects for Netscape and Internet Explorer. A Macromedia Flash file might be embedded as follows:
<<embed id="demo" name="demo" src="http://www.javascriptref.com/examples/ch18/flash.swf" width="318" height="252" play="true" loop="false" pluginspage="http://www.macromedia.com/go/getflashplayer" swliveconnect="true">><</embed>>
The result of loading a page with this file is shown in Figure 18-3.
The most important attributes of the <<embed>> tag are src, which gives the URL of the embedded object, and pluginspage, which indicates to the browser where the required plug-in is to be found if it is not installed in the browser. Plug-in vendors typically make available the embedding syntax, so check their site for the value of pluginspage.
Recall that applets embedded with <<object>> tags are passed initial parameters in <<param>> tags. The syntax of <<embed>> is different in that initial parameters are passed using attributes of the element itself. For instance, in the preceding example the play attribute tells the plug-in to immediately begin playing the specified file.
The <<object>> element is the newer, official way to include embedded objects of any kind in your pages. However, <<object>> is not supported in Netscape browsers prior to version 4, and <<embed>> continues to be supported by new browsers. So it is unlikely that <<object>> will completely supplant <<embed>> any time in the near future. However, <<object>> and <<embed>> are very often used together in order to maximize client compatibility. This technique is illustrated in the later ActiveX section of this chapter.
So how does the browser know what kind of data is appropriate for each plug-in? The answer lies in Multipurpose Internet Mail Extension types, or MIME types for short. MIME types are short strings of the form mediatype/subtype, where the mediatype describes the general nature of the data and the subtype describes it more specifically. For example, GIF images have type image/gif, which indicates that the data is an image and its specific format is GIF (Graphics Interchange Format). In contrast, CSS files have type text/css, which indicates that the file is composed of plain text adhering to CSS specifications. The MIME major media types are application (proprietary data format used by some application), audio, image, message, model, multipart, text, and video.
Each media type is associated with at most one handler in the browser. Common Web media such as (X)HTML, CSS, plain text, and images are handled by the browser itself. Other media, for example, MPEG video and Macromedia Flash, are associated with the appropriate plug-in (if it is installed). Keep in mind that a plug-in can handle multiple MIME types (for example, different types of video), but that each MIME type is associated with at most one plug-in. If one type were associated with more than one plug-in, the browser would have to find some way to arbitrate which component actually receives the data.
Netscape 3+, Opera 4+, and Mozilla-based browsers provide an easy way to examine the ability of the browser to handle particular MIME types. The mimeTypes[] property of the Navigator object holds an array of MimeType objects. Some interesting properties of this object are shown in Table 18-1.
Property |
Description |
---|---|
description |
String describing the type of data the MIME type is associated with |
EnabledPlugin |
Reference to the plug-in associated with this MIME type |
suffixes |
Array of strings holding the filename suffixes for files associated with this MIME type |
type |
String holding the MIME type |
The browser hands embedded objects off to plug-ins according to the data that makes up each of these objects. A good way to think about the process is that the browser looks up MIME types and filename suffixes in the mimeTypes array to find the enabledPlugin reference to the appropriate plug-in. The programmer can therefore use the mimeTypes array to check whether the browser will be able to handle a particular kind of data.
Before delving into this process, it might be insightful to see what MIME types your Netscape browser supports. The following code prints out the contents of the mimeTypes[] array.
if (navigator.mimeTypes) { document.write("<<table>><<tr>><<th>>Type<</th>>"); document.write("<<th>>Suffixes<</th>><<th>>Description<</th>><</tr>>"); for (var i=0; i<<navigator.mimeTypes.length; i++) { document.write("<<tr>><<td>>" + navigator.mimeTypes[i].type + "<</td>>"); document.write("<<td>>" + navigator.mimeTypes[i].suffixes + "<</td>>"); document.write("<<td>>" + navigator.mimeTypes[i].description + "<</td>><</tr>>"); } document.write("<</table>>"); }
Part of the result in a typical installation of Mozilla-based browsers is shown in Figure 18-4. Of course, you can also access similar information by typing about:plugins in the location bar of Netscape and Mozilla-based browsers.
To detect support for a particular data type, you first access the mimeTypes[] array by the MIME type string in which you are interested. If a MimeType object exists for the desired type, you then make sure that the plug-in is available by checking the MimeType object’s enabledPlugin property. The concept is illustrated by the following code:
if (navigator.mimeTypes && navigator.mimeTypes["video/mpeg"] && navigator.mimeTypes["video/mpeg"].enabledPlugin) document.write('<<embed src="/movies/mymovie.mpeg" width="300"' + ' height="200">><</embed>>'); else document.write('<<img src="myimage.jpg" width="300" height="200"' + 'alt="My Widget" />>');
If the user’s browser has the mimeTypes[] array and it supports MPEG video (video/mpeg) and the plug-in is enabled, an embedded MPEG video file is written to the document. If these conditions are not fulfilled, then a simple image is written to the page. Note that the pluginspage attribute was omitted for brevity because the code has already detected that an appropriate plug-in is installed.
This technique of MIME type detection is used when you care only whether a browser supports a particular kind of data. It gives you no guarantee about the particular plug-in that will handle it. To harness some of the more advanced capabilities that plug-ins provide, you often need to know if a specific vendor’s plug-in is in use. This requires a different approach.
In Netscape 3+, Opera 4+, and Mozilla-based browsers, each plug-in installed in the browser has an entry in the plugins[] array of the Navigator object. Each entry in this array is a Plugin object containing information about the specific vendor and version of the component installed. Some interesting properties of the Plugin object are listed in Table 18-2.
Property |
Description |
---|---|
Description |
String describing the nature of the plug-in. Exercise caution with this property because this string can be rather long. |
name |
String indicating the name of the plug-in. |
length |
Number indicating the number of MIME types this plug-in is currently supporting. |
Each Plugin object is an array of the MimeType objects that it supports (hence its length property). You can visualize the plugins[] and mimeTypes[] arrays as being cross-connected. Each element in plugins[] is an array containing references to one or more elements in mimeTypes[]. Each element in mimeTypes[] is an object referred to by exactly one element in plugins[], the element referred to by the MimeType’s pluginEnabled reference.
You can refer to the individual MimeType objects in a Plugin element by using double-array notation:
navigator.plugins[0][2]
This example references the third MimeType object supported by the first plug-in.
More useful is to index the plug-ins by name. For example, to write all the MIME types supported by the Flash plug-in (if it exists!), you might write
if (navigator.plugins["Shockwave Flash"]) { for (var i=0; i<<navigator.plugins["Shockwave Flash"].length; i++) document.write("Flash MimeType: " + navigator.plugins["Shockwave Flash"][i].type + "<<br />>"); }
Of course, as with all things plug-in–related, you need to read vendor documentation very carefully in order to determine the exact name of the particular plug-in in which you are interested.
To illustrate the composition of the Plugin object more clearly, the following code prints out the contents of the entire plugins[] array:
for (var i=0; i<<navigator.plugins.length; i++) { document.write("Name: " + navigator.plugins[i].name + "<<br />>"); document.write("Description: " + navigator.plugins[i].description + "<<br />>"); document.write("Supports: "); for (var j=0; j<<navigator.plugins[i].length; j++) document.write(" " + navigator.plugins[i][j].type); // the nonbreaking space included so the types are more readable document.write("<<br />><<br />>"); }
The results are shown in Figure 18-5.
One thing to be particularly conscious of is that Internet Explorer defines a faux plugins[] array as a property of Navigator. It does so in order to prevent poorly written Netscape-specific scripts from throwing errors while they probe for plug-ins. Under Internet Explorer, you have some reference to plug-in–related data through the document.embeds[] collection. However, probing for MIME types and other functions is not supported, since Explorer actually uses ActiveX controls to achieve the function of plug-ins included via an <<embed>> tag. For more information on using JavaScript with ActiveX, see the section entitled “ActiveX” later in this chapter. For now, simply consider that to rely solely on information from navigator.plugins[] without first doing some browser detection can have some odd or even disastrous consequences.
By now you might be wondering why one would want to detect whether a specific plug-in will be handling a particular MIME type. The reason is that, like Java applets, plug-ins are LiveConnect-enabled in Netscape 3+, Internet Explorer 4+, and Mozilla-based browsers. This means that plug-ins can implement a public interface through which JavaScript can interact with them. This capability is most commonly used by multimedia plug-ins to provide JavaScript with fine-grained control over how video and audio are played. For example, plug-ins often make methods available to start, stop, and rewind content as well as to control volume, quality, and size settings. The developer can then present the user with form fields that control the behavior of the plug-in through JavaScript.
This capability works in the reverse direction as well. Embedded objects can invoke JavaScript in the browser to control navigation or to manipulate the content of the page. The more advanced aspects of this technology are beyond the scope of this book, but common aspects include functions that plug-ins are programmed to invoke when a particular event occurs. Like a JavaScript event handler, the plug-in will attempt to invoke a function with a specific name at a well-defined time, for example, when the user halts playback of a multimedia file. To prevent namespace collisions with other objects in the page, these methods are typically prefixed with the name or id attribute of the <<object>> or <<embed>> of the object instance.
As with applets, there remains the issue of how the JavaScript developer knows which methods the plug-in provides and invokes. The primary source for this information is documentation from the plug-in vendor. But be warned: These interfaces are highly specific to vendor, version, and platform. When using LiveConnect capabilities, careful browser and plug-in sensing is usually required.
We now have most of the preliminary information required in order to detect and interact safely with plug-ins. There is, however, one final aspect of defensive programming to cover before jumping into the interaction itself.
Suppose you have written some custom JavaScript to harness the capabilities provided by a specific plug-in. When users visit your page without the plug-in they are prompted to install it because you have included the proper pluginspage attribute in your <<embed>>. Unfortunately, if a user visits your page without the plug-in, agrees to download and install it, and then returns to your page, your JavaScript will not detect that the browser has the required plug-in. The reason is that the plugins[] array needs to be refreshed whenever a new plug-in is installed (a browser restart will work as well).
Refreshing the plugins[] array is as simple as invoking its refresh() method. Doing so causes the browser to check for newly installed plug-ins and to reflect the changes in the plugins[] and mimeTypes[] arrays. This method takes a Boolean argument indicating whether the browser should reload any current documents containing an <<embed>>. If you supply true, the browser causes any documents (and frames) that might be able to take advantage of the new plug-in to reload. If false is passed to the method, the plugins[] array is updated, but no documents are reloaded. A typical example of the method’s use is found here:
<<em>>If you have just installed the plugin, please <<a href="javascript:navigator.plugins.refresh(true)">>reload the page with plugin support<</a>><</em>>
Of course, this should be presented only to users of Netscape, Opera, or Mozilla-based browsers where plug-ins are supported in the first place.
Nearly everything that was true of applet interaction remains true for plug-ins as well. Applets are accessed through the Document object, using the applet’s name or id attribute. Similarly, the plug-in handling data embedded in the page is accessed by the name attribute of the <<embed>> tag that includes it. As with applets, you need to be careful that you do not attempt to access embedded data before it is finished loading. The same technique of using the onload handler of the Document to set a global flag indicating load completion is often used. However, one major difference between applets and plug-ins is that as far as the DOM specification is concerned, the <<embed>> tag doesn’t exist, nor do plug-ins. Despite the fact that their use, particularly in the form of Flash, is so widespread, the specification chooses not to acknowledge their dominance and try to standardize their use.
To illustrate interaction with plug-ins, we show a simple example using a Macromedia Flash file. The first thing to note is that there are two plug-in names corresponding to Flash players capable of LiveConnect interaction. They are “Shockwave Flash” and “Shockwave Flash 2.0.” Second, consulting Macromedia’s documentation reveals that the <<embed>> tag should have its swliveconnect attribute set to true (though it does not appear to be required for this example) if you wish to use JavaScript to call into the Flash player.
You can find a list of methods supported by the Flash player at Macromedia’s Web site (for example, at http://www.macromedia.com/support/flash/publishexport/scriptingwithflash/). The methods we will use in our simple example are GotoFrame(), IsPlaying(), Play(), Rewind(), StopPlay(), TotalFrames(), and Zoom(). The following example controls a simple Flash file extolling the wonders of JavaScript.
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>Simple Flash control example (Netscape and Mozilla only)<</title>> <<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />> <<script type="text/javascript">> <<!-- var pluginReady = false; var pluginAvailable = false; if (document.all) alert("Demo for netscape only"); function detectPlugin() { // if the appropriate plugin exists and is configured // then it is ok to interact with the plugin if (navigator.plugins && ((navigator.plugins["Shockwave Flash"] && navigator.plugins["Shockwave Flash"]["application/x-shockwave-flash"]) || (navigator.plugins["Shockwave Flash 2.0"] && navigator.plugins["Shockwave Flash 2.0"]["application/x-shockwave-flash"])) ) pluginAvailable = true; } function changeFrame(i) { if (!pluginReady || !pluginAvailable) return; if (i>>=0 && i<<document.demo.TotalFrames()) // function expects an integer, not a string! document.demo.GotoFrame(parseInt(i)); } function play() { if (!pluginReady || !pluginAvailable) return; if (!document.demo.IsPlaying()) document.demo.Play(); } function stop() { if (!pluginReady || !pluginAvailable) return; if (document.demo.IsPlaying()) document.demo.StopPlay(); } function rewind() { if (!pluginReady || !pluginAvailable) return; if (document.demo.IsPlaying()) document.demo.StopPlay(); document.demo.Rewind(); } function zoom(percent) { if (!pluginReady || !pluginAvailable) return; if (percent >> 0) document.demo.Zoom(parseInt(percent)); // method expects an integer } //-->> <</script>> <</head>> <<body onload="pluginReady=true; detectPlugin();">> <<!-- Note: embed tag will not validate against -->> <<embed id="demo" name="demo" src="http://demos.javascriptref.com/jscript.swf" width="318" height="300" play="false" loop="false" pluginspage="http://www.macromedia.com/go/getflashplayer" swliveconnect="true">><</embed>> <<form name="controlform" id="controlform" action="#" method="get">> <<input type="button" value="Start" onclick="play();" />> <<input type="button" value="Stop" onclick="stop();" />> <<input type="button" value="Rewind" onclick="rewind();" />><<br />> <<input type="text" name="whichframe" id="whichframe" />> <<input type="button" value="Change Frame" onclick="changeFrame(controlform.whichframe.value);" />><<br />> <<input type="text" name="zoomvalue" id="zoomvalue" />> <<input type="button" value="Change Zoom" onclick="zoom(controlform.zoomvalue.value);" />> (greater than 100 to zoom out, less than 100 to zoom in)<<br />> <</form>> <</body>> <</html>>
The example—stopped in the middle of playback and zoomed in—is shown in Figure 18-6.
There exist far more powerful capabilities than the previous example demonstrates. One particularly useful aspect of Flash is that embedded files can issue commands using FSCommand() that can be “caught” with JavaScript by defining an appropriately named function. Whenever an embedded Flash file in a LiveConnect-enabled browser issues an FSCommand(), the Flash file crosses over into browser territory to invoke the name_doFSCommand() method if one exists. The name portion of name_doFSCommand() corresponds to the name or id of the element in which the object is defined. In the previous example, the Flash file would look for demo_doFS Command() because the file was included in an <<embed>> with name equal to “demo.” Common applications include alerting the script when the data has completed loading and keeping scripts apprised of the playback status of video or audio. As with other more advanced capabilities, details about these kinds of callback functions can be obtained from the plug-in vendors.