Developing cross-browser and cross-platform pages

An important practice when doing cross-browser, cross-platform pages and DHTML development involves the ability to determine the capabilities of the browser which loads your web page. As a web author, you understandably want to avoid script errors and page layout problems and you may want to ensure your scripts reach as wide an audience as possible. There are 2 known approaches for such goals: the browser identification approach (also known as userAgent string detection and often referred as "browser sniffing") and the Object/Feature support detection approach. The browser identification approach is now known to be complicated, unreliable and difficult to maintain.

Browser identification approach (aka "browser sniffing"): not best, not reliable approach

This approach, still commonly used nowadays, attempts to identify the browser and makes the web author at design time decide what that implies in terms of capabilities of the visiting browser. Such approach is fraught with problems and difficulties. It requires from the web author to have knowledge of the capabilities of all current browsers that may visit the page and then to code appropriately for these. It requires from the web author to make assumptions about what will happen with future browsers or to decide to provide future browsers a safe fallback service. It assumes that web authors are able to correctly identify browsers and browser versions in the first place... which is far from being a reliable and easy task to achieve.

The browser identification approach relies on functions that check the browser type string value and browser version string value and that search for certains characters or sub-strings in the navigator.userAgent property string. Once "detected", the web author then uses different functions (aka code branching) or points the user to different pages (aka site branching) or web content. Site branching can be particularly dangerous as people may enter a page through a link, bookmark, search engine or cache with a "wrong" browser.

Let's see a basic example of this approach.

// BAD SAMPLE
if (navigator.appVersion.charAt(0) = "8") {
  if (navigator.appName = "Netscape") {
    isNS8 = true;
    alert("Netscape 8");
  };
} else if (navigator.appVersion.indexOf("MSIE") != -1) {
  isIE = true;
  alert("Internet Explorer");
};

While this kind of checking in the above code can work in a crude sense, sharp readers may wonder what happens when Internet Explorer 8 is released or when an Opera 8.x user visits the page or even when an user with any non-Netscape browser starting with a "8" character in the appVersion string visits that page. As new browsers are released, it becomes necessary to make updates to such code which attempts to narrow down the browser and browser version and to make the appropriate switches.

Another major problem with this approach is that the browser identity can be "spoofed" because, in many modern browsers, the navigator.appVersion and navigator.userAgent string properties are user configurable strings. For example,

  • Firefox 1.x users and users of any/all Mozilla-based browsers can customize the "general.useragent.*" string properties to any value.
  • Opera 6+ allows users to set the browser identification string via a menu
  • Internet Explorer uses the Windows registry
  • Safari, Konqueror and Icab browsers can mask their browser identity under Internet Explorer or Netscape labels

A user or browser distributor can put what they want in the navigator.userAgent string and this may trick your code into executing a "wrong" block of code. Moreover, there are many cases where even the accurately-identified browser does not perform as it is reputed/expected to.

So if "browser sniffing" is unreliable and difficult, then how do you code safely for different browsers and different browser versions? ...

Using Object/Feature support detection approach: best and overall most reliable

When you use object/feature support detection, you only implement those features whose support you have first tested and verified on the visiting browser. This method has the advantage of not requiring you to test for anything except whether the particular features you code are supported in the visiting browser.

Let's see a basic, simple object support detection example.

function hideElement(id_attribute_value) {
  if (document.getElementById &&
      document.getElementById(id_attribute_value) &&
      document.getElementById(id_attribute_value).style
  ) {
    document.getElementById(id_attribute_value).style.visibility = "hidden";
  };
}

// example:
// <button type="button" onclick="hideElement('d1');">hide div</button>
// <div id="d1">Some text</div>

These repeated calls to document.getElementById are not the most efficient way to check for the existence of particular objects or features in the browser's DOM implementation, but they serve to illustrate clearly how object support detection works.

The top-level if clause looks to see if there is support for a method called getElementById on the document object, which is the one of the most basic levels of support for the DOM in a browser. If there is, the code sees if document.getElementById(id_attribute_value) returns a reference to an existing element, which it then checks for a style object. If the style object exists on the element, then it sets that object's visibility property. The browser will not error if you set this unimplemented property, so you do not need to check that the visiblity property itself exists.

So, instead of needing to know which browsers and browser versions support a particular DOM method (or DOM attribute or DOM feature), you can verify the support for that particular method in the visiting browser. With this approach, you ensure that all browsers -- including future releases and browsers whose userAgent strings you do not know about -- will continue working with your code.

More on object/feature support detection:

Document Tags and Contributors

Contributors to this page: ethertank, kohei.yoshino, GT, Kohei, Mgjbot
Last updated by: ethertank,