Search

Rss Posts

Rss Comments

Login

 

Env: Feature Testing

April 19, 2009

As a followup to my last post regarding browser detection, I have decided to release a new standalone and lightweight library that will hopefully put browser sniffing to rest once and for all. In a project I’ve entitled Env (short for environment), you now have the power of knowing exactly what your browser environment is and is not capable of without actually knowing what browser it is. Many of the features and bugs that are often encountered during runtime no longer need to be sniffed out by the user agent, despite the fact that some of the mainstream libraries still insist upon it. In addition to bug and feature testing, the library also supports advanced type detection and event support detection to further enhance your usability, but also separate your dependence on browser sniffing. Goodbye navigator.userAgent and good riddens – you’ve broken my app for the last time!

Support

The bread and butter of the library is of course the capability tests, Env.support is designed to provide a reliable test case for some of the most popular browser features available today that are difficult to detect and/or not well supported amongst the various browsers. Tested features include; canvas, flash, native array conversion, dynamic script eval, mutation events, the box model, and more!

By default, any features that cannot be immediately resolved to a Boolean value are set to false and must prove their support. For instance, the test cases for box model support and CSS fixed positioning support require DOM node injection, this means the DOM needs to be at least partially loaded. Currently, we poll for the body element and once found execute a method to inject the nodes into the DOM and complete the test cases. However due to inconsistent and unreliable results and conflicts in execution order with the load event listener of the window and onDOMready-related methods, Env.onReady is provided to manually execute the method and finish any DOM related capability tests.

It should be noted that for the most part, I intentionally disregarded any feature tests that can otherwise be detected with simple object or typeof inference. Don’t expect objects or methods such as addEventListener or querySelectorAll to be supported, this suite is exclusive to capabilities tests that are typically difficult to detect.

Bugs

Although they are few and relatively easy to avoid, being able to harness the native capabilities of the browsers that do not possess a flawed implementation or bug versus those that do can work wonders in terms of ease and performance. For example, if you ever wanted to know if your browser supports DOM injection via innerHTML for table or select elements – now you do, and can fully exploit the faster technique for each browser. In addition to that, bugs pertaining to getElementById, getElementsByTagName, querySelectorAll, and getElementsByClassName are also all supported.

Type Detection

As a means of determining exactly what a specific object is at any given time, Env supports advanced type detection for a wide variety of objects including arrays, functions, elements, numbers, and strings, just to name a few. However, aside from the more rudimentary objects, there is also support for the much more unique objects such as nodelists, events, windows, and native methods. This allows for a definitive and reliable test for almost all objects you will encounter within the JavaScript environment:

Env.isArray([]); // true
Env.isNodeList(element.childNodes); // true

In addition to that, Env.isEmpty is a very useful method that can be used to test if a variable holds a value. Inspired by PHP’s empty function, Env.isEmpty can be tested against strings, numbers, booleans, arrays, nodelists, objects, and null and undefined values:

Env.isEmpty({}); // true
Env.isEmpty("   "); // true

Of course, what would be a feature testing library without the three methods made famous by Peter Michaux’s classic article on feature detection. Env.isHostMethod, Env.isHostCollection, and Env.isHostObject are three helpful methods designed to determine if a member of a host is what you think it is:

Env.isHostObject(element, 'firstChild'); // true
Env.isHostMethod(element, 'appendChild'); // true
Env.isHostCollection(element, 'childNodes'); // true

Finally, what might be the most innovative method to the library is Env.type which is quite simply a much greater expansion of the typeof operator. Now you have the ability of knowing exactly what any object is by simply supplying it to this method and receiving a string representation of its type:

Env.type([1, 2, 3]); // array
Env.type(element.childNodes); // nodelist

Ultimately, if no match is found for the object provided, the method will default to the typeof operator.

Event Support

One of the most difficult aspects to feature testing is determining support for event listeners of specific elements. Whether it be mutation events, contextmenu, or IE’s much desired mousenter and mouseleave; there is a wealth of varying event support amongst the respective browsers with no standard means of detecting support for them, which ultimate leads many developers to browser sniffing.

Env.support.event aims to fix this by providing a reliable test case suited for all modern day browsers. Testing for event support is simple:

Env.support.event('click');
Env.support.event('DOMNodeInserted');

By default, all event types are tested against a standard div element created within the context of a method call. As an optional second parameter, you can specify element inference by providing either a tag name (which will be used to create the element), or a raw element itself:

Env.support.event('abort', 'img');
Env.support.event('load', window);

Currently, the only mutation events supported are DOMNodeInserted, DOMNodeRemoved, DOMAttrModified, and DOMSubtreeModified, none of which are actually tested for within the context of a Env.support.event method call, but rather the context of Env on initial load and are available as part of the Env.support namespace (Env.support.DOMNodeInserted).

Much of the credit for the techniques utilized within this method goes to Juriy Zaytsev and his recent post concerning event support detection along with the talented developers involved in the discussion over at Ajaxian for all the information and inspiration, thanks!

CSS Style Support

With all the revolutionary things we’re seeing with CSS these days including transforms, transitions, rounded-corners, and the truly awesome effects such as gradients and reflectors coming out of webkit, why the hell are we still stuck looking for support for opacity, min-width, or fixed positioning. Wouldn’t it be nice to harness these latest CSS styles without resorting to browser detection or some nasty hacks?

Well wait no longer,  if you’re simply looking to determine support for a CSS property, just call the method and get your answer – no browser sniffing, no elaborate test case, no false positives, nothing but a definitive result, what else could you ask for?

Env.support.style('min-width');
Env.support.style('-moz-border-radius-topleft');

As if that wasn’t enough, you can also determine support for a specific style’s assignable values, such as fixed positioning. By simply providing the value as an optional second argument, you now have a 100% reliable test case for not only the CSS styles themselves but their supported values:

Env.support.style('position', 'fixed');
Env.support.style('-webkit-transform', '2ex');

Now were golden, so go start using those CSS styles you’ve longed to try! For a detailed description of how this method works, see the post here; http://ryanmorr.com/archives/detecting-browser-css-style-support.

Example

To see a simple example of all the tests available within the Env.support and Env.bugs namespaces, try clicking here and see how your browser stacks up!

Summary

My greatest hope here that the community of developers will realize (if they haven’t already) where and when feature detection can and should be utilized. It has always been my contention that browser detection has no place within a script, not now and not ever. Despite some long dilemmas and unique workarounds, I for one am still waiting for that immense problem that can only be solved via browser detection – in three years of developing in Javascript (which I know isn’t very long) I haven’t encountered anything to that effect yet, and Env will help to ensure that I never do. So this is it people, no more excuses, defending browser detection is like defending gas-powered cars – it may be faster, but the world is moving to a much cleaner and environmentally safer approach, or at least I hope they are!

Please report any bugs or false positives you may experience, and if you have any suggestions of bugs or features to test for in future versions, I would love to hear them.

Download

8 Comments

  1. Apr 25 at 3:05 AM

    Putting browser sniffing to rest once and for all? That is totally an overstatement.

    In my opinion, there are things that feature detection does not do much good like determining whether innerHTML or createElement is faster in a particular browser (test this in Chrome or Safari 4 vs other browsers). There’s where I think browser detection comes to place.

    What I am saying is use feature detection to determine the Javascript API of that particular browser but use browser detection when multiple APIs are supported in the browser and you want to choose the best one for the job.

  2. Ryan Morr
    Apr 25 at 4:00 PM

    @Kean Tan

    If you can use feature testing to detect the multiple APIs, even the speed of them, than why not use it?

    Using your example, you can easily detect the speed of the DOM vs innerHTML (although it is a widely known fact that innerHTML is faster in all browsers). Just look here http://www.quirksmode.org/dom/innerhtml.html. By performing a few quick test cases and than measuring the time to complete them, you can get a reliable measure of which method offers the highest performance.

  3. Apr 26 at 12:14 AM

    Yes, it’s widely known that innerHTML is faster than DOM, and that’s precisely why I put this example out with specific mention of Chrome and Safari 4.

    If you are testing the different APIs, wouldn’t you say that only a huge enough iterations will guarantee a good/reliable result?? The time to test the speed of APIs will just exceed just running let’s say 10 iterations of the function for normal use in the script/library.

    If your main concern is browser detect breaking the script, I dare say that by using a combination of feature detect + browser detect, you can actually create a script that runs fast and also won’t break if it encounters an unknown browser as it will fallback to feature detection.

    Here’s a proof of concept. Feature detect does not work correctly.

    //bad lazy code, don’t follow
    var supportInnerHTML = !!el.innerHTML;
    var supportCreateEl = !!document.createElement;

    if (isSafari4 && supportInnerHTML) {
    // do something
    }
    else if (supportInnerHTML) {
    // other browsers that support innerHTML
    }
    else if (supportCreateEl) {
    // other browsers that support DOM method
    }

    Yes, no libraries have done that and I think both camp (feature detect vs browser detect) are arguing at the wrong points. I do advocate a careful mixture of them to future-proof javascript.

  4. Ryan Morr
    Apr 26 at 9:08 PM

    @Kean Tan

    My problem with browser detection is when your code contains a certain amount of reliance on it, which will break your code sooner or later. The example you provided doesn’t do this because it offers fallbacks.

    It’s a technique I haven’t seen before, and as you said, libraries don’t utilize it either. I suppose all things considering its OK, I may have to look into it further and post a new article about it.

    Thanks for the information!

  5. David Mark
    Jun 27 at 11:05 PM

    Not too bad, but then I’m biased (several of these are my ideas or ones I’ve promoted for years in CLJ.)

    Your type checking won’t work cross-frame in IE, same as the instanceof checking it replaces. You have to fall back to “duck typing.”

  6. Jun 30 at 3:58 PM

    Ohh.. the great David Mark. Too bad you won’t see me participate in cljs.

  7. David Mark
    Jul 08 at 5:32 PM

    Kean Tan, why should I care?

  8. Jul 31 at 5:54 PM

    One less person to help you boost your ego by arguing with you.

Post a comment