Resources > Browser Sniffing

This gives information about making code which differs for different browsers, using client-side browser sniffing and other techniques.

Sometimes, to overcome browser differences, it is necessary to use different code for different browsers, especially to cater to older browsers which complied more poorly with browser standards. Different code should be used as seldom as possible: ideally never; this author’s experience is that modern browsers need different code very, very rarely.

There are several ways to do this, with different levels of reliability, for different purposes:

Examples are given below.

Internet Explorer Conditional Comments

This technique enables different HTML to be parsed by IE, by different versions of IE, or by all browsers other than IE. With this technique HTML can be enclosed in a special form of comments in such a way that browsers will view the HTML as comments instead of HTML unless the conditions specified in the comments are just right.

Microsoft introduced conditional comments with IE5 for Windows. They are supported by IE for Windows from versions 5 to 9, though not 10 and up.

The big advantage of conditional comments is that they are completely reliable, which is not something which can be said of other techniques.

The big disadvantages are that (a) they are HTML only, so can’t be put in CSS or JavaScript files, (b) they hide the conditional HTML, so validators can’t see the HTML, allowing hidden errors to lurk, and (c) they are not supported by all versions of IE. There are several simple and obvious ways to overcome the first problem, e.g. putting the code specific to certain browsers in separate CSS or JavaScript files, and using conditional comments to link to the appropriate files. The best way to overcome the second problem is to keep any HTML within conditional comments both short and simple. The only way to overcome the third problem is to carefully analyze the userAgent string.

One use of conditional comments, as noted in the sidebar, is to link to stylesheets which only certain versions of IE will see. For example, the following conditional comment appears in this page:

    <!--[if lt IE 6]>
        <link rel="stylesheet" type="text/css" href="style_ie5x.css">
    <![endif]-->

This links to a special stylesheet for IE less than IE 6. The conditional comments follow HTML (not shown above) which links to a standard stylesheet, so the IE stylesheet simply augments the standard stylesheet, typically to overcome IE defects, or to hide IE-specific CSS such as filters and scrollbar styling. One benefit of putting IE-specific CSS in IE-specific stylesheets is that this CSS will be hidden from CSS validators, which avoids messy validator error messages.

Microsoft documents conditional comments completely. One detail which is commonly overlooked is that, to specify IE 5.5, it must be referred to as version 5.5000.

JavaScript Object Detection

This technique uses browser differences in JavaScript objects to do things differently in different browsers.

The big advantage of object detection is that it does not require that the browser be identified, which dispenses with problems there might be with faulty browser identification.

The big disadvantage is that one must take great care in examining objects: an object must not be used unless it exists, but the fact that it exists does not always means that it can be used. For example:

Object detection is typically used either to overcome a browser defect, or to take advantage of a browser feature which is not universal.

An example of object detection to overcome a defect appears in the above function, getElement(), which overcomes the failure of old versions of IE to support the standard document.getElementById().

An example of detection to take advantage of features which are not universal would be detection of canvas.getContext() to determine whether the browser supports JavaScript related to the <canvas> tag, which is in the proposed HTML 5 standard, but not in any official standard.

CSS Tricks

This technique uses browser CSS differences to make different browsers see different CSS.

This is complex, and quite well described elsewhere, for example in Eric Meyer’s Tricking Browsers and Hiding Styles, so will not be discussed here further. Note that he mentions some tricks which apply only to extinct browsers.

Caution : some tricks depend on bugs in how browsers process invalid CSS, or on how well browsers support CSS features, so the tricks may not work, or may not work exactly the same, when browsers are updated.

Media Queries

This technique uses CSS 3 media queries to load stylesheets for devices with smaller screens, e.g. for smartphones and tablet PCs. Typically the stylesheet will override the normal stylesheet to adjust the normal layout for small screens: if the site is designed in accordance with fluid design principles, the mobile stylesheet is often fairly small. This technique is simple and well supported by better smartphones.

For example this loads stylesheet style_mob.css only if the screen width is 480px or less:

<link type="text/css" href="style_mob.css" rel="stylesheet" media="only screen and (max-width:480px)">

For details see the pertinent CSS 3 specification.

JavaScript Browser Detection

This technique involves JavaScript code — commonly called a browser sniffer — which identifies the browser by examining certain JavaScript objects, chiefly the navigator.userAgent string.

This is the least reliable approach to dealing with browser differences, but it is more reliable than many give it credit for, and it often works well when other techniques cannot be applied. Browser sniffers have a sordid reputation, largely because of the widespread use of sniffers which are far too simplistic. The earliest sniffers dealt only with versions of IE and Netscape which today are extinct, e.g. IE 3-4 and Netscape 3-4. The early sniffers could not handle the range of browsers which appeared later, and in many cases the sniffers were never updated, so many sites broke with newer browsers. This encouraged browser makers to set userAgent strings which mimiced those of more common browsers, to make broken sites think the browser was one they expected: but this made more subtle sniffers necessary for correct identification. Also, many sniffers updated to handle newer browsers are very naïve: e.g. they assume that, if 'MSIE' appears in the user agent string, the browser is Internet Explorer; this overlooks the fact that user agent strings of other browsers, such as Opera, may contain 'MSIE'. Much better sniffers are possible.

The big disadvantage to browser sniffing, of course, is that it requires that JavaScript be enabled.

Another disadvantage is that it relies on the value of the navigator.userAgent string, and this string can be faked: e.g., someone using Firefox could use a plugin which gives it the same user agent string as IE. This disadvantage is not, however, as serious as one might think, because someone would use such a plugin only for a site which has a sniffer so bad that the site cannot work with the right user agent. No one would need to fake the user agent for a site with a good sniffer.

A Modest Browser Sniffer

This describes a browser sniffer I designed which I think is very good. It has four parts:

Browser Sniffer Part 1 : HTML

The HTML goes in the <head> section, and creates a variable isTrulyIE only if the browser is IE 5-9 for Windows:

<!--[if gte IE 5]>
    <script type="text/javascript">
        var isTrulyIE = true;
    </script>
<![endif]-->
Browser Sniffer Part 2 : cBrowser() Constructor

This JavaScript constructs an object with information about the browser. It takes two optional arguments: a string to be used as the userAgent string, with the real navigator.userAgent as the default; and a string to be used as the appVersion string, with the real navigator.appVersion as the default. (You can find this JavaScript in the text file at ../js/cBrowser.js — with tabs assumed set at 4-space intervals.)

// Updated Dec 29, 2013
function cBrowser ( ua, ver )
{
if ( arguments.length == 0 )
    ua = navigator.userAgent;
this.ua = ua.toLowerCase();
if ( arguments.length < 2 )
    ver = navigator.appVersion;
this.isIE8 = this.isIE9 = this.isIE10 = this.isIE11 = false;
if ( typeof(isTrulyIE) != 'undefined' )
  {
    this.isIE = true;
    this.isKhtml = this.isChrome = this.isSafari = this.isOpera = this.isGecko = this.isNetscape = this.isWebtv = false;
  }
else
  {
    this.isKhtml = (this.ua.indexOf("khtml") != -1);
    this.isChrome = (this.ua.indexOf("chrome/") != -1);
    this.isSafari = (this.ua.indexOf("safari") != -1);
    this.isOpera = (this.ua.indexOf("opera") != -1) || (this.ua.indexOf("opr/") != -1);
    this.isGecko = !this.isKhtml && !this.isChrome && !this.isSafari && !this.isOpera &&
        (this.ua.indexOf("gecko/") != -1);
    this.isNetscape = !this.isKhtml && !this.isChrome && !this.isSafari && !this.isOpera &&
      ( (this.ua.indexOf('mozilla')!=-1) &&
        ((this.ua.indexOf('spoofer')==-1) &&
        (this.ua.indexOf('trident')==-1) &&
        (this.ua.indexOf('compatible')==-1)) );
    this.isWebtv = (this.ua.indexOf("webtv")!=-1);
    this.isIE = !this.isWebtv && !this.isOpera &&
      ( (this.ua.indexOf('msie') != -1) || (this.ua.indexOf('trident') != -1) );
    this.version = ver;
    if ( this.isNetscape && (this.version>=5) )
        this.isGecko = true;
  }
if ( this.isIE )
  {
    if ( this.ua.indexOf('rv:') != -1 )
        this.version = this.ua.substring(3+this.ua.indexOf("rv:"));
    else
        this.version = parseFloat(this.ua.substring(4+this.ua.indexOf("msie")));
    this.isIE8 = (this.version == "8.0") || (this.ua.indexOf("trident/4") != -1);
    this.isIE9 = (this.version == "9.0") || (this.ua.indexOf("trident/5") != -1);
    this.isIE10 = (this.version == "10.0") || (this.ua.indexOf("trident/6") != -1);
    this.isIE11 = (this.version == "11.0") || (this.ua.indexOf("trident/7") != -1);
    this.version = new cVersion( this.version );
  }
else if ( this.isOpera )
  {
    if ( this.ua.indexOf('version/') != -1 )
        this.version = new cVersion( parseFloat(this.ua.substring(8+this.ua.indexOf("version/"))) );
    else if ( this.ua.indexOf('opera/') == 0 )
        this.version = new cVersion( parseFloat(this.ua.substring(1+this.ua.indexOf("/"))) );
    else if ( this.ua.indexOf("opera/") != -1 )
        this.version = new cVersion( parseFloat(this.ua.substring(2+this.ua.indexOf(")"))) );
    else if ( this.ua.indexOf("opr/") != -1 )
        this.version = new cVersion( this.ua.substring(4+this.ua.indexOf("opr/")) );
    else
        this.version = new cVersion( parseFloat(this.ua.substring(6+this.ua.indexOf("opera/"))) );
  }
else
  {
    this.version = new cVersion( this.version );
  }
}

The above creates an object which has the following properties:

Browser Sniffer Part 3 : cVersion() Class

This JavaScript constructs an object which holds a version vector, with methods which act on the vector. The methods are cVersion.toString(), which returns a string corresponding to the version vector in a cVersion object, and cVersion.comp() which compares two version vectors and returns a value indicating whether the first is less than, equal to, or greater than the second version vector. (You can find this JavaScript in the text file at ../js/cVersion.js — with tabs assumed set at 4-space intervals.)

// Feb 19, 2007
function cVersion ( version, separator, bSkipSpace )
{
if ( arguments.length < 1 )
    version = '0';
if ( arguments.length < 2 )
    separator = '.';
if ( arguments.length < 3 )
    bSkipSpace = false;
if ( version instanceof cVersion )
  {
    this.separator = (arguments.length < 2) ? version.separator : separator;
    this.v = new Array();
    this.v = version.v;
  }
else
  {
    var s = ( typeof(version) == 'number' ) ? version.toString() : version;
    this.separator = separator;
    this.v = new Array();
    var vindex = 0;
    var sindex = 0;
    var c;
    this.v[vindex] = '';
    if ( bSkipSpace) for ( ; sindex < s.length; ++sindex )
      {
        c = s.charAt(sindex);
        if ( c != ' ' )
            break;
      }
    for ( ; sindex < s.length; ++sindex )
      {
        c = s.charAt(sindex);
        if ( c == separator )
            this.v[++vindex] = '';
        else if ( (c >= '0') && (c <= '9') )
            this.v[vindex] += c.toString();
        else
            break;
      }
  }
return;
}

cVersion.prototype.toString = function ( separator )
{
if ( arguments.length < 1 )
    separator = this.separator;
var rv = '';
for ( var i = 0; i < this.v.length; ++i )
  {
    if ( i == 0 )
        rv += this.v[0];
    else
        rv += separator + this.v[i];
  }
return rv;
}

cVersion.prototype.comp = function ( version2 )
{
var operand;
if ( arguments.length < 1 )
    version2 = '0';
if ( version2 instanceof cVersion )
    operand = version2;
else
    operand = new cVersion( version2 );
var nLoops = Math.max( this.v.length, operand.v.length );
var rv = 0;
for ( var i = 0; i < nLoops; ++i )
  {
    var n1 = Number( (i < this.v.length) ? this.v[i] : 0 );
    var n2 = Number( (i < operand.v.length) ? operand.v[i] : 0 );
    if ( n1 == n2 )
        continue;
    else if ( n1 < n2 )
        { rv = -1; break; }
    else
        { rv = 1; break; }
  }
return rv;
}

Browser Sniffer Part 4 : cBrowser() Instantiation

An object may now be created to hold the browser information, e.g.:

var browser = new cBrowser();

If JavaScript is enabled the above code reliably detects IE for IE 5-9 for Windows with 100% certainty due to the dependence on conditional comments, even if the user agent has been faked.

Identification of other browsers will be extremely reliable: not 100%, but close. This is partly because the certain detection of IE 5-9 eliminates a lot of possible errors, but also because the code is very careful in analyzing the user agent: for example, before checking to see if the user agent contains 'gecko/', it excludes browsers such as Opera whose user agents sometimes contain 'gecko/' in order to mimic Gecko user agents. The code is likely to misidentify the browser only if the browser is not IE 5-9 and if the entire user agent string has been faked: and, as pointed out above, the user would have no need to fake the user agent string for a site with a good browser sniffer; in practical terms, therefore, this code is exceedingly reliable.

The least reliable information is the version number: it should be correct for IE or Opera. More reliable identification of the version is possible: I have code to do it, but is not listed here because it rarely matters, since few people use very old versions of browsers other than IE. Note : for IE 8-11 the version number may be wrongly identified because IE 8-11 may emulate an older version of IE; for example, if IE 8 decides to render a page in IE 7 mode, the version number will be 7, not 8; but in this case the version number, though wrong in fact is right in practical terms. Note : reliable identification of the version of Safari can be very hard, because the navigator.userAgent string for older versions of Safari does not contain the version number, but instead contains build numbers which can only with effort be mapped to the version numbers.

This browser sniffer detects a wider range of browsers than many sites will need to recognize, e.g. MSN-TV (WebTV). Moreover, this sniffer retains a complete version vector rather than a major version number, e.g. 4.1.1 instead of just 4. This is intentional: a key concept of the above code is that it is a standard block of code which can be plugged into any site, and which can later be safely replaced by an updated equivalent. The overhead of detecting things unneeded for some sites is a small price for having a single reliable, reüsable module.

Caution: this browser sniffer may be updated from time to time in order to handle critical changes in user agent strings. For example, it was updated when IE 11 appeared because IE 11 has a user agent string which is so different that the previous version of the browser sniffer failed to reliably detect IE 11. If, therefore, you use this browser sniffer, you will have to be careful to check here regularly for any updates.

Examples

Here are some examples of achieving different code for different browsers:

Linking to IE Stylesheets

One way to specify different CSS rules for IE, or for different versions of IE, is to link to one master stylesheet file which defines the standard rules for all browsers, and then to use conditional comments to link to other, much smaller stylesheet files which set a few rules, or override a few rules, to deal with the vagaries of various versions of IE. For example:

    <link rel="stylesheet" type="text/css" href="style.css" />

    <!--[if lt IE 6]>
        <link rel="stylesheet" type="text/css" href="style_ie5x.css">
    <![endif]-->

    <!--[if gte IE 5]>
        <link rel="stylesheet" type="text/css" href="style_ie.css">
    <![endif]-->

This links to the standard stylesheet, style.css. If the browser is IE 5.x for Windows, this then links to style_ie5x.css. If the browser is any version of IE for Windows from 5.0 up, this then links to style_ie.css.

This is completely reliable for IE for Windows 5 and up.

Linking to a Mobile Stylesheet

One way to cope with mobile devices is to link to a standard stylesheet used for all pages, and then to conditionally link to a stylesheet which copes with differences in mobile devices. For example:

    <link type="text/css" href="mobile.css" rel="stylesheet"
        media="only screen and (max-width:480px)">

This links to mobile.css if the browser supports CSS 3 media queries and the device has a screen width of 480px or less.

This is reliable for modern mobile browsers which use a Gecko, Opera, or WebKit browser engine.

JavaScript for a Particular Version of IE

Sometimes JavaScript is needed which is different for a specific version of IE. For example, I have several sites with JavaScript that generates HTML to produce CSS-styled bar charts, but it does not work well for IE 5.0x because its poor CSS support produces ugly results. What I did for these sites is use conditional comments to set a JavaScript variable calBarCharts, i.e:

    <!--[if lt IE 5.5000]>
        <script type="text/javascript">
            var calBarCharts = false;
        </script>
    <![endif]-->

In some cases the bar charts were not critical and could be omitted, so the JavaScript generating the HTML would generate nothing if the variable existed and was false.

In some cases the bar charts were more important, so the JavaScript generating the HTML would generate different HTML, producing less attractive but adequate bar charts, if the variable existed and was false.

In either case, using conditional comments for this purpose eliminates the need for a complex browser sniffer. This is completely reliable for IE for Windows 5 and up.

Scrolling to a Bookmark

Sometimes JavaScript is needed which can scroll to a bookmark on the current page. Some browsers, however, do not support the preferred method, so an alternate method is used for these browsers. In the following function, object detection is used to decide which method to use:

function myScrollToAnchor ( id )
{
    if ( arguments.length < 1 )
        id = window.location.hash;
    if ( id.indexOf('#') == 0 )
        id = id.substring(1);
    var o = getElement( id );
    if ( o )
      {
        if ( (typeof(o.offsetTop) != 'undefined') && (typeof(window.scrollTo) != 'undefined') )
          {
            var x = 0;
            var y = 0;
            while ( o )
              {
                x += o.offsetLeft;
                y += o.offsetTop;
                o = o.offsetParent;
              }
            window.scrollTo( x, y );
          }
        else
            window.location.replace( '#' + id );
      }
}

The function takes an optional argument, the ID for the bookmark. If the argument is omitted, the bookmark is extracted from the URL for the current page.

Object detection is done in two locations, marked above by gold underlines:

The above should be reliable unless the browser supports neither method: I am not aware of any which don’t.

Caution : the above function has been observed to fail with certain old browsers if the function is called just after the HTML containing the anchor has been dynamically changed, e.g. using innerHTML. It appears that the browser tries to scroll to the bookmark before the dynamic changes have completed.

Extracting the Filename from a Pathname

Consider the problem of extracting a filename from a pathname. The following function was created to do this, which uses a method akin to object detection to do it:

function myGetFilename ( pathname )
{
    var sFilename;
    if ( (arguments.length == 0) || (pathname == null) )
        pathname = document.location.pathname;
    var nSlash = pathname.lastIndexOf( '/' );
    if ( nSlash == -1 )
        sFilename = pathname;
    else
        sFilename = pathname.substring(nSlash+1);
    nSlash = pathname.lastIndexOf( '\\' );
    if ( nSlash != -1 )
        sFilename = sFilename.substring(nSlash+1);
    return( sFilename );
}

This function takes a pathname as an optional argument: if the argument is omitted or null, the pathname to the current document is used.

The basic algorithm looks for the last '/' in the pathname: if not found, the filename is the pathname; if found, the filename is the string to the right of the '/'. However, some older versions of IE use a '\' instead of a '/' to separate directory names and the filename in a pathname: so the function then looks for the last '\' in the pathname: if not found, the filename is the pathname; if found, the filename is the string to the right of the '\'. This enables the function to work no matter which browser is used: not exactly by object detection, but, like object detection, the identity of the browser need not be determined, as it suffices simply to recognize the different behaviours of different browsers.

Setting Basic Font Sizes

When using CSS to style text, one common thing to do is set the basic font size. I often prefer to do so using a method which honours the user’s preferred font size. With CSS, one should be able to do this using:

html { font-size:medium; }

Unfortunately, this assumes that the default size is medium, which is what it should be, but IE5 wrongly uses small. This means that, to get consistent results, setting the size for IE5 requires:

html { font-size:small; }

One method to this with one rule uses a CSS trick which depends on how IE5 handles invalid CSS:

html { font-size:small; voice-family: "\"}\""; voice-family:inherit; font-size:medium; }

This works pretty well, but a better method is to put this in the standard stylesheet …

html { font-size:medium; }

… and then to put the CSS for IE5 in an IE5-specific stylesheet, using conditional comments, for example …

<!--[if lt IE 6]>
    <link rel="stylesheet" type="text/css" href="style_ie5x.css">
<![endif]-->

… which has the merit of using an IE5-specific file into which other CSS can also be put to get around IE5’s many other defects.

Checking for HTML 5 Support

Object detection can be used to see whether the browser supports various HTML 5 features. Here are JavaScript code fragments for detecting support of the <audio>, <video>, and <canvas> tags:

Check for <audio> Support
if ( window.HTMLAudioElement )
  {
    // <audio> is supported
  }
Check for <video> Support
if ( window.HTMLVideoElement )
  {
    // <video> is supported
  }
Check for <canvas> Support
if ( window.HTMLCanvasElement )
  {
    // <canvas> is supported
  }

 

 Top of Page   Legal Notices