reptile7's JavaScript blog
Tuesday, January 05, 2010
 
Quest for Flash
Blog Entry #167

We conclude our tour of HTML Goodies' "JavaScript Browser Test Scripts" series of tutorials with a look at "Test Visitors for the Flash Plug-In", which offers a script that first tests if the user's browser has a Flash plug-in and then sends the user to either a FLASHPAGE.html page containing Flash content or a NON-FLASHPAGE.html page without such content. The "Test Visitors for the Flash Plug-In" script is a strange and dysfunctional script for which no deconstruction is provided - as usual, it is left to your humble narrator to clean up the mess - but it's also an interesting script that includes browser sniffing, some feature detection, even a bit of 'platform sniffing', and a number of other noteworthy features, and it definitely merits a full-fledged analysis.

In practice

Clicking the Test Visitors for the Flash Plug-In link on the "JavaScript Browser Test Scripts" page will initially take you to a "Test Visitors for the Flash Plug-In" page whose source contains the "Test Visitors for the Flash Plug-In" script. If your browser has a Flash plug-in, you would in theory be redirected to a "Flash Test Page" page that presents and discusses the "Test Visitors for the Flash Plug-In" script. If your browser doesn't have a Flash plug-in, you would in theory be redirected to an "Oooops..." page bearing a message that in part reads: Unless something has gone wrong, you do not have the Flash [browser plug-in] installed on your system.

Excepting MSIE 5.2.3, the OS X GUI browsers on my computer share a common Flash plug-in located at /Library/Internet Plug-Ins/Flash Player.plugin. But regardless of browser, when I go to the "Test Visitors for the Flash Plug-In" page, I am not sent to the "Flash Test Page" page; instead, once the "Test Visitors for the Flash Plug-In" page has loaded, I am after eight seconds sent to the "Oooops..." page via a "meta refresh" (more on this later).

I control-clicked the Test Visitors for the Flash Plug-In link on the "JavaScript Browser Test Scripts" page, saved the 3470911 file to my desktop, and then ran through the 3470911 source. Here's what "has gone wrong": the square bracket characters in the
ok = (plugins && plugins[plgIn]);
line of script code have been 'escaped' to numeric character references, i.e.,
ok = (plugins && plugins[plgIn]);
which effectively kills the script. You may recall we encountered this same problem with some of the Script Tips scripts, e.g., check the source of Script Tip #31, whose "Here are the goods:" date demo is accordingly 'missing'. (I don't think Joe is responsible for the escaping-square-brackets error(s), as pre-2005 versions of the "Test Visitors for the Flash Plug-In" page correctly direct me to the corresponding "Flash Test Page" pages.)

Respectively converting the [ and ] references to [ and ] characters gives a functioning but still problematic script that does work, sort of, for non-MSIE browsers but gives an accidental or false positive for MSIE, as explained below. Before we get our analysis under way, let me note that the "Oooops..." page's get the plugin here link is still live and will take you to an Adobe Downloads page at which you can install/upgrade a Flash plug-in; you can test your installation here.

For your convenience, the "Test Visitors for the Flash Plug-In" script is reproduced in the div below:

<html>
<head>

<script type="text/javascript">
<!--
function MM_checkPlugin(plgIn, theURL, altURL, autoGo) 
{ 

// v3.0

var ok = false; 
document.MM_returnValue = false;
with (navigator) 
if (appName.indexOf("Microsoft") == -1) 
ok = (plugins && plugins[plgIn]);
else if (appVersion.indexOf("3.1") == -1) 

{ 

// Not Netscape nor Win3.1

if (plgIn.indexOf("Flash") != -1 && window.MM_flash != null) 
ok = window.MM_flash;
else if (plgIn.indexOf("Director") != -1 && window.MM_dir != null) 
ok = window.MM_dir;
else ok = autoGo; 
}

if (!ok) theURL = altURL; 
if (theURL) window.location = theURL;

}
// -->
</script>

<meta http-equiv="refresh" content="8; url=NON-FLASHPAGE.html">

</head> 

<!-- Get the onload event handler onto one line -->

<body bgcolor="#ffffff" onload="MM_checkPlugin('Shockwave Flash', 'FLASHPAGE.html', 'NON-FLASHPAGE.html', true); return document.MM_returnValue;"> 

</body>
</html>

Non-MSIE deconstruction

As intimated above, the "Test Visitors for the Flash Plug-In" script holds a non-MSIE part and an MSIE part - we'll go through the former in this section.

The action kicks off with the script's body element start-tag:

<body bgcolor="#ffffff" onload="MM_checkPlugin('Shockwave Flash', 'FLASHPAGE.html', 'NON-FLASHPAGE.html', true); return document.MM_returnValue;">

When the "Test Visitors for the Flash Plug-In" page has loaded, the script's MM_checkPlugin( ) function (MM refers to Macromedia, the original Flash developer)

function MM_checkPlugin(plgIn, theURL, altURL, autoGo) { ... }

is called and passed four values:
arguments[0]: Shockwave Flash, the name of the plug-in we are fishing for, is assigned to a plgIn variable.
arguments[1]: FLASHPAGE.html, the destination to which Flash-enabled users will be sent, is assigned to a theURL variable.
arguments[2]: NON-FLASHPAGE.html, the destination to which users without Flash support will be sent, is assigned to an altURL variable.
arguments[3]: A Boolean true is assigned to an autoGo variable, which has a cameo role in the script's MSIE part.

The MM_checkPlugin( ) function begins with a // v3.0 comment, which is probably a reference to Netscape 3.0, the first browser to implement the plugins[ ] array of the navigator object.

var ok = false;

The non-MSIE and MSIE parts of the script both use an ok variable as a Boolean marker to indicate whether the user's browser does (ok == true) or does not (ok == false) have a Flash plug-in; ok is initialized to false prior to doing any browser sniffing.

document.MM_returnValue = false;

The weirdness begins. You may have noticed that the onload MM_checkPlugin( ) function call is followed by a return document.MM_returnValue; statement. MM_returnValue is neither a standard nor a proprietary document object property but rather a custom one-off property that the script's author gave to the document object for a reason beyond my understanding. The MM_returnValue property is set to false following the ok initialization but does not appear subsequently in the MM_checkPlugin( ) function; specifically, it's not returned by MM_checkPlugin( ) to the onload function call, but then again, we don't want MM_checkPlugin( ) to return anything as its ultimate purpose is to send the user to another page (FLASHPAGE.html or NON-FLASHPAGE.html).

As for what the return document.MM_returnValue; is doing in the onload event handler, I can't figure that out either. There is no separate, default action associated with the load event that we might need to ensure via return true or suppress via return false (regarding the latter, the load event, like most HTML event types, is not cancelable anyway). Suffice it to say that the MM_returnValue code serves no purpose and can in fact be thrown out without affecting the script in any way.

FYI: Custom one-off JavaScript properties are briefly addressed in the "Objects and Properties" section of the Core JavaScript 1.5 Guide. Such properties are defined for a custom object in "Objects and Properties" although you can legitimately tack them onto standard (client-side/core) objects as well - this is the reason why assignments to miscapitalized/misspelled properties, e.g., navigator.useragent = my_new_identity;, do not throw errors: the JavaScript engine thinks you're creating a new property on the fly for a preexisting object.

Moving on, we next encounter a with statement

with (navigator)

that provides a default object, navigator in this case, for the script's following three lines. Noteworthily, those lines are not surrounded by braces, and therefore you'd think that the with statement scope would only include the subsequent line of code and nothing further than that; however, I can confirm that the with effect does propagate to the other two lines. (If you did want to delimit the with statement body with braces - probably a good idea - then the closing brace should be placed just before the script's if (!ok) theURL = altURL; statement; only surrounding the next three lines with braces will produce a syntax error.)

Right after the with line we have an if statement that sets the ok variable for non-MSIE browsers:

if (appName.indexOf("Microsoft") == -1)
ok = (plugins && plugins[plgIn]);


(If Opera is set to identify as MSIE, it will skip over the ok line and move to the subsequent else statement. History-wise, Opera is not a 'new kid in town' and goes back further than you might think - it was definitely around at the time "Test Visitors for the Flash Plug-In" was written.)

navigator.plugins returns [object PluginArray] for browsers that support it and undefined for those that don't. Similarly, for a browser that supports the navigator.plugins array, navigator.plugins["Shockwave Flash"] returns [object Plugin] if a Flash plug-in is installed and undefined if it isn't.

As described in the "Logical operators" section of the Mozilla JavaScript Guide, the && operator [r]eturns [its first operand] if it can be converted to false; otherwise, returns [its second operand]. It follows that:

(1) For a browser that supports the navigator.plugins array and has a Flash plug-in, [object PluginArray] && [object Plugin] will assign [object Plugin] to ok.

(2) For a browser that supports the navigator.plugins array but doesn't have a Flash plug-in, [object PluginArray] && undefined will assign undefined to ok.

[object PluginArray], [object Plugin], and undefined are not Boolean values, but as the && description implies, they are convertible to Boolean values. In general, object references, nonempty strings, and nonzero numbers can be converted to true; undefined, null, 0, and the empty string can be converted to false. The && operator is normally used with Boolean operands, and I don't know if the script's author expected the plugins && plugins[plgIn] operation to directly return true for a browser that has a Flash plug-in and false for one that doesn't, but a Boolean return in this case is ultimately unnecessary, as we'll see in a moment.

For a browser that doesn't support the navigator.plugins array in the first place, undefined is assigned to ok. The navigator.plugins["Shockwave Flash"] expression would ordinarily throw an error with such a browser, but in the context of the plugins && plugins[plgIn] operation this is 'short-circuited' by the undefined plugins return.

We now move past the subsequent else if { ... } block constituting the MSIE part of the script to MM_checkPlugin( )'s last two lines:

if (!ok) theURL = altURL;
if (theURL) window.location = theURL;


The ! operator (see the aforecited Mozilla "Logical Operators" section) [r]eturns false if its single operand can be converted to true; otherwise, returns true. It follows that:

(1) For non-MSIE browsers that have a Flash plug-in, the ok value, [object Plugin], is convertible to true and thus !ok returns false; these browsers skip over the theURL = altURL; assignment, and then send the user to theURL, the FLASHPAGE.html page, via the window.location = theURL; assignment.

(2) For non-MSIE browsers that don't have a Flash plug-in, the ok value, undefined, is convertible to false and thus !ok returns true; these browsers switch theURL from the FLASHPAGE.html page to the NON-FLASHPAGE.html page by setting theURL to altURL, and then send the user to theURL via the window.location = theURL; assignment.

As an if condition, theURL necessarily returns true, and therefore the if (theURL) declaration that precedes the window.location = theURL; assignment can be thrown out.

Microsoft and the plugins[ ] collection

Microsoft also defines a plugins[ ] array for the navigator object, although the Remarks section of the MSDN Library's "plugins property" page would seem to indicate that Microsoft's plugins[ ] array is not equivalent to Netscape's plugins[ ] array: The plugins collection is exposed for compatibility with other browsers. The collection is an alias for the embeds collection on the document. (Nonstandard as of this writing but having cross-browser support, the HTML embed element is detailed by Netscape here. Much like the relationship between the applet element and a Java applet, the embed element does not represent a plug-in but rather a plug-in's output.)

Nonetheless, I find that MSIE 5.x for the Mac supports not only the plugins[ ] array but also the entire Netscape plugin object API in the same way that the Netscape/Mozilla family of browsers does (in contrast, MSIE 4.5 for the Mac gives an undefined navigator.plugins return) - I don't know what happens with MSIE for Windows in this regard. In any case, our next task is to go through the script's MSIE part, and we'll do that and perhaps also discuss the meta refresh business in the following entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)