Bug 1186409 - Use a single global for all JSMs. r=kmag draft
authorAndrew McCreight <continuation@gmail.com>
Tue, 18 Jul 2017 14:47:27 -0700
changeset 661539 b10c12c1eb1d64d3260c0143cb522b86ac0a01a1
parent 661538 8c3a38ce9f74a0c8eec219ee6ac71cd72060313a
child 661540 3faf73f50096f4870ebcd6dfe934dc9099141fda
push id78818
push userbmo:continuation@gmail.com
push dateFri, 08 Sep 2017 18:08:50 +0000
reviewerskmag
bugs1186409
milestone57.0a1
Bug 1186409 - Use a single global for all JSMs. r=kmag This patch adds a preference jsloader.shareGlobal that makes it so that JSMs share a single global, in order to reduce memory usage. The pref is disabled by default, and will be enabled in a later bug. Each JSM gets its own NonSyntacticVariablesObject (NSVO), which is used for top level variable bindings and as the value of |this| within the JSM. For the module loader, the main change is setting up the shared global, and the NSVO for each JSM. A number of files are blacklisted from the shared global, because they do things to the global that would interfer with other JSMs. This is detailed in mozJSComponentLoader::ReuseGlobal(). MozReview-Commit-ID: 3qVAc1c5aMI
dom/ipc/ContentPrefs.cpp
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/loader/mozJSComponentLoader.h
js/xpconnect/loader/mozJSSubScriptLoader.cpp
js/xpconnect/loader/mozJSSubScriptLoader.h
modules/libpref/init/all.js
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -112,16 +112,17 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
   "javascript.options.wasm",
   "javascript.options.wasm_baselinejit",
   "javascript.options.wasm_ionjit",
   "javascript.options.werror",
   "javascript.use_us_english_locale",
+  "jsloader.shareGlobal",
   "layout.idle_period.required_quiescent_frames",
   "layout.idle_period.time_limit",
   "layout.interruptible-reflow.enabled",
   "mathml.disabled",
   "media.apple.forcevda",
   "media.clearkey.persistent-license.enabled",
   "media.cubeb.backend",
   "media.cubeb.sandbox",
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -196,17 +196,19 @@ ReportOnCallerUTF8(JSCLContextHelper& he
     return NS_OK;
 }
 
 mozJSComponentLoader::mozJSComponentLoader()
     : mModules(16),
       mImports(16),
       mInProgressImports(16),
       mLocations(16),
-      mInitialized(false)
+      mInitialized(false),
+      mShareLoaderGlobal(false),
+      mLoaderGlobal(dom::RootingCx())
 {
     MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton");
 
     sSelf = this;
 }
 
 #define ENSURE_DEP(name) { nsresult rv = Ensure##name(); NS_ENSURE_SUCCESS(rv, rv); }
 #define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__));
@@ -294,16 +296,18 @@ mozJSComponentLoader::sSelf;
 NS_IMPL_ISUPPORTS(mozJSComponentLoader,
                   mozilla::ModuleLoader,
                   xpcIJSModuleLoader,
                   nsIObserver)
 
 nsresult
 mozJSComponentLoader::ReallyInit()
 {
+    mShareLoaderGlobal = Preferences::GetBool("jsloader.shareGlobal");
+
     nsresult rv;
     nsCOMPtr<nsIObserverService> obsSvc =
         do_GetService(kObserverServiceContractID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -423,17 +427,23 @@ mozJSComponentLoader::LoadModule(FileLoc
     // The hash owns the ModuleEntry now, forget about it
     return entry.forget();
 }
 
 void
 mozJSComponentLoader::FindTargetObject(JSContext* aCx,
                                        MutableHandleObject aTargetObject)
 {
-    aTargetObject.set(CurrentGlobalOrNull(aCx));
+    aTargetObject.set(js::GetJSMEnvironmentOfScriptedCaller(aCx));
+
+    // The above could fail if the scripted caller is not a
+    // component/JSM (it could be a DOM scope, for instance).
+    if (!aTargetObject) {
+        aTargetObject.set(CurrentGlobalOrNull(aCx));
+    }
 }
 
 // This requires that the keys be strings and the values be pointers.
 template <class Key, class Data, class UserData>
 static size_t
 SizeOfTableExcludingThis(const nsBaseHashtable<Key, Data, UserData>& aTable,
                          MallocSizeOf aMallocSizeOf)
 {
@@ -503,38 +513,98 @@ mozJSComponentLoader::CreateLoaderGlobal
 
     // Set the location information for the new global, so that tools like
     // about:memory may use that information
     xpc::SetLocationForGlobal(global, aLocation);
 
     aGlobal.set(global);
 }
 
+bool
+mozJSComponentLoader::ReuseGlobal(bool aIsAddon, nsIURI* aURI)
+{
+    if (aIsAddon || !mShareLoaderGlobal)
+        return false;
+
+    nsCString spec;
+    NS_ENSURE_SUCCESS(aURI->GetSpec(spec), false);
+
+    // The loader calls Object.freeze on global properties, which
+    // causes problems if the global is shared with other code.
+    if (spec.EqualsASCII("resource://gre/modules/commonjs/toolkit/loader.js")) {
+        return false;
+    }
+
+    // Various tests call addDebuggerToGlobal on the result of
+    // importing this JSM, which would be annoying to fix.
+    if (spec.EqualsASCII("resource://gre/modules/jsdebugger.jsm")) {
+        return false;
+    }
+
+    // Some SpecialPowers jsms call Cu.forcePermissiveCOWs(),
+    // which sets a per-compartment flag that disables certain
+    // security wrappers, so don't use the shared global for them
+    // to avoid breaking tests.
+    if (FindInReadable(NS_LITERAL_CSTRING("chrome://specialpowers/"), spec)) {
+        return false;
+    }
+
+    return true;
+}
+
 JSObject*
 mozJSComponentLoader::PrepareObjectForLocation(JSContext* aCx,
                                                nsIFile* aComponentFile,
                                                nsIURI* aURI,
+                                               bool* aReuseGlobal,
                                                bool* aRealFile)
 {
     nsAutoCString nativePath;
     NS_ENSURE_SUCCESS(aURI->GetSpec(nativePath), nullptr);
 
+    JSAddonId* addonId = MapURIToAddonID(aURI);
+    bool reuseGlobal = ReuseGlobal(!!addonId, aURI);
+
+    *aReuseGlobal = reuseGlobal;
+
+    bool createdNewGlobal = false;
     RootedObject globalObj(aCx);
+    if (reuseGlobal)
+        globalObj = mLoaderGlobal;
 
-    CreateLoaderGlobal(aCx, nativePath, MapURIToAddonID(aURI), &globalObj);
+    if (!globalObj) {
+        nsAutoCString globalLocation;
+        if (reuseGlobal) {
+            globalLocation.AssignLiteral("shared JSM global");
+        } else {
+            globalLocation.Assign(nativePath);
+        }
+
+        CreateLoaderGlobal(aCx, globalLocation,
+                           addonId, &globalObj);
+        NS_ENSURE_TRUE(globalObj, nullptr);
+
+        if (reuseGlobal) {
+            mLoaderGlobal = globalObj;
+        }
+
+        createdNewGlobal = true;
+    }
 
     // |thisObj| is the object we set properties on for a particular .jsm.
-    // XXX Right now, thisObj is always globalObj, but if we start
-    // sharing globals between jsms, they won't be the same.
-    // See bug 1186409.
     RootedObject thisObj(aCx, globalObj);
     NS_ENSURE_TRUE(thisObj, nullptr);
 
     JSAutoCompartment ac(aCx, thisObj);
 
+    if (reuseGlobal) {
+        thisObj = js::NewJSMEnvironment(aCx);
+        NS_ENSURE_TRUE(thisObj, nullptr);
+    }
+
     *aRealFile = false;
 
     // need to be extra careful checking for URIs pointing to files
     // EnsureFile may not always get called, especially on resource URIs
     // so we need to call GetFile to make sure this is a valid file
     nsresult rv = NS_OK;
     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
     nsCOMPtr<nsIFile> testFile;
@@ -562,17 +632,17 @@ mozJSComponentLoader::PrepareObjectForLo
     // Expose the URI from which the script was imported through a special
     // variable that we insert into the JSM.
     RootedString exposedUri(aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length()));
     NS_ENSURE_TRUE(exposedUri, nullptr);
 
     if (!JS_DefineProperty(aCx, thisObj, "__URI__", exposedUri, 0))
         return nullptr;
 
-    {
+    if (createdNewGlobal) {
         // AutoEntryScript required to invoke debugger hook, which is a
         // Gecko-specific concept at present.
         dom::AutoEntryScript aes(globalObj,
                                  "component loader report global");
         JS_FireOnNewGlobalObject(aes.cx(), globalObj);
     }
 
     return thisObj;
@@ -591,20 +661,21 @@ mozJSComponentLoader::ObjectForLocation(
 
     dom::AutoJSAPI jsapi;
     jsapi.Init();
     JSContext* cx = jsapi.cx();
 
     bool realFile = false;
     nsresult rv = aInfo.EnsureURI();
     NS_ENSURE_SUCCESS(rv, rv);
+    bool reuseGlobal = false;
     RootedObject obj(cx, PrepareObjectForLocation(cx, aComponentFile, aInfo.URI(),
-                                                  &realFile));
+                                                  &reuseGlobal, &realFile));
     NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);
-    MOZ_ASSERT(JS_IsGlobalObject(obj));
+    MOZ_ASSERT(JS_IsGlobalObject(obj) == !reuseGlobal);
 
     JSAutoCompartment ac(cx, obj);
 
     RootedScript script(cx);
 
     nsAutoCString nativePath;
     rv = aInfo.URI()->GetSpec(nativePath);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -657,17 +728,20 @@ mozJSComponentLoader::ObjectForLocation(
 
         if (realFile) {
             AutoMemMap map;
             MOZ_TRY(map.init(aComponentFile));
 
             // Note: exceptions will get handled further down;
             // don't early return for them here.
             auto buf = map.get<char>();
-            Compile(cx, options, buf.get(), map.size(), &script);
+            if (reuseGlobal)
+                CompileForNonSyntacticScope(cx, options, buf.get(), map.size(), &script);
+            else
+                Compile(cx, options, buf.get(), map.size(), &script);
         } else {
             rv = aInfo.EnsureScriptChannel();
             NS_ENSURE_SUCCESS(rv, rv);
             nsCOMPtr<nsIInputStream> scriptStream;
             rv = NS_MaybeOpenChannelUsingOpen2(aInfo.ScriptChannel(),
                    getter_AddRefs(scriptStream));
             NS_ENSURE_SUCCESS(rv, rv);
 
@@ -688,17 +762,20 @@ mozJSComponentLoader::ObjectForLocation(
 
             /* read the file in one swoop */
             rv = scriptStream->Read(buf.get(), len, &bytesRead);
             if (bytesRead != len)
                 return NS_BASE_STREAM_OSERROR;
 
             buf[len] = '\0';
 
-            Compile(cx, options, buf.get(), bytesRead, &script);
+            if (reuseGlobal)
+                CompileForNonSyntacticScope(cx, options, buf.get(), bytesRead, &script);
+            else
+                Compile(cx, options, buf.get(), bytesRead, &script);
         }
         // Propagate the exception, if one exists. Also, don't leave the stale
         // exception on this context.
         if (!script && aPropagateExceptions && jsapi.HasException()) {
             if (!jsapi.StealException(aException))
                 return NS_ERROR_OUT_OF_MEMORY;
         }
     }
@@ -731,18 +808,26 @@ mozJSComponentLoader::ObjectForLocation(
 
     {   // Scope for AutoEntryScript
 
         // We're going to run script via JS_ExecuteScript, so we need an
         // AutoEntryScript. This is Gecko-specific and not in any spec.
         dom::AutoEntryScript aes(CurrentGlobalOrNull(cx),
                                  "component loader load module");
         JSContext* aescx = aes.cx();
-        JS::RootedValue rval(cx);
-        if (!JS::CloneAndExecuteScript(aescx, script, &rval)) {
+
+        bool executeOk = false;
+        if (JS_IsGlobalObject(obj)) {
+            JS::RootedValue rval(cx);
+            executeOk = JS::CloneAndExecuteScript(aescx, script, &rval);
+        } else {
+            executeOk = js::ExecuteInJSMEnvironment(aescx, script, obj);
+        }
+
+        if (!executeOk) {
             if (aPropagateExceptions && aes.HasException()) {
                 // Ignore return value because we're returning an error code
                 // anyway.
                 Unused << aes.StealException(aException);
             }
             aObject.set(nullptr);
             aTableScript.set(nullptr);
             return NS_ERROR_FAILURE;
@@ -760,16 +845,28 @@ mozJSComponentLoader::ObjectForLocation(
     return NS_OK;
 }
 
 void
 mozJSComponentLoader::UnloadModules()
 {
     mInitialized = false;
 
+    if (mLoaderGlobal) {
+        dom::AutoJSAPI jsapi;
+        jsapi.Init();
+        JSContext* cx = jsapi.cx();
+        RootedObject global(cx, mLoaderGlobal);
+        JSAutoCompartment ac(cx, global);
+        MOZ_ASSERT(JS_HasExtensibleLexicalEnvironment(global));
+        JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalEnvironment(global));
+        JS_SetAllNonReservedSlotsToUndefined(cx, global);
+        mLoaderGlobal = nullptr;
+    }
+
     mInProgressImports.Clear();
     mImports.Clear();
     mLocations.Clear();
 
     for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
         iter.Data()->Clear();
         iter.Remove();
     }
@@ -1125,16 +1222,19 @@ mozJSComponentLoader::Unload(const nsACS
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
     ModuleEntry* mod;
     if (mImports.Get(info.Key(), &mod)) {
         mLocations.Remove(mod->resolvedURL);
         mImports.Remove(info.Key());
     }
 
+    // If this is the last module to be unloaded, we will leak mLoaderGlobal
+    // until UnloadModules is called. So be it.
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozJSComponentLoader::Observe(nsISupports* subject, const char* topic,
                               const char16_t* data)
 {
     if (!strcmp(topic, "xpcom-shutdown-loaders")) {
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -67,19 +67,22 @@ class mozJSComponentLoader : public mozi
     nsresult ReallyInit();
     void UnloadModules();
 
     void CreateLoaderGlobal(JSContext* aCx,
                             nsACString& aLocation,
                             JSAddonId* aAddonID,
                             JS::MutableHandleObject aGlobal);
 
+    bool ReuseGlobal(bool aIsAddon, nsIURI* aComponent);
+
     JSObject* PrepareObjectForLocation(JSContext* aCx,
                                        nsIFile* aComponentFile,
                                        nsIURI* aComponent,
+                                       bool* aReuseGlobal,
                                        bool* aRealFile);
 
     nsresult ObjectForLocation(ComponentLoaderInfo& aInfo,
                                nsIFile* aComponentFile,
                                JS::MutableHandleObject aObject,
                                JS::MutableHandleScript aTableScript,
                                char** location,
                                bool aCatchException,
@@ -163,11 +166,13 @@ class mozJSComponentLoader : public mozi
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mInProgressImports;
 
     // A map of on-disk file locations which are loaded as modules to the
     // pre-resolved URIs they were loaded from. Used to prevent the same file
     // from being loaded separately, from multiple URLs.
     nsClassHashtable<nsCStringHashKey, nsCString> mLocations;
 
     bool mInitialized;
+    bool mShareLoaderGlobal;
+    JS::PersistentRooted<JSObject*> mLoaderGlobal;
 };
 
 #endif
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -162,31 +162,43 @@ PrepareScript(nsIURI* uri,
         return JS::Compile(cx, options, buf, len, script);
     }
     return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
 }
 
 static bool
 EvalScript(JSContext* cx,
            HandleObject targetObj,
+           HandleObject loadScope,
            MutableHandleValue retval,
            nsIURI* uri,
            bool startupCache,
            bool preloadCache,
            MutableHandleScript script)
 {
     if (JS_IsGlobalObject(targetObj)) {
         if (!JS::CloneAndExecuteScript(cx, script, retval)) {
             return false;
         }
     } else {
         JS::AutoObjectVector envChain(cx);
         if (!envChain.append(targetObj)) {
             return false;
         }
+        if (loadScope != targetObj &&
+            loadScope &&
+            !JS_IsGlobalObject(loadScope))
+        {
+            MOZ_DIAGNOSTIC_ASSERT(js::GetObjectCompartment(loadScope) ==
+                                  js::GetObjectCompartment(targetObj));
+
+            if (!envChain.append(loadScope)) {
+                return false;
+            }
+        }
         if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
             return false;
         }
     }
 
     JSAutoCompartment rac(cx, targetObj);
     if (!JS_WrapValue(cx, retval)) {
         return false;
@@ -236,59 +248,64 @@ class AsyncScriptLoader : public nsIIncr
 {
 public:
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
 
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader)
 
     AsyncScriptLoader(nsIChannel* aChannel, bool aWantReturnValue,
-                      JSObject* aTargetObj, const nsAString& aCharset,
-                      bool aCache, Promise* aPromise)
+                      JSObject* aTargetObj, JSObject* aLoadScope,
+                      const nsAString& aCharset, bool aCache,
+                      Promise* aPromise)
         : mChannel(aChannel)
         , mTargetObj(aTargetObj)
+        , mLoadScope(aLoadScope)
         , mPromise(aPromise)
         , mCharset(aCharset)
         , mWantReturnValue(aWantReturnValue)
         , mCache(aCache)
     {
         // Needed for the cycle collector to manage mTargetObj.
         mozilla::HoldJSObjects(this);
     }
 
 private:
     virtual ~AsyncScriptLoader() {
         mozilla::DropJSObjects(this);
     }
 
     RefPtr<nsIChannel> mChannel;
     Heap<JSObject*> mTargetObj;
+    Heap<JSObject*> mLoadScope;
     RefPtr<Promise> mPromise;
     nsString mCharset;
     bool mWantReturnValue;
     bool mCache;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader)
   NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
   tmp->mTargetObj = nullptr;
+  tmp->mLoadScope = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLoadScope)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader)
 
 class MOZ_STACK_CLASS AutoRejectPromise
 {
 public:
@@ -363,37 +380,39 @@ AsyncScriptLoader::OnStreamComplete(nsII
     }
 
     RootedScript script(cx);
     nsAutoCString spec;
     nsresult rv = uri->GetSpec(spec);
     NS_ENSURE_SUCCESS(rv, rv);
 
     RootedObject targetObj(cx, mTargetObj);
+    RootedObject loadScope(cx, mLoadScope);
 
     if (!PrepareScript(uri, cx, targetObj, spec.get(), mCharset,
                        reinterpret_cast<const char*>(aBuf), aLength,
                        mWantReturnValue, &script))
     {
         return NS_OK;
     }
 
     JS::Rooted<JS::Value> retval(cx);
-    if (EvalScript(cx, targetObj, &retval, uri, mCache,
+    if (EvalScript(cx, targetObj, loadScope, &retval, uri, mCache,
                    mCache && !mWantReturnValue,
                    &script)) {
         autoPromise.ResolvePromise(retval);
     }
 
     return NS_OK;
 }
 
 nsresult
 mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri,
                                       HandleObject targetObj,
+                                      HandleObject loadScope,
                                       const nsAString& charset,
                                       nsIIOService* serv,
                                       bool wantReturnValue,
                                       bool cache,
                                       MutableHandleValue retval)
 {
     nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(targetObj);
     ErrorResult result;
@@ -430,16 +449,17 @@ mozJSSubScriptLoader::ReadScriptAsync(ns
     }
 
     channel->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
 
     RefPtr<AsyncScriptLoader> loadObserver =
         new AsyncScriptLoader(channel,
                               wantReturnValue,
                               targetObj,
+                              loadScope,
                               charset,
                               cache,
                               promise);
 
     nsCOMPtr<nsIIncrementalStreamLoader> loader;
     rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -548,28 +568,32 @@ mozJSSubScriptLoader::LoadSubScriptWithO
 nsresult
 mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
                                                  LoadSubScriptOptions& options,
                                                  JSContext* cx,
                                                  MutableHandleValue retval)
 {
     nsresult rv = NS_OK;
     RootedObject targetObj(cx);
-    if (options.target) {
+    RootedObject loadScope(cx);
+    mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+    loader->FindTargetObject(cx, &loadScope);
+
+    if (options.target)
         targetObj = options.target;
-    } else {
-        mozJSComponentLoader* loader = mozJSComponentLoader::Get();
-        loader->FindTargetObject(cx, &targetObj);
-        MOZ_ASSERT(JS_IsGlobalObject(targetObj));
-    }
+    else
+        targetObj = loadScope;
 
     targetObj = JS_FindCompilationScope(cx, targetObj);
-    if (!targetObj)
+    if (!targetObj || !loadScope)
         return NS_ERROR_FAILURE;
 
+    if (js::GetObjectCompartment(loadScope) != js::GetObjectCompartment(targetObj))
+        loadScope = nullptr;
+
     /* load up the url.  From here on, failures are reflected as ``custom''
      * js exceptions */
     nsCOMPtr<nsIURI> uri;
     nsAutoCString uriStr;
     nsAutoCString scheme;
 
     // Figure out who's calling us
     JS::AutoFilename filename;
@@ -651,27 +675,27 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
         if (NS_FAILED(rv) || !script) {
             // ReadCachedScript may have set a pending exception.
             JS_ClearPendingException(cx);
         }
     }
 
     // If we are doing an async load, trigger it and bail out.
     if (!script && options.async) {
-        return ReadScriptAsync(uri, targetObj, options.charset, serv,
+        return ReadScriptAsync(uri, targetObj, loadScope, options.charset, serv,
                                options.wantReturnValue, !!cache, retval);
     }
 
     if (script) {
         // |script| came from the cache, so don't bother writing it
         // |back there.
         cache = nullptr;
     } else if (!ReadScript(uri, cx, targetObj, options.charset,
                         static_cast<const char*>(uriStr.get()), serv,
                         options.wantReturnValue, &script)) {
         return NS_OK;
     }
 
-    Unused << EvalScript(cx, targetObj, retval, uri, !!cache,
+    Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache,
                          !ignoreCache && !options.wantReturnValue,
                          &script);
     return NS_OK;
 }
--- a/js/xpconnect/loader/mozJSSubScriptLoader.h
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.h
@@ -36,16 +36,17 @@ private:
     bool ReadScript(nsIURI* uri, JSContext* cx, JS::HandleObject targetObj,
                     const nsAString& charset, const char* uriStr,
                     nsIIOService* serv,
                     bool wantReturnValue,
                     JS::MutableHandleScript script);
 
     nsresult ReadScriptAsync(nsIURI* uri,
                              JS::HandleObject targetObj,
+                             JS::HandleObject loadScope,
                              const nsAString& charset,
                              nsIIOService* serv,
                              bool wantReturnValue,
                              bool cache,
                              JS::MutableHandleValue retval);
 
     nsresult DoLoadSubScriptWithOptions(const nsAString& url,
                                         LoadSubScriptOptions& options,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5237,16 +5237,22 @@ pref("social.toast-notifications.enabled
 pref("dom.idle-observers-api.fuzz_time.disabled", true);
 
 // Minimum delay in milliseconds between network activity notifications (0 means
 // no notifications). The delay is the same for both download and upload, though
 // they are handled separately. This pref is only read once at startup:
 // a restart is required to enable a new value.
 pref("network.activity.blipIntervalMilliseconds", 0);
 
+// If true, reuse the same global for (almost) everything loaded by the component
+// loader (JS components, JSMs, etc). This saves memory, but makes it possible
+// for the scripts to interfere with each other.  A restart is required for this
+// to take effect.
+pref("jsloader.shareGlobal", false);
+
 // When we're asked to take a screenshot, don't wait more than 2000ms for the
 // event loop to become idle before actually taking the screenshot.
 pref("dom.browserElement.maxScreenshotDelayMS", 2000);
 
 // Whether we should show the placeholder when the element is focused but empty.
 pref("dom.placeholder.show_on_focus", true);
 
 // WebVR is enabled by default in beta and release for Windows and for all