MDN may have intermittent access issues April 18 13:00 - April 19 01:00 UTC. See whistlepig.mozilla.org for all notifications.

mozilla

Revision 54562 of Writing forward-compatible websites

  • Revision slug: Web_development/Writing_forward-compatible_websites
  • Revision title: Writing forward-compatible websites
  • Revision id: 54562
  • Created:
  • Creator: Sheppy
  • Is current revision? No
  • Comment minor cleanup; 129 words added, 31 words removed

Revision Content

This is a list of best practices for creating websites that do not break when browsers are updated. It's not always possible to follow all of these, but following as many of them as possible will help future-proof your website. This is especially important for intranet applications and other non-public websites where problems are likely not to be noticed during testing by browser vendors.

JavaScript

Prefix all global variable access in onfoo attributes with “window.”

When an event handler content attribute (onclick, onmouseover, and so forth) is used on HTML element, all name lookup in the attribute first happens on the element itself, then on the element's form if the element is a form control, then on the document, and then on the window. For example, if you have this markup:

<div onclick="alert(ownerDocument)">Click me</div>

then clicking on the text alerts the ownerDocument of the div. This happens even if there is a var ownerDocument declared in global scope.

What this means is that any time you access a global variable in an event handler content attribute, including calling any function declared globally, you can end up with a name collision if a specification adds a new DOM property to elements or documents. If that happens, then suddenly your function stops being called. This has happened multiple times to various sites already during the evolution of HTML5.

To avoid this, fully qualify global variable access, like so:

<script>
  function localName() {
    alert('Function localName has been called');
  }
</script>
<div onclick="window.localName()">Clicking me should show an alert<div>

Note that removing "window." in the example above completely changes the behavior in this case.

Don't concatenate scripts you don't control

The "use strict;" directive in ECMAScript, when used on the file level, applies to everything in the file. So appending a script that depends on non-strict-mode behavior to a strict-mode script will cause things to break.

Ask the authors of any JavaScript libraries you use to also follow these guidelines

Unfortunately, libraries have a strong tendency to violate many of them, so suggest to their developers that they make sure they follow these guidelines too. Otherwise you can't rely on them not breaking in the future.

Sniffing

Sniff for specific features

If you plan to use some feature, use object-detection to sniff for that exact feature, if possible.  As a simple example, don't assume that any browser in which "filter" in body.style tests true must be Microsoft Internet Explorer and have a window.event available in event handlers. Don't assume that browsers with support for a given DOM feature must also have some other, especially nonstandard, DOM feature. Or that they don't have support for some other feature (e.g., don't assume that a browser that supports onload on script elements will never support onreadystatechange on them). As browsers converge behavior, they both add features and remove them. They also fix bugs.  All three of these have happened in the past and will happen again.

So don't sniff for one feature or object, then assume that because it exists, some other feature or object must also exist.

Don't UA-sniff

This is really a particularly common instance of assuming that one feature (the presence of a particular substring in the user agent (UA) string) implies something about the presence or absence of other features.

If you have to UA-sniff, only sniff for past browser versions

If you have to resort to UA sniffing, only use it to target past browser versions of particular browsers. First, have a default code path that runs in unknown browsers and in the current and future versions of browsers you are testing with. Then, if the default code path doesn't work in past versions of particular browsers and the breakage cannot be detected by sniffing for the absence of certain features your default code path uses, it's OK to add hacks that are targeted to old versions of particular browsers by sniffing for those old browser versions.

For the purpose of this piece of advice, "current" means the most recent version of a browser you have tested. For example, if you have tested that your default code path runs properly in Firefox Aurora but Firefox Beta and the latest release have a bug that make your default code path fail, it is OK to treat the Firefox version number that is in beta at the moment of testing and earlier versions as "past" versions even though the version in beta isn't even current yet from the point of view of users of the release channel.

Don't unnecessarily create separate codepaths for different browsers

Don't go out of your way to run different code based on either object detection or UA sniffing if one of the codepaths involved actually works in all browsers. There is a good chance of browsers changing behavior to converge with each other and hence breaking one of the codepaths that didn't use to work in all browsers to start with.

Testing

Test with all major engines

Test your code at least in Firefox, Chrome or Safari (since both are based on the same WebKit engine), Opera, and Internet Explorer. If you are following the advice given above so that you have a single code path for all current and unknown browsers, testing that this single code path works in all the major engines makes it extremely probable that your code won't break in the future.

Sometimes browsers implement a given feature slightly differently. If you have a single code path that works in all the top engines, it means that you are either using features where browser behavior has already converged or, if the behavior hasn't quite converged yet, your code works regardless of which engine's behavior standards turn out to uphold.

Browser-specific features and prefixes

Don't target hacks at current or future versions of browsers

This is also a common instance of assuming that present correlation between bugs implies future correlation between bugs.  Targeting hacks at old versions of browsers whose current versions no longer have the bug you're relying on for your hack is OK; once a browser has fixed bug X, you can know for certain that all releases that had bug X also had bug Y and use the presence of bug X to target workarounds for bug Y.

For the purpose of this piece of advice, "current" means the most recent version of a browser you have tested, as in the case of the UA sniffing advice above.

Avoid depending on cutting-edge nonstandard features

Even if the feature is prefixed, using it could be dangerous: as the specification evolves the browser's prefixed implementation can likewise change to track the specification. And once the feature is standardized, the prefixed version is likely to be removed.

Prefixed, non-standard features are provided by browser developers for you to experiment with and offer feedback on, and aren't meant to be deployed. If you choose to use them, be prepared to need to frequently update your site to keep up with changes.

When using cutting-edge features (even standard ones) that are not universally implemented, make sure to test fallback paths

Make sure to test what happens in a browser that doesn't implement the feature you're using, especially if you don't use such a browser day-to-day while working on the site.

Don't use vendor-prefixed features except to target old buggy versions

Vendor-prefixed features can change behavior in future releases.  Once a browser has shipped a feature unprefixed, however, you can use the prefixed version to target old releases by making sure to always use the unprefixed version of the feature when available.  A good example, for a browser vendor using the -vnd CSS prefix that has shipped an unprefixed implementation of the make-it-pretty property, with a behavior for the value "sometimes" that differs from the prefixed version:

<style>
  .pretty-element {
    -vnd-make-it-pretty: sometimes;
    make-it-pretty: sometimes;
  }
</style>

The order of the declarations in the rule above is important: the unprefixed one needs to come last.

Don't use unprefixed versions of CSS properties or APIs until at least one browser supports them

Until there's decently widespread support of the unprefixed version of something, its behavior can still change in unexpected ways.  Most especially, don't use the unprefixed version if no browser actually supports it.

Code hygiene

Avoid missing >

Passing a validator is one way to ensure this, but even if your website doesn't validate entirely you should make sure all your > characters are present. Missing those can lead to unexpected situations due to a following tag name being treated as an attribute on a previous tag. This can work for a bit, then break if a specification attaches a meaning to that attribute. Here's an example that works in browsers without HTML5 support but breaks in a browser supporting HTML5:

<form action="http://www.example.com">
  <input type="submit" value="Submit the form"
</form>

due to the missing > on the input tag.

Don't leave experiments that didn't work in your code

If you try using a CSS property to do something you want, but it has no effect, remove it.  It might start doing something you don't expect in the future.

Revision Source

<p>This is a list of best practices for creating websites that do not break when browsers are updated. It's not always possible to follow all of these, but following as many of them as possible will help future-proof your website. This is especially important for intranet applications and other non-public websites where problems are likely not to be noticed during testing by browser vendors.</p>
<h2>JavaScript</h2>
<h3>Prefix all global variable access in <code>onfoo</code> attributes with “<code>window</code>.”</h3>
<p>When an event handler content attribute (<code>onclick</code>, <code>onmouseover</code>, and so forth) is used on HTML element, all name lookup in the attribute first happens on the element itself, then on the element's form if the element is a form control, then on the document, and then on the window. For example, if you have this markup:</p>
<pre>&lt;div onclick="alert(ownerDocument)"&gt;Click me&lt;/div&gt;</pre>
<p>then clicking on the text alerts the <code>ownerDocument</code> of the <code>div</code>. This happens even if there is a <code>var ownerDocument</code> declared in global scope.</p>
<p>What this means is that any time you access a global variable in an event handler content attribute, including calling any function declared globally, you can end up with a name collision if a specification adds a new DOM property to elements or documents. If that happens, then suddenly your function stops being called. This has happened multiple times to various sites already during the evolution of HTML5.</p>
<p>To avoid this, fully qualify global variable access, like so:</p>
<pre class="brush: html">&lt;script&gt;
  function localName() {
    alert('Function localName has been called');
  }
&lt;/script&gt;
&lt;div onclick="window.localName()"&gt;Clicking me should show an alert&lt;div&gt;
</pre>
<p>Note that removing "<code>window.</code>" in the example above completely changes the behavior in this case.</p>
<h3>Don't concatenate scripts you don't control</h3>
<p>The <code>"use strict;"</code> directive in ECMAScript, when used on the file level, applies to everything in the file. So appending a script that depends on non-strict-mode behavior to a strict-mode script will cause things to break.</p>
<h3>Ask the authors of any JavaScript libraries you use to also follow these guidelines</h3>
<p>Unfortunately, libraries have a strong tendency to violate many of them, so suggest to their developers that they make sure they follow these guidelines too. Otherwise you can't rely on them not breaking in the future.</p>
<h2>Sniffing</h2>
<h3>Sniff for specific features</h3>
<p>If you plan to use some feature, use object-detection to sniff for that exact feature, if possible.  As a simple example, don't assume that any browser in which <code>"filter" in body.style</code> tests true must be Microsoft Internet Explorer and have a <code>window.event</code> available in event handlers. Don't assume that browsers with support for a given DOM feature must also have some other, especially nonstandard, DOM feature. Or that they don't have support for some other feature (e.g., don't assume that a browser that supports <code>onload</code> on script elements will never support <code>onreadystatechange</code> on them). As browsers converge behavior, they both add features and remove them. They also fix bugs.  All three of these have happened in the past and will happen again.</p>
<p>So don't sniff for one feature or object, then assume that because it exists, some other feature or object must also exist.</p>
<h3>Don't UA-sniff</h3>
<p>This is really a particularly common instance of assuming that one feature (the presence of a particular substring in the user agent (UA) string) implies something about the presence or absence of other features.</p>
<h3>If you have to UA-sniff, only sniff for past browser versions</h3>
<p>If you have to resort to UA sniffing, only use it to target past browser versions of particular browsers. First, have a default code path that runs in unknown browsers and in the current and future versions of browsers you are testing with. Then, if the default code path doesn't work in past versions of particular browsers and the breakage cannot be detected by sniffing for the absence of certain features your default code path uses, it's OK to add hacks that are targeted to old versions of particular browsers by sniffing for those old browser versions.</p>
<p>For the purpose of this piece of advice, "current" means the most recent version of a browser you have tested. For example, if you have tested that your default code path runs properly in Firefox Aurora but Firefox Beta and the latest release have a bug that make your default code path fail, it is OK to treat the Firefox version number that is in beta at the moment of testing and earlier versions as "past" versions even though the version in beta isn't even current yet from the point of view of users of the release channel.</p>
<h3>Don't unnecessarily create separate codepaths for different browsers</h3>
<p>Don't go out of your way to run different code based on either object detection or UA sniffing if one of the codepaths involved actually works in all browsers. There is a good chance of browsers changing behavior to converge with each other and hence breaking one of the codepaths that didn't use to work in all browsers to start with.</p>
<h2>Testing</h2>
<h3>Test with all major engines</h3>
<p>Test your code at least in Firefox, Chrome or Safari (since both are based on the same WebKit engine), Opera, and Internet Explorer. If you are following the advice given above so that you have a single code path for all current and unknown browsers, testing that this single code path works in all the major engines makes it extremely probable that your code won't break in the future.</p>
<p>Sometimes browsers implement a given feature slightly differently. If you have a single code path that works in all the top engines, it means that you are either using features where browser behavior has already converged or, if the behavior hasn't quite converged yet, your code works regardless of which engine's behavior standards turn out to uphold.</p>
<h2>Browser-specific features and prefixes</h2>
<h3>Don't target hacks at current or future versions of browsers</h3>
<p>This is also a common instance of assuming that present correlation between bugs implies future correlation between bugs.  Targeting hacks at <strong>old</strong> versions of browsers whose current versions no longer have the bug you're relying on for your hack is OK; once a browser has fixed bug X, you can know for certain that all releases that had bug X also had bug Y and use the presence of bug X to target workarounds for bug Y.</p>
<p>For the purpose of this piece of advice, "current" means the most recent version of a browser you have tested, as in the case of the UA sniffing advice above.</p>
<h3>Avoid depending on cutting-edge nonstandard features</h3>
<p>Even if the feature is prefixed, using it could be dangerous: as the specification evolves the browser's prefixed implementation can likewise change to track the specification. And once the feature is standardized, the prefixed version is likely to be removed.</p>
<p>Prefixed, non-standard features are provided by browser developers for you to experiment with and offer feedback on, and aren't meant to be deployed. If you choose to use them, be prepared to need to frequently update your site to keep up with changes.</p>
<h3>When using cutting-edge features (even standard ones) that are not universally implemented, make sure to test fallback paths</h3>
<p>Make sure to test what happens in a browser that doesn't implement the feature you're using, especially if you don't use such a browser day-to-day while working on the site.</p>
<h3>Don't use vendor-prefixed features except to target old buggy versions</h3>
<p>Vendor-prefixed features can change behavior in future releases.  Once a browser has shipped a feature unprefixed, however, you can use the prefixed version to target old releases by making sure to always use the unprefixed version of the feature when available.  A good example, for a browser vendor using the <code>-vnd</code> CSS prefix that has shipped an unprefixed implementation of the <code>make-it-pretty</code> property, with a behavior for the value <code>"sometimes"</code> that differs from the prefixed version:</p>
<pre class="brush: html">&lt;style&gt;
  .pretty-element {
    -vnd-make-it-pretty: sometimes;
    make-it-pretty: sometimes;
  }
&lt;/style&gt;
</pre>
<p>The order of the declarations in the rule above is important: the unprefixed one needs to come last.</p>
<h3>Don't use unprefixed versions of CSS properties or APIs until at least one browser supports them</h3>
<p>Until there's decently widespread support of the unprefixed version of something, its behavior can still change in unexpected ways.  Most especially, don't use the unprefixed version if no browser actually supports it.</p>
<h2>Code hygiene</h2>
<h3>Avoid missing <code>&gt;</code></h3>
<p>Passing a validator is one way to ensure this, but even if your website doesn't validate entirely you should make sure all your <code>&gt;</code> characters are present. Missing those can lead to unexpected situations due to a following tag name being treated as an attribute on a previous tag. This can work for a bit, then break if a specification attaches a meaning to that attribute. Here's an example that works in browsers without HTML5 support but breaks in a browser supporting HTML5:</p>
<pre class="brush: html">&lt;form action="http://www.example.com"&gt;
  &lt;input type="submit" value="Submit the form"
&lt;/form&gt;
</pre>
<p>due to the missing <code>&gt;</code> on the <code>input</code> tag.</p>
<h3>Don't leave experiments that didn't work in your code</h3>
<p>If you try using a CSS property to do something you want, but it has no effect, remove it.  It might start doing something you don't expect in the future.</p>
Revert to this revision