TL;DR
Many add-ons are currently broken on Nightly (Firefox 44) due to some changes that were done in the way let
and const
behave. These changes were introduced to make Firefox compliant with the final ES6 standard. These changes can lead to JS errors that break your code entirely, so we suggest you test your add-ons extensively to make sure they continue to work.
All add-ons built with JPM (except the latest version) are currently affected by this. We plan to automatically repack all affected JPM add-ons on AMO, but encourage you to repackage the add-on yourself and save some time. Pre-JPM versions of the SDK aren’t affected.
Update: additionally, with the change in bug 1167029, let blocks (like let (x = 42) { use(x); }
) will also stop working.
Please read on for the details of this change, written by Shu-yu Guo. It’s an interesting read even if your add-on isn’t affected.
SpiderMonkey has had non-standard let
and const
bindings for years. Recently we updated the semantics of let
and const
bindings for the global level to be compliant with ES6 semantics. ES6 semantics is not compatible with SpiderMonkey’s legacy semantics. (For an introduction to ES6 semantics, please refer to Jason Orendorff’s post.)
Did this update break your add-on? This post will help you diagnose and fix issues it caused.
Legacy Semantics
At the global level, legacy let
was equivalent to var
. Inside the parser, it was in fact parsed as if the token were var
.
Global-level legacy const
was like var
, except that the property it introduced was read-only.
ES6 global lexical bindings are not properties
The biggest incompatibility is that ES6 let
and const
bindings, unlike their legacy counterparts, are no longer properties on the global object. Instead, they are bindings in the global lexical scope directly below the global object.
For example,
const x = 42;
// Prints false for ES6, true for legacy
dump('x' in this);
Many add-ons have the expectation that global let
and const
introduce properties. For instance, a legacy JSM might define constants and globals:
// Foo.jsm
const MY_CONSTANT = 42;
let gFoo = "foo";
// addon.js
var FooModule = Cu.import("Foo.jsm", {});
dump(FooModule.MY_CONSTANT);
dump(FooModule.gFoo);
With ES6 const
, FooModule.MY_CONSTANT
and FooModule.gFoo
are both undefined
, because their bindings are in a separate scope and not properties on the global object. This makes bugs caused by these errors particularly elusive.
For uses of global legacy let
bindings that need to be accessed as properties, I recommend declaring them with var
.
Unfortunately, there is no ES6 syntax with the same semantics as legacy const
. If the read-only aspect of the property is necessary, I recommend manually defining a property on the global object:
Object.defineProperty(globalObject, "MY_CONSTANT", {
value: 42,
enumerable: true,
writable: false
});
If your add-on imports XPCOMUtils
, XPCOMUtils.defineConstant(obj, key, value)
does exactly that.
ES6 global lexical bindings may not be redeclared
In ES6, global lexical bindings may not be redeclared in any way: not by let
, const
, nor var
. For example, if the global level has let foo
or const foo
, any subsequent occurrences of let foo
, const foo
, or var foo
will throw.
Redeclaration errors are easy to fix: rename the variable or remove the declarator and assign to the already-declared variable directly.
ES6 global lexical bindings have TDZ
In ES6, no lexical binding may be used before its declaration is reached. For example, the following throws:
dump(x);
let x;
This has long been regarded as poor style in the community, and fortunately, such errors in existing code are rare. If such an error is encountered, it is very likely a bug in the code.
Global lexical scope and JSSubScriptLoader
The subscript loader may load new scripts into the global scope. This interacts with the ES6 global lexical scope. The pitfall is that since lexical bindings may not be redeclared, loading multiple scripts that redeclare globals with let
or const
now result in error. For example,
// foo.js
let gFoo;
// bar.js
let gFoo;
// addon.js
loader.loadSubScript("foo.js");
loader.loadSubScript("bar.js"); // throws due to redeclaration of gFoo
Global lexical scope and the component loader
When loading components, such as via Cu.import
, each component has its own global scope and global lexical scope, so cross-script redeclaration issues do not arise.
Cu.import
returns the global object of the imported component, so the main pitfall is using let
and const
-declared bindings as properties on that scope.
Global lexical scope and custom scopes
Both the subscript and component loaders let users load scripts whose global variables and properties are stored in a user-provided scope object. For example, suppose we had foo.js:
// foo.js
var gVar;
let gLet;
const gConst;
Calling loader.loadSubScript("foo.js", myScope)
would result in parsing foo.js with the following scope chain, from outermost to innermost:
Global object | Global lexical scope | myScope | ...
Without user-passed scopes, var
bindings go on the global object. Lexical let
and const
bindings go on the global lexical scope.
From the point of view of var
bindings, myScope
behaves like the global object: they capture var
bindings as properties. That is, gVar
is a property on myScope
, not the global object.
Global lexical let
and const
bindings shadow global properties: gLet
would hide a property reachable via this.gLet
. Since myScope
captures var
bindings, consistency requires myScope
to have its own lexical scope that captures let
and const
bindings:
Global object | Global lexical scope | myScope | myScope's lexical scope
In the example, gLet
and gConst
are bindings in myScope
‘s lexical scope. Multiple scripts loaded into myScope
would get the same lexical scope. Scripts loaded into myOtherScope
would get myOtherScope
‘s lexical scope, an entirely different scope.
Note that lexical bindings are still not properties on the non-syntactic scope. If your add-on uses custom scopes, you may run into the problems described in “ES6 global lexical bindings are not properties” above.
Noitidart wrote on
Vangelis wrote on
Jorge Villalobos wrote on
Vangelis wrote on
kup wrote on
Vangelis wrote on
kup wrote on
Vangelis wrote on
kup wrote on