--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -132,17 +132,17 @@ ReportError(JSContext* cx, const char* o
msg.AppendLiteral(": ");
msg.Append(spec);
ReportError(cx, msg);
}
static bool
PrepareScript(nsIURI* uri,
JSContext* cx,
- HandleObject targetObj,
+ bool wantGlobalScript,
const char* uriStr,
const nsAString& charset,
const char* buf,
int64_t len,
bool wantReturnValue,
MutableHandleScript script)
{
JS::CompileOptions options(cx);
@@ -159,25 +159,25 @@ PrepareScript(nsIURI* uri,
JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
JS::SourceBufferHolder::GiveOwnership);
if (NS_FAILED(rv)) {
ReportError(cx, LOAD_ERROR_BADCHARSET, uri);
return false;
}
- if (JS_IsGlobalObject(targetObj)) {
+ if (wantGlobalScript) {
return JS::Compile(cx, options, srcBuf, script);
}
return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script);
}
// We only use lazy source when no special encoding is specified because
// the lazy source loader doesn't know the encoding.
options.setSourceIsLazy(true);
- if (JS_IsGlobalObject(targetObj)) {
+ if (wantGlobalScript) {
return JS::Compile(cx, options, buf, len, script);
}
return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
}
static bool
EvalScript(JSContext* cx,
HandleObject targetObj,
@@ -249,17 +249,19 @@ EvalScript(JSContext* cx,
// This has the side-effect of keeping the global that the script
// was compiled for alive, too.
//
// For most startups, the global in question will be the
// CompilationScope, since we pre-compile any scripts that were
// needed during the last startup in that scope. But for startups
// when a non-cached script is used (e.g., after add-on
// installation), this may be a Sandbox global, which may be
- // nuked but held alive by the JSScript.
+ // nuked but held alive by the JSScript. We can avoid this problem
+ // by using a different scope when compiling the script. See
+ // useCompilationScope in ReadScript().
//
// In general, this isn't a problem, since add-on Sandboxes which
// use the script preloader are not destroyed until add-on shutdown,
// and when add-ons are uninstalled or upgraded, the preloader cache
// is immediately flushed after shutdown. But it's possible to
// disable and reenable an add-on without uninstalling it, leading
// to cached scripts being held alive, and tied to nuked Sandbox
// globals. Given the unusual circumstances required to trigger
@@ -414,18 +416,18 @@ 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,
+ if (!PrepareScript(uri, cx, JS_IsGlobalObject(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, loadScope, &retval, uri, mCache,
mCache && !mWantReturnValue,
@@ -503,16 +505,17 @@ mozJSSubScriptLoader::ReadScriptAsync(ns
bool
mozJSSubScriptLoader::ReadScript(nsIURI* uri,
JSContext* cx,
HandleObject targetObj,
const nsAString& charset,
const char* uriStr,
nsIIOService* serv,
bool wantReturnValue,
+ bool useCompilationScope,
MutableHandleScript script)
{
script.set(nullptr);
// We create a channel and call SetContentType, to avoid expensive MIME type
// lookups (bug 632490).
nsCOMPtr<nsIChannel> chan;
nsCOMPtr<nsIInputStream> instream;
@@ -550,18 +553,32 @@ mozJSSubScriptLoader::ReadScript(nsIURI*
ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
return false;
}
nsCString buf;
rv = NS_ReadInputStreamToString(instream, buf, len);
NS_ENSURE_SUCCESS(rv, false);
- return PrepareScript(uri, cx, targetObj, uriStr, charset,
- buf.get(), len, wantReturnValue,
+ Maybe<JSAutoRealm> ar;
+
+ // Note that when using the ScriptPreloader cache with loadSubScript, there
+ // will be a side-effect of keeping the global that the script was compiled
+ // for alive. See note above in EvalScript().
+ //
+ // This will compile the script in XPConnect compilation scope. When the
+ // script is evaluated, it will be cloned into the target scope to be
+ // executed, avoiding leaks on the first session when we don't have a
+ // startup cache.
+ if (useCompilationScope) {
+ ar.emplace(cx, xpc::CompilationScope());
+ }
+
+ return PrepareScript(uri, cx, JS_IsGlobalObject(targetObj),
+ uriStr, charset, buf.get(), len, wantReturnValue,
script);
}
NS_IMETHODIMP
mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
HandleValue target,
const nsAString& charset,
JSContext* cx,
@@ -687,33 +704,35 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
tmp.AppendLiteral(" -> ");
tmp.Append(uriStr);
uriStr = tmp;
}
// Suppress caching if we're compiling as content or if we're loading a
// blob: URI.
+ bool useCompilationScope = false;
auto* principal = BasePrincipal::Cast(GetObjectPrincipal(targetObj));
bool isSystem = principal->Is<SystemPrincipal>();
if (!isSystem && principal->Is<ContentPrincipal>()) {
auto* content = principal->As<ContentPrincipal>();
nsAutoCString scheme;
content->mCodebase->GetScheme(scheme);
// We want to enable caching for scripts with Activity Stream's
// codebase URLs.
if (scheme.EqualsLiteral("about")) {
nsAutoCString filePath;
content->mCodebase->GetFilePath(filePath);
- isSystem = filePath.EqualsLiteral("home") ||
- filePath.EqualsLiteral("newtab") ||
- filePath.EqualsLiteral("welcome");
+ useCompilationScope = filePath.EqualsLiteral("home") ||
+ filePath.EqualsLiteral("newtab") ||
+ filePath.EqualsLiteral("welcome");
+ isSystem = true;
}
}
bool ignoreCache = options.ignoreCache || !isSystem || scheme.EqualsLiteral("blob");
StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton();
nsAutoCString cachePath;
SubscriptCachePath(cx, uri, targetObj, cachePath);
@@ -737,17 +756,17 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
}
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)) {
+ options.wantReturnValue, useCompilationScope, &script)) {
return NS_OK;
}
Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache,
!ignoreCache && !options.wantReturnValue,
&script);
return NS_OK;
}