Coping with browser differences

Last updated: 08.05.2002


"Now I see what you have been doing with your time - a new look site. No more browser grey! Except for that big box which blocked out half the homepage saying something about "javascript error". Nina has one of those on her homepage too." -- Marie N.

  1. Introduction
  2. Preparing for the worst
  3. Language, browser or feature sensing?
  4. Some known bugs

Introduction

One of the joys of the Web is that every browser out there does things in a slightly different way. If you thought HTML incompatibilities between browsers were bad, welcome to JavaScript. Half the browsers don't support half the functionality you want, and to make life still more interesting, early releases are typically crawling with bizarre and unpredictable bugs.

This note does not pretend to be a definitive guide to writing compatible JavaScripts. It is simply a brief description of some of the more irritating bugs I've come across, and some possible workarounds and tips which may help your scripts to run on a wider range of browsers.

One area this note doesn't address is DHTML. This is a minefield that I'm not going to enter until the standard stabilises. Microsoft and Netscape, in their collective wisdom, have come up with two distinct 'standards' which not only break lamentably on their rival's browsers, but on earlier versions of their own as well. It is theoretically possible to work around the incompatibilities (masochists who are determined to be the first one on the block to use DHTML may want to look at Macromedia's DHTML Zone for some useful tips), but only at the cost of considerable effort and of making your HTML source nearly unmaintainable. As far as I'm concerned, the technology is not yet mature enough for use.


Preparing for the worst

When you add a script to your pages, you should probably start by assuming that it will turn out to be incompatible with at least one widely-used browser. Assume also that many of your visitors will have switched off JavaScript either for security reasons or simply as a way of avoiding pop-up windows and other nuisances. Accordingly:

  1. Try to ensure that if your scripts fail, they fail silently. After broken links and images, nothing makes your site look sloppier and more amateurish than a shower of error dialogs.
  2. Try to ensure that even if the script can't run the user has an alternative way of getting access to the functionality provided by the script. If you make navigation within your site dependent on JavaScript, you'll block the site to everyone who either doesn't have a JavaScript browser, or has disabled it.
  3. Don't rely on the script working. For instance, one useful application of JavaScript is to pre-validate forms filled out by the user before the data is sent to the server. But your server-side script must still be able to handle bad data sensibly, or the first user who has turned off JavaScript will crash your CGI script or corrupt your database.

Language, browser or feature sensing?

Different browsers support different subsets of the JavaScript standard. If you call on a feature that isn't supported, your script will either fail to work or it will throw up an error dialog. To avoid this, you need to write your script in such a way that it doesn't try to do anything unsafe. Here are some ways you might go about that.

Language sensing

With Navigator 3, Netscape introduced a collection of new features under the name of JavaScript 1.1. In theory, if you want to use the new features, you can ensure that your code doesn't mistakenly execute on an older or incompatible browser by specifying "JavaScript 1.1" as the value of the LANGUAGE attribute of the <SCRIPT> tag. A script that begins with:

<SCRIPT LANGUAGE="JavaScript 1.1">

will only be executed on browsers that have the necessary features. Older browsers will simply ignore it.

Sometimes it's not enough for the script to be simply ignored. Suppose that you have a function which is defined in a <SCRIPT> block in the <HEAD> of your document, and called by an onMouseOver event handler when the user moves the mouse over some object on the page. On an older browser that doesn't execute the <SCRIPT> block, moving the mouse over the object will cause an immediate crash with an 'undefined function' error. This is not what you want.

To protect yourself against this, you can use multiple <SCRIPT> blocks. The first one should be specified simply as "JavaScript" and should contain a harmless definition of the function which does nothing. The next one is specified as "JavaScript 1.1", and contains a redefinition of the function using whatever new features you wish to use. In this way, older browsers will still find a function definition to call, but calling it won't do any damage, while newer browsers will have full access to the features of the new language.

In theory, language sensing works fine. In practice, it's rather less useful, for two main reasons. The first is that browsers contain bugs. Even a browser that declares itself to be compatible with JavaScript 1.1 may fail to implement it properly. The second reason is that, needless to say, Microsoft have come along and broken the feature for everyone. Internet Explorer 4 doesn't recognise JavaScript 1.1 (although it supports some JavaScript 1.1 features). Moreover, when it sees a <SCRIPT> block with a LANGUAGE attribute it doesn't recognise, it doesn't simply skip it; instead, it throws up an alert informing the user that it's found something it doesn't recognise, and asking them what they want to do about it. So much for language sensing.

Browser sensing

An alternative approach is to try to detect the kind of browser that's accessing your pages, and write your code accordingly. You can get information about the browser via the navigator object, which contains such useful properties as appVersion and appCodeName. Many scripts examine these properties to identify the browser and then adapt their behaviour accordingly.

Unfortunately, browser sensing also has problems. You have to make a choice about whether you're going to perform an inclusive or an exclusive test. An inclusive test says "... if the browser is one of these, then it's safe to call this code ...". An exclusive test says "... if the browser is one of these, don't ever call this code ...". Inclusive tests are safer; your code only runs on browsers that are known to be good. But a future browser which might be perfectly capable of running the code without problems gets shut out, simply because it's not on the known list of capable browsers. By contrast, exclusive tests are dangerous; you may not unjustly shut out capable browsers, but if some browser you've never heard of but which happens to be incompatible with your code comes along, you'll get an error.

A further problem is that some browsers pass themselves off as other browsers. Microsoft's Internet Explorer 4.0 claims to be "Mozilla/4.0" ('Mozilla' is the code name for Netscape's Navigator) but without implementing all of Navigator's features. If you invoke an unsupported Navigator-specific feature simply because you believed Explorer when it told you it was really Mozilla, you're going to get a crash. Thanks again, Microsoft, and so much for browser sensing.

Feature sensing

Fortunately, there's one further approach, which may well be the best. This is to test not for a language (which the browser may not recognise or support properly) or a browser (which may not be one you've ever heard of, or which may be lying to you about its identity) but to test for the specific feature that you're trying to use.

To give an example, Navigator 3 introduced the random method to the Math object. If you call Math.random on Navigator 2, you get an error. But JavaScript allows you to test for a function to see if it's implemented before you call it. The following snippet shows how this might work:

        if (Math.random) {
                ... call Math.random() with a clear conscience ...
        }
        else {
                ... fake up our own pseudo-random function ...
        }

In the same way, you can test for properties. For instance, it's a good idea to test for the existence of documentimages before you start manipulating the documentimages[] array. In theory, specifying the language as JavaScript 1.1 should have been enought (documentimages was introduced in JavaScript 1.1) but as we've seen, that won't always work.

In conclusion, feature sensing is probably the best approach to take. It's proof against the problems discussed above with language and browser sensing, and it's actually directly testing the thing you're interested in, rather than some vague associated property.

A word about <NOSCRIPT>

Recent browsers provide the <NOSCRIPT> tag to allow you to specify a chunk of HTML - which can be arbitrarily complex - to be displayed if the browser doesn't support JavaScript, or if JavaScript has been disabled. One use for this is to write a condescending message telling the user to get a JavaScript-capable browser. In some cases, however, you can make better use of it by using it to provide an alternative to whatever the JavaScript was supposed to do. For example, you might have chosen to use a navigation menu on your page because space is limited, and a pop-up menu takes less space than a list of links. But if the user doesn't have JavaScript, they can end up being shut out of your site because they've got no way to navigate around it. With <NOSCRIPT> you can output a list of links in place of the navigation menu, so that even users without JavaScript have somewhere to go.

Unfortunately, there's a catch. Navigator 2 doesn't recognise the <NOSCRIPT> tag. Even if JavaScript is enabled, it will still display the contents of the <NOSCRIPT> block. This means that you need to allow for the possibility that both whatever is produced by your JavaScript and the contents of the <NOSCRIPT> block will be shown.

Some known bugs

Defensive coding to make sure that your code doesn't break when you run it on an inappropriate platform is only half the battle. There are also some quite bizarre JavaScript bugs in circulation. This isn't a complete list, but it includes some that I've encountered, plus some suggested workarounds.

Navigator 2 and 3: 'document.writeln()' in tables

If you use tables to help lay out your pages, beware of using document.writeln() to generate HTML code dynamically within the context of a table. Early versions of Navigator (v2 and v3) don't like it. Navigator 2 will typically throw an error, while Navigator 3 won't cause an error, but it won't run the script either and, moreover, it will output the text of your script in the Web page for all to see. Ugly.

The only known workaround is not to use document.writeln() in a table context. You may occasionally escape the bug by sheer good fortune (it's a little hard to tell when it does and doesn't happen) but it's unwise to rely on it.

Navigator 3: image swapping with 'documentimages'

Navigator 3 introduced the documentimages property to allow access to all the images contained in a document. Netscape's own documentation explains how to use this array in order to implement image swapping - when one image is replaced by another in response to a mouse-click or other event. On the face of it, this should be a good way to implement rollovers.

Unfortunately, there are once again problems where tables are concerned. A fragment of code such as:

        documentimages[0].src = someImage.src;

works acceptably in a non-table context, but if you use it to swap an image inside a table, there's a high risk that the replacement image will be drawn in the wrong place on the page.

The workaround is to name your images and refer to them by name. A form such as:

        document.myImage.src = someImage.src;

where "myImage" is the name of the image will work correctly. It also makes for clearer code and means that you don't have to recalculate all your image indices if you add a new image to the page.


This note and the list of bugs is probably not exhaustive or authoritative. If you have other debugging tips which you think are worth mentioning here, or there's something here that you disagree with or don't understand, please mail angus@pobox.com.

[raingod:resources:javascript] -- [up][links][home]