Categories
Mozilla Web Development

Browser Detection In JavaScript Libraries

I was curious what browser detection in various JS libraries look like. While we always try to avoid doing browser detection, it’s sometimes a necessary evil. Here’s what I found.

jQuery

jQuery looks something like this in syntax:

if($.browser.msie) {
  // do something IE specific
}

Here’s how it’s actually implemented:

browser: {
  version: (/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/.exec(userAgent) || [0,‘0’])[1],
  safari: /webkit/.test( userAgent ),
  opera: /opera/.test( userAgent ),
  msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
  mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
}

jQuery takes a pretty simple user-agent parsing approach. All jQuery supports (though deprecated officially) is the rendering engine, and version. Noteworthy is that “safari” is used as opposed to “webkit“. This is important in the case of Google Chrome etc. which you would detect using the poorly named “safari“. No support for detecting mobile browsers, though that could be added pretty easily.

jQuery is pretty bare bones, but the tightest of the implementations. It’s also the only one I’m aware of that has deprecated this functionality. The one thing I occasionally miss is OS detection (helpful when Linux lacks a few things like good Flash support). I supplement it with:

var jQbrowser = navigator.userAgent.toLowerCase();
jQuery.os = {
  mac: /mac/.test(jQbrowser),
  win: /win/.test(jQbrowser),
  linux: /linux/.test(jQbrowser)
};

Then use it like this:

if($.os.linux) {
  // do something for Linux
}

MooTools

MooTools syntax looks something like this:

if(Browser.Engine.trident) {
  // do something IE specific
}

It also supports a few other attributes such as Platform, Browser.Features.xpath (XPath supported?) Browser.Features.xhr (xmlHttpRequest supported?), and Browser.Plugins.Flash.version (Flash version).

Here’s how the core of it is implemented (I’m omitting the extras):

var Browser = $merge({
 
  Engine: {name: ‘unknown’, version: 0},
 
  Platform: {name: (window.orientation != undefined) ? ‘ipod’ : (navigator.platform.match(/mac|win|linux/i) || [‘other’])[0].toLowerCase()},
 
  Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
 
  Plugins: {},
 
  Engines: {
 
    presto: function(){
      return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
    },
 
    trident: function(){
      return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
    },
 
    webkit: function(){
      return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
    },
 
    gecko: function(){
      return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
    }
 
  }
 
}, Browser || {});

MooTools choose feature detection rather than User Agent sniffing. This approach takes care of the problem with spoofed request headers, but is failtastic when using an unsupported method. MooTools users need to upgrade ASAP. This will be problematic with the upcoming Firefox 3.6 release.

Prototype.js

Prototype’s syntax looks something like this:

if(Prototype.Browser.IE){
  // do something IE specific
}

Here is what the implementation looks like:

Browser: (function(){
    var ua = navigator.userAgent;
    // Opera (at least) 8.x+ has "Opera" as a [[Class]] of `window.opera`
    // This is a safer inference than plain boolean type conversion of `window.opera`
    var isOpera = Object.prototype.toString.call(window.opera) == ‘[object Opera]’;
    return {
      IE: !!window.attachEvent && !isOpera,
      Opera: isOpera,
      WebKit: ua.indexOf(‘AppleWebKit/’) > 1,
      Gecko: ua.indexOf(‘Gecko’) > 1 && ua.indexOf(‘KHTML’) === 1,
      MobileSafari: /Apple.*Mobile.*Safari/.test(ua)
    }
  })(),

It’s pretty similar (though not identical) to jQuery with the most notable exception being the addition of MobileSafari and object detection for IE.

YUI

The syntax in the YUI world is like this:

if (Y.UA.ie > 0) {
  // do something IE specific
}

The implementation is the longest I’ve seen:

ua = nav && nav.userAgent,
if (ua) {
 
  if ((/windows|win32/i).test(ua)) {
    o.os = ‘windows’;
  } else if ((/macintosh/i).test(ua)) {
    o.os = ‘macintosh’;
  }
 
  // Modern KHTML browsers should qualify as Safari X-Grade
  if ((/KHTML/).test(ua)) {
    o.webkit=1;
  }
  // Modern WebKit browsers are at least X-Grade
  m=ua.match(/AppleWebKit\/([^\s]*)/);
  if (m&&m[1]) {
    o.webkit=numberfy(m[1]);
 
    // Mobile browser check
    if (/ Mobile\//.test(ua)) {
      o.mobile = "Apple"; // iPhone or iPod Touch
    } else {
      m=ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);
      if (m) {
        o.mobile = m[0]; // Nokia N-series, Android, webOS, ex: NokiaN95
      }
    }
 
    m=ua.match(/AdobeAIR\/([^\s]*)/);
    if (m) {
      o.air = m[0]; // Adobe AIR 1.0 or better
    }
 
  }
 
  if (!o.webkit) { // not webkit
    // @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
    m=ua.match(/Opera[\s\/]([^\s]*)/);
    if (m&&m[1]) {
      o.opera=numberfy(m[1]);
      m=ua.match(/Opera Mini[^;]*/);
      if (m) {
        o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
      }
    } else { // not opera or webkit
      m=ua.match(/MSIE\s([^;]*)/);
      if (m&&m[1]) {
        o.ie=numberfy(m[1]);
      } else { // not opera, webkit, or ie
        m=ua.match(/Gecko\/([^\s]*)/);
        if (m) {
          o.gecko=1; // Gecko detected, look for revision
          m=ua.match(/rv:([^\s\)]*)/);
          if (m&&m[1]) {
            o.gecko=numberfy(m[1]);
          }
        }
      }
    }
  }
}

This seems a little excessive, especially for UA parsing, though the detection of AdobeAIR and Opera Mini is a nice touch. The code being well commented is nice though.

Conclusion

So there you have it. Unlike many of those websites that don’t use a library, these JS code bases don’t rely on document.all and/or window.xmlHttpRequest[1] to do it all.

1. For those wondering xhr is to tell IE7+ from previous IE versions.

14 replies on “Browser Detection In JavaScript Libraries”

Flash support is hardly the best reason to want to do OS detection, since Linux might have Flash, and Windows might not. That’s the kind of thing that just annoys people, wondering why someone has seen fit to deny them the ability to see the same content as a Windows user.

Same story as for browser sniffing – much better to enable/disable features based on actual capabilities, rather than on what you think they might be capable of. The only good reason I can think of to do OS detection is for a site offering binary downloads, to help the user find the correct version…

I agree with Simon on all points. That these libraries include browser detection at all seems like a bug, except insofar as it internally lets them work around known bugs with no other possible workaround, and even then they shouldn’t expose that outside the library. Furthermore, doing feature detection for the purpose of browser detection, with the expectation that people will then use the browser detection results, seems much worse.

@Simon: Totally not true. AFAIK no Flash implementation on Linux is really a top notch experience (Windows is solid, Mac is now tolerable). Performance is still not very good (rather serve an other format video for Linux users). Also have seen issues with copy/paste via those flash/js solutions.

It’s a much better solution to ship something that works than to tell people “use Windows if you want it to work” as you seem to suggest.

@Robert – again, wrong approach. Firstly, you’re making a decision that Flash will never be good enough on Linux, and the user should never use it. If in six months a new flash player comes out that works properly, well, too bad – you’ve decided Linux users can’t use it.

Second, it there’s something else that works better for a particular OS, is there a reason you’re not using it anywhere else? HTML5 video, say – if that’s better than Flash for Linux users, is it also better than Flash for Windows and Mac users? And if so, we’re back to capability detection again – if something supports native video, use that, else fall back to Flash…

@Simon: Not exactly. As far as I know you can’t do capability detection to see if Flash performance. Knowing that nothing less than Flash 10 is usable on Linux for the vast majority of users, what is an alternate way to provide:

1. Experience to Flash 8+ users on all platforms, and Flash 10+ on Linux.
2. Experience to non-flash and Linux users with Flash 9 or below.

Another thing you can’t detect correctly via feature detection is font handling. Linux and Mac (especially Mac OS 10.4 with default anti-aliasing) fonts at sizes < 8px don't appear as readable as they do on Windows XP/Vista/7. The best solution available other than change the design (not always possible depending on designer) is to up the font size per platform.

For the jquery os detection, wouldn’t using navigator.platform make more sense? Or are you explicitly allowing the user to override it?

(Also, Flash runs much better on my Linux machine compared to my Windows one. Probably because it is two years newer and has ten times the RAM, and a standalone video card. Just means you really need to let the user tell you things.)

Great job! This is probably the best explanation of the various JS frameworks I’ve seen with regards to this feature (browser sniffing). I would have loved to see how ExtJS fits into the mix… maybe you could add that with a follow-up post.

The one thing I consistently don’t like about YUI is their naming conventions. The namespace titles and local variables are named so generically that it’s often difficult to follow the thought process. ExtJS is also sometimes guilty of that (at least with the local variables), though I suppose it could be a result of Yahoo/Ext using JavaScript code compression.

Lastly, I must say that I hate the fact that OS/Browser detection is even used. I won’t go as far as to say that there’s never a reason to use it, but generally speaking a good web developer will do everything humanly possible to avoid it. It’s like using the NOSCRIPT tags or hacking CSS for IE6 – you need to know who your users are and build a tool that supports their hardware (not the other way around).

@Robert – if the problem is people running older, poorly performing versions of Flash, I can’t see that you can do much about that. But what’s your fallback in the second case, for users without Flash, or without a good enough version? If the fallback is a good option (e.g HTML5 video), why is it not the first choice if supported? And if it’s not a good option, why force Linux users onto an inferior choice when they *might* have a perfectly workable Flash?

Seriously, if a good enough Flash plugin is available on Linux, why not just assume that everyone has it? If 10 is that much better than previous versions, surely anyone who actually cares about Flash will have it?

I have a calendar popup library that I use to add a javascript behavior to input elements of type calendar. However, Opera already has implemented such behavior, so I skip attaching it for Opera (the native functionality of the browser being better than the javascript anyway).

As different browsers implement different sections of HTML 5 at different times this is going to become common again I’m afraid if you want to add features beyond baseline.

have a look at the tiny-browser-Plugin for tinyJS. This is the smallest yet most open client detection I have ever done. Since the data in the useragent string is mostly generic, the generic approach works best – with a few minor tricks to allow for the less generic useragents.

You actually make it appear really easy along with your presentation but I in finding this matter to be actually something that I think I’d by no means understand. It seems too complex and very large for me. I am looking forward for your next publish, I will attempt to get the hang of it!

Leave a Reply

Your email address will not be published. Required fields are marked *