Chapter 9

Client Detection

What’s in This Chapter?

  • Using capability detection
  • The history of user-agent detection
  • When to use each type of detection

Although browser vendors have made a concerted effort to implement common interfaces, the fact remains that each browser presents its own capabilities and flaws. Browsers that are available cross-platform often have different issues, even though they are technically the same version. These differences force web developers to either design for the lowest common denominator or, more commonly, use various methods of client detection to work with or around limitations.

Client detection remains one of the most controversial topics in web development. The idea that browsers should support a common set of functionality pervades most conversations on the topic. In an ideal world, this would be the case. In reality, however, there are enough browser differences and quirks that client detection becomes not just an afterthought but also a vital part of the development strategy.

There are several approaches to determine the web client being used, and each has advantages and disadvantages. It’s important to understand that client detection should be the very last step in solving a problem; whenever a more common solution is available, that solution should be used. Design for the most common solution first and then augment it with browser-specific solutions later.

Capability Detection

The most commonly used and widely accepted form of client detection is called capability detection. Capability detection (also called feature detection) aims not to identify a specific browser being used but rather to identify the browser’s capabilities. This approach presumes that specific browser knowledge is unnecessary and that the solution may be found by determining if the capability in question actually exists. The basic pattern for capability detection is as follows:

if (object.propertyInQuestion){
//use object.propertyInQuestion
}

For example, the DOM method document.getElementById() didn’t exist in Internet Explorer prior to version 5. This method simply didn’t exist in earlier versions, although the same functionality could be achieved using the nonstandard document.all property. This led to a capability detection fork such as the following:

function getElement(id){
if (document.getElementById){
return document.getElementById(id);
} else if (document.all){
return document.all[id];
} else {
throw new Error("No way to retrieve element!");
}
}

The purpose of the getElement() function is to return an element with the given ID. Since document.getElementById() is the standard way of achieving this, it is tested for first. If the function exists (it isn’t undefined), then it is used. Otherwise, a check is done to determine if document.all is available, and if so, that is used. If neither method is available (which is highly unlikely), an error is thrown to indicate that the function won’t work.

There are two important concepts to understand in capability detection. As just mentioned, the most common way to achieve the result should be tested for first. In the previous example, this meant testing for document.getElementById() before document.all. Testing for the most common solution ensures optimal code execution by avoiding multiple-condition testing in the common case.

The second important concept is that you must test for exactly what you want to use. Just because one capability exists doesn’t necessarily mean another exists. Consider the following example:

function getWindowWidth(){
if (document.all){ //assumes IE
return document.documentElement.clientWidth; //INCORRECT USAGE!!!
} else {
return window.innerWidth;
}
}

This example shows an incorrect usage of capability detection. The getWindowWidth() function first checks to see if document.all exists. It does, so the function then returns document.documentElement.clientWidth. As discussed in Chapter 8, Internet Explorer 8 and earlier versions do not support the window.innerWidth property. The problem in this code is that a test for document.all does not necessarily indicate that the browser is Internet Explorer. It could, in fact, be an early version of Opera, which supported document.all and window.innerWidth.

Safer Capability Detection

Capability detection is most effective when you verify not just that the feature is present but also that the feature is likely to behave in an appropriate manner. The examples in the previous section rely on type coercion of the tested object member to make a determination as to its presence. While this tells you about the presence of the object member, there is no indication if the member is the one you’re expecting. Consider the following function that tries to determine if an object is sortable:

//AVOID! Incorrect capability detection - only checks for existence
function isSortable(object){
return !!object.sort;
}

This function attempts to determine that an object can be sorted by checking for the presence of the sort() method. The problem is that any object with a sort property will also return true:

var result = isSortable({ sort: true });

Simply testing for the existence of a property doesn’t definitively indicate that the object in question is sortable. The better approach is to check that sort is actually a function:

//Better - checks if sort is a function
function isSortable(object){
 return typeof object.sort == "function";
}

The typeof operator is used in this code to determine that sort is actually a function and therefore can be called to sort the data contained within.

Capability detection using typeof is preferred whenever possible, but it is not infallible. In particular, host objects are under no obligation to return rational values for typeof. The most egregious example of this occurs with Internet Explorer. In most browsers, the following code returns true if document.createElement() is present:

//doesn't work properly in Internet Explorer <= 8
function hasCreateElement(){
return typeof document.createElement == "function";
}

In Internet Explorer 8 and earlier, the function returns false because typeof document.createElement returns "object" instead of "function". As mentioned previously, DOM objects are host objects, and host objects are implemented via COM instead of JScript in Internet Explorer 8 and earlier. As such, the actual function document.createElement() is implemented as a COM object and typeof then returns "object". Internet Explorer 9 correctly returns "function" for DOM methods.

Internet Explorer has further examples where using typeof doesn’t behave as expected. ActiveX objects (supported only in Internet Explorer) act very differently than other objects. For instance, testing for a property without using typeof may cause an error, as in this code:

//causes an error in Internet Explorer
var xhr = new ActiveXObject("Microsoft.XMLHttp");
if (xhr.open){ //error occurs here
//do something
}

Simply accessing a function as a property, which this example does, causes a JavaScript error. It is safer to use typeof; however, Internet Explorer returns "unknown" for typeof xhr.open. That means the most complete way to test for the existence of a function on any object in a browser environment is along the lines of this function:

//credit: Peter Michaux
function isHostMethod(object, property) {
var t = typeof object[property];
return t=='function' ||
(!!(t=='object' && object[property])) ||
t=='unknown';
}

You can then use this function as follows:

result = isHostMethod(xhr, "open"); //true
result = isHostMethod(xhr, "foo"); //false

The isHostMethod() function is the safest to use today, understanding the quirks of browsers. Note that host objects are under no obligation to maintain their current implementation details or to mimic already-existing host object behavior. Because of this, there is no guarantee that this function, or any other, will continue to be accurate if implementations change. As the developer, you must assess your risk tolerance based on the functionality you’re trying to implement.

image

For an exhaustive discussion of the ins and outs of capability detection in JavaScript, please see Peter Michaux’s article “Feature Detection: State of the Art Browser Scripting” at http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting.

Capability Detection Is Not Browser Detection

Detecting a particular capability or set of capabilities does not necessarily indicate the browser in use. The following “browser detection” code, or something similar, can be found on numerous web sites and is an example of improper capability detection:

//AVOID! Not specific enough
var isFirefox = !!(navigator.vendor && navigator.vendorSub);
//AVOID! Makes too many assumptions
var isIE = !!(document.all && document.uniqueID);

This code represents a classic misuse of capability detection. In the past, Firefox could be determined by checking for navigator.vendor and navigator.vendorSub, but then Safari came along and implemented the same properties, meaning this code would give a false positive. To detect Internet Explorer, the code checks for the presence of document.all and document.uniqueID. This assumes that both of these properties will continue to exist in future versions of IE and won’t ever be implemented by any other browser. Both checks use a double NOT operator to produce a Boolean result (which is more optimal to store and access).

It is appropriate, however, to group capabilities together into classes of browsers. If you know that your application needs to use specific browser functionality, it may be useful to do detection for all of the capabilities once rather than doing it repeatedly. Consider this example:

image
//determine if the browser has Netscape-style plugins
var hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
//determine if the browser has basic DOM Level 1 capabilities
var hasDOM1 = !!(document.getElementById && document.createElement &&
document.getElementsByTagName);

CapabilitiesDetectionExample01.htm

In this example, two detections are done: one to see if the browser supports Netscape-style plug-ins, and one to determine if the browser supports basic DOM Level 1 capabilities. These Boolean values can later be queried, and it will take less time than it would to retest the capabilities.

image

Capability detection should be used only to determine the next step in a solution, not as a flag indicating a particular browser is being used.

Quirks Detection

Similar to capability detection, quirks detection aims to identify a particular behavior of the browser. Instead of looking for something that’s supported, however, quirks detection attempts to figure out what isn’t working correctly (“quirk” really means “bug”). This often involves running a short amount of code to determine that a feature isn’t working correctly. For example, a bug in Internet Explorer 8 and earlier causes instance properties with the same name as prototype properties whose [[Enumerable]] attribute is set to false to not appear in for-in loops. This quirk can be tested using the following code:

image
var hasDontEnumQuirk = function(){
var o = { toString : function(){} };
for (var prop in o){
if (prop == "toString"){
return false;
}
}
return true;
}();

QuirksDetectionExample01.htm

This code uses an anonymous function to test for the quirk. An object is created with the toString() method defined. In proper ECMAScript implementations, toString should be returned as a property in the for-in loop.

Another quirk commonly tested for is Safari versions prior to 3 enumerating over shadowed properties. This can be tested for as follows:

var hasEnumShadowsQuirk = function(){
var o = { toString : function(){} };
var count = 0;
for (var prop in o){
if (prop == "toString"){
count++;
}
}
return (count > 1);
}();

QuirksDetectionExample01.htm

If the browser has this quirk, an object with a custom toString() method will cause two instances of toString to appear in the for-in loop.

Quirks are frequently browser-specific and often are recognized as bugs that may or may not be fixed in later versions. Since quirks detection requires code to be run, it’s advisable to test for only the quirks that will affect you directly and to do so at the beginning of the script to get it out of the way.

User-Agent Detection

The third, and most controversial, client-detection method is called user-agent detection. User-agent detection uses the browser’s user-agent string to determine the exact browser being used. The user-agent string is sent as a response header for every HTTP request and is made accessible in JavaScript through navigator.userAgent. On the server side, it is a common and accepted practice to look at the user-agent string to determine the browser being used and to act accordingly. On the client side, however, user-agent detection is generally considered a last-ditch approach for when capability detection and/or quirks detection cannot be used.

Among the controversial aspects of the user-agent string is its long history of spoofing, when browsers try to fool servers by including erroneous or misleading information in their user-agent string. To understand this problem, it’s necessary to take a look back at how the user-agent string has evolved since the Web first appeared.

History

The HTTP specification, both versions 1.0 and 1.1, indicates that browsers should send short user-agent strings specifying the browser name and version. RFC 2616 (the HTTP 1.1 protocol specification) describes the user-agent string in this way:

Product tokens are used to allow communicating applications to identify themselves by software name and version. Most fields using product tokens also allow sub-products which form a significant part of the application to be listed, separated by white space. By convention, the products are listed in order of their significance for identifying the application.

The specification further stipulates that the user-agent string should be specified as a list of products in the form token/product version. In reality, however, user-agent strings have never been that simple.

Early Browsers

The first web browser, Mosaic, was released in 1993 by the National Center for Supercomputing Applications (NCSA). Its user-agent string was fairly simple, taking a form similar to this:

Mosaic/0.9

Though this would vary depending on the operating system and platform, the basic format was simple and straightforward. The text before the forward slash indicated the product name (sometimes appearing as NCSA Mosaic or other derivatives), and the text after the slash is the product version.

When Netscape Communications began developing its web browser, its code name was Mozilla (short for “Mosaic Killer”). Netscape Navigator 2, the first publicly available version, had a user-agent string with the following format:

Mozilla/Version [Language] (Platform; Encryption)

Netscape kept the tradition of using the product name and version as the first part of the user-agent string but added the following information afterward:

  • Language — The language code indicating where the application was intended to be used.
  • Platform — The operating system and/or platform on which the application is running.
  • Encryption — The type of security encryption included. Possible values are U (128-bit encryption), I (40-bit encryption), and N (no encryption).

A typical user-agent string from Netscape Navigator 2 looked like this:

Mozilla/2.02 [fr] (WinNT; I)

This string indicates Netscape Navigator 2.02 is being used, is compiled for use in French-speaking countries, and is being run on Windows NT with 40-bit encryption. At this point in time, it was fairly easy to determine what browser was being used just by looking at the product name in the user-agent string.

Netscape Navigator 3 and Internet Explorer 3

In 1996, Netscape Navigator 3 was released and became the most popular web browser, surpassing Mosaic. The user-agent string went through only a small change, removing the language token and allowing optional information about the operating system or CPU used on the system. The format became the following:

Mozilla/Version (Platform; Encryption [; OS-or-CPU description])

A typical user-agent string for Netscape Navigator 3 running on a Windows system looked like this:

Mozilla/3.0 (Win95; U)

This string indicates Netscape Navigator 3 running on Windows 95 with 128-bit encryption. Note that the OS or CPU description was left off when the browser ran on Windows systems.

Shortly after the release of Netscape Navigator 3, Microsoft released its first publicly available web browser, Internet Explorer 3. Since Netscape was the dominant browser at the time, many servers specifically checked for it before serving up pages. The inability to access pages in Internet Explorer would have crippled adoption of the fledgling browser, so the decision was made to create a user-agent string that would be compatible with the Netscape user-agent string. The result was the following format:

Mozilla/2.0 (compatible; MSIE Version; Operating System)

For example, Internet Explorer 3.02 running on Windows 95 had this user-agent string:

Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)

Since most browser sniffers at the time looked only at the product-name part of the user-agent string, Internet Explorer successfully identified itself as Mozilla, the same as Netscape Navigator. This move caused some controversy since it broke the convention of browser identification. Furthermore, the true browser version is buried in the middle of the string.

Another interesting part of this string is the identification of Mozilla 2.0 instead of 3.0. Since 3.0 was the dominant browser at the time, it would have made more sense to use that. The actual reason remains a mystery — it was more likely an oversight than anything else.

Netscape Communicator 4 and Internet Explorer 4–8

In August 1997, Netscape Communicator 4 was released (the name was changed from Navigator to Communicator for this release). Netscape opted to keep the following user-agent string format from version 3:

Mozilla/Version (Platform; Encryption [; OS-or-CPU description])

With version 4 on a Windows 98 machine, the user-agent string looked like this:

Mozilla/4.0 (Win98; I)

As Netscape released patches and fixes for its browser, the version was incremented accordingly, as the following user-agent string from version 4.79 indicates:

Mozilla/4.79 (Win98; I)

When Microsoft released Internet Explorer 4, the user-agent string featured an updated version, taking the following format:

Mozilla/4.0 (compatible; MSIE Version; Operating System)

For example, Internet Explorer 4 running on Windows 98 returned the following user-agent string:

Mozilla/4.0 (compatible; MSIE 4.0; Windows 98)

With this change, the reported Mozilla version and the actual version of Internet Explorer were synchronized, allowing for easy identification of these fourth-generation browsers. Unfortunately, the synchronization ended there. When Internet Explorer 4.5 (released only for Macs) debuted, the Mozilla version remained 4 while the Internet Explorer version changed as follows:

Mozilla/4.0 (compatible; MSIE 4.5; Mac_PowerPC)

In Internet Explorer versions through version 7, the following pattern has remained:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)

Internet Explorer 8 introduced an additional token called Trident, which is the name of the rendering engine. The format became:

Mozilla/4.0 (compatible; MSIE Version; Operating System; Trident/TridentVersion)

For example:

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)

The extra Trident token is designed to help developers determine when Internet Explorer 8 is running in compatibility mode. In that case the MSIE version becomes 7, but the Trident version remains in the user-agent string:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0)

Adding this extra token makes it possible to determine if a browser is Internet Explorer 7 (in which case there is no Trident token) or Internet Explorer 8 running in compatibility mode.

Internet Explorer 9 slightly changed this format. The Mozilla version was incremented to 5.0, and the Trident version was also incremented to 5.0. The default user-agent string for Internet Explorer 9 looks like this:

Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)

When Internet Explorer 9 runs in compatibility mode, the old Mozilla version and MSIE version are restored while the Trident version remains at 5.0. For example, the following user-agent string is Internet Explorer 9 running in Internet Explorer 7 compatibility mode:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0)

All of these changes were made to ensure that past user-agent detection scripts continue to work correctly while enabling new scripts to have additional information.

Gecko

The Gecko rendering engine is at the heart of Firefox. When Gecko was first developed, it was as part of the generic Mozilla browser that was to become Netscape 6. A specification was written for Netscape 6, indicating how the user-agent string should be constructed in all future versions. The new format represented a fairly drastic departure from its relatively simple user-agent string used through version 4.x. The format is as follows:

Mozilla/MozillaVersion (Platform; Encryption; OS-or-CPU; Language;
PrereleaseVersion)Gecko/GeckoVersion
ApplicationProduct/ApplicationProductVersion

A lot of thought went into this remarkably complex user-agent string. The following table lists the meaning of each section.

String Required? Description
String Required? Description
MozillaVersion Yes The version of Mozilla.
Platform Yes The platform on which the browser is running. Possible values include Windows, Mac, and X11 (for Unix X-windows systems).
Encryption Yes Encryption capabilities: U for 128-bit, I for 40-bit, or N for no encryption.
OS-or-CPU Yes The operating system the browser is being run on or the processor type of the computer running the browser. If the platform is Windows, this is the version of Windows (such as WinNT, Win95, and so on). If the platform is Macintosh, then this is the CPU (either 68k, PPC for PowerPC, or MacIntel). If the Platform is X11, this is the Unix operating-system name as obtained by the Unix command uname -sm.
Language Yes The language that the browser was created for use in.
Prerelease Version No Originally intended as the prerelease version number for Mozilla, it now indicates the version number of the Gecko rendering engine.
GeckoVersion Yes The version of the Gecko rendering engine represented by a date in the format yyyymmdd.
ApplicationProduct No The name of the product using Gecko. This may be Netscape, Firefox, and so on.
ApplicationProductVersion No The version of the ApplicationProduct; this is separate from the MozillaVersion and the GeckoVersion.

To better understand the Gecko user-agent string format, consider the following user-agent strings taken from various Gecko-based browsers.

Netscape 6.21 on Windows XP:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4) Gecko/20011128
Netscape6/6.2.1

SeaMonkey 1.1a on Linux:

Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1b2) Gecko/20060823 SeaMonkey/1.1a

Firefox 2.0.0.11 on Windows XP:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127
Firefox/2.0.0.11

Camino 1.5.1 on Mac OS X:

Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.6) Gecko/20070809
Camino/1.5.1

All of these user-agent strings indicate Gecko-based browsers (albeit using different versions). Oftentimes, looking for a particular browser is not as important as understanding whether it’s Gecko-based. The Mozilla version hasn’t changed from 5.0 since the first Gecko-based browser was released, and it likely won’t change again.

With the release of Firefox 4, Mozilla simplified the user-agent string. The major changes include:

  • Removal of the Language token (i.e., “en-US” in the previous examples).
  • The Encryption token is not present when the browser uses strong encryption (which is the default). That means there will no longer be a “U” in Mozilla user-agent strings, but “I” and “N” might still be present.
  • The Platform token has been removed for Windows user-agent strings as “Windows” was redundant with the OS-or-CPU token, which always contained the string “Windows”.
  • The GeckoVersion token is now frozen to “Gecko/20100101”.

An example of the final Firefox 4 user-agent string is:

Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox 4.0.1

WebKit

In 2003, Apple announced that it would release its own web browser, called Safari. The Safari rendering engine, called WebKit, began as a fork of the KHTML rendering engine used in the Linux-based Konqueror web browser. A couple of years later, WebKit was split off into its own open-source project, focusing on development of the rendering engine.

Developers of this new browser and rendering engine faced a problem similar to that faced by Internet Explorer 3: how do you ensure that the browser isn’t locked out of popular sites? The answer is, put enough information into the user-agent string to convince web sites that the browser is compatible with another popular browser. This led to a user-agent string with the following format:

Mozilla/5.0 (Platform; Encryption; OS-or-CPU; Language)
AppleWebKit/AppleWebKitVersion (KHTML, like Gecko) Safari/SafariVersion

Here’s an example:

Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko)
Safari/125.1

As you can see, this is another long user-agent string. It takes into account not only the version of the Apple WebKit but also the Safari version. A point of contention over whether to identify the browser as Mozilla was resolved rather quickly for compatibility reasons. Now, all WebKit-based browsers identify themselves as Mozilla 5.0, the same as all Gecko-based browsers. The Safari version has typically been the build number of the browser, not necessarily a representation of the release version number. So although Safari 1.25 has the number 125.1 in the user-agent string, there may not always be a one-to-one match.

The most interesting and controversial part of this user-agent string is the addition of the string "(KHTML, like Gecko)" in a pre-1.0 version of Safari. Apple got a lot of pushback from developers who saw this as a blatant attempt to trick clients and servers into thinking Safari was actually Gecko (as if adding Mozilla/5.0 wasn’t enough). Apple’s response was similar to Microsoft’s when the Internet Explorer user-agent string came under fire: Safari is compatible with Mozilla, and web sites shouldn’t block out Safari users because they appear to be using an unsupported browser.

Safari’s user-agent string was augmented slightly when version 3 was released. The following version token is now used to identify the actual version of Safari being used:

Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/522.15.5
(KHTML, like Gecko) Version/3.0.3 Safari/522.15.5

Note that this change was made only to Safari, not to WebKit, so other WebKit-based browsers may not have this change. Generally speaking, as with Gecko, it’s typical to determine that a browser is WebKit-based rather than trying to identify Safari specifically.

Konqueror

Konqueror, the browser bundled with the KDE Linux desktop environment, is based on the KHTML open-source rendering engine. Though available only on Linux, Konqueror has an active user base. For optimal compatibility, Konqueror opted to format its user-agent string after Internet Explorer as follows:

Mozilla/5.0 (compatible; Konqueror/Version; OS-or-CPU)

However, Konqueror 3.2 introduced a change to coincide with changes to the WebKit user-agent string, identifying itself as KHTML as follows:

Mozilla/5.0 (compatible; Konqueror/Version; OS-or-CPU) KHTML/KHTMLVersion
(like Gecko)

Here’s an example:

Mozilla/5.0 (compatible; Konqueror/3.5; SunOS) KHTML/3.5.0 (like Gecko)

The version numbers for Konqueror and KHTML tend to coincide or be within a subpoint difference, such as Konquerer 3.5 using KHTML 3.5.1.

Chrome

Google’s Chrome web browser uses WebKit as its rendering engine but uses a different JavaScript engine. Chrome’s user-agent string carries along all of the information from WebKit and an extra section for the Chrome version. The format is as follows:

Mozilla/5.0 (Platform; Encryption; OS-or-CPU; Language)
AppleWebKit/AppleWebKitVersion (KHTML, like Gecko)
Chrome/ChromeVersion Safari/SafariVersion

The full user-agent string for Chrome 7 is as follows:

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7
(KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7

It’s likely that the WebKit version and Safari version will always be synchronized going forward, though this is not guaranteed.

Opera

One of the most controversial web browsers, as far as user-agent strings are concerned, is Opera. The default user-agent string for Opera is the most logical of all modern browsers, correctly identifying itself and its version. Prior to version 8, the Opera user-agent string was in the following format:

Opera/Version (OS-or-CPU; Encryption) [Language]

Using Opera 7.54 on a Windows XP computer, the user-agent string is as follows:

Opera/7.54 (Windows NT 5.1; U) [en]

With the release of Opera 8, the language part of the user-agent string was moved inside of the parentheses to better match other browsers, as follows:

Opera/Version (OS-or-CPU; Encryption; Language)

Opera 8 on Windows XP yields the following user-agent string:

Opera/8.0 (Windows NT 5.1; U; en)

By default, Opera returns a user-agent string in this simple format. Currently it is the only one of the major browsers to use the product name and version to fully and completely identify itself. As with other browsers, however, Opera found problems with using its own user-agent string. Even though it’s technically correct, there is a lot of browser-sniffing code on the Internet that is geared toward user-agent strings reporting the Mozilla product name. There is also a fair amount of code looking specifically for Internet Explorer or Gecko. Instead of confusing sniffers by changing its own user-agent string, Opera identifies itself as a different browser completely by changing its own user-agent string.

As of Opera 9, there are two ways to change the user-agent string. One way is to identify it as another browser, either Firefox or Internet Explorer. When using this option, the user-agent string changes to look just like the corresponding one for Firefox or Internet Explorer, with the addition of the string "Opera" and Opera’s version number at the end. Here’s an example:

Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0
Opera 9.50
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50

The first string identifies Opera 9.5 as Firefox 2 while maintaining the Opera version information. The second string identifies Opera 9.5 as Internet Explorer 6 and includes the Opera version information. Although these user-agent strings pass most tests for Firefox and Internet Explorer, the possibility of identifying Opera is open.

Another option for identifying the browser is to mask it as either Firefox or Internet Explorer. When masking the browser’s identity, the user-agent strings are exactly the same as would be returned from the other browsers — the string "Opera" does not appear, nor does any Opera version information. There is literally no way to distinguish Opera from the other browsers when identity masking is used. Further complicating the issue is Opera’s tendency to set site-specific user-agent strings without notifying the user. For instance, navigating to the My Yahoo! site (http://my.yahoo.com) automatically causes Opera to mask itself as Firefox. This makes identifying Opera by user-agent string very difficult.

image

Before version 7, Opera could interpret the meaning of Windows operating-system strings. For example, Windows NT 5.1 actually means Windows XP, so in Opera 6, the user-agent string included Windows XP instead of Windows NT 5.1. In an effort to be more compatible with other browsers, version 7 started including the officially reported operating-system version instead of an interpreted one.

Opera 10 introduced changes to its user-agent string. The format is now:

Opera/9.80 (OS-or-CPU; Encryption; Language) Presto/PrestoVersion Version/Version

Note that the initial version, Opera/9.80, remains fixed. There was no Opera 9.8, but Opera engineers were afraid that poor browser sniffing might cause a token of Opera/10.0 to be incorrectly interpreted as Opera 1 instead of Opera 10. Thus, Opera 10 introduced the additional Presto token (Presto is the rendering engine for Opera) and the Version token to hold the actual browser version. This is the user-agent string for Opera 10.63 on Windows 7:

Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.63

iOS and Android

The default web browsers for both iOS and Android mobile operating systems are based on WebKit and so share the same basic user-agent string format as their desktop counterparts. iOS devices follow this basic format:

Mozilla/5.0 (Platform; Encryption; OS-or-CPU like Mac OS X; Language)
AppleWebKit/AppleWebKitVersion (KHTML, like Gecko) Version/BrowserVersion
Mobile/MobileVersion Safari/SafariVersion

Note the addition of "like Mac OS X" to aid in detecting Mac operating systems and the addition of a Mobile token. The Mobile token version number is typically not useful and is used primarily to determine a mobile WebKit versus a desktop one. The platform will be "iPhone", "iPod", or "iPad", depending on the device. Example:

Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us)
AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16

Note that prior to iOS 3, the version number of the operating system did not appear in the user-agent string.

The default Android browser generally follows the format set forth on iOS but without a Mobile version (the Mobile token is still present). For example:

Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91)
AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1

This user-agent string is from a Google Nexus One phone, but other Android devices follow the same pattern.

Working with User-Agent Detection

Using the user-agent string to detect specific browsers can get quite complicated because of the history and usage of user-agent strings in modern browsers. It’s often necessary to first determine how specific you need the browser information to be. Typically, knowing the rendering engine and a minimum version is enough to determine the correct course of action. For instance, the following is not recommended:

if (isIE6 || isIE7) { //avoid!!!
//code
}

In this example, code is executed if the browser is Internet Explorer version 6 or 7. This code is very fragile because it relies on specific browser versions to determine what to do. What should happen for version 8? Anytime a new version of Internet Explorer is released, this code would have to be updated. However, using relative version numbers as shown in the following example avoids this problem:

if (ieVer >= 6){
//code
}

This rewritten example checks to see if the version of Internet Explorer is at least 6 to determine the correct course of action. Doing so ensures that this code will continue functioning appropriately in the future. The browser-detection script focuses on this methodology for identifying browsers.

Identifying the Rendering Engine

As mentioned previously, the exact name and version of a browser isn’t as important as the rendering engine being used. If Firefox, Camino, and Netscape all use the same version of Gecko, their capabilities will be the same. Likewise, any browser using the same version of WebKit that Safari 3 uses will likely have the same capabilities. Therefore, this script focuses on detecting the five major rendering engines: Internet Explorer, Gecko, WebKit, KHTML, and Opera.

This script uses the module-augmentation pattern to encapsulate the detection script and avoid adding unnecessary global variables. The basic code structure is as follows:

var client = function(){
var engine = {
//rendering engines
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//specific version
ver: null
};
//detection of rendering engines/platforms/devices here
return {
engine: engine
};
}();

In this code, a global variable named client is declared to hold the information. Within the anonymous function is a local variable named engine that contains an object literal with some default settings. Each rendering engine is represented by a property that is set to 0. If a particular engine is detected, the version of that engine will be placed into the corresponding property as a floating-point value. The full version of the rendering engine (a string) is placed into the ver property. This setup allows code such as the following:

if (client.engine.ie) { //if it's IE, client.engine.ie is greater than 0
//IE-specific code
} else if (client.engine.gecko > 1.5){
if (client.engine.ver == "1.8.1"){
//do something specific to this version
}
}

Whenever a rendering engine is detected, its property on client.engine gets set to a number greater than 0, which converts to a Boolean true. This allows a property to be used with an if statement to determine the rendering engine being used, even if the specific version isn’t necessary. Since each property contains a floating-point value, it’s possible that some version information may be lost. For instance, the string "1.8.1" becomes the number 1.8 when passed into parseFloat(). The ver property ensures that the full version is available if necessary.

To identify the correct rendering engine, you need to test in the correct order. Testing out of order may result in incorrect results because of the user-agent inconsistencies. For this reason, the first step is to identify Opera, since its user-agent string may completely mimic other browsers. Opera’s user-agent string cannot be trusted since it won’t, in all cases, identify itself as Opera.

To identify Opera, you need to look for the window.opera object. This object is present in all versions of Opera 5 and later and is used to identify information about the browser and to interact directly with the browser. In versions later than 7.6, a method called version()returns the browser version number as a string, which is the best way to determine the Opera version number. Earlier versions may be detected using the user-agent string, since identity masking wasn’t supported. However, since Opera’s most recent version at the end of 2010 was 10.63, it’s unlikely that anyone is using a version older than 7.6. The first step in the rendering engine’s detection code is as follows:

if (window.opera){
engine.ver = window.opera.version();
engine.opera = parseFloat(engine.ver);
}

The string representation of the version is stored in engine.ver, and the floating-point representation is stored in engine.opera. If the browser is Opera, the test for window.opera will return true. Otherwise, it’s time to detect another browser.

The next logical rendering engine to detect is WebKit. Since WebKit’s user-agent string contains "Gecko" and "KHTML", incorrect results could be returned if you were to check for those rendering engines first.

WebKit’s user-agent string, however, is the only one to contain the string "AppleWebKit", so it’s the most logical one to check for. The following is an example of how to do this:

var ua = navigator.userAgent;
if (window.opera){
engine.ver = window.opera.version();
engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
 engine.ver = RegExp["$1"];
 engine.webkit = parseFloat(engine.ver);
}

This code begins by storing the user-agent string in a variable called ua. A regular expression tests for the presence of "AppleWebKit" in the user-agent string and uses a capturing group around the version number. Since the actual version number may contain a mixture of numbers, decimal points, and letters, the non–white-space special character (S) is used. The separator between the version number and the next part of the user-agent string is a space, so this pattern ensures all of the versions will be captured. The test() method runs the regular expression against the user-agent string. If it returns true, then the captured version number is stored in engine.ver and the floating-point representation is stored in engine.webkit. WebKit versions correspond to Safari versions, as detailed in the following table.

Safari Version Minimum WebKit Version
1.0 through 1.0.2 85.7
1.0.3 85.8.2
1.1 through 1.1.1 100
1.2.2 125.2
1.2.3 125.4
1.2.4 125.5.5
1.3 312.1
1.3.1 312.5
1.3.2 312.8
2.0 412
2.0.1 412.7
2.0.2 416.11
2.0.3 417.9
2.0.4 418.8
3.0.4 523.10
3.1 525
image

Sometimes Safari versions don’t match up exactly to WebKit versions and may be a subpoint off. The preceding table indicates the most-likely WebKit versions but is not exact.

The next rendering engine to test for is KHTML. Once again, this user-agent string contains "Gecko", so you cannot accurately detect a Gecko-based browser before first ruling out KHTML. The KHTML version is included in the user-agent string in a format similar to WebKit, so a similar regular expression is used. Also, since Konqueror 3.1 and earlier don’t include the KHTML version specifically, the Konquerer version is used instead. Here’s an example:

var ua = navigator.userAgent;
if (window.opera){
engine.ver = window.opera.version();
engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
} else if (/KHTML/(S+)/.test(ua) || /Konqueror/([^;]+)/.test(ua)){
 engine.ver = RegExp["$1"];
 engine.khtml = parseFloat(engine.ver);
}

Once again, since the KHTML version number is separated from the next token by a space, the non–white-space character is used to grab all of the characters in the version. Then the string version is stored in engine.ver, and the floating-point version is stored in engine.khtml. If KHTML isn’t in the user-agent string, then the match is against Konqueror, followed by a slash, followed by all characters that aren’t a semicolon.

If both WebKit and KHTML have been ruled out, it is safe to check for Gecko. The actual Gecko version does not appear after the string "Gecko" in the user-agent; instead, it appears after the string "rv:". This requires a more complicated regular expression than the previous tests, as you can see in the following example:

var ua = navigator.userAgent;
if (window.opera){
engine.ver = window.opera.version();
engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
} else if (/KHTML/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.khtml = parseFloat(engine.ver);
} else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)){
 engine.ver = RegExp["$1"];
 engine.gecko = parseFloat(engine.ver);
}

The Gecko version number appears between "rv:" and a closing parenthesis, so to extract the version number, the regular expression looks for all characters that are not a closing parenthesis. The regular expression also looks for the string "Gecko/" followed by eight numbers. If the pattern matches, then the version number is extracted and stored in the appropriate properties. Gecko version numbers are related to Firefox versions, as detailed in the following table.

Firefox Version Minimum Gecko Version
1.0 1.7.5
1.5 1.8.0
2.0 1.8.1
3.0 1.9.0
3.5 1.9.1
3.6 1.9.2
4.0 2.0.0
image

As with Safari and WebKit, matches between Firefox and Gecko version numbers are not exact.

Internet Explorer is the last rendering engine to detect. The version number is found following "MSIE" and before a semicolon, so the regular expression is fairly simple, as you can see in the following example:

var ua = navigator.userAgent;
if (window.opera){
engine.ver = window.opera.version();
engine.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
} else if (/KHTML/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.khtml = parseFloat(engine.ver);
} else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
} else if (/MSIE ([^;]+)/.test(ua)){
 engine.ver = RegExp["$1"];
 engine.ie = parseFloat(engine.ver);
}

The last part of this rendering engine’s detection script uses a negation class in the regular expression to get all characters that aren’t a semicolon. Even though Internet Explorer typically keeps version numbers as standard floating-point values, that won’t necessarily always be so. The negation class [^;] is used to allow for multiple decimal points and possibly letters.

Identifying the Browser

In most cases, identifying the browser’s rendering engine is specific enough to determine a correct course of action. However, the rendering engine alone doesn’t indicate that JavaScript functionality is present. Apple’s Safari browser and Google’s Chrome browser both use WebKit as their rendering engine but use different JavaScript engines. Both browsers would return a value for client.engine.webkit, but that may not be specific enough. For these two browsers, it’s helpful to add new properties to the client object, as shown in the following example:

var client = function(){
var engine = {
//rendering engines
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//specific version
ver: null
};
 var browser = {
 //browsers
 ie: 0,
 firefox: 0,
 safari: 0,
 konq: 0,
 opera: 0,
 chrome: 0,
 //specific version
 ver: null
 };
//detection of rendering engines/platforms/devices here
return {
engine: engine,
 browser: browser
};
}();

This code adds a private variable called browser that contains properties for each of the major browsers. As with the engine variable, these properties remain zero unless the browser is being used, in which case the floating-point version is stored in the property. Also, the ver property contains the full string version of the browser in case it’s necessary. As you can see in the following example, the detection code for browsers is intermixed with the rendering-engine-detection code because of the tight coupling between most browsers and their rendering engines:

//detect rendering engines/browsers
var ua = navigator.userAgent;
if (window.opera){
 engine.ver = browser.ver = window.opera.version();
 engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
 //figure out if it's Chrome or Safari
 if (/Chrome/(S+)/.test(ua)){
 browser.ver = RegExp["$1"];
 browser.chrome = parseFloat(browser.ver);
 } else if (/Version/(S+)/.test(ua)){
 browser.ver = RegExp["$1"];
 browser.safari = parseFloat(browser.ver);
 } else {
 //approximate version
 var safariVersion = 1;
 if (engine.webkit < 100){
 safariVersion = 1;
 } else if (engine.webkit < 312){
 safariVersion = 1.2;
 } else if (engine.webkit < 412){
 safariVersion = 1.3;
 } else {
 safariVersion = 2;
 }
 browser.safari = browser.ver = safariVersion;
 }
} else if (/KHTML/(S+)/.test(ua) || /Konqueror/([^;]+)/.test(ua)){
 engine.ver = browser.ver = RegExp["$1"];
 engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
 //determine if it's Firefox
 if (/Firefox/(S+)/.test(ua)){
 browser.ver = RegExp["$1"];
 browser.firefox = parseFloat(browser.ver);
 }
} else if (/MSIE ([^;]+)/.test(ua)){
 engine.ver = browser.ver = RegExp["$1"];
 engine.ie = browser.ie = parseFloat(engine.ver);
}

For Opera and Internet Explorer, the values in the browser object are equal to those in the engine object. For Konqueror, the browser.konq and browser.ver properties are equivalent to the engine.khtml and engine.ver properties, respectively.

To detect Chrome and Safari, add additional if statements into the engine-detection code. The version number for Chrome is extracted by looking for the string "Chrome/" and then taking the numbers after that. Safari detection is done by looking for the "Version/" string and taking the number after that. Since this works only for Safari versions 3 and higher, there’s some fallback logic to map WebKit version numbers to the approximate Safari version numbers (see the table in the previous section).

For the Firefox version, the string "Firefox/" is found and the numbers after it are extracted as the version number. This happens only if the detected rendering engine is Gecko.

Using this code, you can now write logic such as the following:

if (client.engine.webkit) { //if it's WebKit
if (client.browser.chrome){
//do something for Chrome
} else if (client.browser.safari){
//do something for Safari
}
} else if (client.engine.gecko){
if (client.browser.firefox){
//do something for Firefox
} else {
//do something for other Gecko browsers
}
}

Identifying the Platform

In many cases, simply knowing the rendering engine is enough to get your code working. In some circumstances, however, the platform is of particular interest. Browsers that are available cross-platform (such as Safari, Firefox, and Opera) may have different issues on different platforms. The three major platforms are Windows, Mac, and Unix (including flavors of Linux). To allow for detection of these platforms, add a new object to client as follows:

var client = function(){
var engine = {
//rendering engines
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//specific version
ver: null
};
var browser = {
//browsers
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//specific version
ver: null
};
 var system = {
 win: false,
 mac: false,
 x11: false
 };
//detection of rendering engines/platforms/devices here
return {
engine: engine,
browser: browser,
 system: system
};
}();

This code introduces a new system variable that has three properties. The win property indicates if the platform is Windows, mac indicates Mac, and x11 indicates Unix. Unlike rendering engines, platform information is typically very limited, without access to operating systems or versions. Of these three platforms, browsers regularly report only Windows versions. For this reason, each of these properties is represented initially by a Boolean false instead of a number (as with the rendering-engine properties).

To determine the platform, it’s much easier to look at navigator.platform than to look at the user-agent string, which may represent platform information differently across browsers. The possible values for navigator.platform are "Win32", "Win64", "MacPPC", "MacIntel", "X11", and "Linux i686", which are consistent across browsers. The platform-detection code is very straightforward, as you can see in the following example:

var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p.indexOf("X11") == 0) || (p.indexOf("Linux") == 0);

This code uses the indexOf() method to look at the beginning of the platform string. To detect Windows, the platform-detection code simply looks for the string "Win" at the beginning of the platform string (covers both "Win32" and "Win64"). Testing for a Mac platform is done in the same way to accommodate both "MacPPC" and "MacIntel". The test for Unix looks for both "X11" and "Linux" at the beginning of the platform string to future-proof this code against other variants.

image

Earlier versions of Gecko returned "Windows" for all Windows platforms and "Macintosh" for all Mac platforms. This occurred prior to the release of Firefox 1, which stabilized navigator.platform values.

Identifying Windows Operating Systems

If the platform is Windows, it’s possible to get specific operating-system information from the user-agent string. Prior to Windows XP, there were two versions of Windows: one for home use and one for business use. The version for home use was simply called Windows and had specific versions of 95, 98, and ME. The business version was called Windows NT and eventually was marketed as Windows 2000. Windows XP represented the convergence of these two product lines into a common code base evolved from Windows NT. Windows Vista then was built on Windows XP.

This information is important because of the way a Windows operating system is represented in the user-agent string. The following table shows the different strings used to represent the various Windows operating systems across browsers.

image

Because of the various ways the Windows operating system is represented in the user-agent string, detection isn’t completely straightforward. The good news is that since Windows 2000, the string representation has remained mostly the same, with only the version number changing. To detect the different Windows operating systems, you need a regular expression. Keep in mind that Opera versions prior to 7 are no longer in significant use, so there’s no need to prepare for them.

The first step is to match the strings for Windows 95 and Windows 98. The only difference between the strings returned by Gecko and the other browsers is the absence of "dows" and a space between "Win" and the version number. This is a fairly easy regular expression, as you can see here:

/Win(?:dows )?([^do]{2})/

Using this regular expression, the capturing group returns the operating-system version. Since this may be any two-character code not containing "d" or "o" (such as 95, 98, 9x, NT, ME, or XP) two non–white-space characters are used.

The Gecko representation for Windows NT adds a "4.0" at the end. Instead of looking for that exact string, it makes more sense to look for a decimal number like this:

/Win(?:dows )?([^do]{2})(d+.d+)?/

This regular expression introduces a second capturing group to get the NT version number. Since that number won’t be there for Windows 95 or 98, it must be optional. The only difference between this pattern and the Opera representation of Windows NT is the space between "NT" and "4.0", which can easily be added as follows:

/Win(?:dows )?([^do]{2})s?(d+.d+)?/

With these changes, the regular expression will also successfully match the strings for Windows ME, Windows XP, and Windows Vista. The first capturing group will capture 95, 98, 9x, NT, ME, or XP. The second capturing group is used only for Windows ME and all Windows NT derivatives. This information can be used to assign specific operating-system information to the system.win property, as in the following example:

if (system.win){
if (/Win(?:dows )?([^do]{2})s?(d+.d+)?/.test(ua)){
if (RegExp["$1"] == "NT"){
switch(RegExp["$2"]){
case "5.0":
system.win = "2000";
break;
case "5.1":
system.win = "XP";
break;
case "6.0":
system.win = "Vista";
break;
case "6.1":
system.win = "7";
break;
default:
system.win = "NT";
break;
}
} else if (RegExp["$1"] == "9x"){
system.win = "ME";
} else {
system.win = RegExp["$1"];
}
}
}

If system.win is true, then the regular expression is used to extract specific information from the user-agent string. It’s possible that some future version of Windows won’t be detectable via this method, so the first step is to check if the pattern is matched in the user-agent string. When the pattern matches, the first capturing group will contain one of the following: "95", "98", "9x", or "NT". If the value is "NT", then system.win is set to a specific string for the operating system in question; if the value is "9x", then system.win is set to "ME"; otherwise the captured value is assigned directly to system.win. This setup allows code such as the following:

if (client.system.win){
if (client.system.win == "XP") {
//report XP
} else if (client.system.win == "Vista"){
//report Vista
}
}

Since a nonempty string converts to the Boolean value of true, the client.system.win property can be used as a Boolean in an if statement. When additional information about the operating system is necessary, the string value can be used.

Identifying Mobile Devices

In 2006–2007, the use of web browsers on mobile devices exploded. There are mobile versions of all major browsers, and versions that run on other devices, so it’s important to identify these cases. The first step is to add properties for all of the mobile devices to detect for, as in the following example:

var client = function(){
var engine = {
//rendering engines
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//specific version
ver: null
};
var browser = {
//browsers
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//specific version
ver: null
};
var system = {
win: false,
mac: false,
x11: false,
 //mobile devices
 iphone: false,
 ipod: false,
 ipad: false,
 ios: false,
 android: false,
 nokiaN: false,
 winMobile: false };
//detection of rendering engines/platforms/devices here
return {
engine: engine,
browser: browser,
system: system
};
}();

Detecting iOS devices is as simple as searching for the strings "iPhone", "iPod", and "iPad":

system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;

In addition to knowing the iOS device, it’s also helpful to know the version of iOS. Prior to iOS 3, the user-agent string simply said "CPU like Mac OS X", while later it was changed to "CPU iPhone OS 3_0 like Mac OS X" for iPhone and "CPU OS 3_2 like Mac OS X" for the iPad. This means detecting iOS requires a regular expression to take these changes into account:

//determine iOS version
if (system.mac && ua.indexOf("Mobile") > -1){
if (/CPU (?:iPhone )?OS (d+_d+)/.test(ua)){
system.ios = parseFloat(RegExp.$1.replace("_", "."));
} else {
system.ios = 2; //can't really detect - so guess
}
}

Checking to see if the system is a Mac OS and if the string "Mobile" is present ensures that system.ios will be nonzero regardless of the version. After that, a regular expression is used to determine if the iOS version is present in the user-agent string. If it is, then system.ios is set to a floating-point value for the version; otherwise, the version is hardcoded to 2. (There is no way to determine the actual version, so picking the previous version is safe for making this value useful.)

Detecting the Android operating system is a simple search for the string "Android" and retrieving the version number immediately after:

//determine Android version
if (/Android (d+.d+)/.test(ua)){
system.android = parseFloat(RegExp.$1);
}

Since all versions of Android include the version number, this regular expression accurately detects all versions and sets system.android to the correct value.

Nokia Nseries mobile phones also use WebKit. The user-agent string is very similar to other WebKit-based phones, such as the following:

Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/11.0.026;
Profile MIDP-2.0 Configuration/CLDC-1.1) AppleWebKit/413 (KHTML, like Gecko)
Safari/413

Note that even though the Nokia Nseries phones report "Safari" in the user-agent string, the browser is not actually Safari though it is WebKit-based. A simple check for "NokiaN" in the user-agent string, as shown here, is sufficient to detect this series of phones:

system.nokiaN = ua.indexOf("NokiaN") > -1;

With this device information, it’s possible to figure out how the user is accessing a page with WebKit by using code such as this:

if (client.engine.webkit){
if (client.system.ios){
//iOS stuff
} else if (client.system.android){
//android stuff
} else if (client.system.nokiaN){
//nokia stuff
}
}

The last major mobile-device platform is Windows Mobile (previously called Windows CE), which is available on both Pocket PCs and smartphones. Since these devices are technically a Windows platform, the Windows platform and operating system will return correct values. For Windows Mobile 5.0 and earlier, the user-agent strings for these two devices were very similar, such as the following:

Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320)
Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220)

The first of these is mobile Internet Explorer 4.01 on the Pocket PC, and the second one is the same browser on a smartphone. When the Windows operating system detection script is run against either of these strings, client.system.win gets filled with "CE", so detection for early Windows Mobile devices can be done using this value.

It’s not advisable to test for "PPC" or "Smartphone" in the string, because these tokens have been removed in browsers on Windows Mobile later than 5.0. Oftentimes, simply knowing that the device is using Windows Mobile is enough.

Windows Phone 7 features a slightly augmented user-agent string with the following basic format:

Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0)
Asus;Galaxy6

The format of the Windows operating system identifier breaks from tradition, so the value of client.system.win is equal to "Ph" when this user-agent is encountered. This information can be used to get more information about the system:

//windows mobile
if (system.win == "CE"){
system.winMobile = system.win;
} else if (system.win == "Ph"){
if(/Windows Phone OS (d+.d+)/.test(ua)){;
system.win = "Phone";
system.winMobile = parseFloat(RegExp["$1"]);
}
}

If the value of system.win is "CE", that means it’s an older version of Windows Mobile, so system.winMobile is set to that value (it’s the only information you have). If system.win is "Ph", then the device is probably Windows Phone 7 or later, so another regular expression is used to test for the format and extract the version number. The value of system.win is then reset to "Phone" and system.winMobile is set to the version number.

Identifying Game Systems

Another new area in which web browsers have become increasingly popular is on video game systems. Both the Nintendo Wii and Playstation 3 have web browsers either built in or available for download. The Wii browser is actually a custom version of Opera, designed specifically for use with the Wii remote. The Playstation browser is custom and is not based on any of the rendering engines previously mentioned. The user-agent strings for these browsers are as follows:

Opera/9.10 (Nintendo Wii;U; ; 1621; en)
Mozilla/5.0 (PLAYSTATION 3; 2.00)

The first user-agent string is Opera running on the Wii. It stays true to the original Opera user-agent string (keep in mind that Opera on the Wii does not have identity-masking capabilities). The second string is from a Playstation 3, which reports itself as Mozilla 5.0 for compatibility but doesn’t give much information. Oddly, it uses all uppercase letters for the device name, prompting concerns that future versions may change the case.

Before detecting these devices, you must add appropriate properties to the client.system object as follows:

var client = function(){
var engine = {
//rendering engines
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//specific version
ver: null
};
var browser = {
//browsers
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//specific version
ver: null
};
var system = {
win: false,
mac: false,
x11: false,
//mobile devices
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false,
 //game systems
 wii: false,
 ps: false
};
//detection of rendering engines/platforms/devices here
return {
engine: engine,
browser: browser,
system: system
};
}();

The following code detects each of these game systems:

system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);

For the Wii, a simple test for the string "Wii" is enough. The rest of the code will pick up that the browser is Opera and return the correct version number in client.browser.opera. For the Playstation, a regular expression is used to test against the user-agent string in a case-insensitive way.

The Complete Script

The complete user-agent detection script, including rendering engines, platforms, Windows operating systems, mobile devices, and game systems is as follows:

image
var client = function(){
//rendering engines
var engine = {
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,
//complete version
ver: null
};
//browsers
var browser = {
//browsers
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//specific version
ver: null
};
//platform/device/OS
var system = {
win: false,
mac: false,
x11: false,
//mobile devices
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false,
//game systems
wii: false,
ps: false
};
//detect rendering engines/browsers
var ua = navigator.userAgent;
if (window.opera){
engine.ver = browser.ver = window.opera.version();
engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S+)/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
//figure out if it's Chrome or Safari
if (/Chrome/(S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.chrome = parseFloat(browser.ver);
} else if (/Version/(S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.safari = parseFloat(browser.ver);
} else {
//approximate version
var safariVersion = 1;
if (engine.webkit < 100){
safariVersion = 1;
} else if (engine.webkit < 312){
safariVersion = 1.2;
} else if (engine.webkit < 412){
safariVersion = 1.3;
} else {
safariVersion = 2;
}
browser.safari = browser.ver = safariVersion;
}
} else if (/KHTML/(S+)/.test(ua) || /Konqueror/([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
//determine if it's Firefox
if (/Firefox/(S+)/.test(ua)){
browser.ver = RegExp["$1"];
browser.firefox = parseFloat(browser.ver);
}
} else if (/MSIE ([^;]+)/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.ie = browser.ie = parseFloat(engine.ver);
}
//detect browsers
browser.ie = engine.ie;
browser.opera = engine.opera;
//detect platform
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
//detect windows operating systems
if (system.win){
if (/Win(?:dows )?([^do]{2})s?(d+.d+)?/.test(ua)){
if (RegExp["$1"] == "NT"){
switch(RegExp["$2"]){
case "5.0":
system.win = "2000";
break;
case "5.1":
system.win = "XP";
break;
case "6.0":
system.win = "Vista";
break;
case "6.1":
system.win = "7";
break;
default:
system.win = "NT";
break;
}
} else if (RegExp["$1"] == "9x"){
system.win = "ME";
} else {
system.win = RegExp["$1"];
}
}
}
//mobile devices
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
system.nokiaN = ua.indexOf("NokiaN") > -1;
//windows mobile
if (system.win == "CE"){
system.winMobile = system.win;
} else if (system.win == "Ph"){
if(/Windows Phone OS (d+.d+)/.test(ua)){;
system.win = "Phone";
system.winMobile = parseFloat(RegExp["$1"]);
}
}
//determine iOS version
if (system.mac && ua.indexOf("Mobile") > -1){
if (/CPU (?:iPhone )?OS (d+_d+)/.test(ua)){
system.ios = parseFloat(RegExp.$1.replace("_", "."));
} else {
system.ios = 2; //can't really detect - so guess
}
}
//determine Android version
if (/Android (d+.d+)/.test(ua)){
system.android = parseFloat(RegExp.$1);
}
//gaming systems
system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);
//return it
return {
engine: engine,
browser: browser,
system: system
};
}();

client.js

Usage

As mentioned previously, user-agent detection is considered the last option for client detection. Whenever possible, capability detection and/or quirks detection should be used first. User-agent detection is best used under the following circumstances:

  • If a capability or quirk cannot be accurately detected directly. For example, some browsers implement functions that are stubs for future functionality. In that case, testing for the existence of the function doesn’t give you enough information.
  • If the same browser has different capabilities on different platforms. It may be necessary to determine which platform is being used.
  • If you need to know the exact browser for tracking purposes.

Summary

Client detection is one of the most controversial topics in JavaScript. Because of differences in browsers, it is often necessary to fork code based on the browser’s capabilities. There are several approaches to client detection, but the following three are used most frequently:

  • Capability detection — Tests for specific browser capabilities before using them. For instance, a script may check to see if a function exists before calling it. This approach frees developers from worrying about specific browser types and versions, letting them simply focus on whether the capability exists or not. Capabilities detection cannot accurately detect a specific browser or version.
  • Quirks detection — Quirks are essentially bugs in browser implementations, such as WebKit’s early quirk of returning shadowed properties in a for-in loop. Quirks detection often involves running a short piece of code to determine if the browser has the particular quirk. Since it is less efficient than capability detection, quirks detection is used only when a specific quirk may interfere with the processing of the script. Quirks detection cannot detect a specific browser or version.
  • User-agent detection — Identifies the browser by looking at its user-agent string. The user-agent string contains a great deal of information about the browser, often including the browser, platform, operating system, and browser version. There is a long history to the development of the user-agent string, with browser vendors attempting to fool web sites into believing they are another browser. User-agent detection can be tricky, especially when dealing with Opera’s ability to mask its user-agent string. Even so, the user-agent string can determine the rendering engine being used and the platform on which it runs, including mobile devices and gaming systems.

When you are deciding which client-detection method to use, it’s preferable to use capability detection first. Quirks detection is the second choice for determining how your code should proceed. User-agent detection is considered the last choice for client detection, because it is so dependent on the user-agent string.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.145.82