Bug 1186409 - Use a single global for all JSMs. r=kmag
authorAndrew McCreight <continuation@gmail.com>
Tue, 18 Jul 2017 14:47:27 -0700
changeset 429298 37e7829372ebe046dfd7624ee8a74bc6f4cd5c2d
parent 429297 9109b1f49d233b53246887ae39c3fd598720d54b
child 429299 811d09140ee07f871cb05a478f2b3db7346beb80
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1186409
milestone57.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 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
@@ -5238,16 +5238,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