Taming the Lawless Web with Feature Detection

The Lawless Web

Over the past 15 years or so that I’ve been developing websites, I’ve seen various conventions come and go. Common practices arise, thrive for a while, and die. I, myself, spent a great portion of my past hammering out lines of tabular markup (and teaching classrooms to do the same) when working on my layout.

When I wasn’t building complicated table-based layouts littered with colspans, I was double-wrapping elements to iron out the differences between browser-interpretations of the box-model.

Fortunately, these practices have died, and others like them continue to die every day.

The Web Hasn’t Been Completely Tamed

While atrocities like table-based layouts are quickly becoming a thing of the past, other old habits have chosen not to go as peacefully. One that has managed to stick around for some time is user-agent sniffing; the practice of inferring a browser’s abilities by reading an identification token.

var browser = {
     vendor: navigator.userAgent.match(/Chrome|Mozilla|Opera|MSIE/),
    version: navigator.userAgent.match(/\d+/)

if ( browser.vendor == “Mozilla” && browser.version == 5 ) {
    // This matched Chrome, Internet Explorer, and a Playstation!
    // Nailed it.

Since shortly after the dawn of the epoch, developers have been scanning the browser’s navigator.userAgent string for bits and pieces of data. They then couple this with a knowledge of which browsers support which features, and in conclusion make judgements against the user’s browser and machine. Sometimes their conclusions are correct, but the ends don’t justify the means.

Don’t Gamble With Bad Practices

Sniffing a browser’s user agent string is a bit like playing Russian Roulette. Sure, you may have avoided the loaded chamber this time, but if you keep playing the game you will inevitably increase your chances of making a rather nasty mistake.

The navigator.userAgent string is not, and has never been, immutable. It changes. It gets modified. As browsers are released, as new versions are distributed, as browsers are installed on different operating systems, as software is installed in parallel to the browser, and as add-ons are installed within the browser, this string gets modified. So what you sniff today, is not what you will sniff tomorrow.

Have a quick look at a handful of examples for Chrome, Firefox, Opera, and Internet Explorer. Bottom line is that you can’t be sure what you’ll get when you approach the user agent string buffet. As is immediately clear, the above approach would not even match version 12 of Opera consistently with its variations.

So, have I thoroughly convinced you that sniffing is bad? And have you sworn it off as long as you shall live? Let me give you something to replace it; something reliable that will nearly never let you down – feature detection.

Go With What’s Reliable

Feature detection is the practice of giving the browser a test, directly related to the feature you need, and seeing whether it passes or fails. For instance, we can see if the browser supports the <video> tag by quizzing it on whether it places the correct properties on a freshly-created instance of the element. If the browser fails to add the .canPlayType method to the element, we can assume support isn’t there, or isn’t sufficient to be relied upon.

This approach doesn’t require us to make any assumptions. We don’t couple poorly-scavenged data with naive assumptions about which browsers support which feature. Rather, we invite the browser out onto our stage, and we demand it do tricks. If it succeeds, we reward it. If it fails, we ditch it and take another approach.

What Does “Reliable” Look Like?

So how exactly do we quiz the browser? Feature-detections come in all shapes and sizes; some very small and concise, while others can be somewhat lengthy and verbose – it all depends on what feature you’re trying to test for. Working off of our example of checking for <video> support, we could do the following:

if ( document.createElement(“video”).canPlayType ) {
    // Your browser supports the video element
} else {
    // Aw, man. Gotta load Flash.

This approach creates a brand new video element for testing. We then check for the presence of the .canPlayType method. If this property exists, and points to a function, the expression will be truthy and our test will pass. If the browser doesn’t understand what a video element is, there won’t be a .canPlayType method, and this property will instead be undefined – which is falsy.

The Work Has Been Done For You

You could be rightly freaked out about now, thinking about all of the tests you would have to write. You might want to see if localStorage is supported, gradients, video, audio, or geolocation. Who on Earth has time to write all of those tests? Let alone learn the various technologies intimately enough to do so? This is where the community has saved the day.

Modernizr is an open-source library packed full of feature-detection tests authored by dozens of talented developers, and all bundled up in an easy to use solution that you can drop into any new project. You build your custom download of Modernizr, drop it into your project, and then conditionally deliver the best possible experience to all visitors.

Modernizr exposes a global Modernizr object with various properties. You can test for the support of certain features simply by calling properties on the Modernizr object.

if ( Modernizr.video ) {
    // Browser supports the video element

Additionally, it can optionally add classes to your <html> element so your CSS can get in on the action too! This allows you to make conditional adjustments to your document based upon feature support.

html.video {
    /* Woot! Video support. */

html.no-video #custom-video-controls {
    /* Bummer. No video support. */
    display: none;

Building A Better Web

By using feature-detection, and forever turning your back on user agent sniffing, you create a more reliable web that more people on more devices and browsers can enjoy. No longer are you bound to hundreds upon hundreds of user agent strings, or naive assumptions that will come back and bite you in the end. Feature-detection liberates you so that no babysitting of the browser landscape is necessary.

Code for features and APIs. Don’t code for specific browsers. Don’t get caught writing “Best viewed in WebKit” code – build something for everybody. When you focus on features, rather than ever-changing strings that don’t give you much insight, you help make the Internet better for us all. Do it for the children ;)

You’re Not Alone

When you set out to do things the right way, you will undoubtedly run into obstacles and complications. Let’s face it, quality work often times requires more of an investment than the slop that is so often slung out onto the web. The great news though is that you’re not alone – the web is more open than ever before, full of those willing to help you along the way.

Drop in to Stack Overflow to catch up on the various feature detection questions being asked there. Or if you’re trying to get Chrome and Internet Explorer to play nice with one another, post a question on Twitter using the #IEUserAgents hashtag. This notifies an “agent” to come to your aid.

Just be sure to pay it forward. As you learn, and grow, help others to do so as well. Maybe even write a few feature-tests and contribute them back into Modernizr along the way.

2 thoughts on “Taming the Lawless Web with Feature Detection

  1. Stu Cox

    Great description of why feature detection is a good thing there.

    Although I think feature detection *does* still make assumptions. In most cases it’s essentially API detection and there’s no contract that says APIs have to be implemented. Chrome has a poor track record for adding style properties to the DOM with no implementation behind them, for instance.

    So it’s the best we’ve got at the moment, but hopefully we’ll be able to replace feature detection with things like the ‘@supports’ CSS rule in the future.

    1. Jonathan Sampson Post author

      Thanks for the feedback, Stu. The article only provided an explicit example of detecting a method, which isn’t always wise – as you pointed out, it assumes an implementation. One good example I can recall is that of window.postMessage; both Internet Explorer 6 and 7 had an undocumented function entirely different from the modern postMessage method.

Comments are closed.