21.12.09

ECMA-262 ed. 5 backwards-incompatible change coming to SpiderMonkey and to Gecko-based browsers

(preemptive clarification: coming in Firefox 3.7 and not Firefox 3.6, which is to say, a good half year away from now rather than Real Soon Now)

ES5 and compatibility

The fifth edition of ECMA-262, the next iteration of the JavaScript language, is broadly backwards-compatible with existing JavaScript code. Generally, code that worked in past browsers that implemented the specification will continue to work in new browsers as they implement the new edition of the specification. However, there are a few exceptions. The most obvious one is ES5’s strict mode, where specially-tagged scripts and functions will cause parsing and execution of their contents to occur under stricter requirements than in the past. For example, this code would have executed “as intended” in ES3, but in ES5 the definition of a variable named “arguments” inside a function in strict mode is a syntax error (none of the code even executes):

function strictModeError()
{
  "use strict";
  var arguments = 17; // stupid, but permissible, in ES3
  return arguments;
}
if (strictModeError() !== 17)
  throw new Error("up is down");

The above isn’t more than a theoretical problem as it is expected old code wouldn’t have accidentally opted into strict mode. Not all of ES5’s incompatible changes, however, are so benign.

ES5 compatibility with ES3 extensions

One unusual area of compatibility concerns not ES3, but extensions to ES3. One of the more profound changes in ES5 is the introduction of getters and setters, in which what appears syntactically to be a property when used actually will invoke function calls “under the hood”. Most major JS engines support this extension to ES3:

var o =
  {
    get field() { return this._field; },
    set field(f)
    {
      if (typeof f != "number")
        throw new Error("not a number");
      this._field = f;
    },
    _field: 0
  };
print(o.field); // 0
o.field = 5;
print(o.field); // 5
try { o.field = "0"; } catch (e) { /* throws: not a number */ }
print(o.field); // 5
o.field = 17;
print(o.field); // 17

This syntax is in ES5, partly because it addresses a need in a reasonable way but mostly because many developers will already be familiar with it. (Getters and setters were also available programmatically; ES5’s solution is different but more flexible.) However, not all aspects of getters and setters are present in ES5 in the same way they were in extensions to ES3 engines.

Assigning to getter-only properties in ES5

Consider the previous example, slightly tweaked:

var o =
  {
    get field() { return this._field; },
    _field: 17
  };
print(o.field); // 17
o.field = 5;  // ???
print(o.field); // ???

In this case the field property is read-only: you could analogize it to element.childNodes.length, which has a value which it makes no sense to attempt to change. What should happen, then, if you attempt to change it? Current browsers throw a TypeError when you try this. ES5, however, chooses to remain (arguably) more faithful to ES3 and instead makes setting a property that only has a getter do nothing — except in strict mode, where a TypeError exception will be thrown.

SpiderMonkey, the JavaScript engine embedded in Gecko browsers, has just made the switch from its previous always-throw behavior to ES5’s only-throw-if-in-strict-mode behavior when an attempt is made to set a property that only has a getter. (This will also apply to DOM properties like the aforementioned element.childNodes.length.) In the future, if you try to set a property that only has a getter, no exception will be thrown unless you’ve opted into strict mode. This change is now in trunk builds of Firefox; download a nightly build from nightly.mozilla.org and test out the change for yourself (use the profile manager if you want to keep your current Firefox settings and install untouched). If your site relies on an exception being thrown, this change could break it, and we’re hoping that an extended period of time to test the change will help developers iron out any reliance on this non-standard behavior. This change will first appear in Firefox 3.7, which probably won’t be released until the second half of 2010 or so. Firefox 3.6 preserves current behavior where an exception is always thrown, so you should have plenty of time to update your site in response to this change.

The bottom line

Firefox 3.6 and earlier throw an exception whenever you attempt to set a property represented only by a getter (this includes DOM properties defined as readonly). Firefox 3.7 will only throw a TypeError when assigning to a property represented by only a getter if the assignment occurs in ES5 strict mode code. This change will also apply to attempts to set readonly DOM properties like element.childNodes.length. If you’re relying on an exception being thrown in either case, change the assignment location code so that it works when no TypeError exception is thrown.

« Newer