Bug 1202902 - Support non-syntactic extensible lexical scopes. (r=billm)
authorShu-yu Guo <shu@rfrn.org>
Tue, 06 Oct 2015 14:00:29 -0700
changeset 266399 c609df6d3895e655dc6ca85241bbad0ba1de60ef
parent 266398 04eb8f524122c6570c295c3e887638c76e4961ca
child 266400 6917045ffcf586a8f652345abe53a0da04589960
push id66174
push usershu@rfrn.org
push dateTue, 06 Oct 2015 20:55:20 +0000
treeherdermozilla-inbound@cfc1820361f5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1202902
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1202902 - Support non-syntactic extensible lexical scopes. (r=billm)
js/public/MemoryMetrics.h
js/src/builtin/Eval.cpp
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonCaches.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfriendapi.h
js/src/vm/Debugger.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/MemoryMetrics.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/vm/TypeInference.cpp
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/loader/mozJSComponentLoader.h
js/xpconnect/src/XPCShellImpl.cpp
testing/xpcshell/head.js
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -691,17 +691,18 @@ struct CompartmentStats
     macro(Other,   MallocHeap, typeInferenceObjectTypeTables) \
     macro(Other,   MallocHeap, compartmentObject) \
     macro(Other,   MallocHeap, compartmentTables) \
     macro(Other,   MallocHeap, innerViewsTable) \
     macro(Other,   MallocHeap, lazyArrayBuffersTable) \
     macro(Other,   MallocHeap, objectMetadataTable) \
     macro(Other,   MallocHeap, crossCompartmentWrappersTable) \
     macro(Other,   MallocHeap, regexpCompartment) \
-    macro(Other,   MallocHeap, savedStacksSet)
+    macro(Other,   MallocHeap, savedStacksSet) \
+    macro(Other,   MallocHeap, nonSyntacticLexicalScopesTable)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
         allClasses(nullptr),
         notableClasses(),
         isTotals(true)
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -490,29 +490,38 @@ js::ExecuteInGlobalAndReturnScope(JSCont
                                   MutableHandleObject scopeArg)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, global);
     MOZ_ASSERT(global->is<GlobalObject>());
     MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
+    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
     if (script->compartment() != cx->compartment()) {
-        Rooted<ScopeObject*> staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr));
+        Rooted<ScopeObject*> staticScope(cx, &globalRoot->lexicalScope().staticBlock());
+        staticScope = StaticNonSyntacticScopeObjects::create(cx, staticScope);
         if (!staticScope)
             return false;
         script = CloneGlobalScript(cx, staticScope, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
-    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
-    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot));
+    Rooted<ClonedBlockObject*> globalLexical(cx, &globalRoot->lexicalScope());
+    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalLexical));
+    if (!scope)
+        return false;
+
+    // Unlike the non-syntactic scope chain API used by the subscript loader,
+    // this API creates a fresh block scope each time.
+    RootedObject enclosingStaticScope(cx, script->enclosingStaticScope());
+    scope = ClonedBlockObject::createNonSyntactic(cx, enclosingStaticScope, scope);
     if (!scope)
         return false;
 
     JSObject* thisobj = GetThisObject(cx, global);
     if (!thisobj)
         return false;
 
     RootedValue thisv(cx, ObjectValue(*thisobj));
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2339,32 +2339,48 @@ EvalReturningScope(JSContext* cx, unsign
         if (!global->is<GlobalObject>()) {
             JS_ReportError(cx, "Argument must be a global object");
             return false;
         }
     } else {
         global = JS::CurrentGlobalOrNull(cx);
     }
 
-    RootedObject scope(cx);
+    RootedObject varObj(cx);
+    RootedObject lexicalScope(cx);
 
     {
         // If we're switching globals here, ExecuteInGlobalAndReturnScope will
         // take care of cloning the script into that compartment before
         // executing it.
         AutoCompartment ac(cx, global);
 
-        if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope))
+        if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope))
             return false;
+
+        varObj = lexicalScope->enclosingScope();
     }
 
-    if (!cx->compartment()->wrap(cx, &scope))
+    RootedObject rv(cx, JS_NewPlainObject(cx));
+    if (!rv)
+        return false;
+
+    RootedValue varObjVal(cx, ObjectValue(*varObj));
+    if (!cx->compartment()->wrap(cx, &varObjVal))
+        return false;
+    if (!JS_SetProperty(cx, rv, "vars", varObjVal))
         return false;
 
-    args.rval().setObject(*scope);
+    RootedValue lexicalScopeVal(cx, ObjectValue(*lexicalScope));
+    if (!cx->compartment()->wrap(cx, &lexicalScopeVal))
+        return false;
+    if (!JS_SetProperty(cx, rv, "lexicals", lexicalScopeVal))
+        return false;
+
+    args.rval().setObject(*rv);
     return true;
 }
 
 static bool
 ShellCloneAndExecuteScript(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.requireAtLeast(cx, "cloneAndExecuteScript", 2))
--- a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
+++ b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js
@@ -20,13 +20,13 @@ dbg.onNewScript = function (evalScript) 
   };
 };
 
 dbg.onDebuggerStatement = function (frame) {
   log += 'd';
 };
 
 assertEq(log, '');
-var evalScope = g.evalReturningScope("canary = 'dead'; debugger; // nee", g2);
+var evalScopes = g.evalReturningScope("canary = 'dead'; let lex = 42; debugger; // nee", g2);
 assertEq(log, 'ecbd');
 assertEq(canary, 42);
-assertEq(evalScope.canary, 'dead');
-
+assertEq(evalScopes.vars.canary, 'dead');
+assertEq(evalScopes.lexicals.lex, 42);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2476,18 +2476,18 @@ BaselineCompiler::emit_JSOP_DEFVAR()
 
     pushArg(R0.scratchReg());
     pushArg(Imm32(attrs));
     pushArg(ImmGCPtr(script->getName(pc)));
 
     return callVM(DefVarInfo);
 }
 
-typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned);
-static const VMFunction DefLexicalInfo = FunctionInfo<DefLexicalFn>(DefLexicalOperation);
+typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned, HandleObject);
+static const VMFunction DefLexicalInfo = FunctionInfo<DefLexicalFn>(DefLexical);
 
 bool
 BaselineCompiler::emit_JSOP_DEFCONST()
 {
     return emit_JSOP_DEFLET();
 }
 
 bool
@@ -2495,18 +2495,21 @@ BaselineCompiler::emit_JSOP_DEFLET()
 {
     frame.syncStack(0);
 
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
     if (*pc == JSOP_DEFCONST)
         attrs |= JSPROP_READONLY;
     MOZ_ASSERT(attrs <= UINT32_MAX);
 
+    masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg());
+
     prepareVMCall();
 
+    pushArg(R0.scratchReg());
     pushArg(Imm32(attrs));
     pushArg(ImmGCPtr(script->getName(pc)));
 
     return callVM(DefLexicalInfo);
 }
 
 typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject, HandleFunction);
 static const VMFunction DefFunOperationInfo = FunctionInfo<DefFunOperationFn>(DefFunOperation);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -7612,17 +7612,22 @@ DoSetPropFallback(JSContext* cx, Baselin
                op == JSOP_STRICTSETGNAME)
     {
         if (!SetNameOperation(cx, script, pc, obj, rhs))
             return false;
     } else if (op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL) {
         obj->as<ScopeObject>().setAliasedVar(cx, ScopeCoordinate(pc), name, rhs);
     } else if (op == JSOP_INITGLEXICAL) {
         RootedValue v(cx, rhs);
-        InitGlobalLexicalOperation(cx, script, pc, v);
+        ClonedBlockObject* lexicalScope;
+        if (script->hasNonSyntacticScope())
+            lexicalScope = &NearestEnclosingExtensibleLexicalScope(frame->scopeChain());
+        else
+            lexicalScope = &cx->global()->lexicalScope();
+        InitGlobalLexicalOperation(cx, lexicalScope, script, pc, v);
     } else {
         MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
 
         RootedValue v(cx, rhs);
         if (!PutProperty(cx, obj, id, v, op == JSOP_STRICTSETPROP))
             return false;
     }
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3724,17 +3724,17 @@ CodeGenerator::visitDefVar(LDefVar* lir)
     pushArg(scopeChain); // JSObject*
     pushArg(Imm32(lir->mir()->attrs())); // unsigned
     pushArg(ImmGCPtr(lir->mir()->name())); // PropertyName*
 
     callVM(DefVarInfo, lir);
 }
 
 typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned);
-static const VMFunction DefLexicalInfo = FunctionInfo<DefLexicalFn>(DefLexicalOperation);
+static const VMFunction DefLexicalInfo = FunctionInfo<DefLexicalFn>(DefGlobalLexical);
 
 void
 CodeGenerator::visitDefLexical(LDefLexical* lir)
 {
     pushArg(Imm32(lir->mir()->attrs())); // unsigned
     pushArg(ImmGCPtr(lir->mir()->name())); // PropertyName*
 
     callVM(DefLexicalInfo, lir);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1763,16 +1763,17 @@ IonBuilder::inspectOpcode(JSOp op)
       case JSOP_CHECKLEXICAL:
         return jsop_checklexical();
 
       case JSOP_INITLEXICAL:
         current->setLocal(GET_LOCALNO(pc));
         return true;
 
       case JSOP_INITGLEXICAL: {
+        MOZ_ASSERT(!script()->hasNonSyntacticScope());
         MDefinition* value = current->pop();
         current->push(constant(ObjectValue(script()->global().lexicalScope())));
         current->push(value);
         return jsop_setprop(info().getAtom(pc)->asPropertyName());
       }
 
       case JSOP_CHECKALIASEDLEXICAL:
         return jsop_checkaliasedlet(ScopeCoordinate(pc));
@@ -12656,16 +12657,17 @@ IonBuilder::jsop_defvar(uint32_t index)
     current->add(defvar);
 
     return resumeAfter(defvar);
 }
 
 bool
 IonBuilder::jsop_deflexical(uint32_t index)
 {
+    MOZ_ASSERT(!script()->hasNonSyntacticScope());
     MOZ_ASSERT(JSOp(*pc) == JSOP_DEFLET || JSOp(*pc) == JSOP_DEFCONST);
 
     PropertyName* name = script()->getName(index);
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
     if (JSOp(*pc) == JSOP_DEFCONST)
         attrs |= JSPROP_READONLY;
 
     MDefLexical* deflex = MDefLexical::New(alloc(), name, attrs);
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -3336,17 +3336,18 @@ SetPropertyIC::update(JSContext* cx, Han
             oldShape = expando->lastProperty();
     }
 
     // Set/Add the property on the object, the inlined cache are setup for the next execution.
     if (JSOp(*cache.pc()) == JSOP_INITGLEXICAL) {
         RootedScript script(cx);
         jsbytecode* pc;
         cache.getScriptedLocation(&script, &pc);
-        InitGlobalLexicalOperation(cx, script, pc, value);
+        MOZ_ASSERT(!script->hasNonSyntacticScope());
+        InitGlobalLexicalOperation(cx, &cx->global()->lexicalScope(), script, pc, value);
     } else {
         if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
             return false;
     }
 
     // A GC may have caused cache.value() to become stale as it is not traced.
     // In this case the IonScript will have been invalidated, so check for that.
     // Assert no further GC is possible past this point.
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -173,16 +173,37 @@ DefVar(JSContext* cx, HandlePropertyName
     RootedObject obj(cx, scopeChain);
     while (!obj->isQualifiedVarObj())
         obj = obj->enclosingScope();
 
     return DefVarOperation(cx, obj, dn, attrs);
 }
 
 bool
+DefLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain)
+{
+    // Find the extensible lexical scope.
+    Rooted<ClonedBlockObject*> lexical(cx, &NearestEnclosingExtensibleLexicalScope(scopeChain));
+
+    // Find the variables object.
+    RootedObject varObj(cx, scopeChain);
+    while (!varObj->isQualifiedVarObj())
+        varObj = varObj->enclosingScope();
+
+    return DefLexicalOperation(cx, lexical, varObj, dn, attrs);
+}
+
+bool
+DefGlobalLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs)
+{
+    Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
+    return DefLexicalOperation(cx, globalLexical, cx->global(), dn, attrs);
+}
+
+bool
 MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value)
 {
     if (!value.isObjectOrNull())
         return true;
 
     RootedObject newProto(cx, value.toObjectOrNull());
     return SetPrototype(cx, obj, newProto);
 }
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -584,16 +584,18 @@ bool InvokeFunction(JSContext* cx, Handl
 bool InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs,
                                     uint32_t numFormalArgs, Value* argv, MutableHandleValue rval);
 
 bool CheckOverRecursed(JSContext* cx);
 bool CheckOverRecursedWithExtra(JSContext* cx, BaselineFrame* frame,
                                 uint32_t extra, uint32_t earlyCheck);
 
 bool DefVar(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain);
+bool DefLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain);
+bool DefGlobalLexical(JSContext* cx, HandlePropertyName dn, unsigned attrs);
 bool MutatePrototype(JSContext* cx, HandlePlainObject obj, HandleValue value);
 bool InitProp(JSContext* cx, HandleObject obj, HandlePropertyName name, HandleValue value,
               jsbytecode* pc);
 
 template<bool Equal>
 bool LooselyEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res);
 
 template<bool Equal>
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1453,16 +1453,40 @@ JS_GetGlobalForObject(JSContext* cx, JSO
 }
 
 extern JS_PUBLIC_API(bool)
 JS_IsGlobalObject(JSObject* obj)
 {
     return obj->is<GlobalObject>();
 }
 
+extern JS_PUBLIC_API(JSObject*)
+JS_GlobalLexicalScope(JSObject* obj)
+{
+    return &obj->as<GlobalObject>().lexicalScope();
+}
+
+extern JS_PUBLIC_API(bool)
+JS_HasExtensibleLexicalScope(JSObject* obj)
+{
+    return obj->is<GlobalObject>() || obj->compartment()->getNonSyntacticLexicalScope(obj);
+}
+
+extern JS_PUBLIC_API(JSObject*)
+JS_ExtensibleLexicalScope(JSObject* obj)
+{
+    JSObject* lexical = nullptr;
+    if (obj->is<GlobalObject>())
+        lexical = JS_GlobalLexicalScope(obj);
+    else
+        lexical = obj->compartment()->getNonSyntacticLexicalScope(obj);
+    MOZ_ASSERT(lexical);
+    return lexical;
+}
+
 JS_PUBLIC_API(JSObject*)
 JS_GetGlobalForCompartmentOrNull(JSContext* cx, JSCompartment* c)
 {
     AssertHeapIsIdleOrIterating(cx);
     assertSameCompartment(cx, c);
     return c->maybeGlobal();
 }
 
@@ -3451,16 +3475,29 @@ CreateNonSyntacticScopeChain(JSContext* 
         // scopes to load scripts in, expects the dynamic scope chain to be
         // the holder of "var" declarations. In SpiderMonkey, such objects are
         // called "qualified varobjs", the "qualified" part meaning the
         // declaration was qualified by "var". There is only sadness.
         //
         // See JSObject::isQualifiedVarObj.
         if (!dynamicScopeObj->setQualifiedVarObj(cx))
             return false;
+
+        // Also get a non-syntactic lexical scope to capture 'let' and 'const'
+        // bindings. To persist lexical bindings, we have a 1-1 mapping with
+        // the final unwrapped dynamic scope object (the scope that stores the
+        // 'var' bindings) and the lexical scope.
+        //
+        // TODOshu: disallow the subscript loader from using non-distinguished
+        // objects as dynamic scopes.
+        dynamicScopeObj.set(
+            cx->compartment()->getOrCreateNonSyntacticLexicalScope(cx, staticScopeObj,
+                                                                   dynamicScopeObj));
+        if (!dynamicScopeObj)
+            return false;
     }
 
     return true;
 }
 
 static bool
 IsFunctionCloneable(HandleFunction fun)
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1567,16 +1567,25 @@ extern JS_PUBLIC_API(JSObject*)
 JS_GetErrorPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API(JSObject*)
 JS_GetGlobalForObject(JSContext* cx, JSObject* obj);
 
 extern JS_PUBLIC_API(bool)
 JS_IsGlobalObject(JSObject* obj);
 
+extern JS_PUBLIC_API(JSObject*)
+JS_GlobalLexicalScope(JSObject* obj);
+
+extern JS_PUBLIC_API(bool)
+JS_HasExtensibleLexicalScope(JSObject* obj);
+
+extern JS_PUBLIC_API(JSObject*)
+JS_ExtensibleLexicalScope(JSObject* obj);
+
 /*
  * May return nullptr, if |c| never had a global (e.g. the atoms compartment),
  * or if |c|'s global has been collected.
  */
 extern JS_PUBLIC_API(JSObject*)
 JS_GetGlobalForCompartmentOrNull(JSContext* cx, JSCompartment* c);
 
 namespace JS {
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -61,16 +61,17 @@ JSCompartment::JSCompartment(Zone* zone,
     regExps(runtime_),
     globalWriteBarriered(false),
     neuteredTypedObjects(0),
     objectMetadataState(ImmediateMetadata()),
     propertyTree(thisForCtor()),
     selfHostingScriptSource(nullptr),
     objectMetadataTable(nullptr),
     lazyArrayBuffers(nullptr),
+    nonSyntacticLexicalScopes_(nullptr),
     gcIncomingGrayPointers(nullptr),
     gcPreserveJitCode(options.preserveJitCode()),
     debugModeBits(0),
     rngState(0),
     watchpointMap(nullptr),
     scriptCountsMap(nullptr),
     debugScriptMap(nullptr),
     debugScopes(nullptr),
@@ -99,16 +100,17 @@ JSCompartment::~JSCompartment()
 
     js_delete(jitCompartment_);
     js_delete(watchpointMap);
     js_delete(scriptCountsMap);
     js_delete(debugScriptMap);
     js_delete(debugScopes);
     js_delete(objectMetadataTable);
     js_delete(lazyArrayBuffers);
+    js_delete(nonSyntacticLexicalScopes_),
     js_free(enumerators);
 
     runtime_->numCompartments--;
 }
 
 bool
 JSCompartment::init(JSContext* maybecx)
 {
@@ -489,16 +491,58 @@ JSCompartment::wrap(JSContext* cx, Mutab
     if (desc.hasSetterObject()) {
         if (!wrap(cx, desc.setterObject()))
             return false;
     }
 
     return wrap(cx, desc.value());
 }
 
+ClonedBlockObject*
+JSCompartment::getOrCreateNonSyntacticLexicalScope(JSContext* cx,
+                                                   HandleObject enclosingStatic,
+                                                   HandleObject enclosingScope)
+{
+    if (!nonSyntacticLexicalScopes_) {
+        nonSyntacticLexicalScopes_ = cx->new_<ObjectWeakMap>(cx);
+        if (!nonSyntacticLexicalScopes_ || !nonSyntacticLexicalScopes_->init())
+            return nullptr;
+    }
+
+    // The key is the unwrapped dynamic scope, as we may be creating different
+    // DynamicWithObject wrappers each time.
+    MOZ_ASSERT(!enclosingScope->as<DynamicWithObject>().isSyntactic());
+    RootedObject key(cx, &enclosingScope->as<DynamicWithObject>().object());
+    RootedObject lexicalScope(cx, nonSyntacticLexicalScopes_->lookup(key));
+
+    if (!lexicalScope) {
+        lexicalScope = ClonedBlockObject::createNonSyntactic(cx, enclosingStatic, enclosingScope);
+        if (!lexicalScope)
+            return nullptr;
+        if (!nonSyntacticLexicalScopes_->add(cx, key, lexicalScope))
+            return nullptr;
+    }
+
+    return &lexicalScope->as<ClonedBlockObject>();
+}
+
+ClonedBlockObject*
+JSCompartment::getNonSyntacticLexicalScope(JSObject* enclosingScope) const
+{
+    if (!nonSyntacticLexicalScopes_)
+        return nullptr;
+    if (!enclosingScope->is<DynamicWithObject>())
+        return nullptr;
+    JSObject* key = &enclosingScope->as<DynamicWithObject>().object();
+    JSObject* lexicalScope = nonSyntacticLexicalScopes_->lookup(key);
+    if (!lexicalScope)
+        return nullptr;
+    return &lexicalScope->as<ClonedBlockObject>();
+}
+
 void
 JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
 {
     MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
     MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting());
 
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
         Value v = e.front().value();
@@ -594,16 +638,19 @@ JSCompartment::traceRoots(JSTracer* trc,
         MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage());
         for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
             JSScript* script = const_cast<JSScript*>(r.front().key());
             MOZ_ASSERT(script->hasScriptCounts());
             TraceRoot(trc, &script, "profilingScripts");
             MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
         }
     }
+
+    if (nonSyntacticLexicalScopes_)
+        nonSyntacticLexicalScopes_->trace(trc);
 }
 
 void
 JSCompartment::sweepAfterMinorGC()
 {
     globalWriteBarriered = false;
 
     if (innerViews.needsSweepAfterMinorGC())
@@ -1047,32 +1094,35 @@ JSCompartment::addSizeOfIncludingThis(mo
                                       size_t* tiObjectTypeTables,
                                       size_t* compartmentObject,
                                       size_t* compartmentTables,
                                       size_t* innerViewsArg,
                                       size_t* lazyArrayBuffersArg,
                                       size_t* objectMetadataTablesArg,
                                       size_t* crossCompartmentWrappersArg,
                                       size_t* regexpCompartment,
-                                      size_t* savedStacksSet)
+                                      size_t* savedStacksSet,
+                                      size_t* nonSyntacticLexicalScopesArg)
 {
     *compartmentObject += mallocSizeOf(this);
     objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
                                         tiArrayTypeTables, tiObjectTypeTables,
                                         compartmentTables);
     *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf)
                         + initialShapes.sizeOfExcludingThis(mallocSizeOf);
     *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
     if (lazyArrayBuffers)
         *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
     if (objectMetadataTable)
         *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
     *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
     *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf);
     *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
+    if (nonSyntacticLexicalScopes_)
+        *nonSyntacticLexicalScopesArg += nonSyntacticLexicalScopes_->sizeOfIncludingThis(mallocSizeOf);
 }
 
 void
 JSCompartment::reportTelemetry()
 {
     // Only report telemetry for web content, not add-ons or chrome JS.
     if (addonId || isSystem_)
         return;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -24,16 +24,17 @@ namespace jit {
 class JitCompartment;
 } // namespace jit
 
 namespace gc {
 template<class Node> class ComponentFinder;
 } // namespace gc
 
 struct NativeIterator;
+class ClonedBlockObject;
 
 /*
  * A single-entry cache for some base-10 double-to-string conversions. This
  * helps date-format-xparb.js.  It also avoids skewing the results for
  * v8-splay.js when measured by the SunSpider harness, where the splay tree
  * initialization (which includes many repeated double-to-string conversions)
  * is erroneously included in the measurement; see bug 562553.
  */
@@ -392,17 +393,18 @@ struct JSCompartment
                                 size_t* tiObjectTypeTables,
                                 size_t* compartmentObject,
                                 size_t* compartmentTables,
                                 size_t* innerViews,
                                 size_t* lazyArrayBuffers,
                                 size_t* objectMetadataTables,
                                 size_t* crossCompartmentWrappers,
                                 size_t* regexpCompartment,
-                                size_t* savedStacksSet);
+                                size_t* savedStacksSet,
+                                size_t* nonSyntacticLexicalScopes);
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
 
     /* Set of all unowned base shapes in the compartment. */
     js::BaseShapeSet             baseShapes;
@@ -437,16 +439,23 @@ struct JSCompartment
     // Inline transparent typed objects do not initially have an array buffer,
     // but can have that buffer created lazily if it is accessed later. This
     // table manages references from such typed objects to their buffers.
     js::ObjectWeakMap* lazyArrayBuffers;
 
     // All unboxed layouts in the compartment.
     mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
 
+  private:
+    // All non-syntactic lexical scopes in the compartment. These are kept in
+    // a map because when loading scripts into a non-syntactic scope, we need
+    // to use the same lexical scope to persist lexical bindings.
+    js::ObjectWeakMap* nonSyntacticLexicalScopes_;
+
+  public:
     /* During GC, stores the index of this compartment in rt->compartments. */
     unsigned                     gcIndex;
 
     /*
      * During GC, stores the head of a list of incoming pointers from gray cells.
      *
      * The objects in the list are either cross-compartment wrappers, or
      * debugger wrapper objects.  The list link is either in the second extra
@@ -506,16 +515,21 @@ struct JSCompartment
     void removeWrapper(js::WrapperMap::Ptr p) {
         crossCompartmentWrappers.remove(p);
     }
 
     struct WrapperEnum : public js::WrapperMap::Enum {
         explicit WrapperEnum(JSCompartment* c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {}
     };
 
+    js::ClonedBlockObject* getOrCreateNonSyntacticLexicalScope(JSContext* cx,
+                                                               js::HandleObject enclosingStatic,
+                                                               js::HandleObject enclosingScope);
+    js::ClonedBlockObject* getNonSyntacticLexicalScope(JSObject* enclosingScope) const;
+
     /*
      * This method traces data that is live iff we know that this compartment's
      * global is still live.
      */
     void trace(JSTracer* trc);
     /*
      * This method traces JSCompartment-owned GC roots that are considered live
      * regardless of whether the JSCompartment itself is still live.
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2790,23 +2790,22 @@ typedef long
 //
 // Gecko must call SetJitExceptionFilter before any JIT code is compiled and
 // only once per process.
 extern JS_FRIEND_API(void)
 SetJitExceptionHandler(JitExceptionHandler handler);
 #endif
 
 /*
- * Get the object underlying the object environment (in the ES
- * NewObjectEnvironment) sense for a given function.  If the function is not
- * scripted or does not have an object environment, just returns the function's
- * parent.
+ * Get the nearest enclosing with scope object for a given function. If the
+ * function is not scripted or is not enclosed by a with scope, returns the
+ * global.
  */
 extern JS_FRIEND_API(JSObject*)
-GetObjectEnvironmentObjectForFunction(JSFunction* fun);
+GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun);
 
 /*
  * Get the first SavedFrame object in this SavedFrame stack whose principals are
  * subsumed by the cx's principals. If there is no such frame, return nullptr.
  *
  * Do NOT pass a non-SavedFrame object here.
  *
  * The savedFrame and cx do not need to be in the same compartment.
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6520,17 +6520,22 @@ EvaluateInEnv(JSContext* cx, Handle<Env*
     /*
      * Pass in a StaticEvalObject *not* linked to env for evalStaticScope, as
      * ScopeIter should stop at any non-ScopeObject or non-syntactic With
      * boundaries, and we are putting a DebugScopeProxy or non-syntactic With on
      * the scope chain.
      */
     Rooted<ScopeObject*> enclosingStaticScope(cx);
     if (!IsGlobalLexicalScope(env)) {
-        enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, nullptr);
+        // If we are doing a global evalWithBindings, we will still need to
+        // link the static global lexical scope to the static non-syntactic
+        // scope.
+        if (IsGlobalLexicalScope(env->enclosingScope()))
+            enclosingStaticScope = &cx->global()->lexicalScope().staticBlock();
+        enclosingStaticScope = StaticNonSyntacticScopeObjects::create(cx, enclosingStaticScope);
         if (!enclosingStaticScope)
             return false;
     } else {
         enclosingStaticScope = &cx->global()->lexicalScope().staticBlock();
     }
 
     // Do not consider executeInGlobal{WithBindings} as an eval, but instead
     // as executing a series of statements at the global level. This is to
@@ -6670,19 +6675,19 @@ DebuggerGenericEval(JSContext* cx, const
         if (!iter->computeThis(cx))
             return false;
         thisv = iter->computedThisValue();
         env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc());
         if (!env)
             return false;
     } else {
         /*
-         * Use the global as 'this'. If the global is an inner object, it
-         * should have a thisObject hook that returns the appropriate outer
-         * object.
+         * Use the global lexical scope as 'this'. If the global is an inner
+         * object, it should have a thisObject hook that returns the
+         * appropriate outer object.
          */
         JSObject* thisObj = GetThisObject(cx, scope);
         if (!thisObj)
             return false;
         thisv = ObjectValue(*thisObj);
         env = scope;
     }
 
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -295,29 +295,28 @@ SetNameOperation(JSContext* cx, JSScript
                                result);
     } else {
         ok = SetProperty(cx, scope, id, val, receiver, result);
     }
     return ok && result.checkStrictErrorOrWarning(cx, scope, id, strict);
 }
 
 inline bool
-DefLexicalOperation(JSContext* cx, HandlePropertyName name, unsigned attrs)
+DefLexicalOperation(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
+                    HandleObject varObj, HandlePropertyName name, unsigned attrs)
 {
-    Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
-
     // Due to the extensibility of the global lexical scope, we must check for
     // redeclaring a binding.
     mozilla::Maybe<frontend::Definition::Kind> redeclKind;
     RootedId id(cx, NameToId(name));
     RootedShape shape(cx);
-    if ((shape = globalLexical->lookup(cx, name))) {
+    if ((shape = lexicalScope->lookup(cx, name))) {
         redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET
                                                      : frontend::Definition::CONST);
-    } else if ((shape = cx->global()->lookup(cx, name))) {
+    } else if (varObj->isNative() && (shape = varObj->as<NativeObject>().lookup(cx, name))) {
         if (!shape->configurable())
             redeclKind = mozilla::Some(frontend::Definition::VAR);
     } else {
         Rooted<PropertyDescriptor> desc(cx);
         if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc))
             return false;
         if (desc.object() && desc.hasConfigurable() && !desc.configurable())
             redeclKind = mozilla::Some(frontend::Definition::VAR);
@@ -327,36 +326,45 @@ DefLexicalOperation(JSContext* cx, Handl
         return false;
     }
 
     RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL));
     return NativeDefineProperty(cx, lexicalScope, id, uninitialized, nullptr, nullptr, attrs);
 }
 
 inline bool
-DefLexicalOperation(JSContext* cx, JSScript* script, jsbytecode* pc)
+DefLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg,
+                    JSObject* varObjArg, JSScript* script, jsbytecode* pc)
 {
     MOZ_ASSERT(*pc == JSOP_DEFLET || *pc == JSOP_DEFCONST);
     RootedPropertyName name(cx, script->getName(pc));
 
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
     if (*pc == JSOP_DEFCONST)
         attrs |= JSPROP_READONLY;
 
-    return DefLexicalOperation(cx, name, attrs);
+    Rooted<ClonedBlockObject*> lexicalScope(cx, lexicalScopeArg);
+    RootedObject varObj(cx, varObjArg);
+    MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
+                  lexicalScope == &cx->global()->lexicalScope() && varObj == cx->global());
+
+    return DefLexicalOperation(cx, lexicalScope, varObj, name, attrs);
 }
 
 inline void
-InitGlobalLexicalOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleValue value)
+InitGlobalLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg,
+                           JSScript* script, jsbytecode* pc, HandleValue value)
 {
+    MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
+                  lexicalScopeArg == &cx->global()->lexicalScope());
     MOZ_ASSERT(*pc == JSOP_INITGLEXICAL);
-    Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
-    RootedShape shape(cx, globalLexical->lookup(cx, script->getName(pc)));
+    Rooted<ClonedBlockObject*> lexicalScope(cx, lexicalScopeArg);
+    RootedShape shape(cx, lexicalScope->lookup(cx, script->getName(pc)));
     MOZ_ASSERT(shape);
-    globalLexical->setSlot(shape->slot(), value);
+    lexicalScope->setSlot(shape->slot(), value);
 }
 
 inline bool
 InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
 {
     if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
         unsigned propAttrs = GetInitDataPropAttrs(op);
         return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3424,18 +3424,23 @@ CASE(JSOP_INITALIASEDLEXICAL)
     ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
     ScopeObject& obj = REGS.fp()->aliasedVarScope(sc);
     SetAliasedVarOperation(cx, script, REGS.pc, obj, sc, REGS.sp[-1], DontCheckLexical);
 }
 END_CASE(JSOP_INITALIASEDLEXICAL)
 
 CASE(JSOP_INITGLEXICAL)
 {
+    ClonedBlockObject* lexicalScope;
+    if (script->hasNonSyntacticScope())
+        lexicalScope = &REGS.fp()->extensibleLexicalScope();
+    else
+        lexicalScope = &cx->global()->lexicalScope();
     HandleValue value = REGS.stackHandleAt(-1);
-    InitGlobalLexicalOperation(cx, script, REGS.pc, value);
+    InitGlobalLexicalOperation(cx, lexicalScope, script, REGS.pc, value);
 }
 END_CASE(JSOP_INITGLEXICAL)
 
 CASE(JSOP_UNINITIALIZED)
     PUSH_UNINITIALIZED();
 END_CASE(JSOP_UNINITIALIZED)
 
 CASE(JSOP_GETARG)
@@ -3497,17 +3502,26 @@ CASE(JSOP_DEFVAR)
     if (!DefVarOperation(cx, obj, name, attrs))
         goto error;
 }
 END_CASE(JSOP_DEFVAR)
 
 CASE(JSOP_DEFCONST)
 CASE(JSOP_DEFLET)
 {
-    if (!DefLexicalOperation(cx, script, REGS.pc))
+    ClonedBlockObject* lexicalScope;
+    JSObject* varObj;
+    if (script->hasNonSyntacticScope()) {
+        lexicalScope = &REGS.fp()->extensibleLexicalScope();
+        varObj = &REGS.fp()->varObj();
+    } else {
+        lexicalScope = &cx->global()->lexicalScope();
+        varObj = cx->global();
+    }
+    if (!DefLexicalOperation(cx, lexicalScope, varObj, script, REGS.pc))
         goto error;
 }
 END_CASE(JSOP_DEFLET)
 
 CASE(JSOP_DEFFUN)
 {
     /*
      * A top-level function defined in Global or Eval code (see ECMA-262
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -337,17 +337,18 @@ StatsCompartmentCallback(JSRuntime* rt, 
                                         &cStats.typeInferenceObjectTypeTables,
                                         &cStats.compartmentObject,
                                         &cStats.compartmentTables,
                                         &cStats.innerViewsTable,
                                         &cStats.lazyArrayBuffersTable,
                                         &cStats.objectMetadataTable,
                                         &cStats.crossCompartmentWrappersTable,
                                         &cStats.regexpCompartment,
-                                        &cStats.savedStacksSet);
+                                        &cStats.savedStacksSet,
+                                        &cStats.nonSyntacticLexicalScopesTable);
 }
 
 static void
 StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena,
                    JS::TraceKind traceKind, size_t thingSize)
 {
     RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
 
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -11,16 +11,24 @@
 #include "frontend/SharedContext.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/TypeInference-inl.h"
 
 namespace js {
 
+inline ClonedBlockObject&
+NearestEnclosingExtensibleLexicalScope(JSObject* scope)
+{
+    while (!IsExtensibleLexicalScope(scope))
+        scope = scope->enclosingScope();
+    return scope->as<ClonedBlockObject>();
+}
+
 inline void
 ScopeObject::setAliasedVar(JSContext* cx, ScopeCoordinate sc, PropertyName* name, const Value& v)
 {
     MOZ_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
     JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == BlockObject::RESERVED_SLOTS);
 
     // name may be null if we don't need to track side effects on the object.
     MOZ_ASSERT_IF(isSingleton(), name);
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -777,29 +777,31 @@ StaticNonSyntacticScopeObjects::create(J
 
 const Class StaticNonSyntacticScopeObjects::class_ = {
     "StaticNonSyntacticScopeObjects",
     JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScopeObjects::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS
 };
 
 /* static */ NonSyntacticVariablesObject*
-NonSyntacticVariablesObject::create(JSContext* cx, Handle<GlobalObject*> global)
+NonSyntacticVariablesObject::create(JSContext* cx, Handle<ClonedBlockObject*> globalLexical)
 {
+    MOZ_ASSERT(globalLexical->isGlobal());
+
     Rooted<NonSyntacticVariablesObject*> obj(cx,
         NewObjectWithNullTaggedProto<NonSyntacticVariablesObject>(cx, TenuredObject,
                                                                   BaseShape::DELEGATE));
     if (!obj)
         return nullptr;
 
     MOZ_ASSERT(obj->isUnqualifiedVarObj());
     if (!obj->setQualifiedVarObj(cx))
         return nullptr;
 
-    obj->setEnclosingScope(global);
+    obj->setEnclosingScope(globalLexical);
     return obj;
 }
 
 const Class NonSyntacticVariablesObject::class_ = {
     "NonSyntacticVariablesObject",
     JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS
 };
@@ -865,16 +867,36 @@ ClonedBlockObject::createGlobal(JSContex
     if (!lexical)
         return nullptr;
     if (!JSObject::setSingleton(cx, lexical))
         return nullptr;
     return lexical;
 }
 
 /* static */ ClonedBlockObject*
+ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
+                                      HandleObject enclosingScope)
+{
+    MOZ_ASSERT(enclosingStatic->is<StaticNonSyntacticScopeObjects>());
+    MOZ_ASSERT(!IsSyntacticScope(enclosingScope));
+
+    Rooted<StaticBlockObject*> staticLexical(cx, StaticBlockObject::create(cx));
+    if (!staticLexical)
+        return nullptr;
+
+    staticLexical->setLocalOffset(UINT32_MAX);
+    staticLexical->initEnclosingScope(enclosingStatic);
+    Rooted<ClonedBlockObject*> lexical(cx, ClonedBlockObject::create(cx, staticLexical,
+                                                                     enclosingScope));
+    if (!lexical)
+        return nullptr;
+    return lexical;
+}
+
+/* static */ ClonedBlockObject*
 ClonedBlockObject::createHollowForDebug(JSContext* cx, Handle<StaticBlockObject*> block)
 {
     MOZ_ASSERT(!block->needsClone());
 
     // This scope's parent link is never used: the DebugScopeObject that
     // refers to this scope carries its own parent link, which is what
     // Debugger uses to construct the tree of Debugger.Environment objects. So
     // just parent this scope directly to the global lexical scope.
@@ -982,19 +1004,21 @@ StaticBlockObject::addVar(ExclusiveConte
                                              entry,
                                              /* allowDictionary = */ false);
 }
 
 static JSObject*
 block_ThisObject(JSContext* cx, HandleObject obj)
 {
     // No other block objects should ever get passed to the 'this' object
-    // hook.
-    MOZ_ASSERT(obj->as<ClonedBlockObject>().isGlobal());
-    MOZ_ASSERT(&obj->as<ClonedBlockObject>().enclosingScope() == cx->global());
+    // hook except the global lexical scope and non-syntactic ones.
+    MOZ_ASSERT(obj->as<ClonedBlockObject>().isGlobal() ||
+               !obj->as<ClonedBlockObject>().isSyntactic());
+    MOZ_ASSERT_IF(obj->as<ClonedBlockObject>().isGlobal(),
+                  obj->enclosingScope() == cx->global());
     RootedObject enclosing(cx, obj->enclosingScope());
     return GetThisObject(cx, enclosing);
 }
 
 const Class BlockObject::class_ = {
     "Block",
     JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) |
     JSCLASS_IS_ANONYMOUS,
@@ -1661,19 +1685,22 @@ class DebugScopeProxy : public BaseProxy
 
         /* Handle unaliased let and catch bindings at block scope. */
         if (scope->is<ClonedBlockObject>()) {
             Rooted<ClonedBlockObject*> block(cx, &scope->as<ClonedBlockObject>());
             Shape* shape = block->lastProperty()->search(cx, id);
             if (!shape)
                 return true;
 
-            // Currently consider all global lexical bindings to be aliased.
-            if (IsGlobalLexicalScope(block))
+            // Currently consider all global and non-syntactic top-level lexical
+            // bindings to be aliased.
+            if (block->isExtensible()) {
+                MOZ_ASSERT(IsGlobalLexicalScope(block) || !IsSyntacticScope(block));
                 return true;
+            }
 
             unsigned i = block->staticBlock().shapeToIndex(*shape);
             if (block->staticBlock().isAliased(i))
                 return true;
 
             if (maybeLiveScope) {
                 AbstractFramePtr frame = maybeLiveScope->frame();
                 uint32_t local = block->staticBlock().blockIndexToLocalIndex(i);
@@ -2828,23 +2855,26 @@ js::GetDebugScopeForFrame(JSContext* cx,
         return nullptr;
 
     ScopeIter si(cx, frame, pc);
     return GetDebugScope(cx, si);
 }
 
 // See declaration and documentation in jsfriendapi.h
 JS_FRIEND_API(JSObject*)
-js::GetObjectEnvironmentObjectForFunction(JSFunction* fun)
+js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun)
 {
     if (!fun->isInterpreted())
         return &fun->global();
 
     JSObject* env = fun->environment();
-    if (!env || !env->is<DynamicWithObject>())
+    while (env && !env->is<DynamicWithObject>())
+        env = env->enclosingScope();
+
+    if (!env)
         return &fun->global();
 
     return &env->as<DynamicWithObject>().object();
 }
 
 bool
 js::CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                     HandleObject dynamicTerminatingScope,
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -210,17 +210,17 @@ ScopeCoordinateFunctionScript(JSScript* 
  * Scope objects are technically real JSObjects but only belong on the scope
  * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
  * scope objects is:
  *
  *   JSObject                      Generic object
  *     |
  *   ScopeObject---+---+           Engine-internal scope
  *     |   |   |   |   |
- *     |   |   |   |  StaticNonSyntacticScopeObjects  See NB2
+ *     |   |   |   |  StaticNonSyntacticScopeObjects  See "Non-syntactic scope objects"
  *     |   |   |   |
  *     |   |   |  StaticEvalObject  Placeholder so eval scopes may be iterated through
  *     |   |   |
  *     |   |  DeclEnvObject         Holds name of recursive/needsCallObject named lambda
  *     |   |
  *     |  CallObject                Scope of entire function or strict eval
  *     |   |
  *     |  ModuleEnvironmentObject   Module top-level scope on run-time scope chain
@@ -242,19 +242,16 @@ ScopeCoordinateFunctionScript(JSScript* 
  * ScopeObject::enclosingScope() can simply access a fixed slot without further
  * dynamic type information.
  *
  * NB: Static block objects are a special case: these objects are created at
  * compile time to hold the shape/binding information from which block objects
  * are cloned at runtime. These objects should never escape into the wild and
  * support a restricted set of ScopeObject operations.
  *
- * NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic
- * DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject.
- *
  * See also "Debug scope objects" below.
  */
 
 class ScopeObject : public NativeObject
 {
   protected:
     static const uint32_t SCOPE_CHAIN_SLOT = 0;
 
@@ -454,50 +451,151 @@ class StaticEvalObject : public ScopeObj
 
     bool isStrict() const {
         return getReservedSlot(STRICT_SLOT).isTrue();
     }
 
     inline bool isNonGlobal() const;
 };
 
-// Static scope objects that stand in for one or more "polluting global"
-// scopes on the dynamic scope chain.
-//
-// There are two flavors of polluting global scopes on the dynamic scope
-// chain: either 0+ non-syntactic DynamicWithObjects, or 1
-// NonSyntacticVariablesObject, created exclusively in
-// js::ExecuteInGlobalAndReturnScope.
+/*
+ * Non-syntactic scope objects
+ *
+ * A non-syntactic scope is one that was not created due to source code. On
+ * the static scope chain, a single StaticNonSyntacticScopeObjects maps to 0+
+ * non-syntactic dynamic scope objects. This is contrasted with syntactic
+ * scopes, where each syntactic static scope corresponds to 0 or 1 dynamic
+ * scope objects.
+ *
+ * There are 3 kinds of dynamic non-syntactic scopes:
+ *
+ * 1. DynamicWithObject
+ *
+ *    When the embedding compiles or executes a script, it has the option to
+ *    pass in a vector of objects to be used as the initial scope chain. Each
+ *    of those objects is wrapped by a DynamicWithObject.
+ *
+ *    The innermost scope passed in by the embedding becomes a qualified
+ *    variables object that captures 'var' bindings. That is, it wraps the
+ *    holder object of 'var' bindings.
+ *
+ *    Does not hold 'let' or 'const' bindings.
+ *
+ * 2. NonSyntacticVariablesObject
+ *
+ *    When the embedding wants qualified 'var' bindings and unqualified
+ *    bareword assignments to go on a different object than the global
+ *    object. While any object can be made into a qualified variables object,
+ *    only the GlobalObject and NonSyntacticVariablesObject are considered
+ *    unqualified variables objects.
+ *
+ *    Unlike DynamicWithObjects, this object is itself the holder of 'var'
+ *    bindings.
+ *
+ *    Does not hold 'let' or 'const' bindings.
+ *
+ * 3. ClonedBlockObject
+ *
+ *    Each non-syntactic object used as a qualified variables object needs to
+ *    enclose a non-syntactic ClonedBlockObject to hold 'let' and 'const'
+ *    bindings. There is a bijection per compartment between the non-syntactic
+ *    variables objects and their non-syntactic ClonedBlockObjects.
+ *
+ *    Does not hold 'var' bindings.
+ *
+ * The embedding (Gecko) uses non-syntactic scopes for various things, some of
+ * which are detailed below. All scope chain listings below are, from top to
+ * bottom, outermost to innermost.
+ *
+ * A. Component loading
+ *
+ * Components may be loaded in "reuse loader global" mode, where to save on
+ * memory, all JSMs and JS-implemented XPCOM modules are loaded into a single
+ * global. Each individual JSMs are compiled as functions with their own
+ * FakeBackstagePass. They have the following dynamic scope chain:
+ *
+ *   BackstagePass global
+ *       |
+ *   Global lexical scope
+ *       |
+ *   DynamicWithObject wrapping FakeBackstagePass
+ *       |
+ *   Non-syntactic lexical scope
+ *
+ * B. Subscript loading
+ *
+ * Subscripts may be loaded into a target object. They have the following
+ * dynamic scope chain:
+ *
+ *   Loader global
+ *       |
+ *   Global lexical scope
+ *       |
+ *   DynamicWithObject wrapping target
+ *       |
+ *   ClonedBlockObject
+ *
+ * C. Frame scripts
+ *
+ * XUL frame scripts are always loaded with a NonSyntacticVariablesObject as a
+ * "polluting global". This is done exclusively in
+ * js::ExecuteInGlobalAndReturnScope.
+ *
+ *   Loader global
+ *       |
+ *   Global lexical scope
+ *       |
+ *   NonSyntacticVariablesObject
+ *       |
+ *   ClonedBlockObject
+ *
+ * D. XBL
+ *
+ * XBL methods are compiled as functions with XUL elements on the scope chain.
+ * For a chain of elements e0,...,eN:
+ *
+ *      ...
+ *       |
+ *   DynamicWithObject wrapping eN
+ *       |
+ *      ...
+ *       |
+ *   DynamicWithObject wrapping e0
+ *       |
+ *   ClonedBlockObject
+ *
+ */
 class StaticNonSyntacticScopeObjects : public ScopeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
 
     static StaticNonSyntacticScopeObjects* create(JSContext* cx, HandleObject enclosing);
 
     JSObject* enclosingScopeForStaticScopeIter() {
         return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     }
 };
 
 // A non-syntactic dynamic scope object that captures non-lexical
 // bindings. That is, a scope object that captures both qualified var
 // assignments and unqualified bareword assignments. Its parent is always the
-// real global.
+// global lexical scope.
 //
 // This is used in ExecuteInGlobalAndReturnScope and sits in front of the
 // global scope to capture 'var' and bareword asignments.
 class NonSyntacticVariablesObject : public ScopeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
 
-    static NonSyntacticVariablesObject* create(JSContext* cx, Handle<GlobalObject*> global);
+    static NonSyntacticVariablesObject* create(JSContext* cx,
+                                               Handle<ClonedBlockObject*> globalLexical);
 };
 
 class NestedScopeObject : public ScopeObject
 {
   public:
     /*
      * A refinement of enclosingScope that returns nullptr if the enclosing
      * scope is not a NestedScopeObject.
@@ -698,20 +796,24 @@ class StaticBlockObject : public BlockOb
      * A static block object is cloned (when entering the block) iff some
      * variable of the block isAliased.
      */
     bool needsClone() {
         return numVariables() > 0 && !getSlot(RESERVED_SLOTS).isFalse();
     }
 
     // Is this the static global lexical scope?
-    bool isGlobal() {
+    bool isGlobal() const {
         return !enclosingStaticScope();
     }
 
+    bool isSyntactic() const {
+        return !isExtensible() || isGlobal();
+    }
+
     /* Frontend-only functions ***********************************************/
 
     /* Initialization functions for above fields. */
     void setAliased(unsigned i, bool aliased) {
         MOZ_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
         setSlotValue(i, BooleanValue(aliased));
         if (aliased && !needsClone()) {
             setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE));
@@ -760,16 +862,19 @@ class ClonedBlockObject : public BlockOb
                                      HandleObject enclosing);
 
   public:
     static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockObject*> block,
                                      AbstractFramePtr frame);
 
     static ClonedBlockObject* createGlobal(JSContext* cx, Handle<GlobalObject*> global);
 
+    static ClonedBlockObject* createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
+                                                 HandleObject enclosingScope);
+
     static ClonedBlockObject* createHollowForDebug(JSContext* cx,
                                                    Handle<StaticBlockObject*> block);
 
     /* The static block from which this block was cloned. */
     StaticBlockObject& staticBlock() const {
         return getProto()->as<StaticBlockObject>();
     }
 
@@ -790,16 +895,20 @@ class ClonedBlockObject : public BlockOb
         return enclosingScope().is<GlobalObject>();
     }
 
     GlobalObject& global() const {
         MOZ_ASSERT(isGlobal());
         return enclosingScope().as<GlobalObject>();
     }
 
+    bool isSyntactic() const {
+        return !isExtensible() || isGlobal();
+    }
+
     /* Copy in all the unaliased formals and locals. */
     void copyUnaliasedValues(AbstractFramePtr frame);
 
     /*
      * Create a new ClonedBlockObject with the same enclosing scope and
      * variable values as this.
      */
     static ClonedBlockObject* clone(JSContext* cx, Handle<ClonedBlockObject*> block);
@@ -1174,19 +1283,35 @@ JSObject::is<js::StaticBlockObject>() co
     return is<js::BlockObject>() && !getProto();
 }
 
 namespace js {
 
 inline bool
 IsSyntacticScope(JSObject* scope)
 {
-    return scope->is<ScopeObject>() &&
-           (!scope->is<DynamicWithObject>() || scope->as<DynamicWithObject>().isSyntactic()) &&
-           !scope->is<NonSyntacticVariablesObject>();
+    if (!scope->is<ScopeObject>())
+        return false;
+
+    if (scope->is<DynamicWithObject>())
+        return scope->as<DynamicWithObject>().isSyntactic();
+
+    if (scope->is<ClonedBlockObject>())
+        return scope->as<ClonedBlockObject>().isSyntactic();
+
+    if (scope->is<NonSyntacticVariablesObject>())
+        return false;
+
+    return true;
+}
+
+inline bool
+IsExtensibleLexicalScope(JSObject* scope)
+{
+    return scope->is<ClonedBlockObject>() && scope->as<ClonedBlockObject>().isExtensible();
 }
 
 inline bool
 IsGlobalLexicalScope(JSObject* scope)
 {
     return scope->is<ClonedBlockObject>() && scope->as<ClonedBlockObject>().isGlobal();
 }
 
@@ -1229,23 +1354,22 @@ ScopeIter::hasSyntacticScopeObject() con
 {
     return ssi_.hasSyntacticDynamicScopeObject();
 }
 
 inline bool
 ScopeIter::hasNonSyntacticScopeObject() const
 {
     // The case we're worrying about here is a NonSyntactic static scope which
-    // has 0+ corresponding non-syntactic DynamicWithObject scopes or a
-    // NonSyntacticVariablesObject.
+    // has 0+ corresponding non-syntactic DynamicWithObject scopes, a
+    // NonSyntacticVariablesObject, or a non-syntactic ClonedBlockObject.
     if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
         MOZ_ASSERT_IF(scope_->is<DynamicWithObject>(),
                       !scope_->as<DynamicWithObject>().isSyntactic());
-        return scope_->is<DynamicWithObject>() ||
-               scope_->is<NonSyntacticVariablesObject>();
+        return scope_->is<ScopeObject>() && !IsSyntacticScope(scope_);
     }
     return false;
 }
 
 inline bool
 ScopeIter::hasAnyScopeObject() const
 {
     return hasSyntacticScopeObject() || hasNonSyntacticScopeObject();
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -57,24 +57,30 @@ InterpreterFrame::scopeChain() const
 
 inline GlobalObject&
 InterpreterFrame::global() const
 {
     return scopeChain()->global();
 }
 
 inline JSObject&
-InterpreterFrame::varObj()
+InterpreterFrame::varObj() const
 {
     JSObject* obj = scopeChain();
     while (!obj->isQualifiedVarObj())
         obj = obj->enclosingScope();
     return *obj;
 }
 
+inline ClonedBlockObject&
+InterpreterFrame::extensibleLexicalScope() const
+{
+    return NearestEnclosingExtensibleLexicalScope(scopeChain());
+}
+
 inline JSCompartment*
 InterpreterFrame::compartment() const
 {
     MOZ_ASSERT(scopeChain()->compartment() == script()->compartment());
     return scopeChain()->compartment();
 }
 
 inline void
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -139,17 +139,21 @@ InterpreterFrame::createRestParameter(JS
 static inline void
 AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* scope)
 {
 #ifdef DEBUG
     RootedObject originalScope(cx, scope);
     RootedObject enclosingScope(cx, script->enclosingStaticScope());
     for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
         if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) {
-            while (scope->is<DynamicWithObject>() || scope->is<NonSyntacticVariablesObject>()) {
+            while (scope->is<DynamicWithObject>() ||
+                   scope->is<NonSyntacticVariablesObject>() ||
+                   (scope->is<ClonedBlockObject>() &&
+                    !scope->as<ClonedBlockObject>().isSyntactic()))
+            {
                 MOZ_ASSERT(!IsSyntacticScope(scope));
                 scope = &scope->as<ScopeObject>().enclosingScope();
             }
         } else if (i.hasSyntacticDynamicScopeObject()) {
             switch (i.type()) {
               case StaticScopeIter<NoGC>::Module:
                 MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().script() == i.moduleScript());
                 scope = &scope->as<ModuleEnvironmentObject>().enclosingScope();
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -40,16 +40,17 @@ class AsmJSModule;
 class InterpreterRegs;
 class CallObject;
 class FrameIter;
 class ScopeObject;
 class ScriptFrameIter;
 class SPSProfiler;
 class InterpreterFrame;
 class StaticBlockObject;
+class ClonedBlockObject;
 
 class ScopeCoordinate;
 
 class SavedFrame;
 
 namespace jit {
 class CommonFrameLayout;
 }
@@ -611,17 +612,18 @@ class InterpreterFrame
      * discard the script's global variables.
      */
 
     inline HandleObject scopeChain() const;
 
     inline ScopeObject& aliasedVarScope(ScopeCoordinate sc) const;
     inline GlobalObject& global() const;
     inline CallObject& callObj() const;
-    inline JSObject& varObj();
+    inline JSObject& varObj() const;
+    inline ClonedBlockObject& extensibleLexicalScope() const;
 
     inline void pushOnScopeChain(ScopeObject& scope);
     inline void popOffScopeChain();
     inline void replaceInnermostScope(ScopeObject& scope);
 
     /*
      * For blocks with aliased locals, these interfaces push and pop entries on
      * the scope chain.  The "freshen" operation replaces the current block
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2561,19 +2561,21 @@ UpdatePropertyType(ExclusiveContext* cx,
         const Value& value = obj->getSlot(shape->slot());
 
         /*
          * Don't add initial undefined types for properties of global objects
          * that are not collated into the JSID_VOID property (see propertySet
          * comment).
          *
          * Also don't add untracked values (initial uninitialized lexical
-         * magic values and optimized out values) as appearing in CallObjects.
+         * magic values and optimized out values) as appearing in CallObjects
+         * and the global lexical scope.
          */
-        MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value), obj->is<CallObject>());
+        MOZ_ASSERT_IF(TypeSet::IsUntrackedValue(value),
+                      obj->is<CallObject>() || IsExtensibleLexicalScope(obj));
         if ((indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) &&
             !TypeSet::IsUntrackedValue(value))
         {
             TypeSet::Type type = TypeSet::GetValueType(value);
             types->TypeSet::addType(type, &cx->typeLifoAlloc());
             types->postWriteBarrier(cx, type);
         }
 
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -320,16 +320,35 @@ mozJSComponentLoader::ReallyInit()
     rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mInitialized = true;
 
     return NS_OK;
 }
 
+// For terrible compatibility reasons, we need to consider both the global
+// lexical scope and the global of modules when searching for exported
+// symbols.
+static JSObject*
+ResolveModuleObjectProperty(JSContext* aCx, HandleObject aModObj, const char* name)
+{
+    if (JS_HasExtensibleLexicalScope(aModObj)) {
+        RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj));
+        bool found;
+        if (!JS_HasOwnProperty(aCx, lexical, name, &found)) {
+            return nullptr;
+        }
+        if (found) {
+            return lexical;
+        }
+    }
+    return aModObj;
+}
+
 const mozilla::Module*
 mozJSComponentLoader::LoadModule(FileLocation& aFile)
 {
     nsCOMPtr<nsIFile> file = aFile.GetBaseFile();
 
     nsCString spec;
     aFile.GetURIString(spec);
     ComponentLoaderInfo info(spec);
@@ -367,19 +386,22 @@ mozJSComponentLoader::LoadModule(FileLoc
     nsCOMPtr<nsIComponentManager> cm;
     rv = NS_GetComponentManager(getter_AddRefs(cm));
     if (NS_FAILED(rv))
         return nullptr;
 
     JSAutoCompartment ac(cx, entry->obj);
     RootedObject entryObj(cx, entry->obj);
 
+    RootedObject NSGetFactoryHolder(cx, ResolveModuleObjectProperty(cx, entryObj, "NSGetFactory"));
     RootedValue NSGetFactory_val(cx);
-    if (!JS_GetProperty(cx, entryObj, "NSGetFactory", &NSGetFactory_val) ||
-        NSGetFactory_val.isUndefined()) {
+    if (!NSGetFactoryHolder ||
+        !JS_GetProperty(cx, NSGetFactoryHolder, "NSGetFactory", &NSGetFactory_val) ||
+        NSGetFactory_val.isUndefined())
+    {
         return nullptr;
     }
 
     if (JS_TypeOfValue(cx, NSGetFactory_val) != JSTYPE_FUNCTION) {
         JS_ReportError(cx, "%s has NSGetFactory property that is not a function",
                        spec.get());
         return nullptr;
     }
@@ -419,17 +441,17 @@ mozJSComponentLoader::FindTargetObject(J
                                        MutableHandleObject aTargetObject)
 {
     aTargetObject.set(nullptr);
 
     RootedObject targetObject(aCx);
     if (mReuseLoaderGlobal) {
         JSFunction* fun = js::GetOutermostEnclosingFunctionOfScriptedCaller(aCx);
         if (fun) {
-            JSObject* funParent = js::GetObjectEnvironmentObjectForFunction(fun);
+            JSObject* funParent = js::GetNearestEnclosingWithScopeObjectForFunction(fun);
             if (JS_GetClass(funParent) == &kFakeBackstagePassJSClass)
                 targetObject = funParent;
         }
     }
 
     // The above could fail, even if mReuseLoaderGlobal, if the scripted
     // caller is not a component/JSM (it could be a DOM scope, for
     // instance).
@@ -959,16 +981,19 @@ mozJSComponentLoader::UnloadModules()
 
         dom::AutoJSAPI jsapi;
         jsapi.Init();
         jsapi.TakeOwnershipOfErrorReporting();
         JSContext* cx = jsapi.cx();
         RootedObject global(cx, mLoaderGlobal->GetJSObject());
         if (global) {
             JSAutoCompartment ac(cx, global);
+            if (JS_HasExtensibleLexicalScope(global)) {
+                JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalScope(global));
+            }
             JS_SetAllNonReservedSlotsToUndefined(cx, global);
         } else {
             NS_WARNING("Going to leak!");
         }
 
         mLoaderGlobal = nullptr;
     }
 
@@ -1069,16 +1094,32 @@ mozJSComponentLoader::IsModuleLoaded(con
     ComponentLoaderInfo info(aLocation);
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
 
     *retval = !!mImports.Get(info.Key());
     return NS_OK;
 }
 
+static JSObject*
+ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id)
+{
+    if (JS_HasExtensibleLexicalScope(aModObj)) {
+        RootedObject lexical(aCx, JS_ExtensibleLexicalScope(aModObj));
+        bool found;
+        if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) {
+            return nullptr;
+        }
+        if (found) {
+            return lexical;
+        }
+    }
+    return aModObj;
+}
+
 nsresult
 mozJSComponentLoader::ImportInto(const nsACString& aLocation,
                                  HandleObject targetObj,
                                  JSContext* callercx,
                                  MutableHandleObject vp)
 {
     vp.set(nullptr);
 
@@ -1173,18 +1214,20 @@ mozJSComponentLoader::ImportInto(const n
         // not an AutoEntryScript.
         dom::AutoJSAPI jsapi;
         jsapi.Init();
         jsapi.TakeOwnershipOfErrorReporting();
         JSContext* cx = jsapi.cx();
         JSAutoCompartment ac(cx, mod->obj);
 
         RootedValue symbols(cx);
-        RootedObject modObj(cx, mod->obj);
-        if (!JS_GetProperty(cx, modObj,
+        RootedObject exportedSymbolsHolder(cx, ResolveModuleObjectProperty(cx, mod->obj,
+                                                                           "EXPORTED_SYMBOLS"));
+        if (!exportedSymbolsHolder ||
+            !JS_GetProperty(cx, exportedSymbolsHolder,
                             "EXPORTED_SYMBOLS", &symbols)) {
             return ReportOnCaller(cxhelper, ERROR_NOT_PRESENT,
                                   PromiseFlatCString(aLocation).get());
         }
 
         bool isArray;
         if (!JS_IsArrayObject(cx, symbols, &isArray)) {
             return NS_ERROR_FAILURE;
@@ -1205,26 +1248,28 @@ mozJSComponentLoader::ImportInto(const n
         }
 
 #ifdef DEBUG
         nsAutoCString logBuffer;
 #endif
 
         RootedValue value(cx);
         RootedId symbolId(cx);
+        RootedObject symbolHolder(cx);
         for (uint32_t i = 0; i < symbolCount; ++i) {
             if (!JS_GetElement(cx, symbolsObj, i, &value) ||
                 !value.isString() ||
                 !JS_ValueToId(cx, value, &symbolId)) {
                 return ReportOnCaller(cxhelper, ERROR_ARRAY_ELEMENT,
                                       PromiseFlatCString(aLocation).get(), i);
             }
 
-            RootedObject modObj(cx, mod->obj);
-            if (!JS_GetPropertyById(cx, modObj, symbolId, &value)) {
+            symbolHolder = ResolveModuleObjectPropertyById(cx, mod->obj, symbolId);
+            if (!symbolHolder ||
+                !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) {
                 JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId));
                 if (!bytes)
                     return NS_ERROR_FAILURE;
                 return ReportOnCaller(cxhelper, ERROR_GETTING_SYMBOL,
                                       PromiseFlatCString(aLocation).get(),
                                       bytes.ptr());
             }
 
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -107,16 +107,19 @@ class mozJSComponentLoader : public mozi
 
         void Clear() {
             getfactoryobj = nullptr;
 
             if (obj) {
                 mozilla::AutoJSContext cx;
                 JSAutoCompartment ac(cx, obj);
 
+                if (JS_HasExtensibleLexicalScope(obj)) {
+                    JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalScope(obj));
+                }
                 JS_SetAllNonReservedSlotsToUndefined(cx, obj);
                 obj = nullptr;
                 thisObjectKey = nullptr;
             }
 
             if (location)
                 free(location);
 
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -1551,16 +1551,17 @@ XRE_XPCShellMain(int argc, char** argv, 
                     } else {
                         result = EXITCODE_RUNTIME_ERROR;
                     }
                 }
             }
 
             JS_DropPrincipals(rt, gJSPrincipals);
             JS_SetAllNonReservedSlotsToUndefined(cx, glob);
+            JS_SetAllNonReservedSlotsToUndefined(cx, JS_GlobalLexicalScope(glob));
             JS_GC(rt);
         }
         JS_GC(rt);
     } // this scopes the nsCOMPtrs
 
     if (!XRE_ShutdownTestShell())
         NS_ERROR("problem shutting down testshell");
 
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -328,17 +328,17 @@ function _register_protocol_handlers() {
 
   let curDirURI = ios.newFileURI(do_get_cwd());
   protocolHandler.setSubstitution("test", curDirURI);
 
   _register_modules_protocol_handler();
 }
 
 function _register_modules_protocol_handler() {
-  if (!this._TESTING_MODULES_DIR) {
+  if (!_TESTING_MODULES_DIR) {
     throw new Error("Please define a path where the testing modules can be " +
                     "found in a variable called '_TESTING_MODULES_DIR' before " +
                     "head.js is included.");
   }
 
   let ios = Components.classes["@mozilla.org/network/io-service;1"]
                       .getService(Components.interfaces.nsIIOService);
   let protocolHandler =
@@ -1222,17 +1222,17 @@ function do_load_child_test_harness()
       + "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
       + "const _MOZINFO_JS_PATH=" + uneval(_MOZINFO_JS_PATH) + "; "
       + "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
       + "const _TEST_NAME=" + uneval(_TEST_NAME) + "; "
       // We'll need more magic to get the debugger working in the child
       + "const _JSDEBUGGER_PORT=0; "
       + "const _XPCSHELL_PROCESS='child';";
 
-  if (this._TESTING_MODULES_DIR) {
+  if (_TESTING_MODULES_DIR) {
     command += " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
   }
 
   command += " load(_HEAD_JS_PATH);";
   sendCommand(command);
 }
 
 /**