Thursday, January 18, 2007

Browser detection

Using the navigator object to detect client's browser
Until one browser remains standing on the web (if ever), browser detection will continue to be part of any good JavaScripter's life. Whether you're gliding a div across the screen or creating an image rollover, it's fundamental that only relevant browsers pick up on your code. In this tutorial we'll probe the navigator object of JavaScript, and show how to use it to perform browser detection, whether the subject is Firefox, Internet Explorer 6, or even Opera 8.
The navigator object
The navigator object was conceived back in the days when Netscape Navigatorreined supreme. These days it serves as much as an irony of NS's diminished market share as way of probing browser information.
The navigator object of JavaScript contains the following core properties:
Properties
Description
appCodeName
The code name of the browser.
appName
The name of the browser (ie: Microsoft Internet Explorer).
appVersion
Version information for the browser (ie: 4.75 [en] (Win98; U)).
cookieEnabled
Boolean that indicates whether the browser has cookies enabled. IE4 and NS6+.
language
Returns the default language of the browser version (ie: en-US). NS4 and NS6+ only.
mimeTypes[]
An array of all MIME types supported by the client. NS4 and NS6+ only. Array is always empty in IE.
platform[]
The platform of the client's computer (ie: Win32).
plugins
An array of all plug-ins currently installed on the client. NS4 and NS6+ only. Array is always empty in IE.
systemLanguage
IE4+ property that returns the default language of the operating system. Similar to NS's language property.
userAgent
String passed by browser as user-agent header. (ie: Mozilla /4.0 (compatible; MSIE 6.0; Windows NT 5.1))
userLanguage
IE4+ property that returns the preferred language setting of the user. Similar to NS's language property.
Let's see exactly what these properties reveal of your browser:
with (document){
write("AppCodeName: "+navigator.appCodeName+"")
write("AppName: "+navigator.appName+"")
write("AppVersion: "+navigator.appVersion+"")
write("UserAgent: "+navigator.userAgent+"")
write("Platform: "+navigator.platform+"")
}
AppCodeName: MozillaAppName: Microsoft Internet ExplorerAppVersion: 4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)UserAgent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)Platform: Win32
At a glance
You probably think you have a very solid idea now of how to utilize the navigator object to detect your client's browser type. At its most basic form, the following two properties are used:
navigator.appNamenavigator.appVersion
For example:
//detect Netscape 4.7+
if (navigator.appName=="Netscape"&&parseFloat(navigator.appVersion)>=4.7)
alert("You are using Netscape 4.7+")
However, depending on the browser you're trying to detect, you'll find the above two properties too limiting.
Detecting Firefox 1.0+
Take for example, Firefox 1.0+. It shares the same "appName" value as older Netscape browsers, which is "Netscape." The appVersion value returned is also out of wack, which is "5." So we need to look to another property, which turns out to be "UserAgent." For Firefox 1.04 for example, its userAgent property reads:
UserAgent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4
Ah ha, the text at the end of the string apparently contains the information we want. Based on this new found discovery, the below code detects if you're using Firefox 1.0+:
if(navigator.userAgent.indexOf("Firefox")!=-1){
var versionindex=navigator.userAgent.indexOf("Firefox")+8
if (parseInt(navigator.userAgent.charAt(versionindex))>=1)
alert("You are using Firefox 1.x or above")
}
The code assumes that all Firefox versions contain the string "Firefox/" immediately followed by the version number (ie "1").
Detecting IE 5.5+
Detecting IE using the navigator object also poses a similar problem as Firefox, since its "navigator.appVersion" property contains more than just the version number of IE:
4.0 (compatible; MSIE 5.5; Windows 98; Hotbar 3.0)
If we were to use parseFloat to try and extract out the browser version in "navigator.appVersion", we'd get 4.0, instead of the correct number (5.5). This is due to the way parseFloat works- by returning the first number it encounters. So, how does one go about getting the version number in IE? As with Firefox, it really comes down to how you wish to approach the issue. Lets stick with using the "navigator.appVersion" property this time, though you can certainly use "navigator.userAgent" as well:
//Detect IE5.5+
version=0
if (navigator.appVersion.indexOf("MSIE")!=-1){
temp=navigator.appVersion.split("MSIE")
version=parseFloat(temp[1])
}
if (version>=5.5) //NON IE browser will return 0
alert("You're using IE5.5+")
In the above, string.split() is first used to divvy up navigator.appVersion into two parts, using "MSIE" as the separator:
4.0 (compatible; MSIE 5.5; Windows 98; Hotbar 3.0)
As a result temp[1] contains the part after "MSIE", with the browser version appearing first. Doing a parseFloat() on temp[1] therefore retrieves the browser version of IE.
The Opera pitfall
Using the navigator object to screen for Opera is even a little more complex, as Opera by default identifies itself as IE, with Opera 8 identifying itself as IE6, inside "navigator.userAgent." The rationale for this is so that scripts screening for IE would also allow Opera through, so that Opera will render these pages properly. Lets take a look at what the userAgent of Opera 8.5 returns depending on what it is set to identify as:
As IE6: Mozilla/4.0 (compatible; MSIE 6.0; Windows XP) Opera 8.5 [en]As Moz5: Mozilla/5.0 (Windows XP; U) Opera 8.5 [en]As Opera: Opera/8.5 (Windows XP; U) [en]
"What's the problem? Opera 8.5 appears in each case." you may say. Well, look closer, and notice how when Opera is set to identify as itself, the Opera string returned is slightly different ("Opera/8.5" versus "Opera 8.5"). So ironically enough, the expressionif (navigator.userAgent.indexOf("Opera 8.5")!=-1)
will fail if the user has their browser set to identify as Opera, but not if any other browser!
Despite this oddity, the code to detecting Opera need not be any different than the one used to detect Firefox:
if(navigator.userAgent.indexOf("Opera")!=-1){
var versionindex=navigator.userAgent.indexOf("Opera")+6
if (parseInt(navigator.userAgent.charAt(versionindex))>=8)
alert("You are using Opera 8 or 9")
}
This also assumes that all Opera versions contain the string "Opera" immediately followed by either a space or "/", then the version number (ie "8"). And since it only looks at the first number within the version, it can only detect up to Opera 9, not 10 for example.


Using object detection to sniff out different browsers
Since different browsers support different objects, you can use Object Detection as a quick though less than infallible way of detecting various browsers. For example, since only Opera supports the property "window.opera", you can use that to instantly separate an Opera browser from the herd. Here are a few sample test cases, and what they *imply* about the browser running it:
Example objects used to "rough" detect a particular browser
Scheme
Description
document.getElementById
Detects modern browsers in general, which covers IE5+, Firefox1+, and Opera7+
window.getComputedStyle
Detects Firefox1+ and Opera 8+
Array.every
Detects Firefox1.5+ (method detection)
window.Iterator
Detects Firefox2+
document.all
Detects IE4+
window.attachEvent
Detects IE5+
window.createPopup
Detects IE5.5+
document.compatMode && document.all
Detects IE6+
window.XMLHttpRequest
Detects IE7, Firefox1+, and Opera8+
window.XMLHttpRequest && document.all
Detects IE7. Note: Will fail if visitor explicitly disables native xmlHTTPRequest support (under Toolbar-> Internet Options-> Advanced)
document.documentElement && typeof document.documentElement.style.maxHeight!="undefined"
Another way of detecting IE7 that is more reliable than the above.
window.opera
Detects Opera (any version).
* Since Opera by default also identifies itself as IE (apart from Opera), with support for many of IE's proprietary objects, the IE detection schemes above will also return true for Opera. Use "window.opera" in combination to filter out Opera browsers.
It's important to mention that object detection's chief purpose is to help you detect within your script whether the browser supports a particular object/method/property before using it, not browser detection. As the later it may be convenient over probing the Navigator object, but is only as reliable as your understanding of which objects are supported in which browsers. In other words, it's not a 100% reliable way of sniffing out the user's browser. Having said that, the below uses object detection to test for IE7:

Again object detection is really about feature detection. It detects whether your browser supports the feature your script intends to use and manipulate. For the lazy, that will substitute for browser detection just fine!

No comments: