2010-12-05

On browser compatibility issues

This blog post lists the browser compatibility issues (with solutions) I've encountered when creating a simple HTML(4) + JavaScript + CSS web page containing presentation slides with a little bit of user interaction.

My goal was to make the web page compatible with Google Chrome 8.0 or later, Firefox 3.6 or later, Safari in Leopard or later, Opera 10.63 or later, Konqueror 4.4.2 or later, Internet Explorer 8.0 or later. (I'll call them main browsers from now on.) Please note that anything stated in this blog post may not be true for earlier web browser versions. There was no plan to make the web page work in any earlier web browser version, but it turned out that it was possible and easy to make the page work with Internet Explorer 7.0 with some small rendering quality degradation, so I happened to add support for that as well.

My workflow:

  • Edit the web page directly in a text editor, as a single HTML file (containing HTML, JavaScript and CSS). Split it to different files only after the prototype is found working and all browser compatibility issues have been registered (and most of them resolved).
  • Make sure the browser renders the page in standards compliant mode, not in quirks mode (see below how and why).
  • View the local HTML file in Google Chrome, reload the page after each file save. Try it after major modifications in Firefox. As soon as the web page works in these two browsers, fix it for other browsers as well.
  • When porting to other browsers, especially Internet Explorer, search in the jQuery source code for the feature (class or method name) that works in one browser. Most probably they jQuery source will contain its alternative implementations for other browsers.
  • Use the Developer Tools feature of Google Chrome (activate it with Ctrl-Shift-J) to debug the DOM, the CSS and the JavaScript on the current page. With Firefox, use the Firebug extension and/or the shell bookmarklet for debugging. Should you need it, use the Tools / Developer Tools (activate it with F12; it is available as an extension called Developer Toolbar for IE7 and earlier) in Internet Explorer 8 for debugging.
  • With Internet Explorer Developer Tools you can verify that the web page is indeed rendered in standards compliant mode, and you can also force a rendering which Internet Explorer 7 would do.
  • After each major change, validate the web page that it is valid HTML 4.01 transitional. You can use the HTML Validator Google Chrome extension.

My general knowledge and advice on cross-browser compatibility:

  • All main browsers (Google Chrome, Firefox, Safari, Opera and Konqueror) are very similar to each other except for Internet Explorer.
  • Google Chrome, Safari and Konqueror are even more similar to each other, because they share the same original code base (KHTML in Konqueror, which WebKit in Safari is based on, which WebKit in GoogleChrome is based on).
  • Rendering (application of CSS) in most main browsers in much more similar to each other when they render the web page in standards compliant mode rather than in quirks mode. So to get rid of many incompatibilities, just make sure that the browser renders the page in standards compliant mode.
  • To enable standards compliant mode, just start your HTML with a doctype (right at the beginning of the file). To get HTML 4.01 transitional, prepend this to your HTML file:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
  • Make sure your editor saves the HTML file in UTF-8 encoding (character set), without the byte order mark (BOM).
  • Make sure the character set is indicatied the <head>. This is needed and used when the web page is loaded using a file:/// URL or imported to a word processor such as OpenOffice Writer or LibreOffice Writer. Example:
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  • Make sure your webserver returns the proper character set for the web page in the HTTP response headers (e.g. Content-Type: text/html; charset=utf-8). This setting is used for http:// and https:// URLs. The HTTP response header takes precedence over <meta http-equiv=. If you use the Apache webserver, you may be able to force the UTF-8 charset by creating a file named .htaccess (filename starting with a dot) next to the HTML file, containing
    AddDefaultCharset UTF-8
    . If it doesn't seem to take effect, then put an error (temporarily) to the .htaccess file (e.g. nosuchdirective 42), do a Shift-reload on your web page. If that causes an Internal Server Error, then .htaccess is indeed honored by Apache. If you don't get such an error, talk to your webmaster or system administrator (i.e. copy-paste this paragraph to an e-mail in addition to the URL).
  • The corresponding SVN command (for Google Code) for the character set is:
    svn propset 'svn:mime-type' 'text/html; charset=utf-8' webpage.html
  • Use jQuery, and make it take care of most browser compatibility issues. This document assumes, however, that no JavaScript framework is used, so all issues have to be resolved manually.

Some specific cross-browser compatibility issues I have encountered:

  • See a cross-browser comparison of all JavaScript browser events here.
  • Internet Explorer has a different API for installing and handling events.
    • Event propagation and bubbling semantics are different. You can avoid these issues by installing all event handlers for document.body and window. (Sorry, I don't know more about this.)
    • For Internet Explorer, change obj.addEventListener(eventName, handler, false) to obj.attachEvent('on' + eventName, handler).
    • For Internet Explorer, change obj.removeEventListener(eventName, handler, false) to obj.detachEvent('on' + eventName, handler).
    • Internet Explorer supports the DOMContentLoaded event under a different name (onreadystatechange) and different semantics, see below.
    • Internet Explorer needs event.preventDefault() to be specified differently. Here is the cross-browser solution:
      event.preventDefault ? event.preventDefault() : (event.returnValue = false)
    • Internet Explorer needs event.stopPropagation() to be specified differently. Here is the cross-browser solution:
      event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true)
    • Internet Explorer doesn't pass the event object to the event handler function. To make it cross browser, write it like this:
      function handler(event) { if (!event) event = window.event; ... }
  • Internet Explorer doesn't have window.getComputedStyle, so the current (computed) CSS properties have to be queried differently. An example, specific cross-browser solution:
    function getBackgroundColor(element) {
      return element.currentStyle ? element.currentStyle.backgroundColor :
             window.getComputedStyle(element, null).
             getPropertyValue('background-color')
    }
  • Internet Explorer 8 doesn't have elementOrDocument.getElementsByClassName, so it has to be emulated using a loop and e.g. elementOrDocument.getElementsByTagName and some manual checking. The emulation is a bit slower.
  • The string.substr method in Internet Explorer doesn't accept negative values in its first argument, so e.g. myString.substr(-2) has to be replaced by myString.substr(myString.length - 2).
  • The DOMContentLoaded event is fired as soon as the main HTML and the JavaScript and CSS files it references have finished loading — but before images and other objects on the page have finished. By that time, the rendering is done and measurements are made, so e.g. document.body.offsetWidth is valid. In contrast, the onload even fires as soon as images etc. are also finished loading Here is how to handle DOMContentLoaded in a cross-browser way:
    function onDomReady() {
      ...
    }
    if (navigator.userAgent.indexOf(' KHTML/') >= 0) {
      document.addEventListener('load', onDomReady, false) 
    } else if (document.addEventListener) {
      document.addEventListener('DOMContentLoaded', onDomReady, false)
    } else if (document.attachEvent) {
      document.attachEvent('onreadystatechange', function() {
        if (document.readyState == 'complete') {
          document.detachEvent('onreadystatechange', onReadyStateChange)
          onDomReady()
        }
      })
    }
    Here, the load event is used instead of DOMContentLoaded, because Konqueror 4.4.2 doesn't compute element dimensions (e.g. document.body.offsetWidth and CSS properties) at DOMContentLoaded time; but it returns bogus default values. For Internet Explorer, onreadystatechange has to be used instead.
  • There are two keyboard events when a key is pressed or auto-repeated: keydown and keypress.
    • Use keypress for Google Chrome and Safari (i.e. navigator.userAgent.indexOf(' AppleWebKit/') >= 0), because they don't send the arrow key (and any other non-letter) events for keypress.
    • Use keydown for Firefox (for 3.6), because it doesn't send auto-repeats for keydown (see more here).
    • Use onkeydown on Internet Explorer.
    • Some browsers have a meaningless event.keyCode (such as 0 for Firefox 3.6 or the same as the event.charCode for Konqueror 4.4.2) for some keys. In this case, use the event.charCode instead.
    • The event.charCode depends on whether Shift is down (e.g. 65 for A and 97 for Shift-A), but it doesn't depend on whether Ctrl or other modifiers are down. event.charCode is usually a Unicode code point (character code).
    • See event.keyCode constants here.
    • See more here about keyboard event compatibility.
  • Many browsers (such as Firefox 3.6) send multiple resize events (as part of an animation) when the window is maximized.
  • To get the vertical scroll position, use document.body.scrollTop || document.documentElement.scrollTop, because Konqueror always returns 0 for just document.body.scrollTop. Do this respectively for the horizontal scroll position (scrollLeft).
  • To scroll the page in a cross-browser way (which works in Konqueror as well), use window.scroll(newScrollLeft, newScrollTop) .
  • Internet Explorer doesn't support window.innerWidth, use window.innerWidth || document.documentElement.clientWidth for cross-browser compatibility. Do the same with window.innerHeight and document.documentElement.clientHeight as well.
  • Internet Explorer 7 doesn't support an extra comma right before ] and } in array and object constructors.

2 comments:

zsbana said...

One more random compatibility issue. You can load style sheets (with a link element) that don't take effect by default, but can later be enabled by javascript. However, browsers recognize such stylesheets in two different ways: Firefox looks at whether the rel attribute of the link element says "stylesheet" or "alternate stylesheet", whereas Konqueror checks whether the stylesheet has an "id" attribute.

egmont said...

One more important thing: make sure to set the X-UA-Compatible meta tag for Explorer 8 and above. If you don't do so, it will choose one of "standards" (the best it can do) or "compatibility" (emulate IE7 more or less) based on the _user's_ settings and other criteria. So the page might display correcty for some users and incorrectly for others. Testing for both scenarios becomes a nightmare too. The solution is to explicitly tell IE which rendering mode to use, via the meta X-UA-Compatible tag.