Making your web page compatible with Firefox

The following is a list of things to review so that a web page is compatible, not only with Firefox, but with any browser which implements the latest standards, including of course Mozilla derivates as Camino, Galeon, Epiphany, etc.

Some might say that making a page compatible requires double the effort in making it, and that is not worth the trouble since everybody uses explorer. In this article I want to show that this is not true. It's roughly the same effort to make a page that is compatible with Firefox as one that that is not.

This text does not intend to tell you how to make pages compatible with ancient browsers (as Netscape 4 or Explorer 4) That would certainly be a much larger task. This article aims to guide you about supporting browsers that are very similar in their support of things like dynamic HTML. It's not necessary to have a page for each kind of browser, it's better to share the code while taking into account the small differences. The details enumerated here will be used for browsers like Firefox, Mozilla, Netscape 6+, Konqueror, Opera, Apple's Safari, etc.. Some of the properties explained which are equivalent to those of Explorer don't belong to any standard, these cases will be noted.

Sripting. The great majority of the compatibility problems falls in this category, specially in "AJAX" applications. And they are not generally problems with the JavaScript language itself, but with the fact that the browsers represents the page in objects differently. A different API, sortof. This API is called the DOM (although in Microsoft docs you should search for DHTML instead).

Let's now see which are the most common problems...

Detections

A common way of coping with different browser versions is to detect the one which is being used. This is generally a bad idea. It's much more advisable, maintainable and easy just to detect each desired feature when you make use of it. That way you will automatically support all browsers which implement that feature, and will narrow the posibility of leaving a browser unsupported. JavaScript is a very dynamic language that allows one to ask if an object has certain method or property, so it's simple to detect things correctly. In order to verify the existence of a method or property in an object simply put the expression in a boolean context. For example, to ask if the property prop exists in the object o one can write: if(o.prop) {.

Another common error is to check if the browser has something, and if it doesn't to assume that it has another thing. The typical case involves document.layers (the obsolete and frightful layers API included in Netscape 4). Some badly coded pages assume that if a browser does not have document.all it's safe to assume that it supports the other interface. This cannot be relied upon. It's better to directly test what one is going to use.

Locating objects

Explorer 4 introduced the ability to dynamically modify the underlying page structure, and some people called that dynamic HTML. The page structure modifications are done from JavaScript code. As in the browser's JavaScript API there were already many things hanging from document (such as document.forms document.images etc), someone at Microsoft said:

― Where do we put ALL of the others?

― I've got it!: In document.all

Later, the W3C created a function for that: document.getElementById(id). It's supported in Explorer from release 5. What do we do with IE4 users? You should not care! Nobody uses it anymore, as Explorer 5 or better have been forced to Windows users since Windows 98 "Second Edition". Current numbers for IE4 are below %0.1.

Now the thing is just to replace in all places this way: Where you see document.all.theMenu.style.color="black" change it to document.getElementById("theMenu").style.color="black".

Parentheses or brackets

In the JavaScript representation of a web page there are several arrays. And arrays are meant to be accessed with []. Microsoft has the concept of collections in his scripting languages, which is a variation of the same subject, but these collections are accessed with (). They had the terrible idea to transfer this to its implementation of JavaScript, thus document.forms(0) works in Explorer, but in no other browser. One must always use [], i.e.: documents.forms[0].field.value and document.images[0].

Events

Accessing the event object

At first, handling an event was just to put just a little bit of code in an onclick attribute somewhere. Netscape extended this so that one could within that small piece of code, access an event variable. In that variable one could find out most interesting details as what key would have been pressed and other things.

When Microsoft entered the scene they thought:

― In which object should this event variable be so that it's available in the code snippet?

― Let's put it in window, as everything which is in the omnipresent window object is always in scope.

And thus the ugly window.event was born, which would come to be some kind of global variable.

This window.event, in addition to being a terrible idea, is not part of any standard (luckily). Therefore the only safe place from which it's safe to access a variable named "event" is from the onevent attribute (e.g.: onMouseOver="..."). In that place there's a variable named event. If a function is called, event should be passed as a parameter. Example: <a onmouseover="mouseOver(event) "...

A somewhat more advanced use of the events consists of assigning functions to certain properties in the objects you'd like to monitor, e.g.: findObject("myLink").onmouseover=myFunction;. In this case the mechanism explained in the previous paragraph is not applied. In their place, the the DOM event standards compatible browsers will pass the event as a parameter to the myFunction function, which could be defined the following way:

function myFunction(e)
{
	// let's not forget poor Explorer
	// which doesn't pass the event as a parameter
	if(!e)
		e=window.event;

	// rest of the code
	// ...
}

In Explorer, registering for events can be done by using the attachEvent function. In Firefox, the same thing is accomplished with addEventListener (it takes an extra boolean parameter, just put false there). Note that event names in the DOM standard don't begin with "on", so you will need to remove that. BTW, why don't you just assign the function to the proper element.onevent property? =)

Using the event object

The previous section covered the mechanisms necessary to get the object which represents an event. But the issue does not finish there, since the properties defined in Explorer are not the same ones that the properties that the standard dictates (i.e. those that Firefox implements).

Differences in the event object properties/methods
In Explorer Description In Firefox (DOM standard)
srcElementThe element which fired the event. target, but in Firefox the nodes of type text can also fire events, so to keep things working you'll need to climb up the tree until you find a element's (tag's) node:
var node = e.target;
while(node.nodeType != node.ELEMENT_NODE)
	node = node.parentNode;
fromElementThe element in which the mouse was before. target if the event is onmouseout, relatedTarget if the event is to onmouseover.
toElement The element to which the mouse was moved. relatedTarget if the event is onmouseout, target if the event is onmouseover.
cancelBubble Assigning true to this property prevents the event from con­tinuing propa­gat­ing upwards in the DOM tree. The stopPropagation() method of the event should be called.
returnValueAssigning false to this property is re­quest­ing to Explorer not to execute the event's default action (like fol­low­ing a link). The preventDefault() method should be called.
offsetX, offsetY Position of the event with respect to the element that generates it. If the mouse is on a absolutely, fixed or relatively positioned element, then you can use layerX, layerY (non-standard). However, event.target is in a normally (static) positioned element these properties will give you the offset with respect to the document root element (which normally corresponds to the page). In this case your only option is to manually calculate the "offsetX" value by traversing all the hierarchy from the document root element, adding the objects' offsetLeft / offsetTop values.

Assorted differences in the DOM tree

By DOM tree I mean the hierarchic structure of objects that the browser uses to represent the tags that make up the page. There are small differences that can affect the compatibility of a page.

Differences in several properties and methods
In Explorer Description In Firefox
window.screenLeft, window.screenTop Position of the window browser relative to the screen. window.screenX - someValue, window.screenY + someValue These properties are not extactly the same as the Explorer ones. In explorer they give the coordinates of the origin of the IE control, while in Firefox they give the origin of the Firefox window itself.
element.attachEvent( event_name, function) Attachs function to element's event_name. element.addEventListener(event_name, function, false), but see above.
contains(node) This method is available in every element, and allows to ask if another node is a descendant of this one.

DOM level 2 does not have an equivalent method, but a very simple method like the shown below can be used (it works in Firefox and Explorer):

// Finds out if a is an ancestor of b
function contains(a, b)
{
	// we climb through b parents
	// till we find a
 	while(b && (a!=b) && (b!=null))
		b = b.parentNode;
	return a == b;
}

The new DOM level 3 standard defines node.compareDocumentPosition() and Firefox already implements it, but Explorer doesn't.

document.parentWindow It obtains the window in which the document is. document.defaultView. Note that the window object is available everywhere. This property could make some sense in frame-using pages, but I don't think the lack of it would render anything imposible.
myForm Access a form defined by <form name="myForm"> You may use document.myForm, it works everywhere.
element.innerText Replaces element's content with the specified plain text. In Firefox, this property is called textContent. It's part of the DOM Level 3 standard.
children Array of child element nodes. The DOM doesn't define such a thing. But it defines that each node has a childNodes property which is very similar. The big difference is that childNodes also includes text nodes. So, if you have code which does children[n], then you'll have to replace it with childNodes[n+Z], where Z is the number of (previously ignored) text nodes which are before the wanted element.

The sad part is that Explorer does not support childNodes.

Since Firefox 3.5 there's support for IE style children property, but it remains nonstandard

element.designMode element.designMode too, but with many differences. Mozilla has implemented support for having editable content (it's called "Midas" in Mozilla), but it has a slightly changed API. They have documented converting this to Firefox.

ActiveX

No way. No other browser supports ActiveX. But you still may be lucky: Some of the features which are available through ActiveX can be accessed in Firefox without it! If you are using a custom component, there you are out of luck.

The code which can easily be replaced is:

In Explorer In Firefox Notes
new ActiveXObject("Microsoft.XMLHTTP") new XMLHttpRequest() It's also sup­port­ed in the new Explorer 7. You may also check Apple's documentation.
new ActiveXObject("Microsoft.XMLDOM") document.implementation.createDocument(ns, root-element, null) The API is slightly different.

Bibliography

Also visit my introductory article about XML.


By Nicolás Lichtmaier. Questions, comments, suggestions and corrections will be well received.
From Buenos Aires, Argentina.

Valid HTML 4.01!  |Valid CSS!

My curriculum vitae.


Firefox 3