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.