reptile7's JavaScript blog
Tuesday, November 14, 2006
Browser Olfaction II
Blog Entry #57

In our last episode, we looked at a simple browser detection script authored by Joe himself. We turn our attention today to a more elaborate browser sniffer crafted by "Iceman" and discussed in Script Tips #13, #14, and #15.

But just where is Iceman's script, huh? The "Click to See the Script" links in Script Tips #13 and #15 take the reader to an unrelated-to-Script-Tips-#13-15, sprawling "Browser Detect Script" originating from; in Script Tip #14, this link leads to a "404 - File not found" page. Fortunately, only a modicum of detective work was necessary to track down the correct location of the Script Tips #13-15 Script, which is:

For your edification, Iceman's browser sniffer is unfurled below in its entirety:

<script language="javascript">
/* Script by Iceman, You can use this script providing this header stays in. Copyright 1997 Iceman. The parent.location.href statements can be changed to get different redirections for different browsers. */

var r; var browser = navigator.appName;
var version = "4.0 (compatible; MSIE 4.0b1; Windows 95)";
// if statement #1
if (browser == "Microsoft Internet Explorer" && navigator.appVersion == version) {
// If Browser is MSIE 4.0b1 then go to MSIE Page.
parent.location.href = "msiepage.html"; r = 2; }

// start of the else block
else { var ua = "Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)";
// if statement #2
if (navigator.userAgent == ua) {
// If Browser is MSIE 3.02 go to MSIE Page.
parent.location.href = "msiepage.html"; r = 2; }

var version = "4.0 (compatible; MSIE 4.0b2; Windows 95)";
// if statement #3
if (browser == "Microsoft Internet Explorer" && navigator.appVersion == version) {
//If Browser is MSIE 4.0b2 then go to MSIE Page.
parent.location.href = "msiepage.html"; r = 2; }

var version = "2.01 (Win95; I)";
// if statement #4
if (navigator.appVersion == version && navigator.appCodeName == "Mozilla") {
//If Browser is Netscape 2.01 go to Netscape page.
parent.location.href = "nspage.html"; r = 1; }

var version = "2.0 (Win95; I; 16bit)";
// if statement #5
if (navigator.appVersion == version && navigator.appCodeName == "Mozilla") {
//If Browser is Netscape 2.0 16 bit go to Netscape page.
parent.location.href = "nspage.html"; r = 1; }

var version = "4.01 [en] (Win95; I)";
// if statement #6
if (navigator.appVersion == version && navigator.appCodeName == "Mozilla") {
//If Browser is Netscape 4.01 go to Netscape page.
parent.location.href = "nspage.html"; r = 1; }

var version = "3.0 (Win95; I)";
// if statement #7
if (browser == "Netscape" && navigator.appVersion == version) {
// If browser is Netscape 3.0 for Win95 go to Netscape Page.
parent.location.href = "nspage.html"; r = 1; } } // end of the else block

// if statement #8
if (browser == "Netscape") {
// If browser is Netscape and supports JavaScript go to Netscape Page.
parent.location.href = "nspage.html"; r = 1; }

// if statement #9
if (r != 1 && r != 2) {
/* If browser is not Netscape or MSIE known versions go to text-based page (caters for any other JavaScript browsers). */
parent.location.href = "textpage.html"; }
// -->

We're not going to go through this script line by line but will summarize its key features below.

The script comprises basically a series of if statements, nine in all. If statements #2-7 are enveloped in an else statement, although this is unnecessary; the else keyword, the opening else brace {, and the closing else brace } can be removed.

If statements #1-3 use the navigator.appName, navigator.appVersion, and/or navigator.userAgent properties to test for the 4.0b1, 3.02, and 4.0b2 versions of Internet Explorer, respectively. A parent.location.href = "msiepage.html" command, which can be shortened to location = "msiepage.html", sends users of these browsers to a msiepage.html Web page tailored to Internet Explorer.

If statements #4-7 use the navigator.appName, navigator.appVersion, and/or navigator.appCodeName properties to test for the 2.01, 2.0 16 bit, 4.01, and 3.0 for Win95 versions of Netscape, respectively. A parent.location.href = "nspage.html" command sends users of these browsers to a nspage.html Web page tailored to Netscape.

If statements #1-7 all target browsers for the Windows platform; consider, for example, if statement #1, which checks for a navigator.appVersion value of 4.0 (compatible; MSIE 4.0b1; Windows 95). I am tempted to say at this point that application developers who are not serious about the Macintosh platform are not serious, period, but you knew that, of course.

The script concludes with two platform-independent 'catch-alls.' If statement #8 sends users of JavaScript-supporting versions of Netscape not detected by if statements #4-7 to the nspage.html Web page. (FYI: Netscape Navigator's support for JavaScript began with Netscape 2.0.) Finally, if statement #9 sends users of all browsers not detected by if statements #1-8 to a textpage.html Web page, which is meant to be, quoting Joe, a more text-based version of the [msiepage.html/nspage.html] page.

The != and && operators, revisited

Joe briefly discusses the != comparison operator and the && logical operator in Script Tip #14; his comments are on the right track, but a bit of clarification is in order. It's true that the != operator reads "is not equal to" in the context of an if statement condition, but what != actually does, in contrast to the == equal comparison operator, is compare two operands as to their inequality, and then return a Boolean true if the operands are indeed not equal; the != operands do not have to be unequal:

var bool2 = (5 != 6); // returns true
var bool3 = (5 != (10/2)); // returns false

In turn, it's true that the && operator means "and" in a logical sense; more specifically, && usually takes two Boolean/logical operands (hence its categorization as a logical operator), determines whether they are true or false, and then returns true if both operands are true. Somewhat oddly, && can also take non-Boolean operands and return a non-Boolean value:

var bool4 = (5 && 6);
// returns 6, the second operand, if the first operand does not evaluate to false

Simplifying the Script Tips #13-15 Script

A question arises: "Is all this stuff in Iceman's script really necessary?"

One suspects that a massive simplification of the Script Tips #13-15 Script is indeed possible. (Having said this, I concede that I don't have any of the ancient versions of Internet Explorer or Netscape detected by the script on my computer. Maybe these browsers are downloadable somewhere on the Web, but even if I knew where to find them, then I still wouldn't be interested in larding my hard disk with them, even temporarily, for the purpose of checking their returns for the various navigator object properties - I'm just a killjoy, I guess.)

This larger script is better than the dinky one I looks not only at the 'navigator.appName', but also at the navigator version number, Joe states in Script Tip #13. Well, so what? Given that MSIE 4.0b1/3.02/4.0b2 users are all sent to the same msiepage.html page, and that Netscape ≥2.0 users are all sent to the same nspage.html page, then what is the point of testing for this information? Joe himself paves the way for some major housecleaning a little later in Script Tip #13, inadvertently letting the cat out of the bag when he notes:
"You already know that [a navigator.appName value of] 'Netscape' denotes the Navigator browser. Well, now you can see that 'Microsoft Internet Explorer' denotes the MSIE browser."
If this is true for all versions of Netscape and MSIE, then it follows that Iceman's script should be reducible to:

<script type="text/javascript">
if (navigator.appName == "Microsoft Internet Explorer")
window.location = "msiepage.html";
if (navigator.appName == "Netscape")
window.location = "nspage.html";
if (navigator.appName != "Microsoft Internet Explorer" && navigator.appName != "Netscape")
window.location = "textpage.html";

So, is Iceman's browser sniffer, with its mountain of superfluous code, really "better" than Joe's? Not in my book - "less is more" here, I would argue. One more point before moving on: see those navigator.appCodeName == "Mozilla" expressions in the if statements #4-6 Netscape tests? The Microsoft Developer Network (MSDN) notes here that Mozilla is the default navigator.appCodeName value for both Internet Explorer and Netscape.

Detecting more recent browsers

As we all know, Netscape lost the 'first browser war' - this graph says it all. And things haven't gotten any better for Netscape since then - Netscape's usage share is now below that of the Firefox, Opera, and Safari browsers.

Internet Explorer still holds a commanding lead in the browser sweepstakes, of course, but over the last couple of years, Mozilla and Firefox have been slowly but surely chipping away at MSIE's position, reaching a ≅10% usage share. How might we test for Firefox?

The "Additional Browsers' Navigator Information" section of JavaScript Kit's navigator object page notes that recent Windows, Mac, and Linux flavors of Firefox all give a navigator.appName return of Netscape. However, the string Firefox does appear in the navigator.userAgent return for these browsers, and we can fish it out via the indexOf( ) method of the core JavaScript String object as follows:

if (navigator.userAgent.indexOf("Firefox") != -1)
window.location = "firefoxpage.html";

The userAgent property and the indexOf( ) method are generally useful in this regard; they can analogously detect the AOL, Konqueror, Mozilla, MSIE, Netscape, Opera, and Safari browsers, and probably other browsers as well. They can also be used to distinguish Windows, Mac, and Linux users. And in some (but not all) cases, they can, in conjunction with the substr( ) method of the String object and the top-level parseFloat( ) function, be used to determine a browser's version number, as illustrated below:

// this code borrows from the aforelinked browser sniffer
var ua = navigator.userAgent;
var appnamestart = ua.indexOf("MSIE");
var appversionstart = appnamestart + 5;
var appversionstring = ua.substr(appversionstart);
var appversion = parseFloat(appversionstring);

Here's what's happening:
(1) The ua.indexOf("MSIE") expression returns the character index at which the "M" of "MSIE" appears in the navigator.userAgent string; this index is assigned to appnamestart.
(2) The MSIE version number begins at the character index appversionstart, five characters later than appnamestart in the userAgent string.
(3) The ua.substr(appversionstart) expression returns the portion of the userAgent string beginning with appversionstart and running to the end of the userAgent string; this substring is assigned to appversionstring.
(4) The parseFloat(appversionstring) expression returns the number at the beginning of appversionstring, i.e., the browser version number.

For example, MSIE 3.02 would give an appversion value of 3.02, as can be seen from the var ua = "Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)" statement preceding if statement #2 in Iceman's script. This method for obtaining a browser version number gives results that are spot on in some cases, approximately correct in some cases, and wildly off in some cases - use it with caution.

As for the future...

I've recently been reading through HTML Goodies' collection of XHTML articles, which are curiously subdivided between the Beyond HTML : XML section and the HTML Primers section, and the following remarks in the "Following the XHTML Path" article caught my eye:
"The one aspect that every Web page designer has wanted was cross browser compatibility. By current standards, when you code HTML you must either code separately for each browser you wish to support or you must code in such a fashion that all browsers will accept the HTML that you create...With XML and XHTML, if the browser can parse the W3C XHTML 1.0 DTD correctly, then your page should display nicely...The DTD will define the data for [a] specific device and the XML parser contained in the device will process the information correctly and prepare it for presentation to the user."
Go take a good look at the browser sniffer if you didn't do so earlier; my own response to this script: "This is nuts." Perhaps we would all be better off tightening up our code.

I think we've had our fill of browser sniffing, don't you? In the next post, we'll take on Script Tips #16-18, which discuss a script that generates and writes to the page a random integer.


Comments: Post a Comment

<< Home

Powered by Blogger

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