Section D. Browser detection


D. Browser detection

Occasionally it's possible to detect the browsers your visitors are using. Unfortunately, this is the most abused feature of JavaScript, and if you're new to JavaScript development I flatly forbid you to use it.

Why browser detection doesn't work

The idea behind browser detection is that you exclude those browsers in which you know your script will not work. Essentially you take a blurry snapshot of browser compatibility patterns at the time you create your script, and then assume that this snapshot isn't blurry and will remain accurate. But browser compatibility patterns are shifting constantly; what's true today can be false tomorrow.

Let's continue the document.styleSheets example. You have found out that Opera doesn't support it and that Safari has only read-only access, and you rightly decide to make sure that these browsers won't execute Edit Style Sheet.

This is wrong:

   var browser = [detect browser];  function initStyleChange () {  if (browser == 'Opera' || browser == 'Safari')      return      // start initialization 


Sure, Opera and Safari are now excluded from the script. But what about Netscape 4? It doesn't support document.styleSheets, but your browser detect doesn't stop it from trying to execute the script. So you have to add it:

   if (browser == 'Opera' || browser == 'Safari'  || browser == 'Netscape 4')      return; 


All fine and dandy, but iCab doesn't support document.styleSheets either. So here we go again:

   if (browser == 'Opera' || browser == 'Safari'   || browser == 'Netscape 4' || browser == 'iCab')       return 


This is getting ridiculous. You can never be certain that your browser detect will catch all browsers, because there are simply too many of them, and you can't test your script in all of them. As we've seen, one well-placed object detection takes care of the whole problem, making it obvious which method is better.

Nonetheless, suppose you create a 100% accurate browser detect that catches all browsers that don't support document.styleSheets. (Impossiblebut suppose for a moment that it isn't.) Although you have solved your problem for the present time, the forward compatibility of your solution is essentially nil.

I fully expect Opera and Safari to start supporting document.styleSheets one day, although I cannot predict when and in which version. At that time, your browser detect will not allow Opera and Safari access to the script, even though by then they would be able to easily handle it. Your perfect browser detection has become an active bug that cheats your clients and usersvery unprofessional.

Besides, you can't trust what browsers say about themselves. They routinely disguise their identity in order to bypass browser detects.

The browser-detect arms race

From the start of the Web, every browser has had a browser identification string. In JavaScript it's stored in navigator.userAgent, and it's also sent to the server as a HTTP header with every request the browser makes.

Back around 1995 there were Mosaic and Netscape, and of the two Netscape was decidedly the more advanced, since it supported exciting novelties like cookies and the <center> tag.

Along came a now-fortunately-forgotten fool who decided to use a browser detect in order to determine support for these featuresand things went downhill fast from there.

How did these first browser detects find Netscape? They checked whether the browser identification string started with Mozilla/, which meant it was Netscape. Conversely, Mosaic's string started with Mosaic/. Thus Web sites did stuff like this (on the server, of course, JavaScript hadn't been invented yet) and early Web developers felt very clever:

   <% if userAgent starts with 'Mozilla/' %>      <h1><center>Welcome, <% read name from cookie %>!</center></h1>  <% else if userAgent starts with 'Mosaic/' %>      <h1>Welcome, sort of</h1>  <% endif %> 


An HTML parser will ignore tags and attributes it doesn't understand, so serving a <center> tag to Mosaic is no problem. Besides, an application that uses cookies must also provide for a no-cookie situation, since even Netscape users won't have one on their first visit.

From the very start of their existence, browser detects have been an unnecessary scourge inflicted on the world by Web developers who thought they understood browsers but were tragically wrong.

Browser vendors were forced to respond, and the only reasonable course of action was to make sure their identification strings matched the ones expected by the oh-so-clever detection scripts. The arms race had begun.

When Explorer entered the market, it too supported cookies and the <center> tag, and it wanted to end up on the right side of these browser detects. So its identification string starts with Mozilla/, too. It disguised itself as Netscape from the beginning of its career.

Other browsers followed suit. Nowadays any browser that's even remotely capable of more than just showing unstyled HTML starts its browser string with Mozilla/.

The Browser Wars upped the ante. As we saw in 1C, Netscape and Microsoft made sure that their version 4 browsers were totally incompatible. Reflexively, Web developers responded by creating yet more browser detects to separate the world into Netscape and Explorer.

Some developers decided to code their sites for one browser onlyusually Explorer. Although back then there was some justification for this decision, they still used browser detection instead of object detection in order to decide whether to admit a visitor or not.

When the Browser Wars ended, countless sites had set up detection scripts that allowed access only to Explorer. History repeated itself: minor browsers such as Opera could handle most of these sites but weren't allowed access. Therefore they changed their identification string to match Explorer's. They also started offering the user some control over the string.

Figure 3.1. iCab offers its users a wide choice of identities to bypass browser detects written by clueless Web developers.


Nothing has changed since then. Nowadays most browsers need to fuzz their identity in order to access many sites.

Unraveling the browser string

In light of the browser-detect arms race, treating the arcana of the browser string is essentially useless; every rule I mention can and will be broken by many browsers. Nonetheless I'll give you a few examples in order to show you that browser detection is an exercise in futility.

Here are a few browser strings. Try to unravel them:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322) Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.8 (KHTML, like Gecko) Safari/312.6 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2) 


  • The first one is Mozilla 1.7.12 (note how cleverly the Project hid the version number).

  • The second one is Explorer 6.0 on NT 5.1 (which means XP, for reasons nobody outside Microsoft is allowed to know).

  • The third one is Safari 1.3.2 (note that Apple included conflicting AppleWebKit and Safari version numbers).

  • The fourth one is also Safari 1.3.2, but now in the disguise that allows it access to many sites otherwise closed to it.

Do you still want to deny access to certain visitors based on browser strings?

userAgent

The first rule of browser detection is to always use navigator.userAgent. Especially in older browsers, the value of this property may obey certain rules that, though never officially defined, may give some clues to a browser's identity. On the other hand, it may not.

In contrast, all other properties of navigator are unreliable. You should assume that navigator.appName, navigator.appVersion, and their ilk all lie. (Why? To bypass browser detects, of course.)

Mozilla/

As we saw, all browser identification strings start with 'Mozilla/' because that was the starting point of the arms race. The existence of this substring proves only that the browser has been released after 1994.

/[version number]

The slash is always followed by a version number, but unfortunately that, too, is useless. Every modern browser proclaims itself to be version 4 or 5 of the Mozilla code engine.

Netscape 1 through 4 were the only browsers that put their version number in this place in the string. You can use these version numbers as long as you're certain you're dealing with an old Netscape, not another browser in disguise.

(rambling string)

After the false version number comes a long and rambling string that is mostly enclosed in parentheses and contains complicated abbreviations (the reason and meaning of most of which have long since been forgotten).

But it may also hold a substring or two that actually give a clue to the browser's identity. On the other hand, it may not.

Opera

Let's treat our first positive identification. In Opera, navigator.userAgent always contains the string 'Opera', even when it's in disguise. It's instructive to take a quick peek at Opera's identification string. This is the default value for version 8.54:

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 8.54 


Note the 'MSIE 6.0'. As you might guess, this is the disguise which lets Opera bypass all browser detects.

The presence of 'Opera' in the string could be a liability: a too-clever Web developer might use this substring to positively identify Opera and deny it access to his site. Fortunately, most browser detectors don't have the faintest idea what they're doing, and many have never heard of Opera anyway, so the possibility of this happening is slight. Nonetheless, Opera has taken a risk by including this substring.

Opera is the only browser that supports the window.opera property. If you must detect Opera, query this property.

Safari, iCab, and Konqueror

If the substring 'Safari' occurs in navigator.userAgent, the browser is obviously Safari. Unfortunately the reverse is not true; the absence of 'Safari' does not prove that the browser is not Safari. If the user tells Safari to identify itself as Explorer 6 (or Mozilla 5.0 or even Netscape 4.77!), it plays this role to the hilt and doesn't give a clue about its actual identity.

Navigator.Vendor?

Konqueror always has navigator.vendor 'KDE', and Safari's value always contains 'Apple'. Should we hope for a future browser detect based on navigator.vendor?

No. Even if all browsers supported it, all browser detects would switch to if (navigator.vendor == 'Microsoft') and the whole thing would start all over again. It's far better to leave navigator.vendor in peace and forget about browser detects altogether.


The same goes for iCab and Konqueror; their strings might contain 'iCab' or 'Konqueror', but then again, they might not.

I hope that future versions of these browsers will take the same risk as Opera and carry their true name in every identification string, even when in disguise. On the other hand, I can't blame the vendors if they don'twe Web developers have made a mess of things.

Gecko

Mozilla's identification string usually contains 'Gecko.' Unfortunately, Safari's default string also contains 'Gecko.' (Why? To bypass browser detects, of course.)

In general, it's very hard to identify Mozilla positively. I go through all other options, and if I cannot identify the browser I assume it's Mozilla. But of course this assumption may be wrong.

MSIE

Explorer's string always contains the substring 'MSIE'. Most other browsers use this crucial passkey, too, so its presence proves nothing.

The version number

Explorer and Opera allow you to find their version number directly after the substring you were looking for ('Opera', 'MSIE'). Ignore the first character after the substring (it's always a space) and take the number that appears from the second character onwards.

See again Opera's string:

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 8.54 


It disguises itself as Explorer 6, yet it really is Opera 8.54. Both version numbers are separated from the identification substring by a space.

Of course this rule does not work in any other browser. Putting the version number immediately after the browser name would be a dead giveaway, and navigator.userAgent is supposed to be shrouded in mystery.

The operating system

The Windows operating system can be identified by the string 'Win'. Note that some browsers say 'Windows', and others 'Win'.

The Mac operating system can be identified by the string 'Mac'. Here, too, some local variation is possible.

There is no general detect for Linux/Unix; I usually assume that any OS that's not Windows or Mac must be a flavor of Unix. That's not always true, but minor players like Amiga and BeOS are so rare that they can safely be discounted.

Of course these rules apply only if the browser hasn't disguised itself as another browser on another platform (such as Explorer 6 on Windows XP).

Browser Detect Script

My browser detect script can be found at http://www.quirksmode.org/js/detect.html.


Correct use of browser detects

There are two situations in which a browser detect is the correct solution.

The first is when you actually want to know which browser a visitor uses, in order to store that information in a Web-site statistics program. Site Survey does so because my client wanted this information. Since the browser detect does not influence the script logic, it's perfectly safe.

The second situation is a lot messier and usually involves Explorer on Mac. This browser theoretically supports the W3C DOM, and therefore this object detection grants it access to the advanced scripts:

var W3C = document.createElement && document.getElementsByTagName; 


Nonetheless, when you actually run complicated scripts in Explorer Mac, chances are it'll crash. This is a fact that no object detection will ever discover; object detection assumes that a method that is supported does not crash the browser.

There is no solution to this dilemma other than to browser-detect Explorer Mac out of existence. Sandwich Picker does so:

[Sandwich Picker, lines 1-2]

[View full width]

var IEMAC = (navigator.userAgent.indexOf('Mac') != -1 && navigator.userAgent.indexOf( 'MSIE') != -1); var W3CDOM = (document.createElement && document.getElementsByTagName && !IEMAC);


If navigator.userAgent contains the strings 'MSIE 5' and 'Mac', then the browser is Explorer on Mac (or a browser disguised as such). The next line performs a standard object detection, but adds the clause "and if it's NOT Explorer Mac".

A filthy solution, but it's the only one possible.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net